blob: c37ecd34705a4defb50bd81a2a7951382e5208af [file] [log] [blame]
package stats
import (
"math"
"go.skia.org/infra/go/skerr"
)
type statisticFunction func([]float64, float64, Hypothesis, bool) float64
// zeroin: obtain a function zero with the given range.
// Implementation based on zeroin.c in R 4.1.3 (https://cran.r-project.org/bin/windows/base/old/4.1.3/)
// Algorithm
//
// G.Forsythe, M.Malcolm, C.Moler, Computer methods for mathematical computations.
// M., Mir, 1980, p.180 of the Russian edition.
//
// The function makes use of the bisection procedure combined with the linear or quadric
// inverse interpolation. At every step program operates on three abscissae - a, b, and c.
//
// b - the last and the best approximation to the root
// a - the last but one approximation
// c - the last but one or even earlier approximation than a that
// 1) |f(b)| <= |f(c)|
// 2) f(b) and f(c) have opposite signs, i.e. b and c confine the root
//
// At every step zeroin selects one of the two new approximations, the former being obtained by
// the bisection procedure and the latter resulting in the interpolation (if a, b, and c are
// all different the quadric interpolation is utilized, otherwise the linear one). If the latter
// (i.e. obtained by the interpolation) point is reasonable (i.e. lies within the current
// interval [b,c] not being too close to the boundaries) it is accepted. The bisection result
// is used in the other case.
//
// This implementation is also known as Brent's method.
//
// zq - the z-score estimated from the statistic
// a and b - last estimates of the root
// nums - the sample data
// tol - the tolerance of the approximation
// correct - applies the correction in the statisticFunction
// f - the statisticFunction zeroin is finding the root of. Typically asymptoticW.
func zeroin(zq, a, b, tol float64, nums []float64, alt Hypothesis, correct bool, f statisticFunction) (float64, error) {
// Calculate f(a).
fa := f(nums, a, alt, correct)
fa = fa - zq
if fa == 0 {
return a, nil
}
// Calculate f(b).
fb := f(nums, b, alt, correct)
fb = fb - zq
if fb == 0 {
return b, nil
}
// f(a) and f(b) should have opposite signs
if fa*fb >= 0 {
return math.NaN(), skerr.Fmt("f() values at end points should be of opposite sign")
}
var prevStep, newStep, tolAct, p, q, cb, t1, t2, c, fc, epsilon float64
c, fc = a, fa
epsilon = math.Nextafter(1.0, 2.0) - 1.0
it := 0
// Repeat until f(b) == 0 or |b-a| is small enough (convergence).
for fb != 0 {
it++
if it > 1000 {
return math.NaN(), skerr.Fmt("iteration(%d) exceeds the maximum iteration", it)
}
// prevStep is the step at previous iteration.
prevStep = b - a
if math.Abs(fc) < math.Abs(fb) {
a = b
b = c
c = a
fa = fb
fb = fc
fc = fa
}
// tolAct is the actual tolerance
tolAct = 2*epsilon*math.Abs(b) + tol/2
// newStep is the step at this iteration.
newStep = (c - b) / 2
if math.Abs(newStep) <= tolAct || fb == 0 {
return b, nil
}
if math.Abs(prevStep) >= tolAct && math.Abs(fa) > math.Abs(fb) {
cb = c - b
if a == c {
// Linear interpolation.
t1 = fb / fa
p = cb * t1
q = 1.0 - t1
} else {
// Quadratic inverse interpolation.
q = fa / fc
t1 = fb / fc
t2 = fb / fa
p = t2 * (cb*q*(q-t1) - (b-a)*(t1-1.0))
q = (q - 1.0) * (t1 - 1.0) * (t2 - 1.0)
}
if p > 0 {
q = -q
} else {
p = -p
}
if p < (0.75*cb*q-math.Abs(tolAct*q)/2) && p < math.Abs(prevStep*q/2) {
newStep = p / q
}
}
if math.Abs(newStep) < tolAct {
if newStep > 0 {
newStep = tolAct
} else {
newStep = -tolAct
}
}
a, fa = b, fb
b += newStep
fb = f(nums, b, alt, correct)
fb = fb - zq
if (fb > 0 && fc > 0) || (fb < 0 && fc < 0) {
c, fc = a, fa
}
}
return b, nil
}