SolvFilter: Examples
Each example is a verified run of a guided tour script from the shipped python/examples/
bundle (see PROGRESSION.md for the full 01→06 sequence). Run the guided tour
first: it narrates the scenario, explains each metric, and shows when to trust the result. Use
the production one-liner when wiring the same API call into your pipeline.
Beta release. Products in this catalog other than the SolvSRK family and SolvScout / SolvTune are beta — suitable for trials and evaluation; APIs and packaging may change before GA. Do not deploy beta builds in production programs without a signed agreement with Resonix. Activate a trial license before running examples — see Install and Licensing.
Journeys
Example 1: Journey 01 — First contact
Version, license, and the smallest successful predict. Every support ticket starts with the package version. Machine-locked .lic files gate production use.
Walkthrough:
-
What you are doing — Confirm the native library loads, check your license, and run one prediction step so you know SolvFilter is alive before wiring it into a filter.
-
Version — Every support ticket starts with the package version.
version: 0.1.0package_version: 0.1.0
-
License — Machine-locked .lic files gate production use.
license_valid: True
-
Simplest call — A constant-velocity target starts at the origin moving (1, 2) m/s. One predict step of dt=0.5 s should advance the position to (0.5, 1.0) and grow the position uncertainty.
x_pred: [0.5, 1.0, 1.0, 2.0]pos_var_before: 100.0pos_var_after: 102.501— Position advanced by velocity*dt and the position variance grew: the predict works.
Guided tour output (from a verified run):
Version, license, and the smallest successful predict
-- Act 1 - What you are doing --
-> Confirm the native library loads, check your license, and run one prediction step so you know SolvFilter is alive before wiring it into a filter.
-- Act 2 - Version --
-> Every support ticket starts with the package version.
version: 0.1.0
package_version: 0.1.0
-- Act 3 - License --
-> Machine-locked .lic files gate production use.
license_valid: True
-- Act 4 - Simplest call --
-> A constant-velocity target starts at the origin moving (1, 2) m/s. One predict step of dt=0.5 s should advance the position to (0.5, 1.0) and grow the position uncertainty.
x_pred: [0.5, 1.0, 1.0, 2.0]
pos_var_before: 100.0
pos_var_after: 102.501
-> Position advanced by velocity*dt and the position variance grew: the predict works.
-- Run complete --
next_journey: 02_predict.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/01_first_contact.py
# Production one-liner — same API call you ship:
python -c "import solvfilter; print(solvfilter.version(), solvfilter.license_valid())"Example 2: Journey 02 — The predict step
Propagate mean + covariance | EKF vs UKF agree on linear dynamics. mean+covariance propagation, EKF/UKF agreement on linear dynamics.
Walkthrough:
-
What predict does — predict() answers 'where will the target be, and how sure am I?'. It propagates the mean through your dynamics (RK4) and grows the covariance P -> F P F^T + Q, where F linearises your dynamics over dt.
x_pred: [0.5, 1.0, 1.0, 2.0]pos_var_grew: Truesymmetric: True— Covariance stays symmetric and position variance increases with time.
-
EKF and UKF should agree on linear dynamics — On a linear system the Extended and Unscented filters must give the same answer. If they diverge on something linear, something is wrong.
ekf_mean: [1.092614, 0.237052]ukf_mean: [1.092614, 0.237052]analytic_rotation: [1.092614, 0.237052]ekf_ukf_agree: True— Both filters match the closed-form rotation to RK4 truncation.
-
Knobs that matter — rk4_substeps trades speed for propagation accuracy; iekf_iters>0 turns on the iterated EKF for stiff nonlinear dynamics; use_ukf switches to sigma points.
rk4_substeps: 8iekf_iters: 0use_ukf: False
Guided tour output (from a verified run):
Propagate mean + covariance | EKF vs UKF agree on linear dynamics
-- Act 1 - What predict does --
-> predict() answers 'where will the target be, and how sure am I?'. It propagates the mean through your dynamics (RK4) and grows the covariance P -> F P F^T + Q, where F linearises your dynamics over dt.
x_pred: [0.5, 1.0, 1.0, 2.0]
pos_var_grew: True
symmetric: True
-> Covariance stays symmetric and position variance increases with time.
-- Act 2 - EKF and UKF should agree on linear dynamics --
-> On a linear system the Extended and Unscented filters must give the same answer. If they diverge on something linear, something is wrong.
ekf_mean: [1.092614, 0.237052]
ukf_mean: [1.092614, 0.237052]
analytic_rotation: [1.092614, 0.237052]
ekf_ukf_agree: True
-> Both filters match the closed-form rotation to RK4 truncation.
-- Act 3 - Knobs that matter --
-> rk4_substeps trades speed for propagation accuracy; iekf_iters>0 turns on the iterated EKF for stiff nonlinear dynamics; use_ukf switches to sigma points.
rk4_substeps: 8
iekf_iters: 0
use_ukf: False
-- Run complete --
next_journey: 03_update.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/02_predict.py
# Production one-liner — same API call you ship:
python -c "import numpy as np, solvfilter as s; cfg=s.Config(4, lambda t,x: np.array([x[2],x[3],0,0])); print(s.predict(cfg, np.array([0,0,1,2.]), np.diag([100,100,10,10.]), np.eye(4)*1e-3, 0.5)[0])"Example 3: Journey 03 — The Joseph measurement update
Fuse a noisy sensor reading | covariance shrinks and stays SPD. fusing a noisy measurement, covariance shrink, SPD preservation, innovation.
Walkthrough:
-
Predict, then measure — We predict the target forward, then a position sensor returns a noisy reading z. update() fuses z into the estimate using the Joseph form, which keeps the covariance symmetric positive-definite even after many steps.
-
What the update returns — Posterior mean/covariance plus the innovation nu and its covariance S.
x_post: [0.548122, 1.096244, 1.002347, 2.004695]innovation_nu: [0.05, 0.1]pos_var_before: 102.501pos_var_after: 3.8498
-
Did the measurement help? — A good update reduces uncertainty (position variance drops) and preserves a valid covariance (symmetric). Both must hold.
variance_reduced: Truesymmetric: Trueinnovation_matches_prediction: True— The sensor pulled the estimate toward z and shrank the covariance -> the fusion works.
Guided tour output (from a verified run):
Fuse a noisy sensor reading | covariance shrinks and stays SPD
-- Act 1 - Predict, then measure --
-> We predict the target forward, then a position sensor returns a noisy reading z. update() fuses z into the estimate using the Joseph form, which keeps the covariance symmetric positive-definite even after many steps.
-- Act 2 - What the update returns --
-> Posterior mean/covariance plus the innovation nu and its covariance S.
x_post: [0.548122, 1.096244, 1.002347, 2.004695]
innovation_nu: [0.05, 0.1]
pos_var_before: 102.501
pos_var_after: 3.8498
-- Act 3 - Did the measurement help? --
-> A good update reduces uncertainty (position variance drops) and preserves a valid covariance (symmetric). Both must hold.
variance_reduced: True
symmetric: True
innovation_matches_prediction: True
-> The sensor pulled the estimate toward z and shrank the covariance -> the fusion works.
-- Run complete --
next_journey: 04_consistency.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/03_update.py
# Production one-liner — same API call you ship:
python -c "import numpy as np, solvfilter as s; o=s.update(np.array([0.5,1,1,2.]), np.diag([50,50,10,10.]), np.array([0.55,1.1]), np.array([[1.,0,0,0],[0,1.,0,0]]), np.eye(2)*4.); print(o['x'], o['P'][0,0])"Example 4: Journey 04 — Consistency diagnostics (NEES / NIS)
NEES answers 'is my covariance honest?' | NIS answers 'is R honest?'. whether your covariance and measurement noise are honest.
Walkthrough:
-
Why diagnostics matter — A filter can look confident and still be wrong. NEES compares the true error against the reported covariance; NIS compares innovations against S. For a consistent filter, NEES ~ n (state dim) and NIS ~ m (measurement dim).
-
NEES on textbook cases — Normalised Estimation Error Squared: e^T P^-1 e.
nees_unit_error_unit_cov: 1.0nees_scaled: 2.0— Both equal their analytic values (1.0 and 2.0) - the diagnostic is exact.
-
NIS on a textbook case — Normalised Innovation Squared: nu^T S^-1 nu.
nis_value: 1.0
-
Read it like a filter engineer — Now interpret a realistic 4-state estimate against its covariance and a 2-D innovation against its S.
nees: 0.003expected_mean: 4— NEES well below n: the filter is over-confident-safe (covariance too large).nis: 0.0001expected_mean: 2— NIS off m: revisit R (measurement noise) or the observation model H.
Guided tour output (from a verified run):
NEES answers 'is my covariance honest?' | NIS answers 'is R honest?'
-- Act 1 - Why diagnostics matter --
-> A filter can look confident and still be wrong. NEES compares the true error against the reported covariance; NIS compares innovations against S. For a consistent filter, NEES ~ n (state dim) and NIS ~ m (measurement dim).
-- Act 2 - NEES on textbook cases --
-> Normalised Estimation Error Squared: e^T P^-1 e.
nees_unit_error_unit_cov: 1.0
nees_scaled: 2.0
-> Both equal their analytic values (1.0 and 2.0) - the diagnostic is exact.
-- Act 3 - NIS on a textbook case --
-> Normalised Innovation Squared: nu^T S^-1 nu.
nis_value: 1.0
-- Act 4 - Read it like a filter engineer --
-> Now interpret a realistic 4-state estimate against its covariance and a 2-D innovation against its S.
nees: 0.003
expected_mean: 4
-> NEES well below n: the filter is over-confident-safe (covariance too large).
nis: 0.0001
expected_mean: 2
-> NIS off m: revisit R (measurement noise) or the observation model H.
-- Run complete --
next_journey: 05_track_cv.pycd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/04_consistency.py
# Production one-liner — same API call you ship:
python -c "import numpy as np, solvfilter as s; print(s.nees([1,0.],[0,0.],np.eye(2)), s.nis([1,2.],np.diag([2.,8.])))"Example 5: Journey 05 — 05 Track Cv
A target moves at (3, -1.5) m/s. Our estimate starts deliberately wrong at (5, -5). A position sensor with 2 m noise reports every 0.1 s. Can the filter lock on and stay consistent?. predict -> simulate a noisy measurement -> Joseph update, 200 times. How close did we track, and was the reported uncertainty honest?.
Walkthrough:
-
The scenario — A target moves at (3, -1.5) m/s. Our estimate starts deliberately wrong at (5, -5). A position sensor with 2 m noise reports every 0.1 s. Can the filter lock on and stay consistent?
-
Run the loop — predict -> simulate a noisy measurement -> Joseph update, 200 times.
-
Results — How close did we track, and was the reported uncertainty honest?
position_rmse_m: 0.9441meas_sigma_m: 2.0steps: 200— RMSE well below the raw sensor sigma means the filter is extracting signal from noise.nees: 3.0862expected_mean: 4— NEES near n=4: covariance and error agree -> the filter is consistent.
Guided tour output (from a verified run):
200 steps @ 10 Hz | position-only sensor | RMSE + mean NEES
-- Act 1 - The scenario --
-> A target moves at (3, -1.5) m/s. Our estimate starts deliberately wrong at (5, -5). A position sensor with 2 m noise reports every 0.1 s. Can the filter lock on and stay consistent?
-- Act 2 - Run the loop --
-> predict -> simulate a noisy measurement -> Joseph update, 200 times.
-- Act 3 - Results --
-> How close did we track, and was the reported uncertainty honest?
position_rmse_m: 0.9441
meas_sigma_m: 2.0
steps: 200
-> RMSE well below the raw sensor sigma means the filter is extracting signal from noise.
nees: 3.0862
expected_mean: 4
-> NEES near n=4: covariance and error agree -> the filter is consistent.
-- Run complete --
next_journey: 06_limits.py
position_rmse_m: 0.9441cd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/05_track_cv.py
# Production one-liner — same API call you ship:
python examples/journeys/05_track_cv.pyExample 6: Journey 06 — Limits: mean propagation vs SolvSRK
predict_with (bring your own mean) | when to reach for SolvSRK. Propagate the mean with whatever integrator is right for your dynamics, then pass that mean to predict_with. SolvFilter linearises rhs at the prior to build F and returns P_pred = F P F^T + Q; the returned mean is exactly yours. Consistency checks need an invertible covariance. Ask for NEES with a singular P and SolvFilter refuses instead of returning garbage.
Walkthrough:
-
Where the built-in propagator stops — predict() uses RK4 on your rhs. That is ideal for tracking (constant velocity, coordinated turns) but not for stiff systems (fast chemistry, electrical transients) where you need an implicit integrator like SolvSRK.
-
predict_with: covariance-only predict — Propagate the mean with whatever integrator is right for your dynamics, then pass that mean to predict_with. SolvFilter linearises rhs at the prior to build F and returns P_pred = F P F^T + Q; the returned mean is exactly yours.
supplied_mean: [0.5, 1.0, 1.0, 2.0]returned_mean: [0.5, 1.0, 1.0, 2.0]mean_is_yours: Truecovariance_grew: True— SolvFilter did the covariance bookkeeping; your integrator owned the mean.
-
Honest refusal: a degenerate diagnostic — Consistency checks need an invertible covariance. Ask for NEES with a singular P and SolvFilter refuses instead of returning garbage.
refused_as_expected: Trueerror: solvfilter.nees: singular— A singular covariance is caught, not silently inverted.
-
What SolvFilter is NOT — Not a stiff-ODE integrator (use SolvSRK for the mean), not a smoother or batch estimator, not a data association / multi-target tracker. It is the single-target predict+update core you compose those around.
Guided tour output (from a verified run):
predict_with (bring your own mean) | when to reach for SolvSRK
-- Act 1 - Where the built-in propagator stops --
-> predict() uses RK4 on your rhs. That is ideal for tracking (constant velocity, coordinated turns) but not for stiff systems (fast chemistry, electrical transients) where you need an implicit integrator like SolvSRK.
-- Act 2 - predict_with: covariance-only predict --
-> Propagate the mean with whatever integrator is right for your dynamics, then pass that mean to predict_with. SolvFilter linearises rhs at the prior to build F and returns P_pred = F P F^T + Q; the returned mean is exactly yours.
supplied_mean: [0.5, 1.0, 1.0, 2.0]
returned_mean: [0.5, 1.0, 1.0, 2.0]
mean_is_yours: True
covariance_grew: True
-> SolvFilter did the covariance bookkeeping; your integrator owned the mean.
-- Act 3 - Honest refusal: a degenerate diagnostic --
-> Consistency checks need an invertible covariance. Ask for NEES with a singular P and SolvFilter refuses instead of returning garbage.
refused_as_expected: True
error: solvfilter.nees: singular
-> A singular covariance is caught, not silently inverted.
-- Act 4 - What SolvFilter is NOT --
-> Not a stiff-ODE integrator (use SolvSRK for the mean), not a smoother or batch estimator, not a data association / multi-target tracker. It is the single-target predict+update core you compose those around.
-- Run complete --
next_journey: (end of suite) - see examples/PROGRESSION.mdcd python
# Guided tour — narrated scenario walkthrough (recommended first run):
python examples/journeys/06_limits.py
# Production one-liner — same API call you ship:
python -c "import numpy as np, solvfilter as s; cfg=s.Config(4, lambda t,x: np.array([x[2],x[3],0,0])); print(s.predict_with(cfg, np.array([0,0,1,2.]), np.eye(4)*10, np.eye(4)*1e-3, 0.5, np.array([0.5,1,1,2.]))[0])"Example bundle
Every journey is a domain scenario with narrated acts — run the guided tour first to see what each number means, then copy the production one-liner into your pipeline.
| Resource | Purpose |
|---|---|
PROGRESSION.md | Ordered runbook — journeys 01→06 with dual commands |
COVERAGE.md | Capability matrix — which APIs each journey exercises |
APPLICATIONS.md | Where the product applies in real programs |
run_examples.py | Interactive menu to launch any journey |
Guided journeys
| # | Script | Scenario |
|---|---|---|
| 01 | 01_first_contact.py | Version, license, and the smallest successful predict |
| 02 | 02_predict.py | Propagate mean + covariance |
| 03 | 03_update.py | Fuse a noisy sensor reading |
| 04 | 04_consistency.py | NEES answers 'is my covariance honest?' |
| 05 | 05_track_cv.py | A target moves at (3, -1.5) m/s. Our estimate starts deliberately wrong at (5, -5). A posi… |
| 06 | 06_limits.py | predict_with (bring your own mean) |
Run from the python/ directory after install and license activation.
Set SOLVFILTER_QUIET=1 only when you want silent CLI runs (no narration).