func direcbetween(from, to *optim.Point, m optim.Mesh) []int { d := make([]int, from.Len()) step := m.Step() for i, x0 := range from.Pos { d[i] = int((to.Pos[i] - x0) / step) } return d }
func pointFromDirec(from *optim.Point, direc []int, m optim.Mesh) *optim.Point { pos := make([]float64, from.Len()) step := m.Step() for i, x0 := range from.Pos { pos[i] = x0 + float64(direc[i])*step } return &optim.Point{m.Nearest(pos), math.Inf(1)} }
// Poll polls on mesh m centered on point from. It is responsible for // selecting points and evaluating them with ev using obj. If a better // point was found, it returns success == true, the point, and number of // evaluations. If a better point was not found, it returns false, the // from point, and the number of evaluations. If err is non-nil, success // must be false and best must be from - neval may be non-zero. func (cp *Poller) Poll(obj optim.Objectiver, ev optim.Evaler, m optim.Mesh, from *optim.Point) (success bool, best *optim.Point, neval int, err error) { best = from if cp.Spanner == nil { cp.Spanner = Compass2N{} } pollpoints := []*optim.Point{} // Only poll compass directions if we haven't polled from this point // before. DONT DELETE - this can fire sometimes if the mesh isn't // allowed to contract below a certain step (i.e. integer meshes). h := from.Hash() if h != cp.prevhash || cp.prevstep != m.Step() { // TODO: write test that checks we poll compass dirs again if only mesh // step changed (and not from point) pollpoints = genPollPoints(from, cp.Spanner, m) cp.prevhash = h } else { // Use random directions instead. n := len(genPollPoints(from, cp.Spanner, m)) pollpoints = genPollPoints(from, &RandomN{N: n}, m) } cp.prevstep = m.Step() // Add successful directions from last poll. We want to add these points // in front of the other points so we can potentially stop earlier if // polling opportunistically. perms := optim.Rand.Perm(len(pollpoints)) // this is an extra safety check to make sure we don't index out of bounds // on the perms slice max := len(cp.keepdirecs) if max > len(perms) { max = len(perms) } for i, dir := range cp.keepdirecs[:max] { swapindex := perms[i] pollpoints[swapindex] = pointFromDirec(from, dir.dir, m) } // project points onto feasible region and mesh grid if m != nil { for _, p := range pollpoints { p.Pos = m.Nearest(p.Pos) } } cp.points = make([]*optim.Point, 0, len(pollpoints)) if cp.SkipEps == 0 { cp.points = pollpoints } else { for _, p := range pollpoints { // It is possible that due to the mesh gridding, the poll point is // outside of constraints or bounds and will be rounded back to the // current point. Check for this and skip the poll point if this is // the case. dist := optim.L2Dist(from, p) if dist > cp.SkipEps { cp.points = append(cp.points, p) } } } objstop := &objStopper{Objectiver: obj, Best: from.Val} results, n, err := ev.Eval(objstop, cp.points...) if err != nil && err != FoundBetterErr { return false, best, n, err } // this is separate from best to allow all points better than from to be // added to keepdirecs before we update the best point. nextbest := from // Sort results and keep the best Nkeep as poll directions. for _, p := range results { if p.Val < best.Val { cp.keepdirecs = append(cp.keepdirecs, direc{direcbetween(from, p, m), p.Val}) } if p.Val < nextbest.Val { nextbest = p } } best = nextbest nkeep := cp.Nkeep if max := len(pollpoints) / 4; max < nkeep { nkeep = max } sort.Sort(byval(cp.keepdirecs)) if len(cp.keepdirecs) > nkeep { cp.keepdirecs = cp.keepdirecs[:nkeep] } if best.Val < from.Val { return true, best, n, nil } else { return false, from, n, nil } }
// Iterate mutates m and so for each iteration, the same, mutated m should be // passed in. func (m *Method) Iterate(o optim.Objectiver, mesh optim.Mesh) (best *optim.Point, n int, err error) { if m.count == 0 { m.origstep = mesh.Step() } else if mesh.Step() < m.ResetStep { mesh.SetStep(m.origstep) } var nevalsearch, nevalpoll int var success bool defer m.updateDb(&nevalsearch, &nevalpoll, mesh.Step()) m.count++ prevstep := mesh.Step() if !m.DiscreteSearch { mesh.SetStep(0) } success, best, nevalsearch, err = m.Searcher.Search(o, mesh, m.Curr) mesh.SetStep(prevstep) n += nevalsearch if err != nil { return best, n, err } else if success { m.Curr = best return best, n, nil } // It is important to recenter mesh on new best point before polling. // This is necessary because the search may not be operating on the // current mesh grid. This doesn't need to happen if search succeeds // because search either always operates on the same grid, or always // operates in continuous space. mesh.SetOrigin(m.Curr.Pos) // TODO: test that this doesn't get set to Zero pos [0 0 0...] on first iteration. success, best, nevalpoll, err = m.Poller.Poll(o, m.ev, mesh, m.Curr) m.Poller.Spanner.Update(mesh.Step(), success) n += nevalpoll if err != nil { return m.Curr, n, err } else if success { m.Curr = best m.nsuccess++ if m.nsuccess == m.NsuccessGrow { // == allows -1 to mean never grow mesh.SetStep(mesh.Step() * 2.0) m.nsuccess = 0 // reset after resize } // Important to recenter mesh on new best point. More particularly, // the mesh may have been resized and the new best may not lie on the // previous mesh grid. mesh.SetOrigin(best.Pos) return best, n, nil } else { m.nsuccess = 0 var err error mesh.SetStep(mesh.Step() * 0.5) if mesh.Step() == 0 { err = ZeroStepErr } return m.Curr, n, err } }