00001 """
00002 M.G.Poolman 20/06/01 onwards
00003 module Simplex
00004 do simplex optimisations a la Nelder and Mead
00005 loosely based on the description in Numerical Recipies in C
00006 """
00007 import random
00008
00009 class Vertex:
00010 def __init__(self, Params, func, eval=1,**fargs):
00011 """ pre: func is a function that returns a numerical value
00012 Params is a list of numbers that can be used as parameters to func """
00013
00014 self.Params = Params[:]
00015 self.nParams = len(Params)
00016
00017 self.func = func
00018 if eval:
00019 self.Eval(**fargs)
00020
00021
00022 def __len__(self):
00023 return self.nParams
00024
00025 def __repr__(self):
00026 rv = ""
00027 for p in self.Params:
00028 rv = rv + str(p) + " "
00029 return rv + " " + str(self.Value)
00030
00031 def Eval(self, **fargs):
00032 self.Value = self.func(self.Params, **fargs)
00033
00034 def Copy(self):
00035 rv = Vertex(self.Params, self.func, eval=0)
00036 rv.Value = self.Value
00037 return rv
00038
00039 def __getitem__(self,n):
00040 return self.Params[n]
00041
00042 def __setitem__(self,n,value):
00043 self.Params[n] = value
00044
00045 def __cmp__(self, other):
00046
00047 diff = self.Value - other.Value
00048
00049 if diff >0:
00050 return 1
00051 if diff < 0:
00052 return -1
00053
00054 return 0
00055
00056 def Randomise(self,fac):
00057 for pidx in range(len(self.Params)):
00058 p = self.Params[pidx]
00059 lo = p*(1-fac)
00060 hi = p*(1+fac)
00061 self.Params[pidx] = random.uniform(lo, hi)
00062
00063
00064 class Simplex:
00065
00066 def __init__(self, StartParas, Func, **fargs):
00067
00068 self.Vertices = []
00069 self.fargs = fargs
00070 for n in range(len(StartParas)):
00071 self.Vertices.append(Vertex(StartParas[n], Func, **fargs))
00072
00073 self.nVertices = len(self.Vertices)
00074 self.nParams = self.Vertices[0].nParams
00075
00076 self.Vertices.sort()
00077 self.RecalcPsum()
00078 self.WorstIdx = self.nVertices-1
00079 self.Worst2Idx = self.WorstIdx-1
00080 self.BestIdx = 0
00081
00082 def __repr__(self):
00083 rv = ""
00084 for v in self.Vertices:
00085 rv = rv + str(v) + "\n"
00086
00087 return rv
00088
00089
00090 def Eval(self):
00091 for v in self.Vertices:
00092 v.Eval(**self.fargs)
00093 self.Vertices.sort()
00094
00095
00096 def StopFunc(self):
00097 """ users can overload this to provide custom stopping criteria """
00098 self.Iters = self.Iters + 1
00099 return self.Iters == 100
00100
00101
00102 def RecalcPsum(self):
00103 self.Psum = []
00104 for p in range (self.nParams):
00105 sum = 0.0
00106 for v in range(self.nVertices):
00107 sum = sum + self.Vertices[v][p]
00108 self.Psum.append(sum)
00109
00110
00111 def ReplaceWorst(self, Better):
00112
00113 for p in range(self.nParams):
00114 self.Psum[p] = self.Psum[p] - self.Vertices[self.WorstIdx][p] + Better[p]
00115
00116 del self.Vertices[self.WorstIdx]
00117
00118 try:
00119 v = 0
00120 while self.Vertices[v].Value < Better.Value:
00121 v = v + 1
00122 self.Vertices.insert(v, Better)
00123 except:
00124 self.Vertices.append(Better)
00125
00126
00127 def AwayFromWorst(self, Distance):
00128 """ reflect worst point through face of simplex by Distance and replace if better,
00129 Equivalent to the "amotry" routine in NRiC. """
00130
00131 fac1 = (1.0 - Distance)/self.nParams
00132 fac2 = fac1 - Distance
00133
00134 TryVert = self.Vertices[self.WorstIdx].Copy()
00135 WorstVal = TryVert.Value
00136
00137 for p in range(TryVert.nParams):
00138 TryVert[p]= self.Psum[p] * fac1 - self.Vertices[self.WorstIdx][p]*fac2
00139
00140
00141 TryVert.Eval(**self.fargs)
00142 if TryVert.Value < WorstVal:
00143 self.ReplaceWorst(TryVert)
00144
00145 return TryVert.Value
00146
00147
00148 def ContractToBest(self):
00149
00150 fac = 0.5
00151 BestV = self.Vertices[0]
00152 for v in self.Vertices[1:]:
00153 for p in range(self.nParams):
00154 v[p] = fac*(v[p]+BestV[p])
00155 v.Eval(**self.fargs)
00156 self.Vertices.sort()
00157 self.RecalcPsum()
00158
00159
00160 def Run(self):
00161 """ do the downhil simplex until self.StopFun() == 1 """
00162
00163 Alpha = -1
00164 Beta = 0.5
00165 Gamma = 2.0
00166 self.Iters = 0
00167
00168 while not self.StopFunc():
00169
00170 BestVal = self.Vertices[self.BestIdx].Value
00171 WorstVal = self.Vertices[self.WorstIdx].Value
00172 Worst2Val = self.Vertices[self.Worst2Idx].Value
00173
00174 TryVal = self.AwayFromWorst(Alpha)
00175 if TryVal <= BestVal:
00176 TryVal = self.AwayFromWorst(Gamma)
00177 elif TryVal >= Worst2Val:
00178 TryVal = self.AwayFromWorst(Beta)
00179 if TryVal >= WorstVal:
00180 self.ContractToBest()
00181
00182 def Randomise(self,fac):
00183 for v in self.Vertices:
00184 v.Randomise(fac)
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199