use minuit2::{FCN, FCNGradient, MnHesse, MnMigrad, MnMinimize, MnSimplex};
use std::cell::Cell;
#[test]
fn root_hessian_external_indexing_numeric() {
let f = |p: &[f64]| {
let (x, y, z) = (p[0], p[1], p[2]);
x * x + 10.0 * y * y + 100.0 * z * z + 2.0 * x * y + 4.0 * x * z + 8.0 * y * z
};
let minimum = MnMigrad::new()
.add("x", 1.0, 0.1)
.add("y", 2.0, 0.1)
.add("z", 3.0, 0.1)
.fix(0) .minimize(&f);
assert!(minimum.is_valid());
let hesse = MnHesse::new().calculate(&f, &minimum);
assert!(hesse.is_valid());
let state = hesse.user_state();
let cov = state.covariance().expect("Hesse should provide covariance");
assert_eq!(cov.nrow(), 2);
let c00 = 400.0 / 3936.0;
let c01 = -16.0 / 3936.0;
let c11 = 40.0 / 3936.0;
assert!(
(cov.get(0, 0) - c00).abs() < 5e-4,
"cov(0,0) expected ~{c00}, got {}",
cov.get(0, 0)
);
assert!(
(cov.get(0, 1) - c01).abs() < 5e-4,
"cov(0,1) expected ~{c01}, got {}",
cov.get(0, 1)
);
assert!(
(cov.get(1, 1) - c11).abs() < 5e-4,
"cov(1,1) expected ~{c11}, got {}",
cov.get(1, 1)
);
}
struct QuadraticGrad;
impl FCN for QuadraticGrad {
fn value(&self, p: &[f64]) -> f64 {
let (x, y, z) = (p[0], p[1], p[2]);
x * x + 10.0 * y * y + 100.0 * z * z + 2.0 * x * y + 4.0 * x * z + 8.0 * y * z
}
}
impl FCNGradient for QuadraticGrad {
fn gradient(&self, p: &[f64]) -> Vec<f64> {
let (x, y, z) = (p[0], p[1], p[2]);
vec![
2.0 * x + 2.0 * y + 4.0 * z,
2.0 * x + 20.0 * y + 8.0 * z,
4.0 * x + 8.0 * y + 200.0 * z,
]
}
}
#[test]
fn root_hessian_external_indexing_with_gradient() {
let f = QuadraticGrad;
let minimum = MnMigrad::new()
.add("x", 1.0, 0.1)
.add("y", 2.0, 0.1)
.add("z", 3.0, 0.1)
.fix(0)
.minimize_grad(&f);
assert!(minimum.is_valid());
let hesse = MnHesse::new().calculate(&f, &minimum);
let cov = hesse
.user_state()
.covariance()
.expect("Hesse should provide covariance");
assert_eq!(cov.nrow(), 2);
assert!((cov.get(0, 0) - 400.0 / 3936.0).abs() < 5e-4);
assert!((cov.get(0, 1) - (-16.0 / 3936.0)).abs() < 5e-4);
assert!((cov.get(1, 1) - 40.0 / 3936.0).abs() < 5e-4);
}
struct Quadratic2;
impl FCN for Quadratic2 {
fn value(&self, p: &[f64]) -> f64 {
let (x, y) = (p[0], p[1]);
let dx = x - 1.0;
let dy = y + 2.0;
dx * dx + 4.0 * dy * dy + 0.3 * x * y
}
}
struct Rosenbrock2;
impl FCN for Rosenbrock2 {
fn value(&self, p: &[f64]) -> f64 {
let (x, y) = (p[0], p[1]);
let t1 = y - x * x;
let t2 = 1.0 - x;
100.0 * t1 * t1 + t2 * t2
}
}
struct QuadraticNoG2 {
g2_called: Cell<usize>,
}
impl FCN for QuadraticNoG2 {
fn value(&self, p: &[f64]) -> f64 {
let (x, y) = (p[0], p[1]);
let dx = x - 1.0;
let dy = y + 2.0;
dx * dx + dy * dy
}
fn has_hessian(&self) -> bool {
true
}
fn hessian(&self, _p: &[f64]) -> Vec<f64> {
vec![2.0, 0.0, 2.0]
}
fn has_g2(&self) -> bool {
false
}
fn g2(&self, _p: &[f64]) -> Vec<f64> {
self.g2_called.set(self.g2_called.get() + 1);
vec![2.0, 2.0]
}
}
impl FCNGradient for QuadraticNoG2 {
fn gradient(&self, p: &[f64]) -> Vec<f64> {
let (x, y) = (p[0], p[1]);
vec![2.0 * (x - 1.0), 2.0 * (y + 2.0)]
}
}
#[test]
fn root_simplex_reference_quadratic2() {
let f = Quadratic2;
let min = MnSimplex::new()
.add("x", 0.4, 0.1)
.add("y", -1.0, 0.1)
.tolerance(0.1)
.minimize(&f);
assert!(min.is_valid());
let params = min.params();
assert!((params[0] - 1.15).abs() < 5e-3, "x mismatch: {}", params[0]);
assert!((params[1] + 2.05).abs() < 5e-3, "y mismatch: {}", params[1]);
assert!(
(min.fval() - (-0.67475)).abs() < 5e-4,
"fval mismatch: {}",
min.fval()
);
assert!(
(min.edm() - 0.0334887).abs() < 5e-4,
"edm mismatch: {}",
min.edm()
);
}
#[test]
fn root_minimize_reference_rosenbrock2() {
let f = Rosenbrock2;
let min = MnMinimize::new()
.add("x", 0.0, 0.1)
.add("y", 0.0, 0.1)
.tolerance(0.1)
.minimize(&f);
assert!(min.is_valid());
assert!(
min.user_state().has_covariance(),
"MnMinimize should end with covariance for this workload"
);
assert!(
min.nfcn() > 50,
"unexpectedly low call count: {}",
min.nfcn()
);
let params = min.params();
assert!(
(params[0] - 0.99415).abs() < 0.02,
"x mismatch: {}",
params[0]
);
assert!(
(params[1] - 0.98827).abs() < 0.02,
"y mismatch: {}",
params[1]
);
}
#[test]
fn root_no_g2_calls_when_fcn_has_no_g2() {
let f = QuadraticNoG2 {
g2_called: Cell::new(0),
};
let min = MnMigrad::new()
.add("x", 0.4, 0.1)
.add("y", -1.0, 0.1)
.tolerance(0.1)
.minimize_grad(&f);
assert!(min.is_valid());
assert_eq!(
f.g2_called.get(),
0,
"G2() must not be called when has_g2() reports false"
);
}