UPDE — Time-varying natural frequencies¶
UPDEEngine supports natural-frequency schedules for systems where each
oscillator's intrinsic angular frequency is a function of outer-step time:
This is the PHA-C.5 surface required by moving-frame and Doppler workflows. It keeps the ordinary fixed-omega API intact while adding two production-safe forms:
| Form | API | Use case |
|---|---|---|
| Fixed constructor omega | UPDEEngine(..., omega=array) |
Reuse the same frequency vector across many step() or run() calls without passing it every time. |
| Callable omega | UPDEEngine(..., omega=lambda t: omega0 + slope*t) |
Chirps, drifting clocks, moving-agent frequency shifts, thermal detuning, Doppler preparation, or measured frequency schedules. |
Existing calls such as engine.step(phases, omegas, knm, zeta, psi, alpha) and
engine.run(phases, omegas, knm, zeta, psi, alpha, n_steps) remain valid.
Configured omega is used only when the call does not provide an explicit
frequency vector.
Example¶
import numpy as np
from scpn_phase_orchestrator.upde import UPDEEngine
n = 4
dt = 1.0e-6
omega0 = np.array([1.0, 1.1, 0.9, 1.2])
slope = np.array([-0.01, 0.0, 0.02, -0.015])
knm = np.zeros((n, n))
alpha = np.zeros((n, n))
phases = np.zeros(n)
engine = UPDEEngine(
n,
dt=dt,
method="euler",
omega=lambda t: omega0 + slope * t,
)
final_phases = engine.run(phases, knm=knm, alpha=alpha, n_steps=100)
current_omega = engine.omega_current
current_time = engine.time
For zero coupling and no drive, a linear schedule has the analytic reference
The module-specific regression tests use a 1 microsecond step to keep the Euler schedule error within the 1 ppm acceptance envelope.
Backend contract¶
Callable frequencies are resolved into a finite real-valued schedule matrix with
shape (n_steps, n_oscillators). The stateless schedule runner then dispatches
that matrix through the same backend chain as fixed-omega UPDE:
| Backend | Schedule surface |
|---|---|
| Rust | PyUPDEStepper.run_omega_schedule() |
| Go | UPDERunOmegaSchedule in go/upde_engine.go |
| Julia | UPDEEngineJL.upde_run_omega_schedule() |
| Mojo | RUN_SCHEDULE operation in mojo/upde_engine.mojo |
| Python | upde_run_omega_schedule_python() |
Each backend receives the same row-major schedule. Validation rejects boolean, complex, non-finite, empty, wrong-rank, and wrong-width schedules before any integration result is accepted.
Benchmark gate¶
Run the local parity gate:
The committed result is local regression evidence only. It records backend availability and parity, but does not make production timing claims unless the run is repeated under the repository benchmark-isolation requirements.
When to apply time-varying omega¶
- Real systems drift. This API is the contract-safe path for adding that drift without changing every engine call-site.
- It is most useful when clocks or sensors have predictable schedules (thermal drift, rotational speedup, moving-frame corrections).
- Validation remains strict before dispatch to any backend, so you get predictable behavior under missing toolchains and mixed-precision runners.
Business context¶
Time-varying frequency support is the reliability bridge between textbook simulation and real hardware timing realities.
In production settings, intrinsic frequencies drift due to thermal, mechanical, and environmental effects. The callable schedule path lets teams keep a single engine contract while modeling these drifts explicitly.
That design also helps with auditability: the same run record captures both the nominal model and the frequency evolution used to drive it.
When to enable this path¶
- If your telemetry or schedule is predictable enough to encode as an explicit function.
- If you need consistent backend behavior across Python and acceleration runtimes.
- If you require schedule-aware replay evidence for review or safety-case packages.
Public API¶
upde_run_omega_schedule ¶
upde_run_omega_schedule(
phases: FloatArray,
omega_schedule: FloatArray,
knm: FloatArray,
alpha: FloatArray,
zeta: float,
psi: float,
dt: float,
method: str = "euler",
n_substeps: int = 1,
atol: float = 1e-06,
rtol: float = 0.001,
) -> FloatArray
Run UPDE with one resolved natural-frequency vector per outer step.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omega_schedule : FloatArray
Per-step natural-frequency vectors, shape (n_steps, 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.
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
dt : float
Integration step size.
method : str
Integration method (euler, rk4, or rk45).
n_substeps : int
Number of inner substeps per outer step.
atol : float
Absolute tolerance for the adaptive (rk45) integrator.
rtol : float
Relative tolerance for the adaptive (rk45) integrator.
Returns¶
FloatArray The final phases after integrating the omega schedule.