use minuit2::{MnHesse, MnMigrad, MnMinos};
#[test]
fn minos_symmetric_quadratic() {
let quadratic = |p: &[f64]| 2.0 * p[0] * p[0] + 8.0 * p[1] * p[1];
let result = MnMigrad::new()
.add("x", 5.0, 1.0)
.add("y", -3.0, 1.0)
.minimize(&quadratic);
assert!(result.is_valid());
let hesse_result = MnHesse::new().calculate(&quadratic, &result);
assert!(hesse_result.is_valid());
let minos = MnMinos::new(&quadratic, &hesse_result);
let me = minos.minos_error(0);
let _ = minos.errors(0);
let upper = me.upper_error();
let lower = me.lower_error();
if me.is_valid() {
let ratio = upper.abs() / lower.abs();
assert!(
(ratio - 1.0).abs() < 0.5,
"upper/lower ratio should be ~1 for symmetric function, got {ratio} (upper={upper}, lower={lower})"
);
assert!(upper > 0.0, "upper Minos error should be positive: {upper}");
assert!(lower < 0.0, "lower Minos error should be negative: {lower}");
}
assert_eq!(me.parameter(), 0);
assert!(
me.nfcn() < 1_000_000,
"nfcn should be finite, got {}",
me.nfcn()
);
let min_val = me.min();
assert!(
min_val.is_finite(),
"minimum value should be finite, got {min_val}"
);
let _ = me.lower();
let _ = me.upper();
let _ = me.lower_valid();
let _ = me.upper_valid();
let _ = me.at_lower_limit();
let _ = me.at_upper_limit();
let _ = me.at_lower_max_fcn();
let _ = me.at_upper_max_fcn();
let _ = me.lower_new_min();
let _ = me.upper_new_min();
}
#[test]
fn minos_asymmetric() {
let asym = |p: &[f64]| {
if p[0] >= 0.0 {
p[0] * p[0]
} else {
4.0 * p[0] * p[0]
}
};
let result = MnMigrad::new().add("x", 0.5, 0.5).minimize(&asym);
assert!(result.is_valid());
assert!(result.fval() < 0.01, "should find minimum near 0");
let hesse_result = MnHesse::new().calculate(&asym, &result);
let minos = MnMinos::new(&asym, &hesse_result);
let me = minos.minos_error(0);
if me.is_valid() {
let upper = me.upper_error();
let lower = me.lower_error();
assert!(
upper.abs() > lower.abs() * 1.2,
"upper ({upper}) should be larger than |lower| ({lower}) for asymmetric function"
);
}
}
#[test]
fn minos_fixed_parameter() {
let result = MnMigrad::new()
.add("x", 5.0, 1.0)
.add_const("y", 0.0)
.minimize(&|p: &[f64]| p[0] * p[0] + p[1] * p[1]);
assert!(result.is_valid());
let minos = MnMinos::new(&|p: &[f64]| p[0] * p[0] + p[1] * p[1], &result);
let cross = minos.lower(1);
assert!(!cross.is_valid(), "Minos on const param should be invalid");
let me = minos.minos_error(1);
assert!(
!me.is_valid(),
"MinosError should be invalid for const param"
);
assert!(
me.min().is_finite(),
"min() should still return a finite original parameter value"
);
}
#[test]
fn minos_upper_bound_reports_limit() {
let result = MnMigrad::new()
.add_upper_limited("x", -1.0, 0.5, -0.5)
.minimize(&|p: &[f64]| (p[0] + 1.0).powi(2));
assert!(result.is_valid());
let minos = MnMinos::new(&|p: &[f64]| (p[0] + 1.0).powi(2), &result);
let me = minos.minos_error(0);
assert!((me.min() + 1.0).abs() < 1e-12);
assert!(
me.at_upper_limit(),
"upper crossing should be at parameter limit"
);
assert!(!me.lower_new_min());
assert!(!me.upper_new_min());
assert!(me.nfcn() > 0);
}