Python Facade

June 16, 2026 · View on GitHub

The Python facade is the smallest stable local simulation entry point. It exists for notebooks, services, validation scripts, and downstream projects that need a reviewed binding specification without invoking the spo command-line interface.

Positioning (Why this facade exists)

The module has two intentionally different entry points:

  • Orchestrator.run is the narrow convenience facade for research-tier Kuramoto bindings.
  • evaluate_binding_spec is the full non-actuating evaluation facade for the shared simulation core used by spo run.

Both paths make phase-control logic embeddable without inheriting full CLI process management.

Use this for:

  • product teams that already own orchestration and only need deterministic local runs,
  • application code that requires a stable import surface,
  • reproducibility workflows where explicit replay metadata and bounded state are required.

Avoid using it when you need hardware dispatch, policy execution, or service topology management. Those concerns remain on CLI/research-surface flows with their own guardrails.

Orchestrator.run is intentionally narrower than the CLI and narrower than the full research package. It accepts a BindingSpec, can load a reviewed binding_spec.yaml, validates the binding before execution, executes local Kuramoto dynamics, and returns an immutable OrchestratorState record.

evaluate_binding_spec is broader but still non-actuating. It accepts a BindingSpec or binding-spec path, validates it, and calls the shared runtime.simulation.simulate core for Kuramoto or Stuart-Landau specs at any safety tier. This is for review, dry-run, benchmark, and open/closed-loop comparison evidence only; it does not write audit logs, open sockets, or bypass operator approval.

Use it for deterministic local checks and embedded Python workflows. The facade contract includes no hardware actuation and no network IO. Do not use it for supervisor policy execution, audit-log writing, socket serving, external-device dispatch, or mutation of the source binding spec.

Public surface

The module path is scpn_phase_orchestrator.api.

The package root exports Orchestrator and OrchestratorState.

The short import alias also exports those two names.

The short path is the scpn import alias.

from scpn import Orchestrator, OrchestratorState

The long package path is:

from scpn_phase_orchestrator import Orchestrator, OrchestratorState

Both imports resolve to the same facade classes.

The top-level package also exports broader reviewed public names.

The stable top-level manifest is tracked in docs/specs/public_api_manifest.txt.

Any change to scpn_phase_orchestrator.__all__ must update that manifest.

Any change to the import block in docs/reference/api/index.md must stay aligned with the manifest.

Any compatibility-affecting change must be reflected in release notes.

This page documents only the high-level Python facade in scpn_phase_orchestrator.api.

Main use cases

Use the facade to run a deterministic local simulation from Python.

Use the facade to embed a reviewed binding in a notebook.

Use the facade to run a local validation script without Click command invocation.

Use the facade to inspect final phases after a small reviewed run.

Use the facade to inspect natural frequencies resolved from a binding.

Use the facade to inspect the coupling matrix generated from binding coupling settings.

Use the facade to inspect the alpha matrix generated by CouplingBuilder.

Use the facade to compute and persist a compact result record.

Use the facade to compare deterministic seeds across small test runs.

Use the facade to check whether a research-tier Kuramoto binding can execute locally.

Use the facade to avoid shelling out from services that already run Python.

Use the facade to keep application code on a stable import surface.

Use the facade when a full CLI run would add unnecessary process management.

Use the facade when a downstream workflow needs the final numerical arrays, not terminal output.

Use the facade as an onboarding path for simple local examples.

Do not use the facade for live plant control.

Do not use the facade for hardware integration.

Do not use the facade for networked distributed synchronisation.

Do not use either facade for supervisor policy actuation.

Do not use Orchestrator.run for non-research safety tiers.

Do not use Orchestrator.run for amplitude-mode Stuart-Landau specs.

Do not confuse evaluate_binding_spec with admitted CLI execution; it is non-actuating evaluation, not an operational approval path.

Do not use the facade as a replacement for the CLI when operator audit output is required.

Do not use the facade as a replacement for domain-specific model validation.

Contract summary

Orchestrator.from_yaml loads a binding spec file.

Orchestrator.from_yaml validates the loaded binding through the same binding validation layer used by the package.

Orchestrator.from_yaml returns an Orchestrator instance.

Orchestrator.run executes deterministic local dynamics.

Orchestrator.run accepts a non-negative integer steps value.

Orchestrator.run accepts a non-negative integer seed value.

Orchestrator.run returns OrchestratorState.

OrchestratorState carries final phase values.

OrchestratorState carries natural frequencies.

OrchestratorState carries the generated coupling matrix.

OrchestratorState carries the generated alpha matrix.

OrchestratorState carries the Kuramoto order parameter.

OrchestratorState carries the circular mean phase.

OrchestratorState carries the binding sample period.

OrchestratorState.to_record returns a compact JSON-serialisable dictionary.

Orchestrator.run supports research-tier Kuramoto binding specs.

Orchestrator.run rejects non-research safety tiers.

Orchestrator.run rejects amplitude-mode specs.

evaluate_binding_spec accepts Kuramoto and Stuart-Landau binding specs.

evaluate_binding_spec accepts research, clinical, consumer, and production safety tiers only as non-actuating evaluation inputs.

evaluate_binding_spec returns a SimulationResult with objective order parameters, separation, regime, histories, and action counts.

evaluate_binding_spec(policy_enabled=False) runs the same drivers and intrinsic plasticity without supervisor or policy feedback.

evaluate_binding_spec(policy_enabled=True) runs the same shared simulation core used by spo run, while keeping audit-log admission and safety-tier execution gates owned by the CLI.

The facade rejects invalid step counts.

The facade rejects invalid seeds.

The facade rejects binding specs with zero oscillators.

The facade rejects binding specs that fail validate_binding_spec.

The facade uses deterministic random initial phases for a fixed seed.

The facade does not alter global NumPy random state.

The facade uses a local numpy.random.default_rng(seed) generator.

Import examples

Use the short alias for application-facing scripts:

from scpn import Orchestrator

orch = Orchestrator.from_yaml("domainpacks/minimal_domain/binding_spec.yaml")
state = orch.run(steps=100, seed=42)
print(state.to_record())

Use the long package path when code already imports other package names:

from scpn_phase_orchestrator import Orchestrator

orch = Orchestrator.from_yaml("domainpacks/minimal_domain/binding_spec.yaml")
state = orch.run(steps=16)
assert 0.0 <= state.order_parameter <= 1.0

Use direct module import when documenting the facade itself:

from scpn_phase_orchestrator.api import OrchestratorState

All three paths are stable public import surfaces.

The short alias intentionally exposes only Orchestrator and OrchestratorState.

The long package root exposes the broader reviewed package manifest.

The direct module path exposes only the facade classes.

Orchestrator

Orchestrator is the high-level Python entry point.

It is constructed from a BindingSpec.

Most callers should use Orchestrator.from_yaml.

The constructor validates that the input object is a BindingSpec.

The constructor runs the executable-spec validation gate.

The constructor stores the spec as self.spec.

The constructor does not run dynamics.

The constructor does not create a coupling matrix.

The constructor does not allocate phase vectors.

The constructor does not contact hardware.

The constructor does not perform network IO.

The constructor does not evaluate policies.

The constructor can fail before returning an instance.

A failed constructor leaves no partially executed run.

Constructor input

The constructor accepts exactly one object.

That object must be a BindingSpec.

A non-BindingSpec object raises TypeError.

The error message names the expected type.

The error message includes the rejected object representation.

The constructor then calls _validate_executable_spec.

That validation step is deliberately stricter than the general binding validator.

A binding may be valid for the wider package but still rejected by Orchestrator.run.

This protects the convenience facade from silently taking on CLI-only responsibilities.

Executable spec gate

The executable-spec gate calls validate_binding_spec.

Any binding validator errors are joined into one ValueError.

A non-empty validator result prevents execution.

The safety tier must be research.

Any other safety tier raises ValueError.

Orchestrator.run rejects clinical tier specs.

Orchestrator.run rejects consumer tier specs.

Orchestrator.run rejects production tier specs.

Orchestrator.run rejects any other supported non-research tier.

Orchestrator.run also rejects amplitude-mode specs.

A binding with an amplitude block raises ValueError.

The reason is explicit: use StuartLandauEngine directly for amplitude-mode specs.

The facade checks the oscillator count.

The oscillator count must be at least one.

A zero-oscillator binding raises ValueError.

This gate keeps Orchestrator.run focused on one local Kuramoto path. Use evaluate_binding_spec when the task is non-actuating full-core evaluation across Stuart-Landau specs or higher safety tiers.

Orchestrator.from_yaml

Orchestrator.from_yaml loads a binding from disk.

It accepts either a string path or a Path object.

It converts the input to Path.

It calls load_binding_spec.

It passes the resulting spec to the constructor.

It returns an Orchestrator instance.

It does not bypass validation.

It does not treat file extension as proof of validity.

It does not alter the binding file.

It does not write derived artefacts.

It does not resolve a domainpack directory by name.

Callers should pass the actual binding_spec.yaml path.

Relative paths are resolved by normal Python path rules.

File not found failures come from the binding loader path.

Binding parse failures come from the binding loader path.

Binding validation failures are converted by the constructor gate.

Use this method for the common one-command Python API:

orch = Orchestrator.from_yaml("domainpacks/demo/binding_spec.yaml")

Orchestrator.run

Orchestrator.run performs the deterministic local simulation.

Its keyword-only arguments are steps and seed.

steps defaults to 100.

seed defaults to 42.

Both arguments must be non-negative integers.

Boolean aliases are rejected.

Floating point values are rejected.

Strings are rejected.

Negative integers are rejected.

Zero steps are allowed.

A zero-step run returns the initial seeded phase vector after the engine call.

The run reads the number of oscillators from the binding layers.

The run builds coupling with CouplingBuilder.

The builder receives oscillator count, base strength, and decay alpha.

The run initialises phases uniformly on [0, 2*pi).

The run resolves natural frequencies with spec.get_omegas().

The run resolves initial forcing through _initial_zeta.

The run reads psi from the physical driver config.

The run constructs UPDEEngine with the binding sample period.

The run calls engine.run with phases, omegas, K_nm, zeta, psi, alpha, and step count.

The run computes the final order parameter with compute_order_parameter.

The run packages outputs in OrchestratorState.

The run copies the coupling matrix.

The run copies the alpha matrix.

The run does not expose mutable internal builder state.

The run does not mutate the source binding.

The run does not persist results automatically.

The caller decides where to store the result record or arrays.

Numerical path

The facade is a deterministic local numerical path.

It is built from the same components used by package-level Kuramoto simulations.

CouplingBuilder constructs the coupling matrix.

UPDEEngine advances phases.

compute_order_parameter evaluates the circular synchrony summary.

The order parameter is bounded between zero and one by the order-parameter contract.

The mean phase is a circular phase angle.

The natural frequencies are converted to float64.

The seeded phases are converted to float64.

The coupling matrix is returned as a float64 array.

The alpha matrix is returned as a float64 array.

The facade does not currently expose alternate integration backends directly.

Backend acceleration remains owned by lower-level engine surfaces.

The facade does not provide a Rust-specific switch.

The facade follows the engine implementation available in the package environment.

If a downstream workflow needs backend comparison, use the dedicated engine APIs and benchmarks.

If a downstream workflow needs amplitude dynamics, use StuartLandauEngine.

If a downstream workflow needs sparse dynamics, use SparseUPDEEngine.

If a downstream workflow needs sheaf dynamics, use SheafUPDEEngine.

OrchestratorState

OrchestratorState is a frozen dataclass.

It is the return type of Orchestrator.run.

It stores spec_name.

It stores steps.

It stores phases.

It stores omegas.

It stores knm.

It stores alpha.

It stores order_parameter.

It stores mean_phase.

It stores sample_period_s.

The dataclass is frozen.

The arrays inside the dataclass remain NumPy arrays.

Callers should not mutate returned arrays if they want the record to remain semantically immutable.

The state object is designed as a convenient local result container.

It is not an audit event.

It is not a persistence format.

It is not a safety approval.

It is not a policy decision.

It is not a hardware command.

State fields

spec_name is copied from the binding spec name.

steps is the validated run step count.

phases is the final phase vector.

omegas is the natural-frequency vector.

knm is the generated coupling matrix.

alpha is the generated alpha matrix.

order_parameter is the final synchrony magnitude.

mean_phase is the final circular mean phase.

sample_period_s is the binding sample period.

The oscillator count is implied by phases.size.

The coupling matrix shape should be (N, N) for N oscillators.

The alpha matrix shape should be (N, N) for N oscillators.

The natural-frequency vector shape should be (N,).

The phase vector shape should be (N,).

OrchestratorState.to_record

to_record returns a compact dictionary.

The method is intentionally lossy.

It does not include the phase vector.

It does not include natural frequencies.

It does not include K_nm.

It does not include alpha.

It includes spec_name.

It includes steps.

It includes oscillator_count.

It includes order_parameter.

It includes mean_phase.

It includes sample_period_s.

The output is JSON-serialisable under standard encoders.

The output is suitable for logs, notebooks, dashboards, and small summaries.

The output is not sufficient to reproduce a full run.

Store the binding spec, seed, step count, and package version separately when reproducibility is required.

Store the full arrays separately when downstream analysis needs phase-level evidence.

Error boundaries

A non-BindingSpec constructor input raises TypeError.

A binding with validation errors raises ValueError.

A non-research safety tier raises ValueError.

An amplitude-mode binding raises ValueError.

A zero-oscillator binding raises ValueError.

A boolean steps value raises ValueError.

A non-integral steps value raises ValueError.

A negative steps value raises ValueError.

A boolean seed value raises ValueError.

A non-integral seed value raises ValueError.

A negative seed value raises ValueError.

Binding file load failures propagate from load_binding_spec.

Numerical engine failures propagate from UPDEEngine.

This is deliberate.

The facade should not hide invalid physics or invalid binding configuration.

The facade should fail close to the cause.

Determinism contract

For a fixed binding, fixed step count, fixed seed, and fixed package environment, the facade returns reproducible local results.

The seed controls only the initial phase vector.

The seed is passed to numpy.random.default_rng.

The binding controls natural frequencies.

The binding controls coupling base strength.

The binding controls coupling decay.

The binding controls the sample period.

The binding controls physical driver psi and channel forcing values.

The engine controls numerical integration.

The returned arrays may differ if the engine implementation changes across releases.

The returned arrays may differ if lower-level numerical algorithms change across releases.

The public contract is deterministic within a fixed installed version.

Use release-pinned environments for published numerical comparisons.

Use dedicated benchmark artefacts for performance claims.

Safety boundaries

The facade is deliberately non-actuating.

It has no hardware writer.

It has no network transport.

It has no live queue consumer.

It has no supervisor action executor.

It has no deployment approval flow.

It has no policy dry-run report.

It has no audit logger side effect.

It has no remote backend selector.

It has no bridge to external control systems.

The facade only returns local numerical state.

This makes it appropriate for embedding in Python applications that need deterministic local evidence.

It does not make the binding operationally approved.

It does not make a controller safe.

It does not make a domainpack production-ready.

Operational workflows must use the wider CLI, audit, policy, and review surfaces.

Relationship to the CLI

The CLI is broader than the facade.

The CLI validates binding specs.

The CLI inspects resolved binding configuration.

The CLI runs simulations.

The CLI replays audit logs.

The CLI writes reports.

The CLI handles scaffold workflows.

The CLI handles plugin lifecycle workflows.

The CLI handles formal export workflows.

The CLI handles policy dry-runs.

The facade intentionally exposes only local binding-spec simulation.

Use spo run when operator-facing terminal output is desired.

Use spo validate when only validation is required.

Use spo inspect when resolved binding configuration is required.

Use spo report and spo replay for audit-derived workflows.

Use the facade when Python code needs arrays and compact records directly.

Relationship to package root exports

The package root exposes a reviewed public manifest.

Orchestrator and OrchestratorState are part of that manifest.

UPDEEngine remains available for lower-level Kuramoto runs.

StuartLandauEngine remains available for amplitude dynamics.

SparseUPDEEngine remains available for sparse matrix dynamics.

SheafUPDEEngine remains available for sheaf-coupled dynamics.

CouplingBuilder remains available for explicit coupling construction.

BoundaryObserver remains available for boundary checks.

SupervisorPolicy remains available for policy objects.

RegimeManager remains available for regime classification.

AuditLogger remains available for audit streams.

QPUDataArtifact and related functions remain available for QPU artefact packaging.

The facade does not replace those lower-level exports.

The facade composes a small subset of those exports into a safe local path.

Orchestrator.run Research-Tier Restriction

Orchestrator.run only executes research-tier bindings.

This is a deliberate adoption boundary.

Research bindings are appropriate for deterministic local exploration.

Higher safety tiers need operator review and deployment controls.

Higher safety tiers may imply safety documentation outside this facade.

Higher safety tiers may imply hardware, clinical, consumer, or production obligations.

Orchestrator.run cannot enforce those obligations.

Therefore it rejects them.

This prevents downstream application code from mistaking a local Python run for an approved operational run.

If a non-research binding needs admitted CLI execution, use the explicit CLI or domain-specific validation workflow selected by the operator.

If a production binding needs dry-run numerical evidence, use evaluate_binding_spec and keep policy, safety-tier, and deployment evidence alongside the result.

Orchestrator.run Kuramoto Binding Restriction

Orchestrator.run currently supports Kuramoto binding specs.

A binding with an amplitude block is rejected.

Amplitude dynamics are owned by StuartLandauEngine and its documentation.

This avoids mixing two mathematical models behind one convenience method.

It also keeps the return state simple.

The state includes phases, natural frequencies, coupling, alpha, order parameter, and mean phase.

It does not include amplitude variables.

It does not include Stuart-Landau complex state.

It does not include sheaf section data.

It does not include sparse solver diagnostics.

Call evaluate_binding_spec when a non-actuating SimulationResult is enough; call specialised engines directly when lower-level state access is required.

evaluate_binding_spec

evaluate_binding_spec is the full-core, non-actuating evaluation entry point.

It accepts a BindingSpec, string path, or Path.

When a path is provided, an adjacent policy.yaml is loaded into the closed-loop domainpack policy path.

It validates the binding before execution.

It accepts Kuramoto specs.

It accepts Stuart-Landau amplitude-mode specs.

It accepts higher safety tiers because it is review-only evaluation, not an admitted runtime execution path.

It calls runtime.simulation.simulate, the same physics loop used by spo run.

It returns SimulationResult.

It exposes policy_enabled as the open/closed-loop switch.

It does not create an AuditLogger.

It does not write audit JSONL.

It does not open network connections.

It does not write to hardware.

It does not certify a clinical, consumer, or production deployment.

Use it for benchmark and case-study measurements where the same seed must be run open loop and closed loop through one shared implementation.

Minimal binding example

A minimal facade-compatible binding needs one or more layers.

It needs oscillator identifiers.

It needs natural frequencies or a binding default path that resolves them.

It needs coupling settings.

It needs physical, informational, and symbolic driver sections.

It needs objectives.

It needs no amplitude block.

It needs safety_tier: research.

A compact YAML shape is:

name: public-api-minimal
version: "1.0.0"
safety_tier: research
sample_period_s: 0.01
control_period_s: 0.1
layers:
  - name: L0
    index: 0
    oscillator_ids: [o0, o1, o2, o3]
    omegas: [1.0, 1.0, 1.0, 1.0]
oscillator_families:
  default:
    channel: P
    extractor_type: hilbert
    config: {}
coupling:
  base_strength: 0.4
  decay_alpha: 0.3
  templates: {}
drivers:
  physical: {}
  informational: {}
  symbolic: {}
objectives:
  good_layers: [0]
  bad_layers: []
boundaries: []
actuators: []

This example mirrors the dedicated public API regression tests.

It is suitable for demonstrating the facade contract.

It is not a domain-calibrated model.

Result record example

A compact record returned by to_record has this shape:

{
  "spec_name": "public-api-minimal",
  "steps": 8,
  "oscillator_count": 4,
  "order_parameter": 0.42,
  "mean_phase": 1.57,
  "sample_period_s": 0.01
}

The numerical values above are illustrative.

The actual values depend on the binding, seed, step count, and installed numerical implementation.

Do not treat this example as benchmark data.

Do not use this example as a reference synchronisation target.

Use the test suite and benchmark artefacts for verified numerical claims.

Testing contract

tests/test_public_api.py verifies the facade behaviour.

It verifies short-path imports through scpn.

It verifies long-path imports through scpn_phase_orchestrator.

It verifies deterministic repeated runs for a fixed seed.

It verifies state shapes.

It verifies the order parameter remains bounded.

It verifies Orchestrator.run rejects non-research bindings.

It verifies invalid step counts are rejected.

tests/test_simulation_core.py verifies evaluate_binding_spec behaviour.

It verifies gated Stuart-Landau specs can be evaluated without actuation.

It verifies open-loop and closed-loop evaluations share the same simulation core.

It verifies spo run delegates to the same simulate implementation.

tests/test_public_api_manifest.py guards package-root exports.

It verifies __all__ matches docs/specs/public_api_manifest.txt.

It verifies every manifest export resolves.

It verifies the API index lists every top-level manifest export.

tests/test_reference_api_api.py guards this page.

It verifies the API reference depth baseline.

It verifies the page documents the facade execution contract.

These tests are dedicated to the public API and Python facade surfaces.

They are not generic coverage tests.

They are not bucket tests.

They protect user-visible compatibility and documentation contracts.

Versioning and compatibility

The facade is part of the public API manifest.

Removing Orchestrator is a breaking change.

Removing OrchestratorState is a breaking change.

Changing Orchestrator.from_yaml semantics may be a breaking change.

Changing Orchestrator.run inputs may be a breaking change.

Changing the meaning of returned state fields may be a breaking change.

Adding optional fields to OrchestratorState requires compatibility review.

Adding new root exports requires manifest and documentation updates.

Changing the short alias requires application migration notes.

Changing the Orchestrator.run research-tier restriction requires safety review.

Expanding Orchestrator.run to additional models requires dedicated tests and documentation.

Expanding facade execution to hardware or network paths would violate the current facade contract and must not happen silently.

Troubleshooting

If import through scpn fails, confirm the package is installed from this repository or PYTHONPATH includes src.

If import through scpn_phase_orchestrator fails, confirm runtime dependencies are installed.

If from_yaml fails, run spo validate PATH to inspect binding errors.

If Orchestrator.run rejects the safety tier, check that the binding is intended for local research execution.

If Orchestrator.run rejects an amplitude block, use evaluate_binding_spec for review-only full-core evaluation or StuartLandauEngine directly for lower-level amplitude-state work.

If run rejects steps, pass a non-negative integer.

If run rejects seed, pass a non-negative integer.

If order-parameter values differ between environments, compare package versions and backend availability.

If downstream code needs full arrays, use the OrchestratorState fields directly.

If downstream code needs only summary data, use to_record.

If downstream code needs audit evidence, use the CLI audit and replay surfaces rather than this facade.

Design rationale

The facade is intentionally small.

Small public surfaces are easier to keep stable.

Small public surfaces are easier for downstream applications to adopt.

Small public surfaces avoid accidental activation of operator workflows.

The CLI remains the operator-facing orchestration surface.

The lower-level modules remain the research and engineering surfaces.

The facade bridges those surfaces for local application code.

It gives direct Python access to the common path without hiding safety boundaries.

The core invariant is simple: load a reviewed research binding, run local Kuramoto dynamics, return immutable state.

That invariant is what this page, the source module, and the tests protect.

API reference

::: scpn_phase_orchestrator.api