Skip to content

Drivers

External forcing functions that inject the drive signal Ψ(t) into the Kuramoto equation. The built-in drivers cover the standard physical, informational, and symbolic channel profile. Binding specs may also preserve named driver sections for extension channels, while custom actuation code decides how those channels affect Ψ(t).

Pipeline position

PhysicalDriver.compute(t)  ──→ Ψ_P(t)
InformationalDriver.compute(t) ──→ Ψ_I(t)   ──→  UPDEEngine.step(..., ζ, Ψ, ...)
SymbolicDriver.compute(step)  ──→ Ψ_S(k)

SupervisorPolicy.decide() ──→ ControlAction(knob="zeta") ──→ ζ(t)

The supervisor controls the drive strength ζ; the driver controls the drive phase Ψ. Together they form the external forcing term ζ·sin(Ψ - θ_i) in the Kuramoto ODE.

Common interface

All drivers implement compute(t_or_step) → float for single evaluation and compute_batch(array) → NDArray for vectorised evaluation.

Theory

The drive term in the Kuramoto ODE is:

\[\frac{d\theta_i}{dt} = \omega_i + \sum_j K_{ij} \sin(\theta_j - \theta_i - \alpha_{ij}) + \zeta \sin(\Psi - \theta_i)\]

The third term \(\zeta \sin(\Psi - \theta_i)\) represents external forcing. The supervisor controls \(\zeta\) (drive strength); the driver controls \(\Psi\) (drive phase). When \(\zeta > 0\), oscillators are pulled toward the target phase \(\Psi(t)\).

Physical Driver

Sinusoidal external drive:

\[\Psi_P(t) = A \sin(2\pi f t)\]

Models periodic physical forcing — cardiac pacemaker signals, power grid reference frequency (50/60 Hz), mechanical vibration sources, plasma heating pulses.

Parameters:

Parameter Type Constraint Description
frequency float > 0 Drive frequency in Hz
amplitude float >= 0 Peak amplitude (default 1.0)

Batch mode: compute_batch(t_array) vectorises over a NumPy array of time values for efficient integration.

psi_physical

Physical-channel sinusoidal reference-phase driver.

PhysicalDriver generates finite sinusoidal drive values from a positive frequency and non-negative amplitude. Scalar and vector compute paths reject boolean, complex, non-numeric, and non-finite time inputs so external forcing cannot inject invalid values into UPDE integration.

Classes

PhysicalDriver

PhysicalDriver(frequency: float, amplitude: float = 1.0)

Sinusoidal external drive: Psi_P(t) = amplitude * sin(2pifrequency*t).

Initialise a physical sinusoidal reference driver.

Parameters

frequency : float Positive drive frequency in hertz. amplitude : float, default=1.0 Non-negative peak drive amplitude.

Raises

ValueError If either parameter is a boolean alias, non-real, non-finite, or outside its allowed numeric range.

Source code in src/scpn_phase_orchestrator/drivers/psi_physical.py
def __init__(self, frequency: float, amplitude: float = 1.0):
    """Initialise a physical sinusoidal reference driver.

    Parameters
    ----------
    frequency : float
        Positive drive frequency in hertz.
    amplitude : float, default=1.0
        Non-negative peak drive amplitude.

    Raises
    ------
    ValueError
        If either parameter is a boolean alias, non-real, non-finite, or
        outside its allowed numeric range.
    """
    if isinstance(frequency, bool) or isinstance(amplitude, bool):
        raise ValueError("frequency and amplitude must be finite real values")
    parsed_frequency = _require_finite_real(frequency, name="frequency")
    parsed_amplitude = _require_finite_real(amplitude, name="amplitude")
    if parsed_frequency <= 0.0:
        raise ValueError(f"frequency must be finite and positive, got {frequency}")
    if parsed_amplitude < 0.0:
        raise ValueError(
            f"amplitude must be finite and non-negative, got {amplitude}"
        )
    self._frequency = parsed_frequency
    self._amplitude = parsed_amplitude
Methods:
compute
compute(t: float) -> float

Return Psi_P at time t.

Parameters

t : float Time in seconds.

Returns

float Psi_P at time t.

Source code in src/scpn_phase_orchestrator/drivers/psi_physical.py
def compute(self, t: float) -> float:
    """Return Psi_P at time *t*.

    Parameters
    ----------
    t : float
        Time in seconds.

    Returns
    -------
    float
        Psi_P at time *t*.
    """
    t = _require_finite_real(t, name="t")
    return float(self._amplitude * np.sin(TWO_PI * self._frequency * t))
compute_batch
compute_batch(t_array: FloatArray) -> FloatArray

Vectorised Psi_P over an array of time values.

Parameters

t_array : FloatArray Time samples, shape (T,).

Returns

FloatArray Vectorised Psi_P over an array of time values.

Source code in src/scpn_phase_orchestrator/drivers/psi_physical.py
def compute_batch(self, t_array: FloatArray) -> FloatArray:
    """Vectorised Psi_P over an array of time values.

    Parameters
    ----------
    t_array : FloatArray
        Time samples, shape ``(T,)``.

    Returns
    -------
    FloatArray
        Vectorised Psi_P over an array of time values.
    """
    t_array = _require_finite_real_array(t_array, name="t_array")
    result: FloatArray = self._amplitude * np.sin(
        TWO_PI * self._frequency * t_array
    )
    return result

Informational Driver

Linear ramp drive with modular wrapping:

\[\Psi_I(t) = 2\pi f_c t \pmod{2\pi}\]

Models information cadence — packet arrival rates, event stream clocks, data pipeline heartbeats. The phase advances at a constant rate \(f_c\) Hz, producing a sawtooth waveform that resets at \(2\pi\).

This driver is appropriate when the domain has a natural clock rate (e.g. 100 Hz monitoring cadence) and the goal is to synchronise oscillators to that cadence.

Parameters:

Parameter Type Constraint Description
cadence_hz float > 0 Information cadence in Hz

psi_informational

Informational-channel cadence reference-phase driver.

InformationalDriver maps a positive event cadence to wrapped phase-drive values in [0, 2*pi). Constructor and compute paths reject invalid numeric inputs so cadence-driven forcing remains bounded and deterministic.

Classes

InformationalDriver

InformationalDriver(cadence_hz: float)

External drive Psi_I(t) = 2picadence_hzt (mod 2pi).

Initialise an informational cadence reference driver.

Parameters

cadence_hz : float Positive event cadence in hertz.

Raises

ValueError If the cadence is a boolean alias, non-real, non-finite, or not positive.

Source code in src/scpn_phase_orchestrator/drivers/psi_informational.py
def __init__(self, cadence_hz: float):
    """Initialise an informational cadence reference driver.

    Parameters
    ----------
    cadence_hz : float
        Positive event cadence in hertz.

    Raises
    ------
    ValueError
        If the cadence is a boolean alias, non-real, non-finite, or not
        positive.
    """
    parsed_cadence = _require_finite_real(cadence_hz, name="cadence_hz")
    if not isfinite(parsed_cadence) or parsed_cadence <= 0.0:
        raise ValueError(
            f"cadence_hz must be finite and positive, got {cadence_hz}"
        )
    self._cadence_hz = parsed_cadence
Methods:
compute
compute(t: float) -> float

Return Psi_I at time t, wrapped to [0, 2*pi).

Parameters

t : float Time in seconds.

Returns

float Psi_I at time t, wrapped to [0, 2*pi).

Source code in src/scpn_phase_orchestrator/drivers/psi_informational.py
def compute(self, t: float) -> float:
    """Return Psi_I at time *t*, wrapped to [0, 2*pi).

    Parameters
    ----------
    t : float
        Time in seconds.

    Returns
    -------
    float
        Psi_I at time *t*, wrapped to [0, 2*pi).
    """
    t = _require_finite_real(t, name="t")
    return (TWO_PI * self._cadence_hz * t) % TWO_PI
compute_batch
compute_batch(t_array: FloatArray) -> FloatArray

Vectorised Psi_I over an array of time values.

Parameters

t_array : FloatArray Time samples, shape (T,).

Returns

FloatArray Vectorised Psi_I over an array of time values.

Source code in src/scpn_phase_orchestrator/drivers/psi_informational.py
def compute_batch(self, t_array: FloatArray) -> FloatArray:
    """Vectorised Psi_I over an array of time values.

    Parameters
    ----------
    t_array : FloatArray
        Time samples, shape ``(T,)``.

    Returns
    -------
    FloatArray
        Vectorised Psi_I over an array of time values.
    """
    t_array = _require_finite_real_array(t_array, name="t_array")
    result: FloatArray = (TWO_PI * self._cadence_hz * t_array) % TWO_PI
    return result

Symbolic Driver

Deterministic phase sequence:

\[\Psi_S(k) = s_{k \bmod N}\]

where \(s = [s_0, s_1, \ldots, s_{N-1}]\) is a pre-defined phase sequence that repeats with period \(N\).

Models symbolic/semiotic patterns — language token sequences, musical motifs, protocol state machines, ritual rhythms. The sequence encodes a pattern that the oscillator network is driven to reproduce.

Parameters:

Parameter Type Constraint Description
sequence list[float] non-empty Phase values (radians)

Usage:

from scpn_phase_orchestrator.drivers.psi_symbolic import SymbolicDriver

# Musical 4/4 pattern: downbeat, weak, medium, weak
pattern = [0.0, np.pi, np.pi/2, np.pi]
driver = SymbolicDriver(pattern)

# Step 0 → 0.0, step 1 → π, step 4 → 0.0 (wraps)
psi = driver.compute(step=0)

psi_symbolic

Symbolic-channel cyclic sequence reference driver.

SymbolicDriver exposes deterministic cyclic lookup over finite real symbolic phase values. It rejects empty, boolean-containing, multi-dimensional, non-finite, or non-integer step inputs before returning drive values for symbolic or semiotic simulations.

Classes

SymbolicDriver

SymbolicDriver(sequence: list[float])

Deterministic phase sequence driver for symbolic/semiotic channels.

Initialise a cyclic symbolic reference sequence.

Parameters

sequence : list[float] Non-empty one-dimensional phase sequence in radians.

Raises

ValueError If the sequence is empty, multi-dimensional, non-finite, or carries Python or NumPy boolean aliases.

Source code in src/scpn_phase_orchestrator/drivers/psi_symbolic.py
def __init__(self, sequence: list[float]):
    """Initialise a cyclic symbolic reference sequence.

    Parameters
    ----------
    sequence : list[float]
        Non-empty one-dimensional phase sequence in radians.

    Raises
    ------
    ValueError
        If the sequence is empty, multi-dimensional, non-finite, or carries
        Python or NumPy boolean aliases.
    """
    if not sequence:
        raise ValueError("sequence must be non-empty")
    if _contains_bool(sequence):
        raise ValueError("sequence values must be finite real numbers")
    parsed_sequence = np.asarray(sequence, dtype=np.float64)
    if parsed_sequence.ndim != 1:
        raise ValueError("sequence must be one-dimensional")
    if not np.all(np.isfinite(parsed_sequence)):
        raise ValueError("sequence values must be finite real numbers")
    self._sequence = parsed_sequence
    self._n = len(parsed_sequence)
Methods:
compute
compute(step: int) -> float

Return symbolic phase at discrete step (cyclic).

Parameters

step : int Zero-based step index.

Returns

float Symbolic phase at discrete step (cyclic).

Source code in src/scpn_phase_orchestrator/drivers/psi_symbolic.py
def compute(self, step: int) -> float:
    """Return symbolic phase at discrete *step* (cyclic).

    Parameters
    ----------
    step : int
        Zero-based step index.

    Returns
    -------
    float
        Symbolic phase at discrete *step* (cyclic).
    """
    step = _validate_step(step)
    return float(self._sequence[step % self._n])
compute_batch
compute_batch(steps: IntArray) -> FloatArray

Vectorised symbolic phase lookup over an array of step indices.

Parameters

steps : IntArray Number of replay steps.

Returns

FloatArray Vectorised symbolic phase lookup over an array of step indices.

Source code in src/scpn_phase_orchestrator/drivers/psi_symbolic.py
def compute_batch(self, steps: IntArray) -> FloatArray:
    """Vectorised symbolic phase lookup over an array of step indices.

    Parameters
    ----------
    steps : IntArray
        Number of replay steps.

    Returns
    -------
    FloatArray
        Vectorised symbolic phase lookup over an array of step indices.
    """
    steps = _validate_steps(steps)
    result: FloatArray = self._sequence[steps % self._n]
    return result

API summary

All drivers share the same interface:

Method Signature Description
compute (t: float) → float or (step: int) → float Single evaluation
compute_batch (array) → NDArray Vectorised evaluation

Input validation

Driver Validation Raises
PhysicalDriver frequency > 0, amplitude >= 0, finite real scalar and vector time inputs, no Python or NumPy boolean aliases ValueError
InformationalDriver cadence_hz > 0, finite real scalar and vector time inputs, no Python or NumPy boolean aliases ValueError
SymbolicDriver len(sequence) > 0, one-dimensional finite real sequence, integer step inputs, no Python or NumPy boolean aliases ValueError

Output ranges

Driver Output range
PhysicalDriver [-amplitude, +amplitude]
InformationalDriver [0, 2π)
SymbolicDriver values from input sequence

Integration example

from scpn_phase_orchestrator.drivers.psi_physical import PhysicalDriver
from scpn_phase_orchestrator.upde.engine import UPDEEngine

driver = PhysicalDriver(frequency=1.0, amplitude=0.5)
eng = UPDEEngine(n=8, dt=0.01)

for step_i in range(1000):
    t = step_i * 0.01
    psi = driver.compute(t)       # Ψ(t)
    zeta = 0.3                    # from supervisor
    phases = eng.step(phases, omegas, knm, zeta, psi, alpha)

The compute_batch method is useful when pre-computing an entire Ψ trajectory:

import numpy as np
t_array = np.arange(0, 10.0, 0.01)
psi_trajectory = driver.compute_batch(t_array)

Choosing a driver

Domain Driver Rationale
Power grid (50 Hz) PhysicalDriver(50.0) Reference frequency
Cardiac pacemaker PhysicalDriver(1.2) Heart rate
API monitoring (100 Hz) InformationalDriver(100.0) Polling cadence
Protocol FSM SymbolicDriver([0, π/2, π, 3π/2]) State sequence
Music sync SymbolicDriver(beat_phases) Rhythm pattern
No external drive Set ζ = 0 Third term vanishes