Oscillators: P / I / S Channels¶
Three extraction channels convert domain signals into phase states.
This is the universal interface through which any coupled-cycle system
enters the SCPN Phase Orchestrator. The choice of channel determines
how raw data becomes a phase on [0, 2pi), a frequency in rad/s, and
a confidence score in [0, 1].
The key insight: once a signal is expressed as a PhaseState, the
UPDE engine treats it identically regardless of origin. An EEG alpha
wave (P), a packet arrival process (I), and a protocol state machine
(S) all become entries in the same phase vector, coupled through the
same K_nm matrix. Domain knowledge lives in the extractor and the
binding spec, not in the engine.
P — Physical Channel¶
Input: Continuous waveform (voltage, pressure, displacement, acceleration, temperature oscillation — any real-valued time series with a dominant oscillatory component).
Method: Hilbert transform via the analytic signal:
a(t) = s(t) + i * H[s(t)]
theta(t) = arg(a(t)) — instantaneous phase
A(t) = |a(t)| — instantaneous amplitude (envelope)
omega(t) = d(unwrap(theta))/dt — instantaneous frequency
The Hilbert transform H[s] computes the imaginary part of the
analytic signal. For a narrowband signal (e.g., EEG filtered to the
alpha band 8-12 Hz), this yields a clean, slowly varying phase.
For broadband signals, bandpass filtering before extraction is
essential — the Hilbert transform of a broadband signal produces
a noisy, rapidly varying phase.
Implementation detail: PhysicalExtractor applies optional zero-phase
Butterworth band-pass filtering, then calls scipy.signal.hilbert at the
original signal length. Optional edge trimming selects the final retained
interior phase. The angular frequency is the median gradient of unwrapped
phase multiplied by the sample rate, in rad/s.
Quality metric: Variation of the analytic envelope:
A mean envelope below 1e-15 gives zero quality. Envelope magnitudes and
statistics use scaled arithmetic to avoid intermediate overflow for large,
representable analytic envelopes. Filtering and the Hilbert transform must
produce finite values; otherwise public extraction raises ValueError. This score measures envelope regularity, not a
signal-to-noise power ratio; it does not establish physical observability.
When to use: - EEG, MEG, LFP (neural oscillations) - ECG (heartbeat waveform) - Accelerometer, gyroscope (mechanical vibration) - AC voltage/current (power systems) - Acoustic signals (machinery monitoring) - Any signal where the observable is a continuous oscillating quantity
When NOT to use: - Event streams (use I-channel instead) - Discrete state sequences (use S-channel instead) - Signals with no oscillatory component (e.g., monotonic trends)
Extractor: PhysicalExtractor in oscillators.physical.
Rust path: spo-oscillators::physical::extract_from_analytic is exposed
as spo_kernel.physical_extract() through the FFI. The Rust implementation uses the same
envelope and phase equations. Floating-point results agree within the owning
tests' tolerances; this is not a bitwise replay guarantee.
I — Informational Channel¶
Input: Event timestamps (sorted ascending). Each timestamp marks the occurrence of a discrete event: a neural spike, a network packet arrival, a heartbeat R-peak, a job completion, a user click.
Method: A regular point process has an inherent frequency (its rate). The extractor represents the observed event train by its median instantaneous frequency and cumulative phase:
IEI_k = t_{k+1} - t_k — inter-event interval
f_k = 1 / IEI_k — instantaneous frequency
omega = 2 * pi * median(f_k) — angular frequency (robust to outliers)
The extractor returns the cumulative median-frequency phase over the observed train, not an interpolation between the two most recent events:
Phase lies in [0, 2pi) and is invariant under a shift of timestamp origin.
Amplitude is the mean instantaneous frequency. For an even interval count,
median(1 / IEI) need not equal 1 / median(IEI): timestamps
[0, 0.125, 0.375] yield 6 Hz, phase pi/2, amplitude 6 and quality 0.75.
Quality metric: Inverse coefficient of variation:
Regular events (constant IEI, CV=0) score quality=1.0. Bursty or irregular events (high CV) score low. A Poisson process (CV=1) scores quality=0.5 — barely usable.
When to use: - Neural spike trains - Network packet arrivals - Heartbeat R-peaks (when only peak times are available, not the full waveform — otherwise use P-channel on the ECG signal) - Job completion events in distributed systems - User interaction events (clicks, keystrokes) - Any observable that is a point process
Implementation detail: InformationalExtractor handles edge cases:
- Fewer than 2 events: returns quality=0 (no phase estimate possible).
- Duplicate timestamps: ignore zero intervals; an all-identical train returns
zero phase, frequency, amplitude and quality. No interval flooring is applied.
- Non-sorted input: raise ValueError without modifying the timestamps.
- Quality uses population standard deviation of the positive intervals.
Extractor: InformationalExtractor in oscillators.informational.
Rust path: spo-oscillators::informational::event_phase() is exposed via FFI.
The extractor validates timestamps and removes duplicates before calling it.
The raw kernel instead returns zero phase, frequency and quality for
non-positive intervals, including duplicate or unsorted timestamps.
ring_phase() belongs to the symbolic channel, not the informational module.
S — Symbolic Channel¶
Input: Sequence of integer state indices (ring labels wrap modulo N), observed at discrete time steps. Protocol states, workflow stages, Markov chain positions, categorical labels.
Method: Two modes:
Ring Mode (default)¶
Maps discrete state to equally spaced phases on the unit circle:
State 0 maps to phase 0, state 1 to 2pi/N, etc. This preserves
the circular topology — state N-1 is adjacent to state 0.
Frequency is computed from phase differences between consecutive observations:
where the phase difference is wrapped to [-pi, pi) before division.
The first frequency is zero; an exact half-turn selects the negative direction.
Graph Mode¶
Graph mode accumulates absolute linear index differences along the observed sequence, then normalises that cumulative distance:
It does not use an adjacency graph. A singleton uses ring mapping; a stationary multi-state sequence produces zero phases. Label spacing therefore matters in graph mode; ring aliases are not graph-distance aliases.
Signed and unsigned 64-bit labels retain their values, including their full
minimum-to-maximum spans. Narrower integer arrays promote within the same
signedness. Graph differences and cumulative walk lengths use exact integer
arithmetic before conversion to float64; they neither overflow signed
subtraction nor saturate at machine-word capacity. The phase output itself is
rounded to float64, so this is not an exact-rational phase representation.
Backend equivalence is numerical rather than bitwise: ring phases and graph
qualities can differ in the last float64 bits for vocabularies above 2**53,
because Rust converts the operands before division while Python rounds the
integer quotient. Integer residues and graph distances still retain every bit.
Quality metric: Transition regularity:
| Transition type | Quality |
|---|---|
| Single step (k = 1) | 1.0 |
| Stall (k = 0) | 0.2 |
| Multi-step jump (k > 1) | max(0.1, 1.0 - (k - 1) / N) |
| First observation | initial_transition_quality, default 0.5 |
Graph distance k is the absolute linear index difference. Ring distance uses d = |s_new - s_old| mod N and k = min(d, N-d), including signed and out-of-vocabulary labels; a full cycle is a stall.
Low quality indicates the state machine is behaving unexpectedly (stalling, skipping states), which makes the phase estimate unreliable.
When to use: - TCP/protocol state machines - Workflow/pipeline stages - Markov chain position tracking - Categorical variable cycling (e.g., traffic light states) - Game/simulation state - Any observable that is a finite state machine with cyclic structure
Extractor: SymbolicExtractor in oscillators.symbolic.
Rust path: spo-oscillators::symbolic provides vector ring phases,
graph-walk phases and linear transition qualities through the corresponding
*_rust FFI functions. The public extractor scores cyclic ring quality in
Python on both backend paths; without the kernel it also computes phases and
graph quality in Python.
Both paths accept one-dimensional strided and read-only observations. Public
input normalisation copies unaligned arrays before native access. Vocabulary
sizes are integers ≥ 2 without a public upper bound; counts exceeding the native
target's usize capacity use Python, even with the extension installed.
The API integer contract
and public diagnostic document these boundaries.
The dated raw observations
record each real execution path without an isolated speedup claim.
Mixed-Channel Systems¶
A single domain can use all three channels simultaneously. The binding
spec oscillator_families dict maps family names to channels:
oscillator_families:
heart_wave:
channel: P
extractor_type: physical
spike_train:
channel: I
extractor_type: informational
protocol_state:
channel: S
extractor_type: symbolic
config:
n_states: 8
All channels output PhaseState with identical fields. The UPDE engine
operates on the unified phase vector — it does not distinguish channels.
Cross-channel coupling (e.g., a P-channel EEG oscillator coupled to an
I-channel spike train) is handled naturally through K_nm entries that
span oscillator families.
Cross-Channel Coupling Considerations¶
Coupling between channels of different types requires care:
- P-P coupling: natural. Both have continuous phase dynamics.
- I-I coupling: natural. Both have event-driven phase.
- P-I coupling: the I-channel phase is piecewise linear (resets at
each event), while the P-channel phase is smooth. The coupling term
sin(theta_P - theta_I)handles this correctly, but the effective coupling strength may need to be lower to avoid jitter from I-channel resets. - S-X coupling: symbolic phase is quantised. Coupling to continuous
channels works but introduces discrete jumps in the coupling force.
Higher
n_statesreduces quantisation effects.
Initial Phase Extraction¶
The extract_initial_phases() utility extracts PhaseState from all
oscillator families in a binding spec given raw signal data:
from scpn_phase_orchestrator.oscillators import extract_initial_phases
states = extract_initial_phases(
binding_spec=spec,
signals={"heart_wave": ecg_data, "spike_train": spike_times},
sample_rates={"heart_wave": 256.0, "spike_train": 30000.0},
)
Quality Gating¶
Extracted phases with quality < min_quality (default 0.3) are
down-weighted by PhaseQualityScorer.downweight_mask(). The weight
array multiplies into K_nm row-wise, effectively decoupling unreliable
oscillators without removing them from the state vector.
Amplitude-weighted aggregate quality uses finite quality/amplitude pairs, a 1e-12 amplitude floor and weights scaled by their maximum before summation. This preserves finite means for amplitudes near the float64 limit in Python and Rust. It does not change the empirical gating or collapse thresholds.
Collapsed oscillators (quality < 0.1 for majority of states) trigger
detect_collapse(), which the supervisor interprets as a DEGRADED or
CRITICAL condition depending on the scope of collapse.
Quality Calibration¶
The default thresholds (0.3 for gating, 0.1 for collapse) are empirical. Domain-specific calibration:
- Run the system on representative data with quality logging enabled.
- Plot quality distributions per channel.
- Set
min_qualityat the point where phase estimates become visually unreliable (e.g., Hilbert phase no longer tracks the dominant oscillation). - Set collapse threshold at the point where the majority of oscillators produce noise rather than signal.
See docs/ASSUMPTIONS.md § Quality Gating for the current defaults
and their provenance.
Channel Selection Guide¶
| Observable | Channel | Reason |
|---|---|---|
| EEG voltage | P | Continuous waveform with dominant oscillation |
| Neural spike times | I | Point process, not continuous |
| Heart rate from R-peaks | I | Event timestamps |
| ECG waveform | P | Continuous, use Hilbert on QRS complex |
| Network packet arrivals | I | Event timestamps |
| TCP state transitions | S | Finite state machine |
| Machine vibration | P | Continuous mechanical oscillation |
| Workflow stage | S | Discrete categorical |
| Stock price | P | Continuous (after detrending) |
| Trade events | I | Point process |
| Traffic light state | S | Cyclic discrete states |
| Respiratory waveform | P | Continuous |
| Servo motor position | P | Continuous angular signal |
When in doubt: if the signal is continuous and oscillatory, use P. If it is a series of event times, use I. If it is a sequence of discrete states, use S.
References¶
- [gabor1946] D. Gabor (1946). Theory of communication. J. IEE 93, 429-457. — Analytic signal and instantaneous phase (P channel).
- [pikovsky2001] A. Pikovsky, M. Rosenblum & J. Kurths (2001). Synchronization: A Universal Concept in Nonlinear Sciences. Cambridge UP. — Phase extraction from time series, quality metrics.
- [lachaux1999] J.-P. Lachaux et al. (1999). Measuring phase synchrony in brain signals. Human Brain Mapping 8, 194-208. — PLV definition used in quality gating.
- [daley2003] D. J. Daley & D. Vere-Jones (2003). An Introduction to the Theory of Point Processes. Springer. — Point process theory underlying I-channel extraction.