hpstat/src/root_finding.rs
RunasSudo 759c2c4778
turnbull: Use Illinois method rather than interval bisection for likelihood-ratio confidence intervals
27% speedup
NB: Regula falsi alone without Illinois adjustment was slower than interval bisection
2023-12-26 19:18:38 +11:00

89 lines
2.7 KiB
Rust

// hpstat: High-performance statistics implementations
// Copyright © 2023 Lee Yingtong Li (RunasSudo)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pub struct IllinoisRootFinder {
bound_lower: f64,
bound_upper: f64,
value_lower: f64,
value_upper: f64,
last_sign: f64 // Sign of the function at last evaluation (1.0 or -1.0) or 0.0 if first iteration
}
impl IllinoisRootFinder {
pub fn new(bound_lower: f64, bound_upper: f64, value_lower: f64, value_upper: f64) -> IllinoisRootFinder {
return IllinoisRootFinder {
bound_lower: bound_lower,
bound_upper: bound_upper,
value_lower: value_lower,
value_upper: value_upper,
last_sign: 0.0
}
}
pub fn update(&mut self, guess: f64, mut value: f64) {
if value > 0.0 {
if self.value_lower > 0.0 || self.value_upper < 0.0 {
self.bound_lower = guess;
self.value_lower = value;
if self.last_sign == 1.0 {
// Illinois adjustment: Halve the y-value of the retained end point (the other end point) when the new y-value has the same sign as the previous one
self.value_upper *= 0.5;
}
} else {
self.bound_upper = guess;
self.value_upper = value;
if self.last_sign == 1.0 {
self.value_lower *= 0.5;
}
}
self.last_sign = 1.0;
} else {
if self.value_lower < 0.0 || self.value_upper > 0.0 {
self.bound_lower = guess;
self.value_lower = value;
if self.last_sign == -1.0 {
self.value_upper *= 0.5;
}
} else {
self.bound_upper = guess;
self.value_upper = value;
if self.last_sign == -1.0 {
self.value_lower *= 0.5;
}
}
self.last_sign = -1.0;
}
}
pub fn next_guess(&self) -> f64 {
if self.value_lower.is_nan() || self.value_upper.is_nan() {
// Fall back to interval bisection
return (self.bound_lower + self.bound_upper) / 2.0;
} else {
// Regula falsi
return (self.bound_lower * self.value_upper - self.bound_upper * self.value_lower) / (self.value_upper - self.value_lower);
}
}
pub fn precision(&self) -> f64 {
return (self.bound_upper - self.bound_lower).abs();
}
}