File Coverage

blib/lib/PDL/Fit/LM.pm
Criterion Covered Total %
statement 66 66 100.0
branch 8 10 80.0
condition 6 9 66.6
subroutine 7 7 100.0
pod 0 1 0.0
total 87 93 93.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             PDL::Fit::LM -- Levenberg-Marquardt fitting routine for PDL
4              
5             =head1 DESCRIPTION
6              
7             This module provides fitting functions for PDL. Currently, only
8             Levenberg-Marquardt fitting is implemented. Other procedures should
9             be added as required. For a fairly concise overview on fitting
10             see Numerical Recipes, chapter 15 "Modeling of data".
11              
12             =head1 SYNOPSIS
13              
14             use PDL::Fit::LM;
15             $ym = lmfit $x, $y, $sigma, \&expfunc, $initp, {Maxiter => 300};
16              
17             =head1 FUNCTIONS
18              
19             =cut
20              
21             package PDL::Fit::LM;
22              
23 1     1   234437 use strict;
  1         2  
  1         46  
24 1     1   6 use warnings;
  1         3  
  1         80  
25 1     1   8 use PDL::Core;
  1         2  
  1         10  
26 1     1   564 use PDL::Exporter;
  1         5  
  1         8  
27 1     1   48 use PDL::Options;
  1         2  
  1         116  
28 1     1   7 use PDL::MatrixOps qw(lu_decomp lu_backsub inv); # for matrix inversion
  1         2  
  1         15  
29              
30             our @EXPORT_OK = qw(lmfit tlmfit);
31             our %EXPORT_TAGS = (Func=>\@EXPORT_OK);
32             our @ISA = qw( PDL::Exporter );
33              
34             =head2 lmfit
35              
36             =for ref
37              
38             Levenberg-Marquardt fitting of a user supplied model function
39              
40             =for example
41              
42             ($ym,$finalp,$covar,$iters) =
43             lmfit $x, $y, $sigma, \&expfunc, $initp, {Maxiter => 300, Eps => 1e-3};
44              
45             where $x is the independent variable and $y the value of the dependent variable at each $x, $sigma is the estimate of the uncertainty (i.e., standard deviation) of $y at each data point, the fourth argument is a subroutine reference (see below), and $initp the initial values of the parameters to be adjusted.
46              
47             Options:
48              
49             =for options
50              
51             Maxiter: maximum number of iterations before giving up
52             Eps: convergence criterion for fit; success when normalized change
53             in chisquare smaller than Eps
54              
55             The user supplied sub routine reference should accept 4 arguments
56              
57             =over 4
58              
59             =item *
60              
61             a vector of independent values $x
62              
63             =item *
64              
65             a vector of fitting parameters
66              
67             =item *
68              
69             a vector of dependent variables that will be assigned upon return
70              
71             =item *
72              
73             a matrix of partial derivatives with respect to the fitting parameters
74             that will be assigned upon return
75              
76             =back
77              
78             As an example take this definition of a single exponential with
79             3 parameters (width, amplitude, offset):
80              
81             sub expdec {
82             my ($x,$par,$ym,$dyda) = @_;
83             my ($width,$amp,$off) = map {$par->slice("($_)")} (0..2);
84             my $arg = $x/$width;
85             my $ex = exp($arg);
86             $ym .= $amp*$ex+$off;
87             my (@dy) = map {$dyda->slice(",($_)")} (0..2);
88             $dy[0] .= -$amp*$ex*$arg/$width;
89             $dy[1] .= $ex;
90             $dy[2] .= 1;
91             }
92              
93             Note usage of the C<.=> operator for assignment
94              
95             In scalar context returns a vector of the fitted dependent
96             variable. In list context returns fitted y-values, vector
97             of fitted parameters, an estimate of the covariance matrix
98             (as an indicator of goodness of fit) and number of iterations
99             performed.
100              
101             =cut
102              
103             sub PDL::lmfit {
104 2     2 0 321922 my ($x,$y,$sig,$func,$c,$opt) = @_; # not using $ia right now
105 2         28 $opt = {iparse( { Maxiter => 200,
106             Eps => 1e-4}, ifhref($opt))};
107 2         886 my ($maxiter,$eps) = map {$opt->{$_}} qw/Maxiter Eps/;
  4         39  
108             # initialize some variables
109 2         17 my ($isig2,$chisq) = (1/($sig*$sig),0); #$isig2="inverse of sigma squared"
110             my ($ym,$al,$cov,$bet,$oldbet,$olda,$oldal,$ochisq,$di) =
111 2         71 map {null} (0..10);
  22         227  
112 2         37 my ($aldiag,$codiag); # the diagonals for later updating
113             # this will break broadcasting
114 2         11 my $dyda = zeroes($x->type,$x->getdim(0),$c->getdim(0));
115 2         121 my $alv = zeroes($x->type,$x->getdim(0),$c->getdim(0),$c->getdim(0));
116 2         113 my ($iter,$lambda) = (0,0.001);
117              
118 2   66     5 do {
      100        
119 12 100       1415 if ($iter>0) {
120 10         46 $cov .= $al;
121             # local $PDL::debug = 1;
122 10         174 $codiag .= $aldiag*(1+$lambda);
123 10         459 my ($lu, $perm, $par) = lu_decomp($cov);
124 10         61663 $bet .= lu_backsub($lu,$perm,$par, $bet);
125             # print "changing by $da\n";
126 10         22675 $c += $bet; # what we used to call $da is now $bet
127             }
128 12         498 &$func($x,$c,$ym,$dyda);
129 12         10721 $chisq = ($y-$ym)*($y-$ym);
130 12         541 $chisq *= $isig2;
131 12         464 $chisq = $chisq->sumover; # calculate chi^2
132 12         193 $dyda->transpose->outer($dyda->transpose,$alv->mv(0,2));
133 12         1077 $alv *= $isig2;
134 12         469 $alv->sumover($al); # calculate alpha
135 12         43 (($y-$ym)*$isig2*$dyda)->sumover($bet); # calculate beta
136 12 100       795 if ($iter == 0) {$olda .= $c; $ochisq .= $chisq; $oldbet .= $bet;
  2         12  
  2         41  
  2         24  
137 2         21 $oldal .= $al; $aldiag = $al->diagonal(0,1);
  2         29  
138 2         65 $cov .= $al; $codiag = $cov->diagonal(0,1)}
  2         29  
139 12         71 $di .= abs($chisq-$ochisq);
140             # print "$iter: chisq, lambda, dlambda: $chisq, $lambda,",$di/$chisq,"\n";
141 12 100       1276 if ($chisq < $ochisq) {
142 9         922 $lambda *= 0.1;
143 9         37 $ochisq .= $chisq;
144 9         135 $olda .= $c;
145 9         105 $oldbet .= $bet;
146 9         95 $oldal .= $al;
147             } else {
148 3         380 $lambda *= 10;
149 3         12 $chisq .= $ochisq;
150 3         45 $c .= $olda; # go back to previous a
151 3         38 $bet .= $oldbet; # and beta
152 3         30 $al .= $oldal; # and alpha
153             }
154             } while ($iter++==0 || $iter < $maxiter && $di/$chisq > $eps);
155 2 50 33     366 barf "iteration did not converge" if $iter >= $maxiter && $di/$chisq > $eps;
156             # return inv $al as estimate of covariance matrix
157 2 50       23 return wantarray ? ($ym,$c,inv($al),$iter) : $ym;
158             }
159             *lmfit = \&PDL::lmfit;
160              
161             =pod
162              
163             An extended example script that uses lmfit is included below.
164             This nice example was provided by John Gehman and should
165             help you to master the initial hurdles. It can also be found in
166             the F directory.
167              
168             use PDL;
169             use PDL::Math;
170             use PDL::Fit::LM;
171             use strict;
172              
173              
174             ### fit using pdl's lmfit (Marquardt-Levenberg non-linear least squares fitting)
175             ###
176             ### `lmfit' Syntax:
177             ###
178             ### ($ym,$finalp,$covar,$iters)
179             ### = lmfit $x, $y, $sigma, \&fn, $initp, {Maxiter => 300, Eps => 1e-3};
180             ###
181             ### Explanation of variables
182             ###
183             ### OUTPUT
184             ### $ym = pdl of fitted values
185             ### $finalp = pdl of parameters
186             ### $covar = covariance matrix
187             ### $iters = number of iterations actually used
188             ###
189             ### INPUT
190             ### $x = x data
191             ### $y = y data
192             ### $sigma = ndarray of y-uncertainties for each value of $y (can be set to scalar 1 for equal weighting)
193             ### \&fn = reference to function provided by user (more on this below)
194             ### $initp = initial values for floating parameters
195             ### (needs to be explicitly set prior to use of lmfit)
196             ### Maxiter = maximum iterations
197             ### Eps = convergence criterion (maximum normalized change in Chi Sq.)
198              
199             ### Example:
200             # make up experimental data:
201             my $xdata = pdl sequence 5;
202             my $ydata = pdl [1.1,1.9,3.05,4,4.9];
203              
204             # set initial prameters in a pdl (order in accord with fit function below)
205             my $initp = pdl [0,1];
206              
207             # Weight all y data equally (else specify different uncertainties in a pdl)
208             my $sigma = 1;
209              
210             # Use lmfit. Fourth input argument is reference to user-defined
211             # subroutine ( here \&linefit ) detailed below.
212             my ($yf,$pf,$cf,$if) = lmfit $xdata, $ydata, $sigma, \&linefit, $initp;
213              
214             # Note output
215             print "\nXDATA\n$xdata\nY DATA\n$ydata\n\nY DATA FIT\n$yf\n\n";
216             print "Slope and Intercept\n$pf\n\nCOVARIANCE MATRIX\n$cf\n\n";
217             print "NUMBER ITERATIONS\n$if\n\n";
218              
219              
220             # simple example of user defined fit function. Guidelines included on
221             # how to write your own function subroutine.
222             sub linefit {
223              
224             # leave this line as is
225             my ($x,$par,$ym,$dyda) = @_;
226              
227             # $m and $c are fit parameters, internal to this function
228             # call them whatever make sense to you, but replace (0..1)
229             # with (0..x) where x is equal to your number of fit parameters
230             # minus 1
231             my ($m,$c) = map { $par->slice("($_)") } (0..1);
232              
233             # Write function with dependent variable $ym,
234             # independent variable $x, and fit parameters as specified above.
235             # Use the .= (dot equals) assignment operator to express the equality
236             # (not just a plain equals)
237             $ym .= $m * $x + $c;
238              
239             # Edit only the (0..1) part to (0..x) as above
240             my (@dy) = map {$dyda -> slice(",($_)") } (0..1);
241              
242             # Partial derivative of the function with respect to first
243             # fit parameter ($m in this case). Again, note .= assignment
244             # operator (not just "equals")
245             $dy[0] .= $x;
246              
247             # Partial derivative of the function with respect to next
248             # fit parameter ($y in this case)
249             $dy[1] .= 1;
250              
251             # Add $dy[ ] .= () lines as necessary to supply
252             # partial derivatives for all floating parameters.
253             }
254              
255             =cut
256              
257             # the OtherPar is the sub routine ref
258              
259             =head2 tlmfit
260              
261             =for ref
262              
263             broadcasted version of Levenberg-Marquardt fitting routine mfit
264              
265             =for example
266              
267             tlmfit $x, $y, float(1)->dummy(0), $na, float(200), float(1e-4),
268             $ym=null, $afit=null, \&expdec;
269              
270              
271             =for sig
272              
273             Signature: tlmfit(x(n);y(n);sigma(n);initp(m);iter();eps();[o] ym(n);[o] finalp(m);
274             OtherPar => subref)
275              
276             a broadcasted version of C by using perl broadcasting. Direct
277             broadcasting in C seemed difficult since we have an if condition
278             in the iteration. In principle that can be worked around by
279             using C but .... Send a broadcasted C version if
280             you work it out!
281              
282             Since we are using perl broadcasting here speed is not really great
283             but it is just convenient to have a broadcasted version for many
284             applications (no explicit for-loops required, etc). Suffers from
285             some of the current limitations of perl level broadcasting.
286              
287             =cut
288              
289              
290             broadcast_define 'tlmfit(x(n);y(n);sigma(n);initp(m);iter();eps();[o] ym(n);[o] finalp(m)),
291             NOtherPars => 1',
292             over {
293             $_[7] .= $_[3]; # copy our parameter guess into the output
294             $_[6] .= PDL::lmfit $_[0],$_[1],$_[2],$_[8],$_[7],{Maxiter => $_[4],
295             Eps => $_[5]};
296             };
297              
298             1;
299              
300             =head1 BUGS
301              
302             Not known yet.
303              
304             =head1 AUTHOR
305              
306             This file copyright (C) 1999, Christian Soeller
307             (c.soeller@auckland.ac.nz). All rights reserved. There is no
308             warranty. You are allowed to redistribute this software documentation
309             under certain conditions. For details, see the file COPYING in the PDL
310             distribution. If this file is separated from the PDL distribution, the
311             copyright notice should be included in the file.
312              
313             =cut
314              
315             # return true
316             1;