Skip to content

Rust FFI Acceleration

The spo-kernel Rust workspace accelerates selected paths across the engine, coupling, supervisor, SSGF, monitor, extraction, and autotune surfaces. Recorded local speedups vary substantially by operation, size, build, and host. Python classes auto-detect the compiled spo_kernel module and delegate transparently—no application-code change is needed.

For runtime selection across Rust, Python, JAX, and auxiliary research backends, see Backend Fallback Chain.

Prerequisites

  • Rust 1.83+ (workspace MSRV)
  • maturin (pip install maturin)

Building

Use the repository helper so maturin runs through the Python interpreter that owns the target environment:

python tools/install_spo_kernel.py --release

This compiles all Rust crates and installs spo_kernel into the active Python environment. To target the repository virtual environment explicitly:

.venv/bin/python tools/install_spo_kernel.py --release

Verify the selected environment:

python tools/install_spo_kernel.py --check-only

The equivalent raw maturin command is:

python -m maturin develop --release -m spo-kernel/crates/spo-ffi/Cargo.toml

Using python -m maturin is intentional: it prevents a globally installed maturin executable from building into a different interpreter than the one used by spo run, tests, or notebooks.

You can inspect the command without compiling Rust:

python tools/install_spo_kernel.py --dry-run --json

After installation, direct import should work:

import spo_kernel
print(spo_kernel.PyUPDEStepper)

Auto-Delegation

Python classes check for spo_kernel at construction time. If present, hot paths delegate to Rust with no API change:

from scpn_phase_orchestrator import UPDEEngine

engine = UPDEEngine(n_oscillators=64, dt=0.01, method="rk4")
# engine._rust is a PyUPDEStepper if spo_kernel is installed
# engine._rust is None otherwise (pure numpy fallback)

The _compat.HAS_RUST flag controls delegation globally. Set it to False in benchmarks to force the Python path.

Accelerated Modules

Python Class / Function Rust FFI Class Hot path
UPDEEngine PyUPDEStepper step(), run()
StuartLandauEngine PyStuartLandauStepper step(), run()
CouplingBuilder PyCouplingBuilder build(), project()
ImprintModel PyImprintModel update(), modulate_coupling(), modulate_lag()
compute_order_parameter order_parameter single call
compute_plv plv single call
modulation_index pac_modulation_index single call
pac_matrix pac_matrix_compute full NxN
CoherenceMonitor PyCoherenceMonitor compute_r_good(), compute_r_bad(), detect_phase_lock()
RegimeManager PyRegimeManager evaluate(), transition()
ActionProjector PyActionProjector project()
BoundaryObserver PyBoundaryObserver observe()
SupervisorPolicy PySupervisorPolicy decide()
PhaseQualityScorer PyPhaseQualityScorer score(), is_collapsed()
LagModel PyLagModel estimate()
NeurocoreBridge PyLIFEnsemble step() (LIF ensemble, 325x at N=10000)
Physical extractor physical_extract analytic signal extraction
Symbolic extractors ring_phase, graph_walk_phase, transition_quality single call
Informational extractor event_phase timestamp analysis
SimplicialEngine simplicial_run 3-body coupling run()
HypergraphEngine hypergraph_run k-body coupling run()
GeometricEngine geometric_run SO(2) exp map run()
extract_envelope envelope_rms_rust cumulative-sum RMS
OttAntonsenReduction oa_run_rust run(), steady_state_R(), predict_from_oscillators()
SplittingEngine splitting_run_rust Strang split run()
te_adapt_coupling te_adapt_coupling_rust TE-directed coupling update
UniversalPrior.log_probability prior_log_probability_rust Bayesian log-density
load_hcp_connectome load_hcp_connectome_rust synthetic connectome generation
GeometryCarrier.decode carrier_decode_rust softplus(A·z) decode
compute_ethical_cost compute_ethical_cost_rust SEC + CBF ethical cost
classify_sleep_stage classify_sleep_stage_rust AASM stage classification
EVSMonitor._frequency_specificity frequency_specificity_rust target/control ITPC ratio
PhaseSINDy.fit sindy_fit_rust STLSQ sparse regression
estimate_coupling (disabled) normal equations (3x slower than LAPACK)
extract_phases (disabled) naive DFT (60x slower than SciPy FFT)

Benchmark Comparison

bench/run_benchmarks.py measures UPDEEngine.step() with RK4, averaged over 1000 steps after 50 warmup iterations. The table is a historical local snapshot. Re-run the benchmark and record host/build metadata before using a number for capacity planning.

N Python (numpy) Rust (spo_kernel) Speedup
16 ~25 us/step 7.3 us/step 3.4x
64 ~180 us/step 28 us/step 6.4x
256 ~2.8 ms/step 0.32 ms/step 8.7x
1024 ~45 ms/step 8.6 ms/step 5.2x

The speedup saturates at large N because both paths are O(N^2) in coupling computation; the Rust advantage comes from avoiding Python interpreter overhead and numpy dispatch per operation.

LIF Ensemble (NeurocoreBridge)

The PyLIFEnsemble accelerates the neurocore bridge's spiking neuron simulation. Measured on Windows 11, Python 3.12, Rust 1.93.0 release build, N=10000 neurons (10 layers × 1000), 100 substeps:

Backend Time ns/neuron/substep Speedup vs scalar
Rust (PyLIFEnsemble) 0.004 s 3-6 ns 325×
NumPy (vectorised) 0.014 s 14 ns 93×
Scalar (sc-neurocore per-neuron) 1.306 s 1,306 ns

The recorded 4 ms per 100-substep result is local throughput evidence. It does not establish a 250 Hz control-loop deadline: end-to-end sensing, scheduling, transport, actuation, jitter, and worst-case latency were not measured.

Crate Structure

spo-kernel/
  Cargo.toml          # workspace root
  crates/
    spo-types/        # Shared types: UPDEState, LayerState, Regime, Knob, ControlAction
    spo-engine/       # 53 modules: UPDE (12 engines), coupling (11), monitors (14), SSGF (3), autotune (4), + support
    spo-oscillators/  # Physical, informational, symbolic, quality extractors
    spo-supervisor/   # Boundaries, coherence, policy, projector, regime manager
    spo-ffi/          # PyO3 bindings (this is what maturin builds)

All pure-logic crates (spo-types, spo-engine, spo-oscillators, spo-supervisor) have #![no_std] aspirations but currently use std for HashMap and Vec. Only spo-ffi depends on PyO3 and numpy.

FFI Numeric Precision Contract

The Python/Rust boundary is a float64 contract:

  • Python passes contiguous numeric arrays after shape, finite-value, and domain validation.
  • Rust kernels return float64-compatible values and Python revalidates shape, finiteness, and physical bounds before accepting the result.
  • Phase outputs are normalised to [0, 2π) unless the documented monitor contract returns a unitless score or matrix.
  • Fixed-point formal manifests are separate review artefacts; they do not change the runtime float64 solver contract.

Any new FFI path must add a module-specific parity test that compares Python and Rust on the same seeded input and documents the tolerated absolute or relative error. Safety-critical gates must fail closed when either backend emits NaN, infinity, shape drift, or a value outside the declared physical range.

Contributing Rust Code

Run all three before submitting:

cargo fmt --all
cargo clippy --workspace -- -D warnings
cargo test --workspace

The CI pipeline runs:

Job Matrix What
rust-check 3 OS (Linux, macOS, Windows) cargo fmt --check, clippy -D warnings, cargo test
ffi-test 3 OS x 2 Python (3.11, 3.12) maturin develop --release, pytest tests/
cargo-audit Linux cargo audit for known vulnerabilities
cargo-deny Linux RustSec advisories, banned wildcard dependencies, and source registry policy
rust-miri Linux nightly Miri smoke tests for pure-Rust type/supervisor crates
rust-msrv Linux Verify builds on Rust 1.83.0

Numerical Parity

The Rust and Python implementations produce identical results to float64 precision. The CI ffi-test job runs the full Python test suite with spo_kernel installed, confirming parity across all integration methods (euler, rk4, rk45) and both engines (UPDEEngine, StuartLandauEngine).