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 }