UPDE — Bayesian Uncertainty¶
Why Bayesian uncertainty exists in this project¶
Deterministic UPDE runs provide a single trajectory. Bayesian uncertainty surfaces how sensitive that trajectory is to uncertainty in estimated frequencies and couplings. For production planning and safety review, that difference is material.
The module exposes uncertainty in a structured way so teams can make explicit risk decisions rather than relying only on point estimates.
scpn_phase_orchestrator.upde.bayesian propagates uncertainty in natural
frequencies omega and coupling matrices K_nm through the existing UPDE
integrator. It reports posterior-predictive order parameter summaries as
R ± sigma plus a configurable credible interval.
The production backend is deterministic NumPy Monte Carlo over explicit array
distributions. numpyro and blackjax are reserved backend names and raise
NotImplementedError until their samplers are implemented, benchmarked, and
validated against the NumPy propagation baseline.
fit_gaussian_upde_posterior() provides the deterministic production baseline
for posterior fitting from observed Kuramoto phase trajectories. It uses a
finite-difference regression against the same UPDE coupling surface, enforces
non-negative zero-diagonal coupling, emits JSON-safe diagnostics, and feeds the
resulting Gaussian distributions directly into bayesian_upde_run().
audit_bayesian_backend_status() probes backend names through the same
execution path. The NumPy backend must execute, while reserved sampler names
such as numpyro and blackjax must fail closed with audit records until they
have validated implementations and benchmark evidence.
Minimal Example¶
import numpy as np
from scpn_phase_orchestrator.upde import (
BayesianUPDEConfig,
GaussianArrayDistribution,
bayesian_upde_run,
)
phases = np.array([0.0, 0.4, 1.1, 1.9])
omega_mean = np.array([0.9, 1.0, 1.08, 1.16])
knm_mean = np.full((4, 4), 0.18)
np.fill_diagonal(knm_mean, 0.0)
alpha = np.zeros((4, 4))
result = bayesian_upde_run(
phases,
omega=GaussianArrayDistribution(omega_mean, np.full(4, 0.015)),
knm=GaussianArrayDistribution(
knm_mean,
np.full((4, 4), 0.01),
non_negative=True,
zero_diagonal=True,
),
alpha=alpha,
zeta=0.02,
psi=0.1,
config=BayesianUPDEConfig(n_samples=256, seed=7, n_steps=25),
)
r_mean, r_sigma = result.r_plus_minus
Semantics¶
omegaandknmmay be deterministic arrays or distribution objects.GaussianArrayDistributionsamples independent normal uncertainty per array entry and can enforce non-negative coupling and zero self-coupling.fit_gaussian_upde_posterior()estimates GaussianomegaandK_nmdistributions from finite phase trajectories with explicit ridge and uncertainty floors.audit_bayesian_backend_status()records executable and fail-closed backend state for release and safety-review evidence.- The existing UPDE kernel performs every rollout, so deterministic engine validation, phase wrapping, and backend dispatch semantics are preserved.
BayesianUPDEResult.to_audit_record()emits JSON-safe uncertainty diagnostics suitable for safety review and replay logs.
How this is used operationally¶
Use Bayesian runs when estimates are sparse, noisy, or manually inferred. In that setting the output carries both central tendency and uncertainty so decision logic can enforce explicit safety thresholds before proposing changes.
Production interpretation¶
- Use Bayesian UPDE when uncertainty is itself a policy input, not an afterthought.
- The fail-closed backend status check is a compliance boundary: unavailable advanced samplers must not silently become “best effort” paths.
r_plus_minusis intended for risk-aware controller policy: a narrower sigma can justify higher coupling, while a wider sigma should force conservative action bounds.
Practical overview¶
Bayesian UPDE is the uncertainty surface for domains where a single trajectory is not enough to support operational action.
The module keeps one strict boundary: uncertainty must remain explicit in the
audit record. r_plus_minus is not a display-only value; it is intended to feed
risk-aware decision logic and conservative policy envelopes.
That is why backend-gated execution is important here. The code path refuses to promote incomplete uncertainty backends into a production claim while preserving the deterministic NumPy baseline as an auditable anchor.
How teams usually use this surface¶
- Start from deterministic fitting (
fit_gaussian_upde_posterior) on observed trajectories. - Pass distributions through
bayesian_upde_run. - Compare posterior spread against control limits before accepting aggressive knob proposals.
bayesian ¶
Uncertainty propagation for Kuramoto UPDE rollouts.
The shipped backend is deterministic NumPy Monte Carlo over explicit
distributions for omega and K_nm. Probabilistic-programming backends
are reserved as fail-closed names until their samplers are implemented and
benchmarked against this reproducible baseline.
Classes¶
GaussianArrayDistribution
dataclass
¶
GaussianArrayDistribution(
mean: object,
std: object,
non_negative: bool = False,
zero_diagonal: bool = False,
)
Independent Gaussian array distribution with optional matrix guards.
Attributes¶
shape
property
¶
Return the event shape sampled by this Gaussian distribution.
Returns¶
tuple[int, ...] Return the event shape sampled by this Gaussian distribution.
Methods:¶
sample ¶
Draw finite Gaussian samples with optional support guards applied.
Parameters¶
rng : np.random.Generator NumPy random generator used for sampling. n_samples : int Number of samples to draw.
Returns¶
FloatArray
Finite Gaussian samples, shape (n_samples, *event_shape).
Raises¶
TypeError
If rng is not a NumPy random generator.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
BayesianUPDEConfig
dataclass
¶
BayesianUPDEConfig(
n_samples: int = 128,
seed: int | None = None,
dt: float = 0.01,
n_steps: int = 1,
method: MethodName = "rk4",
credible_interval: float = 0.95,
backend: BackendName = "numpy",
n_substeps: int = 1,
atol: float = 1e-06,
rtol: float = 0.001,
)
Configuration for Bayesian UPDE uncertainty propagation.
BayesianUPDEResult
dataclass
¶
BayesianUPDEResult(
r_samples: FloatArray,
final_phase_samples: FloatArray,
omega_mean: FloatArray,
knm_mean: FloatArray,
r_mean: float,
r_sigma: float,
r_lower: float,
r_upper: float,
psi_mean: float,
sample_count: int,
credible_interval: float,
backend: str,
method: str,
)
Posterior predictive order-parameter summary.
Attributes¶
r_plus_minus
property
¶
Methods:¶
to_audit_record ¶
Return JSON-safe uncertainty diagnostics.
Returns¶
dict[str, object] Return JSON-safe uncertainty diagnostics.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
BayesianBackendStatus
dataclass
¶
BayesianBackendStatus(
backend: str,
available: bool,
fail_closed: bool,
reason: str,
sample_count: int,
)
Execution status for one Bayesian UPDE backend name.
Methods:¶
to_audit_record ¶
Return JSON-safe backend availability diagnostics.
Returns¶
dict[str, object] Return JSON-safe backend availability diagnostics.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
GaussianUPDEPosteriorFit
dataclass
¶
GaussianUPDEPosteriorFit(
omega: GaussianArrayDistribution,
knm: GaussianArrayDistribution,
residual_rmse: float,
sample_count: int,
dt: float,
ridge: float,
backend: str = "numpy_lstsq",
)
Gaussian posterior approximation fitted from observed phase trajectories.
Methods:¶
to_audit_record ¶
Return JSON-safe posterior-fit diagnostics.
Returns¶
dict[str, object] Return JSON-safe posterior-fit diagnostics.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
Functions:¶
fit_gaussian_upde_posterior ¶
fit_gaussian_upde_posterior(
phase_trajectory: object,
*,
dt: float,
alpha: object | None = None,
ridge: float = 1e-06,
coupling_std_floor: float = 1e-06,
omega_std_floor: float = 1e-06,
) -> GaussianUPDEPosteriorFit
Fit Gaussian omega and K_nm priors from observed phases.
The estimator is a deterministic NumPy ridge least-squares baseline. It fits the Kuramoto right-hand side independently per target oscillator:
d theta_i / dt = omega_i + sum_j K_ij sin(theta_j - theta_i - alpha_ij).
The result is intentionally review-only: it produces distributions that can
feed :func:bayesian_upde_run, but it does not apply control actions.
Parameters¶
phase_trajectory : object
Observed phase trajectory, shape (T, N).
dt : float
Integration step size.
alpha : object | None
Phase-lag matrix in radians, shape (N, N), or None for no lag.
ridge : float
Ridge-regularisation strength for the prior fit.
coupling_std_floor : float
Lower bound on the fitted coupling standard deviation.
omega_std_floor : float
Lower bound on the fitted natural-frequency standard deviation.
Returns¶
GaussianUPDEPosteriorFit
The fitted Gaussian omega and K_nm prior.
Raises¶
ValueError If the phase trajectory is empty or non-finite.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
audit_bayesian_backend_status ¶
audit_bayesian_backend_status(
phases: object,
*,
omega: object,
knm: object,
alpha: object,
zeta: float,
psi: float,
config: BayesianUPDEConfig | None = None,
backends: tuple[BackendName, ...] = (
"numpy",
"numpyro",
"blackjax",
),
) -> tuple[BayesianBackendStatus, ...]
Probe Bayesian backend names without silently accepting unsupported ones.
Parameters¶
phases : object
Oscillator phases in radians, shape (N,).
omega : object
Natural-frequency distribution or array.
knm : object
Coupling matrix K_nm, shape (N, N).
alpha : object
Phase-lag matrix in radians, shape (N, N). Use a zero matrix for no
lag.
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
config : BayesianUPDEConfig | None
Optional configuration object, or None for defaults.
backends : tuple[BackendName, ...]
Backend names to probe, in priority order.
Returns¶
tuple[BayesianBackendStatus, ...] Per-backend availability diagnostics.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
bayesian_upde_run ¶
bayesian_upde_run(
phases: object,
*,
omega: object,
knm: object,
alpha: object,
zeta: float,
psi: float,
config: BayesianUPDEConfig | None = None,
) -> BayesianUPDEResult
Run UPDE over sampled omega and K_nm distributions.
Parameters¶
phases : object
Oscillator phases in radians, shape (N,).
omega : object
Natural-frequency distribution or array.
knm : object
Coupling matrix K_nm, shape (N, N).
alpha : object
Phase-lag matrix in radians, shape (N, N). Use a zero matrix for no
lag.
zeta : float
External drive strength ζ.
psi : float
External drive reference phase Ψ in radians.
config : BayesianUPDEConfig | None
Optional configuration object, or None for defaults.
Returns¶
BayesianUPDEResult The Bayesian UPDE result with uncertainty diagnostics.
Raises¶
NotImplementedError If the requested backend is not implemented. ValueError If the sampled inputs are invalid.
Source code in src/scpn_phase_orchestrator/upde/bayesian.py
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 | |