Skip to content

Commit 179982d

Browse files
committed
added optional control input to , because it makes one of my scripts way more convenient, so it must be a good idea
1 parent 714c3da commit 179982d

1 file changed

Lines changed: 7 additions & 4 deletions

File tree

pynumdiff/kalman_smooth/_kalman_smooth.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)