optimize: make function converger an interface
* optimize: make function converger an interface
Fixes
Updates
MODIFY
optimize/cmaes_test.go
optimize/cmaes_test.go
@@ -42,6 +42,7 @@ func cmaTestCases() []cmaTestCase {
},
settings: &Settings{
FunctionThreshold: 0.01,
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != FunctionThreshold {
@@ -63,6 +64,7 @@ func cmaTestCases() []cmaTestCase {
method: &CmaEsChol{},
settings: &Settings{
FunctionThreshold: math.Inf(-1),
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != MethodConverge {
@@ -88,6 +90,7 @@ func cmaTestCases() []cmaTestCase {
settings: &Settings{
FunctionThreshold: math.Inf(-1),
MajorIterations: 10,
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != IterationLimit {
@@ -117,6 +120,7 @@ func cmaTestCases() []cmaTestCase {
settings: &Settings{
FunctionThreshold: math.Inf(-1),
FuncEvaluations: 250, // Somewhere in the middle of an iteration.
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != FunctionEvaluationLimit {
@@ -147,6 +151,7 @@ func cmaTestCases() []cmaTestCase {
},
settings: &Settings{
FunctionThreshold: math.Inf(-1),
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != MethodConverge {
@@ -172,6 +177,7 @@ func cmaTestCases() []cmaTestCase {
},
settings: &Settings{
FunctionThreshold: math.Inf(-1),
+ Converger: NeverTerminate{},
},
good: func(result *Result, err error, concurrent int) error {
if result.Status != MethodConverge {

MODIFY
optimize/functionconvergence.go
optimize/functionconvergence.go
package optimize
-import "math"
+import (
+ "math"
+)
-// FunctionConverge tests for the convergence of function values. See comment
-// in Settings.
+// Converger returns the convergence of the optimization based on
+// locations found during optimization. Converger must not modify the value of
+// the provided Location in any of the methods.
+type Converger interface {
+ Init(dim int)
+ Converged(loc *Location) Status
+}
+
+// NeverTerminate implements Converger, always reporting NotTerminated.
+type NeverTerminate struct{}
+
+func (NeverTerminate) Init(dim int) {}
+
+func (NeverTerminate) Converged(loc *Location) Status {
+ return NotTerminated
+}
+
+// FunctionConverge tests for insufficient improvement in the optimum value
+// over the last iterations. A FunctionConvergence status is returned if
+// there is no significant decrease for FunctionConverge.Iterations. A
+// significant decrease is considered if
+// f < f_best
+// and
+// f_best - f > FunctionConverge.Relative * maxabs(f, f_best) + FunctionConverge.Absolute
+// If the decrease is significant, then the iteration counter is reset and
+// f_best is updated.
+//
+// If FunctionConverge.Iterations == 0, it has no effect.
type FunctionConverge struct {
Absolute float64
Relative float64
@@ -18,13 +46,14 @@ type FunctionConverge struct {
iter int
}
-func (fc *FunctionConverge) Init() {
+func (fc *FunctionConverge) Init(dim int) {
fc.first = true
fc.best = 0
fc.iter = 0
}
-func (fc *FunctionConverge) FunctionConverged(f float64) Status {
+func (fc *FunctionConverge) Converged(l *Location) Status {
+ f := l.F
if fc.first {
fc.best = f
fc.first = false

MODIFY
optimize/global.go
optimize/global.go
@@ -12,7 +12,7 @@ import (
func DefaultSettingsGlobal() *Settings {
return &Settings{
FunctionThreshold: math.Inf(-1),
- FunctionConverge: &FunctionConverge{
+ Converger: &FunctionConverge{
Absolute: 1e-10,
Iterations: 100,
},

MODIFY
optimize/listsearch_test.go
optimize/listsearch_test.go
@@ -48,7 +48,9 @@ func TestListSearch(t *testing.T) {
method := &ListSearch{
Locs: locs,
}
- settings := &Settings{}
+ settings := &Settings{
+ Converger: NeverTerminate{},
+ }
initX := make([]float64, c)
result, err := Minimize(p, initX, settings, method)
if err != nil {

MODIFY
optimize/local_example_test.go
optimize/local_example_test.go
@@ -22,7 +22,7 @@ func ExampleMinimize() {
settings := optimize.DefaultSettingsLocal()
settings.Recorder = nil
settings.GradientThreshold = 1e-12
- settings.FunctionConverge = nil
+ settings.Converger = optimize.NeverTerminate{}
result, err := optimize.Minimize(p, x, settings, &optimize.BFGS{})
if err != nil {

MODIFY
optimize/minimize.go
optimize/minimize.go
@@ -143,12 +143,17 @@ func Minimize(p Problem, initX []float64, settings *Settings, method Method) (*R
optLoc := newLocation(dim, method)
optLoc.F = math.Inf(1)
- if settings.FunctionConverge != nil {
- settings.FunctionConverge.Init()
- }
-
initOp, initLoc := getInitLocation(dim, initX, settings.InitValues, method)
+ converger := settings.Converger
+ if converger == nil {
+ converger = &FunctionConverge{
+ Absolute: 1e-10,
+ Iterations: 100,
+ }
+ }
+ converger.Init(dim)
+
stats.Runtime = time.Since(startTime)
// Send initial location to Recorder
@@ -161,7 +166,7 @@ func Minimize(p Problem, initX []float64, settings *Settings, method Method) (*R
// Run optimization
var status Status
- status, err = minimize(&p, method, settings, stats, initOp, initLoc, optLoc, startTime)
+ status, err = minimize(&p, method, settings, converger, stats, initOp, initLoc, optLoc, startTime)
// Cleanup and collect results
if settings.Recorder != nil && err == nil {
@@ -184,7 +189,7 @@ func getDefaultMethod(p *Problem) Method {
// minimize performs an optimization. minimize updates the settings and optLoc,
// and returns the final Status and error.
-func minimize(prob *Problem, method Method, settings *Settings, stats *Stats, initOp Operation, initLoc, optLoc *Location, startTime time.Time) (Status, error) {
+func minimize(prob *Problem, method Method, settings *Settings, converger Converger, stats *Stats, initOp Operation, initLoc, optLoc *Location, startTime time.Time) (Status, error) {
dim := len(optLoc.X)
nTasks := settings.Concurrent
if nTasks == 0 {
@@ -317,7 +322,7 @@ func minimize(prob *Problem, method Method, settings *Settings, stats *Stats, in
case NoOperation:
// Just send the task back.
case MajorIteration:
- status = performMajorIteration(optLoc, task.Location, stats, startTime, settings)
+ status = performMajorIteration(optLoc, task.Location, stats, converger, startTime, settings)
case MethodDone:
methodDone = true
status = MethodConverge
@@ -504,7 +509,7 @@ func updateEvaluationStats(stats *Stats, op Operation) {
// the convergence criteria given by settings. Otherwise a corresponding status is
// returned.
// Unlike checkLimits, checkConvergence is called only at MajorIterations.
-func checkLocationConvergence(loc *Location, settings *Settings) Status {
+func checkLocationConvergence(loc *Location, settings *Settings, converger Converger) Status {
if math.IsInf(loc.F, -1) {
return FunctionNegativeInfinity
}
@@ -517,10 +522,7 @@ func checkLocationConvergence(loc *Location, settings *Settings) Status {
if loc.F < settings.FunctionThreshold {
return FunctionThreshold
}
- if settings.FunctionConverge != nil {
- return settings.FunctionConverge.FunctionConverged(loc.F)
- }
- return NotTerminated
+ return converger.Converged(loc)
}
// checkEvaluationLimits checks the optimization limits after an evaluation
@@ -559,11 +561,11 @@ func checkIterationLimits(loc *Location, stats *Stats, settings *Settings) Statu
// performMajorIteration does all of the steps needed to perform a MajorIteration.
// It increments the iteration count, updates the optimal location, and checks
// the necessary convergence criteria.
-func performMajorIteration(optLoc, loc *Location, stats *Stats, startTime time.Time, settings *Settings) Status {
+func performMajorIteration(optLoc, loc *Location, stats *Stats, converger Converger, startTime time.Time, settings *Settings) Status {
copyLocation(optLoc, loc)
stats.MajorIterations++
stats.Runtime = time.Since(startTime)
- status := checkLocationConvergence(optLoc, settings)
+ status := checkLocationConvergence(optLoc, settings, converger)
if status != NotTerminated {
return status
}

MODIFY
optimize/types.go
optimize/types.go
@@ -185,18 +185,18 @@ type Settings struct {
// The default value is 1e-6.
GradientThreshold float64
- // FunctionConverge tests that the function value decreases by a
- // significant amount over the specified number of iterations.
+ // Converger checks if the optimization has converged based on the (history
+ // of) locations found during the optimization. Minimize will pass the
+ // Location at every MajorIteration to the Converger.
//
- // If f < f_best and
- // f_best - f > FunctionConverge.Relative * maxabs(f, f_best) + FunctionConverge.Absolute
- // then a significant decrease has occurred, and f_best is updated.
- //
- // If there is no significant decrease for FunctionConverge.Iterations
- // major iterations, FunctionConvergence status is returned.
- //
- // If this is nil or if FunctionConverge.Iterations == 0, it has no effect.
- FunctionConverge *FunctionConverge
+ // If the Converger is nil, a default value of
+ // FunctionConverge {
+ // Absolute: 1e-10,
+ // Iterations: 100,
+ // }
+ // will be used. NeverTerminated can be used to always return a
+ // NotTerminated status.
+ Converger Converger
// MajorIterations is the maximum number of iterations allowed.
// IterationLimit status is returned if the number of major iterations
@@ -245,7 +245,7 @@ func DefaultSettingsLocal() *Settings {
return &Settings{
GradientThreshold: defaultGradientAbsTol,
FunctionThreshold: math.Inf(-1),
- FunctionConverge: &FunctionConverge{
+ Converger: &FunctionConverge{
Absolute: 1e-10,
Iterations: 20,
},

MODIFY
optimize/unconstrained_test.go
optimize/unconstrained_test.go
@@ -1164,16 +1164,18 @@ func testLocal(t *testing.T, tests []unconstrainedTest, method Method) {
settings.Recorder = nil
if method != nil && method.Needs().Gradient {
// Turn off function convergence checks for gradient-based methods.
- settings.FunctionConverge = nil
+ settings.Converger = NeverTerminate{}
} else {
if test.fIter == 0 {
test.fIter = 20
}
- settings.FunctionConverge.Iterations = test.fIter
+ c := settings.Converger.(*FunctionConverge)
+ c.Iterations = test.fIter
if test.fAbsTol == 0 {
test.fAbsTol = 1e-12
}
- settings.FunctionConverge.Absolute = test.fAbsTol
+ c.Absolute = test.fAbsTol
+ settings.Converger = c
}
if test.gradTol == 0 {
test.gradTol = 1e-12

optimize: make function converger an interface (#728)

* optimize: make function converger an interface

Fixes #488.
Updates #677.