gonum.org/v1/gonum@v0.14.0/mathext/roots.go (about)

     1  // Derived from SciPy's special/c_misc/fsolve.c and special/c_misc/misc.h
     2  // https://github.com/scipy/scipy/blob/master/scipy/special/c_misc/fsolve.c
     3  // https://github.com/scipy/scipy/blob/master/scipy/special/c_misc/misc.h
     4  
     5  // Copyright ©2017 The Gonum Authors. All rights reserved.
     6  // Use of this source code is governed by a BSD-style
     7  // license that can be found in the LICENSE file.
     8  
     9  package mathext
    10  
    11  import "math"
    12  
    13  type objectiveFunc func(float64, []float64) float64
    14  
    15  type fSolveResult uint8
    16  
    17  const (
    18  	// An exact solution was found, in which case the first point on the
    19  	// interval is the value
    20  	fSolveExact fSolveResult = iota + 1
    21  	// Interval width is less than the tolerance
    22  	fSolveConverged
    23  	// Root-finding didn't converge in a set number of iterations
    24  	fSolveMaxIterations
    25  )
    26  
    27  const (
    28  	machEp = 1.0 / (1 << 53)
    29  )
    30  
    31  // falsePosition uses a combination of bisection and false position to find a
    32  // root of a function within a given interval. This is guaranteed to converge,
    33  // and always keeps a bounding interval, unlike Newton's method. Inputs are:
    34  //
    35  //	x1, x2:   initial bounding interval
    36  //	f1, f2: value of f() at x1 and x2
    37  //	absErr, relErr: absolute and relative errors on the bounding interval
    38  //	bisectTil: if > 0.0, perform bisection until the width of the bounding
    39  //	           interval is less than this
    40  //	f, fExtra: function to find root of is f(x, fExtra)
    41  //
    42  // Returns:
    43  //
    44  //	result: whether an exact root was found, the process converged to a
    45  //	        bounding interval small than the required error, or the max number
    46  //	        of iterations was hit
    47  //	bestX: best root approximation
    48  //	bestF: function value at bestX
    49  //	errEst: error estimation
    50  func falsePosition(x1, x2, f1, f2, absErr, relErr, bisectTil float64, f objectiveFunc, fExtra []float64) (fSolveResult, float64, float64, float64) {
    51  	// The false position steps are either unmodified, or modified with the
    52  	// Anderson-Bjorck method as appropriate. Theoretically, this has a "speed of
    53  	// convergence" of 1.7 (bisection is 1, Newton is 2).
    54  	// Note that this routine was designed initially to work with gammaincinv, so
    55  	// it may not be tuned right for other problems. Don't use it blindly.
    56  
    57  	if f1*f2 >= 0 {
    58  		panic("Initial interval is not a bounding interval")
    59  	}
    60  
    61  	const (
    62  		maxIterations = 100
    63  		bisectIter    = 4
    64  		bisectWidth   = 4.0
    65  	)
    66  
    67  	const (
    68  		bisect = iota + 1
    69  		falseP
    70  	)
    71  
    72  	var state uint8
    73  	if bisectTil > 0 {
    74  		state = bisect
    75  	} else {
    76  		state = falseP
    77  	}
    78  
    79  	gamma := 1.0
    80  
    81  	w := math.Abs(x2 - x1)
    82  	lastBisectWidth := w
    83  
    84  	var nFalseP int
    85  	var x3, f3, bestX, bestF float64
    86  	for i := 0; i < maxIterations; i++ {
    87  		switch state {
    88  		case bisect:
    89  			x3 = 0.5 * (x1 + x2)
    90  			if x3 == x1 || x3 == x2 {
    91  				// i.e., x1 and x2 are successive floating-point numbers
    92  				bestX = x3
    93  				if x3 == x1 {
    94  					bestF = f1
    95  				} else {
    96  					bestF = f2
    97  				}
    98  				return fSolveConverged, bestX, bestF, w
    99  			}
   100  
   101  			f3 = f(x3, fExtra)
   102  			if f3 == 0 {
   103  				return fSolveExact, x3, f3, w
   104  			}
   105  
   106  			if f3*f2 < 0 {
   107  				x1 = x2
   108  				f1 = f2
   109  			}
   110  			x2 = x3
   111  			f2 = f3
   112  			w = math.Abs(x2 - x1)
   113  			lastBisectWidth = w
   114  			if bisectTil > 0 {
   115  				if w < bisectTil {
   116  					bisectTil = -1.0
   117  					gamma = 1.0
   118  					nFalseP = 0
   119  					state = falseP
   120  				}
   121  			} else {
   122  				gamma = 1.0
   123  				nFalseP = 0
   124  				state = falseP
   125  			}
   126  		case falseP:
   127  			s12 := (f2 - gamma*f1) / (x2 - x1)
   128  			x3 = x2 - f2/s12
   129  			f3 = f(x3, fExtra)
   130  			if f3 == 0 {
   131  				return fSolveExact, x3, f3, w
   132  			}
   133  
   134  			nFalseP++
   135  			if f3*f2 < 0 {
   136  				gamma = 1.0
   137  				x1 = x2
   138  				f1 = f2
   139  			} else {
   140  				// Anderson-Bjorck method
   141  				g := 1.0 - f3/f2
   142  				if g <= 0 {
   143  					g = 0.5
   144  				}
   145  				gamma *= g
   146  			}
   147  			x2 = x3
   148  			f2 = f3
   149  			w = math.Abs(x2 - x1)
   150  
   151  			// Sanity check. For every 4 false position checks, see if we really are
   152  			// decreasing the interval by comparing to what bisection would have
   153  			// achieved (or, rather, a bit more lenient than that -- interval
   154  			// decreased by 4 instead of by 16, as the fp could be decreasing gamma
   155  			// for a bit). Note that this should guarantee convergence, as it makes
   156  			// sure that we always end up decreasing the interval width with a
   157  			// bisection.
   158  			if nFalseP > bisectIter {
   159  				if w*bisectWidth > lastBisectWidth {
   160  					state = bisect
   161  				}
   162  				nFalseP = 0
   163  				lastBisectWidth = w
   164  			}
   165  		}
   166  
   167  		tol := absErr + relErr*math.Max(math.Max(math.Abs(x1), math.Abs(x2)), 1.0)
   168  		if w <= tol {
   169  			if math.Abs(f1) < math.Abs(f2) {
   170  				bestX = x1
   171  				bestF = f1
   172  			} else {
   173  				bestX = x2
   174  				bestF = f2
   175  			}
   176  			return fSolveConverged, bestX, bestF, w
   177  		}
   178  	}
   179  
   180  	return fSolveMaxIterations, x3, f3, w
   181  }