mirror of
https://github.com/gonum/gonum.git
synced 2025-10-11 10:01:17 +08:00
optimize: make function converger an interface (#728)
* optimize: make function converger an interface Fixes #488. Updates #677.
This commit is contained in:
@@ -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 {
|
||||
|
@@ -4,10 +4,38 @@
|
||||
|
||||
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
|
||||
|
@@ -12,7 +12,7 @@ import (
|
||||
func DefaultSettingsGlobal() *Settings {
|
||||
return &Settings{
|
||||
FunctionThreshold: math.Inf(-1),
|
||||
FunctionConverge: &FunctionConverge{
|
||||
Converger: &FunctionConverge{
|
||||
Absolute: 1e-10,
|
||||
Iterations: 100,
|
||||
},
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
},
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user