# cython: language_level=3 # cython: profile=True # distutils: language = c++ cimport cython from libcpp.string cimport string from libcpp.vector cimport vector from libcpp cimport bool cimport numpy as np import numpy as np cdef extern from "" namespace "std" nogil: cdef cppclass optional[T]: ctypedef T value_type bool has_value() T& value() cdef extern from "rednose/helpers/ekf_load.h": cdef void ekf_load_and_register(string directory, string name) cdef extern from "rednose/helpers/ekf_sym.h" namespace "EKFS": cdef cppclass MapVectorXd "Eigen::Map": MapVectorXd(double*, int) cdef cppclass MapMatrixXdr "Eigen::Map >": MapMatrixXdr(double*, int, int) cdef cppclass VectorXd "Eigen::VectorXd": VectorXd() double* data() int rows() cdef cppclass MatrixXdr "Eigen::Matrix": MatrixXdr() double* data() int rows() int cols() ctypedef struct Estimate: VectorXd xk1 VectorXd xk MatrixXdr Pk1 MatrixXdr Pk double t int kind vector[VectorXd] y vector[VectorXd] z vector[vector[double]] extra_args cdef cppclass EKFSym: EKFSym(string name, MapMatrixXdr Q, MapVectorXd x_initial, MapMatrixXdr P_initial, int dim_main, int dim_main_err, int N, int dim_augment, int dim_augment_err, vector[int] maha_test_kinds, vector[int] quaternion_idxs, vector[string] global_vars, double max_rewind_age) void init_state(MapVectorXd state, MapMatrixXdr covs, double filter_time) VectorXd state() MatrixXdr covs() void set_filter_time(double t) double get_filter_time() void set_global(string name, double val) void reset_rewind() void predict(double t) optional[Estimate] predict_and_update_batch(double t, int kind, vector[MapVectorXd] z, vector[MapMatrixXdr] z, vector[vector[double]] extra_args, bool augment) # Functions like `numpy_to_matrix` are not possible, cython requires default # constructor for return variable types which aren't available with Eigen::Map @cython.wraparound(False) @cython.boundscheck(False) cdef np.ndarray[np.float64_t, ndim=2, mode="c"] matrix_to_numpy(MatrixXdr arr): cdef double[:,:] mem_view = arr.data() return np.copy(np.asarray(mem_view, dtype=np.double, order="C")) @cython.wraparound(False) @cython.boundscheck(False) cdef np.ndarray[np.float64_t, ndim=1, mode="c"] vector_to_numpy(VectorXd arr): cdef double[:] mem_view = arr.data() return np.copy(np.asarray(mem_view, dtype=np.double, order="C")) cdef class EKF_sym_pyx: cdef EKFSym* ekf def __cinit__(self, str gen_dir, str name, np.ndarray[np.float64_t, ndim=2] Q, np.ndarray[np.float64_t, ndim=1] x_initial, np.ndarray[np.float64_t, ndim=2] P_initial, int dim_main, int dim_main_err, int N=0, int dim_augment=0, int dim_augment_err=0, list maha_test_kinds=[], list quaternion_idxs=[], list global_vars=[], double max_rewind_age=1.0, logger=None): # TODO logger ekf_load_and_register(gen_dir.encode('utf8'), name.encode('utf8')) cdef np.ndarray[np.float64_t, ndim=2, mode='c'] Q_b = np.ascontiguousarray(Q, dtype=np.double) cdef np.ndarray[np.float64_t, ndim=1, mode='c'] x_initial_b = np.ascontiguousarray(x_initial, dtype=np.double) cdef np.ndarray[np.float64_t, ndim=2, mode='c'] P_initial_b = np.ascontiguousarray(P_initial, dtype=np.double) self.ekf = new EKFSym( name.encode('utf8'), MapMatrixXdr( Q_b.data, Q.shape[0], Q.shape[1]), MapVectorXd( x_initial_b.data, x_initial.shape[0]), MapMatrixXdr( P_initial_b.data, P_initial.shape[0], P_initial.shape[1]), dim_main, dim_main_err, N, dim_augment, dim_augment_err, maha_test_kinds, quaternion_idxs, [x.encode('utf8') for x in global_vars], max_rewind_age ) def init_state(self, np.ndarray[np.float64_t, ndim=1] state, np.ndarray[np.float64_t, ndim=2] covs, filter_time): cdef np.ndarray[np.float64_t, ndim=1, mode='c'] state_b = np.ascontiguousarray(state, dtype=np.double) cdef np.ndarray[np.float64_t, ndim=2, mode='c'] covs_b = np.ascontiguousarray(covs, dtype=np.double) self.ekf.init_state( MapVectorXd( state_b.data, state.shape[0]), MapMatrixXdr( covs_b.data, covs.shape[0], covs.shape[1]), np.nan if filter_time is None else filter_time ) def state(self): cdef np.ndarray res = vector_to_numpy(self.ekf.state()) return res def covs(self): return matrix_to_numpy(self.ekf.covs()) def set_filter_time(self, double t): self.ekf.set_filter_time(t) def get_filter_time(self): return self.ekf.get_filter_time() def set_global(self, str global_var, double val): self.ekf.set_global(global_var.encode('utf8'), val) def reset_rewind(self): self.ekf.reset_rewind() def predict(self, double t): self.ekf.predict(t) def predict_and_update_batch(self, double t, int kind, z, R, extra_args=[[]], bool augment=False): cdef vector[MapVectorXd] z_map cdef np.ndarray[np.float64_t, ndim=1, mode='c'] zi_b for zi in z: zi_b = np.ascontiguousarray(zi, dtype=np.double) z_map.push_back(MapVectorXd( zi_b.data, zi.shape[0])) cdef vector[MapMatrixXdr] R_map cdef np.ndarray[np.float64_t, ndim=2, mode='c'] Ri_b for Ri in R: Ri_b = np.ascontiguousarray(Ri, dtype=np.double) R_map.push_back(MapMatrixXdr( Ri_b.data, Ri.shape[0], Ri.shape[1])) cdef vector[vector[double]] extra_args_map cdef vector[double] args_map for args in extra_args: args_map.clear() for a in args: args_map.push_back(a) extra_args_map.push_back(args_map) cdef optional[Estimate] res = self.ekf.predict_and_update_batch(t, kind, z_map, R_map, extra_args_map, augment) if not res.has_value(): return None cdef VectorXd tmpvec return ( vector_to_numpy(res.value().xk1), vector_to_numpy(res.value().xk), matrix_to_numpy(res.value().Pk1), matrix_to_numpy(res.value().Pk), res.value().t, res.value().kind, [vector_to_numpy(tmpvec) for tmpvec in res.value().y], z, # TODO: take return values? extra_args, ) def augment(self): raise NotImplementedError() # TODO def get_augment_times(self): raise NotImplementedError() # TODO def rts_smooth(self, estimates, norm_quats=False): raise NotImplementedError() # TODO def maha_test(self, x, P, kind, z, R, extra_args=[], maha_thresh=0.95): raise NotImplementedError() # TODO def __dealloc__(self): del self.ekf