@aleph-ai/tinyaleph 1.1.0 → 1.2.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.
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Primeon Z-Ladder with Canonical U Evolution
3
+ *
4
+ * Minimal canonical U implementation representing a ladder with:
5
+ * - core sector ψ ∈ ℂ^d (quantum state)
6
+ * - Z sector z ∈ ℂ^dz (closure/sink/bookkeeping channel)
7
+ *
8
+ * Provides reproducible stepping, measurable Z-flux signal,
9
+ * and stable hooks for entropy and coherence metrics.
10
+ */
11
+ 'use strict';
12
+
13
+ /**
14
+ * Minimal Complex number helpers (intentionally tiny for predictable perf).
15
+ */
16
+ class C {
17
+ constructor(re = 0, im = 0) {
18
+ this.re = re;
19
+ this.im = im;
20
+ }
21
+
22
+ static add(a, b) {
23
+ return new C(a.re + b.re, a.im + b.im);
24
+ }
25
+
26
+ static sub(a, b) {
27
+ return new C(a.re - b.re, a.im - b.im);
28
+ }
29
+
30
+ static mul(a, b) {
31
+ return new C(
32
+ a.re * b.re - a.im * b.im,
33
+ a.re * b.im + a.im * b.re
34
+ );
35
+ }
36
+
37
+ static scale(a, s) {
38
+ return new C(a.re * s, a.im * s);
39
+ }
40
+
41
+ static conj(a) {
42
+ return new C(a.re, -a.im);
43
+ }
44
+
45
+ static abs2(a) {
46
+ return a.re * a.re + a.im * a.im;
47
+ }
48
+
49
+ static exp(theta) {
50
+ return new C(Math.cos(theta), Math.sin(theta));
51
+ }
52
+
53
+ static zero() {
54
+ return new C(0, 0);
55
+ }
56
+
57
+ clone() {
58
+ return new C(this.re, this.im);
59
+ }
60
+ }
61
+
62
+ function clamp01(x) {
63
+ return Math.max(0, Math.min(1, x));
64
+ }
65
+
66
+ /**
67
+ * Shannon entropy on probabilities (base e).
68
+ * @param {number[]} probs - Array of probabilities
69
+ * @returns {number} Entropy in nats
70
+ */
71
+ function shannonEntropyNats(probs) {
72
+ let h = 0;
73
+ for (const p of probs) {
74
+ if (p > 1e-15) h -= p * Math.log(p);
75
+ }
76
+ return h;
77
+ }
78
+
79
+ /**
80
+ * Normalize complex vector in-place; returns norm.
81
+ * @param {C[]} vec - Complex vector
82
+ * @returns {number} The norm before normalization
83
+ */
84
+ function normalize(vec) {
85
+ let s = 0;
86
+ for (const v of vec) s += C.abs2(v);
87
+ const n = Math.sqrt(s) || 1;
88
+ const inv = 1 / n;
89
+ for (let i = 0; i < vec.length; i++) {
90
+ vec[i] = C.scale(vec[i], inv);
91
+ }
92
+ return n;
93
+ }
94
+
95
+ /**
96
+ * Computes probabilities |amp|^2 normalized.
97
+ * @param {C[]} vec - Complex vector
98
+ * @returns {number[]} Probability distribution
99
+ */
100
+ function probsOf(vec) {
101
+ let s = 0;
102
+ const p = new Array(vec.length);
103
+ for (let i = 0; i < vec.length; i++) {
104
+ const a2 = C.abs2(vec[i]);
105
+ p[i] = a2;
106
+ s += a2;
107
+ }
108
+ const inv = 1 / (s || 1);
109
+ for (let i = 0; i < p.length; i++) p[i] *= inv;
110
+ return p;
111
+ }
112
+
113
+ /**
114
+ * Compute total norm of complex vector.
115
+ * @param {C[]} vec - Complex vector
116
+ * @returns {number} L2 norm
117
+ */
118
+ function normOf(vec) {
119
+ let s = 0;
120
+ for (const v of vec) s += C.abs2(v);
121
+ return Math.sqrt(s);
122
+ }
123
+
124
+ /**
125
+ * PrimeonZLadderU - A minimal "canonical U" for a ladder system.
126
+ *
127
+ * - Core ψ is coupled to nearest neighbors via a hopping term
128
+ * - Z channel receives controlled leakage from core each step
129
+ *
130
+ * Designed to be:
131
+ * - Easy to simulate
132
+ * - Easy to measure
133
+ * - Easy to integrate with tinyaleph's existing entropy tooling
134
+ */
135
+ class PrimeonZLadderU {
136
+ /**
137
+ * @param {object} opts
138
+ * @param {number} opts.N Number of ladder rungs
139
+ * @param {number} [opts.d=1] Core internal dimension per rung
140
+ * @param {number} [opts.dz=1] Z internal dimension per rung
141
+ * @param {number} [opts.J=0.25] Nearest-neighbor coupling strength (core)
142
+ * @param {number} [opts.leak=0.05] Fraction of amplitude leaked core->Z per step (0..1)
143
+ * @param {boolean} [opts.closeZ=true] If true, Z is projected out each step (closure)
144
+ * @param {boolean} [opts.periodic=true] Periodic boundary conditions
145
+ */
146
+ constructor(opts) {
147
+ this.N = opts.N;
148
+ this.d = opts.d ?? 1;
149
+ this.dz = opts.dz ?? 1;
150
+ this.J = opts.J ?? 0.25;
151
+ this.leak = clamp01(opts.leak ?? 0.05);
152
+ this.closeZ = opts.closeZ ?? true;
153
+ this.periodic = opts.periodic ?? true;
154
+
155
+ // State arrays: ψ and z as [N][d] and [N][dz] flattened
156
+ this.psi = Array.from({ length: this.N * this.d }, () => C.zero());
157
+ this.z = Array.from({ length: this.N * this.dz }, () => C.zero());
158
+
159
+ // Metrics
160
+ this.t = 0;
161
+ this.lastZFlux = 0; // "how much moved into Z this step"
162
+ this.totalZFlux = 0; // accumulated over all steps
163
+ this.stepCount = 0;
164
+ }
165
+
166
+ /**
167
+ * Reset state to vacuum (all zeros).
168
+ */
169
+ reset() {
170
+ for (let i = 0; i < this.psi.length; i++) {
171
+ this.psi[i] = C.zero();
172
+ }
173
+ for (let i = 0; i < this.z.length; i++) {
174
+ this.z[i] = C.zero();
175
+ }
176
+ this.t = 0;
177
+ this.lastZFlux = 0;
178
+ this.totalZFlux = 0;
179
+ this.stepCount = 0;
180
+ }
181
+
182
+ /**
183
+ * Initialize ψ with a localized excitation at rung n.
184
+ * @param {number} n - Rung index
185
+ * @param {C} [amp] - Complex amplitude (default: 1+0i)
186
+ * @param {number} [k=0] - Internal index within rung
187
+ */
188
+ exciteRung(n, amp = new C(1, 0), k = 0) {
189
+ const i = ((n % this.N) + this.N) % this.N * this.d + (k % this.d);
190
+ this.psi[i] = amp;
191
+ normalize(this.psi);
192
+ }
193
+
194
+ /**
195
+ * Excite multiple rungs uniformly.
196
+ * @param {number[]} rungs - Array of rung indices
197
+ * @param {number} [ampScale=1] - Amplitude scale
198
+ */
199
+ exciteRungs(rungs, ampScale = 1) {
200
+ for (const n of rungs) {
201
+ const idx = ((n % this.N) + this.N) % this.N;
202
+ const i = idx * this.d;
203
+ this.psi[i] = C.add(this.psi[i], new C(ampScale, 0));
204
+ }
205
+ normalize(this.psi);
206
+ }
207
+
208
+ /**
209
+ * Prime-friendly helper: excite multiple rungs from a prime list.
210
+ * Map primes -> rung index via mod N (simple, deterministic).
211
+ * @param {number[]} primes - Array of prime numbers
212
+ * @param {number} [ampScale=1] - Amplitude scale for each excitation
213
+ */
214
+ excitePrimes(primes, ampScale = 1) {
215
+ for (const p of primes) {
216
+ const idx = ((p % this.N) + this.N) % this.N;
217
+ const i = idx * this.d;
218
+ this.psi[i] = C.add(this.psi[i], new C(ampScale, 0));
219
+ }
220
+ normalize(this.psi);
221
+ }
222
+
223
+ /**
224
+ * One time step using a simple split operator:
225
+ * 1) Core hopping (discrete Schrödinger-like update)
226
+ * 2) Leak core->Z
227
+ * 3) Closure rule (optional)
228
+ *
229
+ * @param {number} [dt=0.01] - Time step size
230
+ * @returns {object} Current metrics
231
+ */
232
+ step(dt = 0.01) {
233
+ const N = this.N;
234
+ const d = this.d;
235
+
236
+ // --- 1) Core hopping: ψ_i <- ψ_i - i dt J (ψ_{i+1} + ψ_{i-1} - 2ψ_i)
237
+ // Minimal stable discretization (not "perfect unitary", but well-behaved for small dt).
238
+ const next = Array.from({ length: N * d }, () => C.zero());
239
+
240
+ for (let n = 0; n < N; n++) {
241
+ const nL = (n === 0) ? (this.periodic ? N - 1 : 0) : n - 1;
242
+ const nR = (n === N - 1) ? (this.periodic ? 0 : N - 1) : n + 1;
243
+
244
+ for (let k = 0; k < d; k++) {
245
+ const i = n * d + k;
246
+ const iL = nL * d + k;
247
+ const iR = nR * d + k;
248
+
249
+ const psi = this.psi[i];
250
+ const psiL = this.psi[iL];
251
+ const psiR = this.psi[iR];
252
+
253
+ // Discrete Laplacian: (psiL - psi) + (psiR - psi) = psiL + psiR - 2*psi
254
+ const lap = C.add(
255
+ C.sub(psiL, psi),
256
+ C.sub(psiR, psi)
257
+ );
258
+
259
+ // -i * (dt*J) * lap == multiply lap by (0, -dt*J)
260
+ const delta = C.mul(lap, new C(0, -dt * this.J));
261
+ next[i] = C.add(psi, delta);
262
+ }
263
+ }
264
+
265
+ this.psi = next;
266
+
267
+ // --- 2) Leak core->Z (explicit Z-sector coupling)
268
+ let zFlux = 0;
269
+ const leak = this.leak;
270
+
271
+ for (let i = 0; i < this.psi.length; i++) {
272
+ const a = this.psi[i];
273
+ const moved = C.scale(a, leak);
274
+
275
+ // Subtract moved from core
276
+ this.psi[i] = C.sub(a, moved);
277
+
278
+ // Add moved into Z (fold core index into Z index)
279
+ const zi = i % (this.N * this.dz);
280
+ this.z[zi] = C.add(this.z[zi], moved);
281
+
282
+ zFlux += C.abs2(moved);
283
+ }
284
+
285
+ // --- 3) Closure rule
286
+ // If closeZ: project out Z (record flux), renormalize core.
287
+ // If keep Z: Z acts as persistent "closure memory".
288
+ if (this.closeZ) {
289
+ // Optionally wipe Z after recording:
290
+ // for (let i = 0; i < this.z.length; i++) this.z[i] = C.zero();
291
+ normalize(this.psi);
292
+ } else {
293
+ // Normalize combined energy lightly to avoid blowup
294
+ normalize(this.psi);
295
+ }
296
+
297
+ this.lastZFlux = zFlux;
298
+ this.totalZFlux += zFlux;
299
+ this.t += dt;
300
+ this.stepCount++;
301
+
302
+ return this.metrics();
303
+ }
304
+
305
+ /**
306
+ * Run multiple steps.
307
+ * @param {number} steps - Number of steps to run
308
+ * @param {number} [dt=0.01] - Time step size
309
+ * @returns {object[]} Array of metrics for each step
310
+ */
311
+ run(steps, dt = 0.01) {
312
+ const trajectory = [];
313
+ for (let i = 0; i < steps; i++) {
314
+ trajectory.push(this.step(dt));
315
+ }
316
+ return trajectory;
317
+ }
318
+
319
+ /**
320
+ * Coherence proxy: inverse entropy of |ψ|^2 distribution.
321
+ * Low entropy => localized / coherent; high entropy => spread / incoherent
322
+ * @returns {object} Current metrics
323
+ */
324
+ metrics() {
325
+ const p = probsOf(this.psi);
326
+ const H = shannonEntropyNats(p);
327
+
328
+ // "Coherence" as 1/(1+H) for a stable 0..1-ish number
329
+ const coherence = 1 / (1 + H);
330
+
331
+ // Z sector metrics
332
+ const pZ = probsOf(this.z);
333
+ const HZ = shannonEntropyNats(pZ);
334
+ const zNorm = normOf(this.z);
335
+
336
+ // Compute order parameter (mean field amplitude)
337
+ let meanRe = 0, meanIm = 0;
338
+ for (const v of this.psi) {
339
+ meanRe += v.re;
340
+ meanIm += v.im;
341
+ }
342
+ const orderParameter = Math.sqrt(meanRe * meanRe + meanIm * meanIm) / this.psi.length;
343
+
344
+ return {
345
+ t: this.t,
346
+ stepCount: this.stepCount,
347
+ coherence,
348
+ entropy: H,
349
+ orderParameter,
350
+ zFlux: this.lastZFlux,
351
+ zFluxTotal: this.totalZFlux,
352
+ zEntropy: HZ,
353
+ zNorm
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Get full state snapshot for serialization/inspection.
359
+ * @returns {object} Complete state snapshot
360
+ */
361
+ snapshot() {
362
+ return {
363
+ t: this.t,
364
+ stepCount: this.stepCount,
365
+ N: this.N,
366
+ d: this.d,
367
+ dz: this.dz,
368
+ J: this.J,
369
+ leak: this.leak,
370
+ closeZ: this.closeZ,
371
+ periodic: this.periodic,
372
+ psi: this.psi.map(v => ({ re: v.re, im: v.im })),
373
+ z: this.z.map(v => ({ re: v.re, im: v.im })),
374
+ ...this.metrics()
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Restore state from a snapshot.
380
+ * @param {object} snap - Snapshot object from snapshot()
381
+ */
382
+ restore(snap) {
383
+ this.t = snap.t;
384
+ this.stepCount = snap.stepCount;
385
+ this.psi = snap.psi.map(v => new C(v.re, v.im));
386
+ this.z = snap.z.map(v => new C(v.re, v.im));
387
+ this.totalZFlux = snap.zFluxTotal;
388
+ this.lastZFlux = snap.zFlux;
389
+ }
390
+
391
+ /**
392
+ * Get probability distribution over rungs.
393
+ * @returns {number[]} Probability for each rung
394
+ */
395
+ rungProbabilities() {
396
+ const probs = new Array(this.N).fill(0);
397
+ for (let n = 0; n < this.N; n++) {
398
+ for (let k = 0; k < this.d; k++) {
399
+ const i = n * this.d + k;
400
+ probs[n] += C.abs2(this.psi[i]);
401
+ }
402
+ }
403
+ const total = probs.reduce((a, b) => a + b, 0) || 1;
404
+ return probs.map(p => p / total);
405
+ }
406
+
407
+ /**
408
+ * Sample a rung index according to |ψ|^2 distribution.
409
+ * @returns {number} Sampled rung index
410
+ */
411
+ sampleRung() {
412
+ const probs = this.rungProbabilities();
413
+ const r = Math.random();
414
+ let cumulative = 0;
415
+ for (let n = 0; n < this.N; n++) {
416
+ cumulative += probs[n];
417
+ if (r <= cumulative) return n;
418
+ }
419
+ return this.N - 1;
420
+ }
421
+
422
+ /**
423
+ * Collapse state to a specific rung (measurement).
424
+ * @param {number} n - Rung index to collapse to
425
+ */
426
+ collapseToRung(n) {
427
+ const idx = ((n % this.N) + this.N) % this.N;
428
+
429
+ // Zero out all rungs except the collapsed one
430
+ for (let i = 0; i < this.psi.length; i++) {
431
+ const rungIdx = Math.floor(i / this.d);
432
+ if (rungIdx !== idx) {
433
+ this.psi[i] = C.zero();
434
+ }
435
+ }
436
+ normalize(this.psi);
437
+ }
438
+
439
+ /**
440
+ * Perform a measurement: sample and collapse.
441
+ * @returns {object} Measurement result
442
+ */
443
+ measure() {
444
+ const probsBefore = this.rungProbabilities();
445
+ const sampledRung = this.sampleRung();
446
+ const probability = probsBefore[sampledRung];
447
+
448
+ this.collapseToRung(sampledRung);
449
+
450
+ return {
451
+ outcome: sampledRung,
452
+ probability,
453
+ probsBefore,
454
+ metricsAfter: this.metrics()
455
+ };
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Factory function for creating ladder with prime-based configuration.
461
+ * @param {number[]} primes - Prime numbers to use for initialization
462
+ * @param {object} [opts={}] - Additional options
463
+ * @returns {PrimeonZLadderU} Configured ladder instance
464
+ */
465
+ function createPrimeonLadder(primes, opts = {}) {
466
+ const N = opts.N ?? Math.max(16, Math.max(...primes) + 1);
467
+ const ladder = new PrimeonZLadderU({
468
+ N,
469
+ d: opts.d ?? 1,
470
+ dz: opts.dz ?? 1,
471
+ J: opts.J ?? 0.25,
472
+ leak: opts.leak ?? 0.05,
473
+ closeZ: opts.closeZ ?? true,
474
+ periodic: opts.periodic ?? true
475
+ });
476
+
477
+ if (primes.length > 0) {
478
+ ladder.excitePrimes(primes, opts.ampScale ?? 1);
479
+ }
480
+
481
+ return ladder;
482
+ }
483
+
484
+ module.exports = {
485
+ PrimeonZLadderU,
486
+ createPrimeonLadder,
487
+ // Export helpers for potential reuse in entropy tooling
488
+ shannonEntropyNats,
489
+ probsOf,
490
+ normalize,
491
+ // Export Complex class for advanced usage
492
+ C
493
+ };