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
|
=head1 NAME
PDL::Fit::LM -- Levenber-Marquardt fitting routine for PDL
=head1 DESCRIPTION
This module provides fitting functions for PDL. Currently, only
Levenberg-Marquardt fitting is implemented. Other procedures should
be added as required. For a fairly concise overview on fitting
see Numerical Recipes, chapter 15 "Modeling of data".
=head1 SYNOPSIS
use PDL::Fit::LM;
$ym = lmfit $x, $y, $sig, \&expfunc, $a, {Maxiter => 300};
=head1 FUNCTIONS
=cut
package PDL::Fit::LM;
@EXPORT_OK = qw( lmfit tlmfit);
%EXPORT_TAGS = (Func=>[@EXPORT_OK]);
use PDL::Core;
use PDL::Exporter;
use PDL::Options;
use PDL::Slatec; # for matrix inversion
@ISA = qw( PDL::Exporter );
=head2 lmfit
=for ref
Levenberg-Marquardt fitting of a user supplied model function
=for example
($ym,$a,$covar,$iters) =
lmfit $x, $y, $sig, \&expfunc, $a, {Maxiter => 300, Eps => 1e-3};
Options:
=for options
Maxiter: maximum number of iterations before giving up
Eps: convergence citerium for fit; success when normalized change
in chisquare smaller than Eps
The user supplied sub routine reference should accept 4 arguments
=over 4
=item *
a vector of independent values $x
=item *
a vector of fitting parameters
=item *
a vector of dependent variables that will be assigned upon return
=item *
a matrix of partial derivatives with respect to the fitting parameters
that will be assigned upon return
=back
As an example take this definition of a single exponential with
3 parameters (width, amplitude, offset):
sub expdec {
my ($x,$par,$ym,$dyda) = @_;
my ($a,$b,$c) = map {$par->slice("($_)")} (0..2);
my $arg = $x/$a;
my $ex = exp($arg);
$ym .= $b*$ex+$c;
my (@dy) = map {$dyda->slice(",($_)")} (0..2);
$dy[0] .= -$b*$ex*$arg/$a;
$dy[1] .= $ex;
$dy[2] .= 1;
}
Note usage of the C<.=> operator for assignment
In scalar context returns a vector of the fitted dependent
variable. In list context returns fitted y-values, vector
of fitted parameters, an estimate of the covariance matrix
(as an indicator of goodness of fit) and number of iterations
performed.
=cut
sub PDL::lmfit {
my ($x,$y,$sig,$func,$a,$opt) = @_; # not using $ia right now
$opt = {iparse( { Maxiter => 200,
Eps => 1e-4}, ifhref($opt))};
my ($maxiter,$eps) = map {$opt->{$_}} qw/Maxiter Eps/;
# initialize some variables
my ($isig2,$chisq) = (1/($sig*$sig),0);
my ($ym,$al,$cov,$bet,$olda,$ochisq,$di,$pivt,$info) =
map {null} (0..8);
my ($aldiag,$codiag); # the diagonals for later updating
# this will break threading
my $dyda = zeroes($x->type,$x->getdim(0),$a->getdim(0));
my $alv = zeroes($x->type,$x->getdim(0),$a->getdim(0),$a->getdim(0));
my ($iter,$lambda) = (0,0.001);
do {
if ($iter>0) {
$cov .= $al;
local $PDL::debug = 1;
$codiag .= $aldiag*(1+$lambda);
gefa $cov, $pivt, $info; # gefa + gesl = solution by Gaussian elem.
gesl $cov, $pivt, $bet, 0; # solution returned in $bet
# lusd($cov,$bet,$da);
# print "changing by $da\n";
$a += $bet; # what we used to call $da is now $bet
}
&$func($x,$a,$ym,$dyda);
$chisq = ($y-$ym)*($y-$ym);
$chisq *= $isig2;
$chisq = $chisq->sumover; # calculate chi^2
$dyda->xchg(0,1)->outer($dyda->xchg(0,1),$alv->mv(0,2));
$alv *= $isig2;
$alv->sumover($al); # calculate alpha
(($y-$ym)*$isig2*$dyda)->sumover($bet); # calculate beta
if ($iter == 0) {$olda .= $a; $ochisq .= $chisq;
$aldiag = $al->diagonal(0,1);
$cov .= $al; $codiag = $cov->diagonal(0,1)}
$di .= abs($chisq-$ochisq);
# print "$iter: chisq, lambda, dlambda: $chisq, $lambda,",$di/$chisq,"\n";
if ($chisq < $ochisq) {
$lambda *= 0.1;
$ochisq .= $chisq;
$olda .= $a;
} else {
$lambda *= 10;
$chisq .= $ochisq;
$a .= $olda;
}
} while ($iter++==0 || $iter < $maxiter && $di/$chisq > $eps);
barf "iteration did not converge" if $iter >= $maxiter && $di/$chisq > $eps;
# return inv $al as estimate of covariance matrix
return wantarray ? ($ym,$a,matinv($al),$iter) : $ym;
}
*lmfit = \&PDL::lmfit;
# the OtherPar is the sub routine ref
=head2 tlmfit
=for ref
threaded version of Levenberg-Marquardt fitting routine mfit
=for example
tlmfit $x, $y, float(1)->dummy(0), $na, float(200), float(1e-4),
$ym=null, $afit=null, \&expdec;
Signature:
=for sig
tlmfit(x(n);y(n);sig(n);a(m);iter();eps();[o] ym(n);[o] ao(m);
OtherPar => subref)
a threaded version of C<lmfit> by using perl threading. Direct
threading in C<lmfit> seemed difficult since we have an if condition
in the iteration. In principle that can be worked around by
using C<where> but .... Send a threaded C<lmfit> version if
you work it out!
Since we are using perl threading here speed is not really great
but it is just convenient to have a threaded version for many
applications (no explicit for-loops required, etc). Suffers from
some of the current limitations of perl level threading.
=cut
thread_define 'tlmfit(x(n);y(n);sig(n);a(m);iter();eps();[o] ym(n);[o] ao(m)),
NOtherPars => 1',
over {
$_[7] .= $_[3]; # copy our parameter guess into the output
$_[6] .= PDL::lmfit $_[0],$_[1],$_[2],$_[8],$_[7],{Maxiter => $_[4],
Eps => $_[5]};
};
1;
=head1 BUGS
Not known yet.
=head1 AUTHOR
This file copyright (C) 1999, Christian Soeller
(c.soeller@auckland.ac.nz). All rights reserved. There is no
warranty. You are allowed to redistribute this software documentation
under certain conditions. For details, see the file COPYING in the PDL
distribution. If this file is separated from the PDL distribution, the
copyright notice should be included in the file.
1;
|