Control Systems¶
SPO adds a supervision layer over coupled-oscillator dynamics that emits review-only control proposals; TVB, neurolib, Brian2, and NEST are simulate-and-observe libraries. SPO does not close a control loop on hardware.
Model-Predictive Controller (MPC)¶
Predicts R trajectory 10 steps ahead using the Ott-Antonsen mean-field reduction as a fast forward model. Acts BEFORE degradation, not after. Detects divergence and reverts to reactive control as fallback.
from scpn_phase_orchestrator.supervisor.predictive import PredictiveSupervisor
supervisor = PredictiveSupervisor(engine, horizon=10)
# supervisor.step() predicts future R, triggers actions preemptively
predictive ¶
Predictive and free-energy supervisor diagnostics for bounded action proposals.
The module provides Ott-Antonsen horizon prediction, variational free-energy
assessment, and hierarchy-level FEP assessments over validated phase/frequency
state. Predictive supervisors emit conservative ControlAction proposals for
degradation, critical forecasts, hard boundaries, or high surprise. They do not
apply actuation or mutate caller-owned phase/coupling arrays.
Classes¶
Prediction
dataclass
¶
Prediction(
R_predicted: list[float],
will_degrade: bool,
will_critical: bool,
steps_to_degradation: int,
)
Forward model output: predicted R trajectory and degradation flags.
FEPPredictionAssessment
dataclass
¶
FEPPredictionAssessment(
free_energy: float,
complexity: float,
mean_abs_error: float,
precision_mean: float,
precision_spread: float,
observed_R: float,
observed_psi: float,
predicted_R: float,
target_R: float,
surprise: float,
)
One-step variational free-energy assessment for supervisor control.
Attributes¶
above_target
property
¶
Return True when observed coherence exceeds the target.
Returns¶
bool Return True when observed coherence exceeds the target.
Methods:¶
to_audit_record ¶
Return a serialisable audit payload.
Returns¶
dict[str, float] Return a serialisable audit payload.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
FEPHierarchyChildAssessment
dataclass
¶
FEPHierarchyChildAssessment(
name: str,
assessment: FEPPredictionAssessment,
actions: tuple[ControlAction, ...],
)
Assessment for one child node in a hierarchical FEP supervisor.
Methods:¶
to_audit_record ¶
Return a JSON-safe child hierarchy audit record.
Returns¶
dict[str, object] Return a JSON-safe child hierarchy audit record.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
FEPHierarchyAssessment
dataclass
¶
FEPHierarchyAssessment(
hierarchy: str,
children: tuple[FEPHierarchyChildAssessment, ...],
parent_assessment: FEPPredictionAssessment,
parent_actions: tuple[ControlAction, ...],
child_R_values: tuple[float, ...],
parent_phase_encoding: tuple[float, ...],
)
Audit-ready child-to-parent FEP hierarchy assessment.
Methods:¶
to_audit_record ¶
Return a JSON-safe hierarchy assessment payload.
Returns¶
dict[str, object] Return a JSON-safe hierarchy assessment payload.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
PredictiveSupervisor ¶
PredictiveSupervisor(
n_oscillators: int,
dt: float,
horizon: int = 10,
divergence_threshold: float = 0.3,
)
Model-predictive supervisor using Ott-Antonsen forward model.
Predicts R trajectory horizon steps ahead. Acts preemptively when
predicted R crosses thresholds, instead of waiting for actual degradation.
Falls back to reactive supervision if OA prediction diverges.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
Methods:¶
predict ¶
predict(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
alpha: FloatArray,
) -> Prediction
Predict R trajectory using OA reduction as fast forward model.
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).
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
Returns¶
Prediction
The predicted R trajectory.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
decide ¶
decide(
phases: FloatArray,
omegas: FloatArray,
knm: FloatArray,
alpha: FloatArray,
upde_state: UPDEState,
boundary_state: BoundaryState,
) -> list[ControlAction]
Predictive control: act before degradation, not after.
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).
alpha : FloatArray
Phase-lag matrix in radians, shape (N, N), or None for no lag.
upde_state : UPDEState
The current UPDE state.
boundary_state : BoundaryState
The current boundary-observer state.
Returns¶
list[ControlAction] The predictive control actions for the current state.
Raises¶
ValueError If the state inputs are invalid.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
FEPPredictiveSupervisor ¶
FEPPredictiveSupervisor(
n_oscillators: int,
dt: float,
target_R: float = 0.8,
free_energy_threshold: float = 1.0,
error_threshold: float = 0.25,
drive_gain: float = 0.1,
learning_rate: float = 0.01,
prior_precision: float = 1.0,
)
Free-energy predictive supervisor built on VariationalPredictor.
The class turns the existing FEP-Kuramoto variational predictor into a
bounded supervisor mode. It does not claim a complete biological FEP
model; it exposes an auditable one-step free-energy signal and maps high
surprise into conservative zeta / Psi control actions.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
Attributes¶
target_R
property
¶
Target order parameter used by the free-energy controller.
Returns¶
float Target order parameter used by the free-energy controller.
last_assessment
property
¶
Most recent free-energy assessment, if assess has run.
Returns¶
FEPPredictionAssessment | None
Most recent free-energy assessment, if assess has run.
Methods:¶
assess ¶
Update the variational predictor and return audit-ready metrics.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
Returns¶
FEPPredictionAssessment The free-energy prediction assessment.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
decide ¶
decide(
phases: FloatArray,
omegas: FloatArray,
upde_state: UPDEState,
boundary_state: BoundaryState,
) -> list[ControlAction]
Return FEP-MPC control actions for the current observation.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
upde_state : UPDEState
The current UPDE state.
boundary_state : BoundaryState
The current boundary-observer state.
Returns¶
list[ControlAction] The FEP-MPC control actions for the current observation.
Raises¶
ValueError If the state inputs are invalid.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
reset ¶
Functions:¶
assess_fep_hierarchy ¶
assess_fep_hierarchy(
children: Mapping[str, tuple[FloatArray, FloatArray]],
*,
dt: float,
child_target_R: float = 0.8,
parent_target_R: float = 0.8,
parent_dt: float | None = None,
free_energy_threshold: float = 0.0,
child_drive_gain: float = 0.08,
parent_drive_gain: float = 0.05,
hierarchy: str = "child_regions_to_parent_fep_supervisor",
) -> FEPHierarchyAssessment
Assess child FEP supervisors and a parent over reduced child coherence.
Each child receives its own FEPPredictiveSupervisor. The parent encodes
child coherence as phases via arccos(2R - 1) so the same FEP machinery
can reason over cross-child coherence without accessing raw child signals.
Parameters¶
children : Mapping[str, tuple[FloatArray, FloatArray]]
Child supervisor summaries.
dt : float
Integration step size.
child_target_R : float
Target order parameter for each child.
parent_target_R : float
Target order parameter for the parent.
parent_dt : float | None
Parent integration step size, or None.
free_energy_threshold : float
Free-energy threshold above which control acts.
child_drive_gain : float
Drive gain applied at the child level.
parent_drive_gain : float
Drive gain applied at the parent level.
hierarchy : str
Hierarchy label.
Returns¶
FEPHierarchyAssessment The hierarchical free-energy assessment.
Source code in src/scpn_phase_orchestrator/supervisor/predictive.py
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 | |
Regime Manager¶
Finite state machine for synchronization regimes with hysteresis. States: NOMINAL, DEGRADED, CRITICAL. Transitions based on R thresholds with configurable hysteresis bands to prevent oscillation between states.
regimes ¶
Regime classification with hysteresis, cooldown, and optional event emission.
RegimeManager classifies reduced UPDE and boundary state into nominal,
degraded, critical, or recovery regimes, then applies cooldown and hysteresis
rules before committing transitions. Transition history is bounded and optional
events are posted through an injected in-process bus. The manager emits regime
state only; policy modules decide any control proposals.
Classes¶
Regime ¶
Bases: Enum
Operational regime of the SCPN supervisor.
RegimeManager ¶
RegimeManager(
hysteresis: float = 0.05,
cooldown_steps: int = 10,
event_bus: EventBus | None = None,
hysteresis_hold_steps: int = 0,
)
Classify system state into regimes with hysteresis and cooldown.
Source code in src/scpn_phase_orchestrator/supervisor/regimes.py
Attributes¶
current_regime
property
¶
The regime established after the most recent transition.
Returns¶
Regime The regime established after the most recent transition.
Methods:¶
evaluate ¶
Propose a regime based on current R values and boundary state.
Parameters¶
upde_state : UPDEState The current UPDE state. boundary_state : BoundaryState The current boundary-observer state.
Returns¶
Regime The regime proposed for the current state.
Source code in src/scpn_phase_orchestrator/supervisor/regimes.py
transition ¶
Apply cooldown/hysteresis logic and commit the regime transition.
Parameters¶
proposed : Regime The proposed regime to transition into.
Returns¶
Regime The committed regime after cooldown/hysteresis.
Source code in src/scpn_phase_orchestrator/supervisor/regimes.py
force_transition ¶
Bypass cooldown and hysteresis hold.
Parameters¶
regime : Regime The current control regime.
Returns¶
Regime The regime after a forced transition.
Source code in src/scpn_phase_orchestrator/supervisor/regimes.py
Petri Net State Machine¶
Formal Petri net FSM with guard conditions, token counts, and priority-based transition firing. Enables formal verification of safety properties (deadlock freedom, liveness).
from scpn_phase_orchestrator.supervisor.petri_net import PetriNet
net = PetriNet()
net.add_place("nominal", tokens=1)
net.add_place("critical", tokens=0)
net.add_transition("degrade", inputs=["nominal"], outputs=["critical"],
guard=lambda ctx: ctx["R"] < 0.3)
petri_net ¶
Guarded Petri-net primitives for deterministic regime transition modeling.
The module defines validated places, weighted arcs, guards, transitions, markings, and a first-match-priority Petri net. Marking updates are local and non-negative, guard metrics must be finite, and net construction rejects arcs to unknown places. The engine performs no event emission or policy action mapping; adapter modules own those boundaries.
Classes¶
Guard
dataclass
¶
Boolean guard condition on a named metric (e.g. 'stability_proxy > 0.6').
Methods:¶
evaluate ¶
Return True if the guard condition is satisfied by ctx.
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
Transition
dataclass
¶
Petri net transition with input/output arcs and optional guard.
Marking
dataclass
¶
Token distribution across places in a Petri net.
Methods:¶
active_places ¶
Return names of places that hold at least one token.
Returns¶
list[str] Return names of places that hold at least one token.
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
PetriNet ¶
Classical Petri net with guard-gated transitions.
step() fires at most one enabled transition per call (first-match priority).
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
Attributes¶
place_names
property
¶
All place names registered in this net.
Returns¶
frozenset[str] All place names registered in this net.
transitions
property
¶
All transitions in firing-priority order.
Returns¶
list[Transition] All transitions in firing-priority order.
guard_metrics
property
¶
Whitelisted context metric names used by transition guards.
Returns¶
frozenset[str] Whitelisted context metric names used by transition guards.
Methods:¶
enabled ¶
Return all transitions whose input arcs and guards are satisfied.
Parameters¶
marking : Marking The Petri net marking (token distribution). ctx : Mapping[str, float] Context metric values keyed by guard-metric name.
Returns¶
list[Transition] The transitions whose input arcs and guards are satisfied.
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
fire ¶
Fire transition, consuming input tokens and producing output tokens.
Parameters¶
marking : Marking The Petri net marking (token distribution). transition : Transition The transition to fire.
Returns¶
Marking The marking after firing the transition.
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
step ¶
Fire the first enabled transition, return (new_marking, fired_transition).
Parameters¶
marking : Marking The Petri net marking (token distribution). ctx : Mapping[str, float] Context metric values keyed by guard-metric name.
Returns¶
tuple[Marking, Transition | None]
The new marking and the fired transition (or None).
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
Functions:¶
parse_guard ¶
Parse guard string like 'stability_proxy > 0.6'.
Source code in src/scpn_phase_orchestrator/supervisor/petri_net.py
Policy Engine¶
Rule-based policy evaluation for supervisor actions. Rules define conditions (R thresholds, boundary violations) and actions (coupling boost, frequency adjustment, external drive).
policy ¶
Reactive supervisor policy that maps regimes and state into control proposals.
SupervisorPolicy derives a proposed regime from direct metrics or an
optional Petri adapter, commits it through RegimeManager, and emits bounded
ControlAction proposals for degraded, critical, or recovery states. Petri
failures fall back to direct regime logic. The policy only proposes actions; it
does not apply actuation or mutate coupling matrices.
Classes¶
SupervisorPolicyGains
dataclass
¶
SupervisorPolicyGains(
k_bump: float = 0.05,
zeta_bump: float = 0.1,
k_reduce: float = -0.03,
restore_fraction: float = 0.5,
)
Tunable regime-action gains for a deployment-specific supervisor.
SupervisorPolicy ¶
SupervisorPolicy(
regime_manager: RegimeManager,
petri_adapter: PetriNetAdapter | None = None,
gains: SupervisorPolicyGains | None = None,
admission_gate: PolicyCBFAdmissionGate | None = None,
)
Decide control actions based on regime and system state.
When petri_adapter is provided, regime is derived from the Petri net marking instead of RegimeManager.evaluate().
Source code in src/scpn_phase_orchestrator/supervisor/policy.py
Attributes¶
last_admission_records
property
¶
Return the CBF admission records from the latest decision.
Returns¶
tuple[PolicyCBFAdmissionRecord, ...]
Deterministic audit records for actions matched by the optional CBF
admission gate in the previous :meth:decide call.
Methods:¶
decide ¶
decide(
upde_state: UPDEState,
boundary_state: BoundaryState,
petri_ctx: dict[str, float] | None = None,
) -> list[ControlAction]
Evaluate regime and return control actions for the current state.
Parameters¶
upde_state : UPDEState
The current UPDE state.
boundary_state : BoundaryState
The current boundary-observer state.
petri_ctx : dict[str, float] | None
Petri context metric values, or None.
Returns¶
list[ControlAction] The control actions proposed for the current state.
Source code in src/scpn_phase_orchestrator/supervisor/policy.py
Three-Factor Hebbian Plasticity¶
Coupling adaptation rule: K_ij += lr × eligibility × modulator × gate.
- Eligibility: cos(θ_j - θ_i) — pairwise Hebbian trace
- Modulator: scalar neuromodulatory signal from L16 director
- Phase gate: Boolean from topological-integration gate
Grounded in Friston 2005 on free energy and synaptic plasticity.
plasticity ¶
Validated three-factor plasticity updates for coupling matrices.
The module computes pairwise phase eligibility traces and applies a
modulator-gated Hebbian update to K_nm. Public functions reject boolean,
non-numeric, non-finite, non-vector, non-square, and shape-mismatched inputs so
plasticity cannot corrupt coupling state silently. The update preserves the
Kuramoto coupling contract by requiring non-negative zero-diagonal K_nm,
bounded zero-diagonal eligibility traces, and finite real scalar controls.
Functions:¶
compute_eligibility ¶
Pairwise Hebbian eligibility trace: cos(theta_j - theta_i).
Returns shape (n, n) with zero diagonal.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
Returns¶
FloatArray
The pairwise Hebbian eligibility trace cos(θ_j − θ_i).
Source code in src/scpn_phase_orchestrator/coupling/plasticity.py
three_factor_update ¶
three_factor_update(
knm: FloatArray,
eligibility: FloatArray,
modulator: float,
phase_gate: bool,
lr: float = 0.01,
) -> FloatArray
Three-factor plasticity rule: K_ij += lr * eligibility_ij * M * gate.
Factors
- eligibility — pairwise phase correlation (Hebbian trace)
- modulator — scalar reward/error signal from L16 director
- phase_gate — boolean from the topological-integration gate
Friston 2005, Philos. Trans. R. Soc. B 360:815-836 (free energy & synaptic plasticity).
Parameters¶
knm : FloatArray current coupling matrix, shape (n, n). eligibility : FloatArray Hebbian trace, shape (n, n). modulator : float scalar neuromodulatory signal. phase_gate : bool if False, no update occurs (integration gate below threshold). lr : float learning rate.
Returns¶
FloatArray Updated coupling matrix (new array, does not mutate input).
Raises¶
TypeError If an argument has the wrong type. ValueError If the eligibility or coupling shapes mismatch.
Source code in src/scpn_phase_orchestrator/coupling/plasticity.py
Transfer Entropy Adaptive Coupling¶
K_ij(t+1) = (1-decay)·K_ij(t) + lr·TE(i→j)
Unlike symmetric Hebbian learning, transfer entropy breaks symmetry to detect causal direction. Coupling adapts based on directed information flow (Lizier 2012).
te_adaptive ¶
Transfer-entropy-guided coupling adaptation for offline matrix updates.
te_adapt_coupling derives a directed transfer-entropy matrix from phase
history and combines it with the current coupling matrix under learning-rate
and decay parameters. The Python fallback clamps the returned coupling to
non-negative values and clears self-coupling; the optional Rust path preserves
the same dense N x N output contract. The helper returns a new matrix and
does not mutate live solver state or apply actuation.
Functions:¶
te_adapt_coupling ¶
te_adapt_coupling(
knm: FloatArray,
phase_history: FloatArray,
lr: float = 0.01,
decay: float = 0.0,
n_bins: int = 8,
) -> FloatArray
Adapt coupling matrix using transfer entropy as learning signal.
K_ij(t+1) = (1-decay) * K_ij(t) + lr * TE(i→j)
Strengthens coupling along causal information flow channels. Weakens where there is no causal influence.
Lizier 2012, "Local Information Transfer as a Spatiotemporal Filter for Complex Systems," Physical Review E 77(2):026110.
Parameters¶
knm : FloatArray current (n, n) coupling matrix. phase_history : FloatArray (n, T) recent phase trajectories. lr : float learning rate for TE-based update. decay : float coupling decay rate per update (0 = no decay). n_bins : int histogram bins for TE estimation.
Returns¶
FloatArray FloatArray The coupling matrix adapted by the transfer-entropy learning signal.
Raises¶
RuntimeError If the transfer-entropy backend fails.
Source code in src/scpn_phase_orchestrator/coupling/te_adaptive.py
Audit Trail with Deterministic Replay¶
SHA256-chained JSONL audit log with per-step regime, R values, actions, and coupling state. Enables deterministic replay and cryptographic verification of simulation reproducibility.
audit ¶
Audit logging, event streaming, and deterministic replay entry points.
The audit package owns append-only JSONL records, optional hash-chained event streams, replay reconstruction, and integrity verification. Public helpers fail closed on malformed signatures or key material while preserving unsigned development logs for local reproducibility workflows.
Functions:¶
__dir__ ¶
__getattr__ ¶
Lazily expose audit package exports without import cycles.
Source code in src/scpn_phase_orchestrator/audit/__init__.py
What makes this closed-loop in production¶
Most oscillator libraries expose observability but stop before actuation. SPO’s control surface pushes into action selection with three constraints in the same loop:
- Prediction: expected coherence trend via MPC proxy.
- Safety: regime and evidence checks before promotion.
- Governance: audit-ready proposal records for every non-trivial action.
This changes operational posture from “watch and decide” to “predict-then-verify-then-act” under explicit constraints.
Operator operating model¶
For day-to-day deployment, teams typically configure:
- A baseline monitor that maps
Randchimera_indexto supervisory inputs. - A policy layer with conservative defaults.
- A Petri-NET-safe transition set for state changes.
- An audit sink that captures state transitions and proposal rationale.
That model keeps tuning sessions reviewable and supports post-incident replay without manual reconstruction of transient state.
Stability and rollback behavior¶
Because state transitions are explicit and regime-gated, operators can define clear rollback boundaries: if coherence drops or monitor evidence regresses after an action, policy proposals can be bounded and reverted before the next control cycle.
Operational placement in closed-loop projects¶
Use this page when the target outcome is a stable control loop, not just a monitoring dashboard. The control stack in this repository is built in layers:
- prediction: MPC and monitors produce a near-term risk estimate,
- policy and regime selection: Petri-Net and policy DSL select candidate actions,
- constraint application: projector, boundaries, and imprint constraints limit per-cycle movement,
- governance: audit logging and replay preserve every non-trivial change.
A common rollout order is:
- choose monitor set (
R,PLV, boundary metrics), - define objectives and regime thresholds,
- dry-run policy and projection limits,
- replay the same sequence with fixed seeds,
- promote only when both expected and observed lock metrics match the decision gate.
Keep this page as a production entry for teams that need a predictable control path after data onboarding and domain calibration.
Control posture and evidence contract¶
Every production setup using this page should keep three artifacts versioned:
- policy definition files (rules, cooldowns, caps),
- latest audit configuration and lockfile choice,
- baseline replay traces from the last accepted tuning cycle.
That set is what allows an operator to compare one control action against history and answer whether the action improved or degraded the system trajectory.
The supervisor surfaces are intentionally split into prediction, policy, and governance layers. If one layer is changed, replay the full sequence before promotion so you can isolate the effect of that change without mixing it with backend or extractor drift.