| 1 |
|
|---|
| 2 | /****
|
|---|
| 3 |
|
|---|
| 4 | This file contains the LM functions from my cycle 5 modifications and notes.
|
|---|
| 5 |
|
|---|
| 6 | - I added the psMinimizeGaussNewtonDelta function which is used to judge the
|
|---|
| 7 | error on a parameter which is not being fitted
|
|---|
| 8 |
|
|---|
| 9 | - I added the p_psMinLM_dLinear function which is used to perform the gain
|
|---|
| 10 | factor test for convergence
|
|---|
| 11 |
|
|---|
| 12 | - I changed Params in psMinimizeLMChi2 to type F32 from F64 since it must match
|
|---|
| 13 | the type of the input vector params
|
|---|
| 14 |
|
|---|
| 15 | - I adjusted the tracing levels to make the more detailed information a higher
|
|---|
| 16 | level of tracing.
|
|---|
| 17 |
|
|---|
| 18 | - I added the paramMask arguments to the GuessAPB and SetABX functions
|
|---|
| 19 |
|
|---|
| 20 | - I added the gain ratio test for convergence (better defined than the simple
|
|---|
| 21 | delta chisq threshold)
|
|---|
| 22 |
|
|---|
| 23 | - I added the construction of the covariance matrix at the end of psMinimizeLMChi2
|
|---|
| 24 | (if requested with covar != NULL)
|
|---|
| 25 |
|
|---|
| 26 | - I added a test of convergence and set the output bool to false if convergence
|
|---|
| 27 | is not reached.
|
|---|
| 28 |
|
|---|
| 29 | ****/
|
|---|
| 30 |
|
|---|
| 31 | // measure the distance to the minimum assuming a linear model
|
|---|
| 32 | bool psMinimizeGaussNewtonDelta (psVector *delta,
|
|---|
| 33 | const psVector *params,
|
|---|
| 34 | const psVector *paramMask,
|
|---|
| 35 | const psArray *x,
|
|---|
| 36 | const psVector *y,
|
|---|
| 37 | const psVector *yErr,
|
|---|
| 38 | psMinimizeLMChi2Func func) {
|
|---|
| 39 |
|
|---|
| 40 | // allocate internal arrays (current vs Guess)
|
|---|
| 41 | psImage *alpha = psImageAlloc (params->n, params->n, PS_TYPE_F64);
|
|---|
| 42 | psImage *Alpha = psImageAlloc (params->n, params->n, PS_TYPE_F64);
|
|---|
| 43 | psVector *beta = psVectorAlloc (params->n, PS_TYPE_F64);
|
|---|
| 44 | psVector *Params = psVectorAlloc (params->n, PS_TYPE_F64);
|
|---|
| 45 | psVector *dy = psVectorAlloc (y->n, PS_TYPE_F32);
|
|---|
| 46 |
|
|---|
| 47 | // the user provides the error or NULL. we need to convert
|
|---|
| 48 | // to appropriate weights
|
|---|
| 49 | if (yErr != NULL) {
|
|---|
| 50 | for (int i = 0; i < dy->n; i++) {
|
|---|
| 51 | dy->data.F32[i] = 1.0 / PS_SQR (yErr->data.F32[i]);
|
|---|
| 52 | }
|
|---|
| 53 | } else {
|
|---|
| 54 | for (int i = 0; i < dy->n; i++) {
|
|---|
| 55 | dy->data.F32[i] = 1.0;
|
|---|
| 56 | }
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | p_psMinLM_SetABX (alpha, beta, params, paramMask, x, y, dy, func);
|
|---|
| 60 | p_psMinLM_GuessABP (Alpha, delta, Params, alpha, beta, params, paramMask, 0.0);
|
|---|
| 61 |
|
|---|
| 62 | psFree (alpha);
|
|---|
| 63 | psFree (Alpha);
|
|---|
| 64 | psFree (beta);
|
|---|
| 65 | psFree (Params);
|
|---|
| 66 | psFree (dy);
|
|---|
| 67 | return (true);
|
|---|
| 68 | }
|
|---|
| 69 |
|
|---|
| 70 | // measure linear model prediction
|
|---|
| 71 | psF64 p_psMinLM_dLinear (const psVector *Beta, const psVector *beta, psF64 lambda) {
|
|---|
| 72 |
|
|---|
| 73 | /* get linear model prediction */
|
|---|
| 74 | psF64 dLinear = 0;
|
|---|
| 75 | psF64 *B = Beta->data.F64;
|
|---|
| 76 | psF64 *b = beta->data.F64;
|
|---|
| 77 | for (int i = 0; i < beta->n; i++) {
|
|---|
| 78 | dLinear += lambda*PS_SQR(B[i]) + B[i]*b[i];
|
|---|
| 79 | }
|
|---|
| 80 | return (0.5*dLinear);
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | // XXX EAM this is my re-implementation of MinLM
|
|---|
| 84 | psBool psMinimizeLMChi2(psMinimization *min,
|
|---|
| 85 | psImage *covar,
|
|---|
| 86 | psVector *params,
|
|---|
| 87 | const psVector *paramMask,
|
|---|
| 88 | const psArray *x,
|
|---|
| 89 | const psVector *y,
|
|---|
| 90 | const psVector *yErr,
|
|---|
| 91 | psMinimizeLMChi2Func func)
|
|---|
| 92 | {
|
|---|
| 93 | PS_PTR_CHECK_NULL(min, NULL);
|
|---|
| 94 | PS_VECTOR_CHECK_NULL(params, NULL);
|
|---|
| 95 | PS_VECTOR_CHECK_EMPTY(params, NULL);
|
|---|
| 96 | PS_PTR_CHECK_NULL(x, NULL);
|
|---|
| 97 | PS_VECTOR_CHECK_NULL(y, NULL);
|
|---|
| 98 | PS_VECTOR_CHECK_EMPTY(y, NULL);
|
|---|
| 99 | PS_VECTOR_CHECK_SIZE_EQUAL(x, y, NULL);
|
|---|
| 100 | PS_PTR_CHECK_NULL(func, NULL);
|
|---|
| 101 |
|
|---|
| 102 | // this function has test and current values for several things
|
|---|
| 103 | // the current best value is in lower case
|
|---|
| 104 | // the next guess value is in upper case
|
|---|
| 105 |
|
|---|
| 106 | // allocate internal arrays (current vs Guess)
|
|---|
| 107 | psImage *alpha = psImageAlloc (params->n, params->n, PS_TYPE_F64);
|
|---|
| 108 | psImage *Alpha = psImageAlloc (params->n, params->n, PS_TYPE_F64);
|
|---|
| 109 | psVector *beta = psVectorAlloc (params->n, PS_TYPE_F64);
|
|---|
| 110 | psVector *Beta = psVectorAlloc (params->n, PS_TYPE_F64);
|
|---|
| 111 | psVector *Params = psVectorAlloc (params->n, PS_TYPE_F32);
|
|---|
| 112 | psVector *dy = NULL;
|
|---|
| 113 | psF64 Chisq = 0.0;
|
|---|
| 114 | psF64 lambda = 0.001;
|
|---|
| 115 |
|
|---|
| 116 | // XXX EAM: why is this needed here? the value is not used, and the memory
|
|---|
| 117 | // is allocated above. However, if I drop it, I get weird answers or crashes.
|
|---|
| 118 | Params = psVectorCopy (Params, params, PS_TYPE_F32);
|
|---|
| 119 |
|
|---|
| 120 | // the user provides the error or NULL. we need to convert
|
|---|
| 121 | // to appropriate weights
|
|---|
| 122 | dy = psVectorAlloc (y->n, PS_TYPE_F32);
|
|---|
| 123 | if (yErr != NULL) {
|
|---|
| 124 | for (int i = 0; i < dy->n; i++) {
|
|---|
| 125 | dy->data.F32[i] = 1.0 / PS_SQR (yErr->data.F32[i]);
|
|---|
| 126 | }
|
|---|
| 127 | } else {
|
|---|
| 128 | for (int i = 0; i < dy->n; i++) {
|
|---|
| 129 | dy->data.F32[i] = 1.0;
|
|---|
| 130 | }
|
|---|
| 131 | }
|
|---|
| 132 |
|
|---|
| 133 | // calculate initial alpha and beta, set chisq (min->value)
|
|---|
| 134 | min->value = p_psMinLM_SetABX (alpha, beta, params, paramMask, x, y, dy, func);
|
|---|
| 135 | # ifndef PS_NO_TRACE
|
|---|
| 136 | // dump some useful info if trace is defined
|
|---|
| 137 | if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
|
|---|
| 138 | p_psImagePrint (psTraceGetDestination(), alpha, "alpha guess");
|
|---|
| 139 | p_psVectorPrint (psTraceGetDestination(), beta, "beta guess");
|
|---|
| 140 | p_psVectorPrint (psTraceGetDestination(), params, "params guess");
|
|---|
| 141 | }
|
|---|
| 142 | if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") == 4) {
|
|---|
| 143 | p_psVectorPrintRow (psTraceGetDestination(), Params, "params guess");
|
|---|
| 144 | }
|
|---|
| 145 | # endif /* PS_NO_TRACE */
|
|---|
| 146 |
|
|---|
| 147 | // iterate until the tolerance is reached, or give up
|
|---|
| 148 | while ((min->lastDelta > min->tol) && (min->iter < min->maxIter)) {
|
|---|
| 149 |
|
|---|
| 150 | // set a new guess for Alpha, Beta, Params
|
|---|
| 151 | p_psMinLM_GuessABP (Alpha, Beta, Params, alpha, beta, params, paramMask, lambda);
|
|---|
| 152 |
|
|---|
| 153 | // measure linear model prediction
|
|---|
| 154 | psF64 dLinear = p_psMinLM_dLinear (Beta, beta, lambda);
|
|---|
| 155 |
|
|---|
| 156 | # ifndef PS_NO_TRACE
|
|---|
| 157 | // dump some useful info if trace is defined
|
|---|
| 158 | if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
|
|---|
| 159 | p_psImagePrint (psTraceGetDestination(), Alpha, "alpha guess");
|
|---|
| 160 | p_psVectorPrint (psTraceGetDestination(), Beta, "beta guess");
|
|---|
| 161 | p_psVectorPrint (psTraceGetDestination(), Params, "params guess");
|
|---|
| 162 | }
|
|---|
| 163 | if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") == 4) {
|
|---|
| 164 | p_psVectorPrintRow (psTraceGetDestination(), Params, "params guess");
|
|---|
| 165 | }
|
|---|
| 166 | # endif /* PS_NO_TRACE */
|
|---|
| 167 |
|
|---|
| 168 | // calculate Chisq for new guess, update Alpha & Beta
|
|---|
| 169 | Chisq = p_psMinLM_SetABX (Alpha, Beta, Params, paramMask, x, y, dy, func);
|
|---|
| 170 |
|
|---|
| 171 | // XXX EAM alternate convergence criterion:
|
|---|
| 172 | // compare the delta (min->value - Chisq) with the
|
|---|
| 173 | // expected delta from the linear model (dLinear)
|
|---|
| 174 | // accept new guess (if improvement), or increase lambda
|
|---|
| 175 | psF64 rho = (min->value - Chisq) / dLinear;
|
|---|
| 176 |
|
|---|
| 177 | psTrace (".psLib.dataManip.psMinimizeLMChi2", 4, "last chisq: %f, new chisq %f, delta: %f, rho: %f\n", min->value, Chisq, min->lastDelta, rho);
|
|---|
| 178 | # ifndef PS_NO_TRACE
|
|---|
| 179 | // dump some useful info if trace is defined
|
|---|
| 180 | if (psTraceGetLevel (".psLib.dataManip.psMinimizeLMChi2") >= 5) {
|
|---|
| 181 | p_psImagePrint (psTraceGetDestination(), Alpha, "alpha guess");
|
|---|
| 182 | p_psVectorPrint (psTraceGetDestination(), Beta, "beta guess");
|
|---|
| 183 | p_psVectorPrint (psTraceGetDestination(), Params, "params guess");
|
|---|
| 184 | }
|
|---|
| 185 | # endif /* PS_NO_TRACE */
|
|---|
| 186 |
|
|---|
| 187 | /* if (Chisq < min->value) { */
|
|---|
| 188 | if (rho > 0.0) {
|
|---|
| 189 | min->lastDelta = (min->value - Chisq) / (dy->n - params->n);
|
|---|
| 190 | min->value = Chisq;
|
|---|
| 191 | alpha = psImageCopy (alpha, Alpha, PS_TYPE_F64);
|
|---|
| 192 | beta = psVectorCopy (beta, Beta, PS_TYPE_F64);
|
|---|
| 193 | params = psVectorCopy (params, Params, PS_TYPE_F32);
|
|---|
| 194 | lambda *= 0.1;
|
|---|
| 195 | } else {
|
|---|
| 196 | lambda *= 10.0;
|
|---|
| 197 | }
|
|---|
| 198 | min->iter ++;
|
|---|
| 199 | }
|
|---|
| 200 | psTrace (".psLib.dataManip.psMinimizeLMChi2", 3, "chisq: %f, last delta: %f, Niter: %d\n", min->value, min->lastDelta, min->iter);
|
|---|
| 201 |
|
|---|
| 202 | // construct & return the covariance matrix (if requested)
|
|---|
| 203 | if (covar != NULL) {
|
|---|
| 204 | p_psMinLM_GuessABP (covar, Beta, Params, alpha, beta, params, paramMask, 0.0);
|
|---|
| 205 | }
|
|---|
| 206 |
|
|---|
| 207 | // free the internal temporary data
|
|---|
| 208 | psFree (alpha);
|
|---|
| 209 | psFree (Alpha);
|
|---|
| 210 | psFree (beta);
|
|---|
| 211 | psFree (Beta);
|
|---|
| 212 | psFree (Params);
|
|---|
| 213 | psFree (dy);
|
|---|
| 214 |
|
|---|
| 215 | if (min->iter == min->maxIter) {
|
|---|
| 216 | return (false);
|
|---|
| 217 | }
|
|---|
| 218 | return (true);
|
|---|
| 219 | }
|
|---|
| 220 |
|
|---|
| 221 | // XXX EAM: this needs to respect the mask on params
|
|---|
| 222 | // XXX EAM: check not NULL on alpha, beta, params
|
|---|
| 223 | // alpha, beta, params are already allocated
|
|---|
| 224 | psF64 p_psMinLM_SetABX (psImage *alpha,
|
|---|
| 225 | psVector *beta,
|
|---|
| 226 | const psVector *params,
|
|---|
| 227 | const psVector *paramMask,
|
|---|
| 228 | const psArray *x,
|
|---|
| 229 | const psVector *y,
|
|---|
| 230 | const psVector *dy,
|
|---|
| 231 | psMinimizeLMChi2Func func)
|
|---|
| 232 | {
|
|---|
| 233 |
|
|---|
| 234 | psF64 chisq;
|
|---|
| 235 | psF64 delta;
|
|---|
| 236 | psF64 weight;
|
|---|
| 237 | psF64 ymodel;
|
|---|
| 238 | psVector *deriv = psVectorAlloc (params->n, PS_TYPE_F32);
|
|---|
| 239 |
|
|---|
| 240 | // zero alpha and beta for summing below
|
|---|
| 241 | for (int j = 0; j < params->n; j++) {
|
|---|
| 242 | for (int k = 0; k < params->n; k++) {
|
|---|
| 243 | alpha->data.F64[j][k] = 0;
|
|---|
| 244 | }
|
|---|
| 245 | beta->data.F64[j] = 0;
|
|---|
| 246 | }
|
|---|
| 247 | chisq = 0.0;
|
|---|
| 248 |
|
|---|
| 249 | // calculate chisq, alpha, beta
|
|---|
| 250 | for (int i = 0; i < y->n; i++) {
|
|---|
| 251 | ymodel = func (deriv, params, (psVector *) x->data[i]);
|
|---|
| 252 |
|
|---|
| 253 | delta = ymodel - y->data.F32[i];
|
|---|
| 254 | chisq += PS_SQR (delta) * dy->data.F32[i];
|
|---|
| 255 |
|
|---|
| 256 | for (int j = 0; j < params->n; j++) {
|
|---|
| 257 | if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
|
|---|
| 258 | weight = deriv->data.F32[j] * dy->data.F32[i];
|
|---|
| 259 | for (int k = 0; k <= j; k++) {
|
|---|
| 260 | if ((paramMask != NULL) && (paramMask->data.U8[k])) continue;
|
|---|
| 261 | alpha->data.F64[j][k] += weight * deriv->data.F32[k];
|
|---|
| 262 | }
|
|---|
| 263 | beta->data.F64[j] += weight * delta;
|
|---|
| 264 | }
|
|---|
| 265 | }
|
|---|
| 266 |
|
|---|
| 267 | // calculate lower-left half of alpha
|
|---|
| 268 | for (int j = 1; j < params->n; j++) {
|
|---|
| 269 | for (int k = 0; k < j; k++) {
|
|---|
| 270 | alpha->data.F64[k][j] = alpha->data.F64[j][k];
|
|---|
| 271 | }
|
|---|
| 272 | }
|
|---|
| 273 |
|
|---|
| 274 | // fill in pivots if we apply a mask
|
|---|
| 275 | if (paramMask != NULL) {
|
|---|
| 276 | for (int j = 0; j < params->n; j++) {
|
|---|
| 277 | if (paramMask->data.U8[j]) {
|
|---|
| 278 | alpha->data.F64[j][j] = 1;
|
|---|
| 279 | beta->data.F64[j] = 1;
|
|---|
| 280 | }
|
|---|
| 281 | }
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | psFree (deriv);
|
|---|
| 285 | return (chisq);
|
|---|
| 286 | }
|
|---|
| 287 |
|
|---|
| 288 | // XXX EAM : can we use static copies of LUv, LUm, A?
|
|---|
| 289 | psBool p_psMinLM_GuessABP (psImage *Alpha,
|
|---|
| 290 | psVector *Beta,
|
|---|
| 291 | psVector *Params,
|
|---|
| 292 | const psImage *alpha,
|
|---|
| 293 | const psVector *beta,
|
|---|
| 294 | const psVector *params,
|
|---|
| 295 | const psVector *paramMask,
|
|---|
| 296 | psF64 lambda)
|
|---|
| 297 | {
|
|---|
| 298 |
|
|---|
| 299 | # define USE_LU_DECOMP 1
|
|---|
| 300 | # if (USE_LU_DECOMP)
|
|---|
| 301 | psVector *LUv = NULL;
|
|---|
| 302 | psImage *LUm = NULL;
|
|---|
| 303 | psImage *A = NULL;
|
|---|
| 304 | psF32 det;
|
|---|
| 305 |
|
|---|
| 306 | // LU decomposition version
|
|---|
| 307 | psTrace (".psLib.dataManip.psMinLM_GuessABP", 5, "using LUD version\n");
|
|---|
| 308 |
|
|---|
| 309 | // set new guess values (creates matrix A)
|
|---|
| 310 | A = psImageCopy (NULL, alpha, PS_TYPE_F64);
|
|---|
| 311 | for (int j = 0; j < params->n; j++) {
|
|---|
| 312 | if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
|
|---|
| 313 | A->data.F64[j][j] = alpha->data.F64[j][j] * (1.0 + lambda);
|
|---|
| 314 | }
|
|---|
| 315 |
|
|---|
| 316 | // solve A*beta = Beta (Alpha = 1/A)
|
|---|
| 317 | // these operations do not modify the input values (creates LUm, LUv)
|
|---|
| 318 | LUm = psMatrixLUD (NULL, &LUv, A);
|
|---|
| 319 | Beta = psMatrixLUSolve (Beta, LUm, beta, LUv);
|
|---|
| 320 | Alpha = psMatrixInvert (Alpha, A, &det);
|
|---|
| 321 |
|
|---|
| 322 | # else
|
|---|
| 323 | // gauss-jordan version
|
|---|
| 324 | psTrace (".psLib.dataManip.psMinLM_GuessABP", 5, "using Gauss-J version");
|
|---|
| 325 |
|
|---|
| 326 | // set new guess values (creates matrix A)
|
|---|
| 327 | Beta = psVectorCopy (Beta, beta, PS_TYPE_F64);
|
|---|
| 328 | Alpha = psImageCopy (Alpha, alpha, PS_TYPE_F64);
|
|---|
| 329 | for (int j = 0; j < params->n; j++) {
|
|---|
| 330 | if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
|
|---|
| 331 | Alpha->data.F64[j][j] = alpha->data.F64[j][j] * (1.0 + lambda);
|
|---|
| 332 | }
|
|---|
| 333 |
|
|---|
| 334 | psGaussJordan (Alpha, Beta);
|
|---|
| 335 | # endif
|
|---|
| 336 |
|
|---|
| 337 | // apply Beta to get new Params values
|
|---|
| 338 | for (int j = 0; j < params->n; j++) {
|
|---|
| 339 | if ((paramMask != NULL) && (paramMask->data.U8[j])) continue;
|
|---|
| 340 | Params->data.F32[j] = params->data.F32[j] - Beta->data.F64[j];
|
|---|
| 341 | }
|
|---|
| 342 |
|
|---|
| 343 | # if (USE_LU_DECOMP)
|
|---|
| 344 | psFree (A);
|
|---|
| 345 | psFree (LUm);
|
|---|
| 346 | psFree (LUv);
|
|---|
| 347 | # endif
|
|---|
| 348 |
|
|---|
| 349 | return true;
|
|---|
| 350 | }
|
|---|