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 }