forked from imagej/ImageJ
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMinimizer.java
849 lines (802 loc) · 47.3 KB
/
Minimizer.java
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
package ij.measure;
import ij.*;
import ij.gui.*;
import ij.macro.*;
import ij.gui.Roi;
import ij.gui.PolygonRoi;
import ij.gui.Line;
import ij.util.Tools;
import ij.plugin.frame.RoiManager;
import java.util.Random;
import java.util.Arrays;
import java.util.Vector;
import java.util.Hashtable;
/** Minimizer based on Nelder-Mead simplex method (also known as polytope method),
* including the 'outside contraction' as described in:
* J.C. Lagarias, J.A. Reeds, M.H. Wright, P. Wright:
* Convergence properties of the Nelder-Mead simplex algorithm in low dimensions.
* SIAM J. Optim. 9, 112-147 (1998).
* Differences w.r.t. this publication:
* - If outside contraction is rejected, instead of shrinking the whole simplex, an inside
* contraction is tried first. Own experiments show that this results in slightly better
* performance for some test functions (Perm, Mc Kinnon's function with tau=2, theta=6,
* Osborne1 curve-fitting problem). In most cases, there is no difference, however.
* - This implementation does not include any 'ordering rules' in case of equal function values.
* - When checking for convergence, a special iteration step may be performed, improving
* the best vertex of the simplex.
*
* Re-initialization within a minimization run:
* In some cases, the simplex algorithm may fail to find the minimum or the convergence
* check might stop it prematurely. Therefore, the search is initialized again, keeping the
* best vertex of the simplex and setting the other vertices to random values, but keeping
* variations of the parameters w.r.t. the best vertex at a similar value. If re-initializing
* the simplex does not lead to a significant improvement, the value is accepted as true
* (local) minimum.
*
* Multiple minimization runs:
* In spite of re-initializing (see above), there are rare cases where minimization is stopped
* too early. Also, minimization may result in a local minimum. Therefore, unless determined
* otherwise by setting 'setRestarts', two minimization runs with different initialization
* of the simplex are started in parallel threads. If the results don't agree within the
* error limit, two more minimization runs are started. This is repeated until the two best
* results agree within the error limits or the number of restarts (determined by 'setRestarts';
* default 2, i.e., up to 3 runs with two threads each) is exceeded.
* This does not guarantee that the minimum is a global minimum, however: A local minimum
* will be accepted if the minimizer finds a local minimum twice (or two different local
* minima with the same function value within the error bounds), but no better minimum has
* been found at that time.
*
* The user-supplied target function should return NaN for out-of-bounds parameters instead
* of a high (penalty) value (minimization is faster and more reliable with NaNs).
* The region where the function is defined (e.g. not returning NaN) must be convex.
* Sharp corners of the region where the function value is defined (especially in higher dimensions)
* may cause a problem with finding suitable test points when (re-)initializing the simplex.
* If all attempts to find initial points result in NaN, the status returned is
* INITIALIZATION_FAILURE.
*
* Versions:
* Michael Schmid 2012-01-30: first version, based on previous CurveFitter
* 2012-11-20: mor tries to find initial params not leading to NaN
* 2013-09-24: 50% higher maximum iteration count, and never uses more than 0.4*maxIter
* iterations per minimization to avoid trying too few sets of initial params
* 2013-10-13: setStatusAndEscape to show iterations and enable abort by ESC
* 2014-09-16: normalization bug fixed. makeNewParamVariations checks for paramResolutions
*
*/
public class Minimizer {
/** Status returned: successful completion */
public final static int SUCCESS = 0;
/** Status returned: Could not initialize the simplex because either the initialParams
* resulted in the target function returning NaN or all attempts to find starting
* parameters for the other simplex points resulted in the target function returning NaN.
* No minimization was possible. */
public final static int INITIALIZATION_FAILURE = 1;
/** Status returned: aborted by call to abort method. */
public final static int ABORTED = 2;
/** Status returned: Could not reinitialize the simplex because all attempts to find restarting
* parameters resulted in the target function returning NaN. Reinitialization is
* needed to obtain a reliable result; so the result may be inaccurate or wrong. */
public final static int REINITIALIZATION_FAILURE = 3;
/** Status returned: no convergence detected after maximum iteration count */
public final static int MAX_ITERATIONS_EXCEEDED = 4;
/** Status returned: not two equal solutions after maximum number of restarts */
public final static int MAX_RESTARTS_EXCEEDED = 5;
/** Strings describing the status codes */
public final static String[] STATUS_STRING = { "Success",
"Initialization failure; no result",
"Aborted",
"Re-initialization failure (inaccurate result?)",
"Max. no. of iterations reached (inaccurate result?)",
"Max. no. of restarts reached (inaccurate result?)"};
private final static double C_REFLECTION = 1.0; // reflection coefficient
private final static double C_CONTRACTION = 0.5; // contraction coefficient
private final static double C_EXPANSION = 2.0; // expansion coefficient
private final static double C_SHRINK = 0.5; // shrink coefficient
private final static int ITER_FACTOR = 750; // maximum number of iterations per numParams^2; twice that value for 2 threads
private final static int WORST=0, NEXT_WORST=1, BEST=2;//indices in array to pass the numbers of the respective vertices
private int numParams; // number of independent variables (parameters)
private int numVertices; // numParams+1, number of vertices of the simplex
private int numExtraArrayElements; // number of extra array elements for private use in user function
private UserFunction userFunction; // target function to minimize
private double maxRelError = 1e-10; // max relative error
private double maxAbsError = 1e-100; // max absolute error
private double[] paramResolutions; // differences of parameters less than these values are considered 0
private int maxIter; // stops after this number of iterations
private int totalNumIter; // number of iterations performed in all tries so far
private int numCompletedMinimizations; // number of minimizations completed
private int maxRestarts = 2; // number of times to try finding local minima; each uses 2 threads if restarts>0
private int randomSeed; // for starting the random number generator
private boolean useSingleThread = // single thread if one processor or set by useSingleThread(true)
Runtime.getRuntime().availableProcessors() <= 1;
private int status; // SUCCESS or error code
private boolean wasInitialized; // initialization was successful at least once
private double[] result; // result data+function value
private Vector<double[]> resultsVector; // holds several results if multiple tries; the best one is kept.
private String ijStatusString = null; // shown together with iteration count in status, no display if null
private boolean checkEscape; // whether to stop when Escape is pressed
private int nextIterationForStatus = 10;// next iteration when we should display the status
private long startTime; // of the whole minimization process
/*private Hashtable<Thread, double[][]> simpTable =
new Hashtable<Thread, double[][]>(); //for each thread, holds a reference to its simplex */
//-----------------------------------------------------------------------------
// We can't set the function in the constructor because the CurveFitter
// allows specifying the fit function after other variables
/**
* Set the the target function, i.e. function whose value should be minimized.
* @param userFunction The class having a function to minimize (implementing
* the UserFunction interface).
* This function must allow simultaneous calls in multiple threads unless
* setMaximumThreads(1); has been called.
* Note that the function will be called with at least numParams+1 array
* elements; the last one is needed to store the function value. Further
* array elements for private use in the user function may be added by
* calling setExtraArrayElements.
* @param numParams Number of independent variables (also called parameters)
* of the function.
*/
public void setFunction(UserFunction userFunction, int numParams) {
if (maxIter<=0) {
maxIter = ITER_FACTOR*numParams*numParams;
if (maxRestarts > 0)
maxIter *= 2;
}
this.userFunction = userFunction;
this.numParams = numParams;
this.numVertices = numParams+1;
}
/** Perform minimization with the gradient-enhanced simplex method once or a few
* times, depending on the value of 'restarts'. Running it several times helps
* to reduce the probability of finding local minima or accepting one of the rare
* results where the minimizer has got stuck before finding the true minimum.
* We are using two threads and terminate after two equal results. Thus, apart
* from the overhead of starting a new thread (typically < 1 ms), for unproblematic
* minimization problems on a dual-core machine this is almost as fast as running
* it once.
*
* Use 'setFunction' first to define the function and number of parameters.
* Afterwards, use the 'getParams' method to access the result.
*
* @param initialParams Array with starting values of the parameters (variables).
* When null, the starting values are assumed to be 0.
* The target function must be defined (not returning NaN) for
* the values specified as initialParams.
* @param initialParamVariations Parameters (variables) are initially varied by up to +/-
* this value. If not given (null), initial variations are taken as
* 10% of initial parameter value or 0.01 for parameters that are zero.
* When this array is given, all elements must be positive (nonzero).
* If one or several initial parameters are zero, is advisable to set the initialParamVariations
* array to useful values indicating the typical order of magnitude of the parameters.
* For target functions with only one minimum, convergence is fastest with large values of
* initialParamVariations, so that the expected value is within initialParam+/-initialParamVariations.
* If local minima can occur, it is best to use a value close to the expected global minimum,
* and rather small initialParamVariations, much lower than the distance to the nearest local
* minimum.
*
* @return status code; SUCCESS if two attempts have found minima with the
* same value (within the error bounds); so a minimum has been found
* with very high probability.
*/
public int minimize(final double[] initialParams, final double[] initialParamVariations) {
status = SUCCESS;
resultsVector = new Vector<double[]>();
int maxLoopCount = maxRestarts+1;
if (useSingleThread) maxLoopCount*=2; // if we have only one thread, loop twice as many times
for (int i=0; i<maxLoopCount; i++) { // try several times, until we have twice the same result
Thread secondThread = null;
if (maxRestarts>0 && !useSingleThread) { // set up 2nd thread to minimize
final int seed = randomSeed+1000000+i;
final Thread thread = new Thread(
new Runnable() {
final public void run() {
minimizeOnce(initialParams, initialParamVariations, seed);
}
}, "Minimizer-1"
);
thread.setPriority( Thread.currentThread().getPriority() );
thread.start();
secondThread = thread;
}
minimizeOnce(initialParams, initialParamVariations, randomSeed+i); //minimize in main thread
if (secondThread != null) try {
secondThread.join(); // wait until send thread is done
} catch (InterruptedException e) {}
if (resultsVector.size() == 0 && result==null)
return status;
if (result==null)
result = (double[])resultsVector.get(0);
for (double[] r : resultsVector) // find best result so far
if (value(r) < value(result))
result = r;
if (status != SUCCESS && status != REINITIALIZATION_FAILURE && status != MAX_ITERATIONS_EXCEEDED)
return status; // no more tries if permanent error or aborted
if (totalNumIter >= maxIter)
return MAX_ITERATIONS_EXCEEDED; // no more tries if too many iterations
for (int ir=0; ir<resultsVector.size(); ir++)
if (!belowErrorLimit(value((double[])resultsVector.get(ir)), value(result), 1.0)) {
resultsVector.remove(ir); // discard results that are significantly worse
ir --;
}
if (resultsVector.size() >= 2) return SUCCESS; // if we have two (almost) equal results, it's enough
} //for i <= maxRestarts
if (ijStatusString != null)
IJ.showStatus(""); // reset status display
return maxRestarts>0 ?
MAX_RESTARTS_EXCEEDED : // number of restarts exceeded without two equal results
status; // if only one run was required, we can't have 2 equal results
}
/** Perform minimization with the simplex method once, including re-initialization until
* we have a stable solution.
* Use the 'getParams' method to access the result.
*
* @param initialParams Array with starting values of the parameters (variables).
* When null, the starting values are assumed to be 0.
* The target function must be defined (not returning NaN) for
* the values specified as initialParams.
* @param initialParamVariations Parameters (variables) are initially varied by up to +/-
* this value. If not given (null), iniital variations are taken as
* 10% of inial parameter value or 0.01 for parameters that are zero.
* When this array is given, all elements must be positive (nonzero).
* If one or several initial parameters are zero, is advisable to set the initialParamVariations
* array to useful values indicating the typical order of magnitude of the parameters.
* For target functions with only one minimum, convergence is fastest with large values of
* initialParamVariations, so that the expected value is within initialParam+/-initialParamVariations.
* If local minima can occur, it is best to use a value close to the expected global minimum,
* and rather small initialParamVariations, much lower than the distance to the nearest local
* minimum.
*
* @return status code; SUCCESS if it is considered likely that a minimum of the
* target function has been found.
*/
public int minimizeOnce(double[] initialParams, double[] initialParamVariations) {
status = SUCCESS;
minimizeOnce(initialParams, initialParamVariations, randomSeed);
return status;
}
/** Get the result, i.e., the set of parameter values (i.e., variable values)
* from the best corner of the simplex. Note that the array returned may have more
* elements than numParams; ignore the rest.
* May return an array with only NaN values in case the minimize call has returned
* an INITIALIZATION_FAILURE status or that abort() has been called at the very
* beginning of the minimization.
* Do not call this method before minimization. */
public double[] getParams() {
if (result == null) {
result = new double[numParams+1+numExtraArrayElements];
Arrays.fill(result, Double.NaN);
}
return result;
}
/** Get the value of the minimum, i.e. the value associated with the resulting parameters
* as obtained by getParams(). May return NaN in case the minimize call has returned
* an INITIALIZATION_FAILURE status or that abort() has been called at the very
* beginning of the minimization.
* Do not call this method before minimization. */
public double getFunctionValue() {
if (result == null) {
result = new double[numParams+1];
Arrays.fill(result, Double.NaN);
}
return value(result);
}
/** Get number of iterations performed (includes all restarts). One iteration needs
* between one and numParams+3 calls of the target function (typically two calls
* per iteration) */
public int getIterations() {
return totalNumIter;
}
/** Set maximum number of iterations allowed (including all restarts and all threads).
* The number of function calls will be higher, up to about twice the number of
* iterations.
* Note that the number of iterations needed typically scales with the square of
* the dimensions (i.e., numParams^2).
* Default value is 1000 * numParams^2 (half that value if maxRestarts=0), which is
* enough for >99% of all cases (if the maximum number of restarts is set to 2);
* typical number of iterations are below 10 and 20% of this value.
*/
public void setMaxIterations(int x) {
maxIter = x;
}
/** Get maximum number of iterations allowed. Unless given by 'setMaxIterations',
* this value is defined only after running 'setFunction' */
public int getMaxIterations() {
return maxIter;
}
/** Set maximum number of minimization restarts to do.
* With n=0, the minimizer is run once in a single thread.
* With n>0, two threads are used, and if the two results do not agree within
* the error bounds, additional optimizations are done up to n times, each
* with two threads. In any case, if the two best results are within the error
* bounds, the best result is accepted.
* Thus, on dual-core machines running no other jobs, values of n=1 or n=2 (default)
* do not cause a notable increase of computing time for 'easy' optimization problems,
* while greatly reducing the risk of running into spurious local minima or non-
* optimal results due to the minimizer getting stuck. In problematic cases, the
* improved
* The 'n' value does not refer to the restarts within one minimization run
* (there, at least one restart is performed, and restart is repeated until the result
* does not change within the error bounds).
* This value does not affect the 'minimizeOnce' function call.
* When setting the maximum number of restarts to a value much higher than 3, remember
* to adjust the maximum number of iterations (see setMaxIterations).
*/
public void setMaxRestarts(int n) {
maxRestarts = n;
}
/** Get maximum number of minimization restarts to do */
public int getMaxRestarts() {
return maxRestarts;
}
/** Get number of minimizations completed (i.e. not aborted or stopped because the
* number of minimization was exceeded). After a minimize(..) call, typically 2
* for unproblematic cases. Higher number indicate a functin that is difficult to
* minimize or the existence of more than one minimum.
*/
public int getCompletedMinimizations() {
return numCompletedMinimizations;
}
/** Set a seed to initialize the random number generator, which is used for initialization
* of the simplex. */
public void setRandomSeed(int n) {
randomSeed = n;
}
/** Sets the accuracy of convergence. Minimizing is done as long as the
* relative error of the function value is more than this number (Default: 1e-10).
*/
public void setMaxError(double maxRelError) {
this.maxRelError = maxRelError;
}
/** Sets the accuracy of convergence. Minimizing is done as long as the
* relative error of the function value is more than maxRelError (Default: 1e-10)
* and the maximum absolute error is more than maxAbsError
* (i.e. it is enough to fulfil one of these two criteria)
*/
public void setMaxError(double maxRelError, double maxAbsError) {
this.maxRelError = maxRelError;
this.maxAbsError = maxAbsError;
}
/** Set the resolution of the parameters, for problems where the target function is not smooth
* but suffers from numerical noise. If all parameters of all vertices are closer to the
* best value than the respective resolution value, minimization is finished, irrespective
* of the difference of the target function values at the vertices */
public void setParamResolutions(double[] paramResolutions) {
this.paramResolutions = paramResolutions;
}
/** Call setMaximuThreads(1) to avoid multi-threaded execution (in case the user-provided
* target function does not allow moultithreading). Currently a maximum of 2 thread is used
* irrespective of any higher value. */
public void setMaximumThreads (int numThreads) {
useSingleThread = numThreads <= 1;
}
/** Aborts minimization. Calls to getParams() will return the best solution found so far.
* This method may be called from the user-supplied target function.
* If displayStatusAndCheckEsc has been called before, the Minimizer itself checks for the ESC key.
*/
public void abort() {
status = ABORTED;
}
/** Create output on the number of iterations in the ImageJ Status line, e.g.
* "<ijStatusString> 50 (max 750); ESC to stop"
* @param ijStatusString Displayed in the beginning of the status message. No display if null.
* E.g. "Optimization: Iteration "
* @param checkEscape When true, the Minimizer stops if escape is pressed and the status
* becomes ABORTED. Note that checking for ESC does not work in the Event Queue thread. */
public void setStatusAndEsc(String ijStatusString, boolean checkEscape) {
this.ijStatusString = ijStatusString;
this.checkEscape = checkEscape;
}
/** Add a given number of extra elements to array of parameters (independent vaiables)
* for private use in the user function. Note that the first numParams+1 elements
* should not be touched.*/
public void setExtraArrayElements(int numExtraArrayElements) {
this.numExtraArrayElements = numExtraArrayElements;
}
/** Get the full simplex of the current thread. This may be useful if the target function
* wants to modify the simplex */
/* public double[][] getSimplex() {
return simpTable.get(Thread.currentThread());
} */
/** One minimization run (including reinitializations of the simplex until the result is stable) */
private void minimizeOnce(double[] initialParams, double[] initialParamVariations, int seed) {
Random random = new Random(seed);
double[][] simp = makeSimplex(initialParams, initialParamVariations, random);
if (simp == null) {
status = wasInitialized ? REINITIALIZATION_FAILURE : INITIALIZATION_FAILURE;
return;
}
wasInitialized = true;
if (startTime == 0)
startTime = System.currentTimeMillis();
//if (IJ.debugMode) showSimplex(simp, seed+" Initialized:");
int bestVertexNumber = minimize(simp); // first minimization
double bestValueSoFar = value(simp[bestVertexNumber]);
//reinitialize until converged or error/aborted (don't care about reinitialization failure in other thread)
boolean reinitialisationFailure = false;
while (status == SUCCESS || status == REINITIALIZATION_FAILURE) {
double[] paramVariations =
makeNewParamVariations(simp, bestVertexNumber, initialParams, initialParamVariations);
if (!reInitializeSimplex(simp, bestVertexNumber, paramVariations, random)) {
reinitialisationFailure = true;
break;
}
//if (IJ.debugMode) showSimplex(simp, seed+" Reinitialized:");
bestVertexNumber = minimize(simp); // minimize with reinitialized simplex
if (belowErrorLimit(value(simp[bestVertexNumber]), bestValueSoFar, 2.0)) break;
bestValueSoFar = value(simp[bestVertexNumber]);
}
if (reinitialisationFailure)
status = REINITIALIZATION_FAILURE;
else if (status == SUCCESS || status == REINITIALIZATION_FAILURE) //i.e. not aborted, not max iterations exceeded
numCompletedMinimizations++; // it was a complete minimization
//if (IJ.debugMode) showSimplex(simp, seed+" Final:");
if (resultsVector != null) synchronized(resultsVector) {
resultsVector.add(simp[bestVertexNumber]);
} else
result = simp[bestVertexNumber];
}
/** Minimizes the target function by variation of the simplex.
* Note that one call to this function never does more than 0.4*maxIter iterations.
* @return index of the best value in simp
*/
private int minimize(double[][] simp) {
int[] worstNextBestArray = new int[3]; // used to pass these indices from 'order' function
double[] center = new double[numParams+1+numExtraArrayElements]; // center of all vertices except worst
double[] reflected = new double[numParams+1+numExtraArrayElements]; // the 1st new vertex to try
double[] secondTry = new double[numParams+1+numExtraArrayElements]; // expanded or contracted vertex
order(simp, worstNextBestArray);
int worst = worstNextBestArray[WORST];
int nextWorst = worstNextBestArray[NEXT_WORST];
int best = worstNextBestArray[BEST];
//showSimplex(simp, "before minimization, value="+value(simp[best]));
//String operation="ini";
int thisNumIter=0;
while (true) {
totalNumIter++; // global count over all threads
thisNumIter++; // local count for this minimize call
// THE MINIMIZAION ALGORITHM IS HERE
iteration: {
getCenter(simp, worst, center); // centroid of vertices except worst
// Reflect worst vertex through centriod of not-worst
getVertexAndEvaluate(center, simp[worst], -C_REFLECTION, reflected);
if (value(reflected) <= value(simp[best])) { // if it's better than the best...
// try expanding it
getVertexAndEvaluate(center, simp[worst], -C_EXPANSION, secondTry);
if (value(secondTry) <= value(reflected)) {
copyVertex(secondTry, simp[worst]); // if expanded is better than reflected, keep it
//operation="expa";
break iteration;
}
}
if (value(reflected) < value(simp[nextWorst])) {
copyVertex(reflected, simp[worst]); // keep reflected if better than 2nd worst
//operation="refl";
break iteration;
} else if (value(reflected) < value(simp[worst])) {
// try outer contraction
getVertexAndEvaluate(center, simp[worst], -C_CONTRACTION, secondTry);
if (value(secondTry) <= value(reflected)) {
copyVertex(secondTry, simp[worst]); // keep outer contraction
//operation="outC";
break iteration;
}
} else if (value(reflected) > value(simp[worst]) || Double.isNaN(value(reflected))) {
// else inner contraction
getVertexAndEvaluate(center, simp[worst], C_CONTRACTION, secondTry);
if (value(secondTry) < value(simp[worst])) {
copyVertex(secondTry, simp[worst]); // keep contracted if better than 2nd worst
//operation="innC";
break iteration;
}
}
// if everything else has failed, contract simplex in on best
shrinkSimplexAndEvaluate(simp, best);
//operation="shri";
break iteration;
} // iteration:
boolean checkParamResolution = // if new 'worst' is not close to 'best', don't check any further
paramResolutions!=null && belowResolutionLimit(simp[worst], simp[best]);
order(simp, worstNextBestArray);
worst = worstNextBestArray[WORST];
nextWorst = worstNextBestArray[NEXT_WORST];
best = worstNextBestArray[BEST];
if (checkParamResolution)
if (belowResolutionLimit(simp, best)) // check whether all parameters are within the resolution limit
break;
if (belowErrorLimit(value(simp[best]), value(simp[worst]), 4.0)) { // make sure we are at the minimum:
getCenter(simp, -1, secondTry); // check center of the simplex
evaluate(secondTry);
if (value(secondTry) < value(simp[best]))
copyVertex(secondTry, simp[best]); // better than best: keep
}
if (belowErrorLimit(value(simp[best]), value(simp[worst]), 4.0)) // no large spread of values
break; // looks like we are at the minimum
if (totalNumIter > maxIter || thisNumIter>4*(maxIter/10))
status = MAX_ITERATIONS_EXCEEDED;
if (status != SUCCESS)
break;
if ((ijStatusString != null || checkEscape) && totalNumIter > nextIterationForStatus) {
long time = System.currentTimeMillis();
nextIterationForStatus = totalNumIter + (int)(totalNumIter*500L/(time-startTime+1)); //next display 0.5 sec later
if (time - startTime > 1000L) { // display status and check for ESC after the first second
if (checkEscape && IJ.escapePressed()) {
status = ABORTED;
IJ.resetEscape();
IJ.showStatus(ijStatusString+" ABORTED");
break;
}
if (ijStatusString != null) {
String statusString = ijStatusString+totalNumIter+" ("+maxIter+" max)";
if (checkEscape) statusString += " ESC to stop";
IJ.showStatus(statusString);
}
}
}
}
//showSimplex(simp, "after "+totalNumIter+" iterations: value="+value(simp[best]));
return best;
}
/** Move along the line between center and worst, where +1 corresponds to worstVertex
* The new point is written to'newVertex' and target function is evaluated there
*/
private void getVertexAndEvaluate(double[] center, double[] worstVertex, double howFar, double[] newVertex) {
for (int i = 0; i < numParams; i++)
newVertex[i] = (1.-howFar)*center[i] + howFar*worstVertex[i];
evaluate(newVertex);
}
/** Get center of all vertices except one to exclude
* (may be -1 to exclude none)
* Does not care about function values (i.e., last array elements) */
private void getCenter(double[][]simp, int excludeVertex, double[] center) {
Arrays.fill(center, 0.);
int nV = 0;
for (int v=0; v<numVertices; v++)
if (v != excludeVertex) {
for (int i=0; i<numParams; i++)
center[i] += simp[v][i];
nV++;
}
double norm = 1.0/nV;
for (int i=0; i<numParams; i++)
center[i] *= norm;
}
private void shrinkSimplexAndEvaluate(double[][] simp, int best) {
for (int v=0; v<numVertices; v++)
if (v != best) {
for (int i=0; i<numParams; i++)
simp[v][i] = C_SHRINK*simp[v][i] + (1-C_SHRINK)*simp[best][i];
evaluate(simp[v]);
}
}
/** Whether the highest and lowest values fulfill the error limit criterion.
* Error limits are reduced by a factor of 'sensitivity' */
private boolean belowErrorLimit(double highest, double lowest, double sensitivity) {
double absError = sensitivity*Math.abs(highest - lowest);
double relError = absError/(Math.max(Math.abs(highest),Math.abs(lowest))+1e-100);
return relError < maxRelError || absError < maxAbsError;
}
/** Initialise the simplex and evaluate it. Returns null on failure. */
private double[][] makeSimplex(double[] initialParams, double[] initialParamVariations, Random random) {
double[][] simp = new double[numVertices][numParams+1+numExtraArrayElements];
/* simpTable.put(Thread.currentThread(), simp); */
if (initialParams!=null) {
for (int i=0; i<numParams; i++)
if (Double.isNaN(initialParams[i]))
if (IJ.debugMode) IJ.log("Warning: Initial Parameter["+i+"] is NaN");
System.arraycopy(initialParams, 0, simp[0], 0, Math.min(initialParams.length, numParams));
}
evaluate(simp[0]);
if (Double.isNaN(value(simp[0]))) {
if (IJ.debugMode) showVertex(simp[0], "Warning: Initial Parameters yield NaN:");
findValidInitalParams(simp[0], initialParamVariations, random);
}
if (Double.isNaN(value(simp[0]))) {
if (IJ.debugMode) IJ.log("Error: Could not find initial parameters not yielding NaN:");
return null;
}
if (initializeSimplex(simp, initialParamVariations, random))
return simp;
else {
if (IJ.debugMode) showSimplex(simp, "Error: Could not make simplex vertices not yielding NaN");
return null;
}
}
/** Whether the distance between the best and any other simplex vertex
* is less than the resolution of the parameters */
private boolean belowResolutionLimit(double[][] simp, int best) {
for (int v=0; v<numVertices; v++)
if (v!= best && !belowResolutionLimit(simp[v], simp[best]))
return false;
return true;
}
/** Whether the distance between two vertices is less than the resolution of the parameters */
private boolean belowResolutionLimit(double[] vertex1, double[] vertex2) {
for (int i=0; i<numParams; i++)
if (Math.abs(vertex1[i]-vertex2[i]) >= paramResolutions[i])
return false;
return true;
}
/** Find initial parameters not yielding a result of NaN in case those given yield NaN
* Called with params containing the initial parameters that have been tried previously */
private void findValidInitalParams(double[] params, double[] initialParamVariations, Random random) {
final int maxAttempts = 50*numParams*numParams; //max number of attempts to find params that do not lead to NaN
double rangeFactor = 1; // will gradually become larger to handle different orders of magnitude
final double rangeMultiplyLog = Math.log(1e20)/(maxAttempts-1); //will try up to 1e-20 to 1e20*initialParamVariations
double[] firstParams = new double[numParams]; // remember starting params (which may be modified)
double[] variations = new double[numParams]; // new values of parameter variations
for (int i=0; i<numParams; i++) {
firstParams[i] = Double.isNaN(params[i]) ? 0 : params[i];
variations[i] = initialParamVariations!=null ? initialParamVariations[i] : 0.1*firstParams[i];
if (Double.isNaN(variations[i]) || Math.abs(variations[i])<1e-10 || Math.abs(variations[i])>1e10)
variations[i] = 0.1;
}
for (int attempt=0; attempt<maxAttempts; attempt++) {
for (int i=0; i<numParams; i++) {
double multiplier = attempt<maxAttempts/10 ? 1 : //after a while try different orders of magnitude
Math.exp(rangeMultiplyLog*attempt*2*(random.nextDouble()-0.5));
params[i] = multiplier*(firstParams[i]+2*(random.nextDouble()-0.5)*variations[i]);
}
evaluate(params);
//showVertex(params,"findValidInitalParams attempt "+attempt);
if (!Double.isNaN(value(params))) return; //found a valid parameter set
}
}
/** Reinitialize an existing simplex: Create new vertices around the best one, keeping the rough size of
* the simplex. This helps to avoid premature termination or cases where the simplex has become degenerate.
* All points of the new simplex are evaluated already.
* Returns false on error (if it could not create a simplex not resulting in NaN). */
private boolean reInitializeSimplex(double[][] simp, int bestVertexNumber, double[] paramVariations, Random random) {
if (bestVertexNumber != 0) {
double[] swap = simp[0];
simp[0] = simp[bestVertexNumber];
simp[bestVertexNumber] = swap;
}
return initializeSimplex(simp, paramVariations, random);
}
/** Initialize simplex vertices except the number 0, which will be taken as starting point.
* All points of the new simplex are evaluated already.
* Returns false on error (if it could not create a simplex not resulting in NaN).
*/
private boolean initializeSimplex(double[][] simp, double[] paramVariations, Random random) {
double[] variations = new double[numParams]; // improved values of parameter variations
for (int i=0; i<numParams; i++) {
double range = paramVariations!=null && i<paramVariations.length ?
paramVariations[i] : 0.1 * Math.abs(simp[0][i]); // if nothing else given, based on initial parameter
if (range < 1e-100 || Double.isNaN(range))
range = 0.01; //range must be nonzero
if (Math.abs(range/simp[0][i])<1e-10)
range = Math.abs(simp[0][i]*1e-10); //range must be more than very last digits
variations[i] = range;
//IJ.log("param#"+i+" variation="+(float)variations[i]);
}
final int maxAttempts = 100*numParams; //max number of attempts to find params that do not lead to NaN
for (int v=1; v<numVertices; v++) {
int numTries = 0;
do { //try finding a vertex that does not yield NaN
if (numTries++ > maxAttempts)
return false;
// Create a vector orthogonal to all others in a coordinate system where every value is
// normalized to its respective variations value. We first use this coordinate system where
// every parameter is in the [-1, +1] range.
// Don't orthogonalize after getting only NaN function values in many attempts; it might be
// easier to find viable parameters without normalization.
for (int i=0; i<numParams; i++)
simp[v][i] = 2*random.nextFloat() - 1.0;
if (numTries < maxAttempts/3) // (don't orthogonalize if finding valid params was very difficult
//IJ.log("orthogonalize vertex "+v);
for (int v2=1; v2<v; v2++) { // to avoid a degenerate simplex, make orthogonal to all others
double lengthSqr = 0;
double innerProduct = 0;
for (int i=0; i<numParams; i++) {
double x2 = (simp[v2][i] - simp[0][i])/variations[i]; // convert to [-1, +1] range
lengthSqr += x2*x2;
innerProduct += x2*simp[v][i];
}
for (int i=0; i<numParams; i++) //subtract component parallel to the other vector
simp[v][i] -= ((simp[v2][i] - simp[0][i])/variations[i])*(innerProduct/lengthSqr);
}
double sumSqr = 0; // normalize the 'excursion vector' to length = 1
for (int i=0; i<numParams; i++)
sumSqr += simp[v][i]*simp[v][i];
if (sumSqr < 1e-14) continue; // bad luck with random numbers, excursion vector almost zero
if (numTries > 2 && sumSqr > 1e-6)
sumSqr = 1; // don't normalize if attempts with normalized were unsuccessful
for (int i=0; i<numParams; i++)
simp[v][i] = simp[0][i] + simp[v][i]/Math.sqrt(sumSqr)*variations[i];
evaluate(simp[v]);
} while (Double.isNaN(value(simp[v])) && (status == SUCCESS || status == REINITIALIZATION_FAILURE));
}
//showSimplex(simp, 0);
return true;
}
/** Calculate ranges for parameter variations for re-initializing:
* Note that the simplex should have adapted in size to the useful range of each
* parameter, but in special cases it might have become degenerate [i.e. the
* (hyper-)volume spanned by the vertices might be zero; in such a case it may
* happen that one parameter has the same value for all vertices of the simplex].
*
* For each parameter, as a variation value we use the typical difference w.r.t. the best
* vertex of the simplex.
* To avoid a degenerate simplex, we have to know what the the relative orders of magnitude of
* the parameter variations should be. If initialParamVariations are given, we use them as
* indication of the typoical relative values; otherwise we use the parameter values themselves
* and assume that the variation of each parameter should be roughly the same fraction of the value.
* If the variation of a parameter in the simplex w.r.t. this estimated variation is much
* smaller (<10^3) than typical for the parameters, we increase the variation for this parameter.
*/
private double[] makeNewParamVariations(double[][] simp, int bestVertexNumber,
double[] initialParams, double[] initialParamVariations) {
double[] paramVariations = new double[numParams]; // will be the return value
double[] relatedTo = new double[numParams]; // parameter variation should be related to (in size) these values
double logTypicalRelativeVariation = 0;
for (int i=0; i<numParams; i++) { // for all parameters
double variation = 0; // roughly size of simplex in direction of that parameter
for (int v=0; v<numVertices; v++)
if (v != bestVertexNumber) {
double delta = Math.abs(simp[v][i] - simp[bestVertexNumber][i]);
paramVariations[i] += delta*delta;
}
paramVariations[i] = 10*Math.sqrt(paramVariations[i]); // make the new simplex larger than the old one
relatedTo[i] = initialParamVariations!=null && initialParamVariations.length>= numParams ?
initialParamVariations[i] : // relate to initialParamVariations (if given),
Math.max(Math.abs(initialParams[i]), Math.abs(simp[bestVertexNumber][i]));
double logRelativeVariation = paramVariations[i]>relatedTo[i] ? 0 :
Math.log(paramVariations[i]/relatedTo[i]);
logTypicalRelativeVariation += logRelativeVariation;
}
logTypicalRelativeVariation /= numParams;
double typicalRelativeVariation = Math.exp(logTypicalRelativeVariation);
final double WORST_RATIO = 1e-3; //parameter variation should not be lower than typical value by more than this
for (int i=0; i<numParams; i++) {
if (paramVariations[i]<relatedTo[i] && paramVariations[i]/relatedTo[i] < typicalRelativeVariation*WORST_RATIO)
paramVariations[i] = relatedTo[i]*typicalRelativeVariation*WORST_RATIO;
if (paramResolutions != null && paramVariations[i] < 10*paramResolutions[i])
paramVariations[i] = 10*paramResolutions[i];// parameter variation must be well above numeric noise limit
}
return paramVariations;
}
/** Evaluate the target function for the parameters given by 'vertex'
* The function value is stored into the last (extra) array element.
*/
private void evaluate(double[] vertex) {
vertex[numParams] = userFunction.userFunction(vertex, 0);
}
/** Function value of a vertex after call to 'evaluate'
* (stored it as last array element) */
private double value(double[] vertex) {
return vertex[numParams];
}
/** Keep the newVertex: replace a given Vertex with it */
void copyVertex(double[] newVertex, double[] vertex) {
System.arraycopy(newVertex, 0, vertex, 0, newVertex.length);
}
/** Find the simplex vertices with the worst, nextWorst and best values
* of the target function */
private void order(double[][] simp, int[] worstNextBestArray) {
int worst = 0, nextWorst = 0, best = 0;
for (int i = 0; i < numVertices; i++) {
if (value(simp[i]) < value(simp[best])) best = i;
if (value(simp[i]) > value(simp[worst])) worst = i;
}
nextWorst = best;
for (int i = 0; i < numVertices; i++)
if (i != worst && value(simp[i]) > value(simp[nextWorst]))
nextWorst = i;
worstNextBestArray[WORST] = worst;
worstNextBestArray[NEXT_WORST] = nextWorst;
worstNextBestArray[BEST] = best;
}
// Display simplex [Iteration: s0(p1, p2....), s1(),....] in Log window
private synchronized void showSimplex(double[][] simp, String heading) {
IJ.log("Minimizer: "+heading);
for (int i = 0; i < numVertices; i++)
showVertex(simp[i], null);
}
private synchronized void showVertex(double[] vertex, String heading) {
if (heading != null)
IJ.log(heading);
String s = "";
for (int j=0; j < numParams; j++)
s += " " + IJ.d2s(vertex[j], 8,12);
s += " -> " + IJ.d2s(value(vertex), 8,12);
IJ.log(s);
}
}