use super::{Interior, SweptFaces, chernyaev_numerator_test};
use crate::Error;
fn opposed() -> SweptFaces<f64> {
SweptFaces::new([0.1, -2.0, 10.0, -2.0], [-10.0, 2.0, -0.1, 2.0]).expect("ambiguous faces")
}
#[test]
fn the_derived_coefficients_reproduce_the_numerator() {
let faces = opposed();
let d = |k: usize| faces.hi[k] - faces.lo[k];
let a = d(0) * d(2) - d(1) * d(3);
let b = faces.lo[2] * d(0) + faces.lo[0] * d(2) - faces.lo[3] * d(1) - faces.lo[1] * d(3);
let c = faces.lo[0] * faces.lo[2] - faces.lo[1] * faces.lo[3];
for step in 0..=20 {
let t = f64::from(step) / 20.0;
let expanded = a * t * t + b * t + c;
let direct = faces.numerator(t);
assert!(
(expanded - direct).abs() <= 1e-12 * direct.abs().max(1.0),
"t = {t}: expanded {expanded}, direct {direct}"
);
}
for root in faces.numerator_roots() {
assert!(
faces.numerator(root).abs() <= 1e-12 * a.abs().max(1.0),
"root {root} is not a root: F = {}",
faces.numerator(root)
);
}
}
#[test]
fn the_numerator_alone_reads_a_joined_cell_as_separated() {
let faces = opposed();
assert!(faces.denominator(0.0) > 0.0);
assert!(faces.denominator(1.0) < 0.0);
assert_eq!(faces.pole(), Some(0.5));
for step in 0..=100 {
let t = f64::from(step) / 100.0;
assert!(faces.numerator(t) < 0.0, "F({t}) = {}", faces.numerator(t));
}
assert!(
faces.saddle(0.75) > 0.0,
"saddle(0.75) = {}",
faces.saddle(0.75)
);
assert_eq!(chernyaev_numerator_test(&faces), Interior::Separated);
assert_eq!(faces.test(), Interior::Joined);
}
#[test]
fn the_saddle_position_is_where_the_bilinear_gradient_vanishes() {
let faces = opposed();
for step in 0..=20 {
let t = f64::from(step) / 20.0;
if faces.pole() == Some(t) {
continue;
}
let s = 1.0 - t;
let c = |k: usize| faces.lo[k] * s + faces.hi[k] * t;
let (a, b, cc, d) = (c(0), c(1), c(2), c(3));
let [u, v] = faces.saddle_position(t);
let df_du = -a * (1.0 - v) + b * (1.0 - v) + cc * v - d * v;
let df_dv = -a * (1.0 - u) - b * u + cc * u + d * (1.0 - u);
let scale = (a.abs() + b.abs() + cc.abs() + d.abs()).max(1.0);
assert!(
df_du.abs() <= 1e-9 * scale && df_dv.abs() <= 1e-9 * scale,
"t = {t}: gradient at the saddle is ({df_du}, {df_dv}), not zero"
);
let f = a * (1.0 - u) * (1.0 - v) + b * u * (1.0 - v) + cc * u * v + d * (1.0 - u) * v;
assert!(
(f - faces.saddle(t)).abs() <= 1e-9 * scale,
"t = {t}: bilinear at the saddle is {f}, saddle() says {}",
faces.saddle(t)
);
}
}
#[test]
fn without_a_pole_the_correction_changes_nothing() {
let aligned = [
([1.0, -1.0, 1.0, -1.0], [2.0, -3.0, 0.5, -1.0]),
([0.25, -4.0, 4.0, -0.25], [3.0, -0.5, 0.5, -3.0]),
([5.0, -1.0, 0.2, -2.0], [0.1, -0.1, 9.0, -9.0]),
];
for (lo, hi) in aligned {
let faces = SweptFaces::new(lo, hi).expect("ambiguous faces");
assert_eq!(faces.pole(), None, "{lo:?} -> {hi:?}");
assert!(faces.denominator(0.0) > 0.0 && faces.denominator(1.0) > 0.0);
assert_eq!(
faces.test(),
chernyaev_numerator_test(&faces),
"{lo:?} -> {hi:?}: the two tests should agree with no pole"
);
}
}
#[test]
fn a_face_with_no_saddle_is_rejected_rather_than_defaulted() {
assert_eq!(
SweptFaces::new([1.0, 1.0, 1.0, 1.0], [1.0, -1.0, 1.0, -1.0]),
Err(Error::DegenerateSweep)
);
assert_eq!(
SweptFaces::new([1.0, -1.0, 1.0, -1.0], [2.0, 2.0, 2.0, 2.0]),
Err(Error::DegenerateSweep)
);
assert!(SweptFaces::new([0.1, -2.0, 10.0, -2.0], [-10.0, 2.0, -0.1, 2.0]).is_ok());
}
#[test]
fn how_often_the_correction_changes_the_answer() {
let magnitudes = [0.1, 0.25, 1.0, 4.0, 10.0];
let mut total = 0u32;
let mut disagree = 0u32;
let mut with_pole = 0u32;
for a0 in magnitudes {
for c0 in magnitudes {
for b0 in magnitudes {
for a1 in magnitudes {
for c1 in magnitudes {
for b1 in magnitudes {
let lo = [a0, -b0, c0, -b0];
let hi = [-a1, b1, -c1, b1];
let Ok(faces) = SweptFaces::new(lo, hi) else {
continue;
};
total += 1;
if faces.pole().is_some() {
with_pole += 1;
}
if faces.test() != chernyaev_numerator_test(&faces) {
disagree += 1;
}
}
}
}
}
}
}
std::println!(
"opposed face pairs: {total} configurations, {with_pole} with a pole in the sweep, \
{disagree} where the numerator-only test gives the wrong answer"
);
assert_eq!((total, with_pole, disagree), (15625, 15625, 1966));
}
#[test]
fn the_test_is_deterministic_and_independent_of_diagonal_order() {
let faces = opposed();
let rotate2 = |v: [f64; 4]| [v[2], v[3], v[0], v[1]];
let rotated =
SweptFaces::new(rotate2(faces.lo), rotate2(faces.hi)).expect("still ambiguous faces");
assert_eq!(faces.test(), rotated.test());
assert_eq!(faces.pole(), rotated.pole());
let pole = faces.pole();
for step in 0..=20 {
let t = f64::from(step) / 20.0;
if pole == Some(t) {
assert!(
!faces.saddle(t).is_finite(),
"the pole should not be finite"
);
continue;
}
let (a, b) = (faces.saddle(t), rotated.saddle(t));
assert!(
(a - b).abs() <= 1e-12 * a.abs().max(1.0),
"t = {t}: {a} vs {b}"
);
}
assert_eq!(faces.test(), faces.test());
}