Repository navigation
Merge Develop - #10
Merged
Merged
Conversation
Updating tests
…re/prebuilt_filters
P = (I - KH) P (I - KH)^T + K R K^T instead of (I - KH) P. Equivalent for the optimal gain in exact arithmetic, but keeps P symmetric and positive semi-definite under floating point rounding. Applied to LinearUpdate, NonLinearUpdate and the Python ExtendedKalmanFilter.
State(value, *, covariance, epoch), Observation(value, *, epoch), TransitionModel(model, *, state) and UpdateModel(model, *, transition, measurement). Prevents silently swapping same-typed arguments. Breaking: positional use of the trailing arguments now raises TypeError. All tests, examples and the README are updated.
…ructor - f, jac_f, h and jac_h now take (x, t), where t is the integer epoch: f advances the state from t to t + 1 and h is evaluated at the measurement epoch, so models may be time-varying. Autodiff still differentiates x. - ExtendedKalmanFilter(state, *, f, jac_f, q, h, jac_h, r) is keyword-only. - Document that returned states are live zero-copy views of the filter state. - Tests for epoch passing, live views, Joseph-form symmetry on a badly scaled problem and keyword-only enforcement. - README: Python usage notes and a roadmap (gating, UD filter, automatic Jacobians).
The models were const-generic (SMatrix<N, M>), so they could only be used with sizes known at compile time and the Python bindings could not wrap them. They are now generic over na::Dim, so one implementation serves both statically sized (na::Const<N>) and runtime sized (na::Dyn) matrices. - LinearTransition/Measurement/Update, NonLinearTransition/Measurement/Update and KalmanFilterT take N, M: na::Dim. A FilterAllocator<N, M> helper trait keeps the nalgebra allocator bounds readable. - Statically sized aliases follow the existing StateN/KalmanFilterNM pattern: LinearTransitionN<N>, LinearUpdateNM<N, M>, NonLinearMeasurementNM<N, M>... plus KalmanFilterDyn and ExtendedKalmanFilterDyn. Rust call sites use them. - The measurement update (gain + Joseph form covariance), previously copied in LinearUpdate and NonLinearUpdate, is shared in models::update. - Nonlinear models run the user closures on a copy of the state instead of holding the state lock, so a closure that reads the state cannot deadlock. The closure types are now Send + Sync. - moore_penrose_right_inverse computed the left inverse, which fails for a wide H; moore_penrose_inverse uses the left inverse or else the right one. - Tests: the Dyn filters match the statically sized ones exactly.
The Python filters were reimplementations: KalmanFilter only delegated to a Python UpdateModel (so the maths ran in Python), and ExtendedKalmanFilter had its own copy of the EKF equations. Each Python filter is now a thin wrapper around a Rust filter type: KalmanFilterDyn -> KalmanFilter ExtendedKalmanFilterDyn -> ExtendedKalmanFilter KalmanFilterT<Dyn, Dyn, PyUpdateModel> -> CustomFilter - dt.KalmanFilter(state, *, f, q, h, r) takes numpy matrices and runs in Rust. - dt.ExtendedKalmanFilter passes the Python model functions to the Rust NonLinear* models as closures. An exception (or wrong shape) in a model function restores the filter state and is re-raised. - The delegating filter is renamed CustomFilter. Breaking: callers of dt.KalmanFilter(update) must use dt.CustomFilter(update). - State setters copy the incoming state before locking, so assigning a state to itself no longer deadlocks. - The manim demo uses the Rust-computed KalmanFilter. README documents the three filters.
src/python had grown inconsistently (an empty linear.rs, MeasurementModel in measurement.rs next to Observation, filters split over three files). Code is moved, not changed: - epoch.rs, state.rs, measurement.rs: Epoch, State, Observation, matching crate::epoch, crate::state and crate::measurement. - models.rs: the Python-implemented TransitionModel, MeasurementModel and UpdateModel (was transition.rs, update.rs and half of measurement.rs). - algorithms.rs: KalmanFilter, ExtendedKalmanFilter, CustomFilter (was kalman.rs, ekf.rs and algorithms.rs). - callbacks.rs: Python model functions as Rust closures (was in ekf.rs). - arrays.rs and logging.rs are unchanged; linear.rs is removed. Tests are renamed to what they test: src/tests/test_custom_filter.rs (which tests KalmanFilterNM) is now test_kalman.rs, and tests_py/test_algorithm.py (which tests CustomFilter) is now test_custom_filter.py.
The model traits required methods that most filters never use, so models had to provide them anyway. The core traits now hold only what every filter needs, and the rest are separate capability traits: - TransitionModel: state() - MeasurementModel: projection() and the new noise(), the measurement noise covariance R - UpdateModel: state() and apply(); jacobian() is removed (it was unused) - TransitionJacobian, MeasurementJacobian: jacobian(), implemented by the linear and nonlinear models; the EKF update uses MeasurementJacobian - InverseMeasurement: inverse(), implemented by the linear (pseudo-inverse) and nonlinear (with_inverse) measurements The update models read R through noise() and H through jacobian(). The Python model wrappers mirror the Rust traits: MeasurementModel gains noise() (calls the Python object's noise()), and UpdateModel.jacobian is removed. Breaking for Python models: a MeasurementModel used where R is needed must provide noise(), and UpdateModel no longer has jacobian().
UDKalmanFilter and UDExtendedKalmanFilter hold the covariance as factors P = U D U^T and propagate and update the factors directly, following Ramos et al., "A summary on the UD Kalman Filter" (arXiv:2203.06105): - initialise: udu(P) (new udu_decomposition in common::linalg) - time update: weighted modified Gram-Schmidt on W = [F U, G], weights diag(D, D_Q), with Q = G D_Q G^T factorised the same way so Q need not be diagonal; W is kept as two N x N blocks, so static sizes stay allocation-free - measurement update: scalar measurements processed sequentially with the modified Agee-Turner update, after decorrelating a non-diagonal R LinearUDUpdate and NonLinearUDUpdate reuse the existing linear and nonlinear models (no trait changes); aliases UDKalmanFilterNM/Dyn and UDExtendedKalmanFilterNM/Dyn. The state still carries P = U D U^T after each step; if it is changed from outside, the factors are recomputed from it. Backward prediction is not supported. Python: dt.UDKalmanFilter(state, *, f, q, h, r) and dt.UDExtendedKalmanFilter(state, *, f, jac_f, q, h, jac_h, r). Tests: match the Kalman and extended filters to rounding (non-diagonal Q, diagonal and correlated R, several measurements, epoch gaps), stay PSD on a badly scaled problem, recover after outside changes and Python errors, and Dyn matches static.
…ndling. In these tests we expect warnings and error messages to pop up in the logs, but this can make it confusion to track down real errors in the tests. We silent these logs during exception tests with a simple helper.
Prebuilt Filters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Latest version merge