Partial Information Decomposition¶
The monitor.pid module estimates the redundant and synergistic
information that two oscillator groups carry about the global phase state.
It is a time-series Williams-Beer I_min decomposition over circular phase
observables, not a single-snapshot synchrony score.
The implementation has a five-slot backend chain:
- Rust
- Mojo
- Julia
- Go
- Python
Unavailable optional toolchains are reported explicitly by the benchmark gate. Available backends must reproduce the Python reference within the declared tolerance and must satisfy the same decomposition contracts.
Mathematical Contract¶
Input is a finite real phase history phases with shape (T, N), where
T is the number of timesteps and N is the number of oscillators. Each
timestep is reduced to three circular observables:
| Symbol | Observable |
|---|---|
Y_t |
global order-parameter phase over all oscillators |
A_t |
order-parameter phase of group_a |
B_t |
order-parameter phase of group_b |
The three series are wrapped into [0, 2*pi) and binned into n_bins
equal-width phase bins. With Williams and Beer specific information,
the exported components are:
I_red = sum_y p(y) * min(I_spec(Y=y; A), I_spec(Y=y; B))
I_syn = MI(A,B; Y) - MI(A; Y) - MI(B; Y) + I_red
I_red is the Williams-Beer I_min redundancy. I_syn is clipped at zero
after the floating-point estimate so small negative roundoff cannot cross the
public boundary. A single snapshot (T = 1) or an empty group carries no
distributional information and returns 0.0.
API¶
from scpn_phase_orchestrator.monitor.pid import redundancy, synergy
red = redundancy(history, group_a=[0, 1, 2], group_b=[3, 4, 5], n_bins=32)
syn = synergy(history, group_a=[0, 1, 2], group_b=[3, 4, 5], n_bins=32)
redundancy¶
Returns the information about the global phase target that is available from both source groups. Positive redundancy means either group alone carries overlapping information about the same target state.
synergy¶
Returns the information about the global phase target that is available only from the joint observation of both groups. Positive synergy means the groups are complementary: neither group alone carries the full target information.
Input validation¶
The public boundary rejects:
- boolean phase aliases,
- numeric-string phase aliases,
- complex phase samples, including object-dtype complex aliases,
- non-finite phase samples,
- non-vector or out-of-range group indices,
- numeric-string group indices,
- boolean, complex, non-integral, or non-finite group indices,
n_bins < 2,- boolean or non-integral
n_binsvalues, - backend scalar outputs that are numeric strings, non-finite, or negative.
One-dimensional phase input is interpreted as a single timestep. Because a
single timestep does not define a distribution, both public functions return
0.0 in that case.
Backend Chain¶
| Slot | Backend | Loader |
|---|---|---|
| 1 | Rust | spo_kernel.pid_decomposition_rust |
| 2 | Mojo | mojo/pid_mojo subprocess executable |
| 3 | Julia | julia/pid.jl through juliacall |
| 4 | Go | go/libpid.so through ctypes |
| 5 | Python | NumPy reference implementation |
The dispatcher tries the active backend first, then every resolved backend in order, then the Python reference. Loader failures and unavailable optional toolchains are allowed to fall back. Physics-contract failures at the public boundary are rejected before a result can be accepted.
Direct Go, Julia, and Mojo bridge wrappers share the same typed backend input validation before optional runtime loading. Their outputs are validated as finite non-negative redundancy and synergy scalars, never numeric-string aliases, before returning to the public dispatcher.
Parity Gate¶
The release benchmark function is benchmark_pid_polyglot_parity_gate. Run it
directly with:
PYTHONPATH=.:src python benchmarks/pid_benchmark.py \
--parity-gate \
--n-steps 1500 \
--n-bins 12 \
--calls 1
The gate records all declared backend slots in canonical order
rust, mojo, julia, go, python. Acceptance requires:
- the Python reference record,
- one explicit record per declared backend slot,
- parity for every available backend,
- non-negative redundancy and synergy,
- positive synergy for a deterministic co-varying source pair,
- positive redundancy and vanishing synergy for a fully redundant source pair,
- a deterministic benchmark hash excluding wall-clock timing.
The benchmark evidence kind is local_regression_non_isolated; wall-clock
values are local, non-isolated regression evidence. It does not make production
timing claims unless the benchmark metadata records CPU isolation and host-load
controls.
The canonical reference suite exposes the same gate as pid_polyglot.
Relationship To Transfer Entropy¶
Transfer entropy asks whether the past of one phase stream helps predict a target stream beyond the target's own past. PID asks how two source groups share information about a target distribution. Use transfer entropy for directed pairwise influence, and use PID when redundant versus synergistic group information is the quantity of interest.
pid ¶
Partial information decomposition (PID) about global synchronisation.
Decomposes two oscillator groups with a 5-backend fallback chain.
Model¶
Williams & Beer 2010 (Nonnegative Decomposition of Multivariate Information,
arXiv:1004.2515) decompose the information two sources carry about a target into
redundant, unique, and synergistic parts. Estimating it needs a distribution,
so the input is a phase history (T, N) (T timesteps, N
oscillators). Each timestep is reduced to three circular observables:
- target
Y_t— the global order-parameter phase∠⟨e^{iθ}⟩over all oscillators, - source
A_t— the group-A order-parameter phase, - source
B_t— the group-B order-parameter phase.
The three series are binned into n_bins equal-width phase bins and the joint
distribution is estimated over the T samples.
Decomposition¶
With the specific information I_spec(Y=y; S) = Σ_s p(s|y)·log[p(y|s)/p(y)]:
redundancy I_red = Σ_y p(y)·min( I_spec(Y=y; A), I_spec(Y=y; B) )
synergy I_syn = MI(A,B; Y) − MI(A; Y) − MI(B; Y) + I_red
I_red is the Williams & Beer I_min redundancy; the unique information of
each source is MI(S; Y) − I_red and MI(A; Y) = I_red + U_A holds by
construction. All terms are non-negative.
A single snapshot (T = 1) carries no distributional information, so every
component is 0; meaningful decomposition needs T ≥ 2.
Functions:¶
redundancy ¶
redundancy(
phases: FloatArray,
group_a: list[int] | IntArray,
group_b: list[int] | IntArray,
n_bins: int = _DEFAULT_BINS,
) -> float
Redundant information both groups share about the global phase.
I_red = Σ_y p(y)·min(I_spec(Y=y; A), I_spec(Y=y; B)) (Williams & Beer
2010 I_min). phases is a (T, N) phase history.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
group_a : list[int] | IntArray
Indices of the first oscillator group.
group_b : list[int] | IntArray
Indices of the second oscillator group.
n_bins : int
Number of histogram bins.
Returns¶
float The redundant information the groups share about the global phase.
Source code in src/scpn_phase_orchestrator/monitor/pid.py
synergy ¶
synergy(
phases: FloatArray,
group_a: list[int] | IntArray,
group_b: list[int] | IntArray,
n_bins: int = _DEFAULT_BINS,
) -> float
Synergistic information present only in the joint (A, B).
I_syn = MI(A,B; Y) − MI(A; Y) − MI(B; Y) + I_red. Positive synergy means
the combined group carries information about the global state that neither
subgroup carries alone. phases is a (T, N) phase history.
Parameters¶
phases : FloatArray
Oscillator phases in radians, shape (N,).
group_a : list[int] | IntArray
Indices of the first oscillator group.
group_b : list[int] | IntArray
Indices of the second oscillator group.
n_bins : int
Number of histogram bins.
Returns¶
float
The synergistic information present only in the joint (A, B).