blob: 6dfe86526cf407305ed98d899dc491e89354e2b4 [file] [log] [blame] [edit]
// Copyright ©2014 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 "math"
const (
defaultBisectionCurvature = 0.9
)
// Bisection is a Linesearcher that uses a bisection to find a point that
// satisfies the strong Wolfe conditions with the given curvature factor and
// a decrease factor of zero.
type Bisection struct {
// CurvatureFactor is the constant factor in the curvature condition.
// Smaller values result in a more exact line search.
// A set value must be in the interval (0, 1), otherwise Init will panic.
// If it is zero, it will be defaulted to 0.9.
CurvatureFactor float64
minStep float64
maxStep float64
currStep float64
initF float64
minF float64
maxF float64
lastF float64
initGrad float64
lastOp Operation
}
func (b *Bisection) Init(f, g float64, step float64) Operation {
if step <= 0 {
panic("bisection: bad step size")
}
if g >= 0 {
panic("bisection: initial derivative is non-negative")
}
if b.CurvatureFactor == 0 {
b.CurvatureFactor = defaultBisectionCurvature
}
if b.CurvatureFactor <= 0 || b.CurvatureFactor >= 1 {
panic("bisection: CurvatureFactor not between 0 and 1")
}
b.minStep = 0
b.maxStep = math.Inf(1)
b.currStep = step
b.initF = f
b.minF = f
b.maxF = math.NaN()
b.initGrad = g
// Only evaluate the gradient when necessary.
b.lastOp = FuncEvaluation
return b.lastOp
}
func (b *Bisection) Iterate(f, g float64) (Operation, float64, error) {
if b.lastOp != FuncEvaluation && b.lastOp != GradEvaluation {
panic("bisection: Init has not been called")
}
minF := b.initF
if b.maxF < minF {
minF = b.maxF
}
if b.minF < minF {
minF = b.minF
}
if b.lastOp == FuncEvaluation {
// See if the function value is good enough to make progress. If it is,
// evaluate the gradient. If not, set it to the upper bound if the bound
// has not yet been found, otherwise iterate toward the minimum location.
if f <= minF {
b.lastF = f
b.lastOp = GradEvaluation
return b.lastOp, b.currStep, nil
}
if math.IsInf(b.maxStep, 1) {
b.maxStep = b.currStep
b.maxF = f
return b.nextStep((b.minStep + b.maxStep) / 2)
}
if b.minF <= b.maxF {
b.maxStep = b.currStep
b.maxF = f
} else {
b.minStep = b.currStep
b.minF = f
}
return b.nextStep((b.minStep + b.maxStep) / 2)
}
f = b.lastF
// The function value was lower. Check if this location is sufficient to
// converge the linesearch, otherwise iterate.
if StrongWolfeConditionsMet(f, g, minF, b.initGrad, b.currStep, 0, b.CurvatureFactor) {
b.lastOp = MajorIteration
return b.lastOp, b.currStep, nil
}
if math.IsInf(b.maxStep, 1) {
// The function value is lower. If the gradient is positive, an upper bound
// of the minimum been found. If the gradient is negative, search farther
// in that direction.
if g > 0 {
b.maxStep = b.currStep
b.maxF = f
return b.nextStep((b.minStep + b.maxStep) / 2)
}
b.minStep = b.currStep
b.minF = f
return b.nextStep(b.currStep * 2)
}
// The interval has been bounded, and we have found a new lowest value. Use
// the gradient to decide which direction.
if g < 0 {
b.minStep = b.currStep
b.minF = f
} else {
b.maxStep = b.currStep
b.maxF = f
}
return b.nextStep((b.minStep + b.maxStep) / 2)
}
// nextStep checks if the new step is equal to the old step.
// This can happen if min and max are the same, or if the step size is infinity,
// both of which indicate the minimization must stop. If the steps are different,
// it sets the new step size and returns the evaluation type and the step. If the steps
// are the same, it returns an error.
func (b *Bisection) nextStep(step float64) (Operation, float64, error) {
if b.currStep == step {
b.lastOp = NoOperation
return b.lastOp, b.currStep, ErrLinesearcherFailure
}
b.currStep = step
b.lastOp = FuncEvaluation
return b.lastOp, b.currStep, nil
}