Adapters

July 21, 2026 · View on GitHub

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:

AdapterTargetData flow
scpn_control_bridgescpn-control (v0.18.0)Bidirectional: phases, coupling, regime
fusion_core_bridgeSCPN-Fusion-Core (v3.9.3)Export: sync metrics for fusion analysis
neurocore_bridgesc-neurocore (v3.13.3)Export: phase states for SNN processing
plasma_control_bridgePlasma control systemsImport: magnetic diagnostics as P-channel
quantum_control_bridgescpn-quantum-control (v0.9.1)Export: coherence metrics for QPU scheduling
hybrid_cocompilerQuantum + neuromorphic review packageExport: shared audit envelope for simulator handoff
snn_bridgelocal SNN daemonExport: local phase-dynamics signal packets

scpn-control Bridge

SCPNControlBridge(scpn_config: dict) — bidirectional adapter.

MethodSignatureDescription
import_knm(scpn_knm: NDArray) → CouplingStateWrap external K_nm
import_omega(scpn_omega: NDArray) → NDArrayValidate frequencies
export_state(upde_state: UPDEState) → dictTelemetry 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_phase_orchestrator.adapters.scpn_control_bridge

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.

::: scpn_phase_orchestrator.adapters.fusion_core_bridge

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.

::: scpn_phase_orchestrator.adapters.plasma_control_bridge

Quantum Control Bridge

::: scpn_phase_orchestrator.adapters.quantum_control_bridge

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.

::: scpn_phase_orchestrator.adapters.openqasm_conformance

Hybrid Co-Compiler

::: scpn_phase_orchestrator.adapters.hybrid_cocompiler

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.

::: scpn_phase_orchestrator.adapters.snn_bridge

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.

::: scpn_phase_orchestrator.adapters.neuromorphic_ir_export

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.

::: scpn_phase_orchestrator.adapters.neurocore_bridge

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:

MethodSignatureDescription
record_step(upde_state, step_idx)Record metrics for one engine step
record_regime_change(old, new)Record regime transition event

Metrics exported:

MetricTypeDescription
spo.order_parameterGaugeCurrent R value
spo.regimeGaugeCurrent regime (0-3)
spo.step_latency_msHistogramEngine step duration
spo.coupling_meanGaugeMean K_nm value

::: scpn_phase_orchestrator.adapters.opentelemetry

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:

PrometheusAdapter(endpoint: str, timeout: float = 5.0)
MethodSignatureDescription
fetch_metric(query, start, end, step) -> NDArray[np.float64]Fetch a range-vector metric as finite values
fetch_instant(query) -> floatFetch one instant-vector scalar

::: scpn_phase_orchestrator.adapters.prometheus

Metrics Exporter

Lightweight metrics export helpers used by services that do not need the full OpenTelemetry adapter.

::: scpn_phase_orchestrator.adapters.metrics_exporter

Redis Store

Optional Redis-backed state exchange for deployments that need shared runtime state outside the local process.

::: scpn_phase_orchestrator.adapters.redis_store

Hardware Adapters

Modbus/TLS

Industrial control interface for power grids, HVAC, and manufacturing. Translates ControlAction to Modbus register writes over TLS.

::: scpn_phase_orchestrator.adapters.modbus_tls

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)

::: scpn_phase_orchestrator.adapters.opcua_bridge

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)

::: scpn_phase_orchestrator.adapters.mqtt_bridge

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)

::: scpn_phase_orchestrator.adapters.synchrophasor_c37118

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)

::: scpn_phase_orchestrator.adapters.synchrophasor_phase_bridge

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()

::: scpn_phase_orchestrator.adapters.synchrophasor_client

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.

::: scpn_phase_orchestrator.adapters.hardware_io

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,nodesexchangetheirmacroscopicOrderParameters(global,Ψglobal 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.

::: scpn_phase_orchestrator.adapters.gaian_mesh_bridge

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.

::: scpn_phase_orchestrator.adapters.lsl_bci_bridge

Remanentia Bridge

::: scpn_phase_orchestrator.adapters.remanentia_bridge

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.

::: scpn_phase_orchestrator.adapters.synapse_channel_bridge

::: scpn_phase_orchestrator.adapters.synapse_coupling_bridge

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.

::: scpn_phase_orchestrator.adapters.fmi_cosimulation

Adapter selection guidance

Choose adapter layers by failure tolerance and change management profile:

Deployment patternRecommended adapter setPrimary constraint
Internal production control loopcontrol or hardware adapters only where bounded actuators are requiredbounded actuation and deterministic replay
Observability-first rolloutopentelemetry, metrics_exporter, prometheuslow-latency visibility before actuation
Cross-repo data exchangefusion_core_bridge, scpn_control_bridge, neurocore_bridgeschema compatibility and replayability
Hardware field trialmodbus_tls, hardware_io with explicit opt-in flagsphysical safety and rollback path
Research and co-compilationhybrid_cocompiler, quantum_control_bridgereview-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.