use geonum::*;
use std::f64::consts::PI;
const EPSILON: f64 = 1e-10;
fn eval_poly(coeffs: &[Geonum], z: Geonum) -> Geonum {
let n = coeffs.len();
let mut result = coeffs[n - 1];
for i in (0..n - 1).rev() {
result = result * z + coeffs[i];
}
result
}
fn output_angle(g: Geonum) -> f64 {
let x = g.mag * g.angle.grade_angle().cos();
let y = g.mag * g.angle.grade_angle().sin();
y.atan2(x)
}
fn winding_number(coeffs: &[Geonum], radius: f64) -> i32 {
let num_points = 10000;
let mut total_angle_change = 0.0;
let mut prev_angle: Option<f64> = None;
for i in 0..=num_points {
let theta = 2.0 * PI * i as f64 / num_points as f64;
let z = Geonum::new_from_cartesian(radius * theta.cos(), radius * theta.sin());
let p_z = eval_poly(coeffs, z);
let current = output_angle(p_z);
if let Some(prev) = prev_angle {
let mut delta = current - prev;
while delta > PI {
delta -= 2.0 * PI;
}
while delta < -PI {
delta += 2.0 * PI;
}
total_angle_change += delta;
}
prev_angle = Some(current);
}
(total_angle_change / (2.0 * PI)).round() as i32
}
fn scalar(val: f64) -> Geonum {
if val >= 0.0 {
Geonum::new(val, 0.0, 1.0)
} else {
Geonum::new(val.abs(), 1.0, 1.0) }
}
#[test]
fn it_wraps_n_times_for_z_to_the_n() {
let z1_coeffs = [scalar(0.0), scalar(1.0)]; assert_eq!(winding_number(&z1_coeffs, 1.0), 1, "z wraps once");
let z2_coeffs = [scalar(0.0), scalar(0.0), scalar(1.0)]; assert_eq!(winding_number(&z2_coeffs, 1.0), 2, "z² wraps twice");
let z3_coeffs = [scalar(0.0), scalar(0.0), scalar(0.0), scalar(1.0)];
assert_eq!(winding_number(&z3_coeffs, 1.0), 3, "z³ wraps three times");
let z5_coeffs = [
scalar(0.0),
scalar(0.0),
scalar(0.0),
scalar(0.0),
scalar(0.0),
scalar(1.0),
];
assert_eq!(winding_number(&z5_coeffs, 1.0), 5, "z⁵ wraps five times");
}
#[test]
fn it_shows_angle_accumulation_is_the_degree() {
let angles = [0.0, PI / 6.0, PI / 3.0, PI / 2.0, PI, 3.0 * PI / 2.0];
for n in 2..=5 {
for &theta in &angles {
let z = Geonum::new_from_cartesian(theta.cos(), theta.sin());
let mut z_n = Geonum::new(1.0, 0.0, 1.0);
for _ in 0..n {
z_n = z_n * z;
}
let expected_angle = (n as f64 * theta) % (2.0 * PI);
let actual = output_angle(z_n);
let expected_norm = ((expected_angle % (2.0 * PI)) + 2.0 * PI) % (2.0 * PI);
let actual_norm = ((actual % (2.0 * PI)) + 2.0 * PI) % (2.0 * PI);
let diff = (expected_norm - actual_norm).abs();
let diff_wrapped = diff.min(2.0 * PI - diff);
assert!(
diff_wrapped < 0.01 || z_n.mag < EPSILON,
"z^{} at θ={:.3}: output angle {:.3} ≈ {}×{:.3} = {:.3}",
n,
theta,
actual,
n,
theta,
expected_angle
);
}
}
}
#[test]
fn it_counts_roots_of_z_squared_minus_one() {
let coeffs = [scalar(-1.0), scalar(0.0), scalar(1.0)];
assert_eq!(
winding_number(&coeffs, 5.0),
2,
"z²-1 winds twice on large circle: 2 roots exist"
);
let z1 = Geonum::new(1.0, 0.0, 1.0);
let p_z1 = eval_poly(&coeffs, z1);
assert!(
p_z1.mag < 0.01,
"z=1 is a root: |p(1)| = {:.6} ≈ 0",
p_z1.mag
);
let z_neg1 = Geonum::new(1.0, 1.0, 1.0); let p_z_neg1 = eval_poly(&coeffs, z_neg1);
assert!(
p_z_neg1.mag < 0.01,
"z=-1 is a root: |p(-1)| = {:.6} ≈ 0",
p_z_neg1.mag
);
}
#[test]
fn it_counts_roots_of_z_squared_plus_one() {
let coeffs = [scalar(1.0), scalar(0.0), scalar(1.0)];
assert_eq!(
winding_number(&coeffs, 5.0),
2,
"z²+1 winds twice: 2 roots exist even though none are real"
);
let z_i = Geonum::new(1.0, 1.0, 2.0); let p_zi = eval_poly(&coeffs, z_i);
assert!(
p_zi.mag < 0.01,
"z=i is a root: |p(i)| = {:.6} ≈ 0",
p_zi.mag
);
let z_neg_i = Geonum::new(1.0, 3.0, 2.0); let p_z_neg_i = eval_poly(&coeffs, z_neg_i);
assert!(
p_z_neg_i.mag < 0.01,
"z=-i is a root: |p(-i)| = {:.6} ≈ 0",
p_z_neg_i.mag
);
}
#[test]
fn it_counts_roots_of_a_cubic() {
let coeffs = [scalar(-1.0), scalar(0.0), scalar(0.0), scalar(1.0)];
assert_eq!(
winding_number(&coeffs, 5.0),
3,
"z³-1 winds three times: 3 roots exist"
);
let roots_angles = [0.0, 2.0 * PI / 3.0, 4.0 * PI / 3.0];
for (k, &angle) in roots_angles.iter().enumerate() {
let z = Geonum::new_from_cartesian(angle.cos(), angle.sin());
let p_z = eval_poly(&coeffs, z);
assert!(
p_z.mag < 0.01,
"cube root {} at angle {:.3}: |p(z)| = {:.6} ≈ 0",
k,
angle,
p_z.mag
);
}
}
#[test]
fn it_counts_roots_of_a_quartic() {
let coeffs = [
scalar(-1.0),
scalar(0.0),
scalar(0.0),
scalar(0.0),
scalar(1.0),
];
assert_eq!(
winding_number(&coeffs, 5.0),
4,
"z⁴-1 winds four times: 4 roots exist"
);
let root_angles = [0.0, PI / 2.0, PI, 3.0 * PI / 2.0];
for (k, &angle) in root_angles.iter().enumerate() {
let z = Geonum::new_from_cartesian(angle.cos(), angle.sin());
let p_z = eval_poly(&coeffs, z);
assert!(
p_z.mag < 0.01,
"fourth root {} at angle {:.3}: |p(z)| = {:.6} ≈ 0",
k,
angle,
p_z.mag
);
}
}
#[test]
fn it_unwinds_through_roots_as_circle_shrinks() {
let coeffs = [scalar(-1.0), scalar(0.0), scalar(1.0)];
let w_large = winding_number(&coeffs, 3.0);
assert_eq!(w_large, 2, "outside all roots: winding = degree = 2");
let w_small = winding_number(&coeffs, 0.5);
assert_eq!(w_small, 0, "inside all roots: winding = 0");
assert_eq!(w_large - w_small, 2, "winding change = 2 → 2 roots crossed");
}
#[test]
fn it_tracks_winding_change_through_nested_roots() {
let coeffs = [
scalar(0.0), scalar(8.0), scalar(-6.0), scalar(1.0), ];
let w_1 = winding_number(&coeffs, 1.0);
assert_eq!(w_1, 1, "radius 1: 1 root enclosed (z=0)");
let w_3 = winding_number(&coeffs, 3.0);
assert_eq!(w_3, 2, "radius 3: 2 roots enclosed (z=0, z=2)");
let w_5 = winding_number(&coeffs, 5.0);
assert_eq!(w_5, 3, "radius 5: 3 roots enclosed (all)");
assert_eq!(w_3 - w_1, 1, "crossing z=2 adds one winding");
assert_eq!(w_5 - w_3, 1, "crossing z=4 adds one winding");
}
#[test]
fn it_shows_why_algebra_cannot_see_this() {
let z_real = Geonum::new(2.0, 0.0, 1.0); let p_real = eval_poly(&[scalar(1.0), scalar(0.0), scalar(1.0)], z_real);
assert!(p_real.mag > 4.0, "scalar evaluation just gives a number");
let coeffs = [scalar(1.0), scalar(0.0), scalar(1.0)]; let radius = 2.0;
let num_samples = 8;
let mut angles_out = Vec::new();
for i in 0..num_samples {
let theta = 2.0 * PI * i as f64 / num_samples as f64;
let z = Geonum::new_from_cartesian(radius * theta.cos(), radius * theta.sin());
let p_z = eval_poly(&coeffs, z);
angles_out.push(output_angle(p_z));
}
let angle_variance: f64 = {
let mean: f64 = angles_out.iter().sum::<f64>() / angles_out.len() as f64;
angles_out.iter().map(|a| (a - mean).powi(2)).sum::<f64>() / angles_out.len() as f64
};
assert!(
angle_variance > 0.1,
"output angles vary: the winding information is in the angle, which algebra discards"
);
let w = winding_number(&coeffs, radius);
assert_eq!(w, 2, "angle data gives winding = 2 = number of roots");
}
#[test]
fn it_shows_roots_of_unity_are_the_q_lattice_generalized() {
for n in 2..=8 {
let mut coeffs: Vec<Geonum> = vec![scalar(0.0); n + 1];
coeffs[0] = scalar(-1.0); coeffs[n] = scalar(1.0);
assert_eq!(
winding_number(&coeffs, 3.0),
n as i32,
"z^{}-1 has winding {} on large circle",
n,
n
);
for k in 0..n {
let angle = 2.0 * PI * k as f64 / n as f64;
let z = Geonum::new_from_cartesian(angle.cos(), angle.sin());
let p_z = eval_poly(&coeffs, z);
assert!(
p_z.mag < 0.02,
"root {} of z^{}-1 at angle {:.3}: |p(z)| = {:.6} ≈ 0",
k,
n,
angle,
p_z.mag
);
}
}
}
#[test]
fn it_proves_no_rootless_polynomial_exists() {
let p1 = [scalar(1.0), scalar(0.0), scalar(1.0)];
assert_eq!(
winding_number(&p1, 10.0),
2,
"z²+1: winding 2, roots must exist"
);
let p2 = [
scalar(1.0),
scalar(0.0),
scalar(1.0),
scalar(0.0),
scalar(1.0),
];
assert_eq!(
winding_number(&p2, 10.0),
4,
"z⁴+z²+1: winding 4, four roots must exist"
);
let mut p3 = vec![scalar(0.0); 7];
p3[0] = scalar(1.0);
p3[6] = scalar(1.0);
assert_eq!(
winding_number(&p3, 10.0),
6,
"z⁶+1: winding 6, six roots must exist"
);
}