@descix/egpt-math-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -0
- package/dist/egpt-math-sdk.browser.js +5 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +985 -0
- package/dist/notebooks/arithmetic-basics.html +166 -0
- package/dist/notebooks/complex-twiddle.html +148 -0
- package/dist/notebooks/double-slit-rosetta-qft.html +154 -0
- package/dist/notebooks/egpt-fft-test.html +202 -0
- package/dist/notebooks/egpt-matrix-test.html +241 -0
- package/dist/notebooks/egpt-polynomial-test.html +703 -0
- package/dist/notebooks/egpt-prime-composite-test.html +561 -0
- package/dist/notebooks/egpt-test-suite.html +1387 -0
- package/dist/notebooks/egpt-topology-test.html +410 -0
- package/dist/notebooks/factorization-basics.html +131 -0
- package/dist/notebooks/fft.html +155 -0
- package/dist/notebooks/frqtl-qft-benchmark.html +207 -0
- package/dist/notebooks/gallery-atomic-model.html +51 -0
- package/dist/notebooks/gallery-big-bang.html +62 -0
- package/dist/notebooks/gallery-blackbody.html +52 -0
- package/dist/notebooks/gallery-circuit-sat.html +104 -0
- package/dist/notebooks/gallery-collision-matrix-3d.html +51 -0
- package/dist/notebooks/gallery-double-slit.html +53 -0
- package/dist/notebooks/gallery-gun-chamber.html +62 -0
- package/dist/notebooks/gallery-particle-walk-3d.html +53 -0
- package/dist/notebooks/gallery-wave-interference.html +52 -0
- package/dist/notebooks/getting-started-hello-frqtl.html +53 -0
- package/dist/notebooks/iso-canonical-chain.html +203 -0
- package/dist/notebooks/iso-entropy-log.html +127 -0
- package/dist/notebooks/iso-fft-polynomial.html +136 -0
- package/dist/notebooks/iso-matrix-four-views.html +131 -0
- package/dist/notebooks/iso-number-theory-four-views.html +99 -0
- package/dist/notebooks/iso-pi-log-fraction-loop.html +140 -0
- package/dist/notebooks/matrix.html +202 -0
- package/dist/notebooks/notebooks.json +240 -0
- package/dist/notebooks/polynomials.html +201 -0
- package/dist/notebooks/prime-composite.html +268 -0
- package/dist/notebooks/rational-core.html +179 -0
- package/dist/notebooks/reference-egpt-fft-test.html +187 -0
- package/dist/notebooks/reference-order-finder-test.html +196 -0
- package/dist/notebooks/reference-prime-atom-polynomial-test.html +542 -0
- package/dist/notebooks/reference-solve-polynomial-system-test.html +471 -0
- package/dist/notebooks/scaled-vectors.html +112 -0
- package/dist/notebooks/stats.html +206 -0
- package/dist/notebooks/transcendentals.html +195 -0
- package/dist/notebooks/view-vendored-d3.html +78 -0
- package/dist/vendor/d3-array/src/array.js +4 -0
- package/dist/vendor/d3-array/src/ascending.js +3 -0
- package/dist/vendor/d3-array/src/bin.js +125 -0
- package/dist/vendor/d3-array/src/bisect.js +9 -0
- package/dist/vendor/d3-array/src/bisector.js +56 -0
- package/dist/vendor/d3-array/src/blur.js +115 -0
- package/dist/vendor/d3-array/src/constant.js +3 -0
- package/dist/vendor/d3-array/src/count.js +18 -0
- package/dist/vendor/d3-array/src/cross.js +33 -0
- package/dist/vendor/d3-array/src/cumsum.js +6 -0
- package/dist/vendor/d3-array/src/descending.js +7 -0
- package/dist/vendor/d3-array/src/deviation.js +6 -0
- package/dist/vendor/d3-array/src/difference.js +11 -0
- package/dist/vendor/d3-array/src/disjoint.js +15 -0
- package/dist/vendor/d3-array/src/every.js +10 -0
- package/dist/vendor/d3-array/src/extent.js +29 -0
- package/dist/vendor/d3-array/src/filter.js +11 -0
- package/dist/vendor/d3-array/src/fsum.js +69 -0
- package/dist/vendor/d3-array/src/greatest.js +29 -0
- package/dist/vendor/d3-array/src/greatestIndex.js +19 -0
- package/dist/vendor/d3-array/src/group.js +65 -0
- package/dist/vendor/d3-array/src/groupSort.js +10 -0
- package/dist/vendor/d3-array/src/identity.js +3 -0
- package/dist/vendor/d3-array/src/index.js +57 -0
- package/dist/vendor/d3-array/src/intersection.js +19 -0
- package/dist/vendor/d3-array/src/least.js +29 -0
- package/dist/vendor/d3-array/src/leastIndex.js +19 -0
- package/dist/vendor/d3-array/src/map.js +5 -0
- package/dist/vendor/d3-array/src/max.js +20 -0
- package/dist/vendor/d3-array/src/maxIndex.js +22 -0
- package/dist/vendor/d3-array/src/mean.js +19 -0
- package/dist/vendor/d3-array/src/median.js +9 -0
- package/dist/vendor/d3-array/src/merge.js +9 -0
- package/dist/vendor/d3-array/src/min.js +20 -0
- package/dist/vendor/d3-array/src/minIndex.js +22 -0
- package/dist/vendor/d3-array/src/mode.js +28 -0
- package/dist/vendor/d3-array/src/nice.js +18 -0
- package/dist/vendor/d3-array/src/number.js +20 -0
- package/dist/vendor/d3-array/src/pairs.js +15 -0
- package/dist/vendor/d3-array/src/permute.js +3 -0
- package/dist/vendor/d3-array/src/quantile.js +47 -0
- package/dist/vendor/d3-array/src/quickselect.js +53 -0
- package/dist/vendor/d3-array/src/range.js +13 -0
- package/dist/vendor/d3-array/src/rank.js +24 -0
- package/dist/vendor/d3-array/src/reduce.js +14 -0
- package/dist/vendor/d3-array/src/reverse.js +4 -0
- package/dist/vendor/d3-array/src/scan.js +6 -0
- package/dist/vendor/d3-array/src/shuffle.js +13 -0
- package/dist/vendor/d3-array/src/some.js +10 -0
- package/dist/vendor/d3-array/src/sort.js +39 -0
- package/dist/vendor/d3-array/src/subset.js +5 -0
- package/dist/vendor/d3-array/src/sum.js +18 -0
- package/dist/vendor/d3-array/src/superset.js +19 -0
- package/dist/vendor/d3-array/src/threshold/freedmanDiaconis.js +7 -0
- package/dist/vendor/d3-array/src/threshold/scott.js +7 -0
- package/dist/vendor/d3-array/src/threshold/sturges.js +5 -0
- package/dist/vendor/d3-array/src/ticks.js +55 -0
- package/dist/vendor/d3-array/src/transpose.js +15 -0
- package/dist/vendor/d3-array/src/union.js +11 -0
- package/dist/vendor/d3-array/src/variance.js +25 -0
- package/dist/vendor/d3-array/src/zip.js +5 -0
- package/dist/vendor/internmap/src/index.js +61 -0
- package/dist/vendor/manifest.json +11 -0
- package/dist/wasm/egpt_math_sdk_wasm.d.ts +214 -0
- package/dist/wasm/egpt_math_sdk_wasm.js +1120 -0
- package/dist/wasm/egpt_math_sdk_wasm_bg.wasm +0 -0
- package/dist/wasm/egpt_math_sdk_wasm_bg.wasm.d.ts +66 -0
- package/dist/wasm/manifest.json +5 -0
- package/package.json +53 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<frqtl-notebook version="1" title="EGPT Arithmetic Basics — Exact Rational Operations on EGPTReal">
|
|
2
|
+
|
|
3
|
+
<cell type="markdown">
|
|
4
|
+
# EGPT Arithmetic Basics
|
|
5
|
+
|
|
6
|
+
The EGPT math library operates on **exact rational numbers** — no floating-point rounding, no approximation. Every value is a `EGPTReal`, which internally stores a numerator and denominator as BigInts and keeps the fraction in fully reduced form at all times.
|
|
7
|
+
|
|
8
|
+
This notebook walks through the four fundamental arithmetic operations (`add`, `subtract`, `multiply`, `normalDivide`) on two rational constants:
|
|
9
|
+
|
|
10
|
+
- `a = 3/2`
|
|
11
|
+
- `b = 5/4`
|
|
12
|
+
|
|
13
|
+
Every computation below goes through the `math` builtin — the live FRQTL math SDK the IDE resolved. The active math backend is reported by the SDK itself, never asserted.
|
|
14
|
+
</cell>
|
|
15
|
+
|
|
16
|
+
<cell type="js" lane="math" id="backend">
|
|
17
|
+
// Confirm the active backend before any arithmetic.
|
|
18
|
+
// activeMathBackend is a DERIVED field from MathBackendRegistry.active().id.
|
|
19
|
+
// It reads 'js-reference' by default and 'wasm-math' when the shim selects it.
|
|
20
|
+
const { math, display } = caps;
|
|
21
|
+
display(`Active math backend (derived from SDK): ${math.activeMathBackend}`);
|
|
22
|
+
</cell>
|
|
23
|
+
|
|
24
|
+
<cell type="markdown">
|
|
25
|
+
## 1. Constructing exact rationals
|
|
26
|
+
|
|
27
|
+
`EGPTReal.fromRational(numerator, denominator)` accepts BigInt arguments and returns an `EGPTReal` whose internal representation is the fully reduced fraction `numerator / denominator`.
|
|
28
|
+
|
|
29
|
+
Calling `.toMathString()` on an `EGPTReal` returns a human-readable string like `"3/2"` for a rational value, or a log-space representation for values constructed in information space.
|
|
30
|
+
</cell>
|
|
31
|
+
|
|
32
|
+
<cell type="js" lane="math" id="construct" out="a, b">
|
|
33
|
+
// Construct two exact rational values.
|
|
34
|
+
// fromRational(numerator: BigInt, denominator: BigInt) → EGPTReal
|
|
35
|
+
const { math } = caps;
|
|
36
|
+
const { EGPTReal } = math;
|
|
37
|
+
|
|
38
|
+
const a = EGPTReal.fromRational(3n, 2n); // 3/2 = 1.5 exactly
|
|
39
|
+
const b = EGPTReal.fromRational(5n, 4n); // 5/4 = 1.25 exactly
|
|
40
|
+
|
|
41
|
+
console.log('a =', a.toMathString());
|
|
42
|
+
console.log('b =', b.toMathString());
|
|
43
|
+
|
|
44
|
+
return { a, b };
|
|
45
|
+
</cell>
|
|
46
|
+
|
|
47
|
+
<cell type="markdown">
|
|
48
|
+
## 2. Addition
|
|
49
|
+
|
|
50
|
+
`EGPTMath.add(a, b)` returns a new `EGPTReal` equal to `a + b`, computed by finding the common denominator and summing numerators — all in exact integer arithmetic.
|
|
51
|
+
|
|
52
|
+
Expected: `3/2 + 5/4 = 6/4 + 5/4 = 11/4`
|
|
53
|
+
</cell>
|
|
54
|
+
|
|
55
|
+
<cell type="js" lane="math" id="addition" in="a, b" out="sum">
|
|
56
|
+
const { math } = caps;
|
|
57
|
+
const { EGPTMath } = math;
|
|
58
|
+
const { a, b } = inputs; // consumed cross-cell bindings arrive on `inputs`, never as bare globals
|
|
59
|
+
|
|
60
|
+
const sum = EGPTMath.add(a, b);
|
|
61
|
+
|
|
62
|
+
console.log(`${a.toMathString()} + ${b.toMathString()} = ${sum.toMathString()}`);
|
|
63
|
+
|
|
64
|
+
return { sum };
|
|
65
|
+
</cell>
|
|
66
|
+
|
|
67
|
+
<cell type="markdown">
|
|
68
|
+
## 3. Subtraction
|
|
69
|
+
|
|
70
|
+
`EGPTMath.subtract(a, b)` returns `a - b` with the same exact integer discipline.
|
|
71
|
+
|
|
72
|
+
Expected: `3/2 - 5/4 = 6/4 - 5/4 = 1/4`
|
|
73
|
+
</cell>
|
|
74
|
+
|
|
75
|
+
<cell type="js" lane="math" id="subtraction" in="a, b" out="diff">
|
|
76
|
+
const { math } = caps;
|
|
77
|
+
const { EGPTMath } = math;
|
|
78
|
+
const { a, b } = inputs; // consumed cross-cell bindings arrive on `inputs`, never as bare globals
|
|
79
|
+
|
|
80
|
+
const diff = EGPTMath.subtract(a, b);
|
|
81
|
+
|
|
82
|
+
console.log(`${a.toMathString()} - ${b.toMathString()} = ${diff.toMathString()}`);
|
|
83
|
+
|
|
84
|
+
return { diff };
|
|
85
|
+
</cell>
|
|
86
|
+
|
|
87
|
+
<cell type="markdown">
|
|
88
|
+
## 4. Multiplication
|
|
89
|
+
|
|
90
|
+
`EGPTMath.multiply(a, b)` returns `a × b`. For rational numbers this is simply `(num_a × num_b) / (den_a × den_b)`, then fully reduced.
|
|
91
|
+
|
|
92
|
+
Expected: `3/2 × 5/4 = 15/8`
|
|
93
|
+
</cell>
|
|
94
|
+
|
|
95
|
+
<cell type="js" lane="math" id="multiplication" in="a, b" out="product">
|
|
96
|
+
const { math } = caps;
|
|
97
|
+
const { EGPTMath } = math;
|
|
98
|
+
const { a, b } = inputs; // consumed cross-cell bindings arrive on `inputs`, never as bare globals
|
|
99
|
+
|
|
100
|
+
const product = EGPTMath.multiply(a, b);
|
|
101
|
+
|
|
102
|
+
console.log(`${a.toMathString()} × ${b.toMathString()} = ${product.toMathString()}`);
|
|
103
|
+
|
|
104
|
+
return { product };
|
|
105
|
+
</cell>
|
|
106
|
+
|
|
107
|
+
<cell type="markdown">
|
|
108
|
+
## 5. Division
|
|
109
|
+
|
|
110
|
+
`EGPTMath.normalDivide(a, b)` returns `a / b` as an exact rational — it inverts `b` and multiplies, staying entirely in integer arithmetic.
|
|
111
|
+
|
|
112
|
+
Expected: `(3/2) ÷ (5/4) = (3/2) × (4/5) = 12/10 = 6/5`
|
|
113
|
+
</cell>
|
|
114
|
+
|
|
115
|
+
<cell type="js" lane="math" id="division" in="a, b" out="quotient">
|
|
116
|
+
const { math } = caps;
|
|
117
|
+
const { EGPTMath } = math;
|
|
118
|
+
const { a, b } = inputs; // consumed cross-cell bindings arrive on `inputs`, never as bare globals
|
|
119
|
+
|
|
120
|
+
const quotient = EGPTMath.normalDivide(a, b);
|
|
121
|
+
|
|
122
|
+
console.log(`${a.toMathString()} ÷ ${b.toMathString()} = ${quotient.toMathString()}`);
|
|
123
|
+
|
|
124
|
+
return { quotient };
|
|
125
|
+
</cell>
|
|
126
|
+
|
|
127
|
+
<cell type="markdown">
|
|
128
|
+
## 6. Summary — all four operations together
|
|
129
|
+
|
|
130
|
+
The final cell mirrors the original demo's combined `checks` array, verifying that all four results are what exact rational arithmetic requires. No floating-point approximation is involved at any step.
|
|
131
|
+
</cell>
|
|
132
|
+
|
|
133
|
+
<cell type="js" lane="math" id="summary" in="a, b, sum, diff, product, quotient">
|
|
134
|
+
// Reproduce the original demo's combined output.
|
|
135
|
+
// All four results must match exact rational expectations.
|
|
136
|
+
const { math } = caps;
|
|
137
|
+
const { EGPTMath } = math;
|
|
138
|
+
const { sum, diff, product, quotient } = inputs; // consumed cross-cell bindings arrive on `inputs`, never as bare globals
|
|
139
|
+
|
|
140
|
+
const checks = [
|
|
141
|
+
sum.toMathString(),
|
|
142
|
+
diff.toMathString(),
|
|
143
|
+
product.toMathString(),
|
|
144
|
+
quotient.toMathString()
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const expected = ['11/4', '1/4', '15/8', '6/5'];
|
|
148
|
+
|
|
149
|
+
let allPass = true;
|
|
150
|
+
checks.forEach((result, i) => {
|
|
151
|
+
const pass = result === expected[i];
|
|
152
|
+
if (!pass) allPass = false;
|
|
153
|
+
console.log(`${pass ? 'PASS' : 'FAIL'} checks[${i}]: got "${result}", expected "${expected[i]}"`);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log(allPass
|
|
158
|
+
? `All 4 arithmetic checks PASSED — exact rational semantics confirmed on '${math.activeMathBackend}' math backend.`
|
|
159
|
+
: `One or more arithmetic checks FAILED — inspect the results above.`);
|
|
160
|
+
|
|
161
|
+
if (!allPass) {
|
|
162
|
+
throw new Error('Arithmetic checks failed — see console output above.');
|
|
163
|
+
}
|
|
164
|
+
</cell>
|
|
165
|
+
|
|
166
|
+
</frqtl-notebook>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<frqtl-notebook version="1" title="Complex Numbers in EGPT — Exact Rational Arithmetic with ComplexEGPTReal">
|
|
2
|
+
|
|
3
|
+
<cell type="markdown">
|
|
4
|
+
# Complex Numbers in EGPT — Exact Rational Arithmetic
|
|
5
|
+
|
|
6
|
+
Classical floating-point represents complex numbers as pairs of IEEE doubles. EGPT represents them as pairs of **`EGPTReal` values** — rational numbers stored symbolically as prime-factorisation vectors. Every operation is exact: no rounding, no cancellation error, no NaN. The result of `3 + 4i` squared is `25` as a BigInt ratio, not `24.999999...`.
|
|
7
|
+
|
|
8
|
+
This notebook walks through the fundamentals:
|
|
9
|
+
|
|
10
|
+
1. **Constructing** complex numbers from exact integer components.
|
|
11
|
+
2. **Inspecting** the symbolic representation via `toMathString()`.
|
|
12
|
+
3. **Computing magnitude squared** — `|z|² = a² + b²` — using `EGPTMath.multiply` and `EGPTMath.add`, both exact.
|
|
13
|
+
4. **Verifying** that the result matches the known integer value.
|
|
14
|
+
|
|
15
|
+
Everything below runs through the single injected `math` capability — no URL imports, no floating-point.
|
|
16
|
+
</cell>
|
|
17
|
+
|
|
18
|
+
<cell type="js" lane="math" id="backend">
|
|
19
|
+
// Report the active backend — derived from the SDK, never hardcoded.
|
|
20
|
+
const { math, display } = caps;
|
|
21
|
+
const backend = math.activeMathBackend;
|
|
22
|
+
display(`Active math backend (derived): ${backend}`);
|
|
23
|
+
</cell>
|
|
24
|
+
|
|
25
|
+
<cell type="markdown">
|
|
26
|
+
## 1. Constructing complex numbers
|
|
27
|
+
|
|
28
|
+
A `ComplexEGPTReal` is a pair `(real: EGPTReal, imag: EGPTReal)`. We build each component from an exact integer via `EGPTReal.fromBigInt(n)` — the canonical integer entry point. There is no approximate coercion from JS `Number`.
|
|
29
|
+
|
|
30
|
+
Here we construct:
|
|
31
|
+
- `z1 = 3 + 4i` — a classic Pythagorean complex number whose magnitude is exactly 5.
|
|
32
|
+
- `z2 = 5 + (-2)i` — a second number to illustrate that negative imaginary parts work identically.
|
|
33
|
+
</cell>
|
|
34
|
+
|
|
35
|
+
<cell type="js" lane="math" id="construct" out="z1str, z2str">
|
|
36
|
+
const { math, display } = caps;
|
|
37
|
+
const { EGPTReal, ComplexEGPTReal } = math;
|
|
38
|
+
|
|
39
|
+
// Build z1 = 3 + 4i
|
|
40
|
+
const z1 = new ComplexEGPTReal(EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n));
|
|
41
|
+
|
|
42
|
+
// Build z2 = 5 + (-2)i
|
|
43
|
+
const z2 = new ComplexEGPTReal(EGPTReal.fromBigInt(5n), EGPTReal.fromBigInt(-2n));
|
|
44
|
+
|
|
45
|
+
// toMathString() renders the EGPTReal symbolic representation
|
|
46
|
+
const z1str = `z1: real=${z1.real.toMathString()}, imag=${z1.imag.toMathString()}`;
|
|
47
|
+
const z2str = `z2: real=${z2.real.toMathString()}, imag=${z2.imag.toMathString()}`;
|
|
48
|
+
|
|
49
|
+
display(z1str);
|
|
50
|
+
display(z2str);
|
|
51
|
+
|
|
52
|
+
return { z1str, z2str };
|
|
53
|
+
</cell>
|
|
54
|
+
|
|
55
|
+
<cell type="markdown">
|
|
56
|
+
## 2. The symbolic representation
|
|
57
|
+
|
|
58
|
+
`toMathString()` exposes how the EGPT system stores a number internally. Small integers like `3`, `4`, `5`, `-2` appear in a compact form — but the underlying type is always an exact rational (a numerator/denominator pair of BigInts). There is no lossy float encoding happening here.
|
|
59
|
+
|
|
60
|
+
Both `z1` and `z2` are confirmed to be `ComplexEGPTReal` instances, not raw JS objects.
|
|
61
|
+
</cell>
|
|
62
|
+
|
|
63
|
+
<cell type="js" lane="math" id="instanceof-check" out="isComplex">
|
|
64
|
+
const { math, display } = caps;
|
|
65
|
+
const { EGPTReal, ComplexEGPTReal } = math;
|
|
66
|
+
|
|
67
|
+
// Reconstruct locally (cells are independent — no cross-cell variable sharing)
|
|
68
|
+
const z1 = new ComplexEGPTReal(EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n));
|
|
69
|
+
const z2 = new ComplexEGPTReal(EGPTReal.fromBigInt(5n), EGPTReal.fromBigInt(-2n));
|
|
70
|
+
|
|
71
|
+
const isComplex = z1 instanceof ComplexEGPTReal && z2 instanceof ComplexEGPTReal;
|
|
72
|
+
display(`Both are ComplexEGPTReal instances: ${isComplex}`);
|
|
73
|
+
|
|
74
|
+
return { isComplex };
|
|
75
|
+
</cell>
|
|
76
|
+
|
|
77
|
+
<cell type="markdown">
|
|
78
|
+
## 3. Magnitude squared: |z|² = a² + b²
|
|
79
|
+
|
|
80
|
+
For a complex number `z = a + bi`, the magnitude squared is:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
|z|² = a·a + b·b
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
We compute this using `EGPTMath.multiply` (exact rational multiplication) and `EGPTMath.add` (exact rational addition). For `z1 = 3 + 4i`:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
|z1|² = 3·3 + 4·4 = 9 + 16 = 25
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This is the Pythagorean triple `(3, 4, 5)` — the magnitude `|z1|` is exactly 5.
|
|
93
|
+
</cell>
|
|
94
|
+
|
|
95
|
+
<cell type="js" lane="math" id="magnitude-squared" out="magSqStr">
|
|
96
|
+
const { math, display } = caps;
|
|
97
|
+
const { EGPTReal, EGPTMath, ComplexEGPTReal } = math;
|
|
98
|
+
|
|
99
|
+
const z1 = new ComplexEGPTReal(EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n));
|
|
100
|
+
|
|
101
|
+
// Exact rational arithmetic — no floating-point at any step
|
|
102
|
+
const realSquared = EGPTMath.multiply(z1.real, z1.real); // 3 * 3 = 9
|
|
103
|
+
const imagSquared = EGPTMath.multiply(z1.imag, z1.imag); // 4 * 4 = 16
|
|
104
|
+
const magnitudeSquared = EGPTMath.add(realSquared, imagSquared); // 9 + 16 = 25
|
|
105
|
+
|
|
106
|
+
const magSqStr = magnitudeSquared.toMathString();
|
|
107
|
+
display(`|z1|² = ${magSqStr}`);
|
|
108
|
+
|
|
109
|
+
// Verify against expected value
|
|
110
|
+
const expected = EGPTReal.fromBigInt(25n);
|
|
111
|
+
const correct = magnitudeSquared.equals(expected);
|
|
112
|
+
display(`Equals 25 (exact comparison via .equals()): ${correct}`);
|
|
113
|
+
|
|
114
|
+
return { magSqStr };
|
|
115
|
+
</cell>
|
|
116
|
+
|
|
117
|
+
<cell type="markdown">
|
|
118
|
+
## 4. Why `.equals()`, not `===` or `==`
|
|
119
|
+
|
|
120
|
+
EGPT stores rationals as prime-factorisation vectors. Two `EGPTReal` values may have different internal representations that are mathematically equal. The only correct comparison is **`.equals()`** — a symbolic equality check on the reduced rational. Never use `==`, `===`, or string comparison (`.toMathString() ===`) to compare EGPTReal values.
|
|
121
|
+
|
|
122
|
+
This is the same discipline as comparing fractions: `1/2` and `2/4` are equal but not string-identical.
|
|
123
|
+
</cell>
|
|
124
|
+
|
|
125
|
+
<cell type="js" lane="math" id="summary" in="z1str, z2str, isComplex, magSqStr">
|
|
126
|
+
const { math, display } = caps;
|
|
127
|
+
const { EGPTReal, EGPTMath, ComplexEGPTReal } = math;
|
|
128
|
+
|
|
129
|
+
// Reproduce the original run(sdk) return value exactly
|
|
130
|
+
const z1 = new ComplexEGPTReal(EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n));
|
|
131
|
+
const z2 = new ComplexEGPTReal(EGPTReal.fromBigInt(5n), EGPTReal.fromBigInt(-2n));
|
|
132
|
+
const magnitudeSquaredZ1 = EGPTMath.add(
|
|
133
|
+
EGPTMath.multiply(z1.real, z1.real),
|
|
134
|
+
EGPTMath.multiply(z1.imag, z1.imag)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const result = {
|
|
138
|
+
category: "complex-twiddle",
|
|
139
|
+
z1: { real: z1.real.toMathString(), imag: z1.imag.toMathString() },
|
|
140
|
+
z2: { real: z2.real.toMathString(), imag: z2.imag.toMathString() },
|
|
141
|
+
magnitudeSquaredZ1: magnitudeSquaredZ1.toMathString(),
|
|
142
|
+
isComplex: z1 instanceof ComplexEGPTReal && z2 instanceof ComplexEGPTReal
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
display(result);
|
|
146
|
+
</cell>
|
|
147
|
+
|
|
148
|
+
</frqtl-notebook>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<frqtl-notebook version="1" title="Double Slit → Rosetta → QFT — one notebook, three engines">
|
|
2
|
+
|
|
3
|
+
<cell type="markdown">
|
|
4
|
+
# Double Slit → Rosetta → QFT
|
|
5
|
+
|
|
6
|
+
One environment, three engines, **individually runnable**. This dogfood proves the unified-notebook capability end-to-end:
|
|
7
|
+
|
|
8
|
+
- **imperative cells** — nothing runs on load; you click Run per cell.
|
|
9
|
+
- **typed object-bag handoff** — a cell declares what it consumes (`in`) and produces (`out`); the kernel wires named outputs to named inputs and marks consumers *stale* (never auto-runs them).
|
|
10
|
+
- **a live physics canvas alongside math** — the double-slit simulation mounts only when you Run its canvas cell, and at most one canvas runs at a time.
|
|
11
|
+
|
|
12
|
+
The loose through-line: an interference pattern *is* information; we encode it (Rosetta), and we factor a number whose period extraction is the "quantum" step of Shor's algorithm (QFT) — all classical, all here.
|
|
13
|
+
</cell>
|
|
14
|
+
|
|
15
|
+
<cell type="markdown">
|
|
16
|
+
## 1. The double slit
|
|
17
|
+
|
|
18
|
+
The canvas below DECLARES the canonical double-slit experiment (`setup="./setups/double-slit.js#setupDoubleSlit"` + `stage="480x480"` + `seed="42"`); the shim synthesizes the builder boilerplate (import → ensureEngine → ExperimentRunner → setup → mount) and runs it in the realm. It does **not** start on notebook load — click **Run** to mount and start it. The canvas also declares `produceAfter="600"`: before committing the `hist` binding (§2) the shim warms the realm 600 deterministic ticks so particles cross the stage and accumulate at the detector — so the histogram (and the §3 chart) shows **real** fringe data rather than an empty tick-0 snapshot. The runner stays **realm-internal** (it is never a kernel binding); realm-produced data reaches downstream cells via the realm→kernel binding seam (§2). Switch to another notebook and back, Run again — the pane disposes the prior runner first, so there is never a duplicate RAF loop.
|
|
19
|
+
|
|
20
|
+
The canvas also declares `liveChart="detectorHistogram"`: a **same-cell** live accumulation chart renders **inside this canvas's own realm**, beneath the sim, and **fills as the sim runs** (the original-gallery model). It subscribes to the realm's **local** tick stream (`handle.on('tick')`), polls the same `detectorHistogram` projector, and redraws ~10×/s — so you watch the fringes build live. This is **orthogonal** to `produces=`: the live chart draws continuously and commits **nothing**, while `produces="hist"` derives the histogram **once** and commits the `hist` kernel binding the §3 chart consumes. Because the live chart lives in the realm, its data **never** crosses the renderer↔realm boundary.
|
|
21
|
+
</cell>
|
|
22
|
+
|
|
23
|
+
<cell type="module" name="./setups/double-slit.js">
|
|
24
|
+
/* setupDoubleSlit — THIN ADAPTER over the canonical setup factory
|
|
25
|
+
* (WS gym-controls-as-sugar Bite 0: this cell used to hand-copy the physics —
|
|
26
|
+
* a divergent fork of lib/frqtl/objects/setup/setupDoubleSlit.js that had
|
|
27
|
+
* already drifted onto a stale `universe.init(...)` positional-argument call the
|
|
28
|
+
* engine now FAIL-LOUDs on. The canonical factory is imported by BARE SPECIFIER
|
|
29
|
+
* (a relative path here would 404 — see lib/shim/notebook/frqtl-setups-importmap.js);
|
|
30
|
+
* this cell only bridges the notebook builder's `(frqtl, universe, options)` call
|
|
31
|
+
* shape to the factory's `(universe, options)` shape — ZERO physics here. This
|
|
32
|
+
* is the SAME canonical factory the gallery-double-slit notebook and the
|
|
33
|
+
* lib/frqtl/experiments/double-slit/ page all drive — one experiment identity.)
|
|
34
|
+
*/
|
|
35
|
+
import { setupDoubleSlit as _setupDoubleSlit } from '@frqtl/setups/double-slit';
|
|
36
|
+
|
|
37
|
+
export function setupDoubleSlit(frqtl, universe, options) {
|
|
38
|
+
return _setupDoubleSlit(universe, options);
|
|
39
|
+
}
|
|
40
|
+
</cell>
|
|
41
|
+
|
|
42
|
+
<cell type="canvas" id="c-ds-canvas" setup="./setups/double-slit.js#setupDoubleSlit" stage="480x480" seed="42" produces="hist" produceAfter="600" liveChart="detectorHistogram"></cell>
|
|
43
|
+
|
|
44
|
+
<cell type="markdown">
|
|
45
|
+
## 2. Pull the interference histogram (the realm→kernel binding seam)
|
|
46
|
+
|
|
47
|
+
The canvas above DECLARES its setup (`setup="./setups/double-slit.js#setupDoubleSlit"`) **and** what it produces (`produces="hist"`); the shim synthesizes the builder and runs it **realm-internal** — the runner is **never** a kernel binding (WS-NOTEBOOK-SHIM-MANAGED-BUILDER C9).
|
|
48
|
+
|
|
49
|
+
After the runner mounts, the realm DERIVES a **serializable** detector histogram from the live runner via the one frozen-handle projector (`detectorHistogram(runner)`, default `produceVia`) and commits it as the `hist` binding through the single binding-commit authority `kernel.commitViewOutputs` (WS-NOTEBOOK-REALM-KERNEL-BINDING). The committed value is a plain `{ binKey: count }` object — a live runner can never cross the seam (it FAILS LOUD at the serializability gate). The `hist`-consuming cells below now resolve once you Run the canvas; before it has produced, they still FAIL LOUD ("`hist` not yet produced") — by design, never a silent empty result.
|
|
50
|
+
</cell>
|
|
51
|
+
|
|
52
|
+
<cell type="markdown">
|
|
53
|
+
## 3. Chart the fringes
|
|
54
|
+
|
|
55
|
+
The chart cell consumes `hist` and renders the interference fringes as a bar chart (Chart.js). It FAILs LOUD naming the producing cell if `hist` is not yet produced — it never silently draws an empty chart, and never auto-runs the upstream cell. (`hist` arrives via the realm→kernel binding seam — see §2.)
|
|
56
|
+
</cell>
|
|
57
|
+
|
|
58
|
+
<cell type="chart" id="c-ds-chart" in="hist" spec="bar:hist"></cell>
|
|
59
|
+
|
|
60
|
+
<cell type="markdown">
|
|
61
|
+
## 4. Encoding the pattern (Rosetta)
|
|
62
|
+
|
|
63
|
+
Now we cross lanes — `frqtl` → `math`. This cell consumes `hist` (produced in the frqtl lane) and builds a Rosetta `Codec` over the histogram counts: a typed pipeline `HostFloats → EGPTReal → HostFloats` with an explicit `claim`. Running it captures per-stage `snapshots` (the object-bag-per-stage trace) — the same provenance discipline the notebook kernel generalizes. This is the `hist → chain` cross-lane handoff.
|
|
64
|
+
</cell>
|
|
65
|
+
|
|
66
|
+
<cell type="js" lane="math" id="c-rosetta" in="hist" out="chain">
|
|
67
|
+
// Cross-lane handoff: hist (frqtl) → a Rosetta Codec (math).
|
|
68
|
+
const { math, display } = caps;
|
|
69
|
+
// `hist` arrives via the realm→kernel binding seam as a SERIALIZABLE plain
|
|
70
|
+
// { binKey: count } object (a live runner / Map can never cross the seam). Read its
|
|
71
|
+
// counts from the plain object's values (NOT Map.values()).
|
|
72
|
+
const hist = inputs.hist;
|
|
73
|
+
const counts = (hist && typeof hist === 'object')
|
|
74
|
+
? Object.keys(hist).map((k) => Number(hist[k]))
|
|
75
|
+
: [];
|
|
76
|
+
// Pad to a non-empty array so the chain always has something to encode.
|
|
77
|
+
const payload = counts.length ? counts : [0];
|
|
78
|
+
|
|
79
|
+
const codecRuntime = math.codec; // { registry, Codec, buildCodecDsl, EncodingClasses }
|
|
80
|
+
const dsl = codecRuntime.buildCodecDsl(
|
|
81
|
+
codecRuntime.registry, codecRuntime.Codec, codecRuntime.EncodingClasses
|
|
82
|
+
).dsl;
|
|
83
|
+
|
|
84
|
+
// A minimal typed round-trip chain over the histogram values.
|
|
85
|
+
dsl.input('HostFloats', { payload, format: 'fp64' });
|
|
86
|
+
dsl.push('HostFloatsToEGPTReal');
|
|
87
|
+
dsl.push('EGPTRealToHostFloats');
|
|
88
|
+
// Codec.run THROWS MissingClaimError unless a claim was declared first
|
|
89
|
+
// (architect ground-truth correction #4).
|
|
90
|
+
dsl.claim('round-trip-exact', { reference: 'round-trip-exact', tolerance: 'exact' });
|
|
91
|
+
|
|
92
|
+
const built = dsl.build();
|
|
93
|
+
const codec = built.codec;
|
|
94
|
+
const chain = codec.run(built.headEncoding); // RunResult { snapshots, output, trace }
|
|
95
|
+
|
|
96
|
+
display(`Rosetta chain ran: ${chain.snapshots.length} snapshots ` +
|
|
97
|
+
`(input → EGPTReal → host floats); claim="${chain.claimMeta && chain.claimMeta.name}".`);
|
|
98
|
+
return { chain };
|
|
99
|
+
</cell>
|
|
100
|
+
|
|
101
|
+
<cell type="js" lane="math" id="c-rosetta-trace" in="chain">
|
|
102
|
+
// Display the per-stage snapshots (the object-bag-per-stage trace) + the
|
|
103
|
+
// round-trip verdict. Ties the Rosetta paradigm to the notebook paradigm:
|
|
104
|
+
// named typed handoff, captured at every stage.
|
|
105
|
+
const { display } = caps;
|
|
106
|
+
const chain = inputs.chain;
|
|
107
|
+
const kinds = chain.snapshots.map(s => (s && s.constructor && s.constructor.kind) || '?');
|
|
108
|
+
display(`stages: ${kinds.join(' → ')}`);
|
|
109
|
+
const head = chain.snapshots[0];
|
|
110
|
+
const tail = chain.output;
|
|
111
|
+
const roundTrips = (head && typeof head.equals === 'function')
|
|
112
|
+
? head.equals(tail) : false;
|
|
113
|
+
display(`round-trip (head.equals(output)): ${roundTrips ? 'OK (exact)' : 'differs'}`);
|
|
114
|
+
</cell>
|
|
115
|
+
|
|
116
|
+
<cell type="markdown">
|
|
117
|
+
## 5. The QFT benchmark
|
|
118
|
+
|
|
119
|
+
This `math`-lane cell drives the **same** `FraqtlSession` the QFT showcase notebook drives (`math.FraqtlSession.auto()` → `session.run(N)`) — reuse, not fork. It factors the 39-bit semiprime `549755813701 = 712321 × 771781` (the period-extraction "quantum" step of Shor's algorithm, run classically here). N and the 594-core-year comparison come from the single baseline authority `math.benchmarks.qftBaseline` — no inlined figures. This cell is deliberately **not** wired to the others: not every cell must chain.
|
|
120
|
+
</cell>
|
|
121
|
+
|
|
122
|
+
<cell type="js" lane="math" id="c-qft" out="qft">
|
|
123
|
+
const { math, display } = caps;
|
|
124
|
+
const b = math.benchmarks && math.benchmarks.qftBaseline;
|
|
125
|
+
if (!b) {
|
|
126
|
+
throw new Error('math.benchmarks.qftBaseline absent — figures live in ONE authority (FAIL-LOUD).');
|
|
127
|
+
}
|
|
128
|
+
const N = b.benchmarkRun.N; // the SAME semiprime, from the authority
|
|
129
|
+
const session = math.FraqtlSession.auto();
|
|
130
|
+
const t0 = performance.now();
|
|
131
|
+
const result = session.run(N);
|
|
132
|
+
const elapsedMs = (typeof result.elapsed_ms === 'number') ? result.elapsed_ms : (performance.now() - t0);
|
|
133
|
+
|
|
134
|
+
const expected = b.benchmarkRun.factors;
|
|
135
|
+
const got = (result.factors || []).slice().sort((x, y) => x - y);
|
|
136
|
+
const want = expected.slice().sort((x, y) => x - y);
|
|
137
|
+
const ok = got.length === want.length && got.every((v, i) => v === want[i]);
|
|
138
|
+
|
|
139
|
+
display(ok
|
|
140
|
+
? `FACTORS VERIFIED on ${math.activeMathBackend}: ${N} = ${result.factors.join(' x ')} ` +
|
|
141
|
+
`in ${elapsedMs.toFixed(0)} ms — vs the published ${b.studyCompute.coreYears} core-years of supercomputer compute.`
|
|
142
|
+
: `MISMATCH: got [${result.factors}], expected [${expected}].`);
|
|
143
|
+
|
|
144
|
+
const qft = { N, factors: result.factors, ok, elapsedMs, backend: math.activeMathBackend };
|
|
145
|
+
return { qft };
|
|
146
|
+
</cell>
|
|
147
|
+
|
|
148
|
+
<cell type="markdown">
|
|
149
|
+
## What this validates
|
|
150
|
+
|
|
151
|
+
Interference → encoding → factoring, in one notebook, on one execution surface, every cell individually runnable, the physics canvas never auto-triggered. This is the capability bar for the CODE + GALLERY merge: the same native `<frqtl-notebook>` format and the same on-demand kernel carry markdown, math, live physics, and charts side by side.
|
|
152
|
+
</cell>
|
|
153
|
+
|
|
154
|
+
</frqtl-notebook>
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<frqtl-notebook version="1" title="EGPTFFT — SDK Parity Tests (forward / inverse round-trip)">
|
|
2
|
+
|
|
3
|
+
<cell type="markdown">
|
|
4
|
+
# EGPTFFT — SDK Parity Tests
|
|
5
|
+
|
|
6
|
+
`EGPTFFT` is a thin pedagogical wrapper over `EGPTPolynomial`'s canonical transform
|
|
7
|
+
primitives. It makes the polynomial-transform interpretation of an FFT explicit: there
|
|
8
|
+
is no floating-point, no Cooley-Tukey butterfly, and no complex exponential. Instead:
|
|
9
|
+
|
|
10
|
+
| Method | What it computes |
|
|
11
|
+
|--------|-----------------|
|
|
12
|
+
| `EGPTFFT.forward(coeffs, N)` | Evaluates `P(x) = c₀ + c₁x + …` at the N uniform rational nodes `k/N` via Horner's method. Each result is an exact `EGPTReal` rational. |
|
|
13
|
+
| `EGPTFFT.inverse(values, N)` | Recovers the original coefficient vector from those N samples via Newton divided-differences — bit-exact. |
|
|
14
|
+
|
|
15
|
+
The round-trip identity `inverse(forward(c, N), N) === c` holds to the last bit because
|
|
16
|
+
both operations live entirely in Shannon/PPF (rational) space.
|
|
17
|
+
|
|
18
|
+
This notebook runs the three canonical parity tests from `sdk/egpt-math-sdk/src/editor/tests/EGPTFFTTest.js`:
|
|
19
|
+
|
|
20
|
+
1. **Agreement test** — `EGPTFFT.forward` matches `EGPTPolynomial.forwardTransform` for a mixed-rational input.
|
|
21
|
+
2. **Integer round-trip** — `inverse(forward(c, 4), 4) === c` for integer coefficients.
|
|
22
|
+
3. **Rational round-trip** — `inverse(forward(c, 5), 5) === c` for fractional coefficients.
|
|
23
|
+
|
|
24
|
+
All three tests use explicit `N` values (matching the source) to exercise the N-override
|
|
25
|
+
path in `EGPTFFT.forward` / `.inverse`.
|
|
26
|
+
</cell>
|
|
27
|
+
|
|
28
|
+
<cell type="markdown">
|
|
29
|
+
## Setup — test harness and value constructors
|
|
30
|
+
|
|
31
|
+
A minimal harness (`test` / `assert` / `assertPolyEquals`) mirrors the original source.
|
|
32
|
+
`passed` / `failed` counts accumulate across all phase cells via the shared `suite` binding.
|
|
33
|
+
</cell>
|
|
34
|
+
|
|
35
|
+
<cell type="js" lane="math" id="fft-setup" out="suite">
|
|
36
|
+
const { math } = caps;
|
|
37
|
+
const { EGPTReal, EGPTPolynomial } = math;
|
|
38
|
+
|
|
39
|
+
// Convenient EGPTReal constructors used across all phase cells.
|
|
40
|
+
const intN = (n) => EGPTReal.fromBigInt(BigInt(n));
|
|
41
|
+
const frac = (n, d) => EGPTReal.fromRational(BigInt(n), BigInt(d));
|
|
42
|
+
|
|
43
|
+
let passed = 0;
|
|
44
|
+
let failed = 0;
|
|
45
|
+
const failures = [];
|
|
46
|
+
|
|
47
|
+
function test(name, fn) {
|
|
48
|
+
try {
|
|
49
|
+
fn();
|
|
50
|
+
console.log(' ok — ' + name);
|
|
51
|
+
passed++;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.log(' FAIL — ' + name + ' (' + err.message + ')');
|
|
54
|
+
failed++;
|
|
55
|
+
failures.push(name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assert(condition, message) {
|
|
60
|
+
if (!condition) throw new Error(message || 'assertion failed');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function assertPolyEquals(actual, expected, message) {
|
|
64
|
+
if (!EGPTPolynomial.equals(actual, expected)) throw new Error(message || 'polynomial mismatch');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
suite: {
|
|
69
|
+
test,
|
|
70
|
+
assert,
|
|
71
|
+
assertPolyEquals,
|
|
72
|
+
intN,
|
|
73
|
+
frac,
|
|
74
|
+
getResults: () => ({ passed, failed, failures })
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
</cell>
|
|
78
|
+
|
|
79
|
+
<cell type="markdown">
|
|
80
|
+
## Phase 1 — forward agrees with EGPTPolynomial.forwardTransform
|
|
81
|
+
|
|
82
|
+
`EGPTFFT.forward(coeffs, N)` is defined as `EGPTPolynomial.forwardTransform(coeffs, N)`.
|
|
83
|
+
This test verifies that identity directly for a three-element mixed-rational input with N = 3.
|
|
84
|
+
|
|
85
|
+
Input: `[ 1/3, -7/6, 1 ]` — one integer, two fractions.
|
|
86
|
+
</cell>
|
|
87
|
+
|
|
88
|
+
<cell type="js" lane="math" id="fft-phase1" in="suite" out="ph1">
|
|
89
|
+
const { math } = caps;
|
|
90
|
+
const { EGPTFFT, EGPTPolynomial } = math;
|
|
91
|
+
const { test, assertPolyEquals, frac, intN, getResults } = inputs.suite;
|
|
92
|
+
|
|
93
|
+
console.log('[Phase 1] EGPTFFT.forward matches EGPTPolynomial.forwardTransform');
|
|
94
|
+
|
|
95
|
+
test('EGPTFFT.forward matches EGPTPolynomial.forwardTransform', () => {
|
|
96
|
+
const coeffs = [
|
|
97
|
+
frac(1, 3),
|
|
98
|
+
frac(-7, 6),
|
|
99
|
+
intN(1)
|
|
100
|
+
];
|
|
101
|
+
assertPolyEquals(
|
|
102
|
+
EGPTFFT.forward(coeffs, 3),
|
|
103
|
+
EGPTPolynomial.forwardTransform(coeffs, 3),
|
|
104
|
+
'forward(c, 3) should equal forwardTransform(c, 3)'
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return { ph1: getResults() };
|
|
109
|
+
</cell>
|
|
110
|
+
|
|
111
|
+
<cell type="markdown">
|
|
112
|
+
## Phase 2 — inverse(forward(c)) round-trips integer coefficients
|
|
113
|
+
|
|
114
|
+
For integer input `[ 2, -3, 0, 5 ]` with N = 4, applying `forward` then `inverse`
|
|
115
|
+
must recover the original vector exactly.
|
|
116
|
+
|
|
117
|
+
The N = 4 override exercises the case where the number of transform sample-points is
|
|
118
|
+
equal to the number of coefficients — this is the "square Vandermonde" case where the
|
|
119
|
+
forward matrix is invertible by construction.
|
|
120
|
+
</cell>
|
|
121
|
+
|
|
122
|
+
<cell type="js" lane="math" id="fft-phase2" in="suite" out="ph2">
|
|
123
|
+
const { math } = caps;
|
|
124
|
+
const { EGPTFFT } = math;
|
|
125
|
+
const { test, assertPolyEquals, intN, getResults } = inputs.suite;
|
|
126
|
+
|
|
127
|
+
console.log('[Phase 2] inverse(forward(c)) round trips integer coefficients');
|
|
128
|
+
|
|
129
|
+
test('EGPTFFT inverse(forward(c)) round trips integer coefficients', () => {
|
|
130
|
+
const coeffs = [
|
|
131
|
+
intN(2),
|
|
132
|
+
intN(-3),
|
|
133
|
+
intN(0),
|
|
134
|
+
intN(5)
|
|
135
|
+
];
|
|
136
|
+
assertPolyEquals(
|
|
137
|
+
EGPTFFT.inverse(EGPTFFT.forward(coeffs, 4), 4),
|
|
138
|
+
coeffs,
|
|
139
|
+
'round trip should recover [ 2, -3, 0, 5 ] exactly'
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return { ph2: getResults() };
|
|
144
|
+
</cell>
|
|
145
|
+
|
|
146
|
+
<cell type="markdown">
|
|
147
|
+
## Phase 3 — inverse(forward(c)) round-trips rational coefficients
|
|
148
|
+
|
|
149
|
+
For fractional input `[ 1/2, -3/4, 5/7 ]` with N = 5, the forward transform produces
|
|
150
|
+
five rational-valued samples at nodes `0/5, 1/5, 2/5, 3/5, 4/5`. The inverse must
|
|
151
|
+
recover the original three coefficients exactly from those five over-determined samples.
|
|
152
|
+
|
|
153
|
+
N = 5 > len(coeffs) = 3 exercises the "over-sampled" path: Newton divided-differences
|
|
154
|
+
reconstruct the unique degree-2 polynomial fitting all five points.
|
|
155
|
+
</cell>
|
|
156
|
+
|
|
157
|
+
<cell type="js" lane="math" id="fft-phase3" in="suite" out="ph3">
|
|
158
|
+
const { math } = caps;
|
|
159
|
+
const { EGPTFFT } = math;
|
|
160
|
+
const { test, assertPolyEquals, frac, getResults } = inputs.suite;
|
|
161
|
+
|
|
162
|
+
console.log('[Phase 3] inverse(forward(c)) round trips rational coefficients');
|
|
163
|
+
|
|
164
|
+
test('EGPTFFT inverse(forward(c)) round trips rational coefficients', () => {
|
|
165
|
+
const coeffs = [
|
|
166
|
+
frac(1, 2),
|
|
167
|
+
frac(-3, 4),
|
|
168
|
+
frac(5, 7)
|
|
169
|
+
];
|
|
170
|
+
assertPolyEquals(
|
|
171
|
+
EGPTFFT.inverse(EGPTFFT.forward(coeffs, 5), 5),
|
|
172
|
+
coeffs,
|
|
173
|
+
'round trip should recover [ 1/2, -3/4, 5/7 ] exactly'
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return { ph3: getResults() };
|
|
178
|
+
</cell>
|
|
179
|
+
|
|
180
|
+
<cell type="markdown">
|
|
181
|
+
## Summary
|
|
182
|
+
|
|
183
|
+
Final tally across all three parity tests.
|
|
184
|
+
</cell>
|
|
185
|
+
|
|
186
|
+
<cell type="js" lane="math" id="fft-summary" in="ph3">
|
|
187
|
+
const { passed, failed, failures } = inputs.ph3;
|
|
188
|
+
const total = passed + failed;
|
|
189
|
+
|
|
190
|
+
console.log('');
|
|
191
|
+
console.log('='.repeat(60));
|
|
192
|
+
console.log('EGPTFFTTest TOTAL: ' + passed + '/' + total + ' passed');
|
|
193
|
+
if (failures.length > 0) {
|
|
194
|
+
console.log('Failed tests:');
|
|
195
|
+
failures.forEach((f) => console.log(' - ' + f));
|
|
196
|
+
}
|
|
197
|
+
console.log('='.repeat(60));
|
|
198
|
+
|
|
199
|
+
if (failed > 0) throw new Error('EGPTFFTTest failed: ' + failed);
|
|
200
|
+
</cell>
|
|
201
|
+
|
|
202
|
+
</frqtl-notebook>
|