line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Algorithm::CurveFit; |
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
76884
|
use 5.006; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
108
|
|
4
|
3
|
|
|
3
|
|
16
|
use strict; |
|
3
|
|
|
|
|
3
|
|
|
3
|
|
|
|
|
100
|
|
5
|
3
|
|
|
3
|
|
13
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
455
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '1.05'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
require Exporter; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( |
14
|
|
|
|
|
|
|
'all' => [ qw( curve_fit ) ] |
15
|
|
|
|
|
|
|
); |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our @EXPORT = qw(); |
20
|
|
|
|
|
|
|
|
21
|
3
|
|
|
3
|
|
17
|
use Carp qw/confess/; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
237
|
|
22
|
3
|
|
|
3
|
|
3487
|
use Math::Symbolic qw/parse_from_string/; |
|
3
|
|
|
|
|
574457
|
|
|
3
|
|
|
|
|
1794
|
|
23
|
3
|
|
|
3
|
|
5206
|
use Math::MatrixReal; |
|
3
|
|
|
|
|
73039
|
|
|
3
|
|
|
|
|
278
|
|
24
|
3
|
|
|
3
|
|
44
|
use Data::Dumper; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
176
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# machine epsilon |
27
|
3
|
|
|
3
|
|
16
|
use constant EPS => 2.2e-16; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
386
|
|
28
|
3
|
|
|
3
|
|
15
|
use constant SQRT_EPS => sqrt(EPS); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
5057
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
sub curve_fit { |
31
|
2
|
100
|
33
|
2
|
1
|
252
|
shift @_ if not ref $_[0] and defined $_[0] and $_[0] eq 'Algorithm::CurveFit'; |
|
|
|
66
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
2
|
50
|
|
|
|
12
|
confess('Uneven number of arguments to Algorithm::CurveFit::curve_fit.') |
34
|
|
|
|
|
|
|
if @_ % 2; |
35
|
|
|
|
|
|
|
|
36
|
2
|
|
|
|
|
18
|
my %args = @_; |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
# Formula |
39
|
2
|
50
|
|
|
|
8
|
confess("Missing 'formula' parameter.") if not defined $args{formula}; |
40
|
2
|
|
|
|
|
5
|
my $formula; |
41
|
2
|
50
|
|
|
|
11
|
if (ref($args{formula}) =~ /^Math::Symbolic/) { |
42
|
0
|
|
|
|
|
0
|
$formula = $args{formula}; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
else { |
45
|
2
|
|
|
|
|
6
|
eval { $formula = parse_from_string( $args{formula} ); }; |
|
2
|
|
|
|
|
17
|
|
46
|
2
|
50
|
33
|
|
|
41643
|
confess( "Cannot parse formula '" . $args{formula} . "'. ($@)" ) |
47
|
|
|
|
|
|
|
if not defined $formula or $@; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# Variable (optional) |
51
|
2
|
|
|
|
|
9
|
my $variable = $args{variable}; |
52
|
2
|
50
|
|
|
|
7
|
$variable = 'x' if not defined $variable; |
53
|
8
|
|
|
|
|
619
|
confess("Formula '" |
54
|
|
|
|
|
|
|
. $args{formula} |
55
|
|
|
|
|
|
|
. "' not explicitly dependent on " |
56
|
|
|
|
|
|
|
. "variable '$variable'." ) |
57
|
2
|
50
|
|
|
|
12
|
if not grep { $_ eq $variable } $formula->explicit_signature(); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Parameters |
60
|
2
|
|
|
|
|
9
|
my $params = $args{params}; |
61
|
|
|
|
|
|
|
|
62
|
2
|
50
|
33
|
|
|
35
|
confess("Parameter 'params' has to be an array reference.") |
63
|
|
|
|
|
|
|
if not defined $params |
64
|
|
|
|
|
|
|
or not ref($params) eq 'ARRAY'; |
65
|
2
|
|
|
|
|
10
|
my @parameters = @$params; |
66
|
2
|
50
|
|
|
|
8
|
confess('No parameters specified.') if not @parameters; |
67
|
6
|
|
33
|
|
|
42
|
confess('Individual parameters need to be array references.') |
68
|
2
|
50
|
|
|
|
6
|
if grep { not defined $_ or not ref($_) eq 'ARRAY' } @parameters; |
69
|
2
|
|
|
|
|
6
|
foreach my $p (@parameters) { |
70
|
18
|
|
|
|
|
41
|
confess("Weird parameter\n'" |
71
|
|
|
|
|
|
|
. Dumper($p) |
72
|
|
|
|
|
|
|
. "' Should have the format\n" |
73
|
|
|
|
|
|
|
. "[ NAME_STRING, GUESSED_VALUE, ACCURACY ]\n" |
74
|
|
|
|
|
|
|
. "With the accuracy being optional. See docs." ) |
75
|
|
|
|
|
|
|
if @$p > 3 |
76
|
|
|
|
|
|
|
or @$p < 2 |
77
|
6
|
50
|
33
|
|
|
39
|
or grep { not defined $_ } @$p; |
|
|
|
33
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
26
|
|
|
|
|
901
|
confess("Formula '" |
80
|
|
|
|
|
|
|
. $args{formula} |
81
|
|
|
|
|
|
|
. "' not explicitly dependent on " |
82
|
|
|
|
|
|
|
. "parameter '" |
83
|
|
|
|
|
|
|
. $p->[0] |
84
|
|
|
|
|
|
|
. "'." ) |
85
|
6
|
50
|
|
|
|
18
|
if not grep { $_ eq $p->[0] } $formula->explicit_signature(); |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# XData |
89
|
2
|
|
|
|
|
7
|
my $xdata = $args{xdata}; |
90
|
2
|
50
|
33
|
|
|
24
|
confess('X-Data missing.') |
|
|
|
33
|
|
|
|
|
91
|
|
|
|
|
|
|
if not defined $xdata |
92
|
|
|
|
|
|
|
or not ref($xdata) eq 'ARRAY' |
93
|
|
|
|
|
|
|
or not @$xdata; |
94
|
2
|
|
|
|
|
30
|
my @xdata = @$xdata; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# YData |
97
|
2
|
|
|
|
|
5
|
my $ydata = $args{ydata}; |
98
|
2
|
50
|
33
|
|
|
23
|
confess('Y-Data missing.') |
|
|
|
33
|
|
|
|
|
99
|
|
|
|
|
|
|
if not defined $ydata |
100
|
|
|
|
|
|
|
or not ref($ydata) eq 'ARRAY' |
101
|
|
|
|
|
|
|
or not @$ydata; |
102
|
2
|
50
|
|
|
|
7
|
confess('Y-Data and X-Data need to have the same number of elements.') |
103
|
|
|
|
|
|
|
if not @$ydata == @xdata; |
104
|
2
|
|
|
|
|
30
|
my @ydata = @$ydata; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Max_Iter (optional) |
107
|
2
|
|
|
|
|
5
|
my $max_iter = $args{maximum_iterations}; |
108
|
2
|
50
|
|
|
|
6
|
$max_iter = 0 if not defined $max_iter; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
# Add third element (dlamda) to parameter arrays in case they're missing. |
111
|
2
|
|
|
|
|
4
|
foreach my $param (@parameters) { |
112
|
6
|
50
|
|
|
|
15
|
push @$param, 0 if @$param < 3; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Array holding all first order partial derivatives of the function in respect |
116
|
|
|
|
|
|
|
# to the parameters in order. |
117
|
2
|
|
|
|
|
4
|
my @derivatives; |
118
|
2
|
|
|
|
|
4
|
my @param_names = ($variable, map {$_->[0]} @parameters); |
|
6
|
|
|
|
|
13
|
|
119
|
2
|
|
|
|
|
5
|
foreach my $param (@parameters) { |
120
|
6
|
|
|
|
|
30
|
my $deriv = |
121
|
|
|
|
|
|
|
Math::Symbolic::Operator->new( 'partial_derivative', $formula, |
122
|
|
|
|
|
|
|
$param->[0] ); |
123
|
6
|
|
|
|
|
18251
|
$deriv = $deriv->simplify()->apply_derivatives()->simplify(); |
124
|
6
|
|
|
|
|
131820
|
my ($sub, $trees) = Math::Symbolic::Compiler->compile_to_sub($deriv, \@param_names); |
125
|
6
|
50
|
|
|
|
4824
|
if ($trees) { |
126
|
6
|
|
|
|
|
88
|
push @derivatives, $deriv; # residual trees, need to evaluate |
127
|
|
|
|
|
|
|
} else { |
128
|
0
|
|
|
|
|
0
|
push @derivatives, $sub; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# if not compilable, close over a ->value call for convenience later on |
133
|
2
|
|
|
|
|
5
|
my $formula_sub = do { |
134
|
2
|
|
|
|
|
12
|
my ($sub, $trees) = Math::Symbolic::Compiler->compile_to_sub($formula, \@param_names); |
135
|
|
|
|
|
|
|
$trees |
136
|
|
|
|
|
|
|
? sub { |
137
|
2089
|
|
|
|
|
5033
|
$formula->value( |
138
|
539
|
|
|
539
|
|
1073
|
map { ($param_names[$_] => $_[$_]) } 0..$#param_names |
139
|
|
|
|
|
|
|
) |
140
|
|
|
|
|
|
|
} |
141
|
2
|
50
|
|
|
|
1189
|
: $sub |
142
|
|
|
|
|
|
|
}; |
143
|
|
|
|
|
|
|
|
144
|
2
|
|
|
|
|
4
|
my $dbeta; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# Iterative approximation of the parameters |
147
|
2
|
|
|
|
|
7
|
my $iteration = 0; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# As long as we're under max_iter or maxiter==0 |
150
|
2
|
|
33
|
|
|
24
|
while ( !$max_iter || ++$iteration < $max_iter ) { |
151
|
|
|
|
|
|
|
# Generate Matrix A |
152
|
6
|
|
|
|
|
13
|
my @cols; |
153
|
6
|
|
|
|
|
10
|
my $pno = 0; |
154
|
6
|
|
|
|
|
13
|
my @par_values = map {$_->[1]} @parameters; |
|
20
|
|
|
|
|
49
|
|
155
|
6
|
|
|
|
|
17
|
foreach my $param (@parameters) { |
156
|
20
|
|
|
|
|
57
|
my $deriv = $derivatives[ $pno++ ]; |
157
|
|
|
|
|
|
|
|
158
|
20
|
|
|
|
|
37
|
my @ary; |
159
|
20
|
50
|
|
|
|
89
|
if (ref $deriv eq 'CODE') { |
160
|
0
|
|
|
|
|
0
|
foreach my $x ( 0 .. $#xdata ) { |
161
|
0
|
|
|
|
|
0
|
my $xv = $xdata[$x]; |
162
|
0
|
|
|
|
|
0
|
my $value = $deriv->($xv, @par_values); |
163
|
0
|
0
|
|
|
|
0
|
if (not defined $value) { # fall back to numeric five-point stencil |
164
|
0
|
|
|
|
|
0
|
my $h = SQRT_EPS*$xv; my $t = $xv + $h; $h = $t-$xv; # numerics. Cf. NR |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
165
|
0
|
|
|
|
|
0
|
$value = $formula_sub->($xv, @parameters) |
166
|
|
|
|
|
|
|
} |
167
|
0
|
|
|
|
|
0
|
push @ary, $value; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
else { |
171
|
20
|
|
|
|
|
88
|
$deriv = $deriv->new; # better safe than sorry |
172
|
20
|
|
|
|
|
8406
|
foreach my $x ( 0 .. $#xdata ) { |
173
|
1156
|
|
|
|
|
1780
|
my $xv = $xdata[$x]; |
174
|
3816
|
|
|
|
|
9846
|
my $value = $deriv->value( |
175
|
|
|
|
|
|
|
$variable => $xv, |
176
|
1156
|
|
|
|
|
1785
|
map { ( @{$_}[ 0, 1 ] ) } @parameters # a, guess |
|
3816
|
|
|
|
|
4007
|
|
177
|
|
|
|
|
|
|
); |
178
|
1156
|
100
|
|
|
|
1096491
|
if (not defined $value) { # fall back to numeric five-point stencil |
179
|
1
|
|
|
|
|
4
|
my $h = SQRT_EPS*$xv; my $t = $xv + $h; $h = $t-$xv; # numerics. Cf. NR |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2
|
|
180
|
1
|
|
|
|
|
6
|
$value = $formula_sub->($xv, @parameters) |
181
|
|
|
|
|
|
|
} |
182
|
1156
|
|
|
|
|
3063
|
push @ary, $value; |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
} |
185
|
20
|
|
|
|
|
1554
|
push @cols, \@ary; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Prepare matrix of datapoints X parameters |
189
|
6
|
|
|
|
|
287
|
my $A = Math::MatrixReal->new_from_cols( \@cols ); |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# transpose |
192
|
6
|
|
|
|
|
49899
|
my $AT = ~$A; |
193
|
6
|
|
|
|
|
2634
|
my $M = $AT * $A; |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# residuals |
196
|
1156
|
|
|
|
|
2237
|
my @beta = |
197
|
|
|
|
|
|
|
map { |
198
|
6
|
|
|
|
|
7208
|
$ydata[$_] - $formula_sub->( |
199
|
|
|
|
|
|
|
$xdata[$_], |
200
|
390
|
|
|
|
|
206729
|
map { $_->[1] } @parameters |
201
|
|
|
|
|
|
|
) |
202
|
|
|
|
|
|
|
} 0 .. $#xdata; |
203
|
6
|
|
|
|
|
3435
|
$dbeta = Math::MatrixReal->new_from_cols( [ \@beta ] ); |
204
|
|
|
|
|
|
|
|
205
|
6
|
|
|
|
|
18467
|
my $N = $AT * $dbeta; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Normalize before solving => better accuracy. |
208
|
6
|
|
|
|
|
2579
|
my ( $matrix, $vector ) = $M->normalize($N); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# solve |
211
|
6
|
|
|
|
|
1300
|
my $LR = $matrix->decompose_LR(); |
212
|
6
|
|
|
|
|
9959
|
my ( $dim, $x, $B ) = $LR->solve_LR($vector); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# extract parameter modifications and test for convergence |
215
|
6
|
|
|
|
|
908
|
my $last = 1; |
216
|
6
|
|
|
|
|
25
|
foreach my $pno ( 1 .. @parameters ) { |
217
|
20
|
|
|
|
|
89
|
my $dlambda = $x->element( $pno, 1 ); |
218
|
20
|
100
|
|
|
|
237
|
$last = 0 if abs($dlambda) > $parameters[ $pno - 1 ][2]; |
219
|
20
|
|
|
|
|
51
|
$parameters[ $pno - 1 ][1] += $dlambda; |
220
|
|
|
|
|
|
|
} |
221
|
6
|
100
|
|
|
|
475
|
last if $last; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# Recalculate dbeta for the squared residuals. |
225
|
390
|
|
|
|
|
768
|
my @beta = |
226
|
|
|
|
|
|
|
map { |
227
|
2
|
|
|
|
|
22
|
$ydata[$_] - $formula_sub->( |
228
|
|
|
|
|
|
|
$xdata[$_], |
229
|
148
|
|
|
|
|
74094
|
map { $_->[1] } @parameters |
230
|
|
|
|
|
|
|
) |
231
|
|
|
|
|
|
|
} 0 .. $#xdata; |
232
|
2
|
|
|
|
|
1429
|
$dbeta = Math::MatrixReal->new_from_cols( [ \@beta ] ); |
233
|
|
|
|
|
|
|
|
234
|
2
|
|
|
|
|
7590
|
my $square_residual = $dbeta->scalar_product($dbeta); |
235
|
2
|
|
|
|
|
485
|
return $square_residual; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
1; |
239
|
|
|
|
|
|
|
__END__ |