summaryrefslogtreecommitdiff
path: root/controller/fw/levmarq/levmarq.c
blob: 4a764db15f41fe7eb0d0933fb79804320f8f5b63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*
 * levmarq.c
 *
 * This file contains an implementation of the Levenberg-Marquardt algorithm
 * for solving least-squares problems, together with some supporting routines
 * for Cholesky decomposition and inversion.  No attempt has been made at
 * optimization.  In particular, memory use in the matrix routines could be
 * cut in half with a little effort (and some loss of clarity).
 *
 * It is assumed that the compiler supports variable-length arrays as
 * specified by the C99 standard.
 *
 * Ron Babich, May 2008
 *
 */

#include <stdio.h>
#include <math.h>

#include "levmarq.h"
#include "simulation.h"


#define TOL 1e-20f /* smallest value allowed in cholesky_decomp() */


/* set parameters required by levmarq() to default values */
void levmarq_init(LMstat *lmstat)
{
    lmstat->max_it = 100;
    lmstat->init_lambda = 0.0001f;
    lmstat->up_factor = 10.0f;
    lmstat->down_factor = 10.0f;
    lmstat->target_derr = 1e-12f;
}

/* perform least-squares minimization using the Levenberg-Marquardt
   algorithm.  The arguments are as follows:

   npar    number of parameters
   par     array of parameters to be varied
   ny      number of measurements to be fit
   y       array of measurements
   dysq    array of error in measurements, squared
   (set dysq=NULL for unweighted least-squares)
   func    function to be fit
   grad    gradient of "func" with respect to the input parameters
   fdata   pointer to any additional data required by the function
   lmstat  pointer to the "status" structure, where minimization parameters
   are set and the final status is returned.

   Before calling levmarq, several of the parameters in lmstat must be set.
   For default values, call levmarq_init(lmstat).
   */
int levmarq(int npar, float *par, int ny, float *y, float *dysq,
        float (*func)(float *, int, void *),
        void (*grad)(float *, float *, int, void *),
        void *fdata, LMstat *lmstat)
{
    int x,i,j,it,nit,ill;
    float lambda,up,down,mult,weight,err,newerr,derr,target_derr;
    float h[npar][npar],ch[npar][npar];
    float g[npar],d[npar],delta[npar],newpar[npar];

    nit = lmstat->max_it;
    lambda = lmstat->init_lambda;
    up = lmstat->up_factor;
    down = 1/lmstat->down_factor;
    target_derr = lmstat->target_derr;
    weight = 1;
    derr = newerr = 0; /* to avoid compiler warnings */

    /* calculate the initial error ("chi-squared") */
    err = error_func(par, ny, y, dysq, func, fdata);

    /* main iteration */
    for (it=0; it<nit; it++) {
        //DEBUG_PRINT("iteration %d", it);

        /* calculate the approximation to the Hessian and the "derivative" d */
        for (i=0; i<npar; i++) {
            d[i] = 0;
            for (j=0; j<=i; j++)
                h[i][j] = 0;
        }

        for (x=0; x<ny; x++) {
            if (dysq) weight = 1/dysq[x]; /* for weighted least-squares */
            grad(g, par, x, fdata);
            for (i=0; i<npar; i++) {
                d[i] += (y[x] - func(par, x, fdata))*g[i]*weight;
                for (j=0; j<=i; j++)
                    h[i][j] += g[i]*g[j]*weight;
            }
        }

        /*  make a step "delta."  If the step is rejected, increase
            lambda and try again */
        mult = 1 + lambda;
        ill = 1; /* ill-conditioned? */
        while (ill && (it<nit)) {
            for (i=0; i<npar; i++)
                h[i][i] = h[i][i]*mult;

            ill = cholesky_decomp(npar, ch, h);

            if (!ill) {
                solve_axb_cholesky(npar, ch, delta, d);
                for (i=0; i<npar; i++)
                    newpar[i] = par[i] + delta[i];
                newerr = error_func(newpar, ny, y, dysq, func, fdata);
                derr = newerr - err;
                ill = (derr > 0);
            } 
            if (ill) {
                mult = (1 + lambda*up)/(1 + lambda);
                lambda *= up;
                it++;
            }
        }
        for (i=0; i<npar; i++)
            par[i] = newpar[i];
        err = newerr;
        lambda *= down;  

        if ((!ill)&&(-derr<target_derr)) break;
    }

    lmstat->final_it = it;
    lmstat->final_err = err;
    lmstat->final_derr = derr;

    if (it == nit) {
        DEBUG_PRINT("did not converge");
        return -1;
    }

    return it;
}


/* calculate the error function (chi-squared) */
float error_func(float *par, int ny, float *y, float *dysq,
        float (*func)(float *, int, void *), void *fdata)
{
    int x;
    float res,e=0;

    for (x=0; x<ny; x++) {
        res = func(par, x, fdata) - y[x];
        if (dysq)  /* weighted least-squares */
            e += res*res/dysq[x];
        else
            e += res*res;
    }
    return e;
}


/* solve the equation Ax=b for a symmetric positive-definite matrix A,
   using the Cholesky decomposition A=LL^T.  The matrix L is passed in "l".
   Elements above the diagonal are ignored.
   */
void solve_axb_cholesky(int n, float l[n][n], float x[n], float b[n])
{
    int i,j;
    float sum;

    /* solve L*y = b for y (where x[] is used to store y) */

    for (i=0; i<n; i++) {
        sum = 0;
        for (j=0; j<i; j++)
            sum += l[i][j] * x[j];
        x[i] = (b[i] - sum)/l[i][i];      
    }

    /* solve L^T*x = y for x (where x[] is used to store both y and x) */

    for (i=n-1; i>=0; i--) {
        sum = 0;
        for (j=i+1; j<n; j++)
            sum += l[j][i] * x[j];
        x[i] = (x[i] - sum)/l[i][i];      
    }
}


/* This function takes a symmetric, positive-definite matrix "a" and returns
   its (lower-triangular) Cholesky factor in "l".  Elements above the
   diagonal are neither used nor modified.  The same array may be passed
   as both l and a, in which case the decomposition is performed in place.
   */
int cholesky_decomp(int n, float l[n][n], float a[n][n])
{
    int i,j,k;
    float sum;

    for (i=0; i<n; i++) {
        for (j=0; j<i; j++) {
            sum = 0;
            for (k=0; k<j; k++)
                sum += l[i][k] * l[j][k];
            l[i][j] = (a[i][j] - sum)/l[j][j];
        }

        sum = 0;
        for (k=0; k<i; k++)
            sum += l[i][k] * l[i][k];
        sum = a[i][i] - sum;
        if (sum<TOL) return 1; /* not positive-definite */
        l[i][i] = sqrtf(sum);
    }
    return 0;
}