[fft] Simplify series operand access: direct indexing, span conversion, zero-pad-tolerant extend_to - #79
Conversation
…n, zero-pad-tolerant extend_to Add operator[] and implicit std::span<const T> conversion to the series-like contract so operands borrow straight into the engine primitives; drop the scattered underlying()/.coeffs() at the kth_term call sites. Make extend_to ignore trailing zero coefficients (via fft::trim_zeros), so zero-padded buffers are accepted and the "extend_to before padding" ordering constraint in kth_term_of_rational_function goes away. Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
GCC Code Coverage Report📂 Overall coverage
|
| // Seed the loop transforms from any whole caches; the buffers below hold the | ||
| // current p, q (zero-padded, which extend_to ignores). |
There was a problem hiding this comment.
🟡 New comment packs two clauses onto one line, against the repository's comment formatting rule
The newly added comment above the transform seeding puts two clauses on the same line and wraps mid-clause (// Seed the loop transforms from any whole caches; the buffers below hold the at src/fft/series.hpp:343-344), so it does not follow the repository's documented comment layout.
Impact: Comment formatting diverges from the project's stated style, making the comment harder to edit line-by-line.
Rule in AGENTS.md: one sentence/clause per comment line
AGENTS.md ("Comment style") requires: "Start each sentence/clause on a new line — this makes comments easier to read and edit in a line-based editor. Don't rewrap them into paragraphs." The added comment instead joins two independent clauses with ; on the first line and continues the second clause onto the next line.
| // Seed the loop transforms from any whole caches; the buffers below hold the | |
| // current p, q (zero-padded, which extend_to ignores). | |
| // Seed the loop transforms from any whole caches; | |
| // the buffers below hold the current p, q (zero-padded, which extend_to ignores). |
Was this helpful? React with 👍 or 👎 to provide feedback.
Each doubling step reads only coeffs.first(2 * t.size()): by the prefix contract, coefficients past twice the existing transform's size must be zero, so zero-padded buffers work without inspecting values. Removes fft::trim_zeros and the floating-point equality workaround it needed. Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
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>
Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
…s via a span constructor Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
…poses prod() nodes Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
Co-Authored-By: Andrew He <he.andrew.mail@gmail.com>
Summary
Removes the recurring rough edges in the
series::layer (now includes the operator normalization originally split out as #80):underlying()is gone. Thelikecontract is now: direct indexing plus two span borrows — into the engine primitives and into the series layer's own exactness-tagged span:Each wrapper provides
operator span<E, exact_v>(vecalready had it;cached/cached_span/prefix_cachedgain it), andvecgainsexplicit vec(span<E, exact_>)so materializing an owned copy of any series-like is justexact<E> r(c).std::span<const T>borrows go through std::span's range constructor (deliberately no conversion operator onseries::span— offering both paths makes every implicit conversion ambiguous under-Wconversion).cached/prefix_cachedkeep a non-contractuncached()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_;.The
sz(coeffs)zero-padding footgun.extend_to's doubling loop clamps each step's read to the coefficients that fit:By the prefix contract, a size-
stransform can only exist if all nonzero coefficients fit in2s— so anything past the clamp is necessarily zero and dropping it is exact (no value inspection, no float-equality trimming). The top-levelsz(coeffs) <= 2 * massert is the one conservative check kept. The old "mustextend_tobefore padding" ordering constraint inkth_term_of_rational_functionis gone, and a cache seeded from short coeffs can later be grown with a longer zero-padded buffer of the same sequence.Series operators normalized onto
cached_span(folded from [fft] Normalize series operators onto cached_span operands #80).detail::whole_operandis the whole-span counterpart toproduct_operand: anylikeoperand becomes acached_span(borrowed coefficients + the cache serving them):square/multiply_add2/middle_productandkth_term_of_linear_recurrencerun on that form (auto av = detail::whole_operand(a, ta_);thenav, av.cache()straight into thefft::entry points);operator*'s call sites,operator+/operator-,ps_inv,ps_log's assert, andcached::operator==drop their coefficient plumbing. Internals uniformly useseries::span, notstd::span, for coefficient views.No algorithmic or semantic changes: cache selection, precisions, and transform sizes are identical throughout.
Testing
extend_to/transform/finish/downsample/negate_argagainst the clamped-prefix contract; series operator cache pairings;poly.hpp/online.hppcall sites) — one issue found and fixed: the dual span-conversion ambiguity above (it produced-Wconversionwarnings at every implicit borrow).underlying()removal.g++andg++-sanitizerenvironments (convolutions incl. crt/split, all FPS ops, composition, multipoint/interpolation, characteristic polynomial,kth_term_of_linearly_recurrent_sequence20/20); kth_term/multipoint/interpolation re-verified after the contract change.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.extend_toverified 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