| // Copyright ©2015 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package optimize |
| |
| import ( |
| "math" |
| "sort" |
| |
| "gonum.org/v1/gonum/floats" |
| ) |
| |
| // nmIterType is a Nelder-Mead evaluation kind |
| type nmIterType int |
| |
| const ( |
| nmReflected = iota |
| nmExpanded |
| nmContractedInside |
| nmContractedOutside |
| nmInitialize |
| nmShrink |
| nmMajor |
| ) |
| |
| type nmVertexSorter struct { |
| vertices [][]float64 |
| values []float64 |
| } |
| |
| func (n nmVertexSorter) Len() int { |
| return len(n.values) |
| } |
| |
| func (n nmVertexSorter) Less(i, j int) bool { |
| return n.values[i] < n.values[j] |
| } |
| |
| func (n nmVertexSorter) Swap(i, j int) { |
| n.values[i], n.values[j] = n.values[j], n.values[i] |
| n.vertices[i], n.vertices[j] = n.vertices[j], n.vertices[i] |
| } |
| |
| var _ Method = (*NelderMead)(nil) |
| |
| // NelderMead is an implementation of the Nelder-Mead simplex algorithm for |
| // gradient-free nonlinear optimization (not to be confused with Danzig's |
| // simplex algorithm for linear programming). The implementation follows the |
| // algorithm described in |
| // |
| // http://epubs.siam.org/doi/pdf/10.1137/S1052623496303470 |
| // |
| // If an initial simplex is provided, it is used and initLoc is ignored. If |
| // InitialVertices and InitialValues are both nil, an initial simplex will be |
| // generated automatically using the initial location as one vertex, and each |
| // additional vertex as SimplexSize away in one dimension. |
| // |
| // If the simplex update parameters (Reflection, etc.) |
| // are zero, they will be set automatically based on the dimension according to |
| // the recommendations in |
| // |
| // http://www.webpages.uidaho.edu/~fuchang/res/ANMS.pdf |
| type NelderMead struct { |
| InitialVertices [][]float64 |
| InitialValues []float64 |
| Reflection float64 // Reflection parameter (>0) |
| Expansion float64 // Expansion parameter (>1) |
| Contraction float64 // Contraction parameter (>0, <1) |
| Shrink float64 // Shrink parameter (>0, <1) |
| SimplexSize float64 // size of auto-constructed initial simplex |
| |
| status Status |
| err error |
| |
| reflection float64 |
| expansion float64 |
| contraction float64 |
| shrink float64 |
| |
| vertices [][]float64 // location of the vertices sorted in ascending f |
| values []float64 // function values at the vertices sorted in ascending f |
| centroid []float64 // centroid of all but the worst vertex |
| |
| fillIdx int // index for filling the simplex during initialization and shrinking |
| lastIter nmIterType // Last iteration |
| reflectedPoint []float64 // Storage of the reflected point location |
| reflectedValue float64 // Value at the last reflection point |
| } |
| |
| func (n *NelderMead) Status() (Status, error) { |
| return n.status, n.err |
| } |
| |
| func (*NelderMead) Uses(has Available) (uses Available, err error) { |
| return has.function() |
| } |
| |
| func (n *NelderMead) Init(dim, tasks int) int { |
| n.status = NotTerminated |
| n.err = nil |
| return 1 |
| } |
| |
| func (n *NelderMead) Run(operation chan<- Task, result <-chan Task, tasks []Task) { |
| n.status, n.err = localOptimizer{}.run(n, math.NaN(), operation, result, tasks) |
| close(operation) |
| } |
| |
| func (n *NelderMead) initLocal(loc *Location) (Operation, error) { |
| dim := len(loc.X) |
| if cap(n.vertices) < dim+1 { |
| n.vertices = make([][]float64, dim+1) |
| } |
| n.vertices = n.vertices[:dim+1] |
| for i := range n.vertices { |
| n.vertices[i] = resize(n.vertices[i], dim) |
| } |
| n.values = resize(n.values, dim+1) |
| n.centroid = resize(n.centroid, dim) |
| n.reflectedPoint = resize(n.reflectedPoint, dim) |
| |
| if n.SimplexSize == 0 { |
| n.SimplexSize = 0.05 |
| } |
| |
| // Default parameter choices are chosen in a dimension-dependent way |
| // from http://www.webpages.uidaho.edu/~fuchang/res/ANMS.pdf |
| n.reflection = n.Reflection |
| if n.reflection == 0 { |
| n.reflection = 1 |
| } |
| n.expansion = n.Expansion |
| if n.expansion == 0 { |
| n.expansion = 1 + 2/float64(dim) |
| if dim == 1 { |
| n.expansion = 2 |
| } |
| } |
| n.contraction = n.Contraction |
| if n.contraction == 0 { |
| n.contraction = 0.75 - 1/(2*float64(dim)) |
| if dim == 1 { |
| n.contraction = 0.5 |
| } |
| } |
| n.shrink = n.Shrink |
| if n.shrink == 0 { |
| n.shrink = 1 - 1/float64(dim) |
| if dim == 1 { |
| n.shrink = 0.5 |
| } |
| } |
| |
| if n.InitialVertices != nil { |
| // Initial simplex provided. Copy the locations and values, and sort them. |
| if len(n.InitialVertices) != dim+1 { |
| panic("neldermead: incorrect number of vertices in initial simplex") |
| } |
| if len(n.InitialValues) != dim+1 { |
| panic("neldermead: incorrect number of values in initial simplex") |
| } |
| for i := range n.InitialVertices { |
| if len(n.InitialVertices[i]) != dim { |
| panic("neldermead: vertex size mismatch") |
| } |
| copy(n.vertices[i], n.InitialVertices[i]) |
| } |
| copy(n.values, n.InitialValues) |
| sort.Sort(nmVertexSorter{n.vertices, n.values}) |
| computeCentroid(n.vertices, n.centroid) |
| return n.returnNext(nmMajor, loc) |
| } |
| |
| // No simplex provided. Begin initializing initial simplex. First simplex |
| // entry is the initial location, then step 1 in every direction. |
| copy(n.vertices[dim], loc.X) |
| n.values[dim] = loc.F |
| n.fillIdx = 0 |
| loc.X[n.fillIdx] += n.SimplexSize |
| n.lastIter = nmInitialize |
| return FuncEvaluation, nil |
| } |
| |
| // computeCentroid computes the centroid of all the simplex vertices except the |
| // final one |
| func computeCentroid(vertices [][]float64, centroid []float64) { |
| dim := len(centroid) |
| for i := range centroid { |
| centroid[i] = 0 |
| } |
| for i := 0; i < dim; i++ { |
| vertex := vertices[i] |
| for j, v := range vertex { |
| centroid[j] += v |
| } |
| } |
| for i := range centroid { |
| centroid[i] /= float64(dim) |
| } |
| } |
| |
| func (n *NelderMead) iterateLocal(loc *Location) (Operation, error) { |
| dim := len(loc.X) |
| switch n.lastIter { |
| case nmInitialize: |
| n.values[n.fillIdx] = loc.F |
| copy(n.vertices[n.fillIdx], loc.X) |
| n.fillIdx++ |
| if n.fillIdx == dim { |
| // Successfully finished building initial simplex. |
| sort.Sort(nmVertexSorter{n.vertices, n.values}) |
| computeCentroid(n.vertices, n.centroid) |
| return n.returnNext(nmMajor, loc) |
| } |
| copy(loc.X, n.vertices[dim]) |
| loc.X[n.fillIdx] += n.SimplexSize |
| return FuncEvaluation, nil |
| case nmMajor: |
| // Nelder Mead iterations start with Reflection step |
| return n.returnNext(nmReflected, loc) |
| case nmReflected: |
| n.reflectedValue = loc.F |
| switch { |
| case loc.F >= n.values[0] && loc.F < n.values[dim-1]: |
| n.replaceWorst(loc.X, loc.F) |
| return n.returnNext(nmMajor, loc) |
| case loc.F < n.values[0]: |
| return n.returnNext(nmExpanded, loc) |
| default: |
| if loc.F < n.values[dim] { |
| return n.returnNext(nmContractedOutside, loc) |
| } |
| return n.returnNext(nmContractedInside, loc) |
| } |
| case nmExpanded: |
| if loc.F < n.reflectedValue { |
| n.replaceWorst(loc.X, loc.F) |
| } else { |
| n.replaceWorst(n.reflectedPoint, n.reflectedValue) |
| } |
| return n.returnNext(nmMajor, loc) |
| case nmContractedOutside: |
| if loc.F <= n.reflectedValue { |
| n.replaceWorst(loc.X, loc.F) |
| return n.returnNext(nmMajor, loc) |
| } |
| n.fillIdx = 1 |
| return n.returnNext(nmShrink, loc) |
| case nmContractedInside: |
| if loc.F < n.values[dim] { |
| n.replaceWorst(loc.X, loc.F) |
| return n.returnNext(nmMajor, loc) |
| } |
| n.fillIdx = 1 |
| return n.returnNext(nmShrink, loc) |
| case nmShrink: |
| copy(n.vertices[n.fillIdx], loc.X) |
| n.values[n.fillIdx] = loc.F |
| n.fillIdx++ |
| if n.fillIdx != dim+1 { |
| return n.returnNext(nmShrink, loc) |
| } |
| sort.Sort(nmVertexSorter{n.vertices, n.values}) |
| computeCentroid(n.vertices, n.centroid) |
| return n.returnNext(nmMajor, loc) |
| default: |
| panic("unreachable") |
| } |
| } |
| |
| // returnNext updates the location based on the iteration type and the current |
| // simplex, and returns the next operation. |
| func (n *NelderMead) returnNext(iter nmIterType, loc *Location) (Operation, error) { |
| n.lastIter = iter |
| switch iter { |
| case nmMajor: |
| // Fill loc with the current best point and value, |
| // and command a convergence check. |
| copy(loc.X, n.vertices[0]) |
| loc.F = n.values[0] |
| return MajorIteration, nil |
| case nmReflected, nmExpanded, nmContractedOutside, nmContractedInside: |
| // x_new = x_centroid + scale * (x_centroid - x_worst) |
| var scale float64 |
| switch iter { |
| case nmReflected: |
| scale = n.reflection |
| case nmExpanded: |
| scale = n.reflection * n.expansion |
| case nmContractedOutside: |
| scale = n.reflection * n.contraction |
| case nmContractedInside: |
| scale = -n.contraction |
| } |
| dim := len(loc.X) |
| floats.SubTo(loc.X, n.centroid, n.vertices[dim]) |
| floats.Scale(scale, loc.X) |
| floats.Add(loc.X, n.centroid) |
| if iter == nmReflected { |
| copy(n.reflectedPoint, loc.X) |
| } |
| return FuncEvaluation, nil |
| case nmShrink: |
| // x_shrink = x_best + delta * (x_i + x_best) |
| floats.SubTo(loc.X, n.vertices[n.fillIdx], n.vertices[0]) |
| floats.Scale(n.shrink, loc.X) |
| floats.Add(loc.X, n.vertices[0]) |
| return FuncEvaluation, nil |
| default: |
| panic("unreachable") |
| } |
| } |
| |
| // replaceWorst removes the worst location in the simplex and adds the new |
| // {x, f} pair maintaining sorting. |
| func (n *NelderMead) replaceWorst(x []float64, f float64) { |
| dim := len(x) |
| if f >= n.values[dim] { |
| panic("increase in simplex value") |
| } |
| copy(n.vertices[dim], x) |
| n.values[dim] = f |
| |
| // Sort the newly-added value. |
| for i := dim - 1; i >= 0; i-- { |
| if n.values[i] < f { |
| break |
| } |
| n.vertices[i], n.vertices[i+1] = n.vertices[i+1], n.vertices[i] |
| n.values[i], n.values[i+1] = n.values[i+1], n.values[i] |
| } |
| |
| // Update the location of the centroid. Only one point has been replaced, so |
| // subtract the worst point and add the new one. |
| floats.AddScaled(n.centroid, -1/float64(dim), n.vertices[dim]) |
| floats.AddScaled(n.centroid, 1/float64(dim), x) |
| } |
| |
| func (*NelderMead) needs() struct { |
| Gradient bool |
| Hessian bool |
| } { |
| return struct { |
| Gradient bool |
| Hessian bool |
| }{false, false} |
| } |