@@ -304,10 +304,10 @@ def robustdiff(x, dt, order, log_q, log_r, proc_huberM=6, meas_huberM=0):
304304 if np .linalg .cond (Q_d ) > 1e12 : Q_d += np .eye (order + 1 )* 1e-12 # for numerical stability with convex solver. Doesn't change answers appreciably (or at all).
305305
306306 x_states = convex_smooth (x , A_d , Q_d , C , R , proc_huberM , meas_huberM ) # outsource solution of the convex optimization problem
307- return x_states [0 ], x_states [1 ]
307+ return x_states [:, 0 ], x_states [:, 1 ]
308308
309309
310- def convex_smooth (y , A , Q , C , R , proc_huberM , meas_huberM ):
310+ def convex_smooth (y , A , Q , C , R , B = None , u = None , proc_huberM = 6 , meas_huberM = 0 ):
311311 """Solve the optimization problem for robust smoothing using CVXPY. Note this currently assumes constant dt
312312 but could be extended to handle variable step sizes by finding discrete-time A and Q for requisite gaps.
313313
@@ -316,21 +316,24 @@ def convex_smooth(y, A, Q, C, R, proc_huberM, meas_huberM):
316316 :param np.array Q: discrete-time process noise covariance matrix
317317 :param np.array C: measurement matrix
318318 :param np.array R: measurement noise covariance matrix
319+ :param np.array B: optional control matrix
320+ :param np.array u: optional control inputs, stacked in the direction of axis 0
319321 :param float proc_huberM: Huber loss parameter. :math:`M=0` reduces to :math:`\\ sqrt{2}\\ ell_1`.
320322 :param float meas_huberM: Huber loss parameter. :math:`M=\\ infty` reduces to :math:`\\ frac{1}{2}\\ ell_2^2`.
321323
322324 :return: (np.array) -- state estimates (state_dim x N)
323325 """
324326 N = len (y )
325327 x_states = cvxpy .Variable ((A .shape [0 ], N )) # each column is [position, velocity, acceleration, ...] at step n
328+ control = isinstance (B , np .ndarray ) and isinstance (u , np .ndarray ) # whether there is a control input
326329
327330 def huber_const (M ): # from https://jmlr.org/papers/volume14/aravkin13a/aravkin13a.pdf, with correction for missing sqrt
328331 a = 2 * np .exp (- M ** 2 / 2 )/ M # huber_const smoothly transitions Huber between 1-norm and 2-norm squared cases
329332 b = np .sqrt (2 * np .pi )* (2 * norm .cdf (M ) - 1 )
330333 return np .sqrt ((2 * a * (1 + M ** 2 )/ M ** 2 + b )/ (a + b ))
331334
332335 # It is extremely important to run time that CVXPY expressions be in vectorized form
333- proc_resids = np .linalg .inv (sqrtm (Q )) @ (x_states [:,1 :] - A @ x_states [:,:- 1 ]) # all Q^(-1/2)(x_n - A x_{n-1})
336+ proc_resids = np .linalg .inv (sqrtm (Q )) @ (x_states [:,1 :] - A @ x_states [:,:- 1 ] - ( 0 if not control else B @ u [ 1 :]. T )) # all Q^(-1/2)(x_n - ( A x_{n-1} + B u_n) )
334337 meas_resids = np .linalg .inv (sqrtm (R )) @ (y .reshape (C .shape [0 ],- 1 ) - C @ x_states ) # all R^(-1/2)(y_n - C x_n)
335338 # Process terms: sum of J(proc_resids)
336339 objective = 0.5 * cvxpy .sum_squares (proc_resids ) if proc_huberM == float ('inf' ) \
@@ -348,4 +351,4 @@ def huber_const(M): # from https://jmlr.org/papers/volume14/aravkin13a/aravkin13
348351 except cvxpy .error .SolverError : pass # Could try another solver here, like SCS, but slows things down
349352
350353 if x_states .value is None : return np .full ((A .shape [0 ], N ), np .nan ) # There can be solver failure, even without error
351- return x_states .value
354+ return x_states .value . T
0 commit comments