diff --git a/interfaces.go b/interfaces.go index 04c7dac..737d5d9 100644 --- a/interfaces.go +++ b/interfaces.go @@ -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 @@ -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 diff --git a/linesearch.go b/linesearch.go index b0e776d..09bc510 100644 --- a/linesearch.go +++ b/linesearch.go @@ -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. @@ -43,66 +41,60 @@ 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 + // 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. 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 { ls.nextMajor = true @@ -110,7 +102,7 @@ func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) { 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) } @@ -118,12 +110,9 @@ func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) { 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) @@ -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 @@ -149,31 +137,31 @@ 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) 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. @@ -181,8 +169,7 @@ func (ls *LinesearchMethod) initNextLinesearch(xNext []float64) (Operation, erro } 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. ls.lastOp = op return ls.lastOp, nil diff --git a/local.go b/local.go index 5b65a8f..1e221ee 100644 --- a/local.go +++ b/local.go @@ -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: @@ -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++ diff --git a/neldermead.go b/neldermead.go index f3c5f4c..0bf96cc 100644 --- a/neldermead.go +++ b/neldermead.go @@ -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 diff --git a/types.go b/types.go index 2c5759d..3c584c3 100644 --- a/types.go +++ b/types.go @@ -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.