mirror of
				https://github.com/gonum/gonum.git
				synced 2025-10-31 18:42:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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"
 | |
| 
 | |
| 	"gonum.org/v1/gonum/floats"
 | |
| )
 | |
| 
 | |
| // LinesearchMethod represents an abstract optimization method in which a
 | |
| // function is optimized through successive line search optimizations.
 | |
| type LinesearchMethod struct {
 | |
| 	// NextDirectioner specifies the search direction of each linesearch.
 | |
| 	NextDirectioner NextDirectioner
 | |
| 	// Linesearcher performs a linesearch along the search direction.
 | |
| 	Linesearcher Linesearcher
 | |
| 
 | |
| 	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 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.
 | |
| }
 | |
| 
 | |
| func (ls *LinesearchMethod) Init(loc *Location) (Operation, error) {
 | |
| 	if loc.Gradient == nil {
 | |
| 		panic("linesearch: gradient is nil")
 | |
| 	}
 | |
| 
 | |
| 	dim := len(loc.X)
 | |
| 	ls.x = resize(ls.x, dim)
 | |
| 	ls.dir = resize(ls.dir, dim)
 | |
| 
 | |
| 	ls.first = true
 | |
| 	ls.nextMajor = false
 | |
| 
 | |
| 	// Indicate that all fields of loc are valid.
 | |
| 	ls.eval = FuncEvaluation | GradEvaluation
 | |
| 	if loc.Hessian != nil {
 | |
| 		ls.eval |= HessEvaluation
 | |
| 	}
 | |
| 
 | |
| 	ls.lastStep = math.NaN()
 | |
| 	ls.lastOp = NoOperation
 | |
| 
 | |
| 	return ls.initNextLinesearch(loc)
 | |
| }
 | |
| 
 | |
| func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) {
 | |
| 	switch ls.lastOp {
 | |
| 	case NoOperation:
 | |
| 		// 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:
 | |
| 		// The previous updated location did not converge the full
 | |
| 		// optimization. Initialize a new Linesearch.
 | |
| 		return ls.initNextLinesearch(loc)
 | |
| 
 | |
| 	default:
 | |
| 		// Update the indicator of valid fields of loc.
 | |
| 		ls.eval |= ls.lastOp
 | |
| 
 | |
| 		if ls.nextMajor {
 | |
| 			ls.nextMajor = false
 | |
| 
 | |
| 			// Linesearcher previously finished, and the invalid fields of loc
 | |
| 			// have now been validated. Announce MajorIteration.
 | |
| 			ls.lastOp = MajorIteration
 | |
| 			return ls.lastOp, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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)
 | |
| 	}
 | |
| 	op, step, err := ls.Linesearcher.Iterate(f, projGrad)
 | |
| 	if err != nil {
 | |
| 		return ls.error(err)
 | |
| 	}
 | |
| 
 | |
| 	switch op {
 | |
| 	case MajorIteration:
 | |
| 		// Linesearch has been finished.
 | |
| 
 | |
| 		ls.lastOp = complementEval(loc, ls.eval)
 | |
| 		if ls.lastOp == NoOperation {
 | |
| 			// loc is complete, MajorIteration can be declared directly.
 | |
| 			ls.lastOp = MajorIteration
 | |
| 		} else {
 | |
| 			// Declare MajorIteration on the next call to Iterate.
 | |
| 			ls.nextMajor = true
 | |
| 		}
 | |
| 
 | |
| 	case FuncEvaluation, GradEvaluation, FuncEvaluation | GradEvaluation:
 | |
| 		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)
 | |
| 			if floats.Equal(ls.x, loc.X) {
 | |
| 				// Step size has become 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
 | |
| 			ls.eval = NoOperation // Indicate all invalid fields of loc.
 | |
| 		}
 | |
| 		ls.lastOp = op
 | |
| 
 | |
| 	default:
 | |
| 		panic("linesearch: Linesearcher returned invalid operation")
 | |
| 	}
 | |
| 
 | |
| 	return ls.lastOp, nil
 | |
| }
 | |
| 
 | |
| func (ls *LinesearchMethod) error(err error) (Operation, error) {
 | |
| 	ls.lastOp = NoOperation
 | |
| 	return ls.lastOp, err
 | |
| }
 | |
| 
 | |
| // initNextLinesearch initializes the next linesearch using the previous
 | |
| // 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(loc, ls.dir)
 | |
| 	} else {
 | |
| 		step = ls.NextDirectioner.NextDirection(loc, ls.dir)
 | |
| 	}
 | |
| 
 | |
| 	projGrad := floats.Dot(loc.Gradient, ls.dir)
 | |
| 	if projGrad >= 0 {
 | |
| 		return ls.error(ErrNonDescentDirection)
 | |
| 	}
 | |
| 
 | |
| 	op := ls.Linesearcher.Init(loc.F, projGrad, step)
 | |
| 	switch op {
 | |
| 	case FuncEvaluation, GradEvaluation, FuncEvaluation | GradEvaluation:
 | |
| 	default:
 | |
| 		panic("linesearch: Linesearcher returned invalid operation")
 | |
| 	}
 | |
| 
 | |
| 	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
 | |
| 	ls.eval = NoOperation // Invalidate all fields of loc.
 | |
| 
 | |
| 	ls.lastOp = op
 | |
| 	return ls.lastOp, nil
 | |
| }
 | |
| 
 | |
| // ArmijoConditionMet returns true if the Armijo condition (aka sufficient
 | |
| // decrease) has been met. Under normal conditions, the following should be
 | |
| // true, though this is not enforced:
 | |
| //  - initGrad < 0
 | |
| //  - step > 0
 | |
| //  - 0 < decrease < 1
 | |
| func ArmijoConditionMet(currObj, initObj, initGrad, step, decrease float64) bool {
 | |
| 	return currObj <= initObj+decrease*step*initGrad
 | |
| }
 | |
| 
 | |
| // StrongWolfeConditionsMet returns true if the strong Wolfe conditions have been met.
 | |
| // The strong Wolfe conditions ensure sufficient decrease in the function
 | |
| // value, and sufficient decrease in the magnitude of the projected gradient.
 | |
| // Under normal conditions, the following should be true, though this is not
 | |
| // enforced:
 | |
| //  - initGrad < 0
 | |
| //  - step > 0
 | |
| //  - 0 <= decrease < curvature < 1
 | |
| func StrongWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
 | |
| 	if currObj > initObj+decrease*step*initGrad {
 | |
| 		return false
 | |
| 	}
 | |
| 	return math.Abs(currGrad) < curvature*math.Abs(initGrad)
 | |
| }
 | |
| 
 | |
| // WeakWolfeConditionsMet returns true if the weak Wolfe conditions have been met.
 | |
| // The weak Wolfe conditions ensure sufficient decrease in the function value,
 | |
| // and sufficient decrease in the value of the projected gradient. Under normal
 | |
| // conditions, the following should be true, though this is not enforced:
 | |
| //  - initGrad < 0
 | |
| //  - step > 0
 | |
| //  - 0 <= decrease < curvature< 1
 | |
| func WeakWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
 | |
| 	if currObj > initObj+decrease*step*initGrad {
 | |
| 		return false
 | |
| 	}
 | |
| 	return currGrad >= curvature*initGrad
 | |
| }
 | 
