mirror of
https://github.com/gonum/gonum.git
synced 2025-10-08 16:40:06 +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{
|
settings: &Settings{
|
||||||
FunctionThreshold: 0.01,
|
FunctionThreshold: 0.01,
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != FunctionThreshold {
|
if result.Status != FunctionThreshold {
|
||||||
@@ -63,6 +64,7 @@ func cmaTestCases() []cmaTestCase {
|
|||||||
method: &CmaEsChol{},
|
method: &CmaEsChol{},
|
||||||
settings: &Settings{
|
settings: &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != MethodConverge {
|
if result.Status != MethodConverge {
|
||||||
@@ -88,6 +90,7 @@ func cmaTestCases() []cmaTestCase {
|
|||||||
settings: &Settings{
|
settings: &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
MajorIterations: 10,
|
MajorIterations: 10,
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != IterationLimit {
|
if result.Status != IterationLimit {
|
||||||
@@ -117,6 +120,7 @@ func cmaTestCases() []cmaTestCase {
|
|||||||
settings: &Settings{
|
settings: &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
FuncEvaluations: 250, // Somewhere in the middle of an iteration.
|
FuncEvaluations: 250, // Somewhere in the middle of an iteration.
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != FunctionEvaluationLimit {
|
if result.Status != FunctionEvaluationLimit {
|
||||||
@@ -147,6 +151,7 @@ func cmaTestCases() []cmaTestCase {
|
|||||||
},
|
},
|
||||||
settings: &Settings{
|
settings: &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != MethodConverge {
|
if result.Status != MethodConverge {
|
||||||
@@ -172,6 +177,7 @@ func cmaTestCases() []cmaTestCase {
|
|||||||
},
|
},
|
||||||
settings: &Settings{
|
settings: &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
|
Converger: NeverTerminate{},
|
||||||
},
|
},
|
||||||
good: func(result *Result, err error, concurrent int) error {
|
good: func(result *Result, err error, concurrent int) error {
|
||||||
if result.Status != MethodConverge {
|
if result.Status != MethodConverge {
|
||||||
|
@@ -4,10 +4,38 @@
|
|||||||
|
|
||||||
package optimize
|
package optimize
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
// FunctionConverge tests for the convergence of function values. See comment
|
// Converger returns the convergence of the optimization based on
|
||||||
// in Settings.
|
// 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 {
|
type FunctionConverge struct {
|
||||||
Absolute float64
|
Absolute float64
|
||||||
Relative float64
|
Relative float64
|
||||||
@@ -18,13 +46,14 @@ type FunctionConverge struct {
|
|||||||
iter int
|
iter int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FunctionConverge) Init() {
|
func (fc *FunctionConverge) Init(dim int) {
|
||||||
fc.first = true
|
fc.first = true
|
||||||
fc.best = 0
|
fc.best = 0
|
||||||
fc.iter = 0
|
fc.iter = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fc *FunctionConverge) FunctionConverged(f float64) Status {
|
func (fc *FunctionConverge) Converged(l *Location) Status {
|
||||||
|
f := l.F
|
||||||
if fc.first {
|
if fc.first {
|
||||||
fc.best = f
|
fc.best = f
|
||||||
fc.first = false
|
fc.first = false
|
||||||
|
@@ -12,7 +12,7 @@ import (
|
|||||||
func DefaultSettingsGlobal() *Settings {
|
func DefaultSettingsGlobal() *Settings {
|
||||||
return &Settings{
|
return &Settings{
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
FunctionConverge: &FunctionConverge{
|
Converger: &FunctionConverge{
|
||||||
Absolute: 1e-10,
|
Absolute: 1e-10,
|
||||||
Iterations: 100,
|
Iterations: 100,
|
||||||
},
|
},
|
||||||
|
@@ -48,7 +48,9 @@ func TestListSearch(t *testing.T) {
|
|||||||
method := &ListSearch{
|
method := &ListSearch{
|
||||||
Locs: locs,
|
Locs: locs,
|
||||||
}
|
}
|
||||||
settings := &Settings{}
|
settings := &Settings{
|
||||||
|
Converger: NeverTerminate{},
|
||||||
|
}
|
||||||
initX := make([]float64, c)
|
initX := make([]float64, c)
|
||||||
result, err := Minimize(p, initX, settings, method)
|
result, err := Minimize(p, initX, settings, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -22,7 +22,7 @@ func ExampleMinimize() {
|
|||||||
settings := optimize.DefaultSettingsLocal()
|
settings := optimize.DefaultSettingsLocal()
|
||||||
settings.Recorder = nil
|
settings.Recorder = nil
|
||||||
settings.GradientThreshold = 1e-12
|
settings.GradientThreshold = 1e-12
|
||||||
settings.FunctionConverge = nil
|
settings.Converger = optimize.NeverTerminate{}
|
||||||
|
|
||||||
result, err := optimize.Minimize(p, x, settings, &optimize.BFGS{})
|
result, err := optimize.Minimize(p, x, settings, &optimize.BFGS{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -143,12 +143,17 @@ func Minimize(p Problem, initX []float64, settings *Settings, method Method) (*R
|
|||||||
optLoc := newLocation(dim, method)
|
optLoc := newLocation(dim, method)
|
||||||
optLoc.F = math.Inf(1)
|
optLoc.F = math.Inf(1)
|
||||||
|
|
||||||
if settings.FunctionConverge != nil {
|
|
||||||
settings.FunctionConverge.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
initOp, initLoc := getInitLocation(dim, initX, settings.InitValues, method)
|
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)
|
stats.Runtime = time.Since(startTime)
|
||||||
|
|
||||||
// Send initial location to Recorder
|
// Send initial location to Recorder
|
||||||
@@ -161,7 +166,7 @@ func Minimize(p Problem, initX []float64, settings *Settings, method Method) (*R
|
|||||||
|
|
||||||
// Run optimization
|
// Run optimization
|
||||||
var status Status
|
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
|
// Cleanup and collect results
|
||||||
if settings.Recorder != nil && err == nil {
|
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,
|
// minimize performs an optimization. minimize updates the settings and optLoc,
|
||||||
// and returns the final Status and error.
|
// 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)
|
dim := len(optLoc.X)
|
||||||
nTasks := settings.Concurrent
|
nTasks := settings.Concurrent
|
||||||
if nTasks == 0 {
|
if nTasks == 0 {
|
||||||
@@ -317,7 +322,7 @@ func minimize(prob *Problem, method Method, settings *Settings, stats *Stats, in
|
|||||||
case NoOperation:
|
case NoOperation:
|
||||||
// Just send the task back.
|
// Just send the task back.
|
||||||
case MajorIteration:
|
case MajorIteration:
|
||||||
status = performMajorIteration(optLoc, task.Location, stats, startTime, settings)
|
status = performMajorIteration(optLoc, task.Location, stats, converger, startTime, settings)
|
||||||
case MethodDone:
|
case MethodDone:
|
||||||
methodDone = true
|
methodDone = true
|
||||||
status = MethodConverge
|
status = MethodConverge
|
||||||
@@ -504,7 +509,7 @@ func updateEvaluationStats(stats *Stats, op Operation) {
|
|||||||
// the convergence criteria given by settings. Otherwise a corresponding status is
|
// the convergence criteria given by settings. Otherwise a corresponding status is
|
||||||
// returned.
|
// returned.
|
||||||
// Unlike checkLimits, checkConvergence is called only at MajorIterations.
|
// 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) {
|
if math.IsInf(loc.F, -1) {
|
||||||
return FunctionNegativeInfinity
|
return FunctionNegativeInfinity
|
||||||
}
|
}
|
||||||
@@ -517,10 +522,7 @@ func checkLocationConvergence(loc *Location, settings *Settings) Status {
|
|||||||
if loc.F < settings.FunctionThreshold {
|
if loc.F < settings.FunctionThreshold {
|
||||||
return FunctionThreshold
|
return FunctionThreshold
|
||||||
}
|
}
|
||||||
if settings.FunctionConverge != nil {
|
return converger.Converged(loc)
|
||||||
return settings.FunctionConverge.FunctionConverged(loc.F)
|
|
||||||
}
|
|
||||||
return NotTerminated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkEvaluationLimits checks the optimization limits after an evaluation
|
// 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.
|
// performMajorIteration does all of the steps needed to perform a MajorIteration.
|
||||||
// It increments the iteration count, updates the optimal location, and checks
|
// It increments the iteration count, updates the optimal location, and checks
|
||||||
// the necessary convergence criteria.
|
// 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)
|
copyLocation(optLoc, loc)
|
||||||
stats.MajorIterations++
|
stats.MajorIterations++
|
||||||
stats.Runtime = time.Since(startTime)
|
stats.Runtime = time.Since(startTime)
|
||||||
status := checkLocationConvergence(optLoc, settings)
|
status := checkLocationConvergence(optLoc, settings, converger)
|
||||||
if status != NotTerminated {
|
if status != NotTerminated {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
@@ -185,18 +185,18 @@ type Settings struct {
|
|||||||
// The default value is 1e-6.
|
// The default value is 1e-6.
|
||||||
GradientThreshold float64
|
GradientThreshold float64
|
||||||
|
|
||||||
// FunctionConverge tests that the function value decreases by a
|
// Converger checks if the optimization has converged based on the (history
|
||||||
// significant amount over the specified number of iterations.
|
// of) locations found during the optimization. Minimize will pass the
|
||||||
|
// Location at every MajorIteration to the Converger.
|
||||||
//
|
//
|
||||||
// If f < f_best and
|
// If the Converger is nil, a default value of
|
||||||
// f_best - f > FunctionConverge.Relative * maxabs(f, f_best) + FunctionConverge.Absolute
|
// FunctionConverge {
|
||||||
// then a significant decrease has occurred, and f_best is updated.
|
// Absolute: 1e-10,
|
||||||
//
|
// Iterations: 100,
|
||||||
// If there is no significant decrease for FunctionConverge.Iterations
|
// }
|
||||||
// major iterations, FunctionConvergence status is returned.
|
// will be used. NeverTerminated can be used to always return a
|
||||||
//
|
// NotTerminated status.
|
||||||
// If this is nil or if FunctionConverge.Iterations == 0, it has no effect.
|
Converger Converger
|
||||||
FunctionConverge *FunctionConverge
|
|
||||||
|
|
||||||
// MajorIterations is the maximum number of iterations allowed.
|
// MajorIterations is the maximum number of iterations allowed.
|
||||||
// IterationLimit status is returned if the number of major iterations
|
// IterationLimit status is returned if the number of major iterations
|
||||||
@@ -245,7 +245,7 @@ func DefaultSettingsLocal() *Settings {
|
|||||||
return &Settings{
|
return &Settings{
|
||||||
GradientThreshold: defaultGradientAbsTol,
|
GradientThreshold: defaultGradientAbsTol,
|
||||||
FunctionThreshold: math.Inf(-1),
|
FunctionThreshold: math.Inf(-1),
|
||||||
FunctionConverge: &FunctionConverge{
|
Converger: &FunctionConverge{
|
||||||
Absolute: 1e-10,
|
Absolute: 1e-10,
|
||||||
Iterations: 20,
|
Iterations: 20,
|
||||||
},
|
},
|
||||||
|
@@ -1164,16 +1164,18 @@ func testLocal(t *testing.T, tests []unconstrainedTest, method Method) {
|
|||||||
settings.Recorder = nil
|
settings.Recorder = nil
|
||||||
if method != nil && method.Needs().Gradient {
|
if method != nil && method.Needs().Gradient {
|
||||||
// Turn off function convergence checks for gradient-based methods.
|
// Turn off function convergence checks for gradient-based methods.
|
||||||
settings.FunctionConverge = nil
|
settings.Converger = NeverTerminate{}
|
||||||
} else {
|
} else {
|
||||||
if test.fIter == 0 {
|
if test.fIter == 0 {
|
||||||
test.fIter = 20
|
test.fIter = 20
|
||||||
}
|
}
|
||||||
settings.FunctionConverge.Iterations = test.fIter
|
c := settings.Converger.(*FunctionConverge)
|
||||||
|
c.Iterations = test.fIter
|
||||||
if test.fAbsTol == 0 {
|
if test.fAbsTol == 0 {
|
||||||
test.fAbsTol = 1e-12
|
test.fAbsTol = 1e-12
|
||||||
}
|
}
|
||||||
settings.FunctionConverge.Absolute = test.fAbsTol
|
c.Absolute = test.fAbsTol
|
||||||
|
settings.Converger = c
|
||||||
}
|
}
|
||||||
if test.gradTol == 0 {
|
if test.gradTol == 0 {
|
||||||
test.gradTol = 1e-12
|
test.gradTol = 1e-12
|
||||||
|
Reference in New Issue
Block a user