Skip to content

v0.3.0 Stochastic Observability and Constructability, per-state regularization

Choose a tag to compare

@florisvb florisvb released this 07 Oct 17:51
· 3 commits to main since this release
28a08de

Stochastic observability and constructability, per-state regularization

Highlights

  • Process noise: stochastic observability and constructability. ObservabilityAnalysis has four new methods:

    • stochastic-observability-classic / -jax: Fisher information about the state at the start of each window;
    • stochastic-constructability-classic / -jax: about the state at the end of each window. Its inverse is the posterior Cramér-Rao bound, which is what a Kalman filter's error covariance tracks.

    With process noise Q, information about a state saturates instead of growing with every added measurement. The method follows B. Boyacioglu & F. van Breugel, Duality of Stochastic Observability and Constructability and their Relation to the Fisher Information, IEEE L-CSS (2025), doi:10.1109/LCSYS.2025.3547297.

    • The model is linearized along the trajectory, with finite differences (-classic) or JAX autodiff (-jax).
    • Q, R and lam can then be changed without calling run() again.
    oa = pybounds.ObservabilityAnalysis(sim, t, x, u, method='stochastic-constructability-classic',
                                        w=20, R={'r': 0.1}, Q={'g': 1e-3, 'd': 1e-6})
    ev = oa.run().min_error_variance()
    ev_noisier = oa.min_error_variance(Q=1e-2)   # no re-run needed
  • Per-state regularization. lam can be a dict keyed by state name, e.g. lam={'g': 1e-6, 'd': 1e-10}, or a 1-D array. Each state then gets its own ceiling 1/λᵢ on its minimum error variance. This is useful when states have very different units. It works for every method and storage mode.

  • alignment option. It sets where each window's result is placed in time:

    • 'center': the window's center, as before (the default);
    • 'bounded_state': the state the result actually bounds, i.e. the start of the window for observability and the end for constructability.
  • Bring your own linearization.

    • ObservabilityAnalysis.from_linearization(Phi, C, method=..., w=...) runs the stochastic methods on a linearized trajectory computed elsewhere, without a simulator.
    • analysis.linearization returns it as a frozen pybounds.Linearization.
    • Changing only the window size reuses the linearization, so sweeping w is cheap.
  • Helpers for choosing Q. analysis.deterministic_states() lists the states with no process noise, such as constant parameters and clocks; these are the ones to give a much smaller Q. analysis.model_state_names gives the names Q is keyed by.

  • Validation. validation/stochastic_duality_fig2.ipynb checks the recursions against the reference MATLAB implementation and redraws Fig. 2 of the letter.

  • New public names.

    • pybounds.stochastic: the recursions themselves, for linear time-varying systems.
    • pybounds.Linearization.
    • pybounds.SlidingO, the input type of from_sliding.

Behavior changes

  • Results of the existing methods are unchanged. For the same settings, bounds-empirical / bounds-jax (previously empirical / jax) give bit-identical results to 0.2.0.
  • Method names are now bounds-empirical and bounds-jax. 'empirical' and 'jax' still work as aliases, and 0.2.0 settings files load. Settings files saved by 0.3.0 use the new names, so 0.2.0 cannot read them.
  • ObservabilityMatrixImage now uses the cmap, vmin_ratio and vmax_percentile given to its constructor. They were silently ignored before.
    • The constructor's vmin_ratio default is now 0.0, the value that was actually in effect, so default plots look the same.
    • A value passed to plot() now stays in effect for later plot() calls.
  • compute_observability() picks the backend from the simulator. use_jax defaults to None; explicit True/False still work. Its finite-difference step remains eps=1e-4, while ObservabilityAnalysis defaults to 1e-5. Pass eps=1e-5 to match it exactly.
  • lam=None now means the default 1e-8 everywhere. It previously raised an error in ObservabilityAnalysis queries.
  • FisherObservability and SlidingFisherObservability accept a 1-D lam array. SlidingFisherObservability also has a new shift_index argument.

Fixes

  • 1-D u_sim: SlidingEmpiricalObservabilityMatrix no longer crashes on a 1-D u_sim array (a single input).
  • ObservabilityMatrixImage:
    • it now raises a clearer error when O is not a DataFrame;
    • numbered sensor names no longer contain a stray $.
  • CI: it now runs all JAX-dependent tests; previously, 11 tests outside the JAX test file never ran.

Upgrading

pip install --upgrade pybounds          # or: pip install --upgrade "pybounds[jax]"

No code changes are needed for existing analyses. Python 3.10+ is still required.

Full changelog: v0.2.0...v0.3.0 (PR #32)