mirror of
https://github.com/gonum/gonum.git
synced 2025-09-28 03:52:16 +08:00
203 lines
5.3 KiB
Go
203 lines
5.3 KiB
Go
// Copyright ©2016 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 (
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"gonum.org/v1/gonum/floats"
|
|
"gonum.org/v1/gonum/matrix/mat64"
|
|
)
|
|
|
|
// newLocation allocates a new locatian structure of the appropriate size. It
|
|
// allocates memory based on the dimension and the values in Needs. The initial
|
|
// function value is set to math.Inf(1).
|
|
func newLocation(dim int, method Needser) *Location {
|
|
// TODO(btracey): combine this with Local.
|
|
loc := &Location{
|
|
X: make([]float64, dim),
|
|
}
|
|
loc.F = math.Inf(1)
|
|
if method.Needs().Gradient {
|
|
loc.Gradient = make([]float64, dim)
|
|
}
|
|
if method.Needs().Hessian {
|
|
loc.Hessian = mat64.NewSymDense(dim, nil)
|
|
}
|
|
return loc
|
|
}
|
|
|
|
func copyLocation(dst, src *Location) {
|
|
dst.X = resize(dst.X, len(src.X))
|
|
copy(dst.X, src.X)
|
|
|
|
dst.F = src.F
|
|
|
|
dst.Gradient = resize(dst.Gradient, len(src.Gradient))
|
|
copy(dst.Gradient, src.Gradient)
|
|
|
|
if src.Hessian != nil {
|
|
if dst.Hessian == nil || dst.Hessian.Symmetric() != len(src.X) {
|
|
dst.Hessian = mat64.NewSymDense(len(src.X), nil)
|
|
}
|
|
dst.Hessian.CopySym(src.Hessian)
|
|
}
|
|
}
|
|
|
|
func checkOptimization(p Problem, dim int, method Needser, recorder Recorder) error {
|
|
if p.Func == nil {
|
|
panic("optimize: objective function is undefined")
|
|
}
|
|
if dim <= 0 {
|
|
panic("optimize: impossible problem dimension")
|
|
}
|
|
if err := p.satisfies(method); err != nil {
|
|
return err
|
|
}
|
|
if p.Status != nil {
|
|
_, err := p.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if recorder != nil {
|
|
err := recorder.Init()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// evaluate evaluates the routines specified by the Operation at loc.X, and stores
|
|
// the answer into loc. loc.X is copied into x before
|
|
// evaluating in order to prevent the routines from modifying it.
|
|
func evaluate(p *Problem, loc *Location, op Operation, x []float64) (Status, error) {
|
|
if !op.isEvaluation() {
|
|
panic(fmt.Sprintf("optimize: invalid evaluation %v", op))
|
|
}
|
|
if p.Status != nil {
|
|
status, err := p.Status()
|
|
if err != nil || status != NotTerminated {
|
|
return status, err
|
|
}
|
|
}
|
|
copy(x, loc.X)
|
|
if op&FuncEvaluation != 0 {
|
|
loc.F = p.Func(x)
|
|
}
|
|
if op&GradEvaluation != 0 {
|
|
p.Grad(loc.Gradient, x)
|
|
}
|
|
if op&HessEvaluation != 0 {
|
|
p.Hess(loc.Hessian, x)
|
|
}
|
|
return NotTerminated, nil
|
|
}
|
|
|
|
// checkConvergence returns NotTerminated if the Location does not satisfy the
|
|
// convergence criteria given by settings. Otherwise a corresponding status is
|
|
// returned.
|
|
// Unlike checkLimits, checkConvergence is called only at MajorIterations.
|
|
//
|
|
// If local is true, gradient convergence is also checked.
|
|
func checkConvergence(loc *Location, settings *Settings, local bool) Status {
|
|
if local && loc.Gradient != nil {
|
|
norm := floats.Norm(loc.Gradient, math.Inf(1))
|
|
if norm < settings.GradientThreshold {
|
|
return GradientThreshold
|
|
}
|
|
}
|
|
if loc.F < settings.FunctionThreshold {
|
|
return FunctionThreshold
|
|
}
|
|
if settings.FunctionConverge != nil {
|
|
return settings.FunctionConverge.FunctionConverged(loc.F)
|
|
}
|
|
return NotTerminated
|
|
}
|
|
|
|
// updateStats updates the statistics based on the operation.
|
|
func updateStats(stats *Stats, op Operation) {
|
|
if op&FuncEvaluation != 0 {
|
|
stats.FuncEvaluations++
|
|
}
|
|
if op&GradEvaluation != 0 {
|
|
stats.GradEvaluations++
|
|
}
|
|
if op&HessEvaluation != 0 {
|
|
stats.HessEvaluations++
|
|
}
|
|
}
|
|
|
|
// checkLimits returns NotTerminated status if the various limits given by
|
|
// settings have not been reached. Otherwise it returns a corresponding status.
|
|
// Unlike checkConvergence, checkLimits is called by Local and Global at _every_
|
|
// iteration.
|
|
func checkLimits(loc *Location, stats *Stats, settings *Settings) Status {
|
|
// Check the objective function value for negative infinity because it
|
|
// could break the linesearches and -inf is the best we can do anyway.
|
|
if math.IsInf(loc.F, -1) {
|
|
return FunctionNegativeInfinity
|
|
}
|
|
|
|
if settings.MajorIterations > 0 && stats.MajorIterations >= settings.MajorIterations {
|
|
return IterationLimit
|
|
}
|
|
|
|
if settings.FuncEvaluations > 0 && stats.FuncEvaluations >= settings.FuncEvaluations {
|
|
return FunctionEvaluationLimit
|
|
}
|
|
|
|
if settings.GradEvaluations > 0 && stats.GradEvaluations >= settings.GradEvaluations {
|
|
return GradientEvaluationLimit
|
|
}
|
|
|
|
if settings.HessEvaluations > 0 && stats.HessEvaluations >= settings.HessEvaluations {
|
|
return HessianEvaluationLimit
|
|
}
|
|
|
|
// TODO(vladimir-ch): It would be nice to update Runtime here.
|
|
if settings.Runtime > 0 && stats.Runtime >= settings.Runtime {
|
|
return RuntimeLimit
|
|
}
|
|
|
|
return NotTerminated
|
|
}
|
|
|
|
// TODO(btracey): better name
|
|
func iterCleanup(status Status, err error, stats *Stats, settings *Settings, statuser Statuser, startTime time.Time, loc *Location, op Operation) (Status, error) {
|
|
if status != NotTerminated || err != nil {
|
|
return status, err
|
|
}
|
|
|
|
if settings.Recorder != nil {
|
|
stats.Runtime = time.Since(startTime)
|
|
err = settings.Recorder.Record(loc, op, stats)
|
|
if err != nil {
|
|
if status == NotTerminated {
|
|
status = Failure
|
|
}
|
|
return status, err
|
|
}
|
|
}
|
|
|
|
stats.Runtime = time.Since(startTime)
|
|
status = checkLimits(loc, stats, settings)
|
|
if status != NotTerminated {
|
|
return status, nil
|
|
}
|
|
|
|
if statuser != nil {
|
|
status, err = statuser.Status()
|
|
if err != nil || status != NotTerminated {
|
|
return status, err
|
|
}
|
|
}
|
|
return status, nil
|
|
}
|