Skip to content
This repository was archived by the owner on Nov 23, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,28 @@ package optimize
//
// It uses a reverse-communication interface between the optimization method
// and the caller. Method acts as a client that asks the caller to perform
// needed operations via RequestType returned from Init and Iterate methods.
// needed operations via Operation returned from Init and Iterate methods.
// This provides independence of the optimization algorithm on user-supplied
// data and their representation, and enables automation of common operations
// like checking for (various types of) convergence and maintaining statistics.
//
// A Method can command an Evaluation, a MajorIteration or NoOperation operations.
//
// An evaluation operation is one or more of the Evaluation operations
// (FuncEvaluation, GradEvaluation, etc.) which can be combined with
// the bitwise or operator. In an evaluation operation, the requested routines
// will be evaluated at the point specified in Location.X. The corresponding
// fields of Location will be filled with the results from the routine and can
// be retrieved upon the next call to Iterate. Alternatively, a Method can
// declare a MajorIteration. In a MajorIteration, all values in Location must
// be valid and consistent, and are interpreted as a new minimum. Convergence
// of the optimization (GradientThreshold, etc.) will be checked using this new
// minimum.
// the bitwise or operator. In an evaluation operation, the requested fields of
// Problem will be evaluated at the point specified in Location.X.
// The corresponding fields of Location will be filled with the results that
// can be retrieved upon the next call to Iterate. The Method interface
// requires that entries of Location are not modified aside from the commanded
// evaluations. Thus, the type implementing Method may use multiple Operations
// to set the Location fields at a particular x value.
//
// Instead of an Evaluation, a Method may declare MajorIteration. In
// a MajorIteration, the values in the fields of Location are treated as
// a potential optimizer. The convergence of the optimization routine
// (GradientThreshold, etc.) is checked at this new best point. In
// a MajorIteration, the fields of Location must be valid and consistent.
//
// A Method must not return InitIteration and PostIteration operations. These are
// reserved for the clients to be passed to Recorders. A Method must also not
Expand All @@ -36,8 +42,6 @@ type Method interface {

// Iterate retrieves data from loc, performs one iteration of the method,
// updates loc and returns the next operation.
// TODO(vladimir-ch): When decided, say something whether the contents of
// Location is preserved between calls to Iterate.
Iterate(loc *Location) (Operation, error)

// Needs specifies information about the objective function needed by the
Expand Down
105 changes: 46 additions & 59 deletions linesearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ type LinesearchMethod struct {
x []float64 // Starting point for the current iteration.
dir []float64 // Search direction for the current iteration.

first bool // Indicator of the first iteration.
nextMajor bool // Indicates that MajorIteration must be requested at the next call to Iterate.

loc Location // Storage for intermediate locations.
eval Operation // Indicator of valid fields in loc.
first bool // Indicator of the first iteration.
nextMajor bool // Indicates that MajorIteration must be commanded at the next call to Iterate.
eval Operation // Indicator of valid fields in Location.

lastStep float64 // Step taken from x in the previous call to Iterate.
lastOp Operation // Operation returned from the previous call to Iterate.
Expand All @@ -43,87 +41,78 @@ func (ls *LinesearchMethod) Init(loc *Location) (Operation, error) {
ls.first = true
ls.nextMajor = false

copyLocation(&ls.loc, loc)
// Indicate that all fields of ls.loc are valid.
// Indicate that all fields of loc are valid.
ls.eval = FuncEvaluation | GradEvaluation
if ls.loc.Hessian != nil {
if loc.Hessian != nil {
ls.eval |= HessEvaluation
}

ls.lastStep = math.NaN()
ls.lastOp = NoOperation

return ls.initNextLinesearch(loc.X)
return ls.initNextLinesearch(loc)
}

func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) {
switch ls.lastOp {
case NoOperation:
// TODO(vladimir-ch): We have previously returned with an error and
// Init was not called. What to do? What about ls's internal state?
// TODO(vladimir-ch): Either Init has not been called, or the caller is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, we'll leave to another PR for fix.

// trying to resume the optimization run after Iterate previously
// returned with an error. Decide what is the proper thing to do. See also #125.

case MajorIteration:
// We previously requested MajorIteration but since we're here, the
// previous location was not good enough to converge the full
// optimization. Start the next linesearch and store the next
// evaluation point in loc.X.
return ls.initNextLinesearch(loc.X)
// The previous updated location did not converge the full
// optimization. Initialize a new Linesearch.
return ls.initNextLinesearch(loc)

default:
// Store the result of the previously requested evaluation into ls.loc.
if ls.lastOp&FuncEvaluation != 0 {
ls.loc.F = loc.F
}
if ls.lastOp&GradEvaluation != 0 {
copy(ls.loc.Gradient, loc.Gradient)
}
if ls.lastOp&HessEvaluation != 0 {
ls.loc.Hessian.CopySym(loc.Hessian)
}
// Update the indicator of valid fields of ls.loc.
// Update the indicator of valid fields of loc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the indicator below the nextMajor check. It's not needed there, so it obfuscates.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although not necessary, I would like ls.eval to be complete when calling initNextLinesearch and for that it has to stay here.

ls.eval |= ls.lastOp

if ls.nextMajor {
ls.nextMajor = false

// Linesearcher previously indicated that it had finished, but we
// needed to evaluate invalid fields of ls.loc. Now we have them and
// can announce MajorIteration.

copyLocation(loc, &ls.loc)
// Linesearcher previously finished, and the invalid fields of loc
// have now been validated. Announce MajorIteration.
ls.lastOp = MajorIteration
return ls.lastOp, nil
}
}

projGrad := floats.Dot(ls.loc.Gradient, ls.dir)
if ls.Linesearcher.Finished(ls.loc.F, projGrad) {
// Form an operation that evaluates invalid fields of ls.loc.
ls.lastOp = complementEval(&ls.loc, ls.eval)
// Continue the linesearch.

f := math.NaN()
if ls.eval&FuncEvaluation != 0 {
f = loc.F
}
projGrad := math.NaN()
if ls.eval&GradEvaluation != 0 {
projGrad = floats.Dot(loc.Gradient, ls.dir)
}

if ls.Linesearcher.Finished(f, projGrad) {
// Form an operation that evaluates invalid fields of loc.
ls.lastOp = complementEval(loc, ls.eval)
if ls.lastOp == NoOperation {
// ls.loc is complete and MajorIteration can be announced directly.
copyLocation(loc, &ls.loc)
// loc is complete and MajorIteration can be announced directly.
ls.lastOp = MajorIteration
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment here like "Continue the linesearch" just to confirm to the code reader that this is the main line? (followed by the update valid fields)

ls.nextMajor = true
}
return ls.lastOp, nil
}

step, op, err := ls.Linesearcher.Iterate(ls.loc.F, projGrad)
step, op, err := ls.Linesearcher.Iterate(f, projGrad)
if err != nil {
return ls.error(err)
}
if !op.isEvaluation() {
panic("linesearch: Linesearcher returned invalid operation")
}

if step == ls.lastStep {
// Linesearcher is requesting another evaluation at the same point
// which is stored in ls.loc.X.
copy(loc.X, ls.loc.X)
} else {
// We are moving to a new location.
if step != ls.lastStep {
// We are moving to a new location, and not, say, evaluating extra
// information at the current location.

// Compute the next evaluation point and store it in loc.X.
floats.AddScaledTo(loc.X, ls.x, step, ls.dir)
Expand All @@ -135,8 +124,7 @@ func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) {
}

ls.lastStep = step
copy(ls.loc.X, loc.X) // Move ls.loc to the next evaluation point
ls.eval = NoOperation // and invalidate all its fields.
ls.eval = NoOperation // Indicate all invalid fields of loc.
}

ls.lastOp = op
Expand All @@ -149,40 +137,39 @@ func (ls *LinesearchMethod) error(err error) (Operation, error) {
}

// initNextLinesearch initializes the next linesearch using the previous
// complete location stored in ls.loc. It fills xNext and returns an evaluation
// to be performed at xNext.
func (ls *LinesearchMethod) initNextLinesearch(xNext []float64) (Operation, error) {
copy(ls.x, ls.loc.X)
// complete location stored in loc. It fills loc.X and returns an evaluation
// to be performed at loc.X.
func (ls *LinesearchMethod) initNextLinesearch(loc *Location) (Operation, error) {
copy(ls.x, loc.X)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to set ls.lastOp to be NoOperation up here so that's what it equals if an error occurs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On error I call LinesearchMethod.error() which sets lastOp to NoOperation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, I missed that. Thanks.


var step float64
if ls.first {
ls.first = false
step = ls.NextDirectioner.InitDirection(&ls.loc, ls.dir)
step = ls.NextDirectioner.InitDirection(loc, ls.dir)
} else {
step = ls.NextDirectioner.NextDirection(&ls.loc, ls.dir)
step = ls.NextDirectioner.NextDirection(loc, ls.dir)
}

projGrad := floats.Dot(ls.loc.Gradient, ls.dir)
projGrad := floats.Dot(loc.Gradient, ls.dir)
if projGrad >= 0 {
return ls.error(ErrNonNegativeStepDirection)
}

op := ls.Linesearcher.Init(ls.loc.F, projGrad, step)
op := ls.Linesearcher.Init(loc.F, projGrad, step)
if !op.isEvaluation() {
panic("linesearch: Linesearcher returned invalid operation")
}

floats.AddScaledTo(xNext, ls.x, step, ls.dir)
if floats.Equal(ls.x, xNext) {
floats.AddScaledTo(loc.X, ls.x, step, ls.dir)
if floats.Equal(ls.x, loc.X) {
// Step size is so small that the next evaluation point is
// indistinguishable from the starting point for the current iteration
// due to rounding errors.
return ls.error(ErrNoProgress)
}

ls.lastStep = step
copy(ls.loc.X, xNext) // Move ls.loc to the next evaluation point
ls.eval = NoOperation // and invalidate all its fields.
ls.eval = NoOperation // Invalidate all fields of loc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Indicate all invalid fields of loc" to mirror the comment in Init.


ls.lastOp = op
return ls.lastOp, nil
Expand Down
14 changes: 2 additions & 12 deletions local.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func minimize(p *Problem, method Method, settings *Settings, stats *Stats, optLo

for {
// Sequentially call method.Iterate, performing the operations it has
// requested, until convergence.
// commanded, until convergence.

switch op {
case NoOperation:
Expand Down Expand Up @@ -366,18 +366,8 @@ func checkLimits(loc *Location, stats *Stats, settings *Settings) Status {
}

// evaluate evaluates the routines specified by the Operation at loc.X, storing
// the answer into loc and updating stats. Unused fields of loc are set to NaN.
// It is the responsibility of Method to assemble a valid Location before
// requesting MajorIteration.
// the answer into loc and updating stats.
func evaluate(p *Problem, loc *Location, eval Operation, stats *Stats) {
loc.F = math.NaN()
if loc.Gradient != nil {
loc.Gradient[0] = math.NaN()
}
if loc.Hessian != nil {
loc.Hessian.SetSym(0, 0, math.NaN())
}

if eval&FuncEvaluation != 0 {
loc.F = p.Func(loc.X)
stats.FuncEvaluations++
Expand Down
4 changes: 2 additions & 2 deletions neldermead.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,13 @@ func (n *NelderMead) Iterate(loc *Location) (Operation, error) {
}

// returnNext updates the location based on the iteration type and the current
// simplex, and returns the next request.
// simplex, and returns the next operation.
func (n *NelderMead) returnNext(iter nmIterType, loc *Location) (Operation, error) {
n.lastIter = iter
switch iter {
case nmMajor:
// Fill loc with the current best point and value,
// and request a convergence check.
// and command a convergence check.
copy(loc.X, n.vertices[0])
loc.F = n.values[0]
return MajorIteration, nil
Expand Down
2 changes: 1 addition & 1 deletion types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

const defaultGradientAbsTol = 1e-6

// Operation represents the set of operations requested by Method at each
// Operation represents the set of operations commanded by Method at each
// iteration. It is a bitmap of various Iteration and Evaluation constants.
// Individual constants must NOT be combined together by the binary OR operator
// except for the Evaluation operations.
Expand Down