@aleph-ai/tinyaleph 1.3.0 → 1.4.1
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 +423 -12
- package/backends/cryptographic/index.js +455 -2
- package/core/beacon.js +735 -0
- package/core/crt-homology.js +1004 -0
- package/core/enochian-vocabulary.js +910 -0
- package/core/enochian.js +744 -0
- package/core/errors.js +587 -0
- package/core/hilbert.js +651 -1
- package/core/index.js +86 -1
- package/core/lambda.js +284 -33
- package/core/logger.js +350 -0
- package/core/prime.js +136 -1
- package/core/quaternion-semantics.js +623 -0
- package/core/reduction.js +391 -1
- package/core/rformer-crt.js +892 -0
- package/core/topology.js +655 -0
- package/docs/README.md +54 -0
- package/docs/design/PYTHON_PORT_DESIGN.md +1400 -0
- package/docs/reference/07-topology.md +257 -0
- package/docs/reference/08-observer.md +421 -0
- package/docs/reference/09-crt-homology.md +369 -0
- package/modular.js +231 -3
- package/package.json +1 -1
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRT-Homology Module for tinyaleph
|
|
3
|
+
*
|
|
4
|
+
* Implements:
|
|
5
|
+
* - Differentiable Chinese Remainder Theorem (CRT) reconstruction
|
|
6
|
+
* - Birkhoff polytope projection (doubly-stochastic attention)
|
|
7
|
+
* - Residue encoding over coprime moduli
|
|
8
|
+
* - Homology loss for detecting consistency failures (holes)
|
|
9
|
+
*
|
|
10
|
+
* Key Insight: "Holes are not degrees of freedom. Holes are consistency
|
|
11
|
+
* failures that persist under perturbation."
|
|
12
|
+
*
|
|
13
|
+
* Mathematical Framework:
|
|
14
|
+
* - r_k = softmax(W_k h + b_k) ∈ Δ(ℤ/p_k) (residue distribution)
|
|
15
|
+
* - L̂ = Σ_k E[r_k] * (P/p_k) * (P/p_k)^{-1} mod p_k (CRT reconstruction)
|
|
16
|
+
* - Ker(ℛ) = { r | ℛ(r) undefined } (obstruction cycles)
|
|
17
|
+
* - ℒ_homology = Σ_{cycles ∈ Ker(ℛ)} f(cycle) (homology loss)
|
|
18
|
+
*
|
|
19
|
+
* @module core/crt-homology
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const { firstNPrimes, isPrime, factorize } = require('./prime');
|
|
25
|
+
const { Complex, PrimeState } = require('./hilbert');
|
|
26
|
+
const { SparsePrimeState, resonanceScore, Quaternion } = require('./rformer');
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// MODULAR ARITHMETIC UTILITIES
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extended Euclidean Algorithm
|
|
34
|
+
* Returns { gcd, x, y } such that gcd = a*x + b*y
|
|
35
|
+
* @param {number} a - First number
|
|
36
|
+
* @param {number} b - Second number
|
|
37
|
+
* @returns {Object} { gcd, x, y }
|
|
38
|
+
*/
|
|
39
|
+
function extendedGCD(a, b) {
|
|
40
|
+
if (b === 0) {
|
|
41
|
+
return { gcd: a, x: 1, y: 0 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { gcd, x: x1, y: y1 } = extendedGCD(b, a % b);
|
|
45
|
+
const x = y1;
|
|
46
|
+
const y = x1 - Math.floor(a / b) * y1;
|
|
47
|
+
|
|
48
|
+
return { gcd, x, y };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Modular multiplicative inverse
|
|
53
|
+
* Returns a^{-1} mod m such that a * a^{-1} ≡ 1 (mod m)
|
|
54
|
+
* @param {number} a - Number to invert
|
|
55
|
+
* @param {number} m - Modulus
|
|
56
|
+
* @returns {number|null} Inverse if exists, null otherwise
|
|
57
|
+
*/
|
|
58
|
+
function modInverse(a, m) {
|
|
59
|
+
a = ((a % m) + m) % m; // Normalize to positive
|
|
60
|
+
const { gcd, x } = extendedGCD(a, m);
|
|
61
|
+
|
|
62
|
+
if (gcd !== 1) {
|
|
63
|
+
return null; // No inverse exists (not coprime)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return ((x % m) + m) % m;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if two numbers are coprime
|
|
71
|
+
* @param {number} a - First number
|
|
72
|
+
* @param {number} b - Second number
|
|
73
|
+
* @returns {boolean} True if gcd(a, b) = 1
|
|
74
|
+
*/
|
|
75
|
+
function areCoprime(a, b) {
|
|
76
|
+
return extendedGCD(a, b).gcd === 1;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Softmax function
|
|
81
|
+
* @param {Array<number>} logits - Input logits
|
|
82
|
+
* @returns {Array<number>} Probability distribution
|
|
83
|
+
*/
|
|
84
|
+
function softmax(logits) {
|
|
85
|
+
const maxLogit = Math.max(...logits);
|
|
86
|
+
const expLogits = logits.map(l => Math.exp(l - maxLogit));
|
|
87
|
+
const sumExp = expLogits.reduce((a, b) => a + b, 0);
|
|
88
|
+
return expLogits.map(e => e / (sumExp + 1e-10));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// RESIDUE ENCODER: r_k = softmax(W_k h + b_k) ∈ Δ(ℤ/p_k)
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Residue Encoder
|
|
97
|
+
*
|
|
98
|
+
* Encodes a hidden vector h into K residue distributions, one per prime modulus.
|
|
99
|
+
* Each residue r_k is a probability distribution over ℤ/p_k.
|
|
100
|
+
*/
|
|
101
|
+
class ResidueEncoder {
|
|
102
|
+
/**
|
|
103
|
+
* Create a residue encoder
|
|
104
|
+
* @param {Array<number>} primes - Array of coprime moduli [p_1, p_2, ..., p_K]
|
|
105
|
+
* @param {number} hiddenDim - Dimension of input hidden vector
|
|
106
|
+
* @param {Object} options - Configuration options
|
|
107
|
+
*/
|
|
108
|
+
constructor(primes, hiddenDim, options = {}) {
|
|
109
|
+
// Validate primes are coprime
|
|
110
|
+
for (let i = 0; i < primes.length; i++) {
|
|
111
|
+
for (let j = i + 1; j < primes.length; j++) {
|
|
112
|
+
if (!areCoprime(primes[i], primes[j])) {
|
|
113
|
+
throw new Error(`Primes ${primes[i]} and ${primes[j]} are not coprime`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.primes = primes;
|
|
119
|
+
this.K = primes.length;
|
|
120
|
+
this.hiddenDim = hiddenDim;
|
|
121
|
+
|
|
122
|
+
// Compute P = ∏ p_k
|
|
123
|
+
this.P = primes.reduce((a, b) => a * b, 1);
|
|
124
|
+
|
|
125
|
+
// Initialize weight matrices W_k (hiddenDim x p_k) and biases b_k
|
|
126
|
+
// In a real differentiable setting, these would be trainable parameters
|
|
127
|
+
this.weights = primes.map(p => this._initMatrix(hiddenDim, p, options.initScale || 0.1));
|
|
128
|
+
this.biases = primes.map(p => new Float64Array(p));
|
|
129
|
+
|
|
130
|
+
// Optional: learnable prime parameters
|
|
131
|
+
this.learnablePrimes = options.learnablePrimes || false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Initialize a weight matrix with small random values
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
_initMatrix(rows, cols, scale = 0.1) {
|
|
139
|
+
const matrix = [];
|
|
140
|
+
for (let i = 0; i < rows; i++) {
|
|
141
|
+
const row = new Float64Array(cols);
|
|
142
|
+
for (let j = 0; j < cols; j++) {
|
|
143
|
+
row[j] = (Math.random() - 0.5) * 2 * scale;
|
|
144
|
+
}
|
|
145
|
+
matrix.push(row);
|
|
146
|
+
}
|
|
147
|
+
return matrix;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Matrix-vector multiplication
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
_matVec(matrix, vec) {
|
|
155
|
+
const result = new Float64Array(matrix[0].length);
|
|
156
|
+
for (let j = 0; j < matrix[0].length; j++) {
|
|
157
|
+
for (let i = 0; i < matrix.length; i++) {
|
|
158
|
+
result[j] += matrix[i][j] * (vec[i] || 0);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Encode hidden vector to K residue distributions
|
|
166
|
+
* r_k = softmax(W_k^T h + b_k) ∈ Δ(ℤ/p_k)
|
|
167
|
+
*
|
|
168
|
+
* @param {Array<number>|Float64Array} h - Hidden vector of dimension hiddenDim
|
|
169
|
+
* @returns {Array<Float64Array>} Array of K probability distributions
|
|
170
|
+
*/
|
|
171
|
+
encode(h) {
|
|
172
|
+
return this.primes.map((p, k) => {
|
|
173
|
+
// Compute logits: W_k^T h + b_k
|
|
174
|
+
const logits = this._matVec(this.weights[k], h);
|
|
175
|
+
for (let i = 0; i < p; i++) {
|
|
176
|
+
logits[i] += this.biases[k][i];
|
|
177
|
+
}
|
|
178
|
+
// Apply softmax to get probability distribution over ℤ/p_k
|
|
179
|
+
return Float64Array.from(softmax(Array.from(logits)));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Compute expected residue E[r_k] = Σ_{i=0}^{p_k-1} i * r_k[i]
|
|
185
|
+
* @param {Float64Array} r_k - Residue distribution for modulus k
|
|
186
|
+
* @returns {number} Expected value
|
|
187
|
+
*/
|
|
188
|
+
expectedResidue(r_k) {
|
|
189
|
+
let sum = 0;
|
|
190
|
+
for (let i = 0; i < r_k.length; i++) {
|
|
191
|
+
sum += i * r_k[i];
|
|
192
|
+
}
|
|
193
|
+
return sum;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get all expected residues from encoded distributions
|
|
198
|
+
* @param {Array<Float64Array>} residues - Encoded residue distributions
|
|
199
|
+
* @returns {Array<number>} Expected residues [E[r_1], ..., E[r_K]]
|
|
200
|
+
*/
|
|
201
|
+
expectedResidues(residues) {
|
|
202
|
+
return residues.map(r => this.expectedResidue(r));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Encode from SparsePrimeState (integration with existing tinyaleph types)
|
|
207
|
+
* @param {SparsePrimeState} state - Sparse prime state
|
|
208
|
+
* @returns {Array<Float64Array>} Residue distributions
|
|
209
|
+
*/
|
|
210
|
+
encodeFromPrimeState(state) {
|
|
211
|
+
// Convert sparse state to dense hidden vector
|
|
212
|
+
const h = new Float64Array(this.hiddenDim);
|
|
213
|
+
const activePrimes = state.getActivePrimes();
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < Math.min(activePrimes.length, this.hiddenDim); i++) {
|
|
216
|
+
const p = activePrimes[i];
|
|
217
|
+
const act = state.get(p);
|
|
218
|
+
h[i] = act.amplitude.norm();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return this.encode(h);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// CRT RECONSTRUCTOR: ℛ(r) = Σ_k E[r_k] * (P/p_k) * (P/p_k)^{-1} mod P
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Chinese Remainder Theorem Reconstructor
|
|
231
|
+
*
|
|
232
|
+
* Reconstructs a unique value L̂ ∈ [0, P) from residues mod each p_k.
|
|
233
|
+
*/
|
|
234
|
+
class CRTReconstructor {
|
|
235
|
+
/**
|
|
236
|
+
* Create a CRT reconstructor
|
|
237
|
+
* @param {Array<number>} primes - Array of coprime moduli
|
|
238
|
+
*/
|
|
239
|
+
constructor(primes) {
|
|
240
|
+
this.primes = primes;
|
|
241
|
+
this.K = primes.length;
|
|
242
|
+
this.P = primes.reduce((a, b) => a * b, 1);
|
|
243
|
+
|
|
244
|
+
// Precompute CRT coefficients: M_k = P/p_k and M_k^{-1} mod p_k
|
|
245
|
+
this.coefficients = primes.map(p => {
|
|
246
|
+
const Mk = this.P / p;
|
|
247
|
+
const MkInv = modInverse(Mk, p);
|
|
248
|
+
if (MkInv === null) {
|
|
249
|
+
throw new Error(`Cannot compute modular inverse of ${Mk} mod ${p}`);
|
|
250
|
+
}
|
|
251
|
+
return { Mk, MkInv };
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Reconstruct value from expected residues (differentiable)
|
|
257
|
+
* L̂ = Σ_k E[r_k] * M_k * M_k^{-1} mod P
|
|
258
|
+
*
|
|
259
|
+
* @param {Array<number>} residues - Expected residues [E[r_1], ..., E[r_K]]
|
|
260
|
+
* @returns {number} Reconstructed value L̂
|
|
261
|
+
*/
|
|
262
|
+
reconstruct(residues) {
|
|
263
|
+
let L = 0;
|
|
264
|
+
for (let k = 0; k < this.K; k++) {
|
|
265
|
+
const { Mk, MkInv } = this.coefficients[k];
|
|
266
|
+
L += residues[k] * Mk * MkInv;
|
|
267
|
+
}
|
|
268
|
+
// Modular reduction (soft for differentiability in practice)
|
|
269
|
+
return ((L % this.P) + this.P) % this.P;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Compute reconstruction error (distance to nearest valid CRT value)
|
|
274
|
+
* ε(r) = |ℛ(r) - nearest_valid|
|
|
275
|
+
*
|
|
276
|
+
* @param {Array<number>} residues - Expected residues
|
|
277
|
+
* @returns {number} Reconstruction error
|
|
278
|
+
*/
|
|
279
|
+
reconstructionError(residues) {
|
|
280
|
+
const L = this.reconstruct(residues);
|
|
281
|
+
const nearestValid = Math.round(L);
|
|
282
|
+
return Math.abs(L - nearestValid);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Detect if residue tuple is in kernel (obstruction)
|
|
287
|
+
* Ker(ℛ) ≈ { r | ε(r) > τ }
|
|
288
|
+
*
|
|
289
|
+
* @param {Array<number>} residues - Expected residues
|
|
290
|
+
* @param {number} tau - Error threshold
|
|
291
|
+
* @returns {boolean} True if in kernel (obstruction detected)
|
|
292
|
+
*/
|
|
293
|
+
detectKernel(residues, tau = 0.1) {
|
|
294
|
+
const epsilon = this.reconstructionError(residues);
|
|
295
|
+
return epsilon > tau;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Validate that residues are consistent (not in kernel)
|
|
300
|
+
* @param {Array<number>} residues - Expected residues
|
|
301
|
+
* @param {number} tau - Threshold
|
|
302
|
+
* @returns {Object} { valid, error, inKernel }
|
|
303
|
+
*/
|
|
304
|
+
validate(residues, tau = 0.1) {
|
|
305
|
+
const error = this.reconstructionError(residues);
|
|
306
|
+
const inKernel = error > tau;
|
|
307
|
+
return {
|
|
308
|
+
valid: !inKernel,
|
|
309
|
+
error,
|
|
310
|
+
inKernel,
|
|
311
|
+
reconstructed: this.reconstruct(residues)
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// BIRKHOFF PROJECTION: A = Birkhoff(QK^T/√d) ⊙ V
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Birkhoff Polytope Projector
|
|
322
|
+
*
|
|
323
|
+
* Projects attention matrices onto the Birkhoff polytope (set of doubly-stochastic
|
|
324
|
+
* matrices) using the Sinkhorn-Knopp algorithm.
|
|
325
|
+
*
|
|
326
|
+
* A doubly-stochastic matrix has all row sums = 1 and all column sums = 1.
|
|
327
|
+
* This constrains attention to "physical" transitions in the modular algebra.
|
|
328
|
+
*/
|
|
329
|
+
class BirkhoffProjector {
|
|
330
|
+
/**
|
|
331
|
+
* Create a Birkhoff projector
|
|
332
|
+
* @param {number} iterations - Number of Sinkhorn iterations
|
|
333
|
+
* @param {number} epsilon - Small constant for numerical stability
|
|
334
|
+
*/
|
|
335
|
+
constructor(iterations = 10, epsilon = 1e-10) {
|
|
336
|
+
this.iterations = iterations;
|
|
337
|
+
this.epsilon = epsilon;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Sinkhorn-Knopp algorithm for doubly-stochastic projection
|
|
342
|
+
* @param {Array<Array<number>>} matrix - Input matrix (non-negative)
|
|
343
|
+
* @returns {Array<Array<number>>} Doubly-stochastic matrix
|
|
344
|
+
*/
|
|
345
|
+
project(matrix) {
|
|
346
|
+
const rows = matrix.length;
|
|
347
|
+
const cols = matrix[0].length;
|
|
348
|
+
|
|
349
|
+
// Exponentiate (soft threshold) and ensure positivity
|
|
350
|
+
let P = matrix.map(row => row.map(x => Math.exp(x) + this.epsilon));
|
|
351
|
+
|
|
352
|
+
for (let iter = 0; iter < this.iterations; iter++) {
|
|
353
|
+
// Row normalization: each row sums to 1
|
|
354
|
+
P = P.map(row => {
|
|
355
|
+
const sum = row.reduce((a, b) => a + b, 0);
|
|
356
|
+
return row.map(x => x / (sum + this.epsilon));
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Column normalization: each column sums to 1
|
|
360
|
+
const colSums = new Float64Array(cols);
|
|
361
|
+
for (let j = 0; j < cols; j++) {
|
|
362
|
+
for (let i = 0; i < rows; i++) {
|
|
363
|
+
colSums[j] += P[i][j];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < rows; i++) {
|
|
368
|
+
for (let j = 0; j < cols; j++) {
|
|
369
|
+
P[i][j] /= (colSums[j] + this.epsilon);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return P;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Birkhoff attention: A = Birkhoff(QK^T/√d) ⊙ V
|
|
379
|
+
* @param {Array<Array<number>>} Q - Query matrix (n x d)
|
|
380
|
+
* @param {Array<Array<number>>} K - Key matrix (m x d)
|
|
381
|
+
* @param {Array<Array<number>>} V - Value matrix (m x d_v)
|
|
382
|
+
* @returns {Array<Array<number>>} Attention output
|
|
383
|
+
*/
|
|
384
|
+
attention(Q, K, V) {
|
|
385
|
+
const n = Q.length;
|
|
386
|
+
const m = K.length;
|
|
387
|
+
const d = Q[0].length;
|
|
388
|
+
const scale = 1 / Math.sqrt(d);
|
|
389
|
+
|
|
390
|
+
// Compute QK^T / √d
|
|
391
|
+
const scores = [];
|
|
392
|
+
for (let i = 0; i < n; i++) {
|
|
393
|
+
const row = [];
|
|
394
|
+
for (let j = 0; j < m; j++) {
|
|
395
|
+
let dot = 0;
|
|
396
|
+
for (let k = 0; k < d; k++) {
|
|
397
|
+
dot += Q[i][k] * K[j][k];
|
|
398
|
+
}
|
|
399
|
+
row.push(dot * scale);
|
|
400
|
+
}
|
|
401
|
+
scores.push(row);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Project to Birkhoff polytope
|
|
405
|
+
const A = this.project(scores);
|
|
406
|
+
|
|
407
|
+
// Apply attention to values: A ⊙ V
|
|
408
|
+
const dv = V[0].length;
|
|
409
|
+
const output = [];
|
|
410
|
+
for (let i = 0; i < n; i++) {
|
|
411
|
+
const row = new Float64Array(dv);
|
|
412
|
+
for (let j = 0; j < m; j++) {
|
|
413
|
+
for (let k = 0; k < dv; k++) {
|
|
414
|
+
row[k] += A[i][j] * V[j][k];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
output.push(Array.from(row));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return output;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Check if matrix is doubly-stochastic (within tolerance)
|
|
425
|
+
* @param {Array<Array<number>>} matrix - Matrix to check
|
|
426
|
+
* @param {number} tolerance - Tolerance for sum deviation
|
|
427
|
+
* @returns {Object} { isDoublyStochastic, rowErrors, colErrors }
|
|
428
|
+
*/
|
|
429
|
+
validate(matrix, tolerance = 0.01) {
|
|
430
|
+
const rows = matrix.length;
|
|
431
|
+
const cols = matrix[0].length;
|
|
432
|
+
|
|
433
|
+
const rowSums = matrix.map(row => row.reduce((a, b) => a + b, 0));
|
|
434
|
+
const colSums = new Float64Array(cols);
|
|
435
|
+
for (let j = 0; j < cols; j++) {
|
|
436
|
+
for (let i = 0; i < rows; i++) {
|
|
437
|
+
colSums[j] += matrix[i][j];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const rowErrors = rowSums.map(s => Math.abs(s - 1));
|
|
442
|
+
const colErrors = Array.from(colSums).map(s => Math.abs(s - 1));
|
|
443
|
+
|
|
444
|
+
const maxRowError = Math.max(...rowErrors);
|
|
445
|
+
const maxColError = Math.max(...colErrors);
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
isDoublyStochastic: maxRowError < tolerance && maxColError < tolerance,
|
|
449
|
+
rowErrors,
|
|
450
|
+
colErrors,
|
|
451
|
+
maxRowError,
|
|
452
|
+
maxColError
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ============================================================================
|
|
458
|
+
// HOMOLOGY LOSS: ℒ_homology = Σ_{cycles ∈ Ker(ℛ)} f(cycle)
|
|
459
|
+
// ============================================================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Homology Loss Calculator
|
|
463
|
+
*
|
|
464
|
+
* Computes loss terms for obstruction cycles in the residue constraint graph.
|
|
465
|
+
* A cycle represents a consistency failure that persists under perturbation.
|
|
466
|
+
*
|
|
467
|
+
* f(cycle) = Σ_{r ∈ cycle} σ(ε(r) - τ) * |cycle|^α * β^γ
|
|
468
|
+
*/
|
|
469
|
+
class HomologyLoss {
|
|
470
|
+
/**
|
|
471
|
+
* Create a homology loss calculator
|
|
472
|
+
* @param {Object} options - Configuration
|
|
473
|
+
* @param {number} options.tau - Kernel detection threshold
|
|
474
|
+
* @param {number} options.alpha - Cycle length exponent
|
|
475
|
+
* @param {number} options.beta - Residue contribution weight
|
|
476
|
+
* @param {number} options.gamma - Residue exponent
|
|
477
|
+
* @param {number} options.lambda - Overall homology loss weight
|
|
478
|
+
*/
|
|
479
|
+
constructor(options = {}) {
|
|
480
|
+
this.tau = options.tau ?? 0.1;
|
|
481
|
+
this.alpha = options.alpha ?? 1.0;
|
|
482
|
+
this.beta = options.beta ?? 1.0;
|
|
483
|
+
this.gamma = options.gamma ?? 0.5;
|
|
484
|
+
this.lambda = options.lambda ?? 1.0;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Sigmoid activation
|
|
489
|
+
* @param {number} x - Input
|
|
490
|
+
* @returns {number} σ(x)
|
|
491
|
+
*/
|
|
492
|
+
sigmoid(x) {
|
|
493
|
+
return 1 / (1 + Math.exp(-x));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Detect cycles in residue space from a batch of residue tuples
|
|
498
|
+
* Groups consecutive kernel points as cycles
|
|
499
|
+
*
|
|
500
|
+
* @param {Array<Array<number>>} residueBatch - Batch of residue tuples
|
|
501
|
+
* @param {CRTReconstructor} crt - CRT reconstructor
|
|
502
|
+
* @returns {Array<Array<Object>>} Detected cycles
|
|
503
|
+
*/
|
|
504
|
+
detectCycles(residueBatch, crt) {
|
|
505
|
+
const cycles = [];
|
|
506
|
+
let currentCycle = [];
|
|
507
|
+
|
|
508
|
+
for (let i = 0; i < residueBatch.length; i++) {
|
|
509
|
+
const residues = residueBatch[i];
|
|
510
|
+
const inKernel = crt.detectKernel(residues, this.tau);
|
|
511
|
+
|
|
512
|
+
if (inKernel) {
|
|
513
|
+
currentCycle.push({
|
|
514
|
+
index: i,
|
|
515
|
+
residues,
|
|
516
|
+
error: crt.reconstructionError(residues)
|
|
517
|
+
});
|
|
518
|
+
} else if (currentCycle.length > 0) {
|
|
519
|
+
cycles.push(currentCycle);
|
|
520
|
+
currentCycle = [];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Don't forget the last cycle
|
|
525
|
+
if (currentCycle.length > 0) {
|
|
526
|
+
cycles.push(currentCycle);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return cycles;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Compute loss for a single cycle
|
|
534
|
+
* f(cycle) = Σ_{r ∈ cycle} σ(ε(r) - τ) * |cycle|^α * β^γ
|
|
535
|
+
*
|
|
536
|
+
* @param {Array<Object>} cycle - Points in the cycle
|
|
537
|
+
* @returns {number} Cycle loss
|
|
538
|
+
*/
|
|
539
|
+
cycleLoss(cycle) {
|
|
540
|
+
const cycleLength = cycle.length;
|
|
541
|
+
let loss = 0;
|
|
542
|
+
|
|
543
|
+
for (const point of cycle) {
|
|
544
|
+
const sigmaEpsilon = this.sigmoid(point.error - this.tau);
|
|
545
|
+
loss += sigmaEpsilon * Math.pow(cycleLength, this.alpha) *
|
|
546
|
+
Math.pow(this.beta, this.gamma);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return loss;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Compute total homology loss over a batch
|
|
554
|
+
* ℒ_homology = Σ_{cycles} f(cycle)
|
|
555
|
+
*
|
|
556
|
+
* @param {Array<Array<number>>} residueBatch - Batch of residue tuples
|
|
557
|
+
* @param {CRTReconstructor} crt - CRT reconstructor
|
|
558
|
+
* @returns {Object} { loss, cycles, details }
|
|
559
|
+
*/
|
|
560
|
+
compute(residueBatch, crt) {
|
|
561
|
+
const cycles = this.detectCycles(residueBatch, crt);
|
|
562
|
+
|
|
563
|
+
let totalLoss = 0;
|
|
564
|
+
const details = [];
|
|
565
|
+
|
|
566
|
+
for (const cycle of cycles) {
|
|
567
|
+
const loss = this.cycleLoss(cycle);
|
|
568
|
+
totalLoss += loss;
|
|
569
|
+
details.push({
|
|
570
|
+
length: cycle.length,
|
|
571
|
+
loss,
|
|
572
|
+
points: cycle.map(p => p.index),
|
|
573
|
+
meanError: cycle.reduce((s, p) => s + p.error, 0) / cycle.length
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
loss: this.lambda * totalLoss,
|
|
579
|
+
cycles: cycles.length,
|
|
580
|
+
details,
|
|
581
|
+
totalPoints: cycles.reduce((s, c) => s + c.length, 0)
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Compute cycle persistence: max_error - min_error across cycle
|
|
587
|
+
* @param {Array<Object>} cycle - Points in the cycle
|
|
588
|
+
* @returns {number} Persistence value
|
|
589
|
+
*/
|
|
590
|
+
cyclePersistence(cycle) {
|
|
591
|
+
if (cycle.length === 0) return 0;
|
|
592
|
+
const errors = cycle.map(p => p.error);
|
|
593
|
+
return Math.max(...errors) - Math.min(...errors);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Compute Betti numbers (topological invariants)
|
|
598
|
+
* β_0 = number of connected components in kernel
|
|
599
|
+
* β_1 = number of holes (cycles that don't bound)
|
|
600
|
+
*
|
|
601
|
+
* @param {Array<Array<number>>} residueBatch - Batch of residue tuples
|
|
602
|
+
* @param {CRTReconstructor} crt - CRT reconstructor
|
|
603
|
+
* @returns {Object} { beta0, beta1 }
|
|
604
|
+
*/
|
|
605
|
+
computeBettiNumbers(residueBatch, crt) {
|
|
606
|
+
const cycles = this.detectCycles(residueBatch, crt);
|
|
607
|
+
|
|
608
|
+
// β_0: number of connected components (cycles)
|
|
609
|
+
const beta0 = cycles.length;
|
|
610
|
+
|
|
611
|
+
// β_1: estimate from persistent cycles (those with high persistence)
|
|
612
|
+
const persistentThreshold = 0.5;
|
|
613
|
+
let beta1 = 0;
|
|
614
|
+
for (const cycle of cycles) {
|
|
615
|
+
if (this.cyclePersistence(cycle) > persistentThreshold) {
|
|
616
|
+
beta1++;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return { beta0, beta1, cycles: cycles.length };
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ============================================================================
|
|
625
|
+
// CRT MODULAR LAYER: Integrated CRT + Birkhoff Attention
|
|
626
|
+
// ============================================================================
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* CRT Modular Layer
|
|
630
|
+
*
|
|
631
|
+
* Combines residue encoding, CRT reconstruction, Birkhoff attention,
|
|
632
|
+
* and homology loss into a single differentiable layer.
|
|
633
|
+
*/
|
|
634
|
+
class CRTModularLayer {
|
|
635
|
+
/**
|
|
636
|
+
* Create a CRT modular layer
|
|
637
|
+
* @param {Array<number>} primes - Coprime moduli
|
|
638
|
+
* @param {number} hiddenDim - Hidden dimension
|
|
639
|
+
* @param {Object} options - Configuration
|
|
640
|
+
*/
|
|
641
|
+
constructor(primes, hiddenDim, options = {}) {
|
|
642
|
+
this.encoder = new ResidueEncoder(primes, hiddenDim, options);
|
|
643
|
+
this.crt = new CRTReconstructor(primes);
|
|
644
|
+
this.birkhoff = new BirkhoffProjector(options.sinkhornIterations || 10);
|
|
645
|
+
this.homology = new HomologyLoss(options.homology || {});
|
|
646
|
+
|
|
647
|
+
this.primes = primes;
|
|
648
|
+
this.hiddenDim = hiddenDim;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Forward pass through the CRT modular layer
|
|
653
|
+
*
|
|
654
|
+
* @param {Array<number>|Float64Array} h - Hidden vector
|
|
655
|
+
* @param {Array<Array<number>>} Q - Query matrix (optional)
|
|
656
|
+
* @param {Array<Array<number>>} K - Key matrix (optional)
|
|
657
|
+
* @param {Array<Array<number>>} V - Value matrix (optional)
|
|
658
|
+
* @returns {Object} Forward pass result
|
|
659
|
+
*/
|
|
660
|
+
forward(h, Q = null, K = null, V = null) {
|
|
661
|
+
// 1. Encode to residue distributions
|
|
662
|
+
const residueDistributions = this.encoder.encode(h);
|
|
663
|
+
const expectedResidues = this.encoder.expectedResidues(residueDistributions);
|
|
664
|
+
|
|
665
|
+
// 2. CRT reconstruction
|
|
666
|
+
const L = this.crt.reconstruct(expectedResidues);
|
|
667
|
+
const inKernel = this.crt.detectKernel(expectedResidues);
|
|
668
|
+
const reconstructionError = this.crt.reconstructionError(expectedResidues);
|
|
669
|
+
|
|
670
|
+
// 3. Birkhoff attention (if Q, K, V provided)
|
|
671
|
+
let attention = null;
|
|
672
|
+
if (Q && K && V) {
|
|
673
|
+
attention = this.birkhoff.attention(Q, K, V);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// 4. Compute coherence (inverse of being in kernel)
|
|
677
|
+
const coherence = 1 - Math.min(1, reconstructionError / this.homology.tau);
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
residues: residueDistributions,
|
|
681
|
+
expectedResidues,
|
|
682
|
+
latent: L,
|
|
683
|
+
inKernel,
|
|
684
|
+
reconstructionError,
|
|
685
|
+
coherence,
|
|
686
|
+
attention,
|
|
687
|
+
P: this.crt.P
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Forward pass for a batch, computing homology loss
|
|
693
|
+
*
|
|
694
|
+
* @param {Array<Array<number>>} hBatch - Batch of hidden vectors
|
|
695
|
+
* @param {Array<number>|null} targets - Target values (for MSE loss)
|
|
696
|
+
* @returns {Object} Batch forward result with losses
|
|
697
|
+
*/
|
|
698
|
+
forwardBatch(hBatch, targets = null) {
|
|
699
|
+
const results = hBatch.map(h => this.forward(h));
|
|
700
|
+
const residueBatch = results.map(r => r.expectedResidues);
|
|
701
|
+
|
|
702
|
+
// Compute homology loss
|
|
703
|
+
const homologyResult = this.homology.compute(residueBatch, this.crt);
|
|
704
|
+
|
|
705
|
+
// Compute MSE loss if targets provided
|
|
706
|
+
let mseLoss = 0;
|
|
707
|
+
if (targets) {
|
|
708
|
+
for (let i = 0; i < results.length; i++) {
|
|
709
|
+
const diff = results[i].latent - targets[i];
|
|
710
|
+
mseLoss += diff * diff;
|
|
711
|
+
}
|
|
712
|
+
mseLoss /= results.length;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Total loss
|
|
716
|
+
const totalLoss = mseLoss + homologyResult.loss;
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
results,
|
|
720
|
+
homologyLoss: homologyResult.loss,
|
|
721
|
+
mseLoss,
|
|
722
|
+
totalLoss,
|
|
723
|
+
homologyDetails: homologyResult,
|
|
724
|
+
bettiNumbers: this.homology.computeBettiNumbers(residueBatch, this.crt)
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Integration with tinyaleph SparsePrimeState
|
|
730
|
+
* @param {SparsePrimeState} state - Sparse prime state
|
|
731
|
+
* @returns {Object} Forward pass result
|
|
732
|
+
*/
|
|
733
|
+
forwardFromPrimeState(state) {
|
|
734
|
+
const residues = this.encoder.encodeFromPrimeState(state);
|
|
735
|
+
const expectedResidues = this.encoder.expectedResidues(residues);
|
|
736
|
+
const L = this.crt.reconstruct(expectedResidues);
|
|
737
|
+
|
|
738
|
+
return {
|
|
739
|
+
residues,
|
|
740
|
+
expectedResidues,
|
|
741
|
+
latent: L,
|
|
742
|
+
inKernel: this.crt.detectKernel(expectedResidues),
|
|
743
|
+
reconstructionError: this.crt.reconstructionError(expectedResidues),
|
|
744
|
+
coherence: 1 - Math.min(1, this.crt.reconstructionError(expectedResidues) / this.homology.tau)
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ============================================================================
|
|
750
|
+
// CRT-FUSED ATTENTION: L̂' = CRT_Fuse({A_k, r_k})
|
|
751
|
+
// ============================================================================
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* CRT-Fused Modular Attention
|
|
755
|
+
*
|
|
756
|
+
* Combines per-modulus Birkhoff attention with CRT reconstruction.
|
|
757
|
+
* Each prime modulus gets its own attention head, and results are fused via CRT.
|
|
758
|
+
*/
|
|
759
|
+
class CRTFusedAttention {
|
|
760
|
+
/**
|
|
761
|
+
* Create CRT-fused attention
|
|
762
|
+
* @param {Array<number>} primes - Coprime moduli (one per attention head)
|
|
763
|
+
* @param {number} hiddenDim - Hidden dimension
|
|
764
|
+
* @param {number} headDim - Per-head dimension
|
|
765
|
+
* @param {Object} options - Configuration
|
|
766
|
+
*/
|
|
767
|
+
constructor(primes, hiddenDim, headDim, options = {}) {
|
|
768
|
+
this.primes = primes;
|
|
769
|
+
this.K = primes.length;
|
|
770
|
+
this.hiddenDim = hiddenDim;
|
|
771
|
+
this.headDim = headDim;
|
|
772
|
+
|
|
773
|
+
this.crt = new CRTReconstructor(primes);
|
|
774
|
+
this.birkhoff = new BirkhoffProjector(options.sinkhornIterations || 10);
|
|
775
|
+
this.homology = new HomologyLoss(options.homology || {});
|
|
776
|
+
|
|
777
|
+
// Per-modulus projection matrices (Q_k, K_k, V_k)
|
|
778
|
+
this.projections = primes.map(p => ({
|
|
779
|
+
Q: this._initMatrix(hiddenDim, headDim),
|
|
780
|
+
K: this._initMatrix(hiddenDim, headDim),
|
|
781
|
+
V: this._initMatrix(hiddenDim, headDim)
|
|
782
|
+
}));
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Initialize a weight matrix
|
|
787
|
+
* @private
|
|
788
|
+
*/
|
|
789
|
+
_initMatrix(rows, cols, scale = 0.1) {
|
|
790
|
+
const matrix = [];
|
|
791
|
+
for (let i = 0; i < rows; i++) {
|
|
792
|
+
const row = new Float64Array(cols);
|
|
793
|
+
for (let j = 0; j < cols; j++) {
|
|
794
|
+
row[j] = (Math.random() - 0.5) * 2 * scale;
|
|
795
|
+
}
|
|
796
|
+
matrix.push(Array.from(row));
|
|
797
|
+
}
|
|
798
|
+
return matrix;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Matrix multiplication
|
|
803
|
+
* @private
|
|
804
|
+
*/
|
|
805
|
+
_matMul(A, B) {
|
|
806
|
+
const m = A.length;
|
|
807
|
+
const n = B[0].length;
|
|
808
|
+
const k = A[0].length;
|
|
809
|
+
|
|
810
|
+
const result = [];
|
|
811
|
+
for (let i = 0; i < m; i++) {
|
|
812
|
+
const row = new Float64Array(n);
|
|
813
|
+
for (let j = 0; j < n; j++) {
|
|
814
|
+
for (let l = 0; l < k; l++) {
|
|
815
|
+
row[j] += A[i][l] * B[l][j];
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
result.push(Array.from(row));
|
|
819
|
+
}
|
|
820
|
+
return result;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Project input through per-modulus heads
|
|
825
|
+
* @param {Array<Array<number>>} X - Input sequence (seq_len x hiddenDim)
|
|
826
|
+
* @param {number} k - Head index
|
|
827
|
+
* @returns {Object} { Q, K, V } projections
|
|
828
|
+
*/
|
|
829
|
+
projectHead(X, k) {
|
|
830
|
+
const proj = this.projections[k];
|
|
831
|
+
return {
|
|
832
|
+
Q: this._matMul(X, proj.Q),
|
|
833
|
+
K: this._matMul(X, proj.K),
|
|
834
|
+
V: this._matMul(X, proj.V)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Forward pass: Per-modulus Birkhoff attention fused via CRT
|
|
840
|
+
*
|
|
841
|
+
* For each modulus k:
|
|
842
|
+
* A_k = Birkhoff(Q_k K_k^T / √d) ⊙ V_k
|
|
843
|
+
* r_k = residue encoding of A_k
|
|
844
|
+
*
|
|
845
|
+
* Fused output: L̂' = CRT_Fuse({A_k, r_k})
|
|
846
|
+
*
|
|
847
|
+
* @param {Array<Array<number>>} X - Input sequence (seq_len x hiddenDim)
|
|
848
|
+
* @returns {Object} Forward result with fused attention
|
|
849
|
+
*/
|
|
850
|
+
forward(X) {
|
|
851
|
+
const seqLen = X.length;
|
|
852
|
+
const perHeadOutputs = [];
|
|
853
|
+
const residueTuples = [];
|
|
854
|
+
|
|
855
|
+
// Process each modular head
|
|
856
|
+
for (let k = 0; k < this.K; k++) {
|
|
857
|
+
const { Q, K, V } = this.projectHead(X, k);
|
|
858
|
+
|
|
859
|
+
// Birkhoff attention for this head
|
|
860
|
+
const A_k = this.birkhoff.attention(Q, K, V);
|
|
861
|
+
perHeadOutputs.push(A_k);
|
|
862
|
+
|
|
863
|
+
// Compute residue from attention output (sum modulo p_k)
|
|
864
|
+
const residue = A_k.reduce((sum, row) => {
|
|
865
|
+
const rowSum = row.reduce((a, b) => a + b, 0);
|
|
866
|
+
return (sum + rowSum) % this.primes[k];
|
|
867
|
+
}, 0);
|
|
868
|
+
residueTuples.push(residue);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// CRT reconstruction
|
|
872
|
+
const L = this.crt.reconstruct(residueTuples);
|
|
873
|
+
const inKernel = this.crt.detectKernel(residueTuples);
|
|
874
|
+
const coherence = 1 - Math.min(1, this.crt.reconstructionError(residueTuples) / 0.1);
|
|
875
|
+
|
|
876
|
+
// Fused output: weighted average of per-head outputs
|
|
877
|
+
const fusedOutput = [];
|
|
878
|
+
for (let i = 0; i < seqLen; i++) {
|
|
879
|
+
const row = new Float64Array(this.headDim);
|
|
880
|
+
for (let k = 0; k < this.K; k++) {
|
|
881
|
+
const weight = 1 / this.K; // Uniform weighting (can be learned)
|
|
882
|
+
for (let j = 0; j < this.headDim; j++) {
|
|
883
|
+
row[j] += weight * (perHeadOutputs[k][i]?.[j] || 0);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
fusedOutput.push(Array.from(row));
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
output: fusedOutput,
|
|
891
|
+
perHeadOutputs,
|
|
892
|
+
residues: residueTuples,
|
|
893
|
+
latent: L,
|
|
894
|
+
inKernel,
|
|
895
|
+
coherence,
|
|
896
|
+
P: this.crt.P
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// ============================================================================
|
|
902
|
+
// COPRIME SELECTOR: Choose optimal coprime moduli for CRT
|
|
903
|
+
// ============================================================================
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Select K coprime moduli for CRT with desired product P
|
|
907
|
+
*
|
|
908
|
+
* Strategy: Use first K primes for maximum P with minimum K
|
|
909
|
+
*/
|
|
910
|
+
class CoprimeSelector {
|
|
911
|
+
/**
|
|
912
|
+
* Select K coprime moduli from available primes
|
|
913
|
+
* @param {number} K - Number of moduli
|
|
914
|
+
* @param {Object} options - Configuration
|
|
915
|
+
*/
|
|
916
|
+
constructor(K = 4, options = {}) {
|
|
917
|
+
this.K = K;
|
|
918
|
+
this.minPrime = options.minPrime || 2;
|
|
919
|
+
this.maxPrime = options.maxPrime || 100;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Get K smallest primes as moduli
|
|
924
|
+
* @returns {Array<number>} Array of K primes
|
|
925
|
+
*/
|
|
926
|
+
selectMinimal() {
|
|
927
|
+
return firstNPrimes(this.K);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Select primes to achieve target product P
|
|
932
|
+
* @param {number} targetP - Target product
|
|
933
|
+
* @returns {Array<number>|null} Primes achieving closest to targetP
|
|
934
|
+
*/
|
|
935
|
+
selectForProduct(targetP) {
|
|
936
|
+
const primes = firstNPrimes(20);
|
|
937
|
+
let bestPrimes = null;
|
|
938
|
+
let bestProduct = 0;
|
|
939
|
+
|
|
940
|
+
// Greedy selection
|
|
941
|
+
const selected = [];
|
|
942
|
+
let product = 1;
|
|
943
|
+
|
|
944
|
+
for (const p of primes) {
|
|
945
|
+
if (product * p <= targetP) {
|
|
946
|
+
selected.push(p);
|
|
947
|
+
product *= p;
|
|
948
|
+
}
|
|
949
|
+
if (selected.length >= this.K) break;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return selected.length > 0 ? selected : null;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Select primes for specific domain (e.g., cryptographic sizes)
|
|
957
|
+
* @param {string} domain - Domain name
|
|
958
|
+
* @returns {Array<number>} Recommended primes
|
|
959
|
+
*/
|
|
960
|
+
selectForDomain(domain) {
|
|
961
|
+
const domains = {
|
|
962
|
+
'small': [2, 3, 5, 7], // P = 210
|
|
963
|
+
'medium': [5, 7, 11, 13], // P = 5005
|
|
964
|
+
'large': [11, 13, 17, 19], // P = 46189
|
|
965
|
+
'semantic': [2, 3, 5, 7, 11], // P = 2310 (good for semantic)
|
|
966
|
+
'temporal': [3, 5, 7, 11, 13] // P = 15015 (good for sequences)
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
return domains[domain] || domains['small'];
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ============================================================================
|
|
974
|
+
// EXPORTS
|
|
975
|
+
// ============================================================================
|
|
976
|
+
|
|
977
|
+
module.exports = {
|
|
978
|
+
// Modular arithmetic utilities
|
|
979
|
+
extendedGCD,
|
|
980
|
+
modInverse,
|
|
981
|
+
areCoprime,
|
|
982
|
+
softmax,
|
|
983
|
+
|
|
984
|
+
// Core classes
|
|
985
|
+
ResidueEncoder,
|
|
986
|
+
CRTReconstructor,
|
|
987
|
+
BirkhoffProjector,
|
|
988
|
+
HomologyLoss,
|
|
989
|
+
CRTModularLayer,
|
|
990
|
+
CRTFusedAttention,
|
|
991
|
+
CoprimeSelector,
|
|
992
|
+
|
|
993
|
+
// Factory functions
|
|
994
|
+
createCRTLayer: (primes, hiddenDim, options = {}) =>
|
|
995
|
+
new CRTModularLayer(primes, hiddenDim, options),
|
|
996
|
+
|
|
997
|
+
createFusedAttention: (primes, hiddenDim, headDim, options = {}) =>
|
|
998
|
+
new CRTFusedAttention(primes, hiddenDim, headDim, options),
|
|
999
|
+
|
|
1000
|
+
// Default configurations
|
|
1001
|
+
DEFAULT_PRIMES_SMALL: [2, 3, 5, 7],
|
|
1002
|
+
DEFAULT_PRIMES_MEDIUM: [5, 7, 11, 13],
|
|
1003
|
+
DEFAULT_PRIMES_SEMANTIC: [2, 3, 5, 7, 11]
|
|
1004
|
+
};
|