Skip to content

[fft] Normalize series operators onto cached_span operands - #80

Merged
devin-ai-integration[bot] merged 1 commit into
devin/1785327766-series-span-cleanupfrom
devin/1785329305-cached-span-operands
Jul 29, 2026
Merged

[fft] Normalize series operators onto cached_span operands#80
devin-ai-integration[bot] merged 1 commit into
devin/1785327766-series-span-cleanupfrom
devin/1785329305-cached-span-operands

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #79. Makes cached_span the canonical operand form for the series operators: one normalization gate turns any like value into a borrowed span paired with the cache serving it, and the operator bodies stop hand-pairing underlying() spans with whole_cache_or.

// the whole-span counterpart to detail::product_operand
template <like S>
cached_span<E, S::exact_v> whole_operand(const S& s, fft::transformed<E>& tmp) {
	return {span<E, S::exact_v>(s.underlying()), whole_cache_or(s, tmp)};
}
  • square / multiply_add2 / middle_product now do auto av = detail::whole_operand(a, ta_); and pass av, av.cache() straight into the fft:: entry points (the operand converts to std::span<const T> implicitly), instead of the previous av.coeffs(), detail::whole_cache_or(a, ta_) pairs. operator* already normalized via product_operand; its call sites drop .underlying().coeffs() the same way.
  • kth_term_of_linear_recurrence replaces its ad-hoc cached_span<E, true>{q.underlying(), detail::whole_cache_or(q, tq)} with detail::whole_operand(q, tq).
  • Incidental de-underlying()-ing now that the contract guarantees indexing/span conversion: operator+/operator- index the operands directly, ps_inv works on std::span<const T> directly, ps_log's assert reads a[0], cached::operator== compares against std::span<const T>(b).

No semantic changes: cache selection, precisions, and transform sizes are identical throughout.

Testing

  • ./build/tests "[fft]" → 26727 assertions, 85 cases, all pass (ntt / goldilocks / real / split / crt).
  • Library Checker kth_term_of_linearly_recurrent_sequence: 20/20 AC under both g++ and g++-sanitizer environments (run on this stack).

Link to Devin session: https://app.devin.ai/sessions/66a2d877f2354e30b3fdfa0c15061db2
Requested by: @ecnerwala


Open in Devin Review (Beta)

Add detail::whole_operand, the whole-span counterpart to product_operand:
any series-like operand becomes a cached_span (borrowed coefficients + the
cache serving them). square/multiply_add2/middle_product and
kth_term_of_linear_recurrence run on that form instead of hand-pairing
underlying() spans with whole_cache_or, and call sites lean on the implicit
std::span conversion instead of .coeffs().

Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
@ecnerwala ecnerwala self-assigned this Jul 29, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@beta-devin-ai-integration beta-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review (Beta)

@github-actions

Copy link
Copy Markdown

GCC Code Coverage Report

📂 Overall coverage

Metric Coverage
Lines 🟡 7888/9623 (82.0%)
Functions 🟢 1048/1152 (91.0%)
Branches 🟡 6276/7997 (78.5%)

@devin-ai-integration
devin-ai-integration Bot merged commit bf759ac into devin/1785327766-series-span-cleanup Jul 29, 2026
17 checks passed
@devin-ai-integration
devin-ai-integration Bot deleted the devin/1785329305-cached-span-operands branch July 29, 2026 19:59
ecnerwala added a commit that referenced this pull request Jul 31, 2026
…n, zero-pad-tolerant extend_to (#79)

## Summary

Removes the recurring rough edges in the `series::` layer (now includes
the operator normalization originally split out as #80):

1. **`underlying()` is gone.** The `like` contract is now: direct
indexing plus two span borrows — into the engine primitives and into the
series layer's own exactness-tagged span:
   ```cpp
   concept like = ... requires(const S& s, int i) {
       { s[i] } -> convertible_to<const value_type&>;
{ std::span<const value_type>(s) }; // borrows into engine ops
requires convertible_to<const S&, span<E, S::exact_v>>; // borrows into
the series layer
   };
   ```
Each wrapper provides `operator span<E, exact_v>` (`vec` already had it;
`cached`/`cached_span`/`prefix_cached` gain it), and `vec` gains
`explicit vec(span<E, exact_>)` so materializing an owned copy of any
series-like is just `exact<E> r(c)`. `std::span<const T>` borrows go
through std::span's range constructor (deliberately no conversion
operator on `series::span` — offering both paths makes every implicit
conversion ambiguous under `-Wconversion`). `cached`/`prefix_cached`
keep a non-contract `uncached()` unwrapper for the by-reference case
(`subproduct_tree::rev_prod`). At the sites: `q.underlying()[0]` →
`q[0]`, `span<E, false> a = a_.underlying();` → `span<E, false> a =
a_;`.

2. **The `sz(coeffs)` zero-padding footgun.** `extend_to`'s doubling
loop clamps each step's read to the coefficients that fit:
   ```cpp
   while (t.size() < m) {
       int s = t.size();
       t.v.resize(2 * s);
       core::extend(t.v, coeffs.first(min(sz(coeffs), 2 * s)));
   }
   ```
By the prefix contract, a size-`s` transform can only exist if all
nonzero coefficients fit in `2s` — so anything past the clamp is
necessarily zero and dropping it is exact (no value inspection, no
float-equality trimming). The top-level `sz(coeffs) <= 2 * m` assert is
the one conservative check kept. The old *"must `extend_to` before
padding"* ordering constraint in `kth_term_of_rational_function` is
gone, and a cache seeded from short coeffs can later be grown with a
longer zero-padded buffer of the same sequence.

3. **Series operators normalized onto `cached_span`** (folded from #80).
`detail::whole_operand` is the whole-span counterpart to
`product_operand`: any `like` operand becomes a `cached_span` (borrowed
coefficients + the cache serving them):
   ```cpp
   template <like S>
cached_span<E, S::exact_v> whole_operand(const S& s,
fft::transformed<E>& tmp) {
       return {s, whole_cache_or(s, tmp)};
   }
   ```
`square`/`multiply_add2`/`middle_product` and
`kth_term_of_linear_recurrence` run on that form (`auto av =
detail::whole_operand(a, ta_);` then `av, av.cache()` straight into the
`fft::` entry points); `operator*`'s call sites,
`operator+`/`operator-`, `ps_inv`, `ps_log`'s assert, and
`cached::operator==` drop their coefficient plumbing. Internals
uniformly use `series::span`, not `std::span`, for coefficient views.

No algorithmic or semantic changes: cache selection, precisions, and
transform sizes are identical throughout.

## Testing

- Full audit of all fft code (all engines'
`extend_to`/`transform`/`finish`/`downsample`/`negate_arg` against the
clamped-prefix contract; series operator cache pairings;
`poly.hpp`/`online.hpp` call sites) — one issue found and fixed: the
dual span-conversion ambiguity above (it produced `-Wconversion`
warnings at every implicit borrow).
- Full unit suite green (2822854 assertions, 106 cases, all engines)
after the `underlying()` removal.
- Full Library Checker verification: every fft-related problem AC under
both `g++` and `g++-sanitizer` environments (convolutions incl.
crt/split, all FPS ops, composition, multipoint/interpolation,
characteristic polynomial, `kth_term_of_linearly_recurrent_sequence`
20/20); kth_term/multipoint/interpolation re-verified after the contract
change.
- Padded-extend harness: seed a transform from unpadded coeffs, extend
with a longer zero-padded buffer, compare `finish(sq(...))` against a
fresh transform — exact agreement (err = 0) across ntt/split/real for
lengths {1,2,3,5} × seeds {2,4} × targets {8,16} × paddings.
- Real-engine length-1 `extend_to` verified bit-identical to fresh
transforms across seed/target sizes.

Stacked follow-up: #81 (storage-separation prototype).

Link to Devin session:
https://app.devin.ai/sessions/66a2d877f2354e30b3fdfa0c15061db2
Requested by: @ecnerwala
<!-- devin-review-badge-beta-begin -->

---

<a href="https://app.beta.devin.ai/review/ecnerwala/cp-book/pull/79"
target="_blank">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-beta-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-beta-light.svg?v=1"
alt="Open in Devin Review (Beta)">
  </picture>
</a>
<!-- devin-review-badge-beta-end -->

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Andrew He <he.andrew.mail@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant