Adapters¶
Bridges between SPO and external systems in the SCPN ecosystem, observability platforms, and hardware controllers. Each adapter translates SPO's internal representations (phases, coupling matrices, regime states) into the wire format expected by the target system.
Pipeline position¶
UPDEEngine ──→ UPDEState ──→ Adapters (output)
│
┌─────────────┼──────────────────┐
↓ ↓ ↓
SCPN Ecosystem Observability Hardware
│ │ │
├─ scpn_control ├─ OpenTelemetry ├─ Modbus/TLS
├─ fusion_core ├─ Prometheus └─ gRPC
├─ neurocore └─ Grafana
├─ plasma_control
├─ quantum_control
└─ snn_bridge
External Systems ──→ Adapters (input) ──→ Oscillator Extractors
Adapters are bidirectional: output adapters export SPO state to external systems; input adapters import external signals for phase extraction.
SCPN Ecosystem Bridges¶
These adapters connect SPO to sibling packages in the SCPN ecosystem. They share the Kuramoto/UPDE phase representation but differ in scope:
| Adapter | Target | Data flow |
|---|---|---|
scpn_control_bridge |
scpn-control (v0.18.0) | Bidirectional: phases, coupling, regime |
fusion_core_bridge |
SCPN-Fusion-Core (v3.9.3) | Export: sync metrics for fusion analysis |
neurocore_bridge |
sc-neurocore (v3.13.3) | Export: phase states for SNN processing |
plasma_control_bridge |
Plasma control systems | Import: magnetic diagnostics as P-channel |
quantum_control_bridge |
scpn-quantum-control (v0.9.1) | Export: coherence metrics for QPU scheduling |
hybrid_cocompiler |
Quantum + neuromorphic review package | Export: shared audit envelope for simulator handoff |
snn_bridge |
local SNN daemon | Export: local phase-dynamics signal packets |
scpn-control Bridge¶
SCPNControlBridge(scpn_config: dict) — bidirectional adapter.
| Method | Signature | Description |
|---|---|---|
import_knm |
(scpn_knm: NDArray) → CouplingState |
Wrap external K_nm |
import_omega |
(scpn_omega: NDArray) → NDArray |
Validate frequencies |
export_state |
(upde_state: UPDEState) → dict |
Telemetry export |
import_knm validates non-empty finite real-valued square matrices with a zero
self-coupling diagonal. import_omega validates non-empty finite real-valued
1-D vectors with strictly positive natural frequencies. export_state produces
a dict with regime, stability, layers (each with R, ψ, lock signatures).
scpn_control_bridge ¶
SCPN-control bridge for validated coupling, frequency, and telemetry exchange.
The bridge accepts JSON-compatible configuration, imports finite dense coupling matrices and positive natural-frequency vectors, and exports reduced UPDE state telemetry with layer locks and cross-alignment. It is a data-shape adapter only; it does not invoke an external control engine or apply actions.
Classes¶
SCPNControlBridge ¶
Adapter between scpn-control telemetry and phase-orchestrator types.
Source code in src/scpn_phase_orchestrator/adapters/scpn_control_bridge.py
Methods:¶
import_knm ¶
Wrap an external Knm matrix into a CouplingState.
Parameters¶
scpn_knm : FloatArray
An external coupling matrix, shape (N, N).
Returns¶
CouplingState The coupling state wrapping the external matrix.
Raises¶
ValueError If the coupling matrix is invalid.
Source code in src/scpn_phase_orchestrator/adapters/scpn_control_bridge.py
import_omega ¶
Validate and pass through natural frequencies.
Parameters¶
scpn_omega : FloatArray
External natural frequencies, shape (N,).
Returns¶
FloatArray The validated natural frequencies.
Raises¶
ValueError If the natural frequencies are invalid.
Source code in src/scpn_phase_orchestrator/adapters/scpn_control_bridge.py
export_state ¶
Convert UPDEState to scpn-control compatible telemetry dict.
Parameters¶
upde_state : UPDEState The UPDE state to export.
Returns¶
dict[str, Any] The scpn-control-compatible telemetry dict.
Source code in src/scpn_phase_orchestrator/adapters/scpn_control_bridge.py
Fusion Core Bridge¶
FusionCoreBridge is a non-executing review bridge for
scpn-fusion-core equilibrium summaries. It maps positive q-profile
bounds, non-negative normalised beta, confinement time, sawtooth/ELM event
counts, and non-negative MHD amplitude into bounded phase channels. The
feedback path rejects empty phase vectors before computing the complex order
parameter, so exported R_global, mean phase, and mean frequency records stay
finite. Stability checks also reject negative beta and confinement-ratio
payloads instead of silently converting them into ordinary soft violations.
fusion_core_bridge ¶
Fusion-Core bridge for phase encoding and stability-review diagnostics.
The bridge maps fusion equilibrium observables into bounded phase vectors, returns aggregate phase feedback summaries, normalises q-profile/equilibrium payloads, and checks local fusion stability invariants. It is pure NumPy/dict code and does not require or call a live fusion solver; outputs are review signals and feedback dictionaries for explicit downstream handoff.
Classes¶
FusionCoreBridge ¶
Adapter between scpn-fusion-core equilibrium data and phase-orchestrator.
All methods work without scpn-fusion-core (pure numpy + dict).
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
Methods:¶
observables_to_phases ¶
Map 6 fusion observables to [0, 2*pi) phases.
Observable → Phase formula: q_profile → 2pi(q - q_min)/(q_max - q_min) beta_n → 2pibeta_n/beta_limit tau_e → 2pitau_e/tau_ref sawtooth_count → countpi mod 2pi elm_count → countpi mod 2pi mhd_amplitude → 2piamplitude/threshold
Parameters¶
snapshot : dict[str, Any] Fusion observable values keyed by name.
Returns¶
FloatArray
The oscillator phases in [0, 2π), shape (N,).
Raises¶
ValueError If the snapshot is missing required observables.
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
phases_to_feedback ¶
Convert phase state back to feedback signals for the equilibrium solver.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
Returns¶
dict[str, Any] The feedback signals for the equilibrium solver.
Raises¶
ValueError If the phases or omegas are invalid.
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
import_q_profile ¶
Parse a q-profile from dict or scpn-fusion-core object.
Returns normalised dict with keys: q_min, q_max, q_axis, q_edge.
Parameters¶
q_profile_or_dict : object A q-profile as an scpn-fusion-core object or a dict.
Returns¶
dict[str, Any] The parsed q-profile as a dict.
Raises¶
ValueError If the q-profile cannot be parsed.
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
import_equilibrium ¶
Extract equilibrium observables from a fusion kernel result dict.
Parameters¶
kernel_result : dict[str, Any] An scpn-fusion-core kernel result dict.
Returns¶
dict[str, Any] The equilibrium observables extracted from the kernel result.
Raises¶
ValueError If the kernel result is malformed.
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
check_stability ¶
Check fusion stability invariants.
Returns a list of violation dicts (empty if all invariants hold).
Parameters¶
observables : dict[str, Any] Fusion observable values keyed by name.
Returns¶
list[dict[str, Any]] The list of stability-invariant violations.
Raises¶
ValueError If the observables are invalid.
Source code in src/scpn_phase_orchestrator/adapters/fusion_core_bridge.py
Functions:¶
Plasma Control Bridge¶
PlasmaControlBridge is the non-actuating plasma telemetry boundary. It
accepts finite layer-coupling matrices, phase snapshots, Lyapunov review
scores, and invariant payloads only after rejecting boolean numeric aliases,
non-zero layer self-coupling, empty phase snapshots, negative beta and
Greenwald ratios, and non-positive safety-factor minima. This keeps the
Kronecker-expanded K_nm graph off-diagonal and prevents placeholder
phase-state exports from entering downstream review paths.
plasma_control_bridge ¶
Plasma-control bridge for telemetry import, coupling expansion, and review.
The bridge converts plasma-layer coupling matrices, phase snapshots, natural
frequencies, Lyapunov verdicts, and proposed control action dictionaries into
SPO-compatible data structures under strict finite-shape validation. It also
checks local plasma invariants for review. The adapter performs no live plasma
actuation and does not require scpn-control to be installed.
Classes¶
PlasmaControlBridge ¶
Adapter between scpn-control plasma telemetry and phase-orchestrator types.
All methods work without scpn-control installed (pure numpy + dict).
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
Methods:¶
import_knm_spec ¶
Expand an (L, L) layer-coupling matrix to (N, N) via Kronecker replication.
Accepts a dict with 'matrix' key (list of lists or ndarray) and optional 'n_osc_per_layer' (int, default 2), or a raw ndarray of shape (L, L).
Parameters¶
knm_spec_or_dict : object A layer-coupling matrix as an object or dict.
Returns¶
CouplingState
The expanded (N, N) coupling state.
Raises¶
ValueError If the coupling spec is malformed.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
import_plasma_omega ¶
Generate natural frequencies spanning plasma timescales.
Returns frequencies ordered: micro_turbulence(fast) → plasma_wall(slow).
Parameters¶
n_osc_per_layer : int Number of oscillators per layer.
Returns¶
FloatArray The natural frequencies spanning plasma timescales.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
import_snapshot ¶
Convert an scpn-control tick result dict to UPDEState.
Expected keys: 'phases' (1-D array), optional 'regime', 'layer_sizes'.
Parameters¶
tick_result : dict[str, Any] An scpn-control tick result dict.
Returns¶
UPDEState The UPDE state for the tick result.
Raises¶
ValueError If the tick result is malformed.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
import_lyapunov_verdict ¶
Map a Lyapunov verdict to a boundary-compatible signal dict.
Accepts dict with 'score' (float in [0,1]).
Parameters¶
verdict_or_dict : object A Lyapunov verdict as an object or dict.
Returns¶
dict[str, Any] The boundary-compatible signal dict for the verdict.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
export_control_actions ¶
Package a list of control action dicts for scpn-control consumption.
Parameters¶
actions : list[Any] Control action dicts to package.
Returns¶
dict[str, Any] The control actions packaged for scpn-control.
Raises¶
ValueError If an action dict is invalid.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
check_physics_invariants ¶
Check plasma physics invariants against local thresholds.
Returns a list of violation dicts (empty if all invariants hold).
Parameters¶
values : dict[str, Any] Plasma observable values keyed by name.
Returns¶
list[dict[str, Any]] The list of physics-invariant violations.
Raises¶
ValueError If the observable values are invalid.
Source code in src/scpn_phase_orchestrator/adapters/plasma_control_bridge.py
Quantum Control Bridge¶
quantum_control_bridge ¶
Quantum-control bridge for reviewable Hamiltonian and phase handoffs.
The bridge imports quantum phase artifacts into UPDE diagnostics, exports UPDE state summaries, validates coupling/frequency arrays, and can build deterministic OpenQASM manifest handoffs with parity hashes and actuation disabled. Live Hamiltonian or Q-UPDE execution is delegated only when external quantum-control packages are explicitly imported by the called method.
Classes¶
QuantumControlBridge ¶
Adapter between scpn-quantum-control artifacts and phase-orchestrator types.
The QuantumControlBridge enables the mapping of classical Kuramoto phase dynamics onto Quantum Hardware (isomorphic XY spin Hamiltonian). It supports Hamiltonian construction, Trotterized time evolution (Q-UPDE), and variational synchronization minimization.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
Methods:¶
import_artifact ¶
Convert a scpn-quantum-control result dict into UPDEState.
Parameters¶
artifact_dict : dict[str, Any] An scpn-quantum-control result dict.
Returns¶
UPDEState The UPDE state for the quantum result.
Raises¶
ValueError If the artifact dict is malformed.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
export_artifact ¶
Convert UPDEState back to a dict compatible with scpn-quantum-control.
Parameters¶
state : UPDEState The current UPDE state.
Returns¶
dict[str, Any] The scpn-quantum-control-compatible state dict.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
import_knm ¶
Wrap a coupling matrix from quantum calibration into CouplingState.
Parameters¶
knm_array : FloatArray
A coupling matrix from quantum calibration, shape (N, N).
Returns¶
CouplingState The coupling state wrapping the calibration matrix.
Raises¶
ValueError If the coupling matrix is invalid.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
import_scpn_upde_edge ¶
Import a QUANTUM knm.scpn-upde edge under a bounded scope.
The accepted edge carries K_nm and omega arrays plus Trotter
metadata. SPO recomputes the payload digests and its own deterministic
compiler manifest. It does not permit QPU execution or actuation.
Parameters¶
edge_payload : dict[str, object]
A payload emitted by
scpn_quantum_control.bridge.scpn_upde_edge.
Returns¶
dict[str, object] Import evidence containing the coupling state and compiler manifest.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
build_quantum_compiler_manifest ¶
build_quantum_compiler_manifest(
knm: FloatArray, omegas: FloatArray, *, dt: float
) -> dict[str, object]
Return a deterministic OpenQASM handoff with parity evidence.
The manifest is dependency-free review output for Qiskit/PennyLane simulator handoff. It does not execute on a QPU and does not permit live actuation.
Parameters¶
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
dt : float
Integration step size.
Returns¶
dict[str, object] The deterministic OpenQASM handoff with parity evidence.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
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 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 | |
audit_qpu_target_readiness ¶
audit_qpu_target_readiness(
manifest: dict[str, object],
*,
target_backend: str,
provider: str,
credentials_configured: bool = False,
operator_approved: bool = False,
) -> dict[str, object]
Return non-executing QPU target-readiness evidence.
The audit validates that a quantum compiler manifest is suitable for a named target backend and records whether operator preconditions are in place. It never runs a simulator, submits a QPU job, or flips the manifest execution/actuation permissions.
Parameters¶
manifest : dict[str, object] The compiler manifest to audit. target_backend : str Name of the target backend. provider : str Name of the hardware provider. credentials_configured : bool Whether provider credentials are configured. operator_approved : bool Whether a human operator approved the target.
Returns¶
dict[str, object] The non-executing QPU target-readiness evidence.
Raises¶
ValueError If the manifest or target details are invalid.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
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 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 | |
build_hamiltonian ¶
Build Kuramoto XY Hamiltonian as SparsePauliOp.
Requires scpn-quantum-control.
Parameters¶
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
Returns¶
object The Kuramoto XY Hamiltonian as a SparsePauliOp.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
solve_q_upde ¶
solve_q_upde(
knm: FloatArray,
omegas: FloatArray,
t_max: float = 1.0,
dt: float = 0.1,
trotter_per_step: int = 5,
) -> dict[str, Any]
Execute Trotterized quantum simulation of the phase network (Q-UPDE).
This method maps the classical sin(delta theta) interaction to the XY spin exchange interaction (XX + YY) and natural frequencies to Z-axis magnetic fields.
Requires scpn-quantum-control.
Parameters¶
knm : FloatArray
Coupling matrix K_nm, shape (N, N).
omegas : FloatArray
Natural frequencies in rad/s, shape (N,).
t_max : float
Total simulation time.
dt : float
Integration step size.
trotter_per_step : int
Number of Trotter steps per outer step.
Returns¶
dict[str, Any] The Trotterised Q-UPDE simulation result.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
orchestrator_to_quantum ¶
Convert orchestrator UPDEState to quantum phase array.
Parameters¶
state : UPDEState The current UPDE state.
Returns¶
FloatArray The quantum phase array for the UPDE state.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
quantum_to_orchestrator ¶
Convert quantum phase array back to orchestrator-compatible dict.
Parameters¶
quantum_theta : FloatArray
Quantum phase array, shape (N,).
Returns¶
dict[str, Any] The orchestrator-compatible dict for the quantum phases.
Raises¶
ValueError If the quantum phase array is invalid.
Source code in src/scpn_phase_orchestrator/adapters/quantum_control_bridge.py
Functions:¶
OpenQASM 3 Conformance¶
check_openqasm3 statically validates the structural conformance of the
OpenQASM 3 text the quantum-control bridge emits: version header, includes,
qubit-register declarations, custom gate declarations, and every gate
application's parameter/qubit arity and register bounds. Its gate registry is
split into honestly-labelled tiers — the gates stdgates.inc actually defines
versus the two-qubit Pauli-rotation extensions (rxx/ryy/rzz/rzx) that
Qiskit and PennyLane provide as builtins — so a program using the extensions is
recorded as portable-to-those-backends rather than mislabelled as pure-standard
conformant. The bridge embeds the report under the manifest's
openqasm_conformance key and surfaces qasm_parse_ok in the co-simulation
parity evidence.
openqasm_conformance ¶
Deterministic structural conformance checker for OpenQASM 3 programs.
This module validates the structural conformance of an OpenQASM 3 program:
version header, includes, quantum-register declarations, custom gate
declarations, and gate applications (measure / reset / barrier are
recognised as non-gate operations). For every gate application it resolves the
gate name against a known registry and checks that the classical-parameter count
and qubit-operand count match the gate's arity, and that every indexed qubit
operand refers to a declared register within bounds.
Scope and honesty boundary¶
The checker is a static structural validator, not a full OpenQASM 3 parser,
type checker, or simulator: it does not evaluate parameter expressions, classical
control flow (if / for / while), subroutines (def), timing, or
pulse-level constructs. Statements outside the checked subset are surfaced in
:attr:OpenQasm3ConformanceReport.unchecked_statements and never silently pass
as "conformant".
The known-gate registry is split into two honestly-labelled tiers:
- :data:
STANDARD_LIBRARY_GATES— the gates the OpenQASM 3 standard library headerstdgates.incactually defines. This table is transcribed from the reference definitions at https://github.com/openqasm/openqasm/blob/main/examples/stdgates.inc and the standard-library documentation, verified at source on 2026-07-21. It notably does not contain the two-qubit Pauli-rotation gatesrxx/ryy/rzz/rzx. - :data:
BACKEND_EXTENSION_GATES— the two-qubit Pauli-rotation gates that common OpenQASM 3 target backends (Qiskit'sqiskit.qasm3importer and PennyLane's QASM loader) provide as builtins even though they are absent fromstdgates.inc. A program that uses these is portable to those backends but is not pure-stdgates.incconformant, so the report records their use explicitly via :attr:OpenQasm3ConformanceReport.extension_gates_usedrather than blurring the distinction.
Classes¶
OpenQasm3ConformanceReport
dataclass
¶
OpenQasm3ConformanceReport(
conformant: bool,
qasm_version: str | None,
includes: tuple[str, ...],
qubit_registers: tuple[tuple[str, int], ...],
gate_call_count: int,
stdgates_used: tuple[str, ...],
extension_gates_used: tuple[str, ...],
custom_gates_declared: tuple[str, ...],
issues: tuple[str, ...] = tuple(),
unchecked_statements: tuple[str, ...] = tuple(),
)
Structured result of an OpenQASM 3 structural conformance check.
Attributes¶
uses_non_stdgates_extensions
property
¶
Return whether the program uses gates absent from stdgates.inc.
Returns¶
bool
True when at least one applied gate is a backend extension
(:data:BACKEND_EXTENSION_GATES) rather than a standard-library or
in-program gate.
Methods:¶
to_audit_record ¶
Return a deterministic JSON-safe audit mapping of this report.
Returns¶
dict[str, object] A sorted, JSON-serialisable mapping suitable for embedding in a review manifest.
Source code in src/scpn_phase_orchestrator/adapters/openqasm_conformance.py
Functions:¶
check_openqasm3 ¶
Check the structural conformance of an OpenQASM 3 program.
The function never raises: any structural violation is recorded as an issue
and reflected in :attr:OpenQasm3ConformanceReport.conformant.
Parameters¶
program : str The OpenQASM 3 source text to validate.
Returns¶
OpenQasm3ConformanceReport
The structural conformance report. conformant is True only when
a version header is present and no structural issue was found.
Raises¶
TypeError If program is not a string.
Source code in src/scpn_phase_orchestrator/adapters/openqasm_conformance.py
Hybrid Co-Compiler¶
hybrid_cocompiler ¶
Deterministic hybrid co-compiler review manifests.
Functions:¶
build_hybrid_cocompiler_manifest ¶
build_hybrid_cocompiler_manifest(
quantum_manifest: Mapping[str, object],
neuromorphic_manifest: Mapping[str, object],
*,
n_channel_semantics: Sequence[str] = (
"Q_control",
"S_spike",
"audit",
),
) -> dict[str, object]
Combine quantum and spiking manifests under one audit envelope.
Parameters¶
quantum_manifest : Mapping[str, object] The quantum compiler manifest. neuromorphic_manifest : Mapping[str, object] The neuromorphic schedule manifest. n_channel_semantics : Sequence[str] Per-channel semantic labels.
Returns¶
dict[str, object] The combined quantum/neuromorphic hybrid manifest.
Source code in src/scpn_phase_orchestrator/adapters/hybrid_cocompiler.py
audit_hybrid_target_readiness ¶
audit_hybrid_target_readiness(
hybrid_manifest: Mapping[str, object],
quantum_readiness: Mapping[str, object],
neuromorphic_readiness: Mapping[str, object],
*,
hybrid_operator_approved: bool = False,
) -> dict[str, object]
Return non-executing hybrid target-readiness evidence.
The audit links the already review-only hybrid manifest to the independent quantum and neuromorphic target-readiness records. It never submits work to a QPU, simulator, neuromorphic backend, or actuator.
Parameters¶
hybrid_manifest : Mapping[str, object] The combined hybrid co-compiler manifest. quantum_readiness : Mapping[str, object] Quantum target-readiness evidence. neuromorphic_readiness : Mapping[str, object] Neuromorphic target-readiness evidence. hybrid_operator_approved : bool Whether a human operator approved the hybrid target.
Returns¶
dict[str, object] The non-executing hybrid target-readiness evidence.
Raises¶
ValueError If the manifests or readiness evidence are invalid.
Source code in src/scpn_phase_orchestrator/adapters/hybrid_cocompiler.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
build_hybrid_operator_handoff_package ¶
build_hybrid_operator_handoff_package(
hybrid_manifest: Mapping[str, object],
hybrid_readiness: Mapping[str, object],
) -> dict[str, object]
Build a deterministic non-executing package for external operators.
Parameters¶
hybrid_manifest : Mapping[str, object] The combined hybrid co-compiler manifest. hybrid_readiness : Mapping[str, object] Hybrid target-readiness evidence.
Returns¶
dict[str, object] The deterministic non-executing operator handoff package.
Raises¶
ValueError If the manifest or readiness evidence is invalid.
Source code in src/scpn_phase_orchestrator/adapters/hybrid_cocompiler.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 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 | |
SNN Bridge¶
SNNControllerBridge maps finite UPDE layer order-parameter magnitudes in
[0, 1] to LIF input currents, validates non-negative real-valued spike rates
before action projection, and rejects boolean or complex array aliases before
schedule-manifest generation.
snn_bridge ¶
Spiking-neural-network bridge for NumPy LIF estimates and review manifests.
SNNControllerBridge maps UPDE layer coherence to input currents, estimates
steady-state LIF rates, proposes control actions from rate thresholds, and
builds deterministic Lava/PyNN schedule manifests with hardware writes disabled.
Optional Lava process construction is isolated to its explicit method. The core
bridge remains pure NumPy and does not perform neuromorphic actuation.
Classes¶
SNNControllerBridge ¶
Bridge between UPDE state and spiking neural network controllers.
All methods are pure-numpy — no external SNN libraries required.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
Methods:¶
upde_state_to_input_current ¶
Map R values from each layer to LIF input currents.
Parameters¶
state : UPDEState The current UPDE state. i_scale : float Scaling factor from order parameter to input current.
Returns¶
FloatArray The LIF input currents for each layer.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
spike_rates_to_actions ¶
spike_rates_to_actions(
rates: FloatArray,
layer_assignments: list[int],
threshold_hz: float = 50.0,
) -> list[ControlAction]
Convert spike rates to control actions.
rates: 1-D array of mean firing rates (Hz) per neuron group. layer_assignments: maps each rate index to a layer. threshold_hz: rates above this trigger coupling boost.
Parameters¶
rates : FloatArray Per-layer spike rates. layer_assignments : list[int] Per-neuron layer assignments. threshold_hz : float Firing-rate threshold in Hz for emitting actions.
Returns¶
list[ControlAction] The control actions for the spike rates.
Raises¶
ValueError If the rates or assignments are invalid.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
lif_rate_estimate ¶
Analytic LIF steady-state firing rate (Abbott 1999, Eq. 1).
rate = 1 / (tau_ref - tau_rc * ln(1 - 1/J)) for J > 1
Parameters¶
currents : FloatArray Per-neuron input currents.
Returns¶
FloatArray The analytic LIF steady-state firing rates.
Raises¶
ValueError If the input currents are invalid.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
build_numpy_network ¶
Build a pure-numpy LIF network for UPDE-SNN coupling.
Returns a SimpleNamespace with input_node, ensemble, output_node attributes and a step() method.
Parameters¶
n_layers : int Number of SCPN layers. seed : int Seed for the deterministic RNG. synapse : float Synaptic weight scale.
Returns¶
SimpleNamespace The pure-numpy LIF network.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
build_lava_process ¶
Build a Lava LIF process for UPDE-SNN coupling.
Raises ImportError if lava-nc is not installed.
Parameters¶
n_layers : int Number of SCPN layers.
Returns¶
object The Lava LIF process.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
build_neuromorphic_schedule_manifest ¶
build_neuromorphic_schedule_manifest(
state: UPDEState,
*,
i_scale: float = 1.0,
threshold_hz: float = 50.0,
projection_delay_ms: float = 1.0,
) -> dict[str, object]
Compile a reviewable Lava/PyNN schedule from a UPDE state.
The manifest is deterministic and contains simulator-parity evidence from the pure-numpy LIF rate path. It opens no hardware handles and does not permit actuation.
Parameters¶
state : UPDEState The current UPDE state. i_scale : float Scaling factor from order parameter to input current. threshold_hz : float Firing-rate threshold in Hz for emitting actions. projection_delay_ms : float Projection delay in milliseconds.
Returns¶
dict[str, object] The reviewable Lava/PyNN schedule manifest.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
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 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 | |
audit_hardware_target_readiness ¶
audit_hardware_target_readiness(
manifest: dict[str, object],
*,
target_backend: str,
hardware_site: str,
credentials_configured: bool = False,
operator_approved: bool = False,
external_simulator_parity_verified: bool = False,
) -> dict[str, object]
Return non-executing neuromorphic hardware readiness evidence.
The audit validates a schedule manifest against a declared target and records whether external operator preconditions are present. It never opens a backend connection, submits a hardware job, or enables actuation/hardware-write permissions.
Parameters¶
manifest : dict[str, object] The compiler manifest to audit. target_backend : str Name of the target backend. hardware_site : str Name of the deployment hardware site. credentials_configured : bool Whether provider credentials are configured. operator_approved : bool Whether a human operator approved the target. external_simulator_parity_verified : bool Whether external-simulator parity has been verified.
Returns¶
dict[str, object] The non-executing neuromorphic hardware readiness evidence.
Raises¶
ValueError If the manifest or target details are invalid.
Source code in src/scpn_phase_orchestrator/adapters/snn_bridge.py
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 532 533 534 535 536 537 538 539 540 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 | |
Functions:¶
Neuromorphic IR Export¶
to_nir_graph serialises a schedule's LIF populations and inter-population
projections into a deterministic, SHA-256-hashed graph in the shape of the
Neuromorphic Intermediate Representation (neuromorphs/NIR): nodes are neuron
populations, edges are weighted (source, target) connections. The export is an
honestly-labelled structural subset (conformance = "structural_subset"),
not a spec-validated NIR export: the SCPN Abbott-rate LIF defines only the
membrane/refractory time constants and a normalised firing threshold, so the
NIR physical parameters it does not model (R, v_leak, v_reset) are listed
in unmodelled_nir_lif_parameters rather than fabricated. The SNN bridge embeds
the graph under the schedule manifest's neuromorphic_ir key with a nir_sha256
digest.
neuromorphic_ir_export ¶
Deterministic NIR-structural graph export for SNN schedule manifests.
This module serialises the LIF populations and inter-population projections of a
reviewable neuromorphic schedule into a portable, deterministic, SHA-256-hashed
graph in the shape of the Neuromorphic Intermediate Representation
(neuromorphs/NIR <https://github.com/neuromorphs/NIR>_): a directed graph whose
nodes are neuron populations and whose edges are (source, target) connection
tuples with weights.
Honesty boundary¶
The output is a structural subset, not a spec-validated NIR export, and it
says so in its metadata (conformance = "structural_subset"). The reference
NIR LIF primitive is parametrised by tau [ms], R [Ω], v_leak [mV],
v_reset [mV], and v_threshold [mV] (verified at source, 2026-07-21). The
SCPN SNN bridge models an Abbott-1999 analytic-rate LIF that genuinely defines
only the membrane and refractory time constants and a normalised firing
threshold; it does not define the NIR physical parameters R, v_leak, or
v_reset. Rather than fabricate those values, the export emits only the
parameters the model actually holds, marks the firing threshold as normalised,
and lists the unmodelled NIR parameters explicitly in
unmodelled_nir_lif_parameters. It adds no dependency and touches no hardware.
Classes¶
NeuromorphicIRGraph
dataclass
¶
NeuromorphicIRGraph(
nodes: tuple[dict[str, object], ...],
edges: tuple[dict[str, object], ...],
metadata: dict[str, object],
)
A deterministic NIR-structural graph of LIF nodes and weighted edges.
Attributes¶
sha256
property
¶
Return the SHA-256 hex digest of the canonical JSON encoding.
Returns¶
str The 64-character lowercase hexadecimal SHA-256 digest.
Methods:¶
to_record ¶
Return a deterministic, JSON-safe mapping of the graph.
Returns¶
dict[str, object]
The metadata / nodes / edges mapping, with the node and
edge tuples materialised as lists in insertion order.
Source code in src/scpn_phase_orchestrator/adapters/neuromorphic_ir_export.py
Functions:¶
to_nir_graph ¶
to_nir_graph(
populations: list[dict[str, object]],
projections: list[dict[str, object]],
*,
tau_membrane_ms: float,
tau_refractory_ms: float,
v_threshold_normalised: float = 1.0,
) -> NeuromorphicIRGraph
Compile schedule populations and projections into a NIR-structural graph.
Parameters¶
populations : list[dict[str, object]]
Per-population records from a neuromorphic schedule manifest; each must
carry a non-empty name and a non-negative estimated_rate_hz.
projections : list[dict[str, object]]
Inter-population projection records; each must carry non-empty
source / target node names and a non-negative weight.
tau_membrane_ms : float
LIF membrane time constant in milliseconds.
tau_refractory_ms : float
LIF refractory period in milliseconds.
v_threshold_normalised : float, optional
The normalised firing threshold of the Abbott-rate LIF (dimensionless;
not the NIR v_threshold in mV, which the model does not define).
Returns¶
NeuromorphicIRGraph
The deterministic NIR-structural graph. Every edge's source and
target is guaranteed to reference a declared node id.
Raises¶
ValueError If a record is malformed, or an edge references an undeclared node.
Source code in src/scpn_phase_orchestrator/adapters/neuromorphic_ir_export.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
Neurocore Bridge¶
NeurocoreBridge maps bounded UPDE layer coherence to stochastic LIF input
currents, accepts only non-negative deterministic seeds, and validates
real-valued non-negative rate vectors from action inputs or Rust backend output
before producing coupling actions.
neurocore_bridge ¶
Bridge between sc-neurocore stochastic neurons and phase-orchestrator.
sc-neurocore provides StochasticLIFNeuron, SCIzhikevichNeuron, and HomeostaticLIFNeuron with get_state()/step(current)/reset_state() API.
This bridge: 1. Maps UPDE layer R values to neuron input currents 2. Runs a LIF ensemble matching sc-neurocore dynamics 3. Converts spike rates back to orchestrator ControlActions
Backend priority
- Rust (spo_kernel.PyLIFEnsemble) — ~1000x faster than scalar Python
- NumPy vectorised — ~50-100x faster than scalar Python
- sc-neurocore scalar — per-neuron Python objects (validation only)
LIF parameters match sc-neurocore v3.13.3 defaults (Gerstner & Kistler 2002): v_rest=0, v_threshold=1, tau_mem=20ms, R=1, dt=1ms.
Install sc-neurocore: pip install sc-neurocore
Classes¶
NeurocoreBridge ¶
NeurocoreBridge(
n_layers: int,
neurons_per_layer: int = 8,
current_scale: float = 2.0,
spike_threshold_hz: float = 40.0,
noise_std: float = 0.0,
backend: str = "auto",
seed: int | None = None,
)
Live integration with sc-neurocore StochasticLIFNeuron ensemble.
Each layer in the UPDE state maps to a group of stochastic LIF neurons. Layer coherence R drives input current; spike rates above threshold generate coupling boost actions.
Backend selection (automatic):
- "rust" — Rust LIF via spo_kernel.PyLIFEnsemble (fastest)
- "numpy" — vectorised numpy LIF integration
- "scalar" — per-neuron sc-neurocore objects (requires sc-neurocore)
Pass backend="numpy" or backend="scalar" to force a specific
backend. Default: best available.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
Attributes¶
backend
property
¶
Active backend: 'rust', 'numpy', or 'scalar'.
Returns¶
str Active backend: 'rust', 'numpy', or 'scalar'.
Methods:¶
step ¶
Run neuron ensemble for n_substeps, return per-layer spike rates.
Parameters¶
state : UPDEState The current UPDE state. n_substeps : int Number of inner substeps to run.
Returns¶
FloatArray
The per-layer spike rates after n_substeps.
Raises¶
ValueError If the state or substep count is invalid.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
rates_to_actions ¶
Convert per-layer spike rates to coupling boost actions.
Parameters¶
rates : FloatArray Per-layer spike rates.
Returns¶
list[ControlAction] The coupling-boost control actions for the spike rates.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
step_and_act ¶
Step the ensemble and return control actions.
Parameters¶
state : UPDEState The current UPDE state. n_substeps : int Number of inner substeps to run.
Returns¶
list[ControlAction] The control actions after stepping the ensemble.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
get_neuron_states ¶
Return voltage/refractory state for all neurons.
Returns¶
list[dict[str, Any]] Return voltage/refractory state for all neurons.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
reset ¶
Reset all neurons and counters.
Source code in src/scpn_phase_orchestrator/adapters/neurocore_bridge.py
Observability¶
Adapters for production monitoring and tracing.
OpenTelemetry¶
Exports SPO metrics and traces to any OTLP-compatible backend
(Jaeger, Zipkin, Grafana Tempo). Requires opentelemetry-api.
OTelExporter API:
| Method | Signature | Description |
|---|---|---|
record_step |
(upde_state, step_idx) |
Record metrics for one engine step |
record_regime_change |
(old, new) |
Record regime transition event |
Metrics exported:
| Metric | Type | Description |
|---|---|---|
spo.order_parameter |
Gauge | Current R value |
spo.regime |
Gauge | Current regime (0-3) |
spo.step_latency_ms |
Histogram | Engine step duration |
spo.coupling_mean |
Gauge | Mean K_nm value |
opentelemetry ¶
Compatibility alias for scpn_phase_orchestrator.runtime.observability.
Prometheus¶
Fetches Prometheus instant and range metrics as a validated telemetry input boundary. The adapter rejects malformed decoded JSON, malformed result/sample structures, non-finite JSON constants, boolean/negative/non-real sample timestamps, and non-finite sample values before returning arrays or scalars.
PrometheusAdapter API:
| Method | Signature | Description |
|---|---|---|
fetch_metric |
(query, start, end, step) -> NDArray[np.float64] |
Fetch a range-vector metric as finite values |
fetch_instant |
(query) -> float |
Fetch one instant-vector scalar |
prometheus ¶
Prometheus HTTP adapter for validated instant and range metric queries.
PrometheusAdapter validates endpoint URLs, timeouts, query text, range
bounds, and step size before issuing standard Prometheus API requests. Network
failures are reported as ConnectionError and malformed responses as
ValueError. The adapter fetches metric values only; it does not run a server
or mutate orchestration state.
Classes¶
PrometheusAdapter ¶
Fetch time-series metrics from a Prometheus endpoint.
Source code in src/scpn_phase_orchestrator/adapters/prometheus.py
Methods:¶
fetch_metric ¶
Query Prometheus range API, return values as 1-D float array.
Raises ConnectionError on network failure, ValueError on bad response.
Parameters¶
query : str PromQL query string. start : float Range start time as a UNIX timestamp. end : float Range end time as a UNIX timestamp. step : float Sampling step in seconds.
Returns¶
FloatArray The range query values as a 1-D float array.
Raises¶
ConnectionError If the Prometheus server is unreachable. ValueError If the query or response is invalid.
Source code in src/scpn_phase_orchestrator/adapters/prometheus.py
fetch_instant ¶
Query Prometheus instant API, return scalar value.
Parameters¶
query : str PromQL query string.
Returns¶
float The instant query scalar value.
Raises¶
ConnectionError If the Prometheus server is unreachable. ValueError If the query or response is invalid.
Source code in src/scpn_phase_orchestrator/adapters/prometheus.py
Metrics Exporter¶
Lightweight metrics export helpers used by services that do not need the full OpenTelemetry adapter.
metrics_exporter ¶
Compatibility alias for scpn_phase_orchestrator.runtime.observability.
Redis Store¶
Optional Redis-backed state exchange for deployments that need shared runtime state outside the local process.
redis_store ¶
Redis-backed JSON state persistence adapter with explicit dependency checks.
RedisStateStore validates host, port, database, and key parameters before
using an injected client or constructing a Redis client when the optional
package is installed. Stored payloads must be JSON objects, and missing keys
return None. The adapter persists caller-provided state only; it does not
manage simulation lifecycle or background synchronization.
Classes¶
RedisStateStore ¶
RedisStateStore(
host: str = "localhost",
port: int = 6379,
db: int = 0,
key: str = "spo:sim_state",
client: Any = None,
password: str | None = None,
ssl: bool = True,
ssl_ca_certs: str | Path | None = None,
ssl_certfile: str | Path | None = None,
ssl_keyfile: str | Path | None = None,
)
Persist simulation state in Redis for survival across restarts.
When redis is not installed, all operations raise RuntimeError.
Source code in src/scpn_phase_orchestrator/adapters/redis_store.py
Attributes¶
key
property
¶
Methods:¶
save_state ¶
Serialise state dict to JSON and store in Redis.
Parameters¶
sim_state : dict[str, Any] The simulation state dict to store.
Raises¶
ValueError If the state dict is not JSON-serialisable.
Source code in src/scpn_phase_orchestrator/adapters/redis_store.py
load_state ¶
Load state from Redis. Returns None if key does not exist.
Returns¶
dict[str, Any] | None Load state from Redis. Returns None if key does not exist.
Raises¶
ValueError If the stored payload is malformed.
Source code in src/scpn_phase_orchestrator/adapters/redis_store.py
Functions:¶
Hardware Adapters¶
Modbus/TLS¶
Industrial control interface for power grids, HVAC, and manufacturing. Translates ControlAction to Modbus register writes over TLS.
modbus_tls ¶
Secure Modbus TCP adapter with mutual TLS.
Wraps pymodbus with an ssl.SSLContext for certificate-authenticated
connections to SCADA endpoints per IEC 62443 zone/conduit requirements.
Server certificate verification is always enabled: pass ca_cert_path for a
deployment CA bundle or rely on the operating-system trust store.
Classes¶
SecureModbusAdapter ¶
SecureModbusAdapter(
host: str,
port: int,
tls_cert_path: str | Path,
tls_key_path: str | Path,
ca_cert_path: str | Path | None = None,
)
Modbus TCP client with TLS mutual authentication.
Parameters¶
host : str Target Modbus device hostname or IP. port : int TCP port (default Modbus/TLS: 802). tls_cert_path : str | Path Path to client certificate (PEM). tls_key_path : str | Path Path to client private key (PEM). ca_cert_path : str | Path | None Optional CA bundle (PEM) for server verification. When omitted, the operating-system trust store is used. Server verification is never disabled by this adapter.
Source code in src/scpn_phase_orchestrator/adapters/modbus_tls.py
Methods:¶
read_register ¶
Read a single holding register.
Raises ConnectionError if the read fails or returns an error frame.
Parameters¶
address : int Modbus register address.
Returns¶
int The holding-register value.
Raises¶
ConnectionError If the read fails or the device returns a Modbus error frame.
Source code in src/scpn_phase_orchestrator/adapters/modbus_tls.py
write_register ¶
Write a single holding register.
Raises ConnectionError if the write fails.
Parameters¶
address : int Modbus register address. value : int Register value to write.
Raises¶
ConnectionError If the write fails or the device returns a Modbus error frame.
Source code in src/scpn_phase_orchestrator/adapters/modbus_tls.py
validate_connection ¶
Return True if the TLS-wrapped Modbus connection is active.
Returns¶
bool Return True if the TLS-wrapped Modbus connection is active.
Source code in src/scpn_phase_orchestrator/adapters/modbus_tls.py
close ¶
Close the Modbus/TLS client when the pymodbus client exposes close().
__enter__ ¶
__exit__ ¶
Functions:¶
OPC-UA SCADA Bridge¶
Read-only ingestion from OPC-UA servers for Industry 4.0 SCADA/DCS systems.
OpcUaTag and OpcUaBridgeConfig validate the endpoint and tag mapping;
OpcUaPhaseBridge.extract_phases turns decoded process-tag sample series
(temperatures, pressures, flow rates) into physical-channel phase states with
the tag's declared waveform extractor (hilbert/physical, wavelet, or
zero_crossing), with no network dependency. Live reads use the optional
asyncua dependency (opcua extra): collect_live connects, reads
samples_per_tag values per tag, and disconnects; read_live reads from an
already-connected client. The bridge never writes to the server.
from scpn_phase_orchestrator.adapters import OpcUaTag, OpcUaPhaseBridge
bridge = OpcUaPhaseBridge.from_tags(
"opc.tcp://plc.local:4840/scada",
[
OpcUaTag(
node_id="ns=2;s=Reactor.Temp",
name="reactor_temp",
sample_rate_hz=10.0,
extractor_type="wavelet",
)
],
)
samples = await bridge.collect_live(samples_per_tag=128)
phases = bridge.extract_phases(samples)
opcua_bridge ¶
OPC-UA bridge for industrial SCADA/DCS phase extraction.
Reads oscillator-relevant process tags (temperatures, pressures, flow rates) from an OPC-UA server and maps each tag's sampled waveform to a physical-channel phase state via the tag's declared waveform extractor. Tags default to Hilbert extraction and can select the wavelet-ridge or zero-crossing extractor where those algorithms match the measured signal.
The bridge separates three concerns so the bulk is testable without a server:
- Configuration — :class:
OpcUaTagand :class:OpcUaBridgeConfigvalidate the endpoint URL, tag declarations, and security posture eagerly. - Phase extraction — :meth:
OpcUaPhaseBridge.extract_phasesturns decoded tag sample series into per-tag :class:PhaseStateobjects with no network orasyncuadependency, and :meth:OpcUaPhaseBridge.collect_samplespolls an injected synchronous reader callable. - Live read — :meth:
OpcUaPhaseBridge.read_liveand :meth:OpcUaPhaseBridge.collect_liveuseasyncua(optional dependency,opcuaextra) to read node values from a connected client.
The bridge never writes to the OPC-UA server; it is a read-only ingestion path.
Classes¶
OpcUaTag
dataclass
¶
OpcUaTag(
node_id: str,
name: str,
channel: str = "P",
scale: float = 1.0,
offset: float = 0.0,
sample_rate_hz: float = 1.0,
extractor_type: str = "hilbert",
)
Declares how one OPC-UA node maps to a physical oscillator.
Attributes¶
node_id : str
OPC-UA node identifier (e.g. "ns=2;i=4" or "ns=2;s=Reactor.Temp").
name : str
Oscillator name the extracted phase state is bound to.
channel : str
SPO channel label; one of "P", "R", "E", "S" (default
"P" for the physical channel).
scale, offset : float
Affine calibration applied to each raw sample as scale * x + offset.
sample_rate_hz : float
Sampling rate of the tag waveform in hertz, used by waveform phase
extraction.
extractor_type : str
Waveform extractor type or channel alias. "physical" resolves to
"hilbert"; "wavelet" and "zero_crossing" select the
corresponding physical-channel algorithms.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the tag.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the tag fields.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
OpcUaBridgeConfig
dataclass
¶
OpcUaBridgeConfig(
endpoint_url: str,
tags: tuple[OpcUaTag, ...],
security_policy: str = "None",
security_mode: str = "None",
request_timeout_s: float = 4.0,
)
Validated OPC-UA connection and tag-mapping configuration.
Attributes¶
endpoint_url : str
OPC-UA endpoint, must use the opc.tcp:// scheme.
tags : tuple[OpcUaTag, ...]
The tags to read; node identifiers and oscillator names must be unique.
security_policy : str
OPC-UA security policy (default "None").
security_mode : str
Message security mode (default "None").
request_timeout_s : float
Per-request timeout in seconds for the live client.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the configuration.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the configuration fields.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
OpcUaPhaseBridge
dataclass
¶
Read-only OPC-UA tag ingestion mapped to physical phase states.
Attributes¶
config : OpcUaBridgeConfig The validated bridge configuration.
Methods:¶
from_tags
classmethod
¶
from_tags(
endpoint_url: str,
tags: Sequence[OpcUaTag],
**config_kwargs: object,
) -> OpcUaPhaseBridge
Build a bridge from an endpoint and a tag sequence.
Parameters¶
endpoint_url : str
OPC-UA endpoint (opc.tcp:// scheme).
tags : Sequence[OpcUaTag]
The tags to read.
**config_kwargs : object
Forwarded to :class:OpcUaBridgeConfig (security and timeout).
Returns¶
OpcUaPhaseBridge A configured bridge.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
extract_phases ¶
Map decoded tag sample series to per-tag physical phase states.
Parameters¶
tag_samples : Mapping[str, Sequence[float]] Sample series keyed by tag name; every declared tag must be present with at least one finite sample.
Returns¶
dict[str, PhaseState] The latest instantaneous phase state per tag, keyed by tag name.
Raises¶
ValueError If a declared tag is missing, a series is empty, or a sample is not a finite real value.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
collect_samples ¶
collect_samples(
reader: Callable[[str], float], *, samples_per_tag: int
) -> dict[str, list[float]]
Poll a synchronous reader callable into per-tag sample series.
Parameters¶
reader : Callable[[str], float]
Returns the current value for a node identifier; called
samples_per_tag times per tag in declaration order.
samples_per_tag : int
Number of samples to collect per tag.
Returns¶
dict[str, list[float]] Sample series keyed by tag name.
Raises¶
ValueError
If samples_per_tag is not a positive integer or a read value is
not a finite real number.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
connect ¶
Create a (not yet connected) asyncua client for the endpoint.
Returns¶
asyncua.Client A client configured for the endpoint and request timeout. Use it as an async context manager to open and close the connection.
Raises¶
RuntimeError
If the optional asyncua dependency is not installed.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
read_live
async
¶
read_live(
client: Any,
*,
samples_per_tag: int,
interval_s: float = 0.0,
) -> dict[str, list[float]]
Read node values from a connected asyncua client.
Parameters¶
client : asyncua.Client
A connected client (or any object exposing get_node(node_id)
with an awaitable read_value).
samples_per_tag : int
Number of samples to read per tag.
interval_s : float, optional
Delay in seconds between sampling rounds (default 0).
Returns¶
dict[str, list[float]] Sample series keyed by tag name.
Raises¶
ValueError
If samples_per_tag is not positive, interval_s is negative, or
a read value is not a finite real number.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
collect_live
async
¶
Connect, read samples_per_tag samples per tag, and disconnect.
Parameters¶
samples_per_tag : int
Number of samples to read per tag.
interval_s : float, optional
Delay in seconds between sampling rounds (default 0).
Returns¶
dict[str, list[float]] Sample series keyed by tag name.
Raises¶
RuntimeError
If the optional asyncua dependency is not installed.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
to_audit_record ¶
Return a JSON-safe audit mapping of the bridge configuration.
Returns¶
dict[str, object]
Deterministic, JSON-safe mapping with the configuration and the
asyncua availability flag.
Source code in src/scpn_phase_orchestrator/adapters/opcua_bridge.py
Functions:¶
MQTT Edge Bridge¶
Read-only ingestion from MQTT brokers for edge/IoT sensor fleets. MqttTag and
MqttBridgeConfig validate the broker endpoint and topic mapping;
decode_payload parses raw or JSON payloads, ingest_messages folds a batch of
received (topic, payload) messages into per-tag sample series, and
extract_phases turns those series into physical-channel phase states with the
tag's declared waveform extractor (hilbert/physical, wavelet, or
zero_crossing) — all with no network dependency. collect_live subscribes via
the optional paho-mqtt dependency (mqtt extra) and accumulates messages; it
accepts an injected client for testing. The bridge never publishes to the broker.
from scpn_phase_orchestrator.adapters import MqttTag, MqttPhaseBridge
bridge = MqttPhaseBridge.from_tags(
"broker.local",
[
MqttTag(
topic="plant/reactor/temp",
name="reactor_temp",
sample_rate_hz=10.0,
extractor_type="zero_crossing",
)
],
)
samples = bridge.collect_live(samples_per_tag=128)
phases = bridge.extract_phases(samples)
mqtt_bridge ¶
MQTT bridge for edge/IoT sensor phase extraction.
Subscribes to MQTT topics carrying oscillator-relevant process measurements and maps each topic's sampled waveform to a physical-channel phase state via the tag's declared waveform extractor. Tags default to Hilbert extraction and can select the wavelet-ridge or zero-crossing extractor where those algorithms match the measured signal.
Like the OPC-UA bridge, the bulk is testable without a broker:
- Configuration — :class:
MqttTagand :class:MqttBridgeConfigvalidate the broker endpoint and topic mapping eagerly. - Decoding and ingestion — :meth:
MqttPhaseBridge.decode_payloadparses raw or JSON payloads, :meth:MqttPhaseBridge.ingest_messagesfolds a batch of(topic, payload)messages into per-tag sample series, and :meth:MqttPhaseBridge.extract_phasesturns those series into :class:PhaseStateobjects — all with no network orpaho-mqttdependency. - Live subscribe — :meth:
MqttPhaseBridge.collect_liveusespaho-mqtt(optional dependency,mqttextra) to subscribe and accumulate messages.
The bridge is read-only: it never publishes to the broker.
Classes¶
MqttTag
dataclass
¶
MqttTag(
topic: str,
name: str,
channel: str = "P",
scale: float = 1.0,
offset: float = 0.0,
sample_rate_hz: float = 1.0,
extractor_type: str = "hilbert",
payload_format: str = "raw",
)
Declares how one MQTT topic maps to a physical oscillator.
Attributes¶
topic : str
MQTT topic the sensor publishes to.
name : str
Oscillator name the extracted phase state is bound to.
channel : str
SPO channel label; one of "P", "R", "E", "S".
scale, offset : float
Affine calibration applied to each decoded value as scale * x + offset.
sample_rate_hz : float
Publish rate of the topic in hertz, used by waveform phase extraction.
extractor_type : str
Waveform extractor type or channel alias. "physical" resolves to
"hilbert"; "wavelet" and "zero_crossing" select the
corresponding physical-channel algorithms.
payload_format : str
"raw" (a decimal number as text) or "json" (a JSON number or a
JSON object with a "value" field).
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the tag.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the tag fields.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
MqttBridgeConfig
dataclass
¶
MqttBridgeConfig(
broker_host: str,
tags: tuple[MqttTag, ...],
broker_port: int = 1883,
keepalive_s: int = 60,
client_id: str = "spo-mqtt-bridge",
use_tls: bool = False,
)
Validated MQTT connection and topic-mapping configuration.
Attributes¶
broker_host : str
MQTT broker hostname or address.
tags : tuple[MqttTag, ...]
Topics to subscribe to; topics and oscillator names must be unique.
broker_port : int
Broker TCP port (default 1883).
keepalive_s : int
Keep-alive interval in seconds for the live client.
client_id : str
MQTT client identifier.
use_tls : bool
Whether the live client should negotiate TLS.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the configuration.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the configuration fields.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
MqttPhaseBridge
dataclass
¶
Read-only MQTT topic ingestion mapped to physical phase states.
Attributes¶
config : MqttBridgeConfig The validated bridge configuration.
Methods:¶
from_tags
classmethod
¶
Build a bridge from a broker host and a tag sequence.
Parameters¶
broker_host : str
MQTT broker hostname or address.
tags : Sequence[MqttTag]
The topics to subscribe to.
**config_kwargs : object
Forwarded to :class:MqttBridgeConfig.
Returns¶
MqttPhaseBridge A configured bridge.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
decode_payload ¶
Decode one MQTT payload into a finite calibrated sample value.
Parameters¶
tag : MqttTag
The tag whose payload_format and calibration apply.
payload : bytes or str
The raw message payload.
Returns¶
float
The decoded scale * value + offset sample.
Raises¶
ValueError If the payload cannot be decoded to a finite real value.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
ingest_messages ¶
Fold a batch of (topic, payload) messages into per-tag series.
Messages on unknown topics are ignored. Decoded values are appended in arrival order to the series of the tag matching their topic.
Parameters¶
messages : Sequence[tuple[str, bytes | str]] The received messages.
Returns¶
dict[str, list[float]] Calibrated sample series keyed by tag name (empty list per tag with no matching messages).
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
extract_phases ¶
Map per-tag sample series to physical phase states.
Parameters¶
topic_samples : Mapping[str, Sequence[float]] Calibrated sample series keyed by tag name; every declared tag must be present with at least one finite sample.
Returns¶
dict[str, PhaseState] The latest instantaneous phase state per tag, keyed by tag name.
Raises¶
ValueError If a declared tag is missing, a series is empty, or a sample is not a finite real value.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
connect ¶
Create a configured (not yet connected) paho-mqtt client.
Returns¶
paho.mqtt.client.Client A client bound to the configured client id and TLS posture.
Raises¶
RuntimeError
If the optional paho-mqtt dependency is not installed.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
collect_live ¶
collect_live(
*,
samples_per_tag: int,
timeout_s: float = 10.0,
client: Any = None,
) -> dict[str, list[float]]
Subscribe to the configured topics and accumulate samples.
Parameters¶
samples_per_tag : int
Target number of samples to collect for every tag before returning.
timeout_s : float, optional
Maximum seconds to wait for the target to be reached (default 10).
client : paho.mqtt.client.Client, optional
An existing client; a new one is created via :meth:connect when
omitted.
Returns¶
dict[str, list[float]]
Sample series keyed by tag name (each truncated to at most
samples_per_tag).
Raises¶
ValueError
If samples_per_tag is not positive or timeout_s is not finite
and positive.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
to_audit_record ¶
Return a JSON-safe audit mapping of the bridge configuration.
Returns¶
dict[str, object]
Deterministic, JSON-safe mapping with the configuration and the
paho-mqtt availability flag.
Source code in src/scpn_phase_orchestrator/adapters/mqtt_bridge.py
Functions:¶
IEEE C37.118.2 Synchrophasor Codec¶
Dependency-free decoder for IEEE C37.118.2-2011 synchrophasor CONFIG-2 and DATA
frames from raw bytes (no network I/O). SynchrophasorFrameCodec.decode_config2
recovers each PMU's measurement layout (FORMAT flags, phasor/analog/digital
counts, nominal frequency); decode_data then decodes the phasor, frequency,
and analog/digital measurements, interpreting FREQ as a deviation from nominal
(millihertz when integer, hertz when float). Every frame is CRC-CCITT validated
before its body is read, and malformed input raises a typed
SynchrophasorFrameError subclass rather than returning partial data. The byte
layout and CRC parameters were cross-checked against two independent open-source
implementations (iicsys/pypmu and marsolla/Open-C37.118).
data_frames_to_frequency_series assembles a (time_s, frequency_hz) series in
the exact layout the PMU ringdown screener consumes, so a decoded stream feeds
directly into hash-sealed ringdown evidence. The live-socket ingestion path is
C37118SessionClient (pure-standard-library asyncio, no optional extra
required); this codec handles only bytes already read.
from scpn_phase_orchestrator.adapters import (
SynchrophasorFrameCodec,
data_frames_to_frequency_series,
)
codec = SynchrophasorFrameCodec()
config = codec.decode_config2(config2_bytes)
frames = tuple(codec.decode_data(data_bytes, config) for data_bytes in stream)
time_s, frequency_hz = data_frames_to_frequency_series(config, frames, pmu_index=0)
synchrophasor_c37118 ¶
Pure decoder for IEEE C37.118.2-2011 synchrophasor CONFIG-2 and DATA frames.
This module decodes the binary framing of the IEEE synchrophasor data-transfer
protocol without any network I/O: given the raw bytes of a CONFIG-2 frame it
recovers the per-PMU measurement layout, and given the bytes of a DATA frame
plus that configuration it recovers the phasor, frequency, and analog/digital
measurements. Every frame is checksum-validated (CRC-CCITT) before its body is
read, and malformed input raises a typed :class:SynchrophasorFrameError
subclass rather than returning partial data.
The byte layout, CRC parameters, and field semantics were cross-checked against
two independent open-source implementations of the standard: the pypmu
Python library (iicsys/pypmu, synchrophasor/frame.py) and the C++
Open-C37.118 library (marsolla/Open-C37.118, src/c37118*.{h,cpp}).
Both agree on the 14-byte common header (SYNC FRAMESIZE IDCODE
SOC FRACSEC, big-endian), the FORMAT-word field sizes, and the
CRC-CCITT checksum (polynomial 0x1021, initial value 0xFFFF, no final
mask, computed over every byte except the trailing two). The FREQ field is a
deviation from the PMU nominal frequency: a signed 16-bit integer in millihertz
when the FORMAT freq bit is clear, or a 32-bit float in hertz when it is
set. The live-socket ingestion path is
:class:~scpn_phase_orchestrator.adapters.synchrophasor_client.C37118SessionClient
(pure-standard-library asyncio, no optional extra required); this module
deliberately handles only bytes already read.
Classes¶
SynchrophasorFrameError ¶
Bases: ValueError
Base class for all synchrophasor frame decoding failures.
FrameTruncationError ¶
Bases: SynchrophasorFrameError
Raised when a frame is shorter than its declared or required length.
FrameChecksumError ¶
Bases: SynchrophasorFrameError
Raised when the trailing CRC-CCITT checksum does not match the body.
UnsupportedFrameError ¶
Bases: SynchrophasorFrameError
Raised for a frame whose SYNC/type is not the expected decodable kind.
SynchrophasorHeader
dataclass
¶
SynchrophasorHeader(
frame_type: int,
version: int,
framesize: int,
id_code: int,
soc: int,
fracsec_raw: int,
)
Decoded 14-byte common header shared by every synchrophasor frame.
Attributes¶
frame_type : int
Frame-type code from SYNC byte 2 (bits 6-4); e.g.
:data:FRAME_TYPE_DATA or :data:FRAME_TYPE_CONFIG2.
version : int
Protocol version number from SYNC byte 2 (bits 3-0).
framesize : int
Declared total frame size in bytes, including SYNC and CRC.
id_code : int
Data-stream / PMU identification code.
soc : int
Second-of-century timestamp (UNIX seconds).
fracsec_raw : int
Raw 32-bit FRACSEC word (time-quality byte plus fraction count).
Attributes¶
fraction_count
property
¶
Return the raw fraction-of-second count (lower 24 bits of FRACSEC).
message_time_quality
property
¶
Return the 4-bit message time-quality code from the FRACSEC top byte.
leap_second_pending
property
¶
Return whether the leap-second-pending flag is set.
leap_second_occurred
property
¶
Return whether the leap-second-occurred flag is set.
leap_second_direction
property
¶
Return the leap-second direction (- if flagged, else +).
Methods:¶
seconds_of_second ¶
Return the fractional-second offset as a float given time_base.
Parameters¶
time_base : int
The CONFIG-2 TIME_BASE resolution of the fractional timestamp.
Returns¶
float
The fraction of a second, fraction_count / time_base.
Raises¶
SynchrophasorFrameError
If time_base is not a positive integer.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
to_audit_record ¶
Return a JSON-safe audit mapping of the header fields.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the header fields.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
PhasorUnit
dataclass
¶
Conversion factor for one phasor channel (a decoded PHUNIT word).
Attributes¶
is_current : bool
True if the channel is a current phasor, False for voltage
(PHUNIT most-significant byte).
scale : int
Unsigned 24-bit scale factor in 10**-5 volts or amperes per bit,
used to convert 16-bit integer phasor components to engineering units.
Ignored for floating-point phasors, which are already in engineering
units.
Attributes¶
volts_or_amperes_per_bit
property
¶
Return the engineering-unit scale per integer bit (scale * 1e-5).
Methods:¶
PmuConfiguration
dataclass
¶
PmuConfiguration(
station_name: str,
id_code: int,
phasor_polar: bool,
phasor_float: bool,
analog_float: bool,
freq_float: bool,
phasor_count: int,
analog_count: int,
digital_word_count: int,
channel_names: tuple[str, ...],
nominal_frequency_hz: float,
phasor_units: tuple[PhasorUnit, ...] = (),
)
Per-PMU measurement layout decoded from a CONFIG-2 frame.
Attributes¶
station_name : str
Human-readable station name (trimmed of NUL/space padding).
id_code : int
PMU identification code.
phasor_polar : bool
True if phasors are polar (magnitude, angle); False if rectangular.
phasor_float : bool
True if phasors use 32-bit floats; False if 16-bit integers.
analog_float : bool
True if analog values use 32-bit floats; False if 16-bit integers.
freq_float : bool
True if FREQ/DFREQ use 32-bit floats (hertz); False if 16-bit
integers (millihertz deviation).
phasor_count, analog_count, digital_word_count : int
PHNMR, ANNMR, and DGNMR counts respectively.
channel_names : tuple[str, ...]
Phasor, analog, and digital channel labels in declared order.
nominal_frequency_hz : float
Nominal line frequency (50.0 or 60.0 Hz) from the FNOM word.
phasor_units : tuple[PhasorUnit, ...]
Per-phasor conversion factors (PHUNIT), one per phasor channel.
Attributes¶
freq_size
property
¶
Return the byte size of the FREQ/DFREQ field (4 if float, else 2).
analog_size
property
¶
Return the byte size of one analog value (4 if float, else 2).
data_block_size
property
¶
Return the byte size of this PMU's block within a DATA frame.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the PMU configuration.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the PMU configuration fields.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
ConfigurationFrame2
dataclass
¶
ConfigurationFrame2(
header: SynchrophasorHeader,
time_base: int,
pmus: tuple[PmuConfiguration, ...],
data_rate: int,
)
Decoded CONFIG-2 frame describing every PMU in the data stream.
Attributes¶
header : SynchrophasorHeader The decoded common header. time_base : int Resolution of the fractional-second timestamp (TIME_BASE). pmus : tuple[PmuConfiguration, ...] Per-PMU measurement layouts in declared order. data_rate : int Reporting rate: frames per second if positive, seconds per frame if negative.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the configuration frame.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the configuration frame.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
PmuMeasurement
dataclass
¶
PmuMeasurement(
stat: int,
phasors: tuple[tuple[float, float], ...],
frequency_hz: float,
frequency_deviation: float,
df_dt: float,
analogs: tuple[float, ...],
digitals: tuple[int, ...],
)
One PMU's measurements decoded from a DATA frame block.
Attributes¶
stat : int
16-bit STAT flag word.
phasors : tuple[tuple[float, float], ...]
Phasor components in the frame's native representation: rectangular
(real, imag) or polar (magnitude, angle) per the PMU's FORMAT.
frequency_hz : float
Absolute frequency in hertz (nominal plus the decoded deviation).
frequency_deviation : float
Raw FREQ deviation from nominal (millihertz if integer, hertz if float).
df_dt : float
Rate-of-change of frequency (DFREQ) in the frame's native units.
analogs : tuple[float, ...]
Analog channel values.
digitals : tuple[int, ...]
Digital status words.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the PMU measurement.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the PMU measurement.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
DataFrame
dataclass
¶
Decoded DATA frame carrying one measurement per configured PMU.
Attributes¶
header : SynchrophasorHeader The decoded common header. measurements : tuple[PmuMeasurement, ...] Per-PMU measurements aligned with the configuration's PMU order.
Methods:¶
to_audit_record ¶
Return a JSON-safe audit mapping of the data frame.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the data frame.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
SynchrophasorFrameCodec ¶
Stateless decoder for IEEE C37.118.2-2011 CONFIG-2 and DATA frames.
The codec performs no network I/O: each method accepts the raw bytes of a
single frame, validates its SYNC word, declared size, and CRC-CCITT
checksum, and returns a fully decoded, immutable frame object. Any structural
fault raises a :class:SynchrophasorFrameError subclass; the codec never
returns partially decoded data.
Methods:¶
decode_config2 ¶
Decode a CONFIG-2 frame into its per-PMU measurement layout.
Parameters¶
frame : bytes The complete CONFIG-2 frame, including SYNC and trailing CRC.
Returns¶
ConfigurationFrame2 The decoded configuration.
Raises¶
SynchrophasorFrameError If the frame is truncated, has the wrong SYNC/type, or fails CRC.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
decode_data ¶
Decode a DATA frame using a previously decoded CONFIG-2 layout.
Parameters¶
frame : bytes The complete DATA frame, including SYNC and trailing CRC. config : ConfigurationFrame2 The configuration describing each PMU's measurement layout.
Returns¶
DataFrame The decoded measurements, one block per configured PMU.
Raises¶
SynchrophasorFrameError If the frame is truncated, has the wrong SYNC/type, or fails CRC.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
Functions:¶
compute_crc_ccitt ¶
Compute the IEEE C37.118.2 CRC-CCITT checksum of a byte string.
The checksum uses the generating polynomial 0x1021
(X^16 + X^12 + X^5 + 1), an initial register value of 0xFFFF, and no
final mask, processing each byte most-significant-bit first. This matches the
checksum both reference implementations apply over every frame byte except
the trailing two CRC bytes.
Parameters¶
data : bytes The bytes to checksum (a full frame excluding its trailing CRC field).
Returns¶
int The 16-bit CRC-CCITT value.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
data_frames_to_frequency_series ¶
data_frames_to_frequency_series(
config: ConfigurationFrame2,
frames: tuple[DataFrame, ...],
*,
pmu_index: int = 0,
) -> tuple[tuple[float, ...], tuple[float, ...]]
Assemble a (time_s, frequency_hz) series for one PMU across frames.
The time vector is relative to the first frame, combining the second-of-
century count and the fractional-second offset resolved against the
configuration's TIME_BASE; the frequency vector reports each frame's
absolute frequency for the selected PMU. The result mirrors the two-column
time_s,frequency_hz layout consumed by the PMU ringdown screener, so a
decoded synchrophasor stream feeds directly into ringdown evidence.
Parameters¶
config : ConfigurationFrame2
The configuration whose TIME_BASE and PMU order the frames follow.
frames : tuple[DataFrame, ...]
The DATA frames in acquisition order.
pmu_index : int, optional
Index of the PMU whose frequency series is extracted (default 0).
Returns¶
tuple[tuple[float, ...], tuple[float, ...]] The relative-time vector in seconds and the frequency vector in hertz.
Raises¶
SynchrophasorFrameError
If frames is empty or pmu_index is out of range for a frame.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_c37118.py
IEEE C37.118.2 Phase Bridge¶
Review-only bridge mapping decoded PMU phasors to oscillator PhaseStates. A PMU
phasor is already phase-resolved, so — unlike the OPC-UA/MQTT waveform bridges —
C37118PhaseBridge reads the phase directly instead of running a waveform
extractor: theta is the phasor angle (rectangular atan2(imag, real), which is
scale-independent, or a floating-point polar angle in radians), omega is
2*pi times the frame's measured frequency, amplitude is the phasor magnitude
in engineering units (integer components scaled by the PHUNIT 10**-5 V/A
factor), and quality derives from the STAT data-error and time-sync bits.
Integer polar phasors raise rather than emit a fabricated angle (the standard
and the reference implementations disagree on the integer polar angle scale). The
bridge never actuates (non_actuating / execution_disabled).
from scpn_phase_orchestrator.adapters import (
C37118PhaseBridge,
PhasorBinding,
SynchrophasorFrameCodec,
)
codec = SynchrophasorFrameCodec()
config = codec.decode_config2(config2_bytes)
frames = [codec.decode_data(data_bytes, config) for data_bytes in stream]
bridge = C37118PhaseBridge.from_bindings([PhasorBinding("bus1_va", phasor_index=0)])
phases = bridge.extract_phases(config, frames)
synchrophasor_phase_bridge ¶
Map decoded IEEE C37.118.2 PMU phasor measurements to oscillator phase states.
A phasor measurement unit already reports a phase-resolved quantity: each voltage or current phasor carries a magnitude and an angle, and the frame carries the measured line frequency. Unlike the scalar SCADA tags of the OPC-UA bridge — a raw waveform from which a phase must be extracted — a PMU phasor's angle is the instantaneous phase, so this bridge reads it directly rather than running a Hilbert or zero-crossing extractor:
thetais the phasor angle, canonicalised to[0, 2*pi). For a rectangular phasor it isatan2(imag, real)(scale-independent, so the PHUNIT conversion factor never enters the angle); for a floating-point polar phasor it is the reported angle in radians.omegais the instantaneous angular frequency,2*pitimes the frame's absolute measured frequency in hertz.amplitudeis the phasor magnitude in engineering units — integer components are scaled by the PHUNIT10**-5V/A-per-bit factor; float components are already in engineering units.qualityis derived only from the STAT word's verified data-error field (bits 15-14) and time-sync bit (bit 13).
Integer polar phasors are an honest boundary: the standard scales an integer polar angle differently from the magnitude, and the open-source references disagree on that scaling, so rather than guess an angle unit this bridge raises for integer polar phasors instead of emitting a fabricated angle. The bridge is review-only: it produces phase states for observation and never actuates.
Classes¶
PhasorBinding
dataclass
¶
Bind one PMU phasor channel to an SPO oscillator.
Attributes¶
oscillator : str
Oscillator name the phasor's phase state is bound to.
pmu_index : int
Index of the PMU within the configuration/data frame (default 0).
phasor_index : int
Index of the phasor channel within the PMU block (default 0).
C37118PhaseBridge
dataclass
¶
Review-only bridge from decoded PMU phasors to oscillator phase states.
Attributes¶
bindings : tuple[PhasorBinding, ...]
The phasor-to-oscillator bindings; oscillator names must be unique.
non_actuating : bool
Always True — the bridge observes and never drives hardware.
execution_disabled : bool
Always True — no control action is emitted from this bridge.
Methods:¶
from_bindings
classmethod
¶
Build a bridge from a sequence of phasor bindings.
Parameters¶
bindings : Sequence[PhasorBinding] The phasor-to-oscillator bindings.
Returns¶
C37118PhaseBridge A configured, review-only bridge.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_phase_bridge.py
extract_phases ¶
Map the most recent frame's phasors to per-oscillator phase states.
Parameters¶
config : ConfigurationFrame2 The configuration describing each PMU's measurement layout. frames : Sequence[DataFrame] The decoded DATA frames; the last frame provides the current state.
Returns¶
dict[str, PhaseState] The latest instantaneous phase state per bound oscillator.
Raises¶
ValueError
If frames is empty, a binding's PMU index is out of range, or a
bound phasor cannot be interpreted (integer polar angle).
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_phase_bridge.py
to_audit_record ¶
Return a JSON-safe audit mapping of the bridge.
Returns¶
dict[str, object] Deterministic, JSON-safe mapping of the bridge configuration and its review-only posture.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_phase_bridge.py
Functions:¶
IEEE C37.118.2 Live Session Client¶
C37118SessionClient reads synchrophasor frames from a PDC/PMU over a TCP stream
using only the standard library's asyncio (no third-party dependency). It
issues the standard C37.118.2 command frames — request CONFIG-2, turn data on,
turn data off — which are a benign protocol handshake that controls only the
measurement data stream; the client writes no device setpoints and cannot
actuate grid equipment (non_actuating). build_command_frame constructs a
CRC-sealed COMMAND frame and read_frame reassembles one frame from the stream
via its SYNC/FRAMESIZE prefix. The command-word values were verified at source
against the pypmu CommandFrame table and the Wireshark synchrophasor dissector
(which cites the standard's Table 15). Decoding is delegated to
SynchrophasorFrameCodec.
from scpn_phase_orchestrator.adapters import C37118SessionClient
client = C37118SessionClient(id_code=7)
reader, writer = await client.open_connection("pdc.local", 4712)
try:
config = await client.request_configuration(reader, writer)
frames = await client.collect_data_frames(reader, writer, config, count=30)
finally:
writer.close()
synchrophasor_client ¶
Live asynchronous IEEE C37.118.2 synchrophasor session client.
Reads synchrophasor frames from a phasor data concentrator (PDC) or PMU over a
TCP stream using only the standard library's :mod:asyncio — no third-party
dependency. The client issues the standard C37.118.2 command frames to drive the
data stream: it requests the CONFIG-2 frame, turns data transmission on, reads
the requested number of DATA frames, and turns transmission off again. These
command frames are a benign protocol handshake that controls only the
measurement data stream; the client never writes device setpoints and cannot
actuate grid equipment (non_actuating).
The command-word values were verified at source against two independent
references: the iicsys/pypmu CommandFrame table and the Wireshark
synchrophasor dissector (epan/dissectors/packet-synphasor.c, which cites the
standard's Table 15): 0x0001 data-off, 0x0002 data-on, 0x0003 send
HDR, 0x0004 send CONFIG-1, 0x0005 send CONFIG-2, 0x0006 send
CONFIG-3. A command frame is the 14-byte common header plus a 2-byte command
word plus the CRC (18 bytes total). Frames are reassembled from the stream using
the SYNC/FRAMESIZE prefix, and decoding is delegated to
:class:~scpn_phase_orchestrator.adapters.synchrophasor_c37118.SynchrophasorFrameCodec.
Classes¶
C37118SessionClient
dataclass
¶
Review-only async client that drives a C37.118.2 measurement stream.
Attributes¶
id_code : int
The destination data-stream identification code (0..65535).
non_actuating : bool
Always True — the client issues only stream-control command frames
and never writes device setpoints.
Methods:¶
request_configuration
async
¶
Request and decode the CONFIG-2 frame from the stream.
Parameters¶
reader : asyncio.StreamReader The stream to read frames from. writer : asyncio.StreamWriter The stream to write the command frame to.
Returns¶
ConfigurationFrame2 The decoded configuration.
Raises¶
FrameTruncationError If the stream ends before a CONFIG-2 frame arrives.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_client.py
collect_data_frames
async
¶
collect_data_frames(
reader: StreamReader,
writer: StreamWriter,
config: ConfigurationFrame2,
*,
count: int,
) -> list[DataFrame]
Turn data on, collect count DATA frames, then turn data off.
Parameters¶
reader : asyncio.StreamReader The stream to read frames from. writer : asyncio.StreamWriter The stream to write command frames to. config : ConfigurationFrame2 The configuration used to decode DATA frames. count : int Number of DATA frames to collect; must be a positive integer.
Returns¶
list[DataFrame] The decoded DATA frames in arrival order.
Raises¶
ValueError
If count is not a positive integer.
FrameTruncationError
If the stream ends before count DATA frames arrive.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_client.py
open_connection
async
¶
Open a TCP connection to a PDC/PMU endpoint.
Parameters¶
host : str Hostname or address of the concentrator/PMU. port : int TCP port of the C37.118.2 data stream.
Returns¶
tuple[asyncio.StreamReader, asyncio.StreamWriter] The connected stream reader and writer.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_client.py
Functions:¶
build_command_frame ¶
build_command_frame(
id_code: int,
command: int,
*,
soc: int = 0,
fracsec: int = 0,
version: int = 1,
) -> bytes
Build a CRC-sealed IEEE C37.118.2 COMMAND frame.
Parameters¶
id_code : int
Destination data-stream identification code (0..65535).
command : int
Command word; one of the COMMAND_* constants.
soc : int
Second-of-century timestamp for the command (default 0).
fracsec : int
Fraction-of-second word for the command (default 0).
version : int
Protocol version number placed in the SYNC word (default 1).
Returns¶
bytes The complete COMMAND frame including SYNC and trailing CRC.
Raises¶
ValueError
If id_code is out of range or command is not a known command.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_client.py
read_frame
async
¶
Read one complete synchrophasor frame from an async stream.
The frame is reassembled by reading the 4-byte SYNC/FRAMESIZE prefix,
validating the SYNC lead, and reading exactly FRAMESIZE bytes in total.
Parameters¶
reader : asyncio.StreamReader The stream to read from.
Returns¶
bytes The complete frame, including SYNC and trailing CRC.
Raises¶
FrameTruncationError
If the stream ends before a full frame, or the declared frame size is
smaller than the minimum header-plus-CRC length.
UnsupportedFrameError
If the frame does not begin with the SYNC lead byte 0xAA.
Source code in src/scpn_phase_orchestrator/adapters/synchrophasor_client.py
Hardware I/O¶
Generic hardware I/O abstraction for digital/analogue outputs.
Hardware I/O sample buffers and simulated-board frequency configuration accept only finite real sensor amplitudes and finite positive real frequencies; boolean and complex aliases are rejected before buffering or synthetic EEG generation so flags and phasors cannot enter real sensor channels.
hardware_io ¶
Real-time hardware I/O via BrainFlow (EEG, PPG, EMG) and SCADA/Modbus.
BrainFlow supports: OpenBCI, Muse, Emotiv, NeuroSky, BrainBit, Enobio, and simulated boards for development. Install: pip install brainflow
SCADA: Modbus TCP via pymodbus. Install: pip install pymodbus
Classes¶
SampleBuffer
dataclass
¶
Ring buffer for streaming sensor data.
Methods:¶
push ¶
Push (n_channels, n_samples) into the ring buffer.
Parameters¶
samples : FloatArray
Sample block, shape (n_channels, n_samples).
Raises¶
ValueError If the sample block shape is invalid.
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
get_recent ¶
Get the last n samples as (n_channels, n).
Parameters¶
n : int Number of most-recent samples to return.
Returns¶
FloatArray
The most recent n samples, shape (n_channels, n).
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
BrainFlowAdapter ¶
Streams EEG/PPG/EMG data from BrainFlow-supported devices.
Usage
adapter = BrainFlowAdapter(board_id=BoardIds.SYNTHETIC_BOARD) adapter.start() signal = adapter.get_channel_data(0, n_samples=256) adapter.stop()
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
Attributes¶
sample_rate
property
¶
eeg_channels
property
¶
BrainFlow EEG channel indices for this board.
Returns¶
list[int] BrainFlow EEG channel indices for this board.
Methods:¶
start ¶
Prepare and start the BrainFlow data stream.
stop ¶
Stop the stream and release the board session.
get_channel_data ¶
Get recent samples from one EEG channel.
Parameters¶
channel_idx : int Index of the channel to read. n_samples : int Number of samples to return.
Returns¶
FloatArray
Recent samples from the channel, shape (n_samples,).
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
get_all_eeg ¶
Get (n_eeg_channels, n_samples) of recent EEG data.
Parameters¶
n_samples : int Number of samples to return.
Returns¶
FloatArray
Recent EEG data, shape (n_eeg_channels, n_samples).
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
SimulatedBoardAdapter ¶
SimulatedBoardAdapter(
n_channels: int = 8,
sample_rate: int = 256,
frequencies: FloatArray | None = None,
)
Generates synthetic sinusoidal signals for development without hardware.
Matches the BrainFlowAdapter interface.
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
Attributes¶
sample_rate
property
¶
n_channels
property
¶
Methods:¶
start ¶
stop ¶
get_channel_data ¶
Return synthetic sinusoidal samples for one channel.
Parameters¶
channel_idx : int Index of the channel to read. n_samples : int Number of samples to return.
Returns¶
FloatArray
Synthetic samples for the channel, shape (n_samples,).
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
get_all_eeg ¶
Return synthetic (n_channels, n_samples) sinusoidal data.
Parameters¶
n_samples : int Number of samples to return.
Returns¶
FloatArray
Synthetic EEG data, shape (n_channels, n_samples).
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
ModbusAdapter ¶
Reads SCADA/PLC registers via Modbus TCP for industrial control.
Usage
adapter = ModbusAdapter("192.168.1.100", port=502) adapter.connect() values = adapter.read_holding_registers(0, count=10) adapter.disconnect()
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
Methods:¶
connect ¶
disconnect ¶
read_holding_registers ¶
Read holding registers, return as float64 array.
Parameters¶
address : int Modbus register address. count : int Number of registers to read.
Returns¶
FloatArray The register values as a float64 array.
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
write_register ¶
Write a single holding register.
Parameters¶
address : int Modbus register address. value : int Register value to write.
Returns¶
bool
True when the write succeeds.
Raises¶
ValueError If the value is out of range.
Source code in src/scpn_phase_orchestrator/adapters/hardware_io.py
Functions:¶
Gaian Mesh Bridge¶
The implements Layer 12: Distributed Mesh of the SCPN architecture. It provides decentralized inter-node synchronization via stateless UDP heartbeats.
Multiple independent instances of SPO running across different machines can "couple" together. Instead of exchanging raw (N)$ phases, nodes exchange their macroscopic Order Parameters ({global}, \Psi_{global}\(). The bridge integrates the peer fields and translates them into external forcing parameters (\)\zeta$, \(\Psi\)) for the local .
Features¶
- Stateless UDP Broadcasting: Designed for high-frequency, loss-tolerant mesh topologies.
- Topological Consensus: Enables thousands of independent agents (drones, servers) to synchronize without a central command node.
- Timeout-Aware: Automatically drops stale peers from the mean-field calculation to prevent phantom drag.
Peer and local psi values are finite real phases on the circle; negative
finite phases are canonicalised modulo 2*pi before mesh-drive computation.
gaian_mesh_bridge ¶
UDP Gaian mesh bridge for exchanging reduced macroscopic order parameters.
GaianMeshNode validates peer addresses and local order-parameter updates,
then uses background UDP loops to broadcast and receive reduced R/psi
heartbeats. Mesh drive computation filters stale or malformed peer state and
returns an external drive proposal only. The bridge exchanges no raw phases,
coupling matrices, credentials, or actuation commands.
Classes¶
PeerState
dataclass
¶
State received from a peer node in the mesh.
GaianMeshNode ¶
GaianMeshNode(
node_id: str,
host: str = "127.0.0.1",
port: int = 12000,
peer_addresses: list[tuple[str, int]] | None = None,
mesh_coupling_strength: float = 1.0,
heartbeat_interval_s: float = 0.05,
peer_timeout_s: float = 1.0,
)
Distributed Gaian Mesh (Layer 12) Coupling Bridge.
Allows multiple independent instances of scpn-phase-orchestrator running on different servers/machines to 'couple' together over UDP. They exchange aggregate Order Parameters (R, Psi) acting as a massive, decentralized super-oscillator.
The peers' macroscopic fields are combined into a resultant vector,
which is then applied to the local integration engine via the external
driver parameters zeta and psi.
Source code in src/scpn_phase_orchestrator/adapters/gaian_mesh_bridge.py
Methods:¶
start ¶
Start the mesh networking threads.
Raises¶
RuntimeError If the mesh networking threads cannot start.
Source code in src/scpn_phase_orchestrator/adapters/gaian_mesh_bridge.py
stop ¶
Stop the mesh networking threads.
Source code in src/scpn_phase_orchestrator/adapters/gaian_mesh_bridge.py
__enter__ ¶
__exit__ ¶
Stop networking threads and release the UDP socket on context exit.
update_local_state ¶
Update the local macro state to be broadcasted to peers.
Parameters¶
R : float Kuramoto order parameter. psi : float Mean phase in radians.
Source code in src/scpn_phase_orchestrator/adapters/gaian_mesh_bridge.py
compute_mesh_drive ¶
Compute the effective external drive (zeta, psi) from the mesh.
Returns¶
zeta: The magnitude of the mesh mean field.
psi_target: The phase angle of the mesh mean field.
Source code in src/scpn_phase_orchestrator/adapters/gaian_mesh_bridge.py
Functions:¶
LSL BCI Entrainment Bridge¶
The LSLBCIBridge implements Phase 9: Biological Integration of the SCPN
augmentation roadmap. It establishes a real-time feedback loop between human
neural oscillations and the phase orchestrator.
By utilizing the Lab Streaming Layer (LSL) protocol, the bridge can ingest
live EEG data from a wide range of hardware (OpenBCI, Muse, Neuralink, etc.).
It extracts the instantaneous phase of target brainwaves (e.g., Alpha or Gamma
rhythms) and provides them as input to the ActiveInferenceAgent for
predictive entrainment.
Captured samples must be finite real EEG amplitudes, not boolean aliases, and LSL timestamps must be finite non-negative values before samples enter the Hilbert phase buffer.
Features¶
- Real-Time Phase Extraction: Uses Hilbert transforms on sliding windows to track neural phase state.
- Hardware Agnostic: Supports any EEG device with an LSL outlet.
- Review-only stimulation targets (research scaffold): can compute proposed auditory/visual stimulation targets from the measured phase, for offline research use only. This is an unvalidated experimental adapter — it makes no clinical claim and must not be used to drive stimulation of a person.
lsl_bci_bridge ¶
Lab Streaming Layer BCI bridge for buffered phase extraction.
The bridge optionally connects to a configured LSL stream, captures one target channel into a bounded background buffer, and extracts the current Hilbert phase from recent samples. Configuration rejects invalid stream names, channels, and buffer durations. When LSL is unavailable or disconnected, it fails without leaking stream identifiers in shared error messages.
Classes¶
LSLBCIBridge ¶
Real-time BCI Entrainment Bridge via Lab Streaming Layer (LSL).
This bridge enables direct human-machine synchronization. It streams raw EEG data from LSL (e.g., from OpenBCI or Muse), extracts the instantaneous phase of target neural oscillations, and provides them to the SCPN orchestrator for closed-loop entrainment.
Attributes¶
stream_name: Name of the LSL stream to listen to.
target_channel: Index of the EEG channel to use.
sampling_rate: Sampling rate of the EEG stream (Hz).
Source code in src/scpn_phase_orchestrator/adapters/lsl_bci_bridge.py
Methods:¶
connect ¶
Resolve and connect to the LSL stream.
Parameters¶
timeout : float Connection timeout in seconds.
Returns¶
bool
True when the LSL stream is resolved and connected.
Source code in src/scpn_phase_orchestrator/adapters/lsl_bci_bridge.py
start ¶
Start the background capture thread.
Raises¶
RuntimeError If the capture thread cannot start.
Source code in src/scpn_phase_orchestrator/adapters/lsl_bci_bridge.py
stop ¶
Stop capture and disconnect.
Source code in src/scpn_phase_orchestrator/adapters/lsl_bci_bridge.py
get_instantaneous_phase ¶
Extract the current phase from the buffered signal.
Uses Hilbert transform on the recent buffer window. Returns phase in [0, 2*pi).
Returns¶
float Extract the current phase from the buffered signal.
Source code in src/scpn_phase_orchestrator/adapters/lsl_bci_bridge.py
Remanentia Bridge¶
remanentia_bridge ¶
Bidirectional bridge between SPO coherence monitoring and Remanentia memory.
Direction 1 (SPO -> Remanentia): Agent coherence metrics feed consolidation decisions. High R = agents aligned = consolidate their outputs together. Low R = agents diverged = index separately, flag conflicts.
Direction 2 (Remanentia -> SPO): Memory recall novelty feeds coupling adaptation. Novel recall = agents exploring new ground = boost K. Stale recall = repetitive work = decay K.
Requires: Remanentia API running at http://localhost:8001
Classes¶
CoherenceMemorySnapshot
dataclass
¶
CoherenceMemorySnapshot(
R_global: float,
regime: str,
n_entities: int,
n_memories: int,
novelty_score: float,
consolidation_suggested: bool,
)
Combined coherence + memory state.
RemanentiaBridge ¶
Bidirectional SPO <-> Remanentia bridge.
Usage::
bridge = RemanentiaBridge(remanentia_url="http://localhost:8001")
# After each SPO step:
bridge.report_coherence(R=0.85, regime="nominal", agent_phases={...})
# Get memory-informed coupling adjustment:
novelty = bridge.get_novelty_score("What coupling topology works?")
K_boost = novelty * 0.5 # novel = explore more = stronger coupling
# Trigger consolidation when agents are aligned:
if R > 0.8:
bridge.trigger_consolidation()
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
Methods:¶
health_check ¶
Check if Remanentia is running.
Returns¶
bool Check if Remanentia is running.
Raises¶
Exception
Re-raises any non-transport, non-decode error; transport and decode failures
are caught and False is returned.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
report_coherence ¶
Report current SPO coherence state to Remanentia.
Remanentia can use this to decide when to consolidate: high R = aligned agents = good time to merge their traces.
Parameters¶
R : float
Kuramoto order parameter.
regime : str
The current control regime label.
agent_phases : dict[str, float] | None
Per-agent phase values, or None.
Raises¶
ValueError
If R, regime, or agent_phases is invalid.
Exception
Re-raises any non-transport, non-decode error; transport and decode failures
are caught and logged.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
get_novelty_score ¶
Query Remanentia and estimate novelty from recall results.
If recall returns many relevant memories -> low novelty (known ground). If recall returns few/none -> high novelty (unexplored territory). Novelty feeds SPO coupling: novel = boost K (explore together).
Parameters¶
query : str PromQL query string.
Returns¶
float The estimated novelty score for the query.
Raises¶
ValueError
If query is invalid.
Exception
Re-raises any non-transport, non-decode error; transport and decode failures
fall back to the last novelty score.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
get_entity_count ¶
Get number of entities in Remanentia's knowledge graph.
Returns¶
int Get number of entities in Remanentia's knowledge graph.
Raises¶
Exception Re-raises any non-transport, non-decode error; transport and decode failures fall back to the last entity count.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
trigger_consolidation ¶
Trigger memory consolidation in Remanentia.
Best called when R is high (agents aligned, traces coherent).
Parameters¶
force : bool Whether to force consolidation regardless of thresholds.
Returns¶
bool
True when consolidation was triggered.
Raises¶
ValueError If the consolidation request is rejected.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
novelty_to_coupling_delta ¶
Convert per-agent novelty scores to coupling adjustment.
Each agent's recent work is queried against Remanentia. Novel agents get coupling boosted (explore together). Redundant agents get coupling decayed (avoid repetition).
Returns (N,) array of per-agent K multipliers.
Parameters¶
queries : list[str] Per-agent novelty queries. scale : float Scaling factor applied to the coupling adjustment.
Returns¶
FloatArray The per-agent coupling adjustments.
Raises¶
ValueError If the queries or scale are invalid.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
snapshot ¶
Return the combined coherence and memory state.
Returns¶
CoherenceMemorySnapshot Return the combined coherence and memory state.
Raises¶
Exception Re-raises any non-transport, non-decode error; transport and decode failures fall back to cached memory counts.
Source code in src/scpn_phase_orchestrator/adapters/remanentia_bridge.py
Synapse Bridges¶
The synapse bridges translate phase-channel and coupling data into sibling service contracts while keeping the normal audit path unchanged. The channel bridge treats hub WebSocket frames as untrusted JSON: decoded messages must use finite JSON values and unique object keys before sender, type, or payload fields can affect phase-channel state.
synapse_channel_bridge ¶
Live bridge from SYNAPSE_CHANNEL hub events to SPO phase dynamics.
Connects to the SYNAPSE_CHANNEL WebSocket hub and maps agent activity into oscillator phases for real-time coherence monitoring.
Mapping: - Heartbeat interval → P-channel frequency (regular = coherent) - Task claim/release rate → I-channel frequency (balanced = coherent) - Chat message similarity → S-channel coupling (same topic = coupled)
Usage::
bridge = SynapseChannelBridge(
hub_uri="ws://localhost:8876",
agents=["Agent-A", "Agent-B", "Agent-C", "Human"],
)
await bridge.connect()
# In your SPO loop:
phases = bridge.get_phases()
knm = bridge.get_coupling()
Classes¶
AgentState
dataclass
¶
AgentState(
last_heartbeat: float = 0.0,
heartbeat_intervals: list[float] = list(),
task_events: list[float] = list(),
message_count: int = 0,
current_task: str | None = None,
phase_p: float = 0.0,
phase_i: float = 0.0,
phase_s: float = 0.0,
)
Tracked state for one agent.
SynapseChannelBridge ¶
Live bridge from SYNAPSE_CHANNEL to SPO oscillator phases.
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
Attributes¶
n_oscillators
property
¶
Return the number of configured agent oscillators.
Returns¶
int Return the number of configured agent oscillators.
Methods:¶
connect
async
¶
Connect to SYNAPSE_CHANNEL hub and start listening.
Raises¶
ImportError If the SYNAPSE channel client is not installed.
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
listen_once
async
¶
Process one message from the hub.
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
get_phases ¶
Extract current oscillator phases from agent heartbeat activity.
The returned phase for each agent is the P-channel phase, which
advances once per call at the agent's recent mean heartbeat frequency
(a regular heartbeat yields a steady phase advance). The per-agent
phase_i (task cadence) and phase_s (topic alignment) channels
are tracked on :class:AgentState but are not folded into this
one-dimensional phase vector.
Returns¶
FloatArray
The P-channel phase per agent, shape (n_oscillators,).
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
get_coupling ¶
Compute coupling from shared task context.
Agents working on related tasks couple strongly. Agents with no task decouple.
Returns¶
FloatArray Compute coupling from shared task context.
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
get_agent_summary ¶
Return per-agent summary for display.
Returns¶
dict[str, dict[str, Any]] Return per-agent summary for display.
Source code in src/scpn_phase_orchestrator/adapters/synapse_channel_bridge.py
close
async
¶
Stop the bridge and close the active hub WebSocket connection.
synapse_coupling_bridge ¶
Bridge sc-neurocore synapse dynamics into the SPO coupling matrix K_nm.
Maps three synapse types to SPO coupling parameters:
-
STDP weight changes → K_nm deltas: spike-timing dependent plasticity modifies pairwise coupling strengths. Potentiation (dW > 0) strengthens K_ij; depression (dW < 0) weakens it.
-
Gap junction conductance → phase coupling: electrical synapses provide direct bidirectional coupling. g_c maps linearly to K_ij (symmetric).
-
Tripartite astrocyte Ca²⁺ → imprint modulation: astrocyte oscillations modulate the imprint memory vector m_k. High Ca²⁺ enhances imprint accumulation; low Ca²⁺ accelerates decay.
Requires: pip install sc-neurocore>=3.13.0
Classes¶
SynapseSnapshot
dataclass
¶
SynapseSnapshot(
knm_delta: FloatArray,
gap_coupling: FloatArray,
astrocyte_modulation: FloatArray,
mean_weight_change: float,
mean_conductance: float,
mean_ca: float,
)
Snapshot of synapse state mapped to SPO parameters.
SynapseCouplingBridge ¶
SynapseCouplingBridge(
n_oscillators: int,
stdp_scale: float = 1.0,
gap_scale: float = 1.0,
ca_scale: float = 1.0,
)
Map sc-neurocore synapse dynamics to SPO coupling parameters.
Usage::
from sc_neurocore.synapses.triplet_stdp import TripletSTDP
from sc_neurocore.synapses.gap_junction import GapJunction
bridge = SynapseCouplingBridge(n_oscillators=8)
# After each SNN step, feed weight changes
bridge.update_stdp_weights(weight_matrix)
bridge.update_gap_conductances(conductance_matrix)
bridge.update_astrocyte_ca(ca_levels)
# Get SPO coupling delta
snap = bridge.snapshot()
knm_new = knm_base + snap.knm_delta
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
Methods:¶
update_stdp_weights ¶
Feed current STDP weight matrix from sc-neurocore.
The bridge computes dW = weights - prev_weights and maps to K_nm deltas.
Parameters¶
weights : FloatArray
STDP weight matrix, shape (N, N).
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
update_gap_conductances ¶
Feed gap junction conductance matrix.
Symmetric: g_c(i,j) = g_c(j,i). Maps directly to K_ij.
Parameters¶
conductances : FloatArray
Gap-junction conductance matrix, shape (N, N).
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
update_astrocyte_ca ¶
Feed astrocyte Ca²⁺ concentration per oscillator.
High Ca²⁺ → strong imprint modulation (facilitates learning).
Parameters¶
ca_levels : FloatArray Per-oscillator astrocyte Ca²⁺ concentrations.
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
snapshot ¶
Compute SPO coupling parameters from current synapse state.
Returns¶
SynapseSnapshot Compute SPO coupling parameters from current synapse state.
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
apply_to_knm ¶
Apply all synapse-derived modifications to a base K_nm.
Parameters¶
knm_base : FloatArray
Base coupling matrix to modify, shape (N, N).
Returns¶
FloatArray The base coupling matrix with synapse-derived modifications.
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
apply_to_imprint ¶
Modulate imprint vector by astrocyte Ca²⁺ levels.
Parameters¶
m_k : FloatArray
Imprint vector, shape (N,).
Returns¶
FloatArray The imprint vector modulated by astrocyte Ca²⁺.
Source code in src/scpn_phase_orchestrator/adapters/synapse_coupling_bridge.py
FMI 3.0 Co-Simulation Export¶
adapters.fmi_cosimulation wraps the Koopman MPC controller as an FMI 3.0
co-simulation slave so a simulation master (Dymola, OpenModelica, FMPy) can drive
the SPO controller as a block: set the measured state and set point, call
do_step, read back the proposed control. The slave, the modelDescription.xml
generator and the .fmu packager are pure NumPy and produce a conformant FMI 3.0
model interface; loading the package inside a third-party FMI tool additionally
needs the C-ABI binary shim, which is an optional, separately-installed build
step (e.g. the unifmu toolchain) outside this module. The reverse import
direction is cosimulate, a co-simulation master that drives the controller
slave against a plant supplied as a step callable — an external plant FMU plugs
in by wrapping its FMI runtime (e.g. fmpy) as that callable.
fmi_cosimulation ¶
Export the Koopman MPC controller as an FMI 3.0 co-simulation slave.
The Functional Mock-up Interface (FMI 3.0, modelica.org) is the industrial
standard for coupling simulation tools. This adapter wraps the condensed Koopman
MPC (actuation.koopman_mpc) as an FMI co-simulation slave so a co-simulation
master — a power-systems or control bench such as Dymola, OpenModelica or FMPy —
can drive the SPO controller as a block: it sets the measured state and the set
point, calls do_step, and reads back the proposed control.
The slave, the modelDescription.xml generator and the .fmu packager are
pure NumPy and fully exercised in-process by driving the slave the way a master
would. They emit a conformant FMI 3.0 model interface; a C-ABI binary shim is
not shipped, so loading the package inside a third-party FMI tool needs a
Python-backed FMI runtime that reconstructs the model from
resources/model.json — the review-only model and its evidence are produced
here.
The reverse, import direction is :func:cosimulate: a co-simulation master that
drives the controller slave against a plant supplied as a step callable, closing
the loop. An external plant FMU plugs in by wrapping its FMI runtime (for example
fmpy) as that callable, so no FMI runtime dependency is imposed here either.
References¶
- Modelica Association 2024, Functional Mock-up Interface Specification 3.0.
Classes¶
FMIVariable
dataclass
¶
A scalar FMI 3.0 Float64 model variable.
Parameters¶
name : str
The variable name, a valid FMI identifier.
value_reference : int
The handle a co-simulation master uses to get or set the variable.
causality : str
"input" or "output".
start : float | None
The required start value for inputs; None for outputs.
CoSimulationSlave ¶
An FMI 3.0 co-simulation slave wrapping a Koopman MPC controller.
The slave mirrors the FMI co-simulation lifecycle — set inputs, do_step,
get outputs — and computes the control by solving the MPC at each step.
Parameters¶
controller : KoopmanMPCController The fitted Koopman MPC controller to expose. model_name : str The FMI model name.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
Methods:¶
enter_initialization_mode ¶
Reset the slave to its start values for a fresh co-simulation run.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
exit_initialization_mode ¶
set_float64 ¶
Set input variables addressed by their value references.
Parameters¶
value_references : list[int] The handles of the variables to set. values : list[float] The values, one per reference.
Raises¶
ValueError If the lengths differ, a reference is unknown, or it is not an input.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
get_float64 ¶
Get any variables addressed by their value references.
Parameters¶
value_references : list[int] The handles of the variables to read.
Returns¶
list[float] The current values, one per reference.
Raises¶
ValueError If a reference is unknown.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
do_step ¶
Advance the co-simulation by solving the MPC for the current inputs.
Parameters¶
current_communication_point : float The master's current time; recorded for the lifecycle contract. communication_step_size : float The communication step; the controller's own sample period governs the internal prediction, so the step is accepted as given.
Raises¶
ValueError If the communication step size is negative.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
Functions:¶
generate_model_description ¶
Render the FMI 3.0 modelDescription.xml for a slave.
Parameters¶
slave : CoSimulationSlave The slave whose model interface to describe.
Returns¶
str
The modelDescription.xml document.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
write_fmu ¶
Package a slave as a .fmu archive (model interface + resources).
The archive carries the conformant modelDescription.xml and a
resources/model.json describing the controller, the self-contained model
a Python-backed FMI runtime reconstructs. No C-ABI binary shim is shipped,
so a third-party FMI tool needs such a runtime to load the archive.
Parameters¶
slave : CoSimulationSlave
The slave to package.
path : str | pathlib.Path
Destination .fmu path.
Returns¶
pathlib.Path The written archive path.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
cosimulate ¶
cosimulate(
controller: CoSimulationSlave,
plant_step: PlantStep,
*,
initial_state: FloatArray,
steps: int,
dt: float,
reference: FloatArray | None = None,
) -> FloatArray
Run a co-simulation master coupling the controller slave with a plant.
This is the import/master direction: SPO drives a plant model in
co-simulation. Each step writes the plant state to the controller's state
inputs, advances the controller by one MPC step, reads its control output and
applies it to the plant, then advances the plant. The plant is any step
callable (state, control, dt) -> next_state; an external plant FMU plugs
in by wrapping its FMI runtime (for example fmpy) as such a callable, so
no FMI runtime dependency is imposed here.
Parameters¶
controller : CoSimulationSlave
The FMI controller slave to drive.
plant_step : Callable[[numpy.ndarray, numpy.ndarray, float], numpy.ndarray]
Advances the plant by dt under the applied control.
initial_state : numpy.ndarray
The plant's initial state x_0 of shape (n,).
steps : int
Number of co-simulation steps.
dt : float
The communication step size.
reference : numpy.ndarray | None
The controller set point of shape (n,); defaults to the origin.
Returns¶
numpy.ndarray
The closed-loop plant-state trajectory of shape (steps + 1, n).
Raises¶
ValueError
If the initial state length, step count, or dt are inconsistent.
Source code in src/scpn_phase_orchestrator/adapters/fmi_cosimulation.py
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 422 423 424 425 426 427 428 429 430 431 432 433 | |
Adapter selection guidance¶
Choose adapter layers by failure tolerance and change management profile:
| Deployment pattern | Recommended adapter set | Primary constraint |
|---|---|---|
| Internal production control loop | control or hardware adapters only where bounded actuators are required |
bounded actuation and deterministic replay |
| Observability-first rollout | opentelemetry, metrics_exporter, prometheus |
low-latency visibility before actuation |
| Cross-repo data exchange | fusion_core_bridge, scpn_control_bridge, neurocore_bridge |
schema compatibility and replayability |
| Hardware field trial | modbus_tls, hardware_io with explicit opt-in flags |
physical safety and rollback path |
| Research and co-compilation | hybrid_cocompiler, quantum_control_bridge |
review-only policy and explicit scope notes |
spo doctor reports the package-local FMI and hybrid co-compiler review/export
surfaces as optional adapter diagnostics. A warning there means the package is
missing an expected local adapter export; it does not mean SPO is connected to a
live FMI runtime, QPU, neuromorphic backend, or actuator.
Every adapter contributes a conversion boundary. Production changes should only depend on adapters that are covered by active parity and boundary tests for the target release.
Security and quality boundary¶
Adapter boundaries should remain explicit in runtime runbooks:
- validate all external payloads before deriving phase or coupling state,
- keep non-production adapters out of critical paths by default,
- keep adapter version, endpoint, and mode in audit metadata so post-hoc reviews can identify where control decisions changed.
That boundary allows teams to reuse the same internal core while keeping external dependencies isolated from safety-critical decision flow.