~ubuntu-branches/ubuntu/karmic/python-scipy/karmic

« back to all changes in this revision

Viewing changes to Lib/optimize/anneal.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T. Chen (new)
  • Date: 2005-03-16 02:15:29 UTC
  • Revision ID: james.westby@ubuntu.com-20050316021529-xrjlowsejs0cijig
Tags: upstream-0.3.2
ImportĀ upstreamĀ versionĀ 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Author: Travis Oliphant 2002
 
2
 
 
3
from __future__ import nested_scopes
 
4
import copy
 
5
import scipy_base
 
6
from scipy_base import asarray, tan, exp, ones, squeeze, sign, \
 
7
     all
 
8
import random
 
9
 
 
10
random.seed()
 
11
__all__ = ['anneal']
 
12
 
 
13
class base_schedule:
 
14
    def __init__(self):
 
15
        self.dwell = 20
 
16
        self.learn_rate = 0.5
 
17
        self.lower = -10
 
18
        self.upper = 10
 
19
        self.Ninit = 50
 
20
        self.accepted = 0
 
21
        self.tests = 0
 
22
        self.feval = 0
 
23
        self.k = 0
 
24
        self.T = None
 
25
 
 
26
    def init(self, **options):
 
27
        self.__dict__.update(options)
 
28
        if self.lower == scipy_base.NINF:
 
29
            self.lower = -scipy_base.limits.double_max
 
30
        if self.upper == scipy_base.PINF:
 
31
            self.upper = scipy_base.limits.double_max
 
32
        self.k = 0
 
33
        self.accepted = 0
 
34
        self.feval = 0
 
35
        self.tests = 0        
 
36
 
 
37
    def getstart_temp(self, best_state):
 
38
        assert(not self.dims is None)
 
39
        x0 = ones(self.dims,'d')
 
40
        lrange = x0*self.lower
 
41
        urange = x0*self.upper
 
42
        fmax = -300e8
 
43
        fmin = 300e8
 
44
        for n in range(self.Ninit):            
 
45
            for k in range(self.dims):
 
46
                x0[k] = random.uniform(lrange[k],urange[k])
 
47
            fval = self.func(x0,*self.args)
 
48
            self.feval += 1
 
49
            if fval > fmax:
 
50
                fmax = fval
 
51
            if fval < fmin:
 
52
                fmin = fval
 
53
                best_state.cost = fval
 
54
                best_state.x = asarray(x0).copy()
 
55
        self.T0 = (fmax-fmin)*1.5
 
56
        return best_state.x
 
57
 
 
58
    def accept_test(self, dE):
 
59
        T = self.T
 
60
        self.tests += 1
 
61
        if dE < 0:
 
62
            self.accepted += 1
 
63
            return 1
 
64
        p = exp(-dE*1.0/self.boltzmann/T)
 
65
        if (p > random.uniform(0.0,1.0)):
 
66
            self.accepted += 1
 
67
            return 1
 
68
        return 0
 
69
 
 
70
    def update_guess(self, x0):
 
71
        pass
 
72
 
 
73
    def update_temp(self, x0):
 
74
        pass
 
75
 
 
76
 
 
77
#  A schedule due to Lester Ingber
 
78
class fast_sa(base_schedule):
 
79
    def init(self, **options):
 
80
        self.__dict__.update(options)
 
81
        if self.m is None:
 
82
            self.m = 1.0
 
83
        if self.n is None:
 
84
            self.n = 1.0
 
85
        self.c = self.m * exp(-self.n * self.quench / self.dims)
 
86
        
 
87
    def update_guess(self, x0):
 
88
        x0 = asarray(x0)
 
89
        u = squeeze(asarray([random.uniform(0.0,1.0) for x in x0]))
 
90
        T = self.T
 
91
        y = sign(u-0.5)*T*((1+1.0/T)**abs(2*u-1)-1.0)
 
92
        xc = y*(self.upper - self.lower)
 
93
        xnew = x0 + xc
 
94
        return xnew
 
95
 
 
96
    def update_temp(self):
 
97
        self.T = self.T0*exp(-self.c * self.k**(self.quench/self.dims))
 
98
        self.k += 1
 
99
        return
 
100
 
 
101
class cauchy_sa(base_schedule):
 
102
    def update_guess(self, x0):
 
103
        x0 = asarray(x0)
 
104
        numbers = squeeze(asarray([random.uniform(-pi/2,pi/2) for x in x0]))
 
105
        xc = self.learn_rate * self.T * tan(numbers)
 
106
        xnew = x0 + xc
 
107
        return xnew
 
108
 
 
109
    def update_temp(self):
 
110
        self.T = self.T0/(1+self.k)
 
111
        self.k += 1
 
112
        return
 
113
 
 
114
class boltzmann_sa(base_schedule):
 
115
    def update_guess(self, x0):
 
116
        std = min(sqrt(self.T), (self.upper-self.lower)/3.0/self.learn_rate)
 
117
        x0 = asarray(x0)
 
118
        xc = squeeze(asarray([random.normalvariate(0,std*self.learn_rate) \
 
119
                      for x in x0]))
 
120
        xnew = x0 + xc
 
121
        return xnew
 
122
 
 
123
    def update_temp(self):
 
124
        self.k += 1
 
125
        self.T = self.T0 / log(self.k+1.0)
 
126
        return
 
127
 
 
128
class _state:
 
129
    def __init__(self):
 
130
        self.x = None
 
131
        self.cost = None
 
132
 
 
133
# TODO:
 
134
#     allow for general annealing temperature profile
 
135
#     in that case use update given by alpha and omega and
 
136
#     variation of all previous updates and temperature?
 
137
 
 
138
# Simulated annealing
 
139
 
 
140
def anneal(func, x0, args=(), schedule='fast', full_output=0,
 
141
           T0=None, Tf=1e-12, maxeval=None, maxaccept=None, maxiter=400,
 
142
           boltzmann=1.0, learn_rate=0.5, feps=1e-6, quench=1.0, m=1.0, n=1.0,
 
143
           lower=-100, upper=100, dwell=50):
 
144
    """Minimize a function using simulated annealing.
 
145
 
 
146
    Schedule is a schedule class implementing the annealing schedule.
 
147
    Available ones are 'fast', 'cauchy', 'boltzmann'
 
148
 
 
149
    Inputs:
 
150
 
 
151
    func         -- Function to be optimized
 
152
    x0           -- Parameters to be optimized over
 
153
    args         -- Extra parameters to function
 
154
    schedule     -- Annealing schedule to use (a class)
 
155
    full_output  -- Return optional outputs
 
156
    T0           -- Initial Temperature (estimated as 1.2 times the largest
 
157
                    cost-function deviation over random points in the range)
 
158
    Tf           -- Final goal temperature
 
159
    maxeval      -- Maximum function evaluations
 
160
    maxaccept    -- Maximum changes to accept
 
161
    maxiter      -- Maximum cooling iterations
 
162
    learn_rate   -- scale constant for adjusting guesses
 
163
    boltzmann    -- Boltzmann constant in acceptance test
 
164
                     (increase for less stringent test at each temperature).
 
165
    feps         -- Stopping relative error tolerance for the function value in
 
166
                     last four coolings.
 
167
    quench, m, n -- Parameters to alter fast_sa schedule
 
168
    lower, upper -- lower and upper bounds on x0 (scalar or array).
 
169
    dwell        -- The number of times to search the space at each temperature.
 
170
 
 
171
    Outputs: (xmin, {Jmin, T, feval, iter, accept,} retval)
 
172
 
 
173
    xmin -- Point giving smallest value found
 
174
    retval -- Flag indicating stopping condition:
 
175
                0 : Cooled to global optimum
 
176
                1 : Cooled to final temperature
 
177
                2 : Maximum function evaluations
 
178
                3 : Maximum cooling iterations reached
 
179
                4 : Maximum accepted query locations reached
 
180
 
 
181
    Jmin  -- Minimum value of function found
 
182
    T     -- final temperature
 
183
    feval -- Number of function evaluations
 
184
    iter  -- Number of cooling iterations
 
185
    accept -- Number of tests accepted.
 
186
    """
 
187
    x0 = asarray(x0)
 
188
 
 
189
    schedule = eval(schedule+'_sa()')
 
190
    #   initialize the schedule
 
191
    schedule.init(dims=len(x0),func=func,args=args,boltzmann=boltzmann,T0=T0,
 
192
                  learn_rate=learn_rate, lower=lower, upper=upper,
 
193
                  m=m, n=n, quench=quench, dwell=dwell)
 
194
 
 
195
    current_state, last_state, best_state = _state(), _state(), _state()
 
196
    feval = 0
 
197
    done = 0
 
198
    if T0 is None:
 
199
        x0 = schedule.getstart_temp(best_state)
 
200
    else:
 
201
        best_state.x = None
 
202
        best_state.cost = 300e8
 
203
    last_state.x = asarray(x0).copy()
 
204
    fval = func(x0,*args)
 
205
    schedule.feval += 1
 
206
    last_state.cost = fval
 
207
    if last_state.cost < best_state.cost:
 
208
        best_state.cost = fval
 
209
        best_state.x = asarray(x0).copy()
 
210
    schedule.T = schedule.T0
 
211
    fqueue = [100,300,500,700]
 
212
    iter=0
 
213
    while 1:
 
214
        for n in range(dwell):
 
215
            xnew = schedule.update_guess(x0)
 
216
            fval = func(xnew,*args)
 
217
            schedule.feval += 1
 
218
            current_state.x = asarray(xnew).copy()
 
219
            current_state.cost = fval
 
220
            dE = current_state.cost - last_state.cost
 
221
            if schedule.accept_test(dE):
 
222
                if dE < 0:
 
223
                    last_state.x = current_state.x.copy()
 
224
                    last_state.cost = current_state.cost
 
225
                if last_state.cost < best_state.cost:                
 
226
                    best_state.x = last_state.x.copy()
 
227
                    best_state.cost = last_state.cost
 
228
        schedule.update_temp()
 
229
        iter += 1
 
230
        # Stopping conditions
 
231
        # 0) last saved values of f from each cooling step
 
232
        #     are all very similar (effectively cooled)
 
233
        # 1) Tf is set and we are below it
 
234
        # 2) maxeval is set and we are past it
 
235
        # 3) maxiter is set and we are past it
 
236
        # 4) maxaccept is set and we are past it
 
237
 
 
238
        fqueue.append(squeeze(last_state.cost))
 
239
        tmp = fqueue.pop(0)
 
240
        af = asarray(fqueue)*1.0
 
241
        if all(abs((af-af[0])/af[0]) < feps):
 
242
            retval = 0
 
243
            if abs(af[-1]-best_state.cost) > feps*10:
 
244
                retval = 5
 
245
                print "Warning: Cooled to %f at %f but this is not" \
 
246
                      % (squeeze(last_state.cost), squeeze(last_state.x)) \
 
247
                      + " the smallest point found."
 
248
            break
 
249
        if (Tf is not None) and (schedule.T < Tf):
 
250
            retval = 1
 
251
            break
 
252
        if (maxeval is not None) and (schedule.feval > maxeval):
 
253
            retval = 2
 
254
            break
 
255
        if (iter > maxiter):
 
256
            print "Warning: Maximum number of iterations exceeded."
 
257
            retval = 3
 
258
            break
 
259
        if (maxaccept is not None) and (schedule.accepted > maxaccept):
 
260
            retval = 4
 
261
            break
 
262
 
 
263
    if full_output:        
 
264
        return best_state.x, best_state.cost, schedule.T, \
 
265
               schedule.feval, iter, schedule.accepted, retval
 
266
    else:
 
267
        return best_state.x, retval
 
268
 
 
269
 
 
270
 
 
271
if __name__ == "__main__":
 
272
    func = lambda x: cos(14.5*x-0.3) + (x+0.2)*x
 
273
    #print anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=1000,schedule=fast_sa())
 
274
    #print anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=1000,schedule=cauchy_sa())
 
275
    #print anneal(func,1.0,full_output=1,upper=3.0,lower=-3.0,feps=1e-4,maxiter=1000,schedule=boltzmann_sa())
 
276