@aleph-ai/tinyaleph 1.0.2 → 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.
- package/README.md +449 -2
- package/core/entanglement.js +712 -0
- package/core/events.js +907 -0
- package/core/hypercomplex.js +500 -0
- package/core/index.js +129 -1
- package/core/lambda.js +845 -0
- package/core/reduction.js +741 -0
- package/core/rformer-layers.js +811 -0
- package/core/types.js +913 -0
- package/docs/README.md +84 -0
- package/docs/design/ALEPH_CHAT_ARCHITECTURE.md +1 -1
- package/docs/design/AUTONOMOUS_LEARNING_DESIGN.md +1492 -0
- package/docs/design/WHITEPAPER_GAP_ANALYSIS.md +171 -4
- package/docs/reference/01-core.md +515 -1
- package/docs/reference/02-physics.md +186 -1
- package/docs/reference/README.md +277 -1
- package/docs/theory/03-phase-synchronization.md +196 -0
- package/docs/theory/README.md +47 -0
- package/package.json +2 -2
- package/physics/index.js +76 -10
- package/physics/primeon_z_ladder_multi.js +669 -0
- package/physics/primeon_z_ladder_u.js +493 -0
- package/physics/stochastic-kuramoto.js +566 -0
- package/physics/sync-models.js +770 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prime Entanglement Graph
|
|
3
|
+
*
|
|
4
|
+
* Explicit representation of prime relationships that emerge from:
|
|
5
|
+
* - Co-occurrence in semantic contexts
|
|
6
|
+
* - Phase resonance in Kuramoto dynamics
|
|
7
|
+
* - Coherence in hypercomplex states
|
|
8
|
+
*
|
|
9
|
+
* Structure:
|
|
10
|
+
* - Nodes: Prime numbers
|
|
11
|
+
* - Edges: Weighted by resonance strength, phase alignment, co-occurrence
|
|
12
|
+
*
|
|
13
|
+
* Applications:
|
|
14
|
+
* - Semantic relationship tracking
|
|
15
|
+
* - Adaptive coupling for synchronization
|
|
16
|
+
* - Memory consolidation patterns
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const { isPrime, firstNPrimes } = require('./prime');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Edge data for entanglement between two primes
|
|
25
|
+
*/
|
|
26
|
+
class EntanglementEdge {
|
|
27
|
+
constructor(source, target) {
|
|
28
|
+
this.source = source;
|
|
29
|
+
this.target = target;
|
|
30
|
+
this.weight = 0;
|
|
31
|
+
this.phaseAlignment = 0;
|
|
32
|
+
this.cooccurrenceCount = 0;
|
|
33
|
+
this.resonanceStrength = 0;
|
|
34
|
+
this.lastUpdated = Date.now();
|
|
35
|
+
this.createdAt = Date.now();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Update edge from observation
|
|
40
|
+
* @param {number} strength - Observation strength (0-1)
|
|
41
|
+
* @param {number} phaseCorrelation - Phase correlation (-1 to 1)
|
|
42
|
+
*/
|
|
43
|
+
observe(strength, phaseCorrelation = 0) {
|
|
44
|
+
this.cooccurrenceCount++;
|
|
45
|
+
|
|
46
|
+
// Exponential moving average for weight
|
|
47
|
+
const alpha = 0.1;
|
|
48
|
+
this.weight = (1 - alpha) * this.weight + alpha * strength;
|
|
49
|
+
this.phaseAlignment = (1 - alpha) * this.phaseAlignment + alpha * phaseCorrelation;
|
|
50
|
+
this.resonanceStrength = Math.sqrt(this.weight * Math.abs(this.phaseAlignment));
|
|
51
|
+
|
|
52
|
+
this.lastUpdated = Date.now();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Apply decay to edge weight
|
|
57
|
+
* @param {number} rate - Decay rate (0-1)
|
|
58
|
+
*/
|
|
59
|
+
decay(rate) {
|
|
60
|
+
this.weight *= (1 - rate);
|
|
61
|
+
this.resonanceStrength *= (1 - rate);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get age in milliseconds
|
|
66
|
+
*/
|
|
67
|
+
age() {
|
|
68
|
+
return Date.now() - this.createdAt;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get time since last update in milliseconds
|
|
73
|
+
*/
|
|
74
|
+
staleness() {
|
|
75
|
+
return Date.now() - this.lastUpdated;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
toJSON() {
|
|
79
|
+
return {
|
|
80
|
+
source: this.source,
|
|
81
|
+
target: this.target,
|
|
82
|
+
weight: this.weight,
|
|
83
|
+
phaseAlignment: this.phaseAlignment,
|
|
84
|
+
cooccurrenceCount: this.cooccurrenceCount,
|
|
85
|
+
resonanceStrength: this.resonanceStrength,
|
|
86
|
+
age: this.age(),
|
|
87
|
+
staleness: this.staleness()
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* PrimeEntanglementGraph - Main graph class
|
|
94
|
+
*
|
|
95
|
+
* Tracks relationships between primes using adjacency list representation.
|
|
96
|
+
*/
|
|
97
|
+
class PrimeEntanglementGraph {
|
|
98
|
+
/**
|
|
99
|
+
* @param {number[]} primes - Initial set of primes (or number for first N primes)
|
|
100
|
+
* @param {object} options - Configuration
|
|
101
|
+
* @param {number} [options.decayRate=0.01] - Edge weight decay per decay() call
|
|
102
|
+
* @param {number} [options.pruneThreshold=0.01] - Minimum weight to keep edges
|
|
103
|
+
* @param {number} [options.maxEdgesPerNode=50] - Maximum neighbors per prime
|
|
104
|
+
*/
|
|
105
|
+
constructor(primes = 100, options = {}) {
|
|
106
|
+
// Handle number input as "first N primes"
|
|
107
|
+
if (typeof primes === 'number') {
|
|
108
|
+
this.primes = new Set(firstNPrimes(primes));
|
|
109
|
+
} else {
|
|
110
|
+
this.primes = new Set(primes.filter(p => isPrime(p)));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.decayRate = options.decayRate ?? 0.01;
|
|
114
|
+
this.pruneThreshold = options.pruneThreshold ?? 0.01;
|
|
115
|
+
this.maxEdgesPerNode = options.maxEdgesPerNode ?? 50;
|
|
116
|
+
|
|
117
|
+
// Adjacency list: Map<prime, Map<prime, EntanglementEdge>>
|
|
118
|
+
this.adjacency = new Map();
|
|
119
|
+
|
|
120
|
+
// Initialize empty adjacency lists
|
|
121
|
+
for (const p of this.primes) {
|
|
122
|
+
this.adjacency.set(p, new Map());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Statistics
|
|
126
|
+
this.totalObservations = 0;
|
|
127
|
+
this.edgeCount = 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Add a prime to the graph
|
|
132
|
+
* @param {number} p - Prime to add
|
|
133
|
+
*/
|
|
134
|
+
addPrime(p) {
|
|
135
|
+
if (!isPrime(p)) return false;
|
|
136
|
+
if (this.primes.has(p)) return false;
|
|
137
|
+
|
|
138
|
+
this.primes.add(p);
|
|
139
|
+
this.adjacency.set(p, new Map());
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get edge between two primes (creates if doesn't exist)
|
|
145
|
+
* @param {number} p1 - First prime
|
|
146
|
+
* @param {number} p2 - Second prime
|
|
147
|
+
* @returns {EntanglementEdge|null} Edge or null if primes not in graph
|
|
148
|
+
*/
|
|
149
|
+
getEdge(p1, p2) {
|
|
150
|
+
if (!this.primes.has(p1) || !this.primes.has(p2)) return null;
|
|
151
|
+
if (p1 === p2) return null;
|
|
152
|
+
|
|
153
|
+
// Ensure consistent ordering
|
|
154
|
+
const [source, target] = p1 < p2 ? [p1, p2] : [p2, p1];
|
|
155
|
+
|
|
156
|
+
const neighbors = this.adjacency.get(source);
|
|
157
|
+
if (!neighbors.has(target)) {
|
|
158
|
+
const edge = new EntanglementEdge(source, target);
|
|
159
|
+
neighbors.set(target, edge);
|
|
160
|
+
this.adjacency.get(target).set(source, edge); // Bidirectional reference
|
|
161
|
+
this.edgeCount++;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return neighbors.get(target);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if edge exists
|
|
169
|
+
* @param {number} p1 - First prime
|
|
170
|
+
* @param {number} p2 - Second prime
|
|
171
|
+
*/
|
|
172
|
+
hasEdge(p1, p2) {
|
|
173
|
+
if (!this.primes.has(p1) || !this.primes.has(p2)) return false;
|
|
174
|
+
const [source, target] = p1 < p2 ? [p1, p2] : [p2, p1];
|
|
175
|
+
return this.adjacency.get(source)?.has(target) ?? false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Observe co-occurrence of primes
|
|
180
|
+
*
|
|
181
|
+
* @param {number[]} primes1 - First set of primes
|
|
182
|
+
* @param {number[]} primes2 - Second set of primes (or same as first)
|
|
183
|
+
* @param {number} strength - Observation strength (0-1)
|
|
184
|
+
* @param {Map<number, number>} phases - Optional phase map for correlation
|
|
185
|
+
*/
|
|
186
|
+
observe(primes1, primes2 = null, strength = 1.0, phases = null) {
|
|
187
|
+
primes2 = primes2 || primes1;
|
|
188
|
+
|
|
189
|
+
// Filter to known primes
|
|
190
|
+
const set1 = primes1.filter(p => this.primes.has(p));
|
|
191
|
+
const set2 = primes2.filter(p => this.primes.has(p));
|
|
192
|
+
|
|
193
|
+
// Create/update edges for all pairs
|
|
194
|
+
for (const p1 of set1) {
|
|
195
|
+
for (const p2 of set2) {
|
|
196
|
+
if (p1 === p2) continue;
|
|
197
|
+
|
|
198
|
+
const edge = this.getEdge(p1, p2);
|
|
199
|
+
if (edge) {
|
|
200
|
+
// Compute phase correlation if phases provided
|
|
201
|
+
let phaseCorr = 0;
|
|
202
|
+
if (phases && phases.has(p1) && phases.has(p2)) {
|
|
203
|
+
const phase1 = phases.get(p1);
|
|
204
|
+
const phase2 = phases.get(p2);
|
|
205
|
+
phaseCorr = Math.cos(phase1 - phase2);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
edge.observe(strength, phaseCorr);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.totalObservations++;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get neighbors of a prime within k hops
|
|
218
|
+
* @param {number} prime - Source prime
|
|
219
|
+
* @param {number} depth - Maximum hop distance (default 1)
|
|
220
|
+
* @param {number} minWeight - Minimum edge weight to follow
|
|
221
|
+
* @returns {Map<number, {distance: number, pathWeight: number}>}
|
|
222
|
+
*/
|
|
223
|
+
neighbors(prime, depth = 1, minWeight = 0) {
|
|
224
|
+
if (!this.primes.has(prime)) return new Map();
|
|
225
|
+
|
|
226
|
+
const result = new Map();
|
|
227
|
+
const visited = new Set([prime]);
|
|
228
|
+
const queue = [{ node: prime, distance: 0, pathWeight: 1 }];
|
|
229
|
+
|
|
230
|
+
while (queue.length > 0) {
|
|
231
|
+
const { node, distance, pathWeight } = queue.shift();
|
|
232
|
+
|
|
233
|
+
if (distance >= depth) continue;
|
|
234
|
+
|
|
235
|
+
const neighbors = this.adjacency.get(node);
|
|
236
|
+
if (!neighbors) continue;
|
|
237
|
+
|
|
238
|
+
for (const [neighbor, edge] of neighbors) {
|
|
239
|
+
if (visited.has(neighbor)) continue;
|
|
240
|
+
if (edge.weight < minWeight) continue;
|
|
241
|
+
|
|
242
|
+
visited.add(neighbor);
|
|
243
|
+
const newPathWeight = pathWeight * edge.weight;
|
|
244
|
+
|
|
245
|
+
result.set(neighbor, {
|
|
246
|
+
distance: distance + 1,
|
|
247
|
+
pathWeight: newPathWeight,
|
|
248
|
+
edge: edge.toJSON()
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
queue.push({
|
|
252
|
+
node: neighbor,
|
|
253
|
+
distance: distance + 1,
|
|
254
|
+
pathWeight: newPathWeight
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Find shortest path between two primes (Dijkstra with inverse weights)
|
|
264
|
+
* @param {number} source - Start prime
|
|
265
|
+
* @param {number} target - End prime
|
|
266
|
+
* @returns {object|null} Path info or null if no path
|
|
267
|
+
*/
|
|
268
|
+
shortestPath(source, target) {
|
|
269
|
+
if (!this.primes.has(source) || !this.primes.has(target)) return null;
|
|
270
|
+
if (source === target) return { path: [source], totalWeight: 1, hops: 0 };
|
|
271
|
+
|
|
272
|
+
const distances = new Map();
|
|
273
|
+
const previous = new Map();
|
|
274
|
+
const unvisited = new Set(this.primes);
|
|
275
|
+
|
|
276
|
+
for (const p of this.primes) {
|
|
277
|
+
distances.set(p, Infinity);
|
|
278
|
+
}
|
|
279
|
+
distances.set(source, 0);
|
|
280
|
+
|
|
281
|
+
while (unvisited.size > 0) {
|
|
282
|
+
// Find minimum distance node
|
|
283
|
+
let current = null;
|
|
284
|
+
let minDist = Infinity;
|
|
285
|
+
for (const p of unvisited) {
|
|
286
|
+
const d = distances.get(p);
|
|
287
|
+
if (d < minDist) {
|
|
288
|
+
minDist = d;
|
|
289
|
+
current = p;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (current === null || minDist === Infinity) break;
|
|
294
|
+
if (current === target) break;
|
|
295
|
+
|
|
296
|
+
unvisited.delete(current);
|
|
297
|
+
|
|
298
|
+
// Update neighbors
|
|
299
|
+
const neighbors = this.adjacency.get(current);
|
|
300
|
+
if (!neighbors) continue;
|
|
301
|
+
|
|
302
|
+
for (const [neighbor, edge] of neighbors) {
|
|
303
|
+
if (!unvisited.has(neighbor)) continue;
|
|
304
|
+
|
|
305
|
+
// Distance = inverse of weight (stronger = shorter)
|
|
306
|
+
const edgeDist = edge.weight > 0.001 ? 1 / edge.weight : 1000;
|
|
307
|
+
const alt = distances.get(current) + edgeDist;
|
|
308
|
+
|
|
309
|
+
if (alt < distances.get(neighbor)) {
|
|
310
|
+
distances.set(neighbor, alt);
|
|
311
|
+
previous.set(neighbor, current);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Reconstruct path
|
|
317
|
+
if (!previous.has(target) && source !== target) return null;
|
|
318
|
+
|
|
319
|
+
const path = [target];
|
|
320
|
+
let current = target;
|
|
321
|
+
let totalWeight = 1;
|
|
322
|
+
|
|
323
|
+
while (previous.has(current)) {
|
|
324
|
+
const prev = previous.get(current);
|
|
325
|
+
const edge = this.getEdge(prev, current);
|
|
326
|
+
totalWeight *= edge.weight;
|
|
327
|
+
path.unshift(prev);
|
|
328
|
+
current = prev;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
path,
|
|
333
|
+
totalWeight,
|
|
334
|
+
hops: path.length - 1,
|
|
335
|
+
distance: distances.get(target)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Compute clustering coefficient for a prime
|
|
341
|
+
* Ratio of edges between neighbors to possible edges
|
|
342
|
+
*
|
|
343
|
+
* @param {number} prime - Prime to analyze
|
|
344
|
+
*/
|
|
345
|
+
clusteringCoefficient(prime) {
|
|
346
|
+
if (!this.primes.has(prime)) return 0;
|
|
347
|
+
|
|
348
|
+
const neighbors = Array.from(this.adjacency.get(prime)?.keys() || []);
|
|
349
|
+
const k = neighbors.length;
|
|
350
|
+
|
|
351
|
+
if (k < 2) return 0;
|
|
352
|
+
|
|
353
|
+
let triangles = 0;
|
|
354
|
+
for (let i = 0; i < k; i++) {
|
|
355
|
+
for (let j = i + 1; j < k; j++) {
|
|
356
|
+
if (this.hasEdge(neighbors[i], neighbors[j])) {
|
|
357
|
+
triangles++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const possibleTriangles = k * (k - 1) / 2;
|
|
363
|
+
return triangles / possibleTriangles;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Compute average clustering coefficient
|
|
368
|
+
*/
|
|
369
|
+
averageClusteringCoefficient() {
|
|
370
|
+
let sum = 0;
|
|
371
|
+
let count = 0;
|
|
372
|
+
|
|
373
|
+
for (const p of this.primes) {
|
|
374
|
+
const cc = this.clusteringCoefficient(p);
|
|
375
|
+
if (!isNaN(cc)) {
|
|
376
|
+
sum += cc;
|
|
377
|
+
count++;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return count > 0 ? sum / count : 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Compute degree centrality for a prime
|
|
386
|
+
*/
|
|
387
|
+
degreeCentrality(prime) {
|
|
388
|
+
if (!this.primes.has(prime)) return 0;
|
|
389
|
+
const degree = this.adjacency.get(prime)?.size || 0;
|
|
390
|
+
return degree / (this.primes.size - 1);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get top primes by degree centrality
|
|
395
|
+
* @param {number} k - Number of top primes
|
|
396
|
+
*/
|
|
397
|
+
topByDegree(k = 10) {
|
|
398
|
+
const centralities = [];
|
|
399
|
+
|
|
400
|
+
for (const p of this.primes) {
|
|
401
|
+
centralities.push({
|
|
402
|
+
prime: p,
|
|
403
|
+
centrality: this.degreeCentrality(p),
|
|
404
|
+
degree: this.adjacency.get(p)?.size || 0
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return centralities
|
|
409
|
+
.sort((a, b) => b.centrality - a.centrality)
|
|
410
|
+
.slice(0, k);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get weighted degree (sum of edge weights) for a prime
|
|
415
|
+
*/
|
|
416
|
+
weightedDegree(prime) {
|
|
417
|
+
if (!this.primes.has(prime)) return 0;
|
|
418
|
+
|
|
419
|
+
let sum = 0;
|
|
420
|
+
const neighbors = this.adjacency.get(prime);
|
|
421
|
+
if (!neighbors) return 0;
|
|
422
|
+
|
|
423
|
+
for (const edge of neighbors.values()) {
|
|
424
|
+
sum += edge.weight;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return sum;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Apply decay to all edges
|
|
432
|
+
* @param {number} rate - Decay rate (uses this.decayRate if not specified)
|
|
433
|
+
*/
|
|
434
|
+
decay(rate = null) {
|
|
435
|
+
const decayRate = rate ?? this.decayRate;
|
|
436
|
+
|
|
437
|
+
for (const neighbors of this.adjacency.values()) {
|
|
438
|
+
for (const edge of neighbors.values()) {
|
|
439
|
+
edge.decay(decayRate);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Prune edges below threshold
|
|
446
|
+
* @param {number} threshold - Minimum weight (uses this.pruneThreshold if not specified)
|
|
447
|
+
* @returns {number} Number of edges pruned
|
|
448
|
+
*/
|
|
449
|
+
prune(threshold = null) {
|
|
450
|
+
const pruneThreshold = threshold ?? this.pruneThreshold;
|
|
451
|
+
let pruned = 0;
|
|
452
|
+
|
|
453
|
+
for (const [prime, neighbors] of this.adjacency) {
|
|
454
|
+
const toRemove = [];
|
|
455
|
+
|
|
456
|
+
for (const [neighbor, edge] of neighbors) {
|
|
457
|
+
if (edge.weight < pruneThreshold) {
|
|
458
|
+
toRemove.push(neighbor);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (const neighbor of toRemove) {
|
|
463
|
+
neighbors.delete(neighbor);
|
|
464
|
+
this.adjacency.get(neighbor)?.delete(prime);
|
|
465
|
+
pruned++;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Each edge counted twice (bidirectional)
|
|
470
|
+
this.edgeCount -= pruned / 2;
|
|
471
|
+
return pruned / 2;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Convert to adjacency matrix for NetworkKuramoto
|
|
476
|
+
* @param {number[]} primeOrder - Ordering of primes (indices)
|
|
477
|
+
* @returns {number[][]} Adjacency matrix
|
|
478
|
+
*/
|
|
479
|
+
toAdjacencyMatrix(primeOrder = null) {
|
|
480
|
+
const order = primeOrder || Array.from(this.primes).sort((a, b) => a - b);
|
|
481
|
+
const n = order.length;
|
|
482
|
+
const matrix = Array(n).fill(null).map(() => Array(n).fill(0));
|
|
483
|
+
|
|
484
|
+
const primeToIdx = new Map(order.map((p, i) => [p, i]));
|
|
485
|
+
|
|
486
|
+
for (const [source, neighbors] of this.adjacency) {
|
|
487
|
+
const i = primeToIdx.get(source);
|
|
488
|
+
if (i === undefined) continue;
|
|
489
|
+
|
|
490
|
+
for (const [target, edge] of neighbors) {
|
|
491
|
+
const j = primeToIdx.get(target);
|
|
492
|
+
if (j === undefined) continue;
|
|
493
|
+
|
|
494
|
+
matrix[i][j] = edge.weight;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return matrix;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Create NetworkKuramoto model from this graph
|
|
503
|
+
* @param {number[]} frequencies - Oscillator frequencies
|
|
504
|
+
* @param {number} coupling - Global coupling multiplier
|
|
505
|
+
* @returns {object} NetworkKuramoto-compatible object
|
|
506
|
+
*/
|
|
507
|
+
toNetworkKuramoto(frequencies, coupling = 0.3) {
|
|
508
|
+
// Import dynamically to avoid circular dependency
|
|
509
|
+
const { NetworkKuramoto } = require('../physics/sync-models');
|
|
510
|
+
|
|
511
|
+
const primeOrder = Array.from(this.primes).sort((a, b) => a - b);
|
|
512
|
+
const adjacency = this.toAdjacencyMatrix(primeOrder);
|
|
513
|
+
|
|
514
|
+
// Ensure frequencies match prime count
|
|
515
|
+
const freqs = frequencies.slice(0, primeOrder.length);
|
|
516
|
+
while (freqs.length < primeOrder.length) {
|
|
517
|
+
freqs.push(1.0);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return new NetworkKuramoto(freqs, adjacency, coupling);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Export to edge list format
|
|
525
|
+
* @param {number} minWeight - Minimum weight to include
|
|
526
|
+
*/
|
|
527
|
+
toEdgeList(minWeight = 0) {
|
|
528
|
+
const edges = [];
|
|
529
|
+
const seen = new Set();
|
|
530
|
+
|
|
531
|
+
for (const [source, neighbors] of this.adjacency) {
|
|
532
|
+
for (const [target, edge] of neighbors) {
|
|
533
|
+
const key = `${Math.min(source, target)}-${Math.max(source, target)}`;
|
|
534
|
+
if (seen.has(key)) continue;
|
|
535
|
+
|
|
536
|
+
if (edge.weight >= minWeight) {
|
|
537
|
+
edges.push(edge.toJSON());
|
|
538
|
+
seen.add(key);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return edges;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Import from edge list
|
|
548
|
+
* @param {object[]} edges - Array of edge objects
|
|
549
|
+
*/
|
|
550
|
+
fromEdgeList(edges) {
|
|
551
|
+
for (const e of edges) {
|
|
552
|
+
this.addPrime(e.source);
|
|
553
|
+
this.addPrime(e.target);
|
|
554
|
+
|
|
555
|
+
const edge = this.getEdge(e.source, e.target);
|
|
556
|
+
if (edge) {
|
|
557
|
+
edge.weight = e.weight || 0;
|
|
558
|
+
edge.phaseAlignment = e.phaseAlignment || 0;
|
|
559
|
+
edge.cooccurrenceCount = e.cooccurrenceCount || 0;
|
|
560
|
+
edge.resonanceStrength = e.resonanceStrength || 0;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Get graph statistics
|
|
567
|
+
*/
|
|
568
|
+
stats() {
|
|
569
|
+
let totalWeight = 0;
|
|
570
|
+
let maxWeight = 0;
|
|
571
|
+
let totalDegree = 0;
|
|
572
|
+
|
|
573
|
+
const seen = new Set();
|
|
574
|
+
|
|
575
|
+
for (const [prime, neighbors] of this.adjacency) {
|
|
576
|
+
totalDegree += neighbors.size;
|
|
577
|
+
|
|
578
|
+
for (const [neighbor, edge] of neighbors) {
|
|
579
|
+
const key = `${Math.min(prime, neighbor)}-${Math.max(prime, neighbor)}`;
|
|
580
|
+
if (seen.has(key)) continue;
|
|
581
|
+
seen.add(key);
|
|
582
|
+
|
|
583
|
+
totalWeight += edge.weight;
|
|
584
|
+
maxWeight = Math.max(maxWeight, edge.weight);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
nodeCount: this.primes.size,
|
|
590
|
+
edgeCount: seen.size,
|
|
591
|
+
totalObservations: this.totalObservations,
|
|
592
|
+
averageDegree: totalDegree / this.primes.size,
|
|
593
|
+
averageWeight: seen.size > 0 ? totalWeight / seen.size : 0,
|
|
594
|
+
maxWeight,
|
|
595
|
+
averageClustering: this.averageClusteringCoefficient(),
|
|
596
|
+
density: seen.size / (this.primes.size * (this.primes.size - 1) / 2)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Clone the graph
|
|
602
|
+
*/
|
|
603
|
+
clone() {
|
|
604
|
+
const copy = new PrimeEntanglementGraph(Array.from(this.primes), {
|
|
605
|
+
decayRate: this.decayRate,
|
|
606
|
+
pruneThreshold: this.pruneThreshold,
|
|
607
|
+
maxEdgesPerNode: this.maxEdgesPerNode
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
copy.fromEdgeList(this.toEdgeList());
|
|
611
|
+
copy.totalObservations = this.totalObservations;
|
|
612
|
+
|
|
613
|
+
return copy;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Merge another graph into this one
|
|
618
|
+
* @param {PrimeEntanglementGraph} other - Graph to merge
|
|
619
|
+
* @param {number} weight - Weight for other graph's edges
|
|
620
|
+
*/
|
|
621
|
+
merge(other, weight = 0.5) {
|
|
622
|
+
for (const p of other.primes) {
|
|
623
|
+
this.addPrime(p);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
for (const e of other.toEdgeList()) {
|
|
627
|
+
const edge = this.getEdge(e.source, e.target);
|
|
628
|
+
if (edge) {
|
|
629
|
+
edge.weight = (1 - weight) * edge.weight + weight * e.weight;
|
|
630
|
+
edge.phaseAlignment = (1 - weight) * edge.phaseAlignment + weight * e.phaseAlignment;
|
|
631
|
+
edge.cooccurrenceCount += e.cooccurrenceCount;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Find strongly connected components
|
|
638
|
+
* Uses Tarjan's algorithm
|
|
639
|
+
*/
|
|
640
|
+
findComponents(minWeight = 0.1) {
|
|
641
|
+
const index = new Map();
|
|
642
|
+
const lowlink = new Map();
|
|
643
|
+
const onStack = new Set();
|
|
644
|
+
const stack = [];
|
|
645
|
+
const components = [];
|
|
646
|
+
let idx = 0;
|
|
647
|
+
|
|
648
|
+
const strongConnect = (v) => {
|
|
649
|
+
index.set(v, idx);
|
|
650
|
+
lowlink.set(v, idx);
|
|
651
|
+
idx++;
|
|
652
|
+
stack.push(v);
|
|
653
|
+
onStack.add(v);
|
|
654
|
+
|
|
655
|
+
const neighbors = this.adjacency.get(v);
|
|
656
|
+
if (neighbors) {
|
|
657
|
+
for (const [w, edge] of neighbors) {
|
|
658
|
+
if (edge.weight < minWeight) continue;
|
|
659
|
+
|
|
660
|
+
if (!index.has(w)) {
|
|
661
|
+
strongConnect(w);
|
|
662
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
|
|
663
|
+
} else if (onStack.has(w)) {
|
|
664
|
+
lowlink.set(v, Math.min(lowlink.get(v), index.get(w)));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (lowlink.get(v) === index.get(v)) {
|
|
670
|
+
const component = [];
|
|
671
|
+
let w;
|
|
672
|
+
do {
|
|
673
|
+
w = stack.pop();
|
|
674
|
+
onStack.delete(w);
|
|
675
|
+
component.push(w);
|
|
676
|
+
} while (w !== v);
|
|
677
|
+
components.push(component);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
for (const v of this.primes) {
|
|
682
|
+
if (!index.has(v)) {
|
|
683
|
+
strongConnect(v);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return components.filter(c => c.length > 1);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Factory function for common graph configurations
|
|
693
|
+
*/
|
|
694
|
+
function createEntanglementGraph(config = {}) {
|
|
695
|
+
const {
|
|
696
|
+
numPrimes = 100,
|
|
697
|
+
primes = null,
|
|
698
|
+
decayRate = 0.01,
|
|
699
|
+
pruneThreshold = 0.01
|
|
700
|
+
} = config;
|
|
701
|
+
|
|
702
|
+
return new PrimeEntanglementGraph(primes || numPrimes, {
|
|
703
|
+
decayRate,
|
|
704
|
+
pruneThreshold
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
module.exports = {
|
|
709
|
+
EntanglementEdge,
|
|
710
|
+
PrimeEntanglementGraph,
|
|
711
|
+
createEntanglementGraph
|
|
712
|
+
};
|