1 /* 2 * This module contains the definition of a Levenberg-Marquardt solver for 3 * non-linear least squares problems of the following form: 4 * 5 * arg min ||f(X)|| = arg min f(X)^T f(X) 6 * X X 7 * 8 * where X is an Nx1 state vector and f(X) is an Mx1 nonlinear measurement error 9 * function of X which we wish to minimize the norm of. 10 * 11 * Levenberg-Marquardt solves the above problem through a damped Gauss-Newton 12 * method where the solver step on each iteration is chosen such that: 13 * (J' J + uI) * step = - J' f(x) 14 * where J = df(x)/dx is the Jacobian of the error function, u is a damping 15 * factor, and I is the identity matrix. 16 * 17 * The algorithm implemented here follows Algorithm 3.16 outlined in this paper: 18 * Madsen, Kaj, Hans Bruun Nielsen, and Ole Tingleff. 19 * "Methods for non-linear least squares problems." (2004). 20 * 21 * This algorithm uses a variant of the Marquardt method for updating the 22 * damping factor which ensures that changes in the factor have no 23 * discontinuities or fluttering behavior between solver steps. 24 */ 25 #ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_LEVENBERG_MARQUARDT_H_ 26 #define LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_LEVENBERG_MARQUARDT_H_ 27 28 #include <stddef.h> 29 #include <stdint.h> 30 31 #ifdef __cplusplus 32 extern "C" { 33 #endif 34 35 // Function pointer for computing residual, f(X), and Jacobian, J = df(X)/dX 36 // for a given state value, X, and other parameter data needed for computing 37 // these terms, f_data. 38 // 39 // Note if f_data is not needed, it is allowable for f_data to be passed in 40 // as NULL. 41 // 42 // jacobian is also allowed to be passed in as NULL, and in this case only the 43 // residual will be computed and returned. 44 typedef void (*ResidualAndJacobianFunction)(const float *state, 45 const void *f_data, 46 float *residual, float *jacobian); 47 48 49 #define MAX_LM_STATE_DIMENSION (10) 50 #define MAX_LM_MEAS_DIMENSION (50) 51 52 // Structure containing fixed parameters for a single LM solve. 53 struct LmParams { 54 // maximum number of iterations allowed by the solver. 55 uint32_t max_iterations; 56 57 // initial scaling factor for setting the damping factor, i.e.: 58 // damping_factor = initial_u_scale * max(J'J). 59 float initial_u_scale; 60 61 // magnitude of the cost function gradient required for solution, i.e. 62 // max(gradient) = max(J'f(x)) < gradient_threshold. 63 float gradient_threshold; 64 65 // magnitude of relative error required for solution step, i.e. 66 // ||step|| / ||state|| < relative_step_thresold. 67 float relative_step_threshold; 68 }; 69 70 // Structure containing data needed for a single LM solve. 71 // Defining the data here allows flexibility in how the memory is allocated 72 // for the solver. 73 struct LmData { 74 float step[MAX_LM_STATE_DIMENSION]; 75 float residual[MAX_LM_MEAS_DIMENSION]; 76 float residual_new[MAX_LM_MEAS_DIMENSION]; 77 float gradient[MAX_LM_MEAS_DIMENSION]; 78 float hessian[MAX_LM_STATE_DIMENSION * MAX_LM_STATE_DIMENSION]; 79 float temp[MAX_LM_STATE_DIMENSION * MAX_LM_MEAS_DIMENSION]; 80 }; 81 82 // Enumeration indicating status of the LM solver. 83 enum LmStatus { 84 RUNNING = 0, 85 // Successful solve conditions: 86 RELATIVE_STEP_SUFFICIENTLY_SMALL, // solver step is sufficiently small. 87 GRADIENT_SUFFICIENTLY_SMALL, // cost function gradient is below threshold. 88 HIT_MAX_ITERATIONS, 89 90 // Solver failure modes: 91 CHOLESKY_FAIL, 92 INVALID_DATA_DIMENSIONS, 93 }; 94 95 // Structure for containing variables needed for a Levenberg-Marquardt solver. 96 struct LmSolver { 97 // Solver parameters. 98 struct LmParams params; 99 100 // Pointer to solver data. 101 struct LmData *data; 102 103 // Function for computing residual (f(x)) and jacobian (df(x)/dx). 104 ResidualAndJacobianFunction func; 105 106 // Number of iterations in current solution. 107 uint32_t num_iter; 108 }; 109 110 // Initializes LM solver with provided parameters and error function. 111 void lmSolverInit(struct LmSolver *solver, const struct LmParams *params, 112 ResidualAndJacobianFunction func); 113 114 // Sets pointer for temporary data needed for an individual LM solve. 115 // Note, this must be called prior to calling lmSolverSolve(). 116 void lmSolverSetData(struct LmSolver *solver, struct LmData *data); 117 118 /* 119 * Runs the LM solver for a given set of data and error function. 120 * 121 * INPUTS: 122 * solver : pointer to an struct LmSolver structure. 123 * initial_state : initial guess of the estimation state. 124 * f_data : pointer to additional data needed by the error function. 125 * state_dim : dimension of X. 126 * meas_dim : dimension of f(X), defined in the error function. 127 * 128 * OUTPUTS: 129 * LmStatus : enum indicating state of the solver on completion. 130 * est_state : estimated state. 131 */ 132 enum LmStatus lmSolverSolve(struct LmSolver *solver, const float *initial_state, 133 void *f_data, size_t state_dim, size_t meas_dim, 134 float *state); 135 136 ////////////////////////// TEST UTILITIES //////////////////////////////////// 137 // This function is exposed here for testing purposes only. 138 /* 139 * Computes the ratio of actual cost function gain over expected cost 140 * function gain for the given solver step. This ratio is used to adjust 141 * the solver step size for the next solver iteration. 142 * 143 * INPUTS: 144 * residual: f(x) for the current state x. 145 * residual_new: f(x + step) for the new state after the solver step. 146 * step: the solver step. 147 * gradient: gradient of the cost function F(x). 148 * damping_factor: the current damping factor used in the solver. 149 * state_dim: dimension of the state, x. 150 * meas_dim: dimension of f(x). 151 */ 152 float computeGainRatio(const float *residual, const float *residual_new, 153 const float *step, const float *gradient, 154 float damping_factor, size_t state_dim, 155 size_t meas_dim); 156 157 #ifdef __cplusplus 158 } 159 #endif 160 161 #endif // LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_LEVENBERG_MARQUARDT_H_ 162