use geonum::{Angle, Geonum};
use std::f64::consts::PI;
const EPSILON: f64 = 1e-9;
#[test]
fn it_inverts_differentiation_preserving_the_projection_ratio() {
for blade in 0..8usize {
let g = Geonum::new_with_angle(1.0, Angle::new_with_blade(blade, 0.0, 1.0));
let g = Geonum::new_with_angle(1.0, g.angle + Angle::new(1.0, 6.0));
let round_trip = g.integrate().differentiate();
assert_eq!(
round_trip.angle.grade(),
g.angle.grade(),
"blade {blade}: grade must survive d(integral)"
);
assert!(
(round_trip.angle.grade_angle() - g.angle.grade_angle()).abs() < EPSILON,
"blade {blade}: the projection ratio t (carried in grade_angle) must return exactly"
);
}
}
#[test]
fn it_integrates_cos_over_an_interval() {
let cases = [
(Angle::new(0.0, 1.0), Angle::new(1.0, 2.0), 1.0), (Angle::new(0.0, 1.0), Angle::new(1.0, 1.0), 0.0), (
Angle::new(1.0, 6.0),
Angle::new(1.0, 3.0),
3.0_f64.sqrt() / 2.0 - 0.5,
), (Angle::new(1.0, 4.0), Angle::new(3.0, 4.0), 0.0), (
Angle::new(0.0, 1.0),
Angle::new(1.0, 3.0),
3.0_f64.sqrt() / 2.0,
), ];
for (a, b, expected) in cases {
let area = definite_integral(a, b, |x| x.cos_sin().1);
assert!(
(area - expected).abs() < EPSILON,
"integral of cos = {expected}, got {area}"
);
}
}
#[test]
fn it_integrates_sin_over_an_interval() {
let cases = [
(Angle::new(0.0, 1.0), Angle::new(1.0, 2.0), 1.0), (Angle::new(0.0, 1.0), Angle::new(1.0, 1.0), 2.0), (Angle::new(0.0, 1.0), Angle::new(1.0, 3.0), 0.5), ];
for (a, b, expected) in cases {
let area = definite_integral(a, b, |x| -x.cos_sin().0);
assert!(
(area - expected).abs() < EPSILON,
"integral of sin = {expected}, got {area}"
);
}
}
#[test]
fn it_cancels_the_four_cardinal_cosines() {
let mut sum = 0.0;
for k in 0..4usize {
sum += Angle::new_with_blade(k, 0.0, 1.0).cos_sin().0;
}
assert!(
sum.abs() < EPSILON,
"the four cardinal cosines cancel in conjugate pairs, got {sum}"
);
}
#[test]
fn it_telescopes_the_definite_integral_at_every_resolution() {
let sin_anti = |x: Angle| x.cos_sin().1; let (a, b) = (Angle::new(0.0, 1.0), Angle::new(1.0, 2.0)); let expected = definite_integral(a, b, sin_anti);
for n in [1usize, 4, 16, 64] {
let mut sum = 0.0;
for k in 0..n {
let x0 = Angle::new(k as f64 / n as f64, 2.0);
let x1 = Angle::new((k + 1) as f64 / n as f64, 2.0);
sum += definite_integral(x0, x1, sin_anti); }
assert!(
(sum - expected).abs() < EPSILON,
"n = {n}: telescoping sum must equal F(b) - F(a) = {expected}, got {sum}"
);
}
}
#[test]
fn it_draws_a_parallelogram_area_as_one_wedge() {
let v = Geonum::new_from_cartesian(3.0, 0.0); let w = Geonum::new_from_cartesian(0.0, 4.0);
assert!(
(v.wedge(&w).mag - 12.0).abs() < EPSILON,
"v ∧ w = 12 — the rectangle's area in one wedge"
);
let a = Geonum::new_from_cartesian(2.0, 0.0);
let b = Geonum::new_from_cartesian(1.0, 2.0);
assert!(
(a.wedge(&b).mag - 4.0).abs() < EPSILON,
"the slanted parallelogram is one wedge — rotation sweeps the area"
);
}
#[test]
fn it_sweeps_a_sector_by_the_radius_blade() {
let r = 2.0;
let radius = Geonum::new(r, 0.0, 1.0);
let full = radius.rotate(Angle::new(2.0, 1.0)); let quarter = radius.rotate(Angle::new(1.0, 2.0));
let sector = |g: &Geonum| 0.5 * r * r * (g.angle.blade() as f64 * PI / 2.0);
assert!(
(sector(&full) - PI * r * r).abs() < EPSILON,
"a full turn (blade 4) sweeps πr² — the disk, the rotation landing on blade 4"
);
assert!(
(sector(&quarter) - PI * r * r / 4.0).abs() < EPSILON,
"a quarter turn (blade 1) sweeps ¼πr² — the area follows the blade"
);
}
#[test]
fn it_integrates_under_a_line_as_one_triangle_wedge() {
let b = 3.0;
let along = Geonum::new_from_cartesian(b, 0.0); let up_the_line = Geonum::new_from_cartesian(b, b);
let integral = areal(&along, &up_the_line);
assert!(
(integral - b * b / 2.0).abs() < EPSILON,
"∫₀^b x dx = b²/2 — the triangle is one wedge, no rectangles summed"
);
}
#[test]
fn it_conserves_swept_area_in_equal_times() {
let (a, e, gm) = (1.0, 0.5, 1.0_f64);
let r_peri = a * (1.0 - e);
let r_apo = a * (1.0 + e);
let v_peri = (gm * (2.0 / r_peri - 1.0 / a)).sqrt(); let v_apo = (gm * (2.0 / r_apo - 1.0 / a)).sqrt();
let areal_peri = areal(
&Geonum::new(r_peri, 0.0, 1.0),
&Geonum::new(v_peri, 1.0, 2.0),
);
let areal_apo = areal(&Geonum::new(r_apo, 0.0, 1.0), &Geonum::new(v_apo, 1.0, 2.0));
assert!(
(areal_peri - areal_apo).abs() < EPSILON,
"½ r ∧ v is equal at perihelion and aphelion — equal areas in equal times"
);
}
#[test]
fn it_orients_the_swept_area_by_its_direction() {
let v = Geonum::new_from_cartesian(2.0, 1.0);
let w = Geonum::new_from_cartesian(1.0, 3.0);
assert!(
(v.wedge(&w) + w.wedge(&v)).mag < EPSILON,
"v ∧ w + w ∧ v = 0 — reversing the sweep negates the area, ∫_a^b = −∫_b^a"
);
}
#[test]
fn it_takes_down_the_riemann_mesh() {
let exact = 0.5; let mut last = f64::INFINITY;
for n in [10usize, 100, 1000] {
let mesh = riemann_left(|x| x, n);
let err = (mesh - exact).abs();
assert!(
err > 1e-4,
"n={n}: the mesh is still off by {err:.2e} — it never arrives"
);
assert!(err < last, "more strips, less error — but never zero");
last = err;
}
let along = Geonum::new_from_cartesian(1.0, 0.0); let up_the_line = Geonum::new_from_cartesian(1.0, 1.0); assert!(
(areal(&along, &up_the_line) - exact).abs() < EPSILON,
"the wedge is exact: ½, no mesh"
);
}
fn definite_integral(a: Angle, b: Angle, antideriv: impl Fn(Angle) -> f64) -> f64 {
antideriv(b) - antideriv(a)
}
fn areal(v: &Geonum, w: &Geonum) -> f64 {
0.5 * v.wedge(w).mag
}
fn riemann_left(f: impl Fn(f64) -> f64, n: usize) -> f64 {
let dx = 1.0 / n as f64;
(0..n).map(|k| f(k as f64 * dx) * dx).sum()
}