mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00

Changes made in dsp/fourier/internal/fftpack break the formatting used there, so these are reverted. There will be complaints in CI. [git-generate] gofmt -w . go generate gonum.org/v1/gonum/blas go generate gonum.org/v1/gonum/blas/gonum go generate gonum.org/v1/gonum/unit go generate gonum.org/v1/gonum/unit/constant go generate gonum.org/v1/gonum/graph/formats/dot go generate gonum.org/v1/gonum/graph/formats/rdf go generate gonum.org/v1/gonum/stat/card git checkout -- dsp/fourier/internal/fftpack
182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
// Derived from SciPy's special/c_misc/fsolve.c and special/c_misc/misc.h
|
|
// https://github.com/scipy/scipy/blob/master/scipy/special/c_misc/fsolve.c
|
|
// https://github.com/scipy/scipy/blob/master/scipy/special/c_misc/misc.h
|
|
|
|
// Copyright ©2017 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mathext
|
|
|
|
import "math"
|
|
|
|
type objectiveFunc func(float64, []float64) float64
|
|
|
|
type fSolveResult uint8
|
|
|
|
const (
|
|
// An exact solution was found, in which case the first point on the
|
|
// interval is the value
|
|
fSolveExact fSolveResult = iota + 1
|
|
// Interval width is less than the tolerance
|
|
fSolveConverged
|
|
// Root-finding didn't converge in a set number of iterations
|
|
fSolveMaxIterations
|
|
)
|
|
|
|
const (
|
|
machEp = 1.0 / (1 << 53)
|
|
)
|
|
|
|
// falsePosition uses a combination of bisection and false position to find a
|
|
// root of a function within a given interval. This is guaranteed to converge,
|
|
// and always keeps a bounding interval, unlike Newton's method. Inputs are:
|
|
//
|
|
// x1, x2: initial bounding interval
|
|
// f1, f2: value of f() at x1 and x2
|
|
// absErr, relErr: absolute and relative errors on the bounding interval
|
|
// bisectTil: if > 0.0, perform bisection until the width of the bounding
|
|
// interval is less than this
|
|
// f, fExtra: function to find root of is f(x, fExtra)
|
|
//
|
|
// Returns:
|
|
//
|
|
// result: whether an exact root was found, the process converged to a
|
|
// bounding interval small than the required error, or the max number
|
|
// of iterations was hit
|
|
// bestX: best root approximation
|
|
// bestF: function value at bestX
|
|
// errEst: error estimation
|
|
func falsePosition(x1, x2, f1, f2, absErr, relErr, bisectTil float64, f objectiveFunc, fExtra []float64) (fSolveResult, float64, float64, float64) {
|
|
// The false position steps are either unmodified, or modified with the
|
|
// Anderson-Bjorck method as appropriate. Theoretically, this has a "speed of
|
|
// convergence" of 1.7 (bisection is 1, Newton is 2).
|
|
// Note that this routine was designed initially to work with gammaincinv, so
|
|
// it may not be tuned right for other problems. Don't use it blindly.
|
|
|
|
if f1*f2 >= 0 {
|
|
panic("Initial interval is not a bounding interval")
|
|
}
|
|
|
|
const (
|
|
maxIterations = 100
|
|
bisectIter = 4
|
|
bisectWidth = 4.0
|
|
)
|
|
|
|
const (
|
|
bisect = iota + 1
|
|
falseP
|
|
)
|
|
|
|
var state uint8
|
|
if bisectTil > 0 {
|
|
state = bisect
|
|
} else {
|
|
state = falseP
|
|
}
|
|
|
|
gamma := 1.0
|
|
|
|
w := math.Abs(x2 - x1)
|
|
lastBisectWidth := w
|
|
|
|
var nFalseP int
|
|
var x3, f3, bestX, bestF float64
|
|
for i := 0; i < maxIterations; i++ {
|
|
switch state {
|
|
case bisect:
|
|
x3 = 0.5 * (x1 + x2)
|
|
if x3 == x1 || x3 == x2 {
|
|
// i.e., x1 and x2 are successive floating-point numbers
|
|
bestX = x3
|
|
if x3 == x1 {
|
|
bestF = f1
|
|
} else {
|
|
bestF = f2
|
|
}
|
|
return fSolveConverged, bestX, bestF, w
|
|
}
|
|
|
|
f3 = f(x3, fExtra)
|
|
if f3 == 0 {
|
|
return fSolveExact, x3, f3, w
|
|
}
|
|
|
|
if f3*f2 < 0 {
|
|
x1 = x2
|
|
f1 = f2
|
|
}
|
|
x2 = x3
|
|
f2 = f3
|
|
w = math.Abs(x2 - x1)
|
|
lastBisectWidth = w
|
|
if bisectTil > 0 {
|
|
if w < bisectTil {
|
|
bisectTil = -1.0
|
|
gamma = 1.0
|
|
nFalseP = 0
|
|
state = falseP
|
|
}
|
|
} else {
|
|
gamma = 1.0
|
|
nFalseP = 0
|
|
state = falseP
|
|
}
|
|
case falseP:
|
|
s12 := (f2 - gamma*f1) / (x2 - x1)
|
|
x3 = x2 - f2/s12
|
|
f3 = f(x3, fExtra)
|
|
if f3 == 0 {
|
|
return fSolveExact, x3, f3, w
|
|
}
|
|
|
|
nFalseP++
|
|
if f3*f2 < 0 {
|
|
gamma = 1.0
|
|
x1 = x2
|
|
f1 = f2
|
|
} else {
|
|
// Anderson-Bjorck method
|
|
g := 1.0 - f3/f2
|
|
if g <= 0 {
|
|
g = 0.5
|
|
}
|
|
gamma *= g
|
|
}
|
|
x2 = x3
|
|
f2 = f3
|
|
w = math.Abs(x2 - x1)
|
|
|
|
// Sanity check. For every 4 false position checks, see if we really are
|
|
// decreasing the interval by comparing to what bisection would have
|
|
// achieved (or, rather, a bit more lenient than that -- interval
|
|
// decreased by 4 instead of by 16, as the fp could be decreasing gamma
|
|
// for a bit). Note that this should guarantee convergence, as it makes
|
|
// sure that we always end up decreasing the interval width with a
|
|
// bisection.
|
|
if nFalseP > bisectIter {
|
|
if w*bisectWidth > lastBisectWidth {
|
|
state = bisect
|
|
}
|
|
nFalseP = 0
|
|
lastBisectWidth = w
|
|
}
|
|
}
|
|
|
|
tol := absErr + relErr*math.Max(math.Max(math.Abs(x1), math.Abs(x2)), 1.0)
|
|
if w <= tol {
|
|
if math.Abs(f1) < math.Abs(f2) {
|
|
bestX = x1
|
|
bestF = f1
|
|
} else {
|
|
bestX = x2
|
|
bestF = f2
|
|
}
|
|
return fSolveConverged, bestX, bestF, w
|
|
}
|
|
}
|
|
|
|
return fSolveMaxIterations, x3, f3, w
|
|
}
|