Advanced Dynamics¶
Beyond basic Kuramoto, SPO provides several additional dynamical modules.
Variational Free Energy Predictor¶
Implementation of Friston's Free Energy Principle mapped to Kuramoto dynamics. The system minimises its own prediction error as a coupling objective.
- Precision-weighted prediction error (maps to coupling K_ij)
- KL complexity term on precision
- Online precision estimation from error variance
- Forward prediction with error injection into the UPDE right-hand side
from scpn_phase_orchestrator.upde.prediction import VariationalPredictor
predictor = VariationalPredictor(n=16, dt=0.01)
# prediction_error = predictor.step(phases, predicted_phases)
# error_coupling() returns ε_gain·ε_i for injection into UPDE
prediction ¶
Forward and variational prediction models for validated UPDE phase states.
The module supplies a linear prediction-error model and a variational free-energy predictor over one-dimensional oscillator phase vectors. Public constructors and update methods validate oscillator counts, positive time steps, finite phase/frequency arrays, and precision vectors before mutating internal weights, sufficient statistics, or error histories. The implementation is a concrete numerical mechanism and does not claim to formalize phenomenological time-consciousness.
Classes¶
PredictionState
dataclass
¶
PredictionState(
predicted_phases: FloatArray,
prediction_error: FloatArray,
mean_error: float,
weights: FloatArray,
)
Snapshot of the forward prediction model after one update step.
PredictionModel ¶
Linear forward model for phase prediction.
Predicts θ̂(t+dt) from θ(t) using learned weights W: θ̂(t+dt) = θ(t) + dt · (ω + W · sin(Δθ))
Prediction error ε = θ_actual - θ̂ (wrapped to [-π, π]). Weights updated via gradient descent on ε²: W += η · ε ⊗ sin(Δθ)
The prediction error signal can be injected into the UPDE as an additional coupling term, implementing a form of predictive coding where the system minimizes its own prediction error.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
Attributes¶
weights
property
¶
Copy of the current learned weight matrix W.
Returns¶
FloatArray Copy of the current learned weight matrix W.
error_gain
property
¶
Scaling factor applied to prediction error before injection.
Returns¶
float Scaling factor applied to prediction error before injection.
Methods:¶
predict ¶
Predict phases at next timestep.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
dt : float
Integration step size.
Returns¶
FloatArray The predicted phases at the next timestep.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
update ¶
Compute prediction error and update weights.
Call once per timestep AFTER the solver step.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
dt : float
Integration step size.
Returns¶
PredictionState The updated prediction state after one learning step.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
error_coupling ¶
Prediction-error signal for injection into UPDE.
Returns ε_gain · ε_i, where ε_i = θ_actual - θ̂_predicted. Add this to the UPDE derivative to implement predictive coding: dθ/dt = ω + K·sin(Δθ) + gain·ε
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
dt : float
Integration step size.
Returns¶
FloatArray The prediction-error coupling signal for UPDE injection.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
reset ¶
VariationalState
dataclass
¶
VariationalState(
predicted_phases: FloatArray,
error: FloatArray,
free_energy: float,
precision: FloatArray,
complexity: float,
)
Snapshot of the variational predictor after one update step.
VariationalPredictor ¶
VariationalPredictor(
n_oscillators: int,
prior_precision: float = 1.0,
learning_rate: float = 0.01,
)
Variational free energy minimization for phase prediction.
Implements the formal mapping between SCPN phase dynamics and Friston's Free Energy Principle:
F = E_q[log q(theta) - log p(theta, y)] ~ prediction_error^2 / (2 * precision) + complexity
where
theta = phase states (sufficient statistics mu in FEP) y = observed phases q(theta) = recognition density (Gaussian, parameterized by mu, Sigma) prediction_error = y - f(mu) (sensory prediction error) precision = 1/sigma^2 (inverse variance, maps to coupling K) complexity = KL[q||p] (prior deviation cost)
The UPDE coupling term K_ij * sin(theta_j - theta_i) maps to precision-weighted prediction error under Laplace approximation (Friston 2010, Eq. 4).
This is NOT a claim to formalize Husserl's protention. It is a concrete numerical implementation of the mathematical correspondence between Kuramoto coupling and variational inference.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
Attributes¶
precision
property
¶
Copy of the current per-oscillator precision vector.
Returns¶
FloatArray Copy of the current per-oscillator precision vector.
Methods:¶
free_energy ¶
Variational free energy F.
F = sum_i [ (y_i - f(mu_i))^2 * pi_i / 2 ] + sum_i [ log(pi_i) ]
First term: precision-weighted prediction error (accuracy). Second term: log-precision (complexity under Gaussian q). The sign convention follows Friston (2010): F is minimized.
Parameters¶
predicted : FloatArray
Predicted phases in radians, shape (N,).
observed : FloatArray
Observed phases in radians, shape (N,).
precision : FloatArray
Per-oscillator precision vector, shape (N,).
Returns¶
float
The variational free energy F.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
update ¶
One variational update step.
- Predict phases from current sufficient statistics mu.
- Compute precision-weighted prediction error.
- Update mu (gradient descent on F).
- Update precision from error statistics.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
dt : float
Integration step size.
Returns¶
VariationalState The updated variational state after one step.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
precision_weighted_coupling ¶
Precision matrix interpretable as K_ij.
Under the FEP-Kuramoto correspondence (Friston 2010, Laplace approximation), the coupling matrix K_ij maps to the off-diagonal elements of the precision matrix of the generative model.
This returns diag(precision) as the simplest such mapping. For a full N x N coupling matrix, use np.diag(result).
Returns¶
FloatArray Precision matrix interpretable as K_ij.
Source code in src/scpn_phase_orchestrator/upde/prediction.py
reset ¶
Reset precision to prior, zero sufficient statistics, clear history.
Combinatorial Hodge Decomposition of Coupling¶
Decomposes the Kuramoto coupling current f_ij = ½(K_ij + K_ji)·sin(θ_j
− θ_i) into three L²-orthogonal edge flows via combinatorial Hodge
theory on the simplicial complex of oscillators (Jiang, Lim, Yao & Ye
2011):
- Gradient: conservative, curl-free flow
grad(s)from a node potential. - Curl: divergence-free rotational flow bounded by triangles.
- Harmonic: topological residual in the kernel of the Hodge
1-Laplacian; its dimension is the first Betti number
β₁— non-zero only when the graph carries cycles that no triangle fills.
Answers: "Is this synchronisation conservative, rotational, or carried by an irreducible topological cycle?"
from scpn_phase_orchestrator.coupling.hodge import hodge_decomposition
result = hodge_decomposition(K, phases)
result.gradient # (N, N) antisymmetric conservative flow
result.curl # (N, N) rotational flow
result.harmonic # (N, N) topological flow
result.betti_one # number of independent unfilled cycles
See the Coupling API reference for the full
hodge_decomposition and HodgeResult signatures.
Simplicial (3-Body) Coupling¶
Higher-order interactions beyond pairwise: the 3-body term induces explosive (first-order) synchronization transitions.
Gambuzza et al. 2023, Nature Physics; Tang et al. 2025.
simplicial ¶
Pairwise + all-to-all 3-body (simplicial) Kuramoto with a 5-backend chain.
Model¶
dθ_i/dt = ω_i
+ (σ₁/N) · Σ_j A_ij · sin(θ_j − θ_i)
+ (σ₂/N²) · Σ_{j,k} sin(θ_j + θ_k − 2θ_i)
+ ζ · sin(ψ − θ_i)
σ₂ > 0 drives explosive (first-order) transitions and shrinks basins of attraction while improving the locking stability of already-synchronous states (Gambuzza et al. 2023; Tang et al. 2025).
Closed form for the 3-body sum¶
Expanding sin(θ_j + θ_k − 2θ_i) = sin((θ_j − θ_i) + (θ_k − θ_i))
and separating the cross terms gives
Σ_{j,k} sin(θ_j + θ_k − 2θ_i) = 2 · S_i · C_i
with
S_i = Σ_j sin(θ_j − θ_i) = (Σ sin θ)·cos θ_i − (Σ cos θ)·sin θ_i
C_i = Σ_j cos(θ_j − θ_i) = (Σ cos θ)·cos θ_i + (Σ sin θ)·sin θ_i
So the 3-body contribution is evaluated in O(N²) (not O(N³))
using two global sums plus the per-node sincos expansion. All five
backends use this identity; the pairwise path matches the Rust
kernel's sincos expansion on the alpha-zero branch and the direct
sin(diff) form otherwise, giving bit-exact parity.
Classes¶
SimplicialEngine ¶
Pairwise + simplicial (3-body, all-to-all) Kuramoto stepper.
The engine's geometry is (n, dt, σ₂); the step itself is
stateless: (phases, omegas, K, α, ζ, ψ) → new_phases.
Initialise the simplicial Kuramoto stepper.
Parameters¶
n_oscillators : int Number of oscillators in the fixed engine geometry. dt : float Positive Euler timestep in seconds. sigma2 : float, default=0.0 Non-negative all-to-all triadic coupling strength.
Source code in src/scpn_phase_orchestrator/upde/simplicial.py
Attributes¶
sigma2
property
writable
¶
Return the configured all-to-all triadic coupling strength.
Returns¶
float Return the configured all-to-all triadic coupling strength.
Methods:¶
step ¶
step(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float,
psi: float,
alpha: FloatArray,
) -> FloatArray
Advance one pairwise-plus-simplicial Kuramoto timestep.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
Returns¶
FloatArray The phases after one pairwise-plus-simplicial step.
Source code in src/scpn_phase_orchestrator/upde/simplicial.py
run ¶
run(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float,
psi: float,
alpha: FloatArray,
n_steps: int,
) -> FloatArray
Integrate pairwise-plus-simplicial Kuramoto dynamics.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
n_steps : int
Number of integration steps to run.
Returns¶
FloatArray
The final phases after n_steps simplicial steps.
Raises¶
ValueError
If n_steps is negative or the state arrays are invalid.
Source code in src/scpn_phase_orchestrator/upde/simplicial.py
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | |
order_parameter ¶
Compute the standard Kuramoto R = |
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
float
The Kuramoto order parameter R.
Source code in src/scpn_phase_orchestrator/upde/simplicial.py
Functions:¶
Time-Delayed Coupling¶
Circular buffer supports arbitrary time delays with automatic fallback to instantaneous coupling. Time delays generate "effective higher-order interactions for free" (Ciszak et al. 2025).
delay ¶
Time-delayed Kuramoto buffer and engine with validated phase history.
DelayBuffer stores copied finite phase snapshots in a bounded deque, and
DelayedEngine advances phases with delayed coupling, optional external
forcing, and Rust acceleration when available. Constructors and step inputs
reject non-positive dimensions, non-finite scalars, shape-mismatched arrays,
and boolean or numeric-string aliases before integration so delayed history
never aliases invalid caller state.
Classes¶
DelayBuffer ¶
Circular buffer storing phase history for delayed coupling.
Stores last max_delay_steps snapshots. Retrieves phases from
delay_steps steps ago.
Source code in src/scpn_phase_orchestrator/upde/delay.py
Attributes¶
length
property
¶
Methods:¶
push ¶
Append a phase snapshot to the buffer.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Source code in src/scpn_phase_orchestrator/upde/delay.py
get_delayed ¶
Return phases from delay_steps ago, or None if not enough history.
Parameters¶
delay_steps : int Number of steps in the past to retrieve from the delay buffer.
Returns¶
FloatArray | None
The phase snapshot from delay_steps ago, or None if history is
short.
Source code in src/scpn_phase_orchestrator/upde/delay.py
DelayedEngine ¶
Kuramoto with time-delayed coupling.
dθ_i/dt = ω_i + Σ_j K_ij sin(θ_j(t-τ) - θ_i(t) - α_ij)
Source code in src/scpn_phase_orchestrator/upde/delay.py
Attributes¶
delay_steps
property
¶
Return the configured discrete coupling delay.
Returns¶
int Return the configured discrete coupling delay.
Methods:¶
step ¶
step(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float = 0.0,
psi: float = 0.0,
alpha: FloatArray | None = None,
step_idx: int = 0,
) -> FloatArray
Advance one delayed Kuramoto timestep from validated state arrays.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray | None
Phase-lag matrix in radians, shape (N, N), or None for no lag.
step_idx : int
Zero-based index of the current step, used to address delayed coupling
history.
Returns¶
FloatArray
The phases after one delayed Kuramoto step, in [0, 2π).
Source code in src/scpn_phase_orchestrator/upde/delay.py
run ¶
run(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float = 0.0,
psi: float = 0.0,
alpha: FloatArray | None = None,
n_steps: int = 100,
) -> FloatArray
Run delayed Kuramoto integration for n_steps validated steps.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray | None
Phase-lag matrix in radians, shape (N, N), or None for no lag.
n_steps : int
Number of integration steps to run.
Returns¶
FloatArray
The final phases after n_steps delayed Kuramoto steps.
Source code in src/scpn_phase_orchestrator/upde/delay.py
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 | |
Functions:¶
Stochastic Resonance with Optimal Noise¶
Euler-Maruyama integration with automatic optimal noise tuning. Counter-intuitive: noise at D* INCREASES synchronization.
Optimal noise: D* ≈ K·R_det/2 (Tselios et al. 2025). Self-consistency via modified Bessel transcendental equation (Acebrón et al. 2005).
stochastic ¶
Stochastic noise injection and noise-level sweeps for UPDE phase dynamics.
StochasticInjector owns a local random generator and applies
Euler-Maruyama phase noise under validated non-negative diffusion and positive
time-step parameters. find_optimal_noise sweeps finite non-negative
candidate noise levels against a supplied UPDE engine and reports the best
coherence profile without changing the engine configuration or caller-provided
input arrays outside normal engine stepping.
Classes¶
NoiseProfile
dataclass
¶
Validated noise-sweep result linking diffusion to bounded order.
StochasticInjector ¶
Add calibrated noise to phase dynamics.
Euler-Maruyama: θ_i(t+dt) = θ_i(t) + f(θ)dt + √(2Ddt) * ξ_i where ξ_i ~ N(0,1) i.i.d.
Tselios et al. 2025 — stochastic resonance in Kuramoto networks.
Create an injector with finite D and an optional valid seed.
Source code in src/scpn_phase_orchestrator/upde/stochastic.py
Attributes¶
D
property
writable
¶
Return the configured non-negative diffusion coefficient.
Returns¶
float Return the configured non-negative diffusion coefficient.
Methods:¶
inject ¶
Add Wiener noise to phases: θ += √(2D*dt) * N(0,1).
Parameters¶
phases : FloatArray
Finite real numeric oscillator phases in radians, shape (N,).
Boolean, complex, and numeric-string aliases are rejected.
dt : float
Integration step size.
Returns¶
FloatArray The phases with added Wiener noise.
Source code in src/scpn_phase_orchestrator/upde/stochastic.py
Functions:¶
optimal_D ¶
Estimate optimal noise for stochastic resonance.
D* ≈ K·R_det/2 (common noise case). Tselios et al. 2025.
find_optimal_noise ¶
find_optimal_noise(
engine: UPDEEngine,
phases_init: FloatArray,
omegas: FloatArray,
knm: FloatArray,
alpha: FloatArray,
D_range: FloatArray | None = None,
n_steps: int = 500,
seed: int = 42,
) -> NoiseProfile
Sweep noise levels, return D that maximizes R.
Uses the engine to simulate n_steps at each D value.
Parameters¶
engine : UPDEEngine
The UPDE engine used to integrate each trial.
phases_init : FloatArray
Initial oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
D_range : FloatArray | None
Finite non-negative real numeric diffusion coefficients to sweep, or
None for the default range. Coercive aliases are rejected.
n_steps : int
Number of integration steps to run.
seed : int
Non-negative non-boolean seed for the deterministic RNG.
Returns¶
NoiseProfile
The noise profile whose diffusion D maximises R.
Source code in src/scpn_phase_orchestrator/upde/stochastic.py
Geometric (Torus-Preserving) Integrator¶
Symplectic Euler on T^N via SO(2) exponential map. Works in unit complex representation z_i = exp(iθ_i), avoiding mod 2π discontinuity errors that cause subtle numerical drift in standard integrators.
Essential for long timescale simulations where standard Euler accumulates phase wrapping errors.
geometric ¶
Torus-preserving symplectic Euler integrator on T^N = (S¹)^N.
Exposes a 5-backend fallback chain.
Scheme¶
Each phase is lifted to the unit circle z_i = exp(iθ_i); the
Kuramoto derivative ω_eff_i is computed in the tangent space,
and z_i is advanced by the exponential map
z_i(t + dt) = z_i(t) · exp(i · ω_eff_i · dt)
followed by renormalisation to the unit circle. This avoids the
mod-2π discontinuity that introduces subtle truncation errors
in standard integrators when trajectories cross θ = 0.
Across the five backends the (z_re, z_im) state is carried in
between steps (no atan2 round-trip per step), matching the Rust
kernel spo-engine/src/geometric.rs bit-for-bit. The pairwise
derivative uses the sincos expansion on the alpha == 0 branch
and the direct atan2 + sin(diff) form otherwise.
Classes¶
TorusEngine ¶
Symplectic Euler on T^N with 5-backend dispatch.
Store (n, dt); step / run are stateless in (θ, ω, K, α,
ζ, ψ).
Source code in src/scpn_phase_orchestrator/upde/geometric.py
Methods:¶
step ¶
step(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float,
psi: float,
alpha: FloatArray,
) -> FloatArray
One torus step; returns phases in [0, 2π).
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
Returns¶
FloatArray
The phases after one torus step, in [0, 2π).
Source code in src/scpn_phase_orchestrator/upde/geometric.py
run ¶
run(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
zeta: float,
psi: float,
alpha: FloatArray,
n_steps: int,
) -> FloatArray
Integrate torus phase dynamics for the requested number of steps.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
n_steps : int
Number of integration steps to run.
Returns¶
FloatArray
The final finite torus phases after n_steps torus steps, in
[0, 2π).
Raises¶
ValueError If the submitted state is malformed or an optional backend returns a phase vector outside the public torus contract.
Source code in src/scpn_phase_orchestrator/upde/geometric.py
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | |
order_parameter ¶
Compute the standard Kuramoto R = |
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
float
The Kuramoto order parameter R.
Source code in src/scpn_phase_orchestrator/upde/geometric.py
Functions:¶
Ott-Antonsen Mean-Field Reduction¶
Exact analytical reduction for Lorentzian frequency distributions:
Critical coupling K_c = 2Δ. Steady-state: R_ss = √(1 - 2Δ/K). Used by the PredictiveSupervisor as a fast forward model for MPC (O(1) vs O(N) for full simulation).
reduction ¶
Exact mean-field (Ott-Antonsen) reduction for globally-coupled Kuramoto.
Uses a Lorentzian g(ω) and a 5-backend fallback chain.
Dynamics¶
On the Ott-Antonsen manifold the full N-oscillator Kuramoto system reduces to a single complex-scalar ODE:
dz/dt = −(Δ + iω₀)·z + (K/2)·(z − |z|²·z)
with z = R·e^{iψ} the mean-field order parameter, Δ the
half-width of the Lorentzian g(ω), ω₀ its centre, and
K the coupling strength.
Steady-state R_ss = √(1 − 2Δ/K) for K > K_c = 2Δ and
R_ss = 0 below. Reference: Ott & Antonsen 2008, Chaos
18(3):037113.
Numerics¶
run(z0, n_steps) is the compute-kernel path: a tight RK4 loop
on the real/imaginary components of z. This is dispatched
across Rust / Mojo / Julia / Go / Python with bit-exact parity
(scalar ODE, no reduction identities, no global sums — the only
differences between backends are the rounding order of the
k1..k4 accumulation, which matches exactly).
The scalar-output helpers K_c, steady_state_R and
predict_from_oscillators stay native Python + optional Rust —
they are O(1) arithmetic or O(N) percentile work and do not
benefit from multi-language chains.
Classes¶
OAState
dataclass
¶
Ott-Antonsen mean-field state: order parameter and critical coupling.
OttAntonsenReduction ¶
Ott-Antonsen mean-field reduction for globally-coupled Kuramoto.
The class stores (ω₀, Δ, K, dt) and exposes K_c,
steady_state_R(), step(z), run(z0, n_steps) and
predict_from_oscillators(omegas, K). run is dispatched
across the 5-backend chain; the scalar helpers stay Python +
optional Rust.
Source code in src/scpn_phase_orchestrator/upde/reduction.py
Attributes¶
Methods:¶
steady_state_R ¶
Return the analytical steady-state R_ss = √(1 − 2Δ/K) for K > K_c.
Returns¶
float
Return the analytical steady-state R_ss = √(1 − 2Δ/K) for K > K_c.
Source code in src/scpn_phase_orchestrator/upde/reduction.py
step ¶
Single RK4 step on the OA ODE.
Parameters¶
z : complex Complex Ott-Antonsen order parameter.
Returns¶
complex The complex order parameter after one RK4 step.
Source code in src/scpn_phase_orchestrator/upde/reduction.py
run ¶
Integrate n_steps RK4 steps; return the final OAState.
Parameters¶
z0 : complex Initial complex Ott-Antonsen order parameter. n_steps : int Number of integration steps to run.
Returns¶
OAState
The final OAState after n_steps RK4 steps.
Source code in src/scpn_phase_orchestrator/upde/reduction.py
predict_from_oscillators ¶
Fit a Lorentzian to omegas and return the relaxed OAState.
Parameters¶
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
K : float
Global coupling strength.
Returns¶
OAState
The relaxed OAState for the fitted Lorentzian.
Source code in src/scpn_phase_orchestrator/upde/reduction.py
Functions:¶
Second-Order Inertial Kuramoto (Power Grids)¶
The swing equation models power grid transient stability:
where m_i is rotor inertia, d_i is damping, P_i is power injection (positive = generator, negative = load), K_ij is line susceptance.
Desynchronization = cascading blackout (Iberian Peninsula, April 2025).
from scpn_phase_orchestrator.upde.inertial import InertialKuramotoEngine
engine = InertialKuramotoEngine(n=100, dt=0.01)
theta, omega, theta_traj, omega_traj = engine.run(
theta0, omega0, power, knm, inertia, damping, n_steps=10000
)
freq_dev = engine.frequency_deviation(omega) # Hz deviation
R = engine.coherence(theta) # phase coherence
inertial ¶
Second-order (swing-equation) Kuramoto with a 5-backend fallback chain.
Model¶
Each oscillator has a phase θ_i and a "frequency-deviation"
ω_i ≡ dθ_i/dt. The swing equation is
M_i · d²θ_i/dt² + D_i · dθ_i/dt = P_i + Σ_j K_ij · sin(θ_j − θ_i)
and is advanced with classical explicit RK4 on the (θ, ω) pair.
This is the power-grid form used in Filatrella-Nielsen-Mallick 2008.
Numerics¶
The derivative uses the sin(θ_j − θ_i) = sin(θ_j)·cos(θ_i) −
cos(θ_j)·sin(θ_i) expansion so that floating-point rounding
matches the Rust kernel (spo-engine/src/inertial.rs) bit-for-bit.
All five backends (Rust, Mojo, Julia, Go, Python) agree within
~1e-14 on the canonical all-to-all test problem; the
dispatcher selects the fastest available path.
Classes¶
InertialKuramotoEngine ¶
Second-order swing-equation Kuramoto stepper with 5-backend dispatch.
The engine's geometry is (n, dt); the step itself is
stateless: (θ, ω, P, K, M, D) → (θ', ω').
Initialise the stateless inertial Kuramoto stepper geometry.
Source code in src/scpn_phase_orchestrator/upde/inertial.py
Methods:¶
step ¶
step(
theta: FloatArray,
omega_dot: FloatArray,
power: FloatArray,
knm: FloatArray,
inertia: FloatArray,
damping: FloatArray,
) -> tuple[FloatArray, FloatArray]
Advance one second-order inertial Kuramoto timestep.
Parameters¶
theta : FloatArray
Oscillator phases in radians, shape (N,).
omega_dot : FloatArray
Instantaneous frequency deviations in rad/s, shape (N,).
power : FloatArray
Per-oscillator power injection in the swing equation, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
inertia : FloatArray
Per-oscillator inertia coefficients, shape (N,).
damping : FloatArray
Per-oscillator damping coefficients, shape (N,).
Returns¶
tuple[FloatArray, FloatArray]
The (θ, ω̇) state after one second-order step.
Source code in src/scpn_phase_orchestrator/upde/inertial.py
run ¶
run(
theta: FloatArray,
omega_dot: FloatArray,
power: FloatArray,
knm: FloatArray,
inertia: FloatArray,
damping: FloatArray,
n_steps: int,
) -> tuple[FloatArray, FloatArray, FloatArray, FloatArray]
Integrate inertial Kuramoto dynamics and return final state plus traces.
Parameters¶
theta : FloatArray
Oscillator phases in radians, shape (N,).
omega_dot : FloatArray
Instantaneous frequency deviations in rad/s, shape (N,).
power : FloatArray
Per-oscillator power injection in the swing equation, shape (N,).
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
inertia : FloatArray
Per-oscillator inertia coefficients, shape (N,).
damping : FloatArray
Per-oscillator damping coefficients, shape (N,).
n_steps : int
Number of integration steps to run.
Returns¶
tuple[FloatArray, FloatArray, FloatArray, FloatArray]
The final (θ, ω̇) plus the θ and ω̇ traces.
Source code in src/scpn_phase_orchestrator/upde/inertial.py
frequency_deviation ¶
Return maximum absolute frequency deviation in cycles per unit time.
Parameters¶
omega_dot : FloatArray
Instantaneous frequency deviations in rad/s, shape (N,).
Returns¶
float The maximum absolute frequency deviation in cycles per unit time.
Source code in src/scpn_phase_orchestrator/upde/inertial.py
coherence ¶
Return the Kuramoto order parameter for the supplied phases.
Parameters¶
theta : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
float
The Kuramoto order parameter R.
Source code in src/scpn_phase_orchestrator/upde/inertial.py
Functions:¶
Financial Market Synchronization¶
Detect market regimes via Kuramoto order parameter on asset price phases. R(t) → 1 precedes market crashes (Black Monday 1987, 2008 crisis).
from scpn_phase_orchestrator.upde.market import (
extract_phase, market_order_parameter, detect_regimes, sync_warning,
)
phases = extract_phase(returns_matrix) # Hilbert transform
R = market_order_parameter(phases) # R(t) across assets
regimes = detect_regimes(R) # 0=desync, 1=transition, 2=sync
warnings = sync_warning(R, threshold=0.7) # crash early warning
market ¶
Kuramoto-based financial market synchronisation analysis.
Exposes a 5-backend fallback chain.
Extracts instantaneous phase from price / return time series via the
Hilbert transform (scipy.signal.hilbert — FFT-based, stays
Python-side because the Rust/Go/Mojo backends do not ship an FFT),
then dispatches the two post-processing compute kernels:
market_order_parameter(phases)—R(t) = |⟨exp(iθ)⟩_N|at every timestep.O(T · N).market_plv(phases, window)— rolling phase-locking-value matrix between assets,O((T − W + 1) · N² · W)with a sincos precompute that eliminates trig from the inner loop.
The detect_regimes classifier and sync_warning crossing
detector are O(T) masking / comparison operations; they stay pure
NumPy. R(t) → 1 preceded Black Monday 1987 and the 2008
crash (arXiv:1109.1167; CEUR-WS Vol-915).
Functions:¶
extract_phase ¶
Extract instantaneous phase from a time series via the Hilbert transform.
Stays Python-side because the transform is FFT-based
(scipy.signal.hilbert) and the compiled backends do not
ship an FFT library.
Parameters¶
series : FloatArray
Real-valued time series, shape (T,).
Returns¶
FloatArray
The instantaneous phase of the series in [0, 2π).
Source code in src/scpn_phase_orchestrator/upde/market.py
market_order_parameter ¶
Return the Kuramoto order parameter R(t) across N assets.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
FloatArray
The Kuramoto order parameter time series R(t).
Source code in src/scpn_phase_orchestrator/upde/market.py
market_plv ¶
Compute the rolling phase-locking-value matrix between assets.
Returns shape (T − window + 1, N, N).
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
window : int
Sliding-window length in samples.
Returns¶
FloatArray
The rolling phase-locking-value matrices, shape (T − window + 1, N, N).
Source code in src/scpn_phase_orchestrator/upde/market.py
detect_regimes ¶
detect_regimes(
R: FloatArray,
sync_threshold: float = 0.7,
desync_threshold: float = 0.3,
) -> IntArray
Classify market synchronisation regimes from R(t).
Returns int32 labels: 0 = desynchronised, 1 = transition,
2 = synchronised. O(T) masking; no multi-language port needed.
Parameters¶
R : FloatArray
Order-parameter time series R(t), shape (T,).
sync_threshold : float
Order parameter above which the market is classed as synchronised.
desync_threshold : float
Order parameter below which the market is classed as desynchronised.
Returns¶
IntArray The per-timestep market regime labels.
Raises¶
ValueError
If the thresholds are inconsistent or R is not 1-D.
Source code in src/scpn_phase_orchestrator/upde/market.py
sync_warning ¶
Detect synchronisation warnings where smoothed R crosses up.
Parameters¶
R : FloatArray
Order-parameter time series R(t), shape (T,).
threshold : float
Decision threshold.
lookback : int
Number of past samples smoothed over before the crossing test.
Returns¶
BoolArray A per-timestep boolean mask of synchronisation warnings.
Source code in src/scpn_phase_orchestrator/upde/market.py
Swarmalator Dynamics¶
Agents that are simultaneously self-propelled particles AND phase oscillators. Phase modulates spatial attraction; proximity modulates phase coupling.
Five collective states emerge depending on J and K: - J > 0, K > 0: static sync (clustered, phase-locked) - J > 0, K < 0: static async (clustered, anti-phase) - J < 0, K > 0: static phase wave (spatially ordered by phase) - J < 0, K < 0: splintered phase wave - |J| ≈ 0: active phase wave (rotating)
from scpn_phase_orchestrator.upde.swarmalator import SwarmalatorEngine
engine = SwarmalatorEngine(n=50, dim=2, dt=0.01, A=1.0, B=1.0, J=0.5, K=1.0)
pos, phases, pos_traj, phase_traj = engine.run(
positions0, phases0, omegas, n_steps=5000
)
# Metrics
R = engine.phase_coherence(phases)
compactness = engine.spatial_coherence(pos)
corr = engine.phase_spatial_correlation(pos, phases)
O'Keeffe, Hong, Strogatz, Nature Communications 2017. Experimental: Nature Communications Dec 2025 (colloidal system).
swarmalator ¶
Swarmalator step (position + phase) with a 5-backend fallback chain.
Swarmalators combine spatial attraction / repulsion with phase
oscillator dynamics (O'Keeffe, Hong & Strogatz, Nat. Commun. 8:1504,
2017). Each agent has a position x_i ∈ ℝ^d and a phase θ_i;
they co-evolve through attract/repulse + phase-coupling terms:
ẋ_i = (1/N) Σ_j (x_j − x_i) [(a + j·cos(θ_j − θ_i)) / |x_j − x_i|
− b / |x_j − x_i|²]
θ̇_i = ω_i + (k / N) Σ_j sin(θ_j − θ_i) / |x_j − x_i|
The repulsion b·(x_j − x_i) / |x_j − x_i|² is the canonical
inverse-distance hard core of O'Keeffe-Hong-Strogatz (magnitude
b / |x_j − x_i|), with a = A = 1, b = B = 1, j = J,
k = K recovering the original model. A single regularisation
constant ε = 1e-6 is added to |x_j − x_i|² (and inside the
sqrt for the attraction/phase |x_j − x_i|) so the kernel is
finite at coincident agents; it vanishes in the ε → 0 limit.
Classes¶
SwarmalatorEngine ¶
Swarmalator stepper with 5-backend dispatch.
The engine is stateful in its (n_agents, dim, dt) geometry
but the step contract is stateless: (pos, phases, omegas) →
(new_pos, new_phases).
Initialise the stateless swarmalator stepper geometry.
Source code in src/scpn_phase_orchestrator/upde/swarmalator.py
Methods:¶
step ¶
step(
pos: FloatArray,
phases: FloatArray,
omegas: FloatArray,
a: float = 1.0,
b: float = 1.0,
j: float = 1.0,
k: float = 1.0,
) -> tuple[FloatArray, FloatArray]
Advance coupled swarmalator positions and phases by one step.
Parameters¶
pos
Agent positions with shape (n_agents, dim).
phases
Agent phases in radians, shape (n_agents,).
omegas
Natural angular frequencies, shape (n_agents,).
a
Baseline spatial attraction coefficient.
b
Spatial repulsion coefficient.
j
Phase-dependent attraction modulation.
k
Phase-coupling coefficient.
Returns¶
tuple[FloatArray, FloatArray]
Updated positions with shape (n_agents, dim) and updated
phases wrapped into [0, 2*pi).
Notes¶
The dispatcher selects the first available accelerated backend and falls back to the NumPy reference path with the same state contract.
Source code in src/scpn_phase_orchestrator/upde/swarmalator.py
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 | |
run ¶
run(
pos: FloatArray,
phases: FloatArray,
omegas: FloatArray,
a: float = 1.0,
b: float = 1.0,
j: float = 1.0,
k: float = 1.0,
n_steps: int = 100,
) -> tuple[FloatArray, FloatArray, FloatArray, FloatArray]
Integrate swarmalator positions and phases with trajectory capture.
Parameters¶
pos : FloatArray
Swarmalator positions, shape (N, 2).
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
a : float
Spatial attraction strength.
b : float
Spatial repulsion strength.
j : float
Phase-to-space coupling strength.
k : float
Space-to-phase coupling strength.
n_steps : int
Number of integration steps to run.
Returns¶
tuple[FloatArray, FloatArray, FloatArray, FloatArray] The final positions and phases plus their trajectory traces.
Source code in src/scpn_phase_orchestrator/upde/swarmalator.py
order_parameter ¶
Return the Kuramoto order parameter for swarmalator phases.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
float
The Kuramoto order parameter R.