Skip to content

v-code01/postprior

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postprior

filterpy's SquareRootKalmanFilter exposes P_post, the posterior state covariance, as a read only property. Its body is a copy of the P_prior property and reconstructs the covariance from the prior square root factor _P1_2_prior instead of the posterior factor _P1_2_post. The filter maintains the posterior factor correctly in update, but P_post never reads it, so the posterior covariance a caller reads back is the prior covariance, unchanged by the measurement update.

The code

filterpy, filterpy/kalman/square_root.py, current master, the two properties side by side:

@property
def P_prior(self):
    """ covariance matrix of the prior"""
    return dot(self._P1_2_prior, self._P1_2_prior.T)

@property
def P_post(self):
    """ covariance matrix of the posterior"""
    return dot(self._P1_2_prior, self._P1_2_prior.T)   # copy of P_prior: uses the PRIOR factor

update maintains the posterior factor (self._P1_2_post = np.copy(self._P1_2)), but P_post reconstructs from _P1_2_prior, the same factor P_prior uses, so the two properties return the same matrix.

What it does

A square root filter stores the covariance as a factor F with P = F Ft. After a measurement update the posterior factor differs from the prior factor, so reconstructing from the prior factor returns the prior covariance:

P_prior:
[[4.  1. ]
 [1.  2.5]]
P_post buggy (prior factor):
[[4.  1. ]
 [1.  2.5]]
P_post fixed (post factor):
[[0.64 0.24]
 [0.24 1.3 ]]
buggy P_post equals P_prior: True

Driving the real library, a predict then a measurement update:

real SquareRootKalmanFilter: P_post == P_prior after update: True (max abs diff 0.0e+00)
KalmanFilter control: P_post == P_prior: False (prior trace 30.2, post trace 6.3133)
master reconstruction on real factors: buggy P_post is the true prior True; fix (posterior factor) is the true posterior True
buggy differs from fix: True; max error buggy vs true posterior 19.1474

On the real filter P_post is byte for byte equal to P_prior after the update. A plain KalmanFilter on the same problem separates them: its posterior trace 6.31 is well below its prior trace 30.2, because the measurement reduces uncertainty. The square root filter does maintain the posterior factor; reconstructing the covariance from it the way master reconstructs the prior, dot(F, F.T), gives exactly the plain filter's posterior, while the prior factor that P_post actually uses gives the plain filter's prior. The two differ by up to 19.1.

Scope

P_post is a documented public read only attribute of a shipped filter class. The failure is silent: it returns a finite positive semidefinite matrix, just the wrong one, the prior instead of the posterior, so any smoother, consistency check, or gating that reads the posterior covariance out of this filter sees a covariance that never reflects the measurement. It is not floating point round off: P_post and P_prior are byte identical because they execute the same expression. The fix mirrors the prior property with the posterior factor: return dot(self._P1_2_post, self._P1_2_post.T).

A note on versions: master reconstructs each covariance as dot(F, F.T), and the numbers above use that form; the installed 1.4.5 uses the transposed dot(F.T, F) in all of its P properties, so on 1.4.5 the prior and posterior covariances are themselves transposed relative to the true ones, a separate and older quirk. The copy of the prior factor into P_post is independent of it: P_post equals P_prior in both versions, which is what consequence.py shows first, directly on the installed filter.

Layout

  • excerpt.py: the two properties quoted from master with the flags that name the copied factor and the fix.
  • kalman_cov.py: the P = F Ft reconstruction with the buggy prior factor and the correct posterior factor, and the check that the posterior must differ from the prior after an update.
  • consequence.py: the real filter's P_post equal to P_prior, the KalmanFilter control separating them, and master's reconstruction on the real factors showing the buggy P_post is the true prior and the fix is the true posterior.
  • test_postprior.py: the model's buggy posterior equals the prior, and the real filter reproduces it while the control and the fix are correct.

Reproduce

python kalman_cov.py
python consequence.py
python test_postprior.py

The properties are quoted from current master; the covariances are produced by the real filterpy. The fix is to reconstruct P_post from _P1_2_post, the posterior factor the filter already maintains.

About

filterpy SquareRootKalmanFilter.P_post reconstructs from the prior sqrt-factor (copy of P_prior) instead of the maintained posterior factor, so the posterior covariance equals the prior

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages