@aleph-ai/tinyaleph 1.1.0 → 1.2.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 +230 -2
- package/core/entanglement.js +712 -0
- package/core/events.js +907 -0
- package/core/hypercomplex.js +500 -0
- package/core/index.js +46 -0
- package/core/rformer-layers.js +811 -0
- package/docs/reference/01-core.md +515 -1
- package/docs/reference/02-physics.md +186 -1
- package/package.json +1 -1
- package/physics/index.js +62 -0
- package/physics/kuramoto-coupled-ladder.js +603 -0
- 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
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stochastic Kuramoto Models
|
|
3
|
+
*
|
|
4
|
+
* Kuramoto oscillators with Langevin noise for robust synchronization:
|
|
5
|
+
* dθᵢ/dt = ωᵢ + (K/N) Σⱼ sin(θⱼ - θᵢ) + σ·ξᵢ(t)
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - White noise (Wiener process)
|
|
9
|
+
* - Colored noise (Ornstein-Uhlenbeck process)
|
|
10
|
+
* - Temperature-dependent coupling
|
|
11
|
+
* - Noise-induced synchronization detection
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const { KuramotoModel } = require('./kuramoto');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Box-Muller transform for Gaussian random numbers
|
|
20
|
+
* @returns {number} Standard normal random variable
|
|
21
|
+
*/
|
|
22
|
+
function gaussianRandom() {
|
|
23
|
+
const u1 = Math.random();
|
|
24
|
+
const u2 = Math.random();
|
|
25
|
+
return Math.sqrt(-2 * Math.log(u1 || 1e-10)) * Math.cos(2 * Math.PI * u2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* StochasticKuramoto - Kuramoto model with Langevin noise
|
|
30
|
+
*
|
|
31
|
+
* Adds thermal fluctuations to oscillator dynamics:
|
|
32
|
+
* dθᵢ = [ωᵢ + K·coupling(i)]dt + σ·dWᵢ
|
|
33
|
+
*
|
|
34
|
+
* where dWᵢ is a Wiener increment with variance dt.
|
|
35
|
+
*/
|
|
36
|
+
class StochasticKuramoto extends KuramotoModel {
|
|
37
|
+
/**
|
|
38
|
+
* @param {number[]} frequencies - Natural frequencies ωᵢ
|
|
39
|
+
* @param {object} options - Configuration options
|
|
40
|
+
* @param {number} [options.coupling=0.3] - Coupling strength K
|
|
41
|
+
* @param {number} [options.noiseIntensity=0.1] - Noise amplitude σ
|
|
42
|
+
* @param {string} [options.noiseType='white'] - 'white' or 'colored'
|
|
43
|
+
* @param {number} [options.correlationTime=1.0] - τ for colored noise
|
|
44
|
+
* @param {number} [options.temperature=1.0] - Temperature for T-dependent coupling
|
|
45
|
+
*/
|
|
46
|
+
constructor(frequencies, options = {}) {
|
|
47
|
+
super(frequencies, options.coupling || 0.3);
|
|
48
|
+
|
|
49
|
+
this.sigma = options.noiseIntensity ?? 0.1;
|
|
50
|
+
this.noiseType = options.noiseType || 'white';
|
|
51
|
+
this.tau = options.correlationTime ?? 1.0;
|
|
52
|
+
this.temperature = options.temperature ?? 1.0;
|
|
53
|
+
this.useTemperatureCoupling = options.temperatureCoupling ?? false;
|
|
54
|
+
|
|
55
|
+
// For colored noise (Ornstein-Uhlenbeck): dη = -η/τ dt + σ/√τ dW
|
|
56
|
+
// Each oscillator has its own OU process
|
|
57
|
+
this.coloredNoiseState = new Float64Array(frequencies.length);
|
|
58
|
+
|
|
59
|
+
// Track noise history for analysis
|
|
60
|
+
this.noiseHistory = [];
|
|
61
|
+
this.maxHistoryLength = 1000;
|
|
62
|
+
|
|
63
|
+
// Statistics
|
|
64
|
+
this.noiseStats = {
|
|
65
|
+
mean: 0,
|
|
66
|
+
variance: 0,
|
|
67
|
+
sampleCount: 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Set noise intensity dynamically
|
|
73
|
+
* @param {number} sigma - New noise intensity
|
|
74
|
+
*/
|
|
75
|
+
setNoiseIntensity(sigma) {
|
|
76
|
+
this.sigma = sigma;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Set temperature (affects coupling if temperatureCoupling is enabled)
|
|
81
|
+
* @param {number} T - Temperature
|
|
82
|
+
*/
|
|
83
|
+
setTemperature(T) {
|
|
84
|
+
this.temperature = Math.max(0.01, T);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get effective coupling (temperature-dependent)
|
|
89
|
+
* K_eff = K / T (Arrhenius-like)
|
|
90
|
+
*/
|
|
91
|
+
getEffectiveCoupling() {
|
|
92
|
+
if (this.useTemperatureCoupling) {
|
|
93
|
+
return this.K / this.temperature;
|
|
94
|
+
}
|
|
95
|
+
return this.K;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate white noise increment
|
|
100
|
+
* @param {number} dt - Time step
|
|
101
|
+
* @returns {number} Noise increment σ·√dt·N(0,1)
|
|
102
|
+
*/
|
|
103
|
+
whiteNoiseIncrement(dt) {
|
|
104
|
+
return this.sigma * Math.sqrt(dt) * gaussianRandom();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Update Ornstein-Uhlenbeck process for colored noise
|
|
109
|
+
* dη = -η/τ dt + (σ/√τ)·dW
|
|
110
|
+
*
|
|
111
|
+
* @param {number} idx - Oscillator index
|
|
112
|
+
* @param {number} dt - Time step
|
|
113
|
+
* @returns {number} Colored noise value η
|
|
114
|
+
*/
|
|
115
|
+
updateColoredNoise(idx, dt) {
|
|
116
|
+
const eta = this.coloredNoiseState[idx];
|
|
117
|
+
const decay = Math.exp(-dt / this.tau);
|
|
118
|
+
const diffusion = this.sigma * Math.sqrt((1 - decay * decay) / 2);
|
|
119
|
+
|
|
120
|
+
// Exact update for OU process
|
|
121
|
+
this.coloredNoiseState[idx] = eta * decay + diffusion * gaussianRandom();
|
|
122
|
+
|
|
123
|
+
return this.coloredNoiseState[idx];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get noise increment based on noise type
|
|
128
|
+
* @param {number} idx - Oscillator index
|
|
129
|
+
* @param {number} dt - Time step
|
|
130
|
+
* @returns {number} Noise increment
|
|
131
|
+
*/
|
|
132
|
+
getNoiseIncrement(idx, dt) {
|
|
133
|
+
if (this.noiseType === 'colored') {
|
|
134
|
+
return this.updateColoredNoise(idx, dt) * dt;
|
|
135
|
+
}
|
|
136
|
+
return this.whiteNoiseIncrement(dt);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Stochastic Kuramoto coupling with noise
|
|
141
|
+
* @param {object} osc - Oscillator
|
|
142
|
+
* @param {number} idx - Oscillator index
|
|
143
|
+
* @param {number} dt - Time step
|
|
144
|
+
* @returns {number} Phase increment (deterministic + stochastic)
|
|
145
|
+
*/
|
|
146
|
+
stochasticCoupling(osc, idx, dt) {
|
|
147
|
+
// Deterministic Kuramoto coupling
|
|
148
|
+
let coupling = 0;
|
|
149
|
+
const Keff = this.getEffectiveCoupling();
|
|
150
|
+
|
|
151
|
+
for (const other of this.oscillators) {
|
|
152
|
+
if (other !== osc) {
|
|
153
|
+
coupling += Math.sin(other.phase - osc.phase);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const deterministicPart = Keff * coupling / this.oscillators.length * dt;
|
|
158
|
+
|
|
159
|
+
// Stochastic part
|
|
160
|
+
const stochasticPart = this.getNoiseIncrement(idx, dt);
|
|
161
|
+
|
|
162
|
+
// Update statistics
|
|
163
|
+
this._updateNoiseStats(stochasticPart);
|
|
164
|
+
|
|
165
|
+
return deterministicPart + stochasticPart;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Update running noise statistics
|
|
170
|
+
* @private
|
|
171
|
+
*/
|
|
172
|
+
_updateNoiseStats(noiseValue) {
|
|
173
|
+
const n = ++this.noiseStats.sampleCount;
|
|
174
|
+
const delta = noiseValue - this.noiseStats.mean;
|
|
175
|
+
this.noiseStats.mean += delta / n;
|
|
176
|
+
const delta2 = noiseValue - this.noiseStats.mean;
|
|
177
|
+
this.noiseStats.variance += (delta * delta2 - this.noiseStats.variance) / n;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Advance system by one time step with stochastic dynamics
|
|
182
|
+
* @param {number} dt - Time step size
|
|
183
|
+
*/
|
|
184
|
+
tick(dt) {
|
|
185
|
+
// Store noise values for this step
|
|
186
|
+
const stepNoise = [];
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < this.oscillators.length; i++) {
|
|
189
|
+
const osc = this.oscillators[i];
|
|
190
|
+
const phaseIncrement = this.stochasticCoupling(osc, i, dt);
|
|
191
|
+
|
|
192
|
+
stepNoise.push(phaseIncrement);
|
|
193
|
+
|
|
194
|
+
// Update phase
|
|
195
|
+
osc.phase += osc.frequency * dt + phaseIncrement;
|
|
196
|
+
osc.phase = ((osc.phase % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
|
|
197
|
+
|
|
198
|
+
// Amplitude decay
|
|
199
|
+
osc.decay(0.02, dt);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Record history
|
|
203
|
+
if (this.noiseHistory.length < this.maxHistoryLength) {
|
|
204
|
+
this.noiseHistory.push({
|
|
205
|
+
t: Date.now(),
|
|
206
|
+
noise: stepNoise,
|
|
207
|
+
orderParameter: this.orderParameter()
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
this.noiseHistory.shift();
|
|
211
|
+
this.noiseHistory.push({
|
|
212
|
+
t: Date.now(),
|
|
213
|
+
noise: stepNoise,
|
|
214
|
+
orderParameter: this.orderParameter()
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Run multiple steps
|
|
221
|
+
* @param {number} steps - Number of steps
|
|
222
|
+
* @param {number} dt - Time step size
|
|
223
|
+
* @returns {object[]} Evolution history
|
|
224
|
+
*/
|
|
225
|
+
evolve(steps, dt = 0.01) {
|
|
226
|
+
const trajectory = [];
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < steps; i++) {
|
|
229
|
+
this.tick(dt);
|
|
230
|
+
trajectory.push({
|
|
231
|
+
step: i,
|
|
232
|
+
orderParameter: this.orderParameter(),
|
|
233
|
+
meanPhase: this.meanPhase(),
|
|
234
|
+
noiseStats: { ...this.noiseStats }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return trajectory;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Detect noise-induced synchronization
|
|
243
|
+
*
|
|
244
|
+
* Phenomenon where noise can actually enhance synchronization
|
|
245
|
+
* by helping oscillators escape from metastable states.
|
|
246
|
+
*
|
|
247
|
+
* @param {number} baselineSteps - Steps to establish baseline
|
|
248
|
+
* @param {number} noisySteps - Steps with noise
|
|
249
|
+
* @param {number} dt - Time step
|
|
250
|
+
* @returns {object} Detection result
|
|
251
|
+
*/
|
|
252
|
+
detectNoiseInducedSync(baselineSteps = 100, noisySteps = 200, dt = 0.01) {
|
|
253
|
+
// Save current state
|
|
254
|
+
const originalSigma = this.sigma;
|
|
255
|
+
|
|
256
|
+
// Baseline (no noise)
|
|
257
|
+
this.sigma = 0;
|
|
258
|
+
const baselineTrajectory = this.evolve(baselineSteps, dt);
|
|
259
|
+
const baselineOrder = baselineTrajectory.slice(-20)
|
|
260
|
+
.reduce((sum, t) => sum + t.orderParameter, 0) / 20;
|
|
261
|
+
|
|
262
|
+
// With noise
|
|
263
|
+
this.sigma = originalSigma;
|
|
264
|
+
const noisyTrajectory = this.evolve(noisySteps, dt);
|
|
265
|
+
const noisyOrder = noisyTrajectory.slice(-20)
|
|
266
|
+
.reduce((sum, t) => sum + t.orderParameter, 0) / 20;
|
|
267
|
+
|
|
268
|
+
const enhancement = noisyOrder - baselineOrder;
|
|
269
|
+
const isNoiseInduced = enhancement > 0.1;
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
baselineOrderParameter: baselineOrder,
|
|
273
|
+
noisyOrderParameter: noisyOrder,
|
|
274
|
+
enhancement,
|
|
275
|
+
isNoiseInduced,
|
|
276
|
+
noiseIntensity: originalSigma,
|
|
277
|
+
baselineTrajectory,
|
|
278
|
+
noisyTrajectory
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Compute stochastic order parameter with error bars
|
|
284
|
+
* @param {number} samples - Number of samples for averaging
|
|
285
|
+
* @param {number} dt - Time step between samples
|
|
286
|
+
* @returns {object} Order parameter with uncertainty
|
|
287
|
+
*/
|
|
288
|
+
orderParameterWithUncertainty(samples = 100, dt = 0.01) {
|
|
289
|
+
const values = [];
|
|
290
|
+
|
|
291
|
+
for (let i = 0; i < samples; i++) {
|
|
292
|
+
this.tick(dt);
|
|
293
|
+
values.push(this.orderParameter());
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const mean = values.reduce((a, b) => a + b, 0) / samples;
|
|
297
|
+
const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / samples;
|
|
298
|
+
const stdError = Math.sqrt(variance / samples);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
mean,
|
|
302
|
+
stdDev: Math.sqrt(variance),
|
|
303
|
+
stdError,
|
|
304
|
+
confidence95: [mean - 1.96 * stdError, mean + 1.96 * stdError],
|
|
305
|
+
samples: values
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Compute autocorrelation of order parameter
|
|
311
|
+
* @param {number} maxLag - Maximum lag to compute
|
|
312
|
+
* @returns {number[]} Autocorrelation values
|
|
313
|
+
*/
|
|
314
|
+
orderParameterAutocorrelation(maxLag = 50) {
|
|
315
|
+
if (this.noiseHistory.length < maxLag + 10) {
|
|
316
|
+
return new Array(maxLag).fill(0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const orderParams = this.noiseHistory.map(h => h.orderParameter);
|
|
320
|
+
const n = orderParams.length;
|
|
321
|
+
const mean = orderParams.reduce((a, b) => a + b, 0) / n;
|
|
322
|
+
const centered = orderParams.map(v => v - mean);
|
|
323
|
+
|
|
324
|
+
const autocorr = [];
|
|
325
|
+
const variance = centered.reduce((sum, v) => sum + v * v, 0);
|
|
326
|
+
|
|
327
|
+
for (let lag = 0; lag < maxLag; lag++) {
|
|
328
|
+
let sum = 0;
|
|
329
|
+
for (let i = 0; i < n - lag; i++) {
|
|
330
|
+
sum += centered[i] * centered[i + lag];
|
|
331
|
+
}
|
|
332
|
+
autocorr.push(variance > 0 ? sum / variance : 0);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return autocorr;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get correlation time from autocorrelation decay
|
|
340
|
+
* @returns {number} Estimated correlation time
|
|
341
|
+
*/
|
|
342
|
+
estimateCorrelationTime() {
|
|
343
|
+
const autocorr = this.orderParameterAutocorrelation(100);
|
|
344
|
+
|
|
345
|
+
// Find where autocorrelation drops to 1/e
|
|
346
|
+
const threshold = 1 / Math.E;
|
|
347
|
+
for (let i = 0; i < autocorr.length; i++) {
|
|
348
|
+
if (autocorr[i] < threshold) {
|
|
349
|
+
return i;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return autocorr.length; // Didn't decay fast enough
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Reset noise state
|
|
358
|
+
*/
|
|
359
|
+
resetNoise() {
|
|
360
|
+
this.coloredNoiseState.fill(0);
|
|
361
|
+
this.noiseHistory = [];
|
|
362
|
+
this.noiseStats = { mean: 0, variance: 0, sampleCount: 0 };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Get current state snapshot
|
|
367
|
+
*/
|
|
368
|
+
getState() {
|
|
369
|
+
return {
|
|
370
|
+
...super.getState(),
|
|
371
|
+
noiseIntensity: this.sigma,
|
|
372
|
+
noiseType: this.noiseType,
|
|
373
|
+
correlationTime: this.tau,
|
|
374
|
+
temperature: this.temperature,
|
|
375
|
+
effectiveCoupling: this.getEffectiveCoupling(),
|
|
376
|
+
noiseStats: { ...this.noiseStats },
|
|
377
|
+
coloredNoiseState: [...this.coloredNoiseState]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* ColoredNoiseKuramoto - Specialized class for Ornstein-Uhlenbeck noise
|
|
384
|
+
*
|
|
385
|
+
* Provides more control over colored noise parameters and analysis.
|
|
386
|
+
*/
|
|
387
|
+
class ColoredNoiseKuramoto extends StochasticKuramoto {
|
|
388
|
+
/**
|
|
389
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
390
|
+
* @param {object} options - Configuration
|
|
391
|
+
* @param {number} [options.coupling=0.3] - Coupling strength
|
|
392
|
+
* @param {number} [options.noiseIntensity=0.1] - Noise amplitude
|
|
393
|
+
* @param {number} [options.correlationTime=1.0] - OU correlation time
|
|
394
|
+
*/
|
|
395
|
+
constructor(frequencies, options = {}) {
|
|
396
|
+
super(frequencies, {
|
|
397
|
+
...options,
|
|
398
|
+
noiseType: 'colored'
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Set correlation time dynamically
|
|
404
|
+
* @param {number} tau - New correlation time
|
|
405
|
+
*/
|
|
406
|
+
setCorrelationTime(tau) {
|
|
407
|
+
this.tau = Math.max(0.01, tau);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get OU process stationary distribution parameters
|
|
412
|
+
* For OU: variance = σ²/(2/τ) = σ²τ/2
|
|
413
|
+
*/
|
|
414
|
+
getStationaryVariance() {
|
|
415
|
+
return (this.sigma ** 2) * this.tau / 2;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Check if OU processes have equilibrated
|
|
420
|
+
* @param {number} threshold - Tolerance for equilibration
|
|
421
|
+
*/
|
|
422
|
+
isEquilibrated(threshold = 0.1) {
|
|
423
|
+
const expectedVar = this.getStationaryVariance();
|
|
424
|
+
let actualVar = 0;
|
|
425
|
+
|
|
426
|
+
for (const eta of this.coloredNoiseState) {
|
|
427
|
+
actualVar += eta ** 2;
|
|
428
|
+
}
|
|
429
|
+
actualVar /= this.coloredNoiseState.length;
|
|
430
|
+
|
|
431
|
+
return Math.abs(actualVar - expectedVar) / expectedVar < threshold;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Get power spectrum estimate of noise
|
|
436
|
+
* @param {number} maxFreq - Maximum frequency
|
|
437
|
+
* @param {number} resolution - Frequency resolution
|
|
438
|
+
*/
|
|
439
|
+
noisePowerSpectrum(maxFreq = 10, resolution = 0.1) {
|
|
440
|
+
const spectrum = [];
|
|
441
|
+
|
|
442
|
+
// Theoretical OU spectrum: S(ω) = 2σ²τ / (1 + (ωτ)²)
|
|
443
|
+
for (let omega = 0; omega <= maxFreq; omega += resolution) {
|
|
444
|
+
const theoretical = 2 * this.sigma ** 2 * this.tau / (1 + (omega * this.tau) ** 2);
|
|
445
|
+
spectrum.push({ omega, power: theoretical });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return spectrum;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* ThermalKuramoto - Temperature-controlled synchronization
|
|
454
|
+
*
|
|
455
|
+
* Models thermal effects on oscillator synchronization:
|
|
456
|
+
* - High temperature: Strong fluctuations, weak effective coupling
|
|
457
|
+
* - Low temperature: Weak fluctuations, strong effective coupling
|
|
458
|
+
*
|
|
459
|
+
* Critical temperature T_c ≈ K (coupling strength)
|
|
460
|
+
*/
|
|
461
|
+
class ThermalKuramoto extends StochasticKuramoto {
|
|
462
|
+
/**
|
|
463
|
+
* @param {number[]} frequencies - Natural frequencies
|
|
464
|
+
* @param {object} options - Configuration
|
|
465
|
+
* @param {number} [options.coupling=0.3] - Coupling strength
|
|
466
|
+
* @param {number} [options.temperature=1.0] - Initial temperature
|
|
467
|
+
*/
|
|
468
|
+
constructor(frequencies, options = {}) {
|
|
469
|
+
super(frequencies, {
|
|
470
|
+
...options,
|
|
471
|
+
noiseType: 'white',
|
|
472
|
+
temperatureCoupling: true
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Noise intensity proportional to √T (fluctuation-dissipation)
|
|
476
|
+
this._updateNoiseFromTemperature();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Update noise intensity from temperature
|
|
481
|
+
* @private
|
|
482
|
+
*/
|
|
483
|
+
_updateNoiseFromTemperature() {
|
|
484
|
+
// Fluctuation-dissipation: σ² ∝ T
|
|
485
|
+
this.sigma = Math.sqrt(this.temperature) * 0.1;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Set temperature and update noise
|
|
490
|
+
* @param {number} T - Temperature
|
|
491
|
+
*/
|
|
492
|
+
setTemperature(T) {
|
|
493
|
+
super.setTemperature(T);
|
|
494
|
+
this._updateNoiseFromTemperature();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Estimate critical temperature from current state
|
|
499
|
+
* T_c ≈ K for all-to-all coupling with uniform frequencies
|
|
500
|
+
*/
|
|
501
|
+
estimateCriticalTemperature() {
|
|
502
|
+
// Approximate T_c = K * (frequency spread factor)
|
|
503
|
+
const freqs = this.oscillators.map(o => o.frequency);
|
|
504
|
+
const meanFreq = freqs.reduce((a, b) => a + b, 0) / freqs.length;
|
|
505
|
+
const freqSpread = Math.sqrt(
|
|
506
|
+
freqs.reduce((sum, f) => sum + (f - meanFreq) ** 2, 0) / freqs.length
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
return this.K * (1 + freqSpread);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Perform temperature sweep to find transition
|
|
514
|
+
* @param {number} Tmin - Minimum temperature
|
|
515
|
+
* @param {number} Tmax - Maximum temperature
|
|
516
|
+
* @param {number} steps - Number of temperature steps
|
|
517
|
+
* @param {number} equilibrationSteps - Steps to equilibrate at each T
|
|
518
|
+
*/
|
|
519
|
+
temperatureSweep(Tmin = 0.1, Tmax = 2.0, steps = 20, equilibrationSteps = 100) {
|
|
520
|
+
const results = [];
|
|
521
|
+
|
|
522
|
+
for (let i = 0; i < steps; i++) {
|
|
523
|
+
const T = Tmin + (Tmax - Tmin) * i / (steps - 1);
|
|
524
|
+
this.setTemperature(T);
|
|
525
|
+
|
|
526
|
+
// Equilibrate
|
|
527
|
+
this.evolve(equilibrationSteps, 0.01);
|
|
528
|
+
|
|
529
|
+
// Measure
|
|
530
|
+
const stats = this.orderParameterWithUncertainty(50, 0.01);
|
|
531
|
+
|
|
532
|
+
results.push({
|
|
533
|
+
temperature: T,
|
|
534
|
+
orderParameter: stats.mean,
|
|
535
|
+
stdDev: stats.stdDev,
|
|
536
|
+
confidence95: stats.confidence95
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return results;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Check if system is in ordered (synchronized) phase
|
|
545
|
+
* @param {number} threshold - Order parameter threshold
|
|
546
|
+
*/
|
|
547
|
+
isOrdered(threshold = 0.5) {
|
|
548
|
+
return this.orderParameter() > threshold;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Check if system is at or near critical temperature
|
|
553
|
+
* @param {number} tolerance - Tolerance factor
|
|
554
|
+
*/
|
|
555
|
+
isNearCritical(tolerance = 0.2) {
|
|
556
|
+
const Tc = this.estimateCriticalTemperature();
|
|
557
|
+
return Math.abs(this.temperature - Tc) / Tc < tolerance;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
module.exports = {
|
|
562
|
+
StochasticKuramoto,
|
|
563
|
+
ColoredNoiseKuramoto,
|
|
564
|
+
ThermalKuramoto,
|
|
565
|
+
gaussianRandom
|
|
566
|
+
};
|