line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
=head1 NAME |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
PDL::Opt::Simplex -- Simplex optimization routines |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=head1 SYNOPSIS |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
use PDL::Opt::Simplex; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
($optimum,$ssize,$optval) = simplex($init,$initsize,$minsize, |
11
|
|
|
|
|
|
|
$maxiter, |
12
|
|
|
|
|
|
|
sub {evaluate_func_at($_[0])}, |
13
|
|
|
|
|
|
|
sub {display_simplex($_[0])} |
14
|
|
|
|
|
|
|
); |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 DESCRIPTION |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
This package implements the commonly used simplex optimization |
19
|
|
|
|
|
|
|
algorithm. The basic idea of the algorithm is to move |
20
|
|
|
|
|
|
|
a "simplex" of N+1 points in the N-dimensional search space |
21
|
|
|
|
|
|
|
according to certain rules. The main |
22
|
|
|
|
|
|
|
benefit of the algorithm is that you do not need to calculate |
23
|
|
|
|
|
|
|
the derivatives of your function. |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
$init is a 1D vector holding the initial values of the N fitted |
26
|
|
|
|
|
|
|
parameters, $optimum is a vector holding the final solution. |
27
|
|
|
|
|
|
|
$optval is the evaluation of the final solution. |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$initsize is the size of $init (more...) |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
$minsize is some sort of convergence criterion (more...) |
32
|
|
|
|
|
|
|
- e.g. $minsize = 1e-6 |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
The sub is assumed to understand more than 1 dimensions and threading. |
35
|
|
|
|
|
|
|
Its signature is 'inp(nparams); [ret]out()'. An example would be |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub evaluate_func_at { |
38
|
|
|
|
|
|
|
my($xv) = @_; |
39
|
|
|
|
|
|
|
my $x1 = $xv->slice("(0)"); |
40
|
|
|
|
|
|
|
my $x2 = $xv->slice("(1)"); |
41
|
|
|
|
|
|
|
return $x1**4 + ($x2-5)**4 + $x1*$x2; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
Here $xv is a vector holding the current values of the parameters |
45
|
|
|
|
|
|
|
being fitted which are then sliced out explicitly as $x1 and $x2. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
$ssize gives a very very approximate estimate of how close we might |
48
|
|
|
|
|
|
|
be - it might be miles wrong. It is the euclidean distance between |
49
|
|
|
|
|
|
|
the best and the worst vertices. If it is not very small, the algorithm |
50
|
|
|
|
|
|
|
has not converged. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head1 FUNCTIONS |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=head2 simplex |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=for ref |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Simplex optimization routine |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=for usage |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
($optimum,$ssize,$optval) = simplex($init,$initsize,$minsize, |
63
|
|
|
|
|
|
|
$maxiter, |
64
|
|
|
|
|
|
|
sub {evaluate_func_at($_[0])}, |
65
|
|
|
|
|
|
|
sub {display_simplex($_[0])} |
66
|
|
|
|
|
|
|
); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
See module C for more information. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
=head1 CAVEATS |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
Do not use the simplex method if your function has local minima. |
73
|
|
|
|
|
|
|
It will not work. Use genetic algorithms or simulated annealing |
74
|
|
|
|
|
|
|
or conjugate gradient or momentum gradient descent. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
They will not really work either but they are not guaranteed not to work ;) |
77
|
|
|
|
|
|
|
(if you have infinite time, simulated annealing is guaranteed to work |
78
|
|
|
|
|
|
|
but only after it has visited every point in your space). |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=head1 SEE ALSO |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Ron Shaffer's chemometrics web page and references therein: |
83
|
|
|
|
|
|
|
C. |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Numerical Recipes (bla bla bla XXX ref). |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
The demonstration (Examples/Simplex/tsimp.pl and tsimp2.pl). |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head1 AUTHOR |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Copyright(C) 1997 Tuomas J. Lukka. |
92
|
|
|
|
|
|
|
All rights reserved. There is no warranty. You are allowed |
93
|
|
|
|
|
|
|
to redistribute this software / documentation under certain |
94
|
|
|
|
|
|
|
conditions. For details, see the file COPYING in the PDL |
95
|
|
|
|
|
|
|
distribution. If this file is separated from the PDL distribution, |
96
|
|
|
|
|
|
|
the copyright notice should be included in the file. |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=cut |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
package PDL::Opt::Simplex; |
103
|
1
|
|
|
1
|
|
67161
|
use PDL; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
9
|
|
104
|
1
|
|
|
1
|
|
6
|
use PDL::Primitive; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
7
|
|
105
|
1
|
|
|
1
|
|
8
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
106
|
1
|
|
|
1
|
|
5
|
use PDL::Exporter; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3
|
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# use AutoLoader; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
@PDL::Opt::Simplex::ISA = qw/PDL::Exporter/; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
@PDL::Opt::Simplex::EXPORT_OK = qw/simplex/; |
113
|
|
|
|
|
|
|
%PDL::Opt::Simplex::EXPORT_TAGS = ( Func => [@PDL::Opt::Simplex::EXPORT_OK] ); |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
*simplex = \&PDL::simplex; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub PDL::simplex { |
118
|
2
|
|
|
2
|
0
|
8
|
my ( $init, $initsize, $minsize, $maxiter, $sub, $logsub, $t ) = @_; |
119
|
2
|
50
|
|
|
|
8
|
if ( !defined $t ) { $t = 0 } |
|
2
|
|
|
|
|
6
|
|
120
|
2
|
|
|
|
|
5
|
my ( $i, $j ); |
121
|
2
|
|
|
|
|
9
|
my ( $nd, $nd2 ) = ( dims($init), 1 ); |
122
|
2
|
|
|
|
|
5
|
my $simp; |
123
|
2
|
100
|
|
|
|
10
|
if ( $nd2 == 1 ) { |
|
|
50
|
|
|
|
|
|
124
|
1
|
|
|
|
|
6
|
$simp = PDL->zeroes( $nd, $nd + 1 ); |
125
|
1
|
|
|
|
|
4
|
$simp .= $init; |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# Constructing a tetrahedron: |
128
|
|
|
|
|
|
|
# At step n (starting from zero) |
129
|
|
|
|
|
|
|
# take vertices 0..n and move them 1/(n+1) to negative dir on axis n. |
130
|
|
|
|
|
|
|
# Take vertex n+1 and move it n/(n+1) to positive dir on axis n |
131
|
1
|
50
|
|
|
|
5
|
if ( !ref $initsize ) { |
132
|
0
|
|
|
|
|
0
|
$initsize = PDL->pdl($initsize)->dummy( 0, $nd ); |
133
|
|
|
|
|
|
|
} |
134
|
1
|
|
|
|
|
7
|
for ( $i = 0 ; $i < $nd ; $i++ ) { |
135
|
2
|
|
|
|
|
18
|
my $pj = $i / ( $i + 1 ); |
136
|
2
|
|
|
|
|
14
|
( my $stoopid = $simp->slice("$i,0:$i") ) -= |
137
|
|
|
|
|
|
|
$initsize->at($i) * $pj; |
138
|
2
|
|
|
|
|
13
|
( my $stoopid1 = $simp->slice( "$i," . ( $i + 1 ) ) ) += |
139
|
|
|
|
|
|
|
$initsize->at($i) * ( 1 - $pj ); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
elsif ( $nd2 == $nd + 1 ) { |
143
|
1
|
|
|
|
|
3
|
$simp = $init; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
else { |
146
|
0
|
|
|
|
|
0
|
return; |
147
|
|
|
|
|
|
|
} |
148
|
2
|
|
|
|
|
9
|
my $maxind = PDL->zeroes(2); |
149
|
2
|
|
|
|
|
10
|
my $minind = PDL->null; |
150
|
2
|
|
|
|
|
8
|
my $ssum = PDL->null; |
151
|
2
|
|
|
|
|
6
|
my $worst; |
152
|
|
|
|
|
|
|
my $new; |
153
|
2
|
|
|
|
|
4
|
my $vals = &{$sub}($simp); |
|
2
|
|
|
|
|
6
|
|
154
|
2
|
|
|
|
|
251
|
my $ss1 = ( $simp - $simp->slice(":,0") )**2; |
155
|
2
|
|
|
|
|
56
|
sumover( $ss1, ( my $ss2 = PDL->null ) ); |
156
|
2
|
|
|
|
|
11
|
my $ssize = PDL::max( sqrt($ss2) ); |
157
|
2
|
50
|
|
|
|
13
|
&{$logsub}( $simp, $vals, $ssize ) |
|
0
|
|
|
|
|
0
|
|
158
|
|
|
|
|
|
|
if $logsub; |
159
|
|
|
|
|
|
|
|
160
|
2
|
|
66
|
|
|
19
|
while ( $maxiter-- and max( PDL->topdl($ssize) ) > $minsize ) { |
161
|
101
|
|
|
|
|
297
|
my $valsn = $vals; |
162
|
101
|
50
|
|
|
|
244
|
if ($t) { |
163
|
0
|
|
|
|
|
0
|
my $noise = $vals->random(); |
164
|
0
|
|
|
|
|
0
|
$noise->random; |
165
|
0
|
|
|
|
|
0
|
$valsn = $vals + $t * ( -log( $noise + 0.00001 ) ); |
166
|
|
|
|
|
|
|
} |
167
|
101
|
|
|
|
|
1403
|
maximum_n_ind( $valsn, $maxind ); |
168
|
101
|
|
|
|
|
1148
|
minimum_ind( $valsn, $minind ); |
169
|
101
|
|
|
|
|
287
|
my @worstvals = map { $valsn->at( $maxind->at($_) ) } 0 .. 1; |
|
202
|
|
|
|
|
453
|
|
170
|
101
|
|
|
|
|
258
|
my $bestval = $valsn->at($minind); |
171
|
|
|
|
|
|
|
|
172
|
101
|
|
|
|
|
1610
|
sumover( $simp->xchg( 0, 1 ), $ssum ); |
173
|
101
|
|
|
|
|
625
|
$ssum -= ( $worst = $simp->slice( ":,(" . $maxind->at(0) . ")" ) ); |
174
|
101
|
|
|
|
|
270
|
$ssum /= $nd; |
175
|
101
|
|
|
|
|
2331
|
$new = 2 * $ssum - $worst; |
176
|
101
|
|
|
|
|
544
|
my $val = ( &{$sub}($new) )->at(0); |
|
101
|
|
|
|
|
296
|
|
177
|
101
|
50
|
|
|
|
638
|
if ($t) { |
178
|
0
|
|
|
|
|
0
|
$val = $val - $t * ( -log( rand() + 0.00001 ) ); |
179
|
|
|
|
|
|
|
} |
180
|
101
|
|
|
|
|
154
|
my $removetop = 0; |
181
|
101
|
100
|
|
|
|
306
|
if ( $val < $bestval ) { |
|
|
100
|
|
|
|
|
|
182
|
30
|
|
|
|
|
556
|
my $newnew = $new + $ssum - $worst; |
183
|
30
|
|
|
|
|
128
|
my $val2 = &{$sub}($newnew); |
|
30
|
|
|
|
|
87
|
|
184
|
30
|
100
|
|
|
|
1353
|
if ( $val2->at(0) < $val ) { |
185
|
|
|
|
|
|
|
# print "CASE1 Reflection and Expansion\n"; |
186
|
22
|
|
|
|
|
57
|
$worst .= $newnew; |
187
|
22
|
|
|
|
|
38
|
$val = $val2; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
else { |
190
|
|
|
|
|
|
|
# print "CASE2 Reflection, $newnew, $val, $val2\n"; |
191
|
8
|
|
|
|
|
26
|
$worst .= $new; |
192
|
|
|
|
|
|
|
} |
193
|
30
|
|
|
|
|
123
|
$removetop = 1; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
elsif ( $val < $worstvals[1] ) { |
196
|
|
|
|
|
|
|
# print "CASE3 Reflection\n"; |
197
|
12
|
|
|
|
|
33
|
$worst .= $new; |
198
|
12
|
|
|
|
|
23
|
$removetop = 1; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
else { |
201
|
59
|
|
|
|
|
1794
|
my $newnew = 0.5 * $ssum + 0.5 * $worst; |
202
|
59
|
|
|
|
|
496
|
my $val2 = &{$sub}($newnew); |
|
59
|
|
|
|
|
166
|
|
203
|
59
|
50
|
|
|
|
2694
|
if ( $val2->at(0) < $worstvals[0] ) { |
204
|
|
|
|
|
|
|
# print "CASE4 Contraction, $newnew, $val, $val2\n"; |
205
|
59
|
|
|
|
|
155
|
$worst .= $newnew; |
206
|
59
|
|
|
|
|
114
|
$val = $val2; |
207
|
59
|
|
|
|
|
229
|
$removetop = 1; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
} |
210
|
101
|
50
|
|
|
|
204
|
if ($removetop) { |
211
|
101
|
|
|
|
|
224
|
( my $stoopid = $vals->slice( "(" . $maxind->at(0) . ")" ) ) .= $val; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
else { |
214
|
|
|
|
|
|
|
# print "CASE5 Multiple Contraction\n"; |
215
|
0
|
|
|
|
|
0
|
$simp = 0.5 * $simp->slice(":,$minind") + 0.5 * $simp; |
216
|
0
|
|
|
|
|
0
|
my $idx = which( sequence($nd+1) != $minind ); |
217
|
0
|
|
|
|
|
0
|
( my $stoopid = $vals->index($idx) ) .= &{$sub}($simp->dice_axis(1,$idx)); |
|
0
|
|
|
|
|
0
|
|
218
|
|
|
|
|
|
|
} |
219
|
101
|
|
|
|
|
559
|
my $ss1 = ( $simp - $simp->slice(":,0") )**2; |
220
|
101
|
|
|
|
|
985
|
sumover( $ss1, ( $ss2 = PDL->null ) ); |
221
|
101
|
|
|
|
|
323
|
$ssize = PDL::max( sqrt($ss2) ); |
222
|
101
|
50
|
|
|
|
950
|
&{$logsub}( $simp, $vals, $ssize ) |
|
0
|
|
|
|
|
0
|
|
223
|
|
|
|
|
|
|
if $logsub; |
224
|
|
|
|
|
|
|
} |
225
|
2
|
|
|
|
|
10
|
minimum_ind( $vals, ( my $mmind = PDL->null ) ); |
226
|
2
|
|
|
|
|
9
|
return ( $simp->slice(":,$mmind"), $ssize, $vals->index($mmind) ); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
1; |