# EGPTFFT — forward / inverse round-trip tests `EGPTFFT` is a pedagogical wrapper that makes the polynomial-transform interpretation explicit. Two canonical methods are exposed: | Method | What it computes | |--------|-----------------| | `EGPTFFT.forward(coeffs, N)` | Evaluates the polynomial `P(x) = c₀ + c₁x + …` at the N uniform rational nodes `k/N` (k = 0 … N−1) via Horner's method. Each sample is an exact `EGPTReal` rational. | | `EGPTFFT.inverse(values, N)` | Recovers the original coefficient vector from those N samples via Newton divided-differences — bit-exact inverse. | The bijection is: ``` coeffs ─forward─► values at k/N values ─inverse─► coeffs (exactly) ``` No floating-point, no approximate inversion — the round trip holds to the last bit because both operations stay in Shannon/PPF (rational) space throughout. This notebook runs the three canonical tests from `EGPTFFTTest.js`: 1. Integer-coefficient round trip (N = 4) 2. Fractional-coefficient round trip (N = 5) 3. `forward` output agrees with per-point Horner evaluation via `EGPTPolynomial.evaluateAt` ## Setup — helpers and test harness Inline helpers for constructing `EGPTReal` values from integers and fractions, plus a minimal test harness (`test` / `assert` / `assertEqPoly`) that mirrors the original file. The harness accumulates pass/fail counts; phase cells consume it via `in="suite"`. const { math } = caps; const { EGPTReal, EGPTPolynomial } = math; const ONE = EGPTReal.fromBigInt(1n); const intN = (n) => EGPTReal.fromBigInt(BigInt(n)); const frac = (n, d) => EGPTReal.fromRational(BigInt(n), BigInt(d)); let passed = 0, failed = 0; const failures = []; function test(name, fn) { try { fn(); console.log(' ok — ' + name); passed++; } catch (err) { console.log(' FAIL — ' + name + '\n ' + err.message); failed++; failures.push(name); } } function assert(cond, msg) { if (!cond) throw new Error(msg || 'assertion failed'); } function assertEqPoly(a, b, label) { if (a.length !== b.length) throw new Error(label + ': length ' + a.length + ' vs ' + b.length); for (let i = 0; i < a.length; i++) { if (!a[i].equals(b[i])) { throw new Error( label + ': index ' + i + ': ' + a[i].breakSymbolicToString() + ' vs ' + b[i].breakSymbolicToString() ); } } } return { suite: { test, assert, assertEqPoly, intN, frac, ONE, getResults: () => ({ passed, failed, failures }) } }; ## Round-trip tests ### Test 1 — integer coefficients, N = 4 Coefficients `[3, 1, 4, 1]` (the first four digits of π). After `forward` the four samples are rational values at `0/4, 1/4, 2/4, 3/4`; `inverse` must return exactly `[3, 1, 4, 1]`. ### Test 2 — fractional coefficients, N = 5 Coefficients `[1/3, −7/6, 1, 2/5, −1/2]`. Fractions stress the rational arithmetic path; the round trip must still be bit-exact. const { math } = caps; const { EGPTFFT } = math; const { test, assertEqPoly, intN, frac, ONE } = inputs.suite; console.log('\n[EGPTFFT] forward / inverse round trip'); test('integer coeffs round trip exactly at N=4', () => { const c = [intN(3), intN(1), intN(4), intN(1)]; const values = EGPTFFT.forward(c); const recovered = EGPTFFT.inverse(values); assertEqPoly(recovered, c, 'integer N=4'); }); test('fractional coeffs round trip exactly at N=5', () => { const c = [frac(1, 3), frac(-7, 6), ONE, frac(2, 5), frac(-1, 2)]; const values = EGPTFFT.forward(c); const recovered = EGPTFFT.inverse(values); assertEqPoly(recovered, c, 'fractional N=5'); }); const snap = inputs.suite.getResults(); return { rt: snap }; ## Horner agreement test ### Test 3 — `forward(c)` agrees with per-point `EGPTPolynomial.evaluateAt` `EGPTFFT.forward(c, N)` must return the same values as evaluating `c` at each node `k/N` individually via `EGPTPolynomial.evaluateAt`. This is T1 from the canonical chain: the transform IS Horner evaluation at uniform nodes. **Note on error-message construction:** the message strings are built lazily (inside the catch branch only) to avoid calling `.breakSymbolicToString()` on values that are equal — calling it eagerly would throw `DiscontinuumViolationError` for any value that actually passes the `.equals()` check. const { math } = caps; const { EGPTFFT, EGPTPolynomial, EGPTReal } = math; const { test, assert, intN, frac } = inputs.suite; console.log('\n[EGPTFFT] forward agrees with per-point Horner evaluation'); test('forward(c) agrees with per-point Horner evaluation', () => { const c = [intN(5), intN(-2), intN(7), intN(-1)]; const N = c.length; const values = EGPTFFT.forward(c, N); for (let k = 0; k < N; k++) { const x = EGPTReal.fromRational(BigInt(k), BigInt(N)); const expected = EGPTPolynomial.evaluateAt(c, x); // Build the diagnostic string ONLY when the check fails (lazy construction) // to avoid DiscontinuumViolationError on equal values. if (!values[k].equals(expected)) { throw new Error( 'bin ' + k + ': forward ' + values[k].breakSymbolicToString() + ' vs Horner ' + expected.breakSymbolicToString() ); } } }); const snap = inputs.suite.getResults(); return { ha: snap }; ## Summary Final tally across all three tests. const { passed, failed, failures } = inputs.ha; const total = passed + failed; console.log(''); console.log('='.repeat(60)); console.log('Results: ' + passed + ' passed, ' + failed + ' failed (total: ' + total + ')'); if (failures.length > 0) { console.log('Failed tests:'); failures.forEach((f) => console.log(' - ' + f)); } console.log('='.repeat(60)); if (failed > 0) throw new Error('[EGPTFFTTest] ' + failed + ' test(s) failed');