gonum.org/v1/gonum@v0.14.0/optimize/linesearch.go (about)

     1  // Copyright ©2014 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package optimize
     6  
     7  import (
     8  	"math"
     9  
    10  	"gonum.org/v1/gonum/floats"
    11  )
    12  
    13  // LinesearchMethod represents an abstract optimization method in which a
    14  // function is optimized through successive line search optimizations.
    15  type LinesearchMethod struct {
    16  	// NextDirectioner specifies the search direction of each linesearch.
    17  	NextDirectioner NextDirectioner
    18  	// Linesearcher performs a linesearch along the search direction.
    19  	Linesearcher Linesearcher
    20  
    21  	x   []float64 // Starting point for the current iteration.
    22  	dir []float64 // Search direction for the current iteration.
    23  
    24  	first     bool      // Indicator of the first iteration.
    25  	nextMajor bool      // Indicates that MajorIteration must be commanded at the next call to Iterate.
    26  	eval      Operation // Indicator of valid fields in Location.
    27  
    28  	lastStep float64   // Step taken from x in the previous call to Iterate.
    29  	lastOp   Operation // Operation returned from the previous call to Iterate.
    30  }
    31  
    32  func (ls *LinesearchMethod) Init(loc *Location) (Operation, error) {
    33  	if loc.Gradient == nil {
    34  		panic("linesearch: gradient is nil")
    35  	}
    36  
    37  	dim := len(loc.X)
    38  	ls.x = resize(ls.x, dim)
    39  	ls.dir = resize(ls.dir, dim)
    40  
    41  	ls.first = true
    42  	ls.nextMajor = false
    43  
    44  	// Indicate that all fields of loc are valid.
    45  	ls.eval = FuncEvaluation | GradEvaluation
    46  	if loc.Hessian != nil {
    47  		ls.eval |= HessEvaluation
    48  	}
    49  
    50  	ls.lastStep = math.NaN()
    51  	ls.lastOp = NoOperation
    52  
    53  	return ls.initNextLinesearch(loc)
    54  }
    55  
    56  func (ls *LinesearchMethod) Iterate(loc *Location) (Operation, error) {
    57  	switch ls.lastOp {
    58  	case NoOperation:
    59  		// TODO(vladimir-ch): Either Init has not been called, or the caller is
    60  		// trying to resume the optimization run after Iterate previously
    61  		// returned with an error. Decide what is the proper thing to do. See also #125.
    62  
    63  	case MajorIteration:
    64  		// The previous updated location did not converge the full
    65  		// optimization. Initialize a new Linesearch.
    66  		return ls.initNextLinesearch(loc)
    67  
    68  	default:
    69  		// Update the indicator of valid fields of loc.
    70  		ls.eval |= ls.lastOp
    71  
    72  		if ls.nextMajor {
    73  			ls.nextMajor = false
    74  
    75  			// Linesearcher previously finished, and the invalid fields of loc
    76  			// have now been validated. Announce MajorIteration.
    77  			ls.lastOp = MajorIteration
    78  			return ls.lastOp, nil
    79  		}
    80  	}
    81  
    82  	// Continue the linesearch.
    83  
    84  	f := math.NaN()
    85  	if ls.eval&FuncEvaluation != 0 {
    86  		f = loc.F
    87  	}
    88  	projGrad := math.NaN()
    89  	if ls.eval&GradEvaluation != 0 {
    90  		projGrad = floats.Dot(loc.Gradient, ls.dir)
    91  	}
    92  	op, step, err := ls.Linesearcher.Iterate(f, projGrad)
    93  	if err != nil {
    94  		return ls.error(err)
    95  	}
    96  
    97  	switch op {
    98  	case MajorIteration:
    99  		// Linesearch has been finished.
   100  
   101  		ls.lastOp = complementEval(loc, ls.eval)
   102  		if ls.lastOp == NoOperation {
   103  			// loc is complete, MajorIteration can be declared directly.
   104  			ls.lastOp = MajorIteration
   105  		} else {
   106  			// Declare MajorIteration on the next call to Iterate.
   107  			ls.nextMajor = true
   108  		}
   109  
   110  	case FuncEvaluation, GradEvaluation, FuncEvaluation | GradEvaluation:
   111  		if step != ls.lastStep {
   112  			// We are moving to a new location, and not, say, evaluating extra
   113  			// information at the current location.
   114  
   115  			// Compute the next evaluation point and store it in loc.X.
   116  			floats.AddScaledTo(loc.X, ls.x, step, ls.dir)
   117  			if floats.Equal(ls.x, loc.X) {
   118  				// Step size has become so small that the next evaluation point is
   119  				// indistinguishable from the starting point for the current
   120  				// iteration due to rounding errors.
   121  				return ls.error(ErrNoProgress)
   122  			}
   123  			ls.lastStep = step
   124  			ls.eval = NoOperation // Indicate all invalid fields of loc.
   125  		}
   126  		ls.lastOp = op
   127  
   128  	default:
   129  		panic("linesearch: Linesearcher returned invalid operation")
   130  	}
   131  
   132  	return ls.lastOp, nil
   133  }
   134  
   135  func (ls *LinesearchMethod) error(err error) (Operation, error) {
   136  	ls.lastOp = NoOperation
   137  	return ls.lastOp, err
   138  }
   139  
   140  // initNextLinesearch initializes the next linesearch using the previous
   141  // complete location stored in loc. It fills loc.X and returns an evaluation
   142  // to be performed at loc.X.
   143  func (ls *LinesearchMethod) initNextLinesearch(loc *Location) (Operation, error) {
   144  	copy(ls.x, loc.X)
   145  
   146  	var step float64
   147  	if ls.first {
   148  		ls.first = false
   149  		step = ls.NextDirectioner.InitDirection(loc, ls.dir)
   150  	} else {
   151  		step = ls.NextDirectioner.NextDirection(loc, ls.dir)
   152  	}
   153  
   154  	projGrad := floats.Dot(loc.Gradient, ls.dir)
   155  	if projGrad >= 0 {
   156  		return ls.error(ErrNonDescentDirection)
   157  	}
   158  
   159  	op := ls.Linesearcher.Init(loc.F, projGrad, step)
   160  	switch op {
   161  	case FuncEvaluation, GradEvaluation, FuncEvaluation | GradEvaluation:
   162  	default:
   163  		panic("linesearch: Linesearcher returned invalid operation")
   164  	}
   165  
   166  	floats.AddScaledTo(loc.X, ls.x, step, ls.dir)
   167  	if floats.Equal(ls.x, loc.X) {
   168  		// Step size is so small that the next evaluation point is
   169  		// indistinguishable from the starting point for the current iteration
   170  		// due to rounding errors.
   171  		return ls.error(ErrNoProgress)
   172  	}
   173  
   174  	ls.lastStep = step
   175  	ls.eval = NoOperation // Invalidate all fields of loc.
   176  
   177  	ls.lastOp = op
   178  	return ls.lastOp, nil
   179  }
   180  
   181  // ArmijoConditionMet returns true if the Armijo condition (aka sufficient
   182  // decrease) has been met. Under normal conditions, the following should be
   183  // true, though this is not enforced:
   184  //   - initGrad < 0
   185  //   - step > 0
   186  //   - 0 < decrease < 1
   187  func ArmijoConditionMet(currObj, initObj, initGrad, step, decrease float64) bool {
   188  	return currObj <= initObj+decrease*step*initGrad
   189  }
   190  
   191  // StrongWolfeConditionsMet returns true if the strong Wolfe conditions have been met.
   192  // The strong Wolfe conditions ensure sufficient decrease in the function
   193  // value, and sufficient decrease in the magnitude of the projected gradient.
   194  // Under normal conditions, the following should be true, though this is not
   195  // enforced:
   196  //   - initGrad < 0
   197  //   - step > 0
   198  //   - 0 <= decrease < curvature < 1
   199  func StrongWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
   200  	if currObj > initObj+decrease*step*initGrad {
   201  		return false
   202  	}
   203  	return math.Abs(currGrad) < curvature*math.Abs(initGrad)
   204  }
   205  
   206  // WeakWolfeConditionsMet returns true if the weak Wolfe conditions have been met.
   207  // The weak Wolfe conditions ensure sufficient decrease in the function value,
   208  // and sufficient decrease in the value of the projected gradient. Under normal
   209  // conditions, the following should be true, though this is not enforced:
   210  //   - initGrad < 0
   211  //   - step > 0
   212  //   - 0 <= decrease < curvature< 1
   213  func WeakWolfeConditionsMet(currObj, currGrad, initObj, initGrad, step, decrease, curvature float64) bool {
   214  	if currObj > initObj+decrease*step*initGrad {
   215  		return false
   216  	}
   217  	return currGrad >= curvature*initGrad
   218  }