use crate::callback_runtime::{self, CallbackFailure, F32Callback, F64Callback};
use slatec_sys::FortranInteger;
use super::{RootBracket, RootError, RootOptions, RootResult, RootStatus};
enum RootStage<T> {
Endpoint {
estimate: T,
status: RootStatus,
},
NoSignChange,
Native {
estimate: T,
other_endpoint: T,
status: FortranInteger,
},
}
fn callback_failure(failure: CallbackFailure) -> RootError {
match failure {
CallbackFailure::Panicked => RootError::CallbackPanicked,
CallbackFailure::NonFinite => RootError::CallbackReturnedNonFinite,
CallbackFailure::InvalidPointer => RootError::NativeContractViolation {
detail: "native callback argument was null",
},
}
}
fn opposite_sign_f64(left: f64, right: f64) -> bool {
left.is_sign_positive() != right.is_sign_positive()
}
fn opposite_sign_f32(left: f32, right: f32) -> bool {
left.is_sign_positive() != right.is_sign_positive()
}
fn validate_f64(
bracket: RootBracket<f64>,
options: RootOptions<f64>,
) -> Result<(f64, f64, f64), RootError> {
if !bracket.lower.is_finite() {
return Err(RootError::NonFiniteEndpoint { argument: "lower" });
}
if !bracket.upper.is_finite() {
return Err(RootError::NonFiniteEndpoint { argument: "upper" });
}
if bracket.lower == bracket.upper {
return Err(RootError::InvalidBracket);
}
for (argument, value) in [
("relative", options.relative_tolerance),
("absolute", options.absolute_tolerance),
] {
if !value.is_finite() || value < 0.0 {
return Err(RootError::InvalidTolerance { argument });
}
}
let minimum = bracket.lower.min(bracket.upper);
let maximum = bracket.lower.max(bracket.upper);
let guess = match options.initial_guess {
Some(value) if value.is_finite() && value > minimum && value < maximum => value,
Some(_) => return Err(RootError::InvalidInitialGuess),
None => bracket.upper,
};
Ok((
guess,
options.relative_tolerance,
options.absolute_tolerance,
))
}
fn validate_f32(
bracket: RootBracket<f32>,
options: RootOptions<f32>,
) -> Result<(f32, f32, f32), RootError> {
if !bracket.lower.is_finite() {
return Err(RootError::NonFiniteEndpoint { argument: "lower" });
}
if !bracket.upper.is_finite() {
return Err(RootError::NonFiniteEndpoint { argument: "upper" });
}
if bracket.lower == bracket.upper {
return Err(RootError::InvalidBracket);
}
for (argument, value) in [
("relative", options.relative_tolerance),
("absolute", options.absolute_tolerance),
] {
if !value.is_finite() || value < 0.0 {
return Err(RootError::InvalidTolerance { argument });
}
}
let minimum = bracket.lower.min(bracket.upper);
let maximum = bracket.lower.max(bracket.upper);
let guess = match options.initial_guess {
Some(value) if value.is_finite() && value > minimum && value < maximum => value,
Some(_) => return Err(RootError::InvalidInitialGuess),
None => bracket.upper,
};
Ok((
guess,
options.relative_tolerance,
options.absolute_tolerance,
))
}
fn result_f64(stage: RootStage<f64>, evaluations: usize) -> Result<RootResult<f64>, RootError> {
match stage {
RootStage::Endpoint { estimate, status } => Ok(RootResult {
estimate,
bracket: RootBracket {
lower: estimate,
upper: estimate,
},
evaluations,
status,
}),
RootStage::NoSignChange => Err(RootError::NoSignChange),
RootStage::Native {
estimate,
other_endpoint,
status,
} => Ok(RootResult {
estimate,
bracket: RootBracket {
lower: estimate,
upper: other_endpoint,
},
evaluations,
status: native_status(status)?,
}),
}
}
fn result_f32(stage: RootStage<f32>, evaluations: usize) -> Result<RootResult<f32>, RootError> {
match stage {
RootStage::Endpoint { estimate, status } => Ok(RootResult {
estimate,
bracket: RootBracket {
lower: estimate,
upper: estimate,
},
evaluations,
status,
}),
RootStage::NoSignChange => Err(RootError::NoSignChange),
RootStage::Native {
estimate,
other_endpoint,
status,
} => Ok(RootResult {
estimate,
bracket: RootBracket {
lower: estimate,
upper: other_endpoint,
},
evaluations,
status: native_status(status)?,
}),
}
}
fn native_status(status: FortranInteger) -> Result<RootStatus, RootError> {
match status {
1 => Ok(RootStatus::Converged),
2 => Ok(RootStatus::EndpointRoot),
3 => Ok(RootStatus::PossibleSingularity),
4 => Ok(RootStatus::NoSignChange),
5 => Ok(RootStatus::MaximumEvaluations),
value => Err(RootError::NativeStatus(value)),
}
}
fn stage_f64(
callback: F64Callback,
lower: f64,
upper: f64,
guess: f64,
relative: f64,
absolute: f64,
) -> RootStage<f64> {
let lower_value = callback.call(lower);
if callback.failed() {
return RootStage::NoSignChange;
}
if lower_value == 0.0 {
return RootStage::Endpoint {
estimate: lower,
status: RootStatus::LowerEndpoint,
};
}
let upper_value = callback.call(upper);
if callback.failed() {
return RootStage::NoSignChange;
}
if upper_value == 0.0 {
return RootStage::Endpoint {
estimate: upper,
status: RootStatus::UpperEndpoint,
};
}
if !opposite_sign_f64(lower_value, upper_value) {
return RootStage::NoSignChange;
}
let mut lower = lower;
let mut upper = upper;
let mut guess = guess;
let mut relative = relative;
let mut absolute = absolute;
let mut status = 0;
unsafe {
slatec_sys::roots::dfzero(
callback.ffi(),
&mut lower,
&mut upper,
&mut guess,
&mut relative,
&mut absolute,
&mut status,
);
}
RootStage::Native {
estimate: lower,
other_endpoint: upper,
status,
}
}
fn stage_f32(
callback: F32Callback,
lower: f32,
upper: f32,
guess: f32,
relative: f32,
absolute: f32,
) -> RootStage<f32> {
let lower_value = callback.call(lower);
if callback.failed() {
return RootStage::NoSignChange;
}
if lower_value == 0.0 {
return RootStage::Endpoint {
estimate: lower,
status: RootStatus::LowerEndpoint,
};
}
let upper_value = callback.call(upper);
if callback.failed() {
return RootStage::NoSignChange;
}
if upper_value == 0.0 {
return RootStage::Endpoint {
estimate: upper,
status: RootStatus::UpperEndpoint,
};
}
if !opposite_sign_f32(lower_value, upper_value) {
return RootStage::NoSignChange;
}
let mut lower = lower;
let mut upper = upper;
let mut guess = guess;
let mut relative = relative;
let mut absolute = absolute;
let mut status = 0;
unsafe {
slatec_sys::roots::fzero(
callback.ffi(),
&mut lower,
&mut upper,
&mut guess,
&mut relative,
&mut absolute,
&mut status,
);
}
RootStage::Native {
estimate: lower,
other_endpoint: upper,
status,
}
}
pub fn find_root<F>(
function: F,
bracket: RootBracket<f64>,
options: RootOptions<f64>,
) -> Result<RootResult<f64>, RootError>
where
F: FnMut(f64) -> f64,
{
let (guess, relative, absolute) = validate_f64(bracket, options)?;
let invocation = callback_runtime::with_f64(function, |callback| {
stage_f64(
callback,
bracket.lower,
bracket.upper,
guess,
relative,
absolute,
)
})
.map_err(|_| RootError::NestedNativeCallback)?;
if let Some(failure) = invocation.failure {
return Err(callback_failure(failure));
}
result_f64(invocation.value, invocation.evaluations)
}
pub fn find_root_f32<F>(
function: F,
bracket: RootBracket<f32>,
options: RootOptions<f32>,
) -> Result<RootResult<f32>, RootError>
where
F: FnMut(f32) -> f32,
{
let (guess, relative, absolute) = validate_f32(bracket, options)?;
let invocation = callback_runtime::with_f32(function, |callback| {
stage_f32(
callback,
bracket.lower,
bracket.upper,
guess,
relative,
absolute,
)
})
.map_err(|_| RootError::NestedNativeCallback)?;
if let Some(failure) = invocation.failure {
return Err(callback_failure(failure));
}
result_f32(invocation.value, invocation.evaluations)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_brackets_tolerances_and_guess_without_native_linking() {
let bracket = RootBracket {
lower: 1.0,
upper: 2.0,
};
assert_eq!(
validate_f64(bracket, RootOptions::default()).unwrap().0,
2.0
);
assert!(matches!(
validate_f64(
RootBracket {
lower: 1.0,
upper: 1.0
},
RootOptions::default()
),
Err(RootError::InvalidBracket)
));
assert!(matches!(
validate_f64(
bracket,
RootOptions {
initial_guess: Some(1.0),
..RootOptions::default()
}
),
Err(RootError::InvalidInitialGuess)
));
assert!(opposite_sign_f64(-1.0, 1.0));
assert!(!opposite_sign_f64(1.0, 1.0));
assert_eq!(-0.0_f64, 0.0);
}
#[test]
fn maps_each_documented_native_status() {
assert_eq!(native_status(1), Ok(RootStatus::Converged));
assert_eq!(native_status(2), Ok(RootStatus::EndpointRoot));
assert_eq!(native_status(3), Ok(RootStatus::PossibleSingularity));
assert_eq!(native_status(4), Ok(RootStatus::NoSignChange));
assert_eq!(native_status(5), Ok(RootStatus::MaximumEvaluations));
}
}