Skip to content

Commit

Permalink
initialise Kalman filter to be time step 0
Browse files Browse the repository at this point in the history
It was off that, despite passing the initial state estimate to the constructor,
it wasn't actually used until the first predict step. Re-jig the ordering.
  • Loading branch information
rjw57 committed Apr 13, 2016
1 parent b5c80e9 commit 06df218
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 37 deletions.
9 changes: 6 additions & 3 deletions doc/partial_kalman.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,16 @@ noisy measurements.

# For each time step
for k, z in enumerate(measurements):
# Predict state for this timestep
kf.predict()

# Update filter with measurement
kf.update(measurement=MultivariateNormal(mean=z, cov=R),
measurement_matrix=H)

# Predict state for the next timestep
kf.predict()

# Truncate final prediction
kf.truncate(kf.state_count - 1)

The :py:class:`starman.KalmanFilter` class has a number of attributes which
give useful information on the filter:

Expand Down
3 changes: 2 additions & 1 deletion example/kalman_filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@
# For each time step
for k, z in enumerate(measurements):
# Predict state for this time step
kf.predict()
if k != 0:
kf.predict()

# Update filter with measurement
kf.update(MultivariateNormal(z, R), H)
Expand Down
46 changes: 15 additions & 31 deletions starman/stateestimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ class GaussianStateEstimation(object):
posteriori* state estimate for a time step is modified via the
:py:meth:`.update_posterior` method.
The object is initialised to have no state estimates. (Time step "-1" if you
will.) Before calling :py:meth:`.update_posterior`,
:py:meth:`.new_prediction` or :py:meth:`reset` must be called at least once.
The object is initialised with the initial state estimate. This is time step
"0". The first call to :py:meth:`.new_prediction` moves to time step 1, etc,
etc.
State estimates are represented via :py:class:`.MultivariateNormal`
instances.
Expand Down Expand Up @@ -86,25 +86,14 @@ class GaussianStateEstimation(object):
"""
def __init__(self, initial_state_estimate=None, state_length=None):
self._initial_state_estimate, self.state_length = \
initial_state_estimate, self.state_length = \
form_initial_state(initial_state_estimate, state_length)

# Initialise prior and posterior estimates
self.prior_state_estimates = []
self.posterior_state_estimates = []
self.prior_state_estimates = [initial_state_estimate]
self.posterior_state_estimates = [initial_state_estimate]

# No measurements just yet
self.measurements = []

def reset(self):
"""
Resets state estimates to timestep 0. The *a priori* and *a posteriori*
estimates are initialised from the initial state estimates passed at
construction time.
"""
self.prior_state_estimates = [self._initial_state_estimate]
self.posterior_state_estimates = [self._initial_state_estimate]
self.measurements = [[]]

def new_prediction(self, estimate):
Expand Down Expand Up @@ -183,12 +172,12 @@ class KalmanFilter(GaussianStateEstimation):
A KalmanFilter maintains an estimate of true state given noisy measurements.
It is a subclass of :py:class:`.GaussianStateEstimation`.
The filter is initialised to have no state estimates. (Time step "-1" if you
will.) Before calling :py:meth:`.update`, :py:meth:`.predict` must be called
at least once.
The filter is initialised with the initial state estimate. This is time step
"0". It is therefore valid to call :py:meth:`.update` before the first call
to :py:meth:`.predict`.
The filter represents its state estimates as frozen
:py:class:`.MultivariateNormal` instances.
The filter represents its state estimates as :py:class:`.MultivariateNormal`
instances.
Args:
initial_state_estimate (None or MultivariateNormal): The
Expand Down Expand Up @@ -246,11 +235,11 @@ def __init__(self, initial_state_estimate=None, process_matrix=None,
)

# No measurements just yet
self.measurement_matrices = []
self.measurement_matrices = [[]]

# Record of process matrices and covariances passed to predict()
self.process_matrices = []
self.process_covariances = []
self.process_matrices = [process_matrix]
self.process_covariances = [process_covariance]

def clone(self):
"""Return a new :py:class:`.KalmanFilter` instance which is a shallow
Expand All @@ -265,7 +254,7 @@ def clone(self):
"""
new_f = KalmanFilter(
initial_state_estimate=self._initial_state_estimate)
initial_state_estimate=self.prior_state_estimates[0])
new_f._defaults = self._defaults # pylint:disable=protected-access
new_f.state_length = self.state_length
new_f.prior_state_estimates = list(self.prior_state_estimates)
Expand Down Expand Up @@ -324,11 +313,6 @@ def predict(self, control=None, control_matrix=None,
self.process_matrices.append(process_matrix)
self.process_covariances.append(process_covariance)

# Special case: first call resets to timestep 0
if len(self.prior_state_estimates) == 0:
self.reset()
return

if control_matrix is not None:
control_matrix = np.atleast_2d(control_matrix)
if control is not None:
Expand Down
5 changes: 3 additions & 2 deletions test/test_kalman.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ def create_filter(true_states, measurements):

# For each time step
for k, z in enumerate(measurements):
# Predict
kf.predict()
# Predict for all but the first time step
if k != 0:
kf.predict()

# Update filter with measurement
kf.update(MultivariateNormal(mean=z, cov=R), H)
Expand Down

0 comments on commit 06df218

Please sign in to comment.