-
Notifications
You must be signed in to change notification settings - Fork 177
Iterated Posterior Linearization Smoother (IPLS) #1029
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Steven Hiscocks <sdhiscocks@dstl.gov.uk>
# Conflicts: # stonesoup/functions/tests/test_functions.py
csherman-dstl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a number of places where changes have been made to parts of the codebase that are not parts of this PR. Otherwise can I confirm that all changes that have been made in #1016 are included within this PR?
| def matrix(self, **kwargs): return self.meas_matrix | ||
|
|
||
| def bias(self, **kwargs): return self.bias_value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put returns on different lines
| from stonesoup.types.array import Matrix | ||
| from stonesoup.types.state import StateVector |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imports should be at the top of the file
|
|
||
| return self.noise_covar | ||
|
|
||
| class GeneralLinearGaussian(MeasurementModel, LinearModel, GaussianModel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class can inherit most methods from LinearGaussian
| self.noise_covar = CovarianceMatrix(self.noise_covar) | ||
|
|
||
| @property | ||
| def ndim_state(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Identical to inherited method
|
|
||
| from ..linear import KnownTurnRateSandwich | ||
| from ..linear import ConstantVelocity | ||
| from ..linear import LinearTransitionModel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import is not used
stonesoup/types/prediction.py
Outdated
| MeasurementPrediction.register(CompositeState) # noqa: E305 | ||
|
|
||
|
|
||
| class AugmentedGaussianState(GaussianState): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class belongs in stonesoup.types.state
stonesoup/predictor/kalman.py
Outdated
| transition_model: TransitionModel = Property(doc="The transition model to be used.") | ||
| control_model: ControlModel = Property( | ||
| default=None, | ||
| doc="The control model to be used. Default `None` where the predictor " | ||
| "will create a zero-effect linear :class:`~.ControlModel`.") | ||
| alpha: float = Property( | ||
| default=0.5, | ||
| doc="Primary sigma point spread scaling parameter. Default is 0.5.") | ||
| beta: float = Property( | ||
| default=2, | ||
| doc="Used to incorporate prior knowledge of the distribution. If the " | ||
| "true distribution is Gaussian, the value of 2 is optimal. " | ||
| "Default is 2") | ||
| kappa: float = Property( | ||
| default=0, | ||
| doc="Secondary spread scaling parameter. Default is calculated as " | ||
| "3-Ns") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All properties are inherited from UnscentedKalmanPredictor
stonesoup/smoother/kalman.py
Outdated
| transition_model: TransitionModel = Property(doc="The transition model to be used.") | ||
| measurement_model: MeasurementModel = Property(default=None, doc="The measurement model to be used.") | ||
| alpha: float = Property( | ||
| default=0.5, | ||
| doc="Primary sigma point spread scaling parameter. Default is 0.5.") | ||
| beta: float = Property( | ||
| default=2, | ||
| doc="Used to incorporate prior knowledge of the distribution. If the " | ||
| "true distribution is Gaussian, the value of 2 is optimal. " | ||
| "Default is 2") | ||
| kappa: float = Property( | ||
| default=0, | ||
| doc="Secondary spread scaling parameter. Default is calculated as " | ||
| "3-Ns") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multiple properties are inherited from UnscentedKalamanSmoother
stonesoup/smoother/kalman.py
Outdated
| while True: | ||
| # we have no test of convergence, but limited the number of iterations | ||
| if len(smoothed_tracks) >= self.n_iterations: | ||
| # warnings.warn("IPLS reached pre-specified number of iterations.") | ||
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make more sense for this to just be a for loop?
|
@csherman-dstl, I’ve checked, and this PR does include the changes from #1016. I’ve applied the corrections you suggested and pushed the updates. |
| model = LinearTransitionModel( | ||
| transition_matrix=F, | ||
| bias_value=a_vector, | ||
| noise_covar=Q |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| noise_covar=Q | |
| covariance_matrix=Q |
The noise_covar property has been renamed to covariance_matrix but there are number of occurrences in the tests where this hasn't been changed.
| ), | ||
| ( # Augmented Kalman | ||
| AugmentedKalmanPredictor, | ||
| LinearTransitionModel( | ||
| transition_matrix=np.array([[0.8, 0.2], [0.3, 0.7]]), | ||
| bias_value=np.zeros([2, 1]), | ||
| noise_covar=np.diag([0.10961003, 0.88557178]) | ||
| ), | ||
| ( # Augmented Unscented Kalman | ||
| AugmentedUnscentedKalmanPredictor, | ||
| LinearTransitionModel( | ||
| transition_matrix=np.array([[0.8, 0.2], [0.3, 0.7]]), | ||
| bias_value=np.zeros([2, 1]), | ||
| noise_covar=np.diag([0.10961003, 0.88557178]) | ||
| ), | ||
| ], | ||
| ids=["standard", "extended", "unscented", "cubature", "stochasticIntegration"] | ||
| ) | ||
| ids=["standard", "extended", "unscented", "cubature", "stochasticIntegration", "augmented_kalman", "augmented_unscented_kalman"] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a mismatch in the number of brackets used here and also there are no given values for prior_mean and prior_covar
stonesoup/smoother/kalman.py
Outdated
| continue | ||
|
|
||
| """ Compute SLR parameters (transition). """ | ||
| from stonesoup.types.prediction import Prediction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| from stonesoup.types.prediction import Prediction |
Prediction is already imported in this file
stonesoup/smoother/kalman.py
Outdated
| ) | ||
| f_matrix, a_vector, lambda_cov_matrix = slr_definition(previous_state, trans_fun, force_symmetry=True) | ||
|
|
||
| "Perform linear time update" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# should be used for comments (this occurs multiple times in this method)
| "Perform linear time update" | |
| # Perform linear time update |
stonesoup/updater/iterated.py
Outdated
| from functools import partial | ||
| from ..types.state import State |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State is unused and from functools import partial should be near the top of the file
stonesoup/updater/iterated.py
Outdated
| break | ||
|
|
||
| # SLR is wrt to tne approximated posterior in post_state, not the original prior in hypothesis.prediction | ||
| meas_fun = partial(super().predict_measurement, measurement_model=measurement_model, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| meas_fun = partial(super().predict_measurement, measurement_model=measurement_model, | |
| meas_fun = partial(self.predict_measurement, measurement_model=measurement_model, |
As far as I can see predict_measurement has not been overwritten and so super() will call the same thing as just using self.
stonesoup/smoother/kalman.py
Outdated
| smoothed_tracks = [] | ||
|
|
||
| for iteration in range(self.n_iterations): | ||
|
|
||
| if iteration == 0: | ||
| # initialising by performing sigma-point smoothing via the UKF smoother | ||
| smoothed_track = UnscentedKalmanSmoother(transition_model=self.transition_model, | ||
| alpha=self.alpha, | ||
| beta=self.beta, | ||
| kappa=self.kappa).smooth(track) | ||
| smoothed_tracks.append(smoothed_track) | ||
| continue | ||
| else: | ||
| smoothed_track = smoothed_tracks[-1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| smoothed_tracks = [] | |
| for iteration in range(self.n_iterations): | |
| if iteration == 0: | |
| # initialising by performing sigma-point smoothing via the UKF smoother | |
| smoothed_track = UnscentedKalmanSmoother(transition_model=self.transition_model, | |
| alpha=self.alpha, | |
| beta=self.beta, | |
| kappa=self.kappa).smooth(track) | |
| smoothed_tracks.append(smoothed_track) | |
| continue | |
| else: | |
| smoothed_track = smoothed_tracks[-1] | |
| smoothed_track = UnscentedKalmanSmoother(transition_model=self.transition_model, | |
| alpha=self.alpha, | |
| beta=self.beta, | |
| kappa=self.kappa).smooth(track) | |
| for _ in range(self.n_iterations): |
I'm not sure of the need for the smoothed_tracks list, as far as I can tell only the latest version is ever used.
Also the smoothed_track can be initiated outside the loop instead of using the first iteration to only do this
|
@csherman-dstl, thank you for your feedback. I've implemented your comments, and pushed the updates. I see that there are a number of test failures (311, 312, 313) following the check, but they appear to be related to |
| Parameters | ||
| ---------- | ||
| prior : :class:`~.State` | ||
| Prior state, :math:`\mathbf{x}_{k-1}` | ||
| timestamp : :class:`datetime.datetime` | ||
| Time to transit to (:math:`k`) | ||
| **kwargs : various, optional | ||
| These are passed to :meth:`~.TransitionModel.covar` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should control_input and transition_noise be included in the docstring?
stonesoup/types/state.py
Outdated
|
|
||
|
|
||
| class AugmentedGaussianState(GaussianState): | ||
| """ This is a new GaussianState class that can also store information on cross-covariance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| """ This is a new GaussianState class that can also store information on cross-covariance | |
| """ This is a GaussianState class that can also store information on cross-covariance |
The class won't always be new
| assert timestamp == prediction.timestamp | ||
|
|
||
|
|
||
| def test_augmentedgaussianstateprediction(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this test check the value of cross_covar
stonesoup/updater/iterated.py
Outdated
| "KLDivergence between current and prior posterior state estimate.") | ||
| max_iterations: int = Property( | ||
| default=5, | ||
| doc="Number of iterations before while loop is exited and a non-convergence warning is " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this docstring still accurate? As far as I can see the warning is commented out.
|
@csherman-dstl, thanks again for the comments, all implemented now. The documentation build is currently failing due to the CI environment:
As noted above, the other test failures are not related to this pull request. |
This PR adds the following classes:
The Iterated Posterior Linearization Smoother (IPLS) described in [1] performs statistical linear regression (SLR) of the nonlinear transition and measurement models w.r.t. to the current posterior approximation.
A running example can be found here.
[1] Á. F. García-Fernández, L. Svensson and S. Särkkä, "Iterated Posterior Linearization Smoother," in IEEE Transactions on Automatic Control, vol. 62, no. 4, pp. 2056-2063, April 2017, doi: 10.1109/TAC.2016.2592681.