1 (defpackage :simplex-anneal
3 (:export
#:make-simplex
5 (in-package :simplex-anneal
)
7 ;; nr3.pdf p.526 10.5 downhill simplex
9 #|| A simplex in N dimensions has N
+1 points
, i.e. a triangle in
2D.
10 You give one ND point. The algorithm constructs the simplex around
11 it
(distances depend on the problems characteristic length scale
)
14 There are five different types of steps
:
15 a
) reflection
(conserving the volume
)
16 b
) reflection and expansion
18 d
) multiple-contraction
20 We keep track of highest and next to highest
23 Step is fractionally smaller than some tolerance tol.
25 Decrease in function value is fractionally smaller than some tolerance tol.
27 tol
> sqrt
(eps), tol can be
\approx eps
29 If a minimum is found
, restart the routine with
2 new points and the
32 3x2 matrix defines simplex in
2D
(rows are coordinates
).
34 Store
3 function values.
35 Determine highest
, next highest and lowest
(best) point.
37 Fractional range from lowest value l to highest value h
:
38 rtol
=2 |h-l|
/ ( |h|
+ |l|
+ TINY
)
41 Extrapolate by factor -
1 through the face oposite of the high point.
42 - If result better than best point extrapolate by two in same direction.
43 - Else if worse than second highest do
1d contraction of
0.5,
44 searching for lower one
46 amotry does the extrapolation
47 it gets the points psum
, the values
, a double fac
50 fac2
=fac1-fac
= 1/n - fac
/n -fac
= 1/n - fac
( 1/n -
1 )
54 try_j
= psum_j
*fac1 -p
[high]_j *fac2
56 if the new value is better than the worst (highest) replace that point
58 get_psum has 2 values for 2D and contains the sum of the coordinates
61 in cvd the reflection is around the centroid
62 alpha 1 reflected size
64 gamma .5 contraction ratio
66 epsilon sqrt(eps) tolerance
68 For simulated annealing add a positive logarithmic distributed random
69 variable proportional to the temperature: (* temp (log (random))) to
70 every stored function value of the vertices. A similar variable is
71 subtracted from every point that is tried.
73 At finite T the simplex has approximately the shape of the region,
74 that can be reached at this temperature. It tumbles with Brownian
75 motion within that region.
77 Possible anneling temperature schedules:
78 - T=(1-eps)*T after every m moves. eps/m is determined by experiment.
79 - Budget K moves. Reduce T after m moves to T_0*(1-k/K)^alpha with k the
80 count of moves so far and a constant alpha=1,2 or 4 or something. alpha
81 depends on the statistical distribution of relative minima at
82 various depths. Larger alpha spend more iterations at low temperature.
83 - After every m moves set T to beta*(f_1-f_b) with beta\approx 1, f_1 is the
84 smallest function value currently in the simplex and f_b is the best
85 value ever encountered. But never reduce T by more than some
88 An occasional restart may help.
92 (declaim (optimize (speed 2) (safety 3) (debug 3)))
94 (defparameter *print-debug-message* nil)
96 (defmacro dformat (&rest rest)
97 `(progn ;; when simplex-anneal::*print-debug-message*
101 (dformat t "~a~%" (list 'test))
103 (declaim (ftype (function (double-float double-float)
104 (values double-float &optional))
106 (defun heat (temperature value)
107 (- value (* temperature (log (random 1d0)))))
109 (defmacro aref-heat (temperature array index)
110 "Access an array and change values with random fluctation."
111 `(heat ,temperature (aref ,array ,index)))
113 (declaim (ftype (function ((simple-array double-float *) double-float)
114 (values fixnum double-float
116 fixnum double-float &optional))
118 (defun find-extrema (values temperature)
119 "Find the best, second-worst and worst point in the array of simplex
123 (bestv (aref-heat temperature values best))
127 ;; be careful to maintain the right relationship between worst
128 ;; and second-worst when initializing: if 0 could be the worst
129 ;; one use something better, so check 1 and use this if its
131 (let ((help (aref-heat temperature values 1)))
133 (setf second-worst 1 ;; 1 is better
135 (setf second-worst 0 ;; 1 is worse
139 ;; we don't have to loop over 0, so start with 1
140 (loop for i from 1 below (length values) do
141 (let ((v (aref-heat temperature values i)))
149 (when (< second-worstv v)
150 (setf second-worstv v
152 (dformat t "~a~%" (list
154 'second-worst second-worst second-worstv
155 'worst worst worstv))
156 (values best bestv second-worst second-worstv worst worstv)))
158 (declaim (ftype (function ((array double-float *)
159 (array double-float *))
160 (values (simple-array double-float *) &optional))
163 (let* ((n (array-dimension a 0))
164 (result (make-array n :element-type 'double-float)))
166 (setf (aref result i) (+ (aref a i) (aref b i))))
170 (let* ((n (array-dimension a 0))
171 (result (make-array n :element-type 'double-float)))
173 (setf (aref result i) (- (aref a i) (aref b i))))
176 (defun v-inc (vec-result vec)
177 "Increase the values in VEC-RESULT with values from VEC. Return the
178 modified VEC-RESULT."
179 (let* ((n (array-dimension vec-result 0)))
181 (incf (aref vec-result i) (aref vec i)))
184 (declaim (ftype (function ((array double-float *)
185 (array double-float *))
186 (values (array double-float *) &optional))
188 (defun v-set (vec-result vec)
189 "Set the values in the vector VEC-RESULT to the contents of VEC."
190 (let* ((n (array-dimension vec-result 0)))
192 (setf (aref vec-result i) (aref vec i)))
195 (declaim (ftype (function ((array double-float *)
197 (values (simple-array double-float *) &optional))
199 (defun v* (vec scalar)
200 (let* ((n (array-dimension vec 0))
201 (result (make-array n :element-type 'double-float)))
203 (setf (aref result i) (* scalar (aref vec i))))
206 (declaim (ftype (function ((simple-array double-float (* *))
208 (values (array double-float (*)) &optional))
210 (defun displace (simplex i)
211 "Extract the i-th i=0..n point from the simplex."
212 (let ((n (array-dimension simplex 1))) ;; we want the small index
214 :element-type 'double-float
215 :displaced-to simplex
216 :displaced-index-offset (* i n))))
218 (declaim (ftype (function ((array double-float (*))
219 (array double-float (*))
221 (values (array double-float (*)) &optional))
223 (defun v-extrapolate (vec-a vec-b alpha)
224 "Extrapolate the two vectors like this: <1+alpha> a - alpha b."
225 (v+ (v* vec-a (1+ alpha))
226 (v* vec-b (- alpha))))
228 (declaim (ftype (function ((simple-array double-float (* *)) fixnum)
229 (values (simple-array double-float *) &optional))
231 (defun calc-centroid (simplex worst)
232 "Go through all n+1 points in the simplex except the worst and sum
233 the coordinates. Return the centroid."
234 (destructuring-bind (nr-points n)
235 (array-dimensions simplex) ;; first dimension is bigger (n+1)
236 (let ((result (make-array n :element-type 'double-float
237 :initial-element 0d0)))
238 (dotimes (i nr-points)
240 (v-inc result (displace simplex i))))
242 (setf (aref result j) (/ (aref result j) n)))
243 (dformat t "~a~%" (list 'centroid result))
247 (declaim (ftype (function ((simple-array double-float (* *))
251 (:temperature double-float))
253 (simple-array double-float *)
256 (simple-array double-float *)
258 (defun amoeba (simplex funk &key (itmax 500) (ftol 1d-5) (temperature 1000d0))
259 (destructuring-bind (nr-points n)
260 (array-dimensions simplex)
261 (let* ((alpha 1d0) ;; reflected size
262 (rho 2d0) ;; expansion ratio
263 (gamma .5d0) ;; contraction ratio
264 (sigma .5d0) ;; shrink ratio
266 (best-ever (make-array n :element-type 'double-float))
267 (best-ever-value 1d100)
268 (values (make-array nr-points :element-type 'double-float)))
269 ;; evaluate function on all vertices
270 (dotimes (i nr-points)
271 (setf (aref values i)
272 (funcall funk (displace simplex i))))
276 (multiple-value-bind (best best-value
277 second-worst second-worst-value
279 (find-extrema values temperature)
280 (declare (ignore second-worst))
281 (when (< best-value best-ever-value) ;; store the best value
282 (v-set best-ever (displace simplex best))
283 (setf best-ever-value best-value))
284 (dformat t "~a~%" (list 'best-ever best-ever best-ever-value))
285 ;; rtol=2 |h-l| / ( |h| + |l| + TINY)
286 (let ((rtol (* 2d0 (/ (abs (- best-value worst-value))
290 (when (or (< rtol ftol)
292 (return-from amoeba (values best-value
293 (displace simplex best)
298 (macrolet ((replace-worst (new-point new-value)
300 (v-set (displace simplex worst) ,new-point)
301 (setf (aref values worst) ,new-value))))
302 (let* ((centroid (calc-centroid simplex worst))
303 ;; reflect worst point around centroid
304 (reflected (v-extrapolate centroid (displace simplex worst) alpha))
305 (reflected-value (funcall funk reflected))
306 (reflected-heat (heat (- temperature) reflected-value)))
307 (dformat t "~a~%" (list 'reflected reflected reflected-value))
308 ;; if new point better than current best, expand the simplex
309 (when (< reflected-heat best-value)
310 (let* ((expanded (v+ (v* reflected rho)
311 (v* centroid (- 1 rho))))
312 (expanded-value (funcall funk expanded))
313 (expanded-heat (heat (- temperature) expanded-value)))
314 (dformat t "~a~%" (list 'expanded expanded expanded-value))
315 ;; keep whichever is best
316 (if (< expanded-heat reflected-value)
317 (replace-worst expanded expanded-value)
318 (replace-worst reflected reflected-value))
320 ;; new point lies between others, keep:
321 (when (< reflected-heat second-worst-value)
323 (replace-worst reflected reflected-value)
325 ;; new point is better than the worst one we had, contract:
326 (when (< reflected-heat worst-value)
327 (let* ((contracted (v-extrapolate centroid
328 (displace simplex worst)
330 (contracted-value (funcall funk contracted))
331 (contracted-heat (heat (- temperature) contracted-value)))
332 (dformat t "~a~%" (list 'contracted
333 contracted contracted-value))
334 ;; use it if it helped
335 (when (< contracted-heat reflected-value)
336 (replace-worst contracted contracted-value)
338 ;; otherwise if reflected is worse than everything else or
339 ;; contracted was worse than reflected shrink the whole
340 ;; simplex around the best point
341 (dotimes (i nr-points)
343 (v-set (displace simplex i)
344 (v+ (displace simplex best)
345 (v* (v- (displace simplex i)
346 (displace simplex best))
348 (setf (aref values i) (funcall funk (displace simplex i)))))
351 (declaim (ftype (function ((simple-array double-float (* *))
353 &key (:itmax fixnum) (:ftol double-float)
354 (:start-temperature double-float))
356 (simple-array double-float *) &optional))))
357 (defun anneal (simplex funk &key (itmax 500) (ftol 1d-5) (start-temperature 100d0))
361 (temp start-temperature))
362 (do ((count 0 (incf count)))
364 (multiple-value-bind (value point rtol best-ever-value best-ever)
365 (amoeba simplex funk :itmax m :ftol ftol
367 (declare (ignore best-ever-value
369 (when (or (< itmax count)
371 (return-from anneal (values value point))))
372 (setf temp (* (- 1 eps) temp)))))
374 ;; run the following code to test the downhill simplex optimizer on a
377 (time (let ((start (make-array 2 :element-type 'double-float
378 :initial-contents (list 1.5d0 1.5d0))))
379 (anneal (make-simplex start 1d0)
383 (declaim (ftype (function ((simple-array double-float *)
385 (values (simple-array double-float (* *)) &optional))
387 (defun make-simplex (start step)
388 (let* ((n (array-dimension start 0))
389 (result (make-array (list (1+ n) n) :element-type 'double-float)))
391 (v-set (displace result j) start))
392 ;; change the first n points by step
393 ;; the last point in result stays unaltered
395 (incf (aref result j j) step))
399 ;; (declaim (ftype (function (double-float)
400 ;; (values double-float &optional))
405 ;; (declaim (ftype (function ((array double-float *))
406 ;; (values double-float &optional))
408 ;; (defun rosenbrock (p)
409 ;; (let* ((x (aref p 0))
411 ;; (result (+ (sq (- 1 x))
412 ;; (* 100 (sq (- y (sq x)))))))
413 ;; (dformat t "~a~%" (list 'rosenbrock p result))
416 ;; (rosenbrock (make-array 2 :element-type 'double-float
417 ;; :initial-contents (list 1.5d0 1.5d0)))