use crate::utils::convergence_data::ConvergenceData;
use crate::utils::enums::TerminationReason;
use crate::utils::solver_settings::SolverSettings;
pub(crate) fn is_xatol_satisfied(
x_curr: f64,
x_next: f64,
solver_settings: &SolverSettings,
convergence_data: Option<&mut ConvergenceData>,
) -> bool {
let satisfied = solver_settings.xatol.is_some()
&& (x_next - x_curr).abs() <= solver_settings.xatol.unwrap();
if satisfied && let Some(convergence_data) = convergence_data {
convergence_data.termination_reason = TerminationReason::AbsoluteStepToleranceSatisfied;
}
satisfied
}
pub(crate) fn is_vtol_satisfied(
v: f64,
solver_settings: &SolverSettings,
convergence_data: Option<&mut ConvergenceData>,
) -> bool {
let satisfied = solver_settings.vtol.is_some() && v.abs() <= solver_settings.vtol.unwrap();
if satisfied && let Some(convergence_data) = convergence_data {
convergence_data.termination_reason = TerminationReason::ValueToleranceSatisfied;
}
satisfied
}
pub(crate) fn is_batol_satisfied(
a: f64,
b: f64,
solver_settings: &SolverSettings,
convergence_data: Option<&mut ConvergenceData>,
) -> bool {
let satisfied =
solver_settings.batol.is_some() && (b - a).abs() <= solver_settings.batol.unwrap();
if satisfied && let Some(convergence_data) = convergence_data {
convergence_data.termination_reason = TerminationReason::AbsoluteBracketToleranceSatisfied;
}
satisfied
}
pub(crate) fn is_brtol_satisfied(
a: f64,
b: f64,
solver_settings: &SolverSettings,
convergence_data: Option<&mut ConvergenceData>,
) -> bool {
let satisfied = solver_settings.brtol.is_some()
&& (b - a).abs() <= solver_settings.brtol.unwrap() * a.abs().max(b.abs());
if satisfied && let Some(convergence_data) = convergence_data {
convergence_data.termination_reason = TerminationReason::RelativeBracketToleranceSatisfied;
}
satisfied
}
pub(crate) fn is_btol_satisfied(
a: f64,
b: f64,
solver_settings: &SolverSettings,
mut convergence_data: Option<&mut ConvergenceData>,
) -> bool {
is_batol_satisfied(a, b, solver_settings, convergence_data.as_deref_mut())
|| is_brtol_satisfied(a, b, solver_settings, convergence_data)
}
#[allow(dead_code)]
pub(crate) fn is_max_feval_satisfied(
n_feval: u32,
solver_settings: &SolverSettings,
convergence_data: Option<&mut ConvergenceData>,
) -> bool {
let satisfied =
solver_settings.max_feval.is_some() && n_feval >= solver_settings.max_feval.unwrap();
if satisfied && let Some(convergence_data) = convergence_data {
convergence_data.termination_reason = TerminationReason::MaxFunctionEvaluationsReached;
}
satisfied
}
#[cfg(test)]
mod is_xatol_satisfied_tests {
use super::*;
fn is_xatol_satisfied_test_helper(
x_curr: f64,
x_next: f64,
xatol: f64,
expect_satisfied: bool,
) {
let solver_settings = SolverSettings {
xatol: Some(xatol),
..Default::default()
};
let result = is_xatol_satisfied(x_curr, x_next, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_xatol_satisfied(
x_curr,
x_next,
&solver_settings,
Some(&mut convergence_data),
);
if expect_satisfied {
assert!(result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::AbsoluteStepToleranceSatisfied
));
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_xatol_satisfied_default() {
assert!(!is_xatol_satisfied(
0.0,
0.0,
&SolverSettings::default(),
None
));
}
#[test]
fn test_is_xatol_satisfied_basic_positive_true() {
is_xatol_satisfied_test_helper(0.5, 1.0, 0.6, true);
}
#[test]
fn test_is_xatol_satisfied_basic_positive_false() {
is_xatol_satisfied_test_helper(0.5, 1.0, 0.4, false);
}
#[test]
fn test_is_xatol_satisfied_basic_negative_true() {
is_xatol_satisfied_test_helper(1.0, 0.5, 0.6, true);
}
#[test]
fn test_is_xatol_satisfied_basic_negative_false() {
is_xatol_satisfied_test_helper(1.0, 0.5, 0.4, false);
}
#[test]
fn test_is_xatol_satisfied_on_limit_positive() {
is_xatol_satisfied_test_helper(0.5, 1.0, 0.5, true);
}
#[test]
fn test_is_xatol_satisfied_on_limit_negative() {
is_xatol_satisfied_test_helper(1.0, 0.5, 0.5, true);
}
}
#[cfg(test)]
mod is_vtol_satisfied_tests {
use super::*;
fn is_vtol_satisfied_test_helper(v: f64, vtol: f64, expect_satisfied: bool) {
let solver_settings = SolverSettings {
vtol: Some(vtol),
..Default::default()
};
let result = is_vtol_satisfied(v, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_vtol_satisfied(v, &solver_settings, Some(&mut convergence_data));
if expect_satisfied {
assert!(result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::ValueToleranceSatisfied
));
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_vtol_satisfied_default() {
assert!(!is_vtol_satisfied(0.0, &SolverSettings::default(), None));
}
#[test]
fn test_is_vtol_satisfied_basic_positive_true() {
is_vtol_satisfied_test_helper(0.5, 1.0, true);
}
#[test]
fn test_is_vtol_satisfied_basic_positive_false() {
is_vtol_satisfied_test_helper(2.0, 1.0, false);
}
#[test]
fn test_is_vtol_satisfied_basic_negative_true() {
is_vtol_satisfied_test_helper(-0.5, 1.0, true);
}
#[test]
fn test_is_vtol_satisfied_basic_negative_false() {
is_vtol_satisfied_test_helper(-2.0, 1.0, false);
}
#[test]
fn test_is_vtol_satisfied_match_vtol_positive() {
is_vtol_satisfied_test_helper(1.0, 1.0, true);
}
#[test]
fn test_is_vtol_satisfied_match_vtol_negative() {
is_vtol_satisfied_test_helper(-1.0, 1.0, true);
}
}
#[cfg(test)]
mod is_batol_satisfied_tests {
use super::*;
fn is_batol_satisfied_test_helper(a: f64, b: f64, batol: f64, expect_satisfied: bool) {
let solver_settings = SolverSettings {
batol: Some(batol),
..Default::default()
};
let result = is_batol_satisfied(a, b, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_batol_satisfied(a, b, &solver_settings, Some(&mut convergence_data));
if expect_satisfied {
assert!(result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::AbsoluteBracketToleranceSatisfied
));
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_batol_satisfied_default() {
assert!(!is_batol_satisfied(
0.0,
0.0,
&SolverSettings::default(),
None
));
}
#[test]
fn test_is_batol_satisfied_basic_true() {
is_batol_satisfied_test_helper(0.0, 1.0, 2.0, true);
}
#[test]
fn test_is_batol_satisfied_basic_false() {
is_batol_satisfied_test_helper(0.0, 1.0, 0.5, false);
}
#[test]
fn test_is_batol_satisfied_match_bracket_width() {
is_batol_satisfied_test_helper(0.0, 1.0, 1.0, true);
}
}
#[cfg(test)]
mod is_brtol_satisfied_tests {
use super::*;
fn is_brtol_satisfied_test_helper(a: f64, b: f64, brtol: f64, expect_satisfied: bool) {
let solver_settings = SolverSettings {
brtol: Some(brtol),
..Default::default()
};
let result = is_brtol_satisfied(a, b, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_brtol_satisfied(a, b, &solver_settings, Some(&mut convergence_data));
if expect_satisfied {
assert!(result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::RelativeBracketToleranceSatisfied
));
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_brtol_satisfied_default() {
assert!(!is_brtol_satisfied(
0.0,
0.0,
&SolverSettings::default(),
None
));
}
#[test]
fn test_is_brtol_satisfied_basic_true() {
is_brtol_satisfied_test_helper(1.0, 2.0, 0.6, true);
}
#[test]
fn test_is_brtol_satisfied_basic_false() {
is_brtol_satisfied_test_helper(1.0, 2.0, 0.4, false);
}
#[test]
fn test_is_brtol_satisfied_match_bracket_width() {
is_brtol_satisfied_test_helper(1.0, 2.0, 0.5, true);
}
}
#[cfg(test)]
mod is_btol_satisfied_tests {
use super::*;
fn is_btol_satisfied_test_helper(
a: f64,
b: f64,
batol: Option<f64>,
brtol: Option<f64>,
expect_satisfied: bool,
expected_termination_reason: Option<TerminationReason>,
) {
if (expect_satisfied && expected_termination_reason.is_none())
|| (!expect_satisfied && expected_termination_reason.is_some())
{
panic!("The test is misconfigured.");
}
let solver_settings = SolverSettings {
batol,
brtol,
..Default::default()
};
let result = is_btol_satisfied(a, b, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_btol_satisfied(a, b, &solver_settings, Some(&mut convergence_data));
if expect_satisfied {
assert!(result);
assert!(convergence_data.termination_reason == expected_termination_reason.unwrap());
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_btol_satisfied_default() {
assert!(!is_btol_satisfied(
0.0,
0.0,
&SolverSettings::default(),
None
));
}
#[test]
fn test_is_btol_satisfied_basic_true_batol_only() {
is_btol_satisfied_test_helper(
0.0,
1.0,
Some(2.0),
None,
true,
Some(TerminationReason::AbsoluteBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_basic_false_batol_only() {
is_btol_satisfied_test_helper(0.0, 1.0, Some(0.5), None, false, None);
}
#[test]
fn test_is_btol_satisfied_match_bracket_width_batol_only() {
is_btol_satisfied_test_helper(
0.0,
1.0,
Some(1.0),
None,
true,
Some(TerminationReason::AbsoluteBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_basic_true_brtol_only() {
is_btol_satisfied_test_helper(
1.0,
2.0,
None,
Some(0.6),
true,
Some(TerminationReason::RelativeBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_basic_false_brtol_only() {
is_btol_satisfied_test_helper(1.0, 2.0, None, Some(0.4), false, None);
}
#[test]
fn test_is_btol_satisfied_match_bracket_width_brtol_only() {
is_btol_satisfied_test_helper(
1.0,
2.0,
None,
Some(0.5),
true,
Some(TerminationReason::RelativeBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_basic_true_both() {
is_btol_satisfied_test_helper(
1.0,
2.0,
Some(1.1),
Some(0.6),
true,
Some(TerminationReason::AbsoluteBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_basic_false_both() {
is_btol_satisfied_test_helper(1.0, 2.0, Some(0.9), Some(0.4), false, None);
}
#[test]
fn test_is_btol_satisfied_true_only_batol_satisfied() {
is_btol_satisfied_test_helper(
1.0,
2.0,
Some(1.1),
Some(0.4),
true,
Some(TerminationReason::AbsoluteBracketToleranceSatisfied),
);
}
#[test]
fn test_is_btol_satisfied_true_only_brtol_satisfied() {
is_btol_satisfied_test_helper(
1.0,
2.0,
Some(0.9),
Some(0.6),
true,
Some(TerminationReason::RelativeBracketToleranceSatisfied),
);
}
}
#[cfg(test)]
mod is_max_feval_satisfied_tests {
use super::*;
fn is_max_feval_satisfied_test_helper(n_feval: u32, max_feval: u32, expect_satisfied: bool) {
let solver_settings = SolverSettings {
max_feval: Some(max_feval),
..Default::default()
};
let result = is_max_feval_satisfied(n_feval, &solver_settings, None);
if expect_satisfied {
assert!(result);
} else {
assert!(!result);
}
let mut convergence_data = ConvergenceData::default();
let result = is_max_feval_satisfied(n_feval, &solver_settings, Some(&mut convergence_data));
if expect_satisfied {
assert!(result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::MaxFunctionEvaluationsReached
));
} else {
assert!(!result);
assert!(matches!(
convergence_data.termination_reason,
TerminationReason::NotYetTerminated
));
}
}
#[test]
fn test_is_max_feval_satisfied_default() {
assert!(!is_max_feval_satisfied(5, &SolverSettings::default(), None));
}
#[test]
fn test_is_max_feval_satisfied_basic_true() {
is_max_feval_satisfied_test_helper(4, 3, true);
}
#[test]
fn test_is_max_feval_satisfied_basic_false() {
is_max_feval_satisfied_test_helper(2, 3, false);
}
#[test]
fn test_is_max_feval_satisfied_at_limit_true() {
is_max_feval_satisfied_test_helper(3, 3, true);
}
}