use echidna::record_multi;
use echidna_optim::{
implicit_adjoint, implicit_hessian, implicit_hvp, implicit_jacobian, implicit_tangent,
ImplicitError,
};
#[allow(clippy::eq_op)]
fn singular_rank1_tape() -> echidna::BytecodeTape<f64> {
record_multi(
|v| {
let z0 = v[0];
let z1 = v[1];
let x = v[2];
let one = x / x;
let two = one + one;
vec![z0 + z1 - x, two * z0 + two * z1 - two * x]
},
&[0.5_f64, 0.5, 1.0],
)
.0
}
#[test]
fn variant_singular_pins_implicit_jacobian() {
let mut tape = singular_rank1_tape();
let err = implicit_jacobian(&mut tape, &[0.5, 0.5], &[1.0], 2)
.expect_err("rank-1 F_z must error from implicit_jacobian");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_implicit_tangent() {
let mut tape = singular_rank1_tape();
let err = implicit_tangent(&mut tape, &[0.5, 0.5], &[1.0], &[1.0], 2)
.expect_err("rank-1 F_z must error from implicit_tangent");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_implicit_adjoint() {
let mut tape = singular_rank1_tape();
let err = implicit_adjoint(&mut tape, &[0.5, 0.5], &[1.0], &[1.0, 0.0], 2)
.expect_err("rank-1 F_z must error from implicit_adjoint");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_implicit_hvp() {
let mut tape = singular_rank1_tape();
let err = implicit_hvp(&mut tape, &[0.5, 0.5], &[1.0], &[1.0], &[1.0], 2)
.expect_err("rank-1 F_z must error from implicit_hvp");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_implicit_hessian() {
let mut tape = singular_rank1_tape();
let err = implicit_hessian(&mut tape, &[0.5, 0.5], &[1.0], 2)
.expect_err("rank-1 F_z must error from implicit_hessian");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_on_nan_jacobian_input() {
let (mut tape, _) = record_multi(
|v| {
let z = v[0];
let x = v[1];
vec![z * z - x]
},
&[2.0_f64, 4.0],
);
let z_nan = [f64::NAN];
let x = [4.0_f64];
assert!(
matches!(
implicit_jacobian(&mut tape, &z_nan, &x, 1),
Err(ImplicitError::Singular)
),
"NaN z_star must surface as ImplicitError::Singular from implicit_jacobian"
);
assert!(
matches!(
implicit_tangent(&mut tape, &z_nan, &x, &[1.0], 1),
Err(ImplicitError::Singular)
),
"NaN z_star must surface as ImplicitError::Singular from implicit_tangent"
);
assert!(
matches!(
implicit_adjoint(&mut tape, &z_nan, &x, &[1.0], 1),
Err(ImplicitError::Singular)
),
"NaN z_star must surface as ImplicitError::Singular from implicit_adjoint"
);
assert!(
matches!(
implicit_hvp(&mut tape, &z_nan, &x, &[1.0], &[1.0], 1),
Err(ImplicitError::Singular)
),
"NaN z_star must surface as ImplicitError::Singular from implicit_hvp"
);
assert!(
matches!(
implicit_hessian(&mut tape, &z_nan, &x, 1),
Err(ImplicitError::Singular)
),
"NaN z_star must surface as ImplicitError::Singular from implicit_hessian"
);
}
#[test]
fn variant_singular_pins_on_nan_x_dot() {
let (mut tape, _) = record_multi(
|v| {
let z = v[0];
let x = v[1];
vec![z * z - x]
},
&[2.0_f64, 4.0],
);
let err = implicit_tangent(&mut tape, &[2.0], &[4.0], &[f64::NAN], 1)
.expect_err("NaN x_dot must trip the post-solve non-finite guard");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_singular_pins_on_nan_z_bar() {
let (mut tape, _) = record_multi(
|v| {
let z = v[0];
let x = v[1];
vec![z * z - x]
},
&[2.0_f64, 4.0],
);
let err = implicit_adjoint(&mut tape, &[2.0], &[4.0], &[f64::NAN], 1)
.expect_err("NaN z_bar must trip the post-solve non-finite guard");
assert!(
matches!(err, ImplicitError::Singular),
"expected Singular, got {err:?}"
);
}
#[test]
fn variant_display_smoke_test() {
let cases = vec![ImplicitError::Singular];
for err in &cases {
let msg = format!("{err}");
assert!(!msg.is_empty(), "empty Display for {err:?}");
assert!(
msg.contains("implicit"),
"Display missing `implicit` prefix: {msg}"
);
let _: &dyn std::error::Error = err;
}
}