@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.
@@ -0,0 +1,623 @@
1
+ /**
2
+ * Quaternionic Semantic Embedding (PIQC.pdf §3)
3
+ *
4
+ * Prime-indexed quaternionic semantics for directional meaning:
5
+ * - Ψ(p) = cos(h(p)) + u(p)sin(h(p)) prime-to-quaternion mapping
6
+ * - Q(A(p1)...N(q)) = Ψ(p1) ⊗ ... ⊗ Ψ(q) chain composition
7
+ * - Configurable axis mapping based on prime number-theoretic properties
8
+ *
9
+ * The semantic axes are NOT hardcoded with meanings. Instead, the axes
10
+ * emerge from the prime's number-theoretic properties:
11
+ * - Quadratic residue character (mod 4, mod 8)
12
+ * - Position in prime sequence
13
+ * - Relationship to golden ratio φ
14
+ *
15
+ * Meaning emerges from the geometric relationships between primes,
16
+ * not from predetermined labels.
17
+ */
18
+
19
+ const { isPrime, twistAngle, nthPrime } = require('./prime');
20
+
21
+ // ============================================================================
22
+ // AXIS MAPPING STRATEGIES
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Golden ratio for axis distribution
27
+ */
28
+ const PHI = (1 + Math.sqrt(5)) / 2;
29
+
30
+ /**
31
+ * Base class for axis mapping strategies
32
+ */
33
+ class AxisMapper {
34
+ /**
35
+ * Map a prime to an axis vector {i, j, k}
36
+ * @param {number} p - Prime number
37
+ * @returns {{i: number, j: number, k: number}} Unit vector
38
+ */
39
+ map(p) {
40
+ throw new Error('Subclass must implement map()');
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Modular axis mapper - uses quadratic residue properties
46
+ *
47
+ * Maps primes to axes based on their behavior mod small numbers:
48
+ * - p mod 4 determines i-component sign
49
+ * - p mod 8 determines j-component weight
50
+ * - p mod 6 determines k-component weight
51
+ *
52
+ * This is purely number-theoretic with no semantic labels.
53
+ */
54
+ class ModularAxisMapper extends AxisMapper {
55
+ map(p) {
56
+ // Use quadratic residue character for i-axis
57
+ // p ≡ 1 (mod 4) means p = a² + b² (sum of two squares)
58
+ const mod4 = p % 4;
59
+ const i = mod4 === 1 ? 1 : mod4 === 3 ? -1 : 0;
60
+
61
+ // Use mod 8 for j-axis (determines if p = a² + 2b²)
62
+ const mod8 = p % 8;
63
+ const j = (mod8 === 1 || mod8 === 3) ? 1 : (mod8 === 5 || mod8 === 7) ? -1 : 0;
64
+
65
+ // Use mod 6 for k-axis (relates to cubic residues)
66
+ const mod6 = p % 6;
67
+ const k = mod6 === 1 ? 1 : mod6 === 5 ? -1 : 0;
68
+
69
+ // Normalize to unit vector
70
+ const len = Math.sqrt(i*i + j*j + k*k);
71
+ if (len < 1e-10) {
72
+ // p = 2 or p = 3: return special axis
73
+ return p === 2 ? { i: 1, j: 0, k: 0 } : { i: 0, j: 1, k: 0 };
74
+ }
75
+
76
+ return { i: i/len, j: j/len, k: k/len };
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Golden ratio axis mapper - uses golden angle distribution
82
+ *
83
+ * Maps each prime to a point on the unit sphere using the golden angle,
84
+ * creating maximally uniform distribution (like sunflower seeds).
85
+ */
86
+ class GoldenAxisMapper extends AxisMapper {
87
+ constructor() {
88
+ super();
89
+ this.primeIndex = new Map();
90
+ this.currentIndex = 0;
91
+ }
92
+
93
+ map(p) {
94
+ // Get or assign index for this prime
95
+ if (!this.primeIndex.has(p)) {
96
+ this.primeIndex.set(p, this.currentIndex++);
97
+ }
98
+ const n = this.primeIndex.get(p);
99
+
100
+ // Golden angle in radians
101
+ const goldenAngle = 2 * Math.PI / (PHI * PHI);
102
+
103
+ // Spherical coordinates using golden ratio
104
+ const theta = goldenAngle * n;
105
+ const phi = Math.acos(1 - 2 * (n + 0.5) / (this.currentIndex + 1));
106
+
107
+ return {
108
+ i: Math.sin(phi) * Math.cos(theta),
109
+ j: Math.sin(phi) * Math.sin(theta),
110
+ k: Math.cos(phi)
111
+ };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Twist angle axis mapper - uses κ(p) = 360°/p to distribute axes
117
+ *
118
+ * The twist angle naturally encodes the prime's "rotation frequency"
119
+ * and creates a geometrically meaningful distribution.
120
+ */
121
+ class TwistAxisMapper extends AxisMapper {
122
+ map(p) {
123
+ // Use twist angle for spherical position
124
+ const kappa = twistAngle(p) * Math.PI / 180; // to radians
125
+
126
+ // Use log(p) for elevation angle (larger primes → lower elevation)
127
+ const elevation = Math.PI / 2 * (1 - Math.log(p) / Math.log(1000));
128
+
129
+ return {
130
+ i: Math.cos(kappa) * Math.cos(elevation),
131
+ j: Math.sin(kappa) * Math.cos(elevation),
132
+ k: Math.sin(elevation)
133
+ };
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Custom axis mapper - user provides the mapping function
139
+ */
140
+ class CustomAxisMapper extends AxisMapper {
141
+ constructor(mapFn) {
142
+ super();
143
+ this.mapFn = mapFn;
144
+ }
145
+
146
+ map(p) {
147
+ const axis = this.mapFn(p);
148
+ // Normalize
149
+ const len = Math.sqrt(axis.i ** 2 + axis.j ** 2 + axis.k ** 2);
150
+ if (len < 1e-10) return { i: 1, j: 0, k: 0 };
151
+ return { i: axis.i / len, j: axis.j / len, k: axis.k / len };
152
+ }
153
+ }
154
+
155
+ // Default mapper uses modular arithmetic (purely number-theoretic)
156
+ let defaultMapper = new ModularAxisMapper();
157
+
158
+ /**
159
+ * Get the current axis mapper
160
+ */
161
+ function getAxisMapper() {
162
+ return defaultMapper;
163
+ }
164
+
165
+ /**
166
+ * Set a new axis mapper
167
+ * @param {AxisMapper} mapper - New mapper to use
168
+ */
169
+ function setAxisMapper(mapper) {
170
+ if (!(mapper instanceof AxisMapper)) {
171
+ throw new TypeError('Mapper must be instance of AxisMapper');
172
+ }
173
+ defaultMapper = mapper;
174
+ }
175
+
176
+ /**
177
+ * Prime → axis mapping using current mapper
178
+ * @param {number} p - Prime number
179
+ * @returns {{i: number, j: number, k: number}} Unit axis vector
180
+ */
181
+ function primeToAxis(p) {
182
+ return defaultMapper.map(p);
183
+ }
184
+
185
+ // ============================================================================
186
+ // UNIT QUATERNION CLASS
187
+ // ============================================================================
188
+
189
+ /**
190
+ * UnitQuaternion - Quaternion with unit norm for rotations
191
+ * q = a + bi + cj + dk where |q| = 1
192
+ */
193
+ class UnitQuaternion {
194
+ constructor(a = 1, b = 0, c = 0, d = 0) {
195
+ this.a = a; // Real/scalar part
196
+ this.b = b; // i component
197
+ this.c = c; // j component
198
+ this.d = d; // k component
199
+ }
200
+
201
+ /**
202
+ * Create unit quaternion from angle-axis representation
203
+ * q = cos(θ/2) + sin(θ/2)(xi + yj + zk)
204
+ *
205
+ * @param {number} theta - Rotation angle in radians
206
+ * @param {Object} axis - Axis vector {i, j, k}
207
+ */
208
+ static fromAxisAngle(theta, axis) {
209
+ const halfTheta = theta / 2;
210
+ const sinHalf = Math.sin(halfTheta);
211
+ const cosHalf = Math.cos(halfTheta);
212
+
213
+ // Normalize axis
214
+ const len = Math.sqrt(axis.i ** 2 + axis.j ** 2 + axis.k ** 2);
215
+ const norm = len > 1e-10 ? len : 1;
216
+
217
+ return new UnitQuaternion(
218
+ cosHalf,
219
+ sinHalf * axis.i / norm,
220
+ sinHalf * axis.j / norm,
221
+ sinHalf * axis.k / norm
222
+ );
223
+ }
224
+
225
+ /**
226
+ * Create identity quaternion (1 + 0i + 0j + 0k)
227
+ */
228
+ static identity() {
229
+ return new UnitQuaternion(1, 0, 0, 0);
230
+ }
231
+
232
+ /**
233
+ * Hamilton product: q₁ ⊗ q₂ (non-commutative!)
234
+ */
235
+ multiply(other) {
236
+ return new UnitQuaternion(
237
+ this.a * other.a - this.b * other.b - this.c * other.c - this.d * other.d,
238
+ this.a * other.b + this.b * other.a + this.c * other.d - this.d * other.c,
239
+ this.a * other.c - this.b * other.d + this.c * other.a + this.d * other.b,
240
+ this.a * other.d + this.b * other.c - this.c * other.b + this.d * other.a
241
+ );
242
+ }
243
+
244
+ /**
245
+ * Quaternion conjugate: q* = a - bi - cj - dk
246
+ */
247
+ conjugate() {
248
+ return new UnitQuaternion(this.a, -this.b, -this.c, -this.d);
249
+ }
250
+
251
+ /**
252
+ * Quaternion norm: |q|
253
+ */
254
+ norm() {
255
+ return Math.sqrt(this.a ** 2 + this.b ** 2 + this.c ** 2 + this.d ** 2);
256
+ }
257
+
258
+ /**
259
+ * Normalize to unit quaternion
260
+ */
261
+ normalize() {
262
+ const n = this.norm();
263
+ if (n < 1e-10) return UnitQuaternion.identity();
264
+ return new UnitQuaternion(
265
+ this.a / n, this.b / n, this.c / n, this.d / n
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Inner product with another quaternion
271
+ */
272
+ dot(other) {
273
+ return this.a * other.a + this.b * other.b + this.c * other.c + this.d * other.d;
274
+ }
275
+
276
+ /**
277
+ * Extract rotation angle (radians)
278
+ */
279
+ angle() {
280
+ return 2 * Math.acos(Math.min(1, Math.max(-1, this.a)));
281
+ }
282
+
283
+ /**
284
+ * Extract rotation axis
285
+ */
286
+ axis() {
287
+ const sinHalf = Math.sqrt(1 - this.a * this.a);
288
+ if (sinHalf < 1e-10) {
289
+ return { i: 0, j: 0, k: 1 }; // Default axis
290
+ }
291
+ return {
292
+ i: this.b / sinHalf,
293
+ j: this.c / sinHalf,
294
+ k: this.d / sinHalf
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Spherical linear interpolation (slerp)
300
+ */
301
+ slerp(other, t) {
302
+ let cosTheta = this.dot(other);
303
+
304
+ // Handle negative dot product (take shorter path)
305
+ let target = other;
306
+ if (cosTheta < 0) {
307
+ cosTheta = -cosTheta;
308
+ target = new UnitQuaternion(-other.a, -other.b, -other.c, -other.d);
309
+ }
310
+
311
+ if (cosTheta > 0.9995) {
312
+ // Linear interpolation for nearly parallel quaternions
313
+ return new UnitQuaternion(
314
+ this.a + t * (target.a - this.a),
315
+ this.b + t * (target.b - this.b),
316
+ this.c + t * (target.c - this.c),
317
+ this.d + t * (target.d - this.d)
318
+ ).normalize();
319
+ }
320
+
321
+ const theta = Math.acos(cosTheta);
322
+ const sinTheta = Math.sin(theta);
323
+ const s0 = Math.sin((1 - t) * theta) / sinTheta;
324
+ const s1 = Math.sin(t * theta) / sinTheta;
325
+
326
+ return new UnitQuaternion(
327
+ s0 * this.a + s1 * target.a,
328
+ s0 * this.b + s1 * target.b,
329
+ s0 * this.c + s1 * target.c,
330
+ s0 * this.d + s1 * target.d
331
+ );
332
+ }
333
+
334
+ /**
335
+ * Convert to array [a, b, c, d]
336
+ */
337
+ toArray() {
338
+ return [this.a, this.b, this.c, this.d];
339
+ }
340
+
341
+ /**
342
+ * Clone the quaternion
343
+ */
344
+ clone() {
345
+ return new UnitQuaternion(this.a, this.b, this.c, this.d);
346
+ }
347
+
348
+ toString() {
349
+ const parts = [];
350
+ if (Math.abs(this.a) > 1e-10) parts.push(this.a.toFixed(4));
351
+ if (Math.abs(this.b) > 1e-10) parts.push(`${this.b.toFixed(4)}i`);
352
+ if (Math.abs(this.c) > 1e-10) parts.push(`${this.c.toFixed(4)}j`);
353
+ if (Math.abs(this.d) > 1e-10) parts.push(`${this.d.toFixed(4)}k`);
354
+ return parts.length > 0 ? parts.join(' + ').replace(/\+ -/g, '- ') : '0';
355
+ }
356
+ }
357
+
358
+ // ============================================================================
359
+ // PRIME-TO-QUATERNION MAPPING Ψ(p)
360
+ // ============================================================================
361
+
362
+ /**
363
+ * Prime-to-quaternion mapping functions
364
+ *
365
+ * From PIQC §3.1:
366
+ * Ψ(p) = cos(h(p)) + u(p)sin(h(p))
367
+ *
368
+ * Where:
369
+ * - h(p) is the half-angle function
370
+ * - u(p) is the unit axis function
371
+ */
372
+
373
+ /**
374
+ * Half-angle function h(p)
375
+ * Maps prime to rotation half-angle
376
+ */
377
+ function halfAngle(p) {
378
+ // Use twist angle κ(p) = 360°/p as the rotation angle
379
+ // h(p) = κ(p)/2 in radians
380
+ const kappaRad = (twistAngle(p) * Math.PI) / 180;
381
+ return kappaRad / 2;
382
+ }
383
+
384
+ /**
385
+ * Axis function u(p)
386
+ * Maps prime to rotation axis based on semantic category
387
+ */
388
+ function axisFunction(p) {
389
+ return primeToAxis(p);
390
+ }
391
+
392
+ /**
393
+ * Prime-to-quaternion map Ψ(p)
394
+ *
395
+ * @param {number} p - Prime number
396
+ * @returns {UnitQuaternion} Unit quaternion for this prime
397
+ */
398
+ function Psi(p) {
399
+ if (!isPrime(p)) {
400
+ throw new Error(`Psi requires prime, got ${p}`);
401
+ }
402
+
403
+ const h = halfAngle(p);
404
+ const u = axisFunction(p);
405
+
406
+ return UnitQuaternion.fromAxisAngle(2 * h, u);
407
+ }
408
+
409
+ // ============================================================================
410
+ // QUATERNIONIC CHAIN COMPOSITION
411
+ // ============================================================================
412
+
413
+ /**
414
+ * Compute quaternionic embedding of an operator chain
415
+ *
416
+ * Q(A(p₁)...A(pₖ)N(q)) = Ψ(p₁) ⊗ ... ⊗ Ψ(pₖ) ⊗ Ψ(q)
417
+ *
418
+ * @param {number[]} operatorPrimes - Array of operator primes [p₁, ..., pₖ]
419
+ * @param {number} nounPrime - Noun prime q
420
+ * @returns {UnitQuaternion} Composed quaternion
421
+ */
422
+ function quaternionicChain(operatorPrimes, nounPrime) {
423
+ // Start with identity
424
+ let result = UnitQuaternion.identity();
425
+
426
+ // Apply operators left-to-right (Hamilton product is associative)
427
+ for (const p of operatorPrimes) {
428
+ result = result.multiply(Psi(p));
429
+ }
430
+
431
+ // Apply noun quaternion
432
+ result = result.multiply(Psi(nounPrime));
433
+
434
+ return result;
435
+ }
436
+
437
+ /**
438
+ * Verify non-commutativity of quaternionic composition
439
+ *
440
+ * @param {number} p1 - First prime
441
+ * @param {number} p2 - Second prime
442
+ * @returns {Object} Comparison of Ψ(p₁)⊗Ψ(p₂) vs Ψ(p₂)⊗Ψ(p₁)
443
+ */
444
+ function verifyNonCommutativity(p1, p2) {
445
+ const psi1 = Psi(p1);
446
+ const psi2 = Psi(p2);
447
+
448
+ const forward = psi1.multiply(psi2);
449
+ const backward = psi2.multiply(psi1);
450
+
451
+ const difference = Math.sqrt(
452
+ (forward.a - backward.a) ** 2 +
453
+ (forward.b - backward.b) ** 2 +
454
+ (forward.c - backward.c) ** 2 +
455
+ (forward.d - backward.d) ** 2
456
+ );
457
+
458
+ return {
459
+ p1, p2,
460
+ forward: forward.toArray(),
461
+ backward: backward.toArray(),
462
+ difference,
463
+ isCommutative: difference < 1e-10
464
+ };
465
+ }
466
+
467
+ // ============================================================================
468
+ // SEMANTIC COHERENCE METRICS
469
+ // ============================================================================
470
+
471
+ /**
472
+ * Compute semantic coherence between two quaternionic states
473
+ *
474
+ * Coherence = |⟨q₁, q₂⟩|² = (q₁ · q₂)²
475
+ *
476
+ * @param {UnitQuaternion} q1 - First quaternion
477
+ * @param {UnitQuaternion} q2 - Second quaternion
478
+ * @returns {number} Coherence in [0, 1]
479
+ */
480
+ function semanticCoherence(q1, q2) {
481
+ const dot = q1.dot(q2);
482
+ return dot * dot;
483
+ }
484
+
485
+ /**
486
+ * Extract axis projections from a quaternion
487
+ *
488
+ * Returns the normalized i, j, k components without semantic labels.
489
+ * The meaning of these axes depends on the AxisMapper in use.
490
+ *
491
+ * @param {UnitQuaternion} q - Quaternion to analyze
492
+ * @returns {Object} Projections onto i, j, k axes
493
+ */
494
+ function axisProjections(q) {
495
+ // The i, j, k components - meaning depends on mapper configuration
496
+ const norm = Math.sqrt(q.b ** 2 + q.c ** 2 + q.d ** 2);
497
+
498
+ return {
499
+ i: norm > 1e-10 ? q.b / norm : 0,
500
+ j: norm > 1e-10 ? q.c / norm : 0,
501
+ k: norm > 1e-10 ? q.d / norm : 0,
502
+ rotationAngle: q.angle() * 180 / Math.PI, // degrees
503
+ purity: norm // How much of the quaternion is "rotation" vs "identity"
504
+ };
505
+ }
506
+
507
+ /**
508
+ * Compute semantic distance between chains
509
+ *
510
+ * @param {number[]} ops1 - First chain operators
511
+ * @param {number} noun1 - First chain noun
512
+ * @param {number[]} ops2 - Second chain operators
513
+ * @param {number} noun2 - Second chain noun
514
+ * @returns {number} Geodesic distance on quaternion sphere
515
+ */
516
+ function chainDistance(ops1, noun1, ops2, noun2) {
517
+ const q1 = quaternionicChain(ops1, noun1);
518
+ const q2 = quaternionicChain(ops2, noun2);
519
+
520
+ const dot = Math.abs(q1.dot(q2));
521
+ const angle = 2 * Math.acos(Math.min(1, dot));
522
+
523
+ return angle;
524
+ }
525
+
526
+ // ============================================================================
527
+ // PRIME FAMILY ANALYSIS
528
+ // ============================================================================
529
+
530
+ /**
531
+ * Analyze quaternionic properties of a prime family
532
+ *
533
+ * @param {number[]} primes - Array of primes to analyze
534
+ * @returns {Object} Family analysis
535
+ */
536
+ function analyzePrimeFamily(primes) {
537
+ const quaternions = primes.map(p => ({ prime: p, psi: Psi(p) }));
538
+
539
+ // Compute pairwise coherences
540
+ const coherences = [];
541
+ for (let i = 0; i < primes.length; i++) {
542
+ for (let j = i + 1; j < primes.length; j++) {
543
+ coherences.push({
544
+ p1: primes[i],
545
+ p2: primes[j],
546
+ coherence: semanticCoherence(quaternions[i].psi, quaternions[j].psi)
547
+ });
548
+ }
549
+ }
550
+
551
+ // Find most coherent pairs
552
+ coherences.sort((a, b) => b.coherence - a.coherence);
553
+
554
+ // Compute centroid (average quaternion)
555
+ let centroid = new UnitQuaternion(0, 0, 0, 0);
556
+ for (const { psi } of quaternions) {
557
+ centroid = new UnitQuaternion(
558
+ centroid.a + psi.a,
559
+ centroid.b + psi.b,
560
+ centroid.c + psi.c,
561
+ centroid.d + psi.d
562
+ );
563
+ }
564
+ centroid = centroid.normalize();
565
+
566
+ // Compute spread (variance around centroid)
567
+ let spread = 0;
568
+ for (const { psi } of quaternions) {
569
+ const dot = psi.dot(centroid);
570
+ spread += 1 - dot * dot;
571
+ }
572
+ spread /= primes.length;
573
+
574
+ return {
575
+ quaternions: quaternions.map(({ prime, psi }) => ({
576
+ prime,
577
+ quaternion: psi.toArray(),
578
+ angle: psi.angle() * 180 / Math.PI,
579
+ projections: axisProjections(psi)
580
+ })),
581
+ coherences: coherences.slice(0, 10), // Top 10
582
+ centroid: centroid.toArray(),
583
+ spread,
584
+ meanCoherence: coherences.reduce((s, c) => s + c.coherence, 0) / coherences.length
585
+ };
586
+ }
587
+
588
+ // ============================================================================
589
+ // EXPORTS
590
+ // ============================================================================
591
+
592
+ module.exports = {
593
+ // Axis mapping strategies
594
+ AxisMapper,
595
+ ModularAxisMapper,
596
+ GoldenAxisMapper,
597
+ TwistAxisMapper,
598
+ CustomAxisMapper,
599
+ getAxisMapper,
600
+ setAxisMapper,
601
+ primeToAxis,
602
+ PHI,
603
+
604
+ // Unit quaternion class
605
+ UnitQuaternion,
606
+
607
+ // Prime-to-quaternion mapping
608
+ halfAngle,
609
+ axisFunction,
610
+ Psi,
611
+
612
+ // Chain composition
613
+ quaternionicChain,
614
+ verifyNonCommutativity,
615
+
616
+ // Coherence metrics (no semantic labels)
617
+ semanticCoherence, // Keep name for compatibility but it's just quaternion coherence
618
+ axisProjections,
619
+ chainDistance,
620
+
621
+ // Analysis
622
+ analyzePrimeFamily
623
+ };