#![cfg(feature = "laurent")]
use approx::assert_relative_eq;
use echidna::Laurent;
use num_traits::Float;
type L4 = Laurent<f64, 4>;
#[test]
fn constant_and_variable() {
let c: L4 = Laurent::constant(3.0);
assert_relative_eq!(c.value(), 3.0);
assert_eq!(c.pole_order(), 0);
assert!(!c.has_pole());
let v: L4 = Laurent::variable(2.0);
assert_relative_eq!(v.value(), 2.0);
assert_eq!(v.pole_order(), 0);
assert_relative_eq!(v.coeff(0), 2.0);
assert_relative_eq!(v.coeff(1), 1.0);
}
#[test]
fn add_sub_regular() {
let a: L4 = Laurent::variable(1.0); let b: L4 = Laurent::constant(2.0); let sum = a + b;
assert_relative_eq!(sum.value(), 3.0);
assert_relative_eq!(sum.coeff(1), 1.0);
let diff = a - b;
assert_relative_eq!(diff.value(), -1.0);
assert_relative_eq!(diff.coeff(1), 1.0);
}
#[test]
fn mul_regular() {
let a: L4 = Laurent::variable(2.0); let b: L4 = Laurent::variable(3.0); let prod = a * b;
assert_relative_eq!(prod.value(), 6.0);
assert_relative_eq!(prod.coeff(1), 5.0);
assert_relative_eq!(prod.coeff(2), 1.0);
}
#[test]
fn div_creates_pole() {
let one: L4 = Laurent::constant(1.0);
let t: L4 = Laurent::variable(0.0); let result = one / t;
assert_eq!(result.pole_order(), -1);
assert_relative_eq!(result.leading_coefficient(), 1.0);
assert!(result.has_pole());
assert!(result.value().is_infinite());
}
#[test]
fn pole_cancellation() {
let t: L4 = Laurent::variable(0.0);
let inv_t = Laurent::constant(1.0) / t;
let product = t * inv_t;
assert_eq!(product.pole_order(), 0);
assert_relative_eq!(product.value(), 1.0, max_relative = 1e-12);
}
#[test]
fn pole_arithmetic() {
let t: L4 = Laurent::variable(0.0);
let inv_t = Laurent::constant(1.0) / t; let inv_t2 = inv_t * inv_t;
let sum = inv_t2 + inv_t;
assert_eq!(sum.pole_order(), -2);
assert_relative_eq!(sum.coeff(-2), 1.0);
assert_relative_eq!(sum.coeff(-1), 1.0);
}
#[test]
fn residue_extraction() {
let t: L4 = Laurent::variable(0.0);
let inv_t = Laurent::constant(3.0) / t; assert_relative_eq!(inv_t.residue(), 3.0);
let inv_t2 = Laurent::constant(1.0) / (t * t);
assert_relative_eq!(inv_t2.residue(), 0.0);
}
#[test]
fn sqrt_even_pole() {
let t: L4 = Laurent::variable(0.0);
let inv_t2 = Laurent::constant(1.0) / (t * t);
let result = inv_t2.sqrt();
assert_eq!(result.pole_order(), -1);
assert_relative_eq!(result.leading_coefficient(), 1.0, max_relative = 1e-10);
}
#[test]
fn exp_of_pole_is_nan() {
let t: L4 = Laurent::variable(0.0);
let inv_t = Laurent::constant(1.0) / t;
let result = inv_t.exp();
assert!(result.value().is_nan());
}
#[test]
fn ln_of_zero_is_nan() {
let t: L4 = Laurent::variable(0.0);
let result = t.ln();
assert!(result.value().is_nan());
}
#[test]
fn powi_with_poles() {
let t: L4 = Laurent::variable(0.0);
let inv_t = Laurent::constant(1.0) / t;
let result = inv_t.powi(3);
assert_eq!(result.pole_order(), -3);
assert_relative_eq!(result.leading_coefficient(), 1.0, max_relative = 1e-10);
}
#[test]
fn regular_matches_taylor() {
use echidna::Taylor;
type T4 = Taylor<f64, 4>;
let lt: L4 = Laurent::variable(2.0);
let tt: T4 = Taylor::variable(2.0);
let le = lt.exp();
let te = tt.exp();
for k in 0..4 {
assert_relative_eq!(le.coeff(k as i32), te.coeff(k), max_relative = 1e-12);
}
let ls = lt.sin();
let ts = tt.sin();
for k in 0..4 {
assert_relative_eq!(ls.coeff(k as i32), ts.coeff(k), max_relative = 1e-12);
}
}
#[cfg(feature = "bytecode")]
#[test]
fn tape_forward_tangent() {
use echidna::record;
let (tape, _) = record(|x| x[0] * x[0] + x[0].sin(), &[1.0]);
let x: L4 = Laurent::variable(1.0);
let mut buf = Vec::new();
tape.forward_tangent(&[x], &mut buf);
let result = buf[tape.output_index()];
assert_relative_eq!(result.value(), 1.0 + 1.0_f64.sin(), max_relative = 1e-12);
assert_eq!(result.pole_order(), 0);
}
#[cfg(feature = "bytecode")]
#[test]
fn singularity_detection_reciprocal() {
use echidna::record;
let (tape, _) = record(|x| x[0].recip(), &[1.0]);
let x: L4 = Laurent::variable(0.0);
let mut buf = Vec::new();
tape.forward_tangent(&[x], &mut buf);
let result = buf[tape.output_index()];
assert!(result.has_pole());
assert_eq!(result.pole_order(), -1);
}
#[test]
fn normalization_strips_zeros() {
let l: L4 = Laurent::new([0.0, 0.0, 1.0, 0.0], -2);
assert_eq!(l.pole_order(), 0);
assert_relative_eq!(l.leading_coefficient(), 1.0);
assert_relative_eq!(l.value(), 1.0);
}
type L3 = Laurent<f64, 3>;
#[test]
#[should_panic(expected = "pole-order gap")]
fn regression_12_laurent_sub_pole_order_gap_panics() {
let a = L3::new([1.0, 0.0, 0.0], -5);
let b = L3::new([1.0, 0.0, 0.0], 0);
let _ = a - b; }
#[test]
fn regression_13_laurent_nonzero_with_positive_pole_order_is_not_zero() {
use num_traits::Zero;
let l = L3::new([1.0, 0.0, 0.0], 1);
assert!(
!l.is_zero(),
"Laurent with nonzero coefficients and pole_order>0 should not be zero"
);
}
#[test]
fn regression_21_laurent_max_nan_returns_non_nan() {
let valid = L3::constant(5.0);
let nan = L3::constant(f64::NAN);
let r1 = valid.max(nan);
assert!(!r1.value().is_nan(), "max(valid, nan) should return valid");
let r2 = nan.max(valid);
assert!(!r2.value().is_nan(), "max(nan, valid) should return valid");
}
#[test]
fn regression_21_laurent_min_nan_returns_non_nan() {
let valid = L3::constant(5.0);
let nan = L3::constant(f64::NAN);
let r1 = valid.min(nan);
assert!(!r1.value().is_nan(), "min(valid, nan) should return valid");
let r2 = nan.min(valid);
assert!(!r2.value().is_nan(), "min(nan, valid) should return valid");
}
#[test]
fn regression_22_laurent_powi_pole_order_overflow_returns_nan() {
let l = L3::new([1.0, 0.0, 0.0], i32::MAX / 2 + 1);
let r = l.powi(3); assert!(
r.coeff(r.pole_order()).is_nan(),
"overflow should yield NaN Laurent, not panic"
);
}
#[test]
fn hypot_basic_3_4() {
let a: L4 = Laurent::constant(3.0);
let b: L4 = Laurent::constant(4.0);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
assert_relative_eq!(r.coeff(0), 5.0);
assert_relative_eq!(r.coeff(1), 0.0);
assert_relative_eq!(r.coeff(2), 0.0);
}
#[test]
fn hypot_large_magnitude_rescale() {
let a: L4 = Laurent::constant(1e200);
let b: L4 = Laurent::constant(1e200);
let r = a.hypot(b);
assert!(
r.coeff(0).is_finite(),
"rescale must keep primal finite, got {}",
r.coeff(0)
);
let expected = 2.0_f64.sqrt() * 1e200;
assert_relative_eq!(r.coeff(0), expected, max_relative = 1e-12);
}
#[test]
fn hypot_small_magnitude_no_underflow() {
let a: L4 = Laurent::constant(1e-200);
let b: L4 = Laurent::constant(1e-200);
let r = a.hypot(b);
assert!(
r.coeff(0) > 0.0,
"rescale must keep primal non-zero, got {}",
r.coeff(0)
);
let expected = 2.0_f64.sqrt() * 1e-200;
assert_relative_eq!(r.coeff(0), expected, max_relative = 1e-12);
}
#[test]
fn hypot_matched_pole_negative() {
let a: L4 = Laurent::new([3.0, 4.0, 0.0, 0.0], -2);
let b: L4 = Laurent::new([4.0, 3.0, 0.0, 0.0], -2);
let r = a.hypot(b);
assert_eq!(r.pole_order(), -2);
assert_relative_eq!(r.coeff(-2), 5.0);
}
#[test]
fn hypot_mismatched_pole_rebase() {
let a: L4 = Laurent::new([3.0, 4.0, 0.0, 0.0], 0);
let b: L4 = Laurent::new([1.0, 0.0, 0.0, 0.0], 1);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
assert_relative_eq!(r.coeff(0), 3.0);
assert_relative_eq!(r.coeff(1), 4.0);
assert_relative_eq!(r.coeff(2), 1.0 / 6.0, max_relative = 1e-12);
assert_relative_eq!(r.coeff(3), -2.0 / 9.0, max_relative = 1e-12);
}
#[test]
fn hypot_zero_origin_via_pole_shift() {
let a: L4 = Laurent::new([0.0, 3.0, 0.0, 0.0], 0);
let b: L4 = Laurent::new([0.0, 4.0, 0.0, 0.0], 0);
assert_eq!(a.pole_order(), 1);
assert_eq!(b.pole_order(), 1);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 1);
assert_relative_eq!(r.coeff(1), 5.0, max_relative = 1e-12);
assert_relative_eq!(r.coeff(2), 0.0);
assert_relative_eq!(r.coeff(3), 0.0);
}
#[test]
fn hypot_both_identically_zero() {
let a: L4 = Laurent::zero();
let b: L4 = Laurent::zero();
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
for k in 0..4 {
assert_eq!(
r.coeff(k as i32),
0.0,
"hypot(zero, zero) must stay all-zero; coeff({k}) = {}",
r.coeff(k as i32)
);
}
}
#[test]
fn hypot_extreme_pole_mismatch_truncates_operand() {
let a: L4 = Laurent::new([3.0, 0.0, 0.0, 0.0], 0);
let b: L4 = Laurent::new([5.0, 0.0, 0.0, 0.0], 4);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
assert_relative_eq!(r.coeff(0), 3.0);
assert_relative_eq!(r.coeff(1), 0.0);
assert_relative_eq!(r.coeff(2), 0.0);
assert_relative_eq!(r.coeff(3), 0.0);
}
#[test]
fn hypot_pole_i32_max_saturates() {
let a: L4 = Laurent::new([2.0, 0.0, 0.0, 0.0], 0);
let b: L4 = Laurent::new([1.0, 0.0, 0.0, 0.0], i32::MAX);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
assert_relative_eq!(r.coeff(0), 2.0);
}
#[test]
fn hypot_rescale_pivot_pin() {
let a: L4 = Laurent::new([1.0, 1e10, 0.0, 0.0], 0);
let b: L4 = Laurent::constant(2.0);
let r = a.hypot(b);
assert_eq!(r.pole_order(), 0);
let sqrt5 = 5.0_f64.sqrt();
assert_relative_eq!(r.coeff(0), sqrt5, max_relative = 1e-12);
assert_relative_eq!(r.coeff(1), 2e9 * sqrt5, max_relative = 1e-10);
assert_relative_eq!(r.coeff(2), 8e18 * sqrt5, max_relative = 1e-10);
assert_relative_eq!(r.coeff(3), -1.6e28 * sqrt5, max_relative = 1e-10);
}