IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Ticket #480: psMinimize-LMmods.c

File psMinimize-LMmods.c, 12.0 KB (added by eugene, 21 years ago)

EAM's current working versions of LMChi2 + support functions

Line 
1
2/****
3
4This 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
32bool 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
71psF64 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
84psBool 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
224psF64 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?
289psBool 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}