github.com/gopherd/gonum@v0.0.4/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  //  x1, x2:   initial bounding interval
    35  //  f1, f2: value of f() at x1 and x2
    36  //  absErr, relErr: absolute and relative errors on the bounding interval
    37  //  bisectTil: if > 0.0, perform bisection until the width of the bounding
    38  //             interval is less than this
    39  //  f, fExtra: function to find root of is f(x, fExtra)
    40  // Returns:
    41  //  result: whether an exact root was found, the process converged to a
    42  //          bounding interval small than the required error, or the max number
    43  //          of iterations was hit
    44  //  bestX: best root approximation
    45  //  bestF: function value at bestX
    46  //  errEst: error estimation
    47  func falsePosition(x1, x2, f1, f2, absErr, relErr, bisectTil float64, f objectiveFunc, fExtra []float64) (fSolveResult, float64, float64, float64) {
    48  	// The false position steps are either unmodified, or modified with the
    49  	// Anderson-Bjorck method as appropriate. Theoretically, this has a "speed of
    50  	// convergence" of 1.7 (bisection is 1, Newton is 2).
    51  	// Note that this routine was designed initially to work with gammaincinv, so
    52  	// it may not be tuned right for other problems. Don't use it blindly.
    53  
    54  	if f1*f2 >= 0 {
    55  		panic("Initial interval is not a bounding interval")
    56  	}
    57  
    58  	const (
    59  		maxIterations = 100
    60  		bisectIter    = 4
    61  		bisectWidth   = 4.0
    62  	)
    63  
    64  	const (
    65  		bisect = iota + 1
    66  		falseP
    67  	)
    68  
    69  	var state uint8
    70  	if bisectTil > 0 {
    71  		state = bisect
    72  	} else {
    73  		state = falseP
    74  	}
    75  
    76  	gamma := 1.0
    77  
    78  	w := math.Abs(x2 - x1)
    79  	lastBisectWidth := w
    80  
    81  	var nFalseP int
    82  	var x3, f3, bestX, bestF float64
    83  	for i := 0; i < maxIterations; i++ {
    84  		switch state {
    85  		case bisect:
    86  			x3 = 0.5 * (x1 + x2)
    87  			if x3 == x1 || x3 == x2 {
    88  				// i.e., x1 and x2 are successive floating-point numbers
    89  				bestX = x3
    90  				if x3 == x1 {
    91  					bestF = f1
    92  				} else {
    93  					bestF = f2
    94  				}
    95  				return fSolveConverged, bestX, bestF, w
    96  			}
    97  
    98  			f3 = f(x3, fExtra)
    99  			if f3 == 0 {
   100  				return fSolveExact, x3, f3, w
   101  			}
   102  
   103  			if f3*f2 < 0 {
   104  				x1 = x2
   105  				f1 = f2
   106  			}
   107  			x2 = x3
   108  			f2 = f3
   109  			w = math.Abs(x2 - x1)
   110  			lastBisectWidth = w
   111  			if bisectTil > 0 {
   112  				if w < bisectTil {
   113  					bisectTil = -1.0
   114  					gamma = 1.0
   115  					nFalseP = 0
   116  					state = falseP
   117  				}
   118  			} else {
   119  				gamma = 1.0
   120  				nFalseP = 0
   121  				state = falseP
   122  			}
   123  		case falseP:
   124  			s12 := (f2 - gamma*f1) / (x2 - x1)
   125  			x3 = x2 - f2/s12
   126  			f3 = f(x3, fExtra)
   127  			if f3 == 0 {
   128  				return fSolveExact, x3, f3, w
   129  			}
   130  
   131  			nFalseP++
   132  			if f3*f2 < 0 {
   133  				gamma = 1.0
   134  				x1 = x2
   135  				f1 = f2
   136  			} else {
   137  				// Anderson-Bjorck method
   138  				g := 1.0 - f3/f2
   139  				if g <= 0 {
   140  					g = 0.5
   141  				}
   142  				gamma *= g
   143  			}
   144  			x2 = x3
   145  			f2 = f3
   146  			w = math.Abs(x2 - x1)
   147  
   148  			// Sanity check. For every 4 false position checks, see if we really are
   149  			// decreasing the interval by comparing to what bisection would have
   150  			// achieved (or, rather, a bit more lenient than that -- interval
   151  			// decreased by 4 instead of by 16, as the fp could be decreasing gamma
   152  			// for a bit). Note that this should guarantee convergence, as it makes
   153  			// sure that we always end up decreasing the interval width with a
   154  			// bisection.
   155  			if nFalseP > bisectIter {
   156  				if w*bisectWidth > lastBisectWidth {
   157  					state = bisect
   158  				}
   159  				nFalseP = 0
   160  				lastBisectWidth = w
   161  			}
   162  		}
   163  
   164  		tol := absErr + relErr*math.Max(math.Max(math.Abs(x1), math.Abs(x2)), 1.0)
   165  		if w <= tol {
   166  			if math.Abs(f1) < math.Abs(f2) {
   167  				bestX = x1
   168  				bestF = f1
   169  			} else {
   170  				bestX = x2
   171  				bestF = f2
   172  			}
   173  			return fSolveConverged, bestX, bestF, w
   174  		}
   175  	}
   176  
   177  	return fSolveMaxIterations, x3, f3, w
   178  }