@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,542 @@
|
|
|
1
|
+
<frqtl-notebook version="1" title="Prime Atom Polynomial — LFTA Bijection and Sparsity Bound">
|
|
2
|
+
|
|
3
|
+
<cell type="markdown">
|
|
4
|
+
# Prime Atom Polynomial — LFTA Bijection and Sparsity Bound
|
|
5
|
+
|
|
6
|
+
The **Logarithmic Fundamental Theorem of Arithmetic (LFTA)** says that every natural number N decomposes additively in log-space:
|
|
7
|
+
|
|
8
|
+
> log₂ N = Σ_{p | N} v_p(N) · log₂ p
|
|
9
|
+
|
|
10
|
+
where v_p(N) is the p-adic valuation of N (how many times the prime p divides N). In the "numbers as waves" framing, each prime is a **pure frequency** and a composite N is a **superposition** of those frequencies with integer amplitudes v_p(N).
|
|
11
|
+
|
|
12
|
+
`PrimeAtomPolynomial` exposes this bijection as a first-class object in the EGPTMath chain. Its methods are thin wrappers over integer operations that the Lean chain already proves:
|
|
13
|
+
|
|
14
|
+
- `factorize(N)` → the coefficient vector `[{ prime, exponent }, …]`
|
|
15
|
+
- `totient(N)` → Euler's φ(N) from the coefficient vector alone (no arithmetic on N needed)
|
|
16
|
+
- `divisorsOf(n)` → sorted divisors
|
|
17
|
+
- `omega(N)` / `bigOmega(N)` → distinct prime count / total atom count with multiplicity
|
|
18
|
+
- `bitLength(N)` → ⌊log₂ N⌋ + 1 (the sparsity-degree bound)
|
|
19
|
+
- `logspaceSum(N)` → pedagogical log-space string
|
|
20
|
+
|
|
21
|
+
A key **sparsity bound** falls out of the LFTA: Ω(N) ≤ bitLength(N). Since 2 is the smallest prime atom, 2^(Ω(N)) ≤ N, so Ω(N) ≤ log₂ N < bitLength(N). This means that no matter how large N is, the factorization vector is never longer than its bit-length.
|
|
22
|
+
|
|
23
|
+
The final suite chains `PrimeAtomPolynomial` through a **prime-atom matMul** using the SDK's `factMultiply`, `reconstruct`, `refactorize`, and `makeMeter` helpers, demonstrating that multiplicative operations stay entirely in compressed (exponent-vector) space while additive accumulation forces visible boundary crossings.
|
|
24
|
+
|
|
25
|
+
*(Ported from `sdk/egpt-math-sdk/src/examples/reference/PrimeAtomPolynomialTest.js`. All compute reaches the SDK through the `math` builtin.)*
|
|
26
|
+
</cell>
|
|
27
|
+
|
|
28
|
+
<cell type="markdown">
|
|
29
|
+
## Setup — test harness
|
|
30
|
+
|
|
31
|
+
A minimal `test` / `assert` / `assertFactors` harness, inlined here (these are not on the SDK surface). Phase cells receive it as `suite` and use it to accumulate pass/fail counts.
|
|
32
|
+
</cell>
|
|
33
|
+
|
|
34
|
+
<cell type="js" lane="math" id="pap-setup" out="suite">
|
|
35
|
+
const { math } = caps;
|
|
36
|
+
const { PrimeAtomPolynomial } = math;
|
|
37
|
+
|
|
38
|
+
let passed = 0, failed = 0;
|
|
39
|
+
const failures = [];
|
|
40
|
+
|
|
41
|
+
function test(name, fn) {
|
|
42
|
+
try {
|
|
43
|
+
fn();
|
|
44
|
+
console.log(` ok — ${name}`);
|
|
45
|
+
passed++;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.log(` FAIL — ${name}\n ${err.message}`);
|
|
48
|
+
failures.push({ name, message: err.message });
|
|
49
|
+
failed++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assert(cond, msg) {
|
|
54
|
+
if (!cond) throw new Error(msg || 'assertion failed');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertFactors(actual, expected) {
|
|
58
|
+
if (actual.length !== expected.length) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`length ${actual.length} vs ${expected.length}; got ${JSON.stringify(actual.map(f => ({ p: String(f.prime), e: String(f.exponent) })))}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
for (let i = 0; i < actual.length; i++) {
|
|
64
|
+
if (actual[i].prime !== expected[i][0] || actual[i].exponent !== expected[i][1]) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`index ${i}: got (${actual[i].prime}, ${actual[i].exponent}) expected (${expected[i][0]}, ${expected[i][1]})`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('Test harness ready. PrimeAtomPolynomial loaded from math builtin.');
|
|
73
|
+
return { suite: { test, assert, assertFactors, getPassed: () => passed, getFailed: () => failed, getFailures: () => failures } };
|
|
74
|
+
</cell>
|
|
75
|
+
|
|
76
|
+
<cell type="markdown">
|
|
77
|
+
## Suite 1 — factorize / LFTA coefficient extraction
|
|
78
|
+
|
|
79
|
+
`factorize(N)` extracts the prime-atom coefficient vector of N. Each `{ prime, exponent }` entry is a (p, v_p(N)) pair: the prime and the number of times it divides N. The vector is sorted by prime in increasing order and contains only atoms with nonzero exponent.
|
|
80
|
+
|
|
81
|
+
- N = 1 has no prime factors → the empty vector (the "zero polynomial")
|
|
82
|
+
- N = 2 → the single atom (2, 1)
|
|
83
|
+
- N = 12 = 2²·3 → [(2,2), (3,1)]
|
|
84
|
+
- N = 391 = 17·23 → the §2 example from the LFTA markdown
|
|
85
|
+
- N = 1024 = 2¹⁰ → pure power of 2
|
|
86
|
+
- N = 210 = 2·3·5·7 → the primorial; all exponents are 1
|
|
87
|
+
- N = 97 (prime) → exits via the "n > 1 tail" branch of trial division
|
|
88
|
+
</cell>
|
|
89
|
+
|
|
90
|
+
<cell type="js" lane="math" id="pap-suite1" in="suite" out="suite">
|
|
91
|
+
const { math } = caps;
|
|
92
|
+
const { PrimeAtomPolynomial } = math;
|
|
93
|
+
const { test, assertFactors } = inputs.suite;
|
|
94
|
+
|
|
95
|
+
console.log('\n[Suite 1] factorize / LFTA coefficient extraction');
|
|
96
|
+
|
|
97
|
+
test('factorize(1) = [] (zero polynomial)', () => {
|
|
98
|
+
assertFactors(PrimeAtomPolynomial.factorize(1n), []);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('factorize(2) = [(2, 1)]', () => {
|
|
102
|
+
assertFactors(PrimeAtomPolynomial.factorize(2n), [[2n, 1n]]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('factorize(12) = [(2, 2), (3, 1)]', () => {
|
|
106
|
+
assertFactors(PrimeAtomPolynomial.factorize(12n), [[2n, 2n], [3n, 1n]]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('factorize(391) = [(17, 1), (23, 1)] (the §2 example in the markdown)', () => {
|
|
110
|
+
assertFactors(PrimeAtomPolynomial.factorize(391n), [[17n, 1n], [23n, 1n]]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('factorize(1024) = [(2, 10)] (pure power of 2)', () => {
|
|
114
|
+
assertFactors(PrimeAtomPolynomial.factorize(1024n), [[2n, 10n]]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('factorize(210) = [(2,1),(3,1),(5,1),(7,1)] (primorial)', () => {
|
|
118
|
+
assertFactors(PrimeAtomPolynomial.factorize(210n), [[2n, 1n], [3n, 1n], [5n, 1n], [7n, 1n]]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('factorize(97) = [(97, 1)] (large prime, exits via n > 1 tail)', () => {
|
|
122
|
+
assertFactors(PrimeAtomPolynomial.factorize(97n), [[97n, 1n]]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return { suite: inputs.suite };
|
|
126
|
+
</cell>
|
|
127
|
+
|
|
128
|
+
<cell type="markdown">
|
|
129
|
+
## Suite 2 — Euler's totient φ(N) from the LFTA coefficients
|
|
130
|
+
|
|
131
|
+
Once the factorization vector is in hand, φ(N) is a **closed form on the coefficients**:
|
|
132
|
+
|
|
133
|
+
> φ(N) = Π_p p^(v_p(N) − 1) · (p − 1)
|
|
134
|
+
|
|
135
|
+
No further arithmetic on N itself is needed. This is the LFTA giving us number-theoretic functions "for free" from the coefficient vector.
|
|
136
|
+
|
|
137
|
+
A quick spot-check table: φ(1)=1, φ(7)=6, φ(8)=4, φ(10)=4, φ(15)=8, φ(21)=12, φ(35)=24, φ(36)=12, φ(100)=40, φ(1024)=512.
|
|
138
|
+
</cell>
|
|
139
|
+
|
|
140
|
+
<cell type="js" lane="math" id="pap-suite2" in="suite" out="suite">
|
|
141
|
+
const { math } = caps;
|
|
142
|
+
const { PrimeAtomPolynomial } = math;
|
|
143
|
+
const { test } = inputs.suite;
|
|
144
|
+
|
|
145
|
+
console.log('\n[Suite 2] totient φ(N) from the LFTA coefficients');
|
|
146
|
+
|
|
147
|
+
function expectTotient(N, expected) {
|
|
148
|
+
const phi = PrimeAtomPolynomial.totient(N);
|
|
149
|
+
if (phi !== expected) {
|
|
150
|
+
throw new Error(`φ(${N}) = ${phi}, expected ${expected}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
test('φ(1) = 1', () => expectTotient(1n, 1n));
|
|
155
|
+
test('φ(7) = 6', () => expectTotient(7n, 6n));
|
|
156
|
+
test('φ(8) = 4', () => expectTotient(8n, 4n));
|
|
157
|
+
test('φ(10) = 4', () => expectTotient(10n, 4n));
|
|
158
|
+
test('φ(15) = 8', () => expectTotient(15n, 8n));
|
|
159
|
+
test('φ(21) = 12', () => expectTotient(21n, 12n));
|
|
160
|
+
test('φ(35) = 24', () => expectTotient(35n, 24n));
|
|
161
|
+
test('φ(36) = 12', () => expectTotient(36n, 12n));
|
|
162
|
+
test('φ(100) = 40', () => expectTotient(100n, 40n));
|
|
163
|
+
test('φ(2^10) = 512', () => expectTotient(1024n, 512n));
|
|
164
|
+
|
|
165
|
+
return { suite: inputs.suite };
|
|
166
|
+
</cell>
|
|
167
|
+
|
|
168
|
+
<cell type="markdown">
|
|
169
|
+
## Suite 3 — divisor enumeration (sorted, unique)
|
|
170
|
+
|
|
171
|
+
`divisorsOf(n)` returns every positive divisor of n in increasing order. In the order-finding context, this list is the search space the order finder walks: by Lagrange's theorem the multiplicative order of a in ℤ_N* divides φ(N), so the smallest divisor r with a^r ≡ 1 (mod N) is the order.
|
|
172
|
+
|
|
173
|
+
Key cases: 1 has one divisor (itself); a prime p has exactly two (1 and p); composites enumerate all divisors from the product of prime-power divisor lists.
|
|
174
|
+
</cell>
|
|
175
|
+
|
|
176
|
+
<cell type="js" lane="math" id="pap-suite3" in="suite" out="suite">
|
|
177
|
+
const { math } = caps;
|
|
178
|
+
const { PrimeAtomPolynomial } = math;
|
|
179
|
+
const { test } = inputs.suite;
|
|
180
|
+
|
|
181
|
+
console.log('\n[Suite 3] divisorsOf');
|
|
182
|
+
|
|
183
|
+
function expectDivisors(n, expected) {
|
|
184
|
+
const d = PrimeAtomPolynomial.divisorsOf(n).map(String);
|
|
185
|
+
if (d.length !== expected.length || !d.every((v, i) => v === String(expected[i]))) {
|
|
186
|
+
throw new Error(`divisorsOf(${n}) = [${d.join(',')}], expected [${expected.join(',')}]`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test('divisorsOf(1) = [1]', () => expectDivisors(1n, [1n]));
|
|
191
|
+
test('divisorsOf(6) = [1,2,3,6]', () => expectDivisors(6n, [1n, 2n, 3n, 6n]));
|
|
192
|
+
test('divisorsOf(12) = [1..12 divisors]', () =>
|
|
193
|
+
expectDivisors(12n, [1n, 2n, 3n, 4n, 6n, 12n]));
|
|
194
|
+
test('divisorsOf(24) = [1,2,3,4,6,8,12,24]', () =>
|
|
195
|
+
expectDivisors(24n, [1n, 2n, 3n, 4n, 6n, 8n, 12n, 24n]));
|
|
196
|
+
test('divisorsOf(prime 97) = [1, 97]', () => expectDivisors(97n, [1n, 97n]));
|
|
197
|
+
|
|
198
|
+
return { suite: inputs.suite };
|
|
199
|
+
</cell>
|
|
200
|
+
|
|
201
|
+
<cell type="markdown">
|
|
202
|
+
## Suite 4 — Sparsity bound: Ω(N) ≤ bitLength(N)
|
|
203
|
+
|
|
204
|
+
This is the key LFTA corollary that makes the prime-atom polynomial **sparse**: the total atom count with multiplicity (Ω(N) = Σ v_p(N)) never exceeds the bit-length of N.
|
|
205
|
+
|
|
206
|
+
**Why?** Since 2 is the smallest prime atom, 2^(Ω(N)) ≤ N, so Ω(N) ≤ log₂ N < bitLength(N).
|
|
207
|
+
|
|
208
|
+
The **tight case** is N = 2^k: here Ω(2^k) = k and bitLength(2^k) = k+1, so the bound is tight by exactly 1. This means we only need bitLength(N) "samples" (conditional-entropy tests = trial divisions) to read the entire polynomial.
|
|
209
|
+
|
|
210
|
+
We also verify that ω(N) ≤ Ω(N) for a sample of N: the distinct prime count is always no greater than the total atom count.
|
|
211
|
+
</cell>
|
|
212
|
+
|
|
213
|
+
<cell type="js" lane="math" id="pap-suite4" in="suite" out="suite">
|
|
214
|
+
const { math } = caps;
|
|
215
|
+
const { PrimeAtomPolynomial } = math;
|
|
216
|
+
const { test, assert } = inputs.suite;
|
|
217
|
+
|
|
218
|
+
console.log('\n[Suite 4] sparsity bound: Ω(N) ≤ bitLength(N)');
|
|
219
|
+
|
|
220
|
+
test('Ω(N) ≤ bitLength(N) for every N in [2, 200]', () => {
|
|
221
|
+
for (let N = 2n; N <= 200n; N++) {
|
|
222
|
+
const omega = PrimeAtomPolynomial.bigOmega(N);
|
|
223
|
+
const bits = BigInt(PrimeAtomPolynomial.bitLength(N));
|
|
224
|
+
assert(omega <= bits, `N=${N}: Ω=${omega} > bitLength=${bits}`);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('Ω(2^k) = k = bitLength(2^k) − 1 (tight case, smallest prime)', () => {
|
|
229
|
+
for (let k = 1n; k <= 20n; k++) {
|
|
230
|
+
const N = 1n << k;
|
|
231
|
+
const omega = PrimeAtomPolynomial.bigOmega(N);
|
|
232
|
+
const bits = BigInt(PrimeAtomPolynomial.bitLength(N));
|
|
233
|
+
assert(omega === k, `Ω(2^${k}) = ${omega}, expected ${k}`);
|
|
234
|
+
assert(bits === k + 1n, `bitLength(2^${k}) = ${bits}, expected ${k + 1n}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('ω(N) ≤ Ω(N) for a sample of N', () => {
|
|
239
|
+
for (const N of [6n, 12n, 30n, 60n, 180n, 1024n, 391n, 2310n]) {
|
|
240
|
+
const o = BigInt(PrimeAtomPolynomial.omega(N));
|
|
241
|
+
const O = PrimeAtomPolynomial.bigOmega(N);
|
|
242
|
+
assert(o <= O, `N=${N}: ω=${o} > Ω=${O}`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return { suite: inputs.suite };
|
|
247
|
+
</cell>
|
|
248
|
+
|
|
249
|
+
<cell type="markdown">
|
|
250
|
+
## Suite 5 — logspaceSum: pedagogical log-space string
|
|
251
|
+
|
|
252
|
+
`logspaceSum(N)` renders the LFTA decomposition as a human-readable string:
|
|
253
|
+
|
|
254
|
+
> 12 ≡ 2·log₂(2) + 1·log₂(3)
|
|
255
|
+
|
|
256
|
+
This makes the "N as a wave = weighted sum of pure-frequency logs" interpretation concrete and printable. The string format is exact (not approximate): each coefficient is the integer exponent, each base is the exact prime atom.
|
|
257
|
+
</cell>
|
|
258
|
+
|
|
259
|
+
<cell type="js" lane="math" id="pap-suite5" in="suite" out="suite">
|
|
260
|
+
const { math } = caps;
|
|
261
|
+
const { PrimeAtomPolynomial } = math;
|
|
262
|
+
const { test, assert } = inputs.suite;
|
|
263
|
+
|
|
264
|
+
console.log('\n[Suite 5] logspaceSum formatting');
|
|
265
|
+
|
|
266
|
+
test('logspaceSum(12) = "12 ≡ 2·log₂(2) + 1·log₂(3)"', () => {
|
|
267
|
+
const s = PrimeAtomPolynomial.logspaceSum(12n);
|
|
268
|
+
assert(s === '12 ≡ 2·log₂(2) + 1·log₂(3)', `got: ${s}`);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('logspaceSum(391) = "391 ≡ 1·log₂(17) + 1·log₂(23)"', () => {
|
|
272
|
+
const s = PrimeAtomPolynomial.logspaceSum(391n);
|
|
273
|
+
assert(s === '391 ≡ 1·log₂(17) + 1·log₂(23)', `got: ${s}`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return { suite: inputs.suite };
|
|
277
|
+
</cell>
|
|
278
|
+
|
|
279
|
+
<cell type="markdown">
|
|
280
|
+
## Suite 6 — Polynomial-system matMul chained through prime-atom factorization
|
|
281
|
+
|
|
282
|
+
This suite chains the polynomial-system construction from `MatrixAsPolynomial` **into** the prime-atom factorization discipline of `PrimeAtomPolynomial`. It uses the SDK's `factMultiply`, `reconstruct`, `refactorize`, and `makeMeter` helpers directly (they are on the SDK surface).
|
|
283
|
+
|
|
284
|
+
**Two levels of the ONE canonical chain:**
|
|
285
|
+
|
|
286
|
+
- **Outer level:** rows of A and B are polynomials in y. MatMul = c_i(y) = Σ_j A[i,j]·b_j(y).
|
|
287
|
+
- **Inner level:** every integer scalar A[i,j] and B[j,l] is carried as its prime-atom factorization vector v⃗(N). Scalar multiply = exponent-vector ADD (compressed-space). Scalar add = decode → int add → re-factorize (boundary).
|
|
288
|
+
|
|
289
|
+
**What the suite asserts:**
|
|
290
|
+
|
|
291
|
+
(a) Every scalar product at the prime-atom level reconstructs to the correct integer product (the LFTA bijection).
|
|
292
|
+
|
|
293
|
+
(b) The chained matMul agrees bit-exactly with the direct integer matMul.
|
|
294
|
+
|
|
295
|
+
(c) The boundary-crossing count matches the theoretical formula: m·n decodes + (k−1)·m·n adds + m·n re-factorizations, while compressed-space ops count m·n·k (one exponent-vector add per scalar multiply inside the polynomial-system sum).
|
|
296
|
+
|
|
297
|
+
Note: `factMultiply`, `reconstruct`, `refactorize`, and `makeMeter` are all on the SDK surface; `chainedMatMul` is a local orchestrator that drives them.
|
|
298
|
+
</cell>
|
|
299
|
+
|
|
300
|
+
<cell type="js" lane="math" id="pap-suite6-setup" out="chainedMatMul">
|
|
301
|
+
const { math } = caps;
|
|
302
|
+
const { factMultiply, reconstruct, refactorize } = math;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Chained matMul — polynomial-system at matrix level, prime-atom at scalar level.
|
|
306
|
+
*
|
|
307
|
+
* The outer loop is the polynomial-system matMul: c_i(y) = Σ_j A[i,j]·b_j(y).
|
|
308
|
+
* The inner scalar ops operate on factorization vectors v⃗.
|
|
309
|
+
*
|
|
310
|
+
* Input matrices carry their entries as factorization vectors.
|
|
311
|
+
* Output matrix C is returned with entries as factorization vectors.
|
|
312
|
+
*/
|
|
313
|
+
function chainedMatMul(vA, vB, meter) {
|
|
314
|
+
const m = vA.length;
|
|
315
|
+
const k = vA[0].length;
|
|
316
|
+
const n = vB[0].length;
|
|
317
|
+
const vC = new Array(m);
|
|
318
|
+
|
|
319
|
+
for (let i = 0; i < m; i++) {
|
|
320
|
+
// c_i(y) starts as the length-n row of ZEROs. At the prime-atom level,
|
|
321
|
+
// "zero" has no factorization — we store it as the literal integer 0
|
|
322
|
+
// and fall back to refactorize once we have a nonzero sum.
|
|
323
|
+
const c_i_int = new Array(n).fill(0n);
|
|
324
|
+
|
|
325
|
+
for (let j = 0; j < k; j++) {
|
|
326
|
+
// For each coefficient position q of b_j, compute scalar · coefficient
|
|
327
|
+
// at the prime-atom level (exponent-vector add), then accumulate into
|
|
328
|
+
// c_i at integer level (boundary crossing per accumulation).
|
|
329
|
+
for (let q = 0; q < n; q++) {
|
|
330
|
+
// prime-atom scalar multiply (compressed)
|
|
331
|
+
const productVec = factMultiply(vA[i][j], vB[j][q], meter);
|
|
332
|
+
// accumulator forces a boundary crossing — the honest cost of "+"
|
|
333
|
+
c_i_int[q] += reconstruct(productVec, meter);
|
|
334
|
+
meter.integerAdds += 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Encode each output coefficient back to prime-atom form. This is the
|
|
338
|
+
// second boundary crossing per output cell and closes the chain.
|
|
339
|
+
vC[i] = c_i_int.map(N => N === 0n ? [] : refactorize(N, meter));
|
|
340
|
+
}
|
|
341
|
+
return vC;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.log('chainedMatMul helper ready (uses SDK factMultiply, reconstruct, refactorize).');
|
|
345
|
+
return { chainedMatMul };
|
|
346
|
+
</cell>
|
|
347
|
+
|
|
348
|
+
<cell type="js" lane="math" id="pap-suite6" in="suite, chainedMatMul" out="suite">
|
|
349
|
+
const { math } = caps;
|
|
350
|
+
const { PrimeAtomPolynomial, factMultiply, reconstruct, refactorize, makeMeter } = math;
|
|
351
|
+
const { test, assert } = inputs.suite;
|
|
352
|
+
const { chainedMatMul } = inputs;
|
|
353
|
+
|
|
354
|
+
console.log('\n[Suite 6] polynomial-system matMul chained through prime-atom factorization');
|
|
355
|
+
|
|
356
|
+
test('6.1 scalar multiplication at the prime-atom level reconstructs correctly', () => {
|
|
357
|
+
// v⃗(12) · v⃗(18) via exponent-vector add must reconstruct to 216.
|
|
358
|
+
const meter = makeMeter();
|
|
359
|
+
const va = PrimeAtomPolynomial.factorize(12n);
|
|
360
|
+
const vb = PrimeAtomPolynomial.factorize(18n);
|
|
361
|
+
const vab = factMultiply(va, vb, meter);
|
|
362
|
+
const ab = reconstruct(vab, meter);
|
|
363
|
+
assert(ab === 216n, `12·18 should be 216, got ${ab}`);
|
|
364
|
+
assert(meter.compressedOps >= 1, 'expected at least one exponent-vector add');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('6.2 chained matMul on a 2×2 integer case agrees with direct matMul', () => {
|
|
368
|
+
// Entries chosen so each factorizes non-trivially, exercising the LFTA path.
|
|
369
|
+
const A = [[ 2n, 3n],
|
|
370
|
+
[ 5n, 7n]];
|
|
371
|
+
const B = [[ 4n, 9n],
|
|
372
|
+
[25n, 49n]];
|
|
373
|
+
|
|
374
|
+
// Encode entries into factorization-vector form (outside the meter so
|
|
375
|
+
// encoding cost is not conflated with engine cost).
|
|
376
|
+
const encMeter = makeMeter();
|
|
377
|
+
const vA = A.map(row => row.map(x => refactorize(x, encMeter)));
|
|
378
|
+
const vB = B.map(row => row.map(x => refactorize(x, encMeter)));
|
|
379
|
+
|
|
380
|
+
const engineMeter = makeMeter();
|
|
381
|
+
const vC = chainedMatMul(vA, vB, engineMeter);
|
|
382
|
+
|
|
383
|
+
// Direct integer matMul for the reference.
|
|
384
|
+
const Cref = [[0n, 0n], [0n, 0n]];
|
|
385
|
+
for (let i = 0; i < 2; i++) {
|
|
386
|
+
for (let l = 0; l < 2; l++) {
|
|
387
|
+
let acc = 0n;
|
|
388
|
+
for (let j = 0; j < 2; j++) acc += A[i][j] * B[j][l];
|
|
389
|
+
Cref[i][l] = acc;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Cross-check every entry. Reconstruction here is part of the assertion,
|
|
394
|
+
// not part of the engine — we use a throwaway meter for it.
|
|
395
|
+
const checkMeter = makeMeter();
|
|
396
|
+
for (let i = 0; i < 2; i++) {
|
|
397
|
+
for (let l = 0; l < 2; l++) {
|
|
398
|
+
const got = reconstruct(vC[i][l], checkMeter);
|
|
399
|
+
if (got !== Cref[i][l]) {
|
|
400
|
+
throw new Error(`C[${i},${l}] = ${got}, expected ${Cref[i][l]}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('6.3 chained matMul ops: boundary count matches m·n·(k+1), integer adds = m·n·k', () => {
|
|
407
|
+
// For the 2×2 · 2×2 case: m=n=k=2. The INVARIANT counts (independent of
|
|
408
|
+
// prime overlap between entries) are:
|
|
409
|
+
// integerAdds = m·n·k (one accumulator add per scalar product)
|
|
410
|
+
// boundaryOps = m·n·(k+1) (k reconstructs per cell + 1 refactorize)
|
|
411
|
+
// Entries: A[i,j] and B[j,l] use disjoint small primes so compressedOps stays 0.
|
|
412
|
+
const A = [[ 2n, 3n],
|
|
413
|
+
[11n, 13n]];
|
|
414
|
+
const B = [[ 5n, 7n],
|
|
415
|
+
[17n, 19n]];
|
|
416
|
+
|
|
417
|
+
const encMeter = makeMeter();
|
|
418
|
+
const vA = A.map(row => row.map(x => refactorize(x, encMeter)));
|
|
419
|
+
const vB = B.map(row => row.map(x => refactorize(x, encMeter)));
|
|
420
|
+
|
|
421
|
+
const meter = makeMeter();
|
|
422
|
+
chainedMatMul(vA, vB, meter);
|
|
423
|
+
|
|
424
|
+
const m = 2, k = 2, n = 2;
|
|
425
|
+
assert(meter.integerAdds === m * n * k,
|
|
426
|
+
`integerAdds: got ${meter.integerAdds}, expected ${m * n * k}`);
|
|
427
|
+
const expectedBoundary = m * n * (k + 1);
|
|
428
|
+
assert(meter.boundaryOps === expectedBoundary,
|
|
429
|
+
`boundaryOps: got ${meter.boundaryOps}, expected ${expectedBoundary}`);
|
|
430
|
+
// All four pairs here use distinct primes, so no exponent-vector add
|
|
431
|
+
// merges a shared prime — the factMultiply fast-path takes the "append"
|
|
432
|
+
// branch for every pair.
|
|
433
|
+
assert(meter.compressedOps === 0,
|
|
434
|
+
`compressedOps: got ${meter.compressedOps}, expected 0 (all pairs use disjoint primes)`);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('6.4 shared-prime entries exercise the compressed-space merge', () => {
|
|
438
|
+
// A = [[12, 18], [24, 36]], B = [[6, 10], [15, 21]] — lots of shared primes.
|
|
439
|
+
// Every pair (A[i,j], B[j,l]) shares at least one prime, so every
|
|
440
|
+
// factMultiply call records at least one compressed-space op.
|
|
441
|
+
const A = [[12n, 18n],
|
|
442
|
+
[24n, 36n]];
|
|
443
|
+
const B = [[ 6n, 10n],
|
|
444
|
+
[15n, 21n]];
|
|
445
|
+
|
|
446
|
+
const encMeter = makeMeter();
|
|
447
|
+
const vA = A.map(row => row.map(x => refactorize(x, encMeter)));
|
|
448
|
+
const vB = B.map(row => row.map(x => refactorize(x, encMeter)));
|
|
449
|
+
|
|
450
|
+
const meter = makeMeter();
|
|
451
|
+
const vC = chainedMatMul(vA, vB, meter);
|
|
452
|
+
|
|
453
|
+
// Reference.
|
|
454
|
+
const Cref = [[0n, 0n], [0n, 0n]];
|
|
455
|
+
for (let i = 0; i < 2; i++) {
|
|
456
|
+
for (let l = 0; l < 2; l++) {
|
|
457
|
+
let acc = 0n;
|
|
458
|
+
for (let j = 0; j < 2; j++) acc += A[i][j] * B[j][l];
|
|
459
|
+
Cref[i][l] = acc;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const checkMeter = makeMeter();
|
|
464
|
+
for (let i = 0; i < 2; i++) {
|
|
465
|
+
for (let l = 0; l < 2; l++) {
|
|
466
|
+
const got = reconstruct(vC[i][l], checkMeter);
|
|
467
|
+
if (got !== Cref[i][l]) {
|
|
468
|
+
throw new Error(`C[${i},${l}] = ${got}, expected ${Cref[i][l]}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
assert(meter.compressedOps >= 8,
|
|
473
|
+
`compressedOps: got ${meter.compressedOps}, expected >= 8 (every pair shares at least one prime)`);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('6.5 chained matMul distributes over (A, B) factorization encoding', () => {
|
|
477
|
+
// Encoding A and B entries into v⃗ form before the chain, or reconstructing
|
|
478
|
+
// them afterwards — both must land at the same integer matrix C. This is
|
|
479
|
+
// the LFTA bijection extended to matMul: the factorization-level chain
|
|
480
|
+
// commutes with integer reconstruction at the boundary.
|
|
481
|
+
const A = [[ 6n, 4n],
|
|
482
|
+
[10n, 15n]];
|
|
483
|
+
const B = [[ 9n, 8n],
|
|
484
|
+
[ 7n, 12n]];
|
|
485
|
+
|
|
486
|
+
const encMeter = makeMeter();
|
|
487
|
+
const vA = A.map(row => row.map(x => refactorize(x, encMeter)));
|
|
488
|
+
const vB = B.map(row => row.map(x => refactorize(x, encMeter)));
|
|
489
|
+
|
|
490
|
+
const meter = makeMeter();
|
|
491
|
+
const vC = chainedMatMul(vA, vB, meter);
|
|
492
|
+
|
|
493
|
+
const Cref = [[0n, 0n], [0n, 0n]];
|
|
494
|
+
for (let i = 0; i < 2; i++) {
|
|
495
|
+
for (let l = 0; l < 2; l++) {
|
|
496
|
+
for (let j = 0; j < 2; j++) Cref[i][l] += A[i][j] * B[j][l];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const checkMeter = makeMeter();
|
|
501
|
+
for (let i = 0; i < 2; i++) {
|
|
502
|
+
for (let l = 0; l < 2; l++) {
|
|
503
|
+
const got = reconstruct(vC[i][l], checkMeter);
|
|
504
|
+
if (got !== Cref[i][l]) {
|
|
505
|
+
throw new Error(`C[${i},${l}] = ${got}, expected ${Cref[i][l]}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return { suite: inputs.suite };
|
|
512
|
+
</cell>
|
|
513
|
+
|
|
514
|
+
<cell type="markdown">
|
|
515
|
+
## Summary
|
|
516
|
+
|
|
517
|
+
Total pass/fail counts and a FAIL-LOUD throw if any test failed.
|
|
518
|
+
</cell>
|
|
519
|
+
|
|
520
|
+
<cell type="js" lane="math" id="pap-summary" in="suite">
|
|
521
|
+
const { getPassed, getFailed, getFailures } = inputs.suite;
|
|
522
|
+
const passed = getPassed();
|
|
523
|
+
const failed = getFailed();
|
|
524
|
+
const failures = getFailures();
|
|
525
|
+
|
|
526
|
+
console.log('\n' + '='.repeat(60));
|
|
527
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
528
|
+
console.log('='.repeat(60));
|
|
529
|
+
|
|
530
|
+
if (failures.length > 0) {
|
|
531
|
+
console.log('\nFailed tests:');
|
|
532
|
+
for (const { name, message } of failures) {
|
|
533
|
+
console.log(` FAIL — ${name}`);
|
|
534
|
+
console.log(` ${message}`);
|
|
535
|
+
}
|
|
536
|
+
throw new Error(`[PrimeAtomPolynomialTest] ${failed} test(s) failed`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
console.log(`TOTAL: ${passed}/${passed + failed} passed — all tests OK`);
|
|
540
|
+
</cell>
|
|
541
|
+
|
|
542
|
+
</frqtl-notebook>
|