@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.
Files changed (114) hide show
  1. package/README.md +224 -0
  2. package/dist/egpt-math-sdk.browser.js +5 -0
  3. package/dist/index.d.ts +100 -0
  4. package/dist/index.js +985 -0
  5. package/dist/notebooks/arithmetic-basics.html +166 -0
  6. package/dist/notebooks/complex-twiddle.html +148 -0
  7. package/dist/notebooks/double-slit-rosetta-qft.html +154 -0
  8. package/dist/notebooks/egpt-fft-test.html +202 -0
  9. package/dist/notebooks/egpt-matrix-test.html +241 -0
  10. package/dist/notebooks/egpt-polynomial-test.html +703 -0
  11. package/dist/notebooks/egpt-prime-composite-test.html +561 -0
  12. package/dist/notebooks/egpt-test-suite.html +1387 -0
  13. package/dist/notebooks/egpt-topology-test.html +410 -0
  14. package/dist/notebooks/factorization-basics.html +131 -0
  15. package/dist/notebooks/fft.html +155 -0
  16. package/dist/notebooks/frqtl-qft-benchmark.html +207 -0
  17. package/dist/notebooks/gallery-atomic-model.html +51 -0
  18. package/dist/notebooks/gallery-big-bang.html +62 -0
  19. package/dist/notebooks/gallery-blackbody.html +52 -0
  20. package/dist/notebooks/gallery-circuit-sat.html +104 -0
  21. package/dist/notebooks/gallery-collision-matrix-3d.html +51 -0
  22. package/dist/notebooks/gallery-double-slit.html +53 -0
  23. package/dist/notebooks/gallery-gun-chamber.html +62 -0
  24. package/dist/notebooks/gallery-particle-walk-3d.html +53 -0
  25. package/dist/notebooks/gallery-wave-interference.html +52 -0
  26. package/dist/notebooks/getting-started-hello-frqtl.html +53 -0
  27. package/dist/notebooks/iso-canonical-chain.html +203 -0
  28. package/dist/notebooks/iso-entropy-log.html +127 -0
  29. package/dist/notebooks/iso-fft-polynomial.html +136 -0
  30. package/dist/notebooks/iso-matrix-four-views.html +131 -0
  31. package/dist/notebooks/iso-number-theory-four-views.html +99 -0
  32. package/dist/notebooks/iso-pi-log-fraction-loop.html +140 -0
  33. package/dist/notebooks/matrix.html +202 -0
  34. package/dist/notebooks/notebooks.json +240 -0
  35. package/dist/notebooks/polynomials.html +201 -0
  36. package/dist/notebooks/prime-composite.html +268 -0
  37. package/dist/notebooks/rational-core.html +179 -0
  38. package/dist/notebooks/reference-egpt-fft-test.html +187 -0
  39. package/dist/notebooks/reference-order-finder-test.html +196 -0
  40. package/dist/notebooks/reference-prime-atom-polynomial-test.html +542 -0
  41. package/dist/notebooks/reference-solve-polynomial-system-test.html +471 -0
  42. package/dist/notebooks/scaled-vectors.html +112 -0
  43. package/dist/notebooks/stats.html +206 -0
  44. package/dist/notebooks/transcendentals.html +195 -0
  45. package/dist/notebooks/view-vendored-d3.html +78 -0
  46. package/dist/vendor/d3-array/src/array.js +4 -0
  47. package/dist/vendor/d3-array/src/ascending.js +3 -0
  48. package/dist/vendor/d3-array/src/bin.js +125 -0
  49. package/dist/vendor/d3-array/src/bisect.js +9 -0
  50. package/dist/vendor/d3-array/src/bisector.js +56 -0
  51. package/dist/vendor/d3-array/src/blur.js +115 -0
  52. package/dist/vendor/d3-array/src/constant.js +3 -0
  53. package/dist/vendor/d3-array/src/count.js +18 -0
  54. package/dist/vendor/d3-array/src/cross.js +33 -0
  55. package/dist/vendor/d3-array/src/cumsum.js +6 -0
  56. package/dist/vendor/d3-array/src/descending.js +7 -0
  57. package/dist/vendor/d3-array/src/deviation.js +6 -0
  58. package/dist/vendor/d3-array/src/difference.js +11 -0
  59. package/dist/vendor/d3-array/src/disjoint.js +15 -0
  60. package/dist/vendor/d3-array/src/every.js +10 -0
  61. package/dist/vendor/d3-array/src/extent.js +29 -0
  62. package/dist/vendor/d3-array/src/filter.js +11 -0
  63. package/dist/vendor/d3-array/src/fsum.js +69 -0
  64. package/dist/vendor/d3-array/src/greatest.js +29 -0
  65. package/dist/vendor/d3-array/src/greatestIndex.js +19 -0
  66. package/dist/vendor/d3-array/src/group.js +65 -0
  67. package/dist/vendor/d3-array/src/groupSort.js +10 -0
  68. package/dist/vendor/d3-array/src/identity.js +3 -0
  69. package/dist/vendor/d3-array/src/index.js +57 -0
  70. package/dist/vendor/d3-array/src/intersection.js +19 -0
  71. package/dist/vendor/d3-array/src/least.js +29 -0
  72. package/dist/vendor/d3-array/src/leastIndex.js +19 -0
  73. package/dist/vendor/d3-array/src/map.js +5 -0
  74. package/dist/vendor/d3-array/src/max.js +20 -0
  75. package/dist/vendor/d3-array/src/maxIndex.js +22 -0
  76. package/dist/vendor/d3-array/src/mean.js +19 -0
  77. package/dist/vendor/d3-array/src/median.js +9 -0
  78. package/dist/vendor/d3-array/src/merge.js +9 -0
  79. package/dist/vendor/d3-array/src/min.js +20 -0
  80. package/dist/vendor/d3-array/src/minIndex.js +22 -0
  81. package/dist/vendor/d3-array/src/mode.js +28 -0
  82. package/dist/vendor/d3-array/src/nice.js +18 -0
  83. package/dist/vendor/d3-array/src/number.js +20 -0
  84. package/dist/vendor/d3-array/src/pairs.js +15 -0
  85. package/dist/vendor/d3-array/src/permute.js +3 -0
  86. package/dist/vendor/d3-array/src/quantile.js +47 -0
  87. package/dist/vendor/d3-array/src/quickselect.js +53 -0
  88. package/dist/vendor/d3-array/src/range.js +13 -0
  89. package/dist/vendor/d3-array/src/rank.js +24 -0
  90. package/dist/vendor/d3-array/src/reduce.js +14 -0
  91. package/dist/vendor/d3-array/src/reverse.js +4 -0
  92. package/dist/vendor/d3-array/src/scan.js +6 -0
  93. package/dist/vendor/d3-array/src/shuffle.js +13 -0
  94. package/dist/vendor/d3-array/src/some.js +10 -0
  95. package/dist/vendor/d3-array/src/sort.js +39 -0
  96. package/dist/vendor/d3-array/src/subset.js +5 -0
  97. package/dist/vendor/d3-array/src/sum.js +18 -0
  98. package/dist/vendor/d3-array/src/superset.js +19 -0
  99. package/dist/vendor/d3-array/src/threshold/freedmanDiaconis.js +7 -0
  100. package/dist/vendor/d3-array/src/threshold/scott.js +7 -0
  101. package/dist/vendor/d3-array/src/threshold/sturges.js +5 -0
  102. package/dist/vendor/d3-array/src/ticks.js +55 -0
  103. package/dist/vendor/d3-array/src/transpose.js +15 -0
  104. package/dist/vendor/d3-array/src/union.js +11 -0
  105. package/dist/vendor/d3-array/src/variance.js +25 -0
  106. package/dist/vendor/d3-array/src/zip.js +5 -0
  107. package/dist/vendor/internmap/src/index.js +61 -0
  108. package/dist/vendor/manifest.json +11 -0
  109. package/dist/wasm/egpt_math_sdk_wasm.d.ts +214 -0
  110. package/dist/wasm/egpt_math_sdk_wasm.js +1120 -0
  111. package/dist/wasm/egpt_math_sdk_wasm_bg.wasm +0 -0
  112. package/dist/wasm/egpt_math_sdk_wasm_bg.wasm.d.ts +66 -0
  113. package/dist/wasm/manifest.json +5 -0
  114. package/package.json +53 -0
@@ -0,0 +1,561 @@
1
+ <frqtl-notebook version="1" title="EGPTPrimeComposite — T5 Engineering Test Suite (Option B)">
2
+
3
+ <cell type="markdown">
4
+ # EGPTPrimeComposite — T5 Engineering Test Suite (Option B)
5
+
6
+ `EGPTPrimeComposite` extends `EGPTReal` to carry the full prime provenance of a rational
7
+ through multiplication and division — **without ever reducing**. It stores every prime factor
8
+ of every multiplication step as an explicit `{ prime, location }` record so that callers can
9
+ recover the full signed-prime structure even after operations that would normally cancel.
10
+
11
+ This matters for the BIPP (Boolean Interpretation Prime-Probability) CNF↔polynomial bijection:
12
+ the literal polarity of a SAT variable is recovered from whether its prime lands in the
13
+ `numerator` or `denominator` of the product composite — so the records must never be collapsed.
14
+
15
+ This suite verifies five acceptance properties (T5 Engineering #1, Option B):
16
+
17
+ | Phase | Property |
18
+ |---|---|
19
+ | 1 — Construction | `fromPrimeRecords` round-trip preserves the multiset of records exactly. |
20
+ | 2 — Factoring | `fromEGPTReal` round-trip for positive/negative integers and reciprocals. |
21
+ | 3 — AP4 acceptance | `(Y − 3) × (Y − 1/3)` constant term keeps both `{3 in numerator}` and `{3 in denominator}` records — the AP4 anti-pattern is not re-introduced. |
22
+ | 4 — Commutativity | Multiply is commutative at the multiset level; divide flips locations correctly. |
23
+ | 5 — Evaluation parity | `evaluateAt` on the no-reduce polynomial agrees with the reduced path. |
24
+ | 6 — Dispatch | `EGPTPolynomial.multiply` auto-detects all-composite inputs and routes to `multiplyNoReduce`. |
25
+ | 8 — Congruence vs equality | `EGPTMath.equals` is value equality; `EGPTMath.congruent` is structural identity — distinguishes `3×(1/3)` from `1` even though their values agree. |
26
+
27
+ All computation uses the `math` builtin — no URL imports.
28
+ </cell>
29
+
30
+ <cell type="markdown">
31
+ ## Setup — test harness and shared helpers
32
+
33
+ A minimal inline harness accumulates pass/fail counts for all phase cells to consume.
34
+ `TestFramework` from the original source is not on the SDK surface, so it is inlined here.
35
+ </cell>
36
+
37
+ <cell type="js" lane="math" id="setup" out="suite">
38
+ const { math } = caps;
39
+ const {
40
+ EGPTReal,
41
+ EGPTMath,
42
+ EGPTPolynomial,
43
+ EGPTPrimeComposite
44
+ } = math;
45
+
46
+ // Shared constructors.
47
+ const intN = (n) => EGPTReal.fromBigInt(BigInt(n));
48
+ const frac = (n, d) => EGPTReal.fromRational(BigInt(n), BigInt(d));
49
+
50
+ // Inline test harness (mirrors TestFramework from EGPTTestSuite.js).
51
+ // Accumulates across phase cells; the final summary cell reads getResults().
52
+ let passed = 0, failed = 0;
53
+ const failures = [];
54
+ const categoryMap = {}; // category -> { passed, total }
55
+
56
+ function test(description, category, fn) {
57
+ if (!categoryMap[category]) categoryMap[category] = { passed: 0, total: 0 };
58
+ categoryMap[category].total++;
59
+ try {
60
+ const result = fn();
61
+ if (result === false) throw new Error('returned false');
62
+ passed++;
63
+ categoryMap[category].passed++;
64
+ console.log(' ok — ' + description);
65
+ } catch (err) {
66
+ failed++;
67
+ failures.push({ description, category, error: err.message });
68
+ console.log(' FAIL — ' + description + ' (' + err.message + ')');
69
+ }
70
+ }
71
+
72
+ function getResults() {
73
+ return { passed, failed, failures, categoryMap };
74
+ }
75
+
76
+ return { suite: { test, getResults, EGPTReal, EGPTMath, EGPTPolynomial, EGPTPrimeComposite, intN, frac } };
77
+ </cell>
78
+
79
+ <cell type="markdown">
80
+ ## Phase 1 — `fromPrimeRecords` round-trip (Construction)
81
+
82
+ `EGPTPrimeComposite.fromPrimeRecords(records)` takes an explicit list of
83
+ `{ sign, prime, location }` records and constructs a composite whose value is the product of
84
+ all the primes (positive in the numerator, negative in the denominator). The key guarantee is
85
+ that `getSignedPrimes()` returns the same multiset of records back — no normalization, no
86
+ reduction.
87
+
88
+ The canonical multiset key (`recordsMultisetKey`) is a static helper on `EGPTPrimeComposite`
89
+ itself; it sorts and serializes the records into a canonical string so that set equality can be
90
+ checked with `===`.
91
+ </cell>
92
+
93
+ <cell type="js" lane="math" id="phase-1" in="suite" out="p1">
94
+ const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
95
+
96
+ // Alias for terseness — matches the source's local alias.
97
+ const recordsMultisetKey = EGPTPrimeComposite.recordsMultisetKey;
98
+
99
+ console.log('=== Phase 1 — fromPrimeRecords round-trip ===');
100
+
101
+ test('fromPrimeRecords: empty records -> sign 1, value 1', 'Construction', () => {
102
+ const c = EGPTPrimeComposite.fromPrimeRecords([]);
103
+ return c.getSignedPrimes().length === 0 &&
104
+ c.getSign() === 1 &&
105
+ c.equals(EGPTReal.fromBigInt(1n));
106
+ });
107
+
108
+ test('fromPrimeRecords: [{1,3,num}] -> 3', 'Construction', () => {
109
+ const c = EGPTPrimeComposite.fromPrimeRecords([
110
+ { sign: 1, prime: 3n, location: 'numerator' }
111
+ ]);
112
+ return c.equals(EGPTReal.fromBigInt(3n)) &&
113
+ c.getSignedPrimes().length === 1 &&
114
+ c.getSignedPrimes()[0].prime === 3n &&
115
+ c.getSignedPrimes()[0].location === 'numerator';
116
+ });
117
+
118
+ test('fromPrimeRecords round-trip preserves multiset', 'Construction', () => {
119
+ const records = [
120
+ { sign: 1, prime: 2n, location: 'numerator' },
121
+ { sign: 1, prime: 5n, location: 'denominator' },
122
+ { sign: 1, prime: 7n, location: 'numerator' }
123
+ ];
124
+ const c = EGPTPrimeComposite.fromPrimeRecords(records);
125
+ const recovered = c.getSignedPrimes().map(r => ({
126
+ sign: r.sign, prime: r.prime, location: r.location
127
+ }));
128
+ return recordsMultisetKey(recovered) === recordsMultisetKey(records);
129
+ });
130
+
131
+ return { p1: getResults() };
132
+ </cell>
133
+
134
+ <cell type="markdown">
135
+ ## Phase 2 — `fromEGPTReal` round-trip (Factoring)
136
+
137
+ `fromEGPTReal` trial-divides an `EGPTReal`'s un-reduced numerator and denominator into prime
138
+ records. The test cases cover the four sign/location combinations and a multi-prime integer:
139
+
140
+ - Positive integer `3` → one numerator record `{3}`.
141
+ - Reciprocal `1/3` → one denominator record `{3}`.
142
+ - Negative integer `-3` → sign = −1, one numerator record `{3}`.
143
+ - Negative reciprocal `−1/3` → sign = −1, one denominator record `{3}`.
144
+ - Composite `12 = 2² × 3` → numerator records `{2:2, 3:1}`.
145
+ </cell>
146
+
147
+ <cell type="js" lane="math" id="phase-2" in="suite" out="p2">
148
+ const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
149
+
150
+ console.log('=== Phase 2 — fromEGPTReal round-trip ===');
151
+
152
+ test('fromEGPTReal(3n) -> numerator {3:1}', 'Factoring', () => {
153
+ const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
154
+ const num = c.getPrimesInNumerator();
155
+ const den = c.getPrimesInDenominator();
156
+ return c.getSign() === 1 &&
157
+ num.size === 1 && num.get(3n) === 1n &&
158
+ den.size === 0;
159
+ });
160
+
161
+ test('fromEGPTReal(1/3) -> denominator {3:1}', 'Factoring', () => {
162
+ const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(1n, 3n));
163
+ const num = c.getPrimesInNumerator();
164
+ const den = c.getPrimesInDenominator();
165
+ return c.getSign() === 1 &&
166
+ num.size === 0 &&
167
+ den.size === 1 && den.get(3n) === 1n;
168
+ });
169
+
170
+ test('fromEGPTReal(-3n) -> sign -1, numerator {3:1}', 'Factoring', () => {
171
+ const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
172
+ const num = c.getPrimesInNumerator();
173
+ return c.getSign() === -1 &&
174
+ num.size === 1 && num.get(3n) === 1n &&
175
+ c.equals(EGPTReal.fromBigInt(-3n));
176
+ });
177
+
178
+ test('fromEGPTReal(-1/3) -> sign -1, denominator {3:1}', 'Factoring', () => {
179
+ const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
180
+ const den = c.getPrimesInDenominator();
181
+ return c.getSign() === -1 &&
182
+ den.size === 1 && den.get(3n) === 1n &&
183
+ c.equals(EGPTReal.fromRational(-1n, 3n));
184
+ });
185
+
186
+ test('fromEGPTReal(12n) -> {2:2, 3:1}', 'Factoring', () => {
187
+ const c = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(12n));
188
+ const num = c.getPrimesInNumerator();
189
+ return num.get(2n) === 2n && num.get(3n) === 1n && num.size === 2;
190
+ });
191
+
192
+ return { p2: getResults() };
193
+ </cell>
194
+
195
+ <cell type="markdown">
196
+ ## Phase 3 — AP4 acceptance: constant term preserves both `{3:num}` and `{3:den}`
197
+
198
+ The **AP4 anti-pattern** is the bug where multiplying `(Y − 3) × (Y − 1/3)` reduces the
199
+ constant term `(−3) × (−1/3) = 1` immediately, discarding the information that prime `3`
200
+ appeared in both the numerator (from the factor `−3`) and the denominator (from `−1/3`).
201
+
202
+ For the BIPP bijection this is fatal: the SAT witness recovery step reads back which primes
203
+ appeared in which location to determine literal polarity. A reduced `1` carries no polarity
204
+ information.
205
+
206
+ `EGPTPolynomial.multiplyNoReduce` multiplies two `EGPTPrimeComposite[]` coefficient arrays
207
+ without ever reducing the coefficients — the constant term of the product must carry both
208
+ `{prime: 3, location: numerator}` AND `{prime: 3, location: denominator}` as distinct records,
209
+ even though the value is `1`. The sibling test for primes `5` confirms the pattern generalizes.
210
+ </cell>
211
+
212
+ <cell type="js" lane="math" id="phase-3" in="suite" out="p3">
213
+ const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
214
+
215
+ console.log('=== Phase 3 — AP4 acceptance ===');
216
+
217
+ test('AP4: (Y - 3) * (Y - 1/3) constant term keeps {3 in num, 3 in den}', 'AP4 Acceptance', () => {
218
+ const negThree = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
219
+ const negOneThird = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
220
+ const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
221
+
222
+ const polyA = [negThree, onePos]; // (Y - 3)
223
+ const polyB = [negOneThird, onePos]; // (Y - 1/3)
224
+
225
+ const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
226
+
227
+ const constTerm = product[0];
228
+ if (!(constTerm instanceof EGPTPrimeComposite)) {
229
+ throw new Error('Constant term is not EGPTPrimeComposite.');
230
+ }
231
+ const records = constTerm.getSignedPrimes();
232
+ const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
233
+ const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
234
+ if (!has3InNum) throw new Error('Missing record {prime: 3, location: numerator}.');
235
+ if (!has3InDen) throw new Error('Missing record {prime: 3, location: denominator}.');
236
+ if (!constTerm.equals(EGPTReal.fromBigInt(1n))) {
237
+ throw new Error('Constant term value should equal 1.');
238
+ }
239
+ return true;
240
+ });
241
+
242
+ test('AP4 sibling: (Y - 5) * (Y - 1/5) constant term keeps {5 in num, 5 in den}', 'AP4 Acceptance', () => {
243
+ const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
244
+ const polyA = [
245
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-5n)),
246
+ onePos
247
+ ];
248
+ const polyB = [
249
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 5n)),
250
+ onePos
251
+ ];
252
+ const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
253
+ const records = product[0].getSignedPrimes();
254
+ const has5InNum = records.some(r => r.prime === 5n && r.location === 'numerator');
255
+ const has5InDen = records.some(r => r.prime === 5n && r.location === 'denominator');
256
+ return has5InNum && has5InDen && product[0].equals(EGPTReal.fromBigInt(1n));
257
+ });
258
+
259
+ return { p3: getResults() };
260
+ </cell>
261
+
262
+ <cell type="markdown">
263
+ ## Phase 4 — Commutativity and division location-flip
264
+
265
+ Multiplication must be commutative at the **multiset** level: `ab` and `ba` must have the same
266
+ multiset of records and the same sign. The test uses `recordsMultisetKey` to compare the
267
+ sorted canonical string representations.
268
+
269
+ Division is multiplication by the reciprocal: the divisor's records have their `location` field
270
+ flipped (`numerator ↔ denominator`) before concatenation. So `3 ÷ 3` should produce a composite
271
+ whose records include both `{prime: 3, location: numerator}` (from the dividend) and
272
+ `{prime: 3, location: denominator}` (from the flipped divisor), while the value is exactly `1`.
273
+ </cell>
274
+
275
+ <cell type="js" lane="math" id="phase-4" in="suite" out="p4">
276
+ const { test, getResults, EGPTReal, EGPTPrimeComposite } = inputs.suite;
277
+ const recordsMultisetKey = EGPTPrimeComposite.recordsMultisetKey;
278
+
279
+ console.log('=== Phase 4 — Commutativity and division location-flip ===');
280
+
281
+ test('multiply commutativity (record multiset equality)', 'Commutativity', () => {
282
+ const a = EGPTPrimeComposite.fromPrimeRecords([
283
+ { sign: 1, prime: 2n, location: 'numerator' },
284
+ { sign: 1, prime: 7n, location: 'denominator' }
285
+ ]);
286
+ const b = EGPTPrimeComposite.fromPrimeRecords([
287
+ { sign: 1, prime: 3n, location: 'numerator' },
288
+ { sign: 1, prime: 11n, location: 'numerator' }
289
+ ]);
290
+ const ab = a.multiply(b);
291
+ const ba = b.multiply(a);
292
+ return recordsMultisetKey(ab.getSignedPrimes()) === recordsMultisetKey(ba.getSignedPrimes()) &&
293
+ ab.getSign() === ba.getSign();
294
+ });
295
+
296
+ test('multiply with sign: (-3) * (-1/3) records contain both {3:num} and {3:den}', 'Commutativity', () => {
297
+ const a = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n));
298
+ const b = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n));
299
+ const ab = a.multiply(b);
300
+ const ba = b.multiply(a);
301
+ return recordsMultisetKey(ab.getSignedPrimes()) === recordsMultisetKey(ba.getSignedPrimes()) &&
302
+ ab.getSign() === 1 && ba.getSign() === 1;
303
+ });
304
+
305
+ test('divide flips locations: (3/1) / (3/1) -> {3:num, 3:den}', 'Division', () => {
306
+ const a = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
307
+ const b = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(3n));
308
+ const q = a.divide(b);
309
+ const records = q.getSignedPrimes();
310
+ const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
311
+ const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
312
+ return has3InNum && has3InDen && q.equals(EGPTReal.fromBigInt(1n));
313
+ });
314
+
315
+ return { p4: getResults() };
316
+ </cell>
317
+
318
+ <cell type="markdown">
319
+ ## Phase 5 — `evaluateAt` parity with the reduced path
320
+
321
+ The AP4 constant-term check (Phase 3) confirms that the no-reduce product *structurally*
322
+ preserves prime records. This phase checks that it is also *numerically* correct: evaluating
323
+ the no-reduce polynomial at any point `x` must agree with evaluating the fully-reduced
324
+ polynomial at the same point.
325
+
326
+ The test builds `(Y − 3)(Y − 1/3)` two ways:
327
+
328
+ - **Reduced:** `EGPTPolynomial.multiply` on plain `EGPTReal` coefficients — the standard path
329
+ that combines terms via `EGPTMath.add`.
330
+ - **No-reduce:** `EGPTPolynomial.multiplyNoReduce` on `EGPTPrimeComposite` coefficients — the
331
+ provenance-preserving path.
332
+
333
+ Both polynomials are evaluated at `x = 7`. They must return the same value.
334
+
335
+ A second test verifies a concrete numeric result: `(Y − 2)(Y − 5)` at `x = 7` should equal
336
+ `(7 − 2)(7 − 5) = 10`.
337
+ </cell>
338
+
339
+ <cell type="js" lane="math" id="phase-5" in="suite" out="p5">
340
+ const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
341
+
342
+ console.log('=== Phase 5 — evaluateAt parity ===');
343
+
344
+ test('evaluateAt(reduced poly, x) === evaluateAt(no-reduce poly, x) on a monomial expansion', 'Evaluation', () => {
345
+ const negThree_en = EGPTReal.fromBigInt(-3n);
346
+ const negOneThird_en = EGPTReal.fromRational(-1n, 3n);
347
+ const one_en = EGPTReal.fromBigInt(1n);
348
+
349
+ const reducedPoly = EGPTPolynomial.multiply(
350
+ [negThree_en, one_en],
351
+ [negOneThird_en, one_en]
352
+ );
353
+
354
+ const noReducePoly = EGPTPolynomial.multiplyNoReduce(
355
+ [
356
+ EGPTPrimeComposite.fromEGPTReal(negThree_en),
357
+ EGPTPrimeComposite.fromEGPTReal(one_en)
358
+ ],
359
+ [
360
+ EGPTPrimeComposite.fromEGPTReal(negOneThird_en),
361
+ EGPTPrimeComposite.fromEGPTReal(one_en)
362
+ ]
363
+ );
364
+
365
+ const x = EGPTReal.fromBigInt(7n);
366
+ const reducedValue = EGPTPolynomial.evaluateAt(reducedPoly, x);
367
+ const noReduceValue = EGPTPolynomial.evaluateAt(noReducePoly, x);
368
+ return reducedValue.equals(noReduceValue);
369
+ });
370
+
371
+ test('evaluateAt: (Y - 2)(Y - 5) at x=7 equals (7-2)(7-5) = 10 (no-reduce)', 'Evaluation', () => {
372
+ const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
373
+ const polyA = [
374
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-2n)),
375
+ onePos
376
+ ];
377
+ const polyB = [
378
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-5n)),
379
+ onePos
380
+ ];
381
+ const product = EGPTPolynomial.multiplyNoReduce(polyA, polyB);
382
+ const x = EGPTReal.fromBigInt(7n);
383
+ const v = EGPTPolynomial.evaluateAt(product, x);
384
+ return v.equals(EGPTReal.fromBigInt(10n));
385
+ });
386
+
387
+ return { p5: getResults() };
388
+ </cell>
389
+
390
+ <cell type="markdown">
391
+ ## Phase 6 — Auto-dispatch via `EGPTPolynomial.multiply`
392
+
393
+ `EGPTPolynomial.multiply` detects whether all coefficients are `EGPTPrimeComposite` instances
394
+ and, if so, routes to `multiplyNoReduce` automatically. This means callers can use the single
395
+ `multiply` entry point — the dispatch is transparent.
396
+
397
+ A regression guard confirms that plain `EGPTReal` inputs still take the reducing path:
398
+ `[1, 2] × [3, 4] = [3, 10, 8]` (the canonical polynomial multiplication check).
399
+ </cell>
400
+
401
+ <cell type="js" lane="math" id="phase-6" in="suite" out="p6">
402
+ const { test, getResults, EGPTReal, EGPTPolynomial, EGPTPrimeComposite } = inputs.suite;
403
+
404
+ console.log('=== Phase 6 — auto-dispatch via EGPTPolynomial.multiply ===');
405
+
406
+ test('EGPTPolynomial.multiply auto-dispatches to no-reduce when all coeffs are composites', 'Dispatch', () => {
407
+ const onePos = EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(1n));
408
+ const polyA = [
409
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromBigInt(-3n)),
410
+ onePos
411
+ ];
412
+ const polyB = [
413
+ EGPTPrimeComposite.fromEGPTReal(EGPTReal.fromRational(-1n, 3n)),
414
+ onePos
415
+ ];
416
+ const product = EGPTPolynomial.multiply(polyA, polyB);
417
+ if (!(product[0] instanceof EGPTPrimeComposite)) {
418
+ throw new Error('Expected EGPTPrimeComposite constant term via auto-dispatch.');
419
+ }
420
+ const records = product[0].getSignedPrimes();
421
+ const has3InNum = records.some(r => r.prime === 3n && r.location === 'numerator');
422
+ const has3InDen = records.some(r => r.prime === 3n && r.location === 'denominator');
423
+ return has3InNum && has3InDen;
424
+ });
425
+
426
+ test('EGPTPolynomial.multiply on plain EGPTReal coefficients still reduces (regression guard)', 'Dispatch', () => {
427
+ // [1, 2] * [3, 4] = [3, 10, 8]
428
+ const poly1 = [EGPTReal.fromBigInt(1n), EGPTReal.fromBigInt(2n)];
429
+ const poly2 = [EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(4n)];
430
+ const result = EGPTPolynomial.multiply(poly1, poly2);
431
+ const expected = [EGPTReal.fromBigInt(3n), EGPTReal.fromBigInt(10n), EGPTReal.fromBigInt(8n)];
432
+ return EGPTPolynomial.equals(result, expected);
433
+ });
434
+
435
+ return { p6: getResults() };
436
+ </cell>
437
+
438
+ <cell type="markdown">
439
+ ## Phase 8 — Congruence vs value equality
440
+
441
+ `EGPTMath.equals` asks whether two values are *numerically* equal — `3 × (1/3)` and `1` both
442
+ have value `1`, so `equals` is `true`. `EGPTMath.congruent` asks whether two values have the
443
+ *same canonical lift* — the same multiset of prime records. A composite built from
444
+ `{3:numerator, 3:denominator}` records has a different lift than a plain `EGPTReal(1)` (which
445
+ has no records), so `congruent` is `false` even though `equals` is `true`.
446
+
447
+ This distinction is first-class in `EGPTMath` (not only on `EGPTPrimeComposite`) as of T5.
448
+ The `congruentTo` instance method on `EGPTPrimeComposite` is the per-instance form of the same
449
+ predicate.
450
+
451
+ Key rules:
452
+ - `equals` is always value equality (the reduced rational comparison).
453
+ - `congruent(a, b)` where both are plain `EGPTReal` degenerates to `equals` (both have the
454
+ trivial lift).
455
+ - `congruent` is sign-sensitive: `{+3:num}` and `{-3:num}` are not congruent.
456
+ - `congruent(a, a)` is always `true` (self-congruence).
457
+ </cell>
458
+
459
+ <cell type="js" lane="math" id="phase-8" in="suite" out="p8">
460
+ const { test, getResults, EGPTReal, EGPTMath, EGPTPrimeComposite } = inputs.suite;
461
+
462
+ console.log('=== Phase 8 — congruent vs equals ===');
463
+
464
+ test('equals is true for 3 * (1/3) and 1 (value equality)', 'Congruence', () => {
465
+ const lhs = EGPTPrimeComposite.fromPrimeRecords([
466
+ { sign: 1, prime: 3n, location: 'numerator' },
467
+ { sign: 1, prime: 3n, location: 'denominator' }
468
+ ]);
469
+ const rhs = EGPTReal.fromBigInt(1n);
470
+ return EGPTMath.equals(lhs, rhs);
471
+ });
472
+
473
+ test('congruent is FALSE for 3 * (1/3) and 1 (different lifts)', 'Congruence', () => {
474
+ const lhs = EGPTPrimeComposite.fromPrimeRecords([
475
+ { sign: 1, prime: 3n, location: 'numerator' },
476
+ { sign: 1, prime: 3n, location: 'denominator' }
477
+ ]);
478
+ const rhs = EGPTReal.fromBigInt(1n);
479
+ return !EGPTMath.congruent(lhs, rhs);
480
+ });
481
+
482
+ test('congruent is true for two equally-built composites (order-invariant)', 'Congruence', () => {
483
+ const a = EGPTPrimeComposite.fromPrimeRecords([
484
+ { sign: 1, prime: 3n, location: 'numerator' },
485
+ { sign: 1, prime: 3n, location: 'denominator' }
486
+ ]);
487
+ const b = EGPTPrimeComposite.fromPrimeRecords([
488
+ { sign: 1, prime: 3n, location: 'denominator' },
489
+ { sign: 1, prime: 3n, location: 'numerator' } // order swapped
490
+ ]);
491
+ return EGPTMath.congruent(a, b);
492
+ });
493
+
494
+ test('congruent is false when sign differs even if records match', 'Congruence', () => {
495
+ const a = EGPTPrimeComposite.fromPrimeRecords([
496
+ { sign: 1, prime: 3n, location: 'numerator' }
497
+ ]);
498
+ const b = EGPTPrimeComposite.fromPrimeRecords([
499
+ { sign: -1, prime: 3n, location: 'numerator' }
500
+ ]);
501
+ return !EGPTMath.congruent(a, b);
502
+ });
503
+
504
+ test('congruent collapses to equals on plain EGPTReal pair', 'Congruence', () => {
505
+ const a = EGPTReal.fromBigInt(3n);
506
+ const b = EGPTReal.fromBigInt(3n);
507
+ const c = EGPTReal.fromBigInt(4n);
508
+ return EGPTMath.congruent(a, b) && !EGPTMath.congruent(a, c);
509
+ });
510
+
511
+ test('congruentTo instance method matches EGPTMath.congruent', 'Congruence', () => {
512
+ const composite = EGPTPrimeComposite.fromPrimeRecords([
513
+ { sign: 1, prime: 3n, location: 'numerator' },
514
+ { sign: 1, prime: 3n, location: 'denominator' }
515
+ ]);
516
+ const plain = EGPTReal.fromBigInt(1n);
517
+ return composite.congruentTo(composite) // self-congruent
518
+ && !composite.congruentTo(plain) // mixed lifts differ
519
+ && composite.equals(plain); // values agree
520
+ });
521
+
522
+ return { p8: getResults() };
523
+ </cell>
524
+
525
+ <cell type="markdown">
526
+ ## Summary — total pass / fail across all phases
527
+ </cell>
528
+
529
+ <cell type="js" lane="math" id="summary" in="p8">
530
+ // p8 holds the cumulative getResults() snapshot (all phases accumulate into the shared harness).
531
+ const { passed, failed, failures, categoryMap } = inputs.p8;
532
+ const total = passed + failed;
533
+
534
+ console.log('');
535
+ console.log('='.repeat(60));
536
+ console.log('TEST SUMMARY');
537
+ console.log('='.repeat(60));
538
+
539
+ for (const [cat, { passed: cp, total: ct }] of Object.entries(categoryMap)) {
540
+ console.log(` ${cat}: ${cp}/${ct} passed`);
541
+ }
542
+
543
+ console.log('-'.repeat(60));
544
+ console.log(`TOTAL: ${passed}/${total} tests passed`);
545
+ console.log(`SUCCESS RATE: ${((passed / total) * 100).toFixed(1)}%`);
546
+
547
+ if (failures.length > 0) {
548
+ console.log('');
549
+ console.log('FAILED TESTS:');
550
+ failures.forEach(f => {
551
+ console.log(` ${f.category}: ${f.description}`);
552
+ if (f.error) console.log(` Error: ${f.error}`);
553
+ });
554
+ }
555
+
556
+ console.log('='.repeat(60));
557
+
558
+ if (failed > 0) throw new Error('[EGPTPrimeCompositeTest] ' + failed + ' test(s) failed');
559
+ </cell>
560
+
561
+ </frqtl-notebook>