Skip to content

Plugin API

Extension points for domain-specific behaviour without modifying engine code.

Custom PhaseExtractor

Subclass PhaseExtractor and implement extract() and quality_score():

from scpn_phase_orchestrator.oscillators.base import PhaseExtractor, PhaseState

class MyExtractor(PhaseExtractor):
    def extract(self, signal, sample_rate):
        # signal: NDArray, sample_rate: float
        # Return: list[PhaseState]
        ...

    def quality_score(self, phase_states):
        # Return: float in [0, 1]
        ...

Register in the binding spec:

oscillator_families:
  my_sensor:
    channel: P
    extractor_type: my_module.MyExtractor
    config:
      param1: value1

The loader resolves extractor_type by dotted import path when it is not one of the built-in types (physical, informational, symbolic).

Custom GeometryConstraint

Implement a callable that constrains the Knm matrix:

def my_constraint(knm, params):
    # Enforce sparsity, band structure, etc.
    # Return: modified knm (NDArray)
    ...

Register in the binding spec:

geometry_prior:
  constraint_type: my_module.my_constraint
  params:
    bandwidth: 3

Called after CouplingBuilder.build() and after every template switch.

Custom DriverSpec

The drivers section of the binding spec configures per-channel external drive parameters:

drivers:
  physical:
    zeta: 0.5
    psi: 0.0
  informational:
    zeta: 0.0
  symbolic:
    zeta: 0.1
    psi: 3.14

Custom driver logic can override the default zeta * sin(Psi - theta) by providing a driver class:

class MyDriver:
    def drive(self, phases, t):
        # Return: NDArray of drive contributions per oscillator
        ...

Registration

All custom classes are resolved at binding-spec load time via Python's importlib. The module must be importable from the Python path. No global registry -- resolution is per-spec.