@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,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>
|