@aleph-ai/tinyaleph 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/core/index.js +83 -1
- package/core/lambda.js +845 -0
- package/core/reduction.js +741 -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/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 +30 -10
- package/physics/sync-models.js +770 -0
|
@@ -0,0 +1,770 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Synchronization Models
|
|
3
|
+
*
|
|
4
|
+
* Advanced Kuramoto-family models extending the base KuramotoModel:
|
|
5
|
+
* - NetworkKuramoto: Topology-aware coupling via adjacency matrix
|
|
6
|
+
* - AdaptiveKuramoto: Hebbian plasticity with evolving coupling
|
|
7
|
+
* - SakaguchiKuramoto: Phase frustration with lag parameter α
|
|
8
|
+
* - SmallWorldKuramoto: Watts-Strogatz small-world topology
|
|
9
|
+
* - MultiSystemCoupling: Cross-system synchronization
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { KuramotoModel } = require('./kuramoto');
|
|
13
|
+
const { OscillatorBank } = require('./oscillator');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* NetworkKuramoto - Topology-Aware Coupling
|
|
17
|
+
*
|
|
18
|
+
* Uses adjacency matrix A for coupling:
|
|
19
|
+
* dθᵢ/dt = ωᵢ + K Σⱼ Aᵢⱼ sin(θⱼ - θᵢ)
|
|
20
|
+
*
|
|
21
|
+
* Enables modular synchronization respecting semantic neighborhoods.
|
|
22
|
+
*/
|
|
23
|
+
class NetworkKuramoto extends KuramotoModel {
|
|
24
|
+
/**
|
|
25
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
26
|
+
* @param {number[][]} adjacency - NxN adjacency matrix (weights or 0/1)
|
|
27
|
+
* @param {number} couplingStrength - Global coupling multiplier
|
|
28
|
+
*/
|
|
29
|
+
constructor(frequencies, adjacency = null, couplingStrength = 0.3) {
|
|
30
|
+
super(frequencies, couplingStrength);
|
|
31
|
+
|
|
32
|
+
const N = frequencies.length;
|
|
33
|
+
|
|
34
|
+
// Default to all-to-all if no adjacency provided
|
|
35
|
+
if (adjacency) {
|
|
36
|
+
this.adjacency = adjacency;
|
|
37
|
+
} else {
|
|
38
|
+
this.adjacency = Array(N).fill(null).map(() => Array(N).fill(1));
|
|
39
|
+
// Remove self-connections
|
|
40
|
+
for (let i = 0; i < N; i++) {
|
|
41
|
+
this.adjacency[i][i] = 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Precompute degree for normalization
|
|
46
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set adjacency from entanglement graph
|
|
51
|
+
* @param {Map} entanglementGraph - Map<prime, Map<prime, {strength}>>
|
|
52
|
+
* @param {number[]} primeList - Ordered list of primes for indexing
|
|
53
|
+
*/
|
|
54
|
+
setFromEntanglementGraph(entanglementGraph, primeList) {
|
|
55
|
+
const N = this.oscillators.length;
|
|
56
|
+
const primeToIdx = new Map(primeList.map((p, i) => [p, i]));
|
|
57
|
+
|
|
58
|
+
// Reset adjacency
|
|
59
|
+
this.adjacency = Array(N).fill(null).map(() => Array(N).fill(0));
|
|
60
|
+
|
|
61
|
+
for (const [prime, neighbors] of entanglementGraph) {
|
|
62
|
+
const i = primeToIdx.get(prime);
|
|
63
|
+
if (i === undefined || i >= N) continue;
|
|
64
|
+
|
|
65
|
+
for (const [otherPrime, pair] of neighbors) {
|
|
66
|
+
const j = primeToIdx.get(otherPrime);
|
|
67
|
+
if (j === undefined || j >= N) continue;
|
|
68
|
+
|
|
69
|
+
this.adjacency[i][j] = pair.strength;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Recompute degrees
|
|
74
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build adjacency from a distance function
|
|
79
|
+
* @param {Function} distFn - (i, j) => distance
|
|
80
|
+
* @param {number} threshold - Connect if distance < threshold
|
|
81
|
+
* @param {boolean} weighted - Use 1/distance as weight
|
|
82
|
+
*/
|
|
83
|
+
buildFromDistance(distFn, threshold = Infinity, weighted = false) {
|
|
84
|
+
const N = this.oscillators.length;
|
|
85
|
+
this.adjacency = Array(N).fill(null).map(() => Array(N).fill(0));
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < N; i++) {
|
|
88
|
+
for (let j = i + 1; j < N; j++) {
|
|
89
|
+
const d = distFn(i, j);
|
|
90
|
+
if (d < threshold) {
|
|
91
|
+
const weight = weighted ? 1 / (1 + d) : 1;
|
|
92
|
+
this.adjacency[i][j] = weight;
|
|
93
|
+
this.adjacency[j][i] = weight;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Network-aware coupling term
|
|
103
|
+
*/
|
|
104
|
+
kuramotoCoupling(osc) {
|
|
105
|
+
const idx = this.oscillators.indexOf(osc);
|
|
106
|
+
if (idx < 0) return 0;
|
|
107
|
+
|
|
108
|
+
let coupling = 0;
|
|
109
|
+
for (let j = 0; j < this.oscillators.length; j++) {
|
|
110
|
+
if (j !== idx && this.adjacency[idx][j] > 0) {
|
|
111
|
+
const other = this.oscillators[j];
|
|
112
|
+
coupling += this.adjacency[idx][j] * Math.sin(other.phase - osc.phase);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Normalize by degree (if nonzero)
|
|
117
|
+
const norm = this.degree[idx] > 0 ? this.degree[idx] : 1;
|
|
118
|
+
return this.K * coupling / norm;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get clustering coefficient for a node
|
|
123
|
+
*/
|
|
124
|
+
clusteringCoefficient(idx) {
|
|
125
|
+
const neighbors = [];
|
|
126
|
+
for (let j = 0; j < this.oscillators.length; j++) {
|
|
127
|
+
if (this.adjacency[idx][j] > 0) neighbors.push(j);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (neighbors.length < 2) return 0;
|
|
131
|
+
|
|
132
|
+
let triangles = 0;
|
|
133
|
+
for (let i = 0; i < neighbors.length; i++) {
|
|
134
|
+
for (let j = i + 1; j < neighbors.length; j++) {
|
|
135
|
+
if (this.adjacency[neighbors[i]][neighbors[j]] > 0) {
|
|
136
|
+
triangles++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const possible = neighbors.length * (neighbors.length - 1) / 2;
|
|
142
|
+
return triangles / possible;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Average clustering coefficient
|
|
147
|
+
*/
|
|
148
|
+
averageClustering() {
|
|
149
|
+
let sum = 0;
|
|
150
|
+
for (let i = 0; i < this.oscillators.length; i++) {
|
|
151
|
+
sum += this.clusteringCoefficient(i);
|
|
152
|
+
}
|
|
153
|
+
return sum / this.oscillators.length;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Network modularity based on synchronization
|
|
158
|
+
* Returns array of synchronized clusters
|
|
159
|
+
*/
|
|
160
|
+
findClusters(phaseThreshold = 0.3) {
|
|
161
|
+
const N = this.oscillators.length;
|
|
162
|
+
const visited = new Array(N).fill(false);
|
|
163
|
+
const clusters = [];
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < N; i++) {
|
|
166
|
+
if (visited[i]) continue;
|
|
167
|
+
|
|
168
|
+
const cluster = [i];
|
|
169
|
+
visited[i] = true;
|
|
170
|
+
const queue = [i];
|
|
171
|
+
|
|
172
|
+
while (queue.length > 0) {
|
|
173
|
+
const current = queue.shift();
|
|
174
|
+
|
|
175
|
+
for (let j = 0; j < N; j++) {
|
|
176
|
+
if (!visited[j] && this.adjacency[current][j] > 0) {
|
|
177
|
+
const phaseDiff = Math.abs(this.oscillators[current].phase - this.oscillators[j].phase);
|
|
178
|
+
const wrapped = Math.min(phaseDiff, 2 * Math.PI - phaseDiff);
|
|
179
|
+
|
|
180
|
+
if (wrapped < phaseThreshold) {
|
|
181
|
+
visited[j] = true;
|
|
182
|
+
cluster.push(j);
|
|
183
|
+
queue.push(j);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
clusters.push(cluster);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return clusters;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* AdaptiveKuramoto - Hebbian Plasticity
|
|
198
|
+
*
|
|
199
|
+
* Coupling strengths evolve based on synchronization:
|
|
200
|
+
* dθᵢ/dt = ωᵢ + (1/N) Σⱼ Kᵢⱼ sin(θⱼ - θᵢ)
|
|
201
|
+
* dKᵢⱼ/dt = ε(cos(θⱼ - θᵢ) - Kᵢⱼ)
|
|
202
|
+
*
|
|
203
|
+
* "Concepts that sync together link together"
|
|
204
|
+
*/
|
|
205
|
+
class AdaptiveKuramoto extends NetworkKuramoto {
|
|
206
|
+
/**
|
|
207
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
208
|
+
* @param {number} couplingStrength - Initial global coupling
|
|
209
|
+
* @param {number} learningRate - Plasticity rate ε
|
|
210
|
+
*/
|
|
211
|
+
constructor(frequencies, couplingStrength = 0.3, learningRate = 0.01) {
|
|
212
|
+
// Start with all-to-all small initial coupling
|
|
213
|
+
const N = frequencies.length;
|
|
214
|
+
const initialAdjacency = Array(N).fill(null).map((_, i) =>
|
|
215
|
+
Array(N).fill(null).map((_, j) => i === j ? 0 : couplingStrength)
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
super(frequencies, initialAdjacency, 1.0); // K=1 since coupling is in adjacency
|
|
219
|
+
|
|
220
|
+
this.epsilon = learningRate;
|
|
221
|
+
this.minCoupling = 0.01;
|
|
222
|
+
this.maxCoupling = 2.0;
|
|
223
|
+
|
|
224
|
+
// Track coupling history for analysis
|
|
225
|
+
this.couplingHistory = [];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Update coupling strengths based on phase alignment
|
|
230
|
+
*/
|
|
231
|
+
adaptCoupling(dt) {
|
|
232
|
+
const N = this.oscillators.length;
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < N; i++) {
|
|
235
|
+
for (let j = i + 1; j < N; j++) {
|
|
236
|
+
const phaseDiff = this.oscillators[j].phase - this.oscillators[i].phase;
|
|
237
|
+
const correlation = Math.cos(phaseDiff);
|
|
238
|
+
|
|
239
|
+
// Hebbian update: dK/dt = ε(correlation - K)
|
|
240
|
+
const delta = this.epsilon * (correlation - this.adjacency[i][j]) * dt;
|
|
241
|
+
|
|
242
|
+
// Apply with bounds
|
|
243
|
+
const newK = Math.max(this.minCoupling,
|
|
244
|
+
Math.min(this.maxCoupling, this.adjacency[i][j] + delta));
|
|
245
|
+
|
|
246
|
+
this.adjacency[i][j] = newK;
|
|
247
|
+
this.adjacency[j][i] = newK;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Recompute degrees
|
|
252
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
tick(dt) {
|
|
256
|
+
// First evolve phases
|
|
257
|
+
super.tick(dt);
|
|
258
|
+
|
|
259
|
+
// Then adapt coupling
|
|
260
|
+
this.adaptCoupling(dt);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Get total coupling strength (sum of all edge weights)
|
|
265
|
+
*/
|
|
266
|
+
totalCoupling() {
|
|
267
|
+
let sum = 0;
|
|
268
|
+
const N = this.oscillators.length;
|
|
269
|
+
for (let i = 0; i < N; i++) {
|
|
270
|
+
for (let j = i + 1; j < N; j++) {
|
|
271
|
+
sum += this.adjacency[i][j];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return sum;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get coupling matrix snapshot
|
|
279
|
+
*/
|
|
280
|
+
getCouplingSnapshot() {
|
|
281
|
+
return this.adjacency.map(row => [...row]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Record coupling state for analysis
|
|
286
|
+
*/
|
|
287
|
+
recordCouplingHistory() {
|
|
288
|
+
this.couplingHistory.push({
|
|
289
|
+
time: Date.now(),
|
|
290
|
+
totalCoupling: this.totalCoupling(),
|
|
291
|
+
orderParameter: this.orderParameter(),
|
|
292
|
+
snapshot: this.getCouplingSnapshot()
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Limit history size
|
|
296
|
+
if (this.couplingHistory.length > 1000) {
|
|
297
|
+
this.couplingHistory.shift();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Reset coupling to uniform initial state
|
|
303
|
+
*/
|
|
304
|
+
resetCoupling(value = 0.3) {
|
|
305
|
+
const N = this.oscillators.length;
|
|
306
|
+
for (let i = 0; i < N; i++) {
|
|
307
|
+
for (let j = 0; j < N; j++) {
|
|
308
|
+
this.adjacency[i][j] = i === j ? 0 : value;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
312
|
+
this.couplingHistory = [];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* SakaguchiKuramoto - Phase Frustration
|
|
318
|
+
*
|
|
319
|
+
* Adds phase lag parameter α:
|
|
320
|
+
* dθᵢ/dt = ωᵢ + (K/N) Σⱼ sin(θⱼ - θᵢ - α)
|
|
321
|
+
*
|
|
322
|
+
* Creates chimera states where some oscillators sync while others don't.
|
|
323
|
+
* Models cognitive dissonance, competing interpretations.
|
|
324
|
+
*/
|
|
325
|
+
class SakaguchiKuramoto extends KuramotoModel {
|
|
326
|
+
/**
|
|
327
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
328
|
+
* @param {number} couplingStrength - Coupling K
|
|
329
|
+
* @param {number} phaseLag - Frustration parameter α (radians)
|
|
330
|
+
*/
|
|
331
|
+
constructor(frequencies, couplingStrength = 0.3, phaseLag = 0) {
|
|
332
|
+
super(frequencies, couplingStrength);
|
|
333
|
+
this.alpha = phaseLag;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Set phase lag dynamically
|
|
338
|
+
*/
|
|
339
|
+
setPhaseLag(alpha) {
|
|
340
|
+
this.alpha = alpha;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Frustrated coupling term
|
|
345
|
+
*/
|
|
346
|
+
kuramotoCoupling(osc) {
|
|
347
|
+
let coupling = 0;
|
|
348
|
+
for (const other of this.oscillators) {
|
|
349
|
+
if (other !== osc) {
|
|
350
|
+
// sin(θⱼ - θᵢ - α) = sin(θⱼ - θᵢ)cos(α) - cos(θⱼ - θᵢ)sin(α)
|
|
351
|
+
const diff = other.phase - osc.phase;
|
|
352
|
+
coupling += Math.sin(diff - this.alpha);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return this.K * coupling / this.oscillators.length;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check for chimera state (partial synchronization)
|
|
360
|
+
* Returns ratio of synchronized oscillators
|
|
361
|
+
*/
|
|
362
|
+
chimeraRatio(syncThreshold = 0.3) {
|
|
363
|
+
const meanPh = this.meanPhase();
|
|
364
|
+
let syncCount = 0;
|
|
365
|
+
|
|
366
|
+
for (const osc of this.oscillators) {
|
|
367
|
+
const diff = Math.abs(osc.phase - meanPh);
|
|
368
|
+
const wrapped = Math.min(diff, 2 * Math.PI - diff);
|
|
369
|
+
if (wrapped < syncThreshold) {
|
|
370
|
+
syncCount++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return syncCount / this.oscillators.length;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Classify the system state
|
|
379
|
+
*/
|
|
380
|
+
classifyState() {
|
|
381
|
+
const r = this.orderParameter();
|
|
382
|
+
const chimera = this.chimeraRatio();
|
|
383
|
+
|
|
384
|
+
if (r > 0.8) return 'synchronized';
|
|
385
|
+
if (r < 0.2) return 'incoherent';
|
|
386
|
+
if (chimera > 0.3 && chimera < 0.7) return 'chimera';
|
|
387
|
+
return 'partial';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Find critical phase lag for chimera formation
|
|
392
|
+
* (Approximate - depends on frequency distribution)
|
|
393
|
+
*/
|
|
394
|
+
static criticalPhaseLag(couplingStrength) {
|
|
395
|
+
// For Lorentzian distribution, chimera appears around α ≈ π/2 - arctan(1/K)
|
|
396
|
+
return Math.PI / 2 - Math.atan(1 / couplingStrength);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* SmallWorldKuramoto - Watts-Strogatz Topology
|
|
402
|
+
*
|
|
403
|
+
* Creates small-world network with:
|
|
404
|
+
* - High clustering (local neighborhoods)
|
|
405
|
+
* - Short average path length (long-range shortcuts)
|
|
406
|
+
*
|
|
407
|
+
* Ideal for modular semantic synchronization.
|
|
408
|
+
*/
|
|
409
|
+
class SmallWorldKuramoto extends NetworkKuramoto {
|
|
410
|
+
/**
|
|
411
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
412
|
+
* @param {number} k - Each node connected to k nearest neighbors
|
|
413
|
+
* @param {number} p - Rewiring probability (0 = ring, 1 = random)
|
|
414
|
+
* @param {number} couplingStrength - Coupling K
|
|
415
|
+
*/
|
|
416
|
+
constructor(frequencies, k = 4, p = 0.1, couplingStrength = 0.3) {
|
|
417
|
+
const N = frequencies.length;
|
|
418
|
+
const adjacency = SmallWorldKuramoto.wattsStrogatz(N, k, p);
|
|
419
|
+
|
|
420
|
+
super(frequencies, adjacency, couplingStrength);
|
|
421
|
+
|
|
422
|
+
this.k = k;
|
|
423
|
+
this.p = p;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Generate Watts-Strogatz small-world graph
|
|
428
|
+
*/
|
|
429
|
+
static wattsStrogatz(N, k, p) {
|
|
430
|
+
// Start with ring lattice
|
|
431
|
+
const adj = Array(N).fill(null).map(() => Array(N).fill(0));
|
|
432
|
+
|
|
433
|
+
// Connect each node to k/2 neighbors on each side
|
|
434
|
+
const halfK = Math.floor(k / 2);
|
|
435
|
+
for (let i = 0; i < N; i++) {
|
|
436
|
+
for (let j = 1; j <= halfK; j++) {
|
|
437
|
+
const neighbor = (i + j) % N;
|
|
438
|
+
adj[i][neighbor] = 1;
|
|
439
|
+
adj[neighbor][i] = 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Rewire edges with probability p
|
|
444
|
+
for (let i = 0; i < N; i++) {
|
|
445
|
+
for (let j = 1; j <= halfK; j++) {
|
|
446
|
+
if (Math.random() < p) {
|
|
447
|
+
const oldNeighbor = (i + j) % N;
|
|
448
|
+
|
|
449
|
+
// Remove old edge
|
|
450
|
+
adj[i][oldNeighbor] = 0;
|
|
451
|
+
adj[oldNeighbor][i] = 0;
|
|
452
|
+
|
|
453
|
+
// Add new random edge (avoiding self and duplicates)
|
|
454
|
+
let newNeighbor;
|
|
455
|
+
do {
|
|
456
|
+
newNeighbor = Math.floor(Math.random() * N);
|
|
457
|
+
} while (newNeighbor === i || adj[i][newNeighbor] > 0);
|
|
458
|
+
|
|
459
|
+
adj[i][newNeighbor] = 1;
|
|
460
|
+
adj[newNeighbor][i] = 1;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return adj;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Regenerate the network with new parameters
|
|
470
|
+
*/
|
|
471
|
+
regenerate(k = this.k, p = this.p) {
|
|
472
|
+
this.k = k;
|
|
473
|
+
this.p = p;
|
|
474
|
+
this.adjacency = SmallWorldKuramoto.wattsStrogatz(this.oscillators.length, k, p);
|
|
475
|
+
this.degree = this.adjacency.map(row => row.reduce((a, b) => a + b, 0));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Estimate average path length (BFS from each node)
|
|
480
|
+
*/
|
|
481
|
+
averagePathLength() {
|
|
482
|
+
const N = this.oscillators.length;
|
|
483
|
+
let totalPath = 0;
|
|
484
|
+
let pairs = 0;
|
|
485
|
+
|
|
486
|
+
for (let source = 0; source < N; source++) {
|
|
487
|
+
const dist = this._bfs(source);
|
|
488
|
+
for (let target = 0; target < N; target++) {
|
|
489
|
+
if (source !== target && dist[target] < Infinity) {
|
|
490
|
+
totalPath += dist[target];
|
|
491
|
+
pairs++;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return pairs > 0 ? totalPath / pairs : Infinity;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
_bfs(source) {
|
|
500
|
+
const N = this.oscillators.length;
|
|
501
|
+
const dist = Array(N).fill(Infinity);
|
|
502
|
+
dist[source] = 0;
|
|
503
|
+
const queue = [source];
|
|
504
|
+
|
|
505
|
+
while (queue.length > 0) {
|
|
506
|
+
const current = queue.shift();
|
|
507
|
+
for (let j = 0; j < N; j++) {
|
|
508
|
+
if (this.adjacency[current][j] > 0 && dist[j] === Infinity) {
|
|
509
|
+
dist[j] = dist[current] + 1;
|
|
510
|
+
queue.push(j);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return dist;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Small-world coefficient σ = (C/C_random) / (L/L_random)
|
|
520
|
+
* σ > 1 indicates small-world properties
|
|
521
|
+
*/
|
|
522
|
+
smallWorldCoefficient() {
|
|
523
|
+
const C = this.averageClustering();
|
|
524
|
+
const L = this.averagePathLength();
|
|
525
|
+
|
|
526
|
+
// Random graph approximations
|
|
527
|
+
const N = this.oscillators.length;
|
|
528
|
+
const avgDegree = this.degree.reduce((a, b) => a + b, 0) / N;
|
|
529
|
+
const C_random = avgDegree / N;
|
|
530
|
+
const L_random = Math.log(N) / Math.log(avgDegree);
|
|
531
|
+
|
|
532
|
+
if (C_random === 0 || L_random === 0) return 0;
|
|
533
|
+
|
|
534
|
+
return (C / C_random) / (L / L_random);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* MultiSystemCoupling - Cross-System Synchronization
|
|
540
|
+
*
|
|
541
|
+
* Couples multiple KuramotoModel systems together:
|
|
542
|
+
* dθᵢ^(a)/dt = ... + Σ_b G_ab (r^(b) sin(ψ^(b) - θᵢ^(a)))
|
|
543
|
+
*
|
|
544
|
+
* Models:
|
|
545
|
+
* - Multi-agent semantic alignment
|
|
546
|
+
* - Hierarchical concept organization
|
|
547
|
+
* - Cross-domain knowledge transfer
|
|
548
|
+
*/
|
|
549
|
+
class MultiSystemCoupling {
|
|
550
|
+
/**
|
|
551
|
+
* @param {KuramotoModel[]} systems - Array of Kuramoto systems
|
|
552
|
+
* @param {number[][]} coupling - System coupling matrix G
|
|
553
|
+
*/
|
|
554
|
+
constructor(systems, coupling = null) {
|
|
555
|
+
this.systems = systems;
|
|
556
|
+
|
|
557
|
+
// Default: weak uniform inter-system coupling
|
|
558
|
+
if (coupling) {
|
|
559
|
+
this.G = coupling;
|
|
560
|
+
} else {
|
|
561
|
+
const M = systems.length;
|
|
562
|
+
this.G = Array(M).fill(null).map(() => Array(M).fill(0.1));
|
|
563
|
+
for (let i = 0; i < M; i++) {
|
|
564
|
+
this.G[i][i] = 0; // No self-coupling
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Set inter-system coupling strength
|
|
571
|
+
*/
|
|
572
|
+
setInterCoupling(i, j, strength) {
|
|
573
|
+
this.G[i][j] = strength;
|
|
574
|
+
this.G[j][i] = strength;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get global order parameters for each system
|
|
579
|
+
*/
|
|
580
|
+
orderParameters() {
|
|
581
|
+
return this.systems.map(sys => ({
|
|
582
|
+
r: sys.orderParameter(),
|
|
583
|
+
psi: sys.meanPhase()
|
|
584
|
+
}));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Inter-system coupling force on oscillator
|
|
589
|
+
*/
|
|
590
|
+
interSystemCoupling(systemIdx, osc) {
|
|
591
|
+
let coupling = 0;
|
|
592
|
+
|
|
593
|
+
for (let b = 0; b < this.systems.length; b++) {
|
|
594
|
+
if (b === systemIdx || this.G[systemIdx][b] === 0) continue;
|
|
595
|
+
|
|
596
|
+
const other = this.systems[b];
|
|
597
|
+
const r_b = other.orderParameter();
|
|
598
|
+
const psi_b = other.meanPhase();
|
|
599
|
+
|
|
600
|
+
// Mean-field coupling from system b
|
|
601
|
+
coupling += this.G[systemIdx][b] * r_b * Math.sin(psi_b - osc.phase);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return coupling;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Tick all systems with inter-system coupling
|
|
609
|
+
*/
|
|
610
|
+
tick(dt) {
|
|
611
|
+
// Store inter-system coupling terms for each oscillator
|
|
612
|
+
const interCouplings = this.systems.map((sys, sIdx) =>
|
|
613
|
+
sys.oscillators.map(osc => this.interSystemCoupling(sIdx, osc) * dt)
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
// Tick each system with combined coupling
|
|
617
|
+
for (let s = 0; s < this.systems.length; s++) {
|
|
618
|
+
const sys = this.systems[s];
|
|
619
|
+
|
|
620
|
+
// Custom tick that includes inter-system coupling
|
|
621
|
+
for (let i = 0; i < sys.oscillators.length; i++) {
|
|
622
|
+
const osc = sys.oscillators[i];
|
|
623
|
+
const intra = sys.kuramotoCoupling(osc) * dt;
|
|
624
|
+
const inter = interCouplings[s][i];
|
|
625
|
+
|
|
626
|
+
osc.tick(dt, intra + inter);
|
|
627
|
+
osc.decay(0.02, dt);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Global synchronization across all systems
|
|
634
|
+
*/
|
|
635
|
+
globalOrderParameter() {
|
|
636
|
+
let sx = 0, sy = 0, total = 0;
|
|
637
|
+
|
|
638
|
+
for (const sys of this.systems) {
|
|
639
|
+
for (const osc of sys.oscillators) {
|
|
640
|
+
sx += osc.amplitude * Math.cos(osc.phase);
|
|
641
|
+
sy += osc.amplitude * Math.sin(osc.phase);
|
|
642
|
+
total++;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return total > 0 ? Math.sqrt((sx/total)**2 + (sy/total)**2) : 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Inter-system synchronization matrix
|
|
651
|
+
* Returns phase coherence between each pair of systems
|
|
652
|
+
*/
|
|
653
|
+
interSystemCoherence() {
|
|
654
|
+
const M = this.systems.length;
|
|
655
|
+
const coherence = Array(M).fill(null).map(() => Array(M).fill(0));
|
|
656
|
+
|
|
657
|
+
for (let a = 0; a < M; a++) {
|
|
658
|
+
const psi_a = this.systems[a].meanPhase();
|
|
659
|
+
for (let b = a + 1; b < M; b++) {
|
|
660
|
+
const psi_b = this.systems[b].meanPhase();
|
|
661
|
+
const c = Math.cos(psi_a - psi_b);
|
|
662
|
+
coherence[a][b] = c;
|
|
663
|
+
coherence[b][a] = c;
|
|
664
|
+
}
|
|
665
|
+
coherence[a][a] = 1;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return coherence;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Excite oscillators in a specific system
|
|
673
|
+
*/
|
|
674
|
+
exciteSystem(systemIdx, primes, primeList, amount = 0.5) {
|
|
675
|
+
if (systemIdx >= 0 && systemIdx < this.systems.length) {
|
|
676
|
+
this.systems[systemIdx].exciteByPrimes(primes, primeList, amount);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Excite corresponding oscillators across all systems
|
|
682
|
+
*/
|
|
683
|
+
exciteAll(primes, primeList, amount = 0.5) {
|
|
684
|
+
for (const sys of this.systems) {
|
|
685
|
+
sys.exciteByPrimes(primes, primeList, amount);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Reset all systems
|
|
691
|
+
*/
|
|
692
|
+
reset() {
|
|
693
|
+
for (const sys of this.systems) {
|
|
694
|
+
sys.reset();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Get state summary
|
|
700
|
+
*/
|
|
701
|
+
getState() {
|
|
702
|
+
return {
|
|
703
|
+
systemCount: this.systems.length,
|
|
704
|
+
orderParameters: this.orderParameters(),
|
|
705
|
+
globalOrder: this.globalOrderParameter(),
|
|
706
|
+
interSystemCoherence: this.interSystemCoherence()
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Create a hierarchical multi-system coupling
|
|
713
|
+
* Lower systems feed into higher ones (bottom-up)
|
|
714
|
+
*/
|
|
715
|
+
function createHierarchicalCoupling(frequencies, levels = 3, oscPerLevel = 16) {
|
|
716
|
+
const systems = [];
|
|
717
|
+
|
|
718
|
+
for (let level = 0; level < levels; level++) {
|
|
719
|
+
const levelFreqs = frequencies.slice(0, oscPerLevel).map(f => f * (1 + level * 0.1));
|
|
720
|
+
systems.push(new KuramotoModel(levelFreqs, 0.3 + level * 0.1));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Asymmetric coupling: lower -> higher is stronger
|
|
724
|
+
const G = Array(levels).fill(null).map(() => Array(levels).fill(0));
|
|
725
|
+
for (let i = 0; i < levels; i++) {
|
|
726
|
+
for (let j = 0; j < levels; j++) {
|
|
727
|
+
if (i < j) {
|
|
728
|
+
G[i][j] = 0.2; // Bottom-up
|
|
729
|
+
} else if (i > j) {
|
|
730
|
+
G[i][j] = 0.05; // Top-down (weaker)
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return new MultiSystemCoupling(systems, G);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Create a peer-to-peer multi-system coupling
|
|
740
|
+
* All systems have equal symmetric coupling
|
|
741
|
+
*/
|
|
742
|
+
function createPeerCoupling(frequencies, numPeers = 3, strength = 0.15) {
|
|
743
|
+
const systems = [];
|
|
744
|
+
|
|
745
|
+
for (let i = 0; i < numPeers; i++) {
|
|
746
|
+
// Each peer has slightly different frequencies (individuality)
|
|
747
|
+
const peerFreqs = frequencies.map(f => f * (1 + (Math.random() - 0.5) * 0.1));
|
|
748
|
+
systems.push(new KuramotoModel(peerFreqs, 0.3));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
const G = Array(numPeers).fill(null).map(() => Array(numPeers).fill(strength));
|
|
752
|
+
for (let i = 0; i < numPeers; i++) {
|
|
753
|
+
G[i][i] = 0;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return new MultiSystemCoupling(systems, G);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
module.exports = {
|
|
760
|
+
// Core extended models
|
|
761
|
+
NetworkKuramoto,
|
|
762
|
+
AdaptiveKuramoto,
|
|
763
|
+
SakaguchiKuramoto,
|
|
764
|
+
SmallWorldKuramoto,
|
|
765
|
+
MultiSystemCoupling,
|
|
766
|
+
|
|
767
|
+
// Factory functions
|
|
768
|
+
createHierarchicalCoupling,
|
|
769
|
+
createPeerCoupling
|
|
770
|
+
};
|