use nalgebra::Vector2;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Triple {
pub a: i64,
pub b: i64,
pub c: i64,
}
pub const PRIMITIVE_TRIPLES: [Triple; 6] = [
Triple { a: 3, b: 4, c: 5 },
Triple { a: 5, b: 12, c: 13 },
Triple { a: 8, b: 15, c: 17 },
Triple { a: 7, b: 24, c: 25 },
Triple { a: 9, b: 40, c: 41 },
Triple { a: 11, b: 60, c: 61 },
];
pub fn all_48_directions() -> Vec<Vector2<i64>> {
let mut dirs = Vec::with_capacity(48);
for triple in &PRIMITIVE_TRIPLES {
let (a, b) = (triple.a, triple.b);
for &(x, y) in &[
(a, b),
(b, a),
(a, -b),
(b, -a),
(-a, b),
(-b, a),
(-a, -b),
(-b, -a),
] {
dirs.push(Vector2::new(x, y));
}
}
debug_assert_eq!(dirs.len(), 48);
dirs
}
pub fn int_det(u: &Vector2<i64>, v: &Vector2<i64>) -> i64 {
u.x * v.y - u.y * v.x
}
pub fn are_collinear(u: &Vector2<i64>, v: &Vector2<i64>) -> bool {
int_det(u, v) == 0
}
pub fn verify_all_pairs() -> (usize, usize, usize) {
let dirs = all_48_directions();
let total = dirs.len() * (dirs.len() - 1) / 2; let mut collinear = 0;
let mut noncollinear = 0;
for i in 0..dirs.len() {
for j in (i + 1)..dirs.len() {
if are_collinear(&dirs[i], &dirs[j]) {
collinear += 1;
} else {
noncollinear += 1;
}
}
}
(total, collinear, noncollinear)
}
pub fn verify_zero_drift() -> bool {
let dirs = all_48_directions();
let eps = 1e-10;
for i in 0..dirs.len() {
for j in i + 1..dirs.len() {
for k in j + 1..dirs.len() {
let c1 = crate::geometry::Circle::new(
Vector2::new(dirs[i].x as f64, dirs[i].y as f64),
1.0,
);
let c2 = crate::geometry::Circle::new(
Vector2::new(dirs[j].x as f64, dirs[j].y as f64),
2.0,
);
let c3 = crate::geometry::Circle::new(
Vector2::new(dirs[k].x as f64, dirs[k].y as f64),
1.5,
);
let s12 = crate::homothetic::external_homothetic_center(&c1, &c2);
let s23 = crate::homothetic::external_homothetic_center(&c2, &c3);
let s31 = crate::homothetic::external_homothetic_center(&c3, &c1);
let (s12, s23, s31) = match (s12, s23, s31) {
(Some(a), Some(b), Some(c)) => (a, b, c),
_ => continue,
};
let area = crate::homothetic::triangle_area(&s12, &s23, &s31);
if area > eps {
return false;
}
}
}
}
true
}
pub fn directions_by_triple() -> Vec<Vec<Vector2<i64>>> {
let dirs = all_48_directions();
let mut groups: Vec<Vec<Vector2<i64>>> = vec![Vec::with_capacity(8); 6];
for (i, &d) in dirs.iter().enumerate() {
groups[i / 8].push(d);
}
groups
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_48_directions() {
let dirs = all_48_directions();
assert_eq!(dirs.len(), 48);
for d in &dirs {
assert!(
d.x != 0 || d.y != 0,
"Direction vector should be non-zero"
);
}
let groups = directions_by_triple();
assert_eq!(groups.len(), 6);
for g in &groups {
assert_eq!(g.len(), 8);
let mut sorted = g.clone();
sorted.sort_by(|a, b| a.x.cmp(&b.x).then(a.y.cmp(&b.y)));
sorted.dedup();
assert_eq!(sorted.len(), 8, "All 8 directions should be distinct");
}
}
#[test]
fn test_primitive_triple_property() {
for triple in &PRIMITIVE_TRIPLES {
let a = triple.a as i128;
let b = triple.b as i128;
let c = triple.c as i128;
assert_eq!(
a * a + b * b,
c * c,
"({}, {}, {}) should satisfy a² + b² = c²",
triple.a,
triple.b,
triple.c
);
}
}
#[test]
fn test_same_triple_collinear() {
let (total, collinear, noncollinear) = verify_all_pairs();
assert_eq!(total, 1128, "48 choose 2 = 1128 pairs");
assert!(collinear > 0, "Some pairs should be collinear");
assert!(noncollinear > 0, "Some pairs should be non-collinear");
assert_eq!(total, collinear + noncollinear);
}
#[test]
fn test_zero_drift() {
assert!(
verify_zero_drift(),
"All Pythagorean48 direction triples should satisfy Monge collinearity"
);
}
#[test]
fn test_determinant_integer() {
let _dirs = all_48_directions();
}
#[test]
fn test_cross_triple_collinearity() {
let dirs = all_48_directions();
let groups = directions_by_triple();
for i in 0..6 {
for j in (i + 1)..6 {
for &v in &groups[i] {
for &w in &groups[j] {
assert!(
!are_collinear(&v, &w),
"Directions from different triples ({}, {}) should not be collinear: {:?} {:?}",
i,
j,
v,
w
);
}
}
}
}
}
#[test]
fn test_p48_det_consistency() {
let dirs = all_48_directions();
for i in 0..dirs.len() {
for j in 0..dirs.len() {
let det_ij = int_det(&dirs[i], &dirs[j]);
let det_ji = int_det(&dirs[j], &dirs[i]);
assert_eq!(det_ij, -det_ji, "Antisymmetry: det(i,j) = -det(j,i)");
}
}
}
}