#![allow(
clippy::needless_range_loop,
reason = "index-heavy math loops are clearer with explicit indices"
)]
use std::collections::HashMap;
pub fn regular_13_gon() -> Vec<(f64, f64)> {
let n = 13;
let mut pts = Vec::with_capacity(n);
for k in 0..n {
let theta = 2.0 * std::f64::consts::PI * (k as f64) / (n as f64);
pts.push((theta.cos(), theta.sin()));
}
pts
}
pub fn c13_rotate(index: usize, k: usize) -> usize {
(index + k) % 13
}
pub fn d13_reflect(index: usize, axis: usize) -> usize {
(2 * axis + 13 - (index % 13)) % 13
}
pub fn dihedral_d13_permutations() -> Vec<Vec<usize>> {
let mut perms = Vec::with_capacity(26);
for k in 0..13 {
let mut p = Vec::with_capacity(13);
for i in 0..13 {
p.push(c13_rotate(i, k));
}
perms.push(p);
}
for axis in 0..13 {
let mut p = Vec::with_capacity(13);
for i in 0..13 {
p.push(d13_reflect(i, axis));
}
perms.push(p);
}
perms
}
pub fn detect_13_fold_symmetry(points: &[(f64, f64)], chi_threshold: f64) -> (bool, f64, f64) {
if points.len() < 13 {
return (false, f64::INFINITY, 0.0);
}
let (sum_x, sum_y) = points
.iter()
.fold((0.0, 0.0), |(sx, sy), (x, y)| (sx + x, sy + y));
let cx = sum_x / points.len() as f64;
let cy = sum_y / points.len() as f64;
let mut radii: Vec<f64> = Vec::with_capacity(points.len());
let mut angles: Vec<f64> = Vec::with_capacity(points.len());
for &(x, y) in points {
let dx = x - cx;
let dy = y - cy;
radii.push((dx * dx + dy * dy).sqrt());
angles.push(dy.atan2(dx)); }
let mut bins = [0usize; 13];
for &a in &angles {
let norm = if a < 0.0 {
a + 2.0 * std::f64::consts::PI
} else {
a
};
let bin = ((norm / (2.0 * std::f64::consts::PI)) * 13.0).floor() as usize % 13;
bins[bin] += 1;
}
let expected = points.len() as f64 / 13.0;
let chi_sq: f64 = bins
.iter()
.map(|&count| {
let diff = count as f64 - expected;
diff * diff / expected
})
.sum();
let mean_r = radii.iter().sum::<f64>() / radii.len() as f64;
let var_r = radii.iter().map(|&r| (r - mean_r).powi(2)).sum::<f64>() / radii.len() as f64;
let std_r = var_r.sqrt();
let in_band = radii
.iter()
.filter(|&&r| (r - mean_r).abs() <= std_r)
.count();
let peak_fraction = in_band as f64 / radii.len() as f64;
let is_symmetric = chi_sq <= chi_threshold && peak_fraction >= 0.5;
(is_symmetric, chi_sq, peak_fraction)
}
pub fn star_polygon_13(k: usize) -> Option<Vec<usize>> {
if k == 0 || k >= 13 || gcd(k as u64, 13) != 1 {
return None;
}
let mut seq = Vec::with_capacity(13);
let mut current = 0;
for _ in 0..13 {
seq.push(current);
current = (current + k) % 13;
}
Some(seq)
}
pub fn roots_of_unity_13() -> Vec<(f64, f64)> {
let mut roots = Vec::with_capacity(13);
for k in 0..13 {
let theta = 2.0 * std::f64::consts::PI * (k as f64) / 13.0;
roots.push((theta.cos(), theta.sin()));
}
roots
}
pub fn cyclotomic_13(x: f64) -> f64 {
if (x - 1.0).abs() < 1e-12 {
return 13.0; }
let mut sum = 1.0;
let mut term = 1.0;
for _ in 0..12 {
term *= x;
sum += term;
}
sum
}
pub fn real_subfield_basis_13() -> [f64; 6] {
let mut basis = [0.0; 6];
for k in 1..=6 {
let theta = 2.0 * std::f64::consts::PI * (k as f64) / 13.0;
basis[k - 1] = 2.0 * theta.cos();
}
basis
}
pub fn gaussian_periods_cubic_13() -> (f64, f64, f64) {
let h: [usize; 4] = [1, 8, 12, 5];
let coset_reps: [usize; 3] = [1, 2, 4];
let mut periods = [0.0; 3];
for i in 0..3 {
let rep = coset_reps[i];
let mut sum_r = 0.0;
for &hj in &h {
let exp = (hj * rep) % 13;
let theta = 2.0 * std::f64::consts::PI * (exp as f64) / 13.0;
sum_r += theta.cos();
}
periods[i] = sum_r; }
(periods[0], periods[1], periods[2])
}
pub fn cusp_form_gamma0_13(tau_imag: f64, terms: usize) -> f64 {
if tau_imag <= 0.0 {
return 0.0;
}
let q = (-2.0 * std::f64::consts::PI * tau_imag).exp();
let q13 = (-2.0 * std::f64::consts::PI * 13.0 * tau_imag).exp();
let eta = |q_base: f64, t: usize| -> f64 {
let mut prod = 1.0;
for n in 1..=t {
prod *= 1.0 - q_base.powi(n as i32);
}
q_base.powf(1.0 / 24.0) * prod
};
let eta_tau = eta(q, terms);
let eta_13tau = eta(q13, terms);
eta_tau.powi(2) * eta_13tau.powi(2)
}
pub fn hauptmodul_x0_13(q: f64, terms: usize) -> f64 {
if q <= 0.0 || q >= 1.0 {
return f64::NAN;
}
let coeffs = [
2.0, 5.0, 4.0, -3.0, 0.0, 1.0, -1.0, 2.0, 0.0, -2.0, ];
let mut sum = q.powi(-1); for (i, &c) in coeffs.iter().take(terms).enumerate() {
sum += c * q.powi(i as i32);
}
sum
}
pub fn power_residue_symbol_13(p: u64, q: u64) -> i32 {
if q == 0 {
return 0;
}
let p = p % q;
if p == 0 {
return 0;
}
if q % 13 != 1 {
return -1; }
let exp = (q - 1) / 13;
let result = mod_pow(p, exp, q);
if result == 1 {
1
} else {
-1
}
}
pub fn circulant_graph_13(s: &[usize]) -> Vec<Vec<usize>> {
let n = 13;
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for &step in s {
let step = step % n;
if step == 0 {
continue;
}
let j1 = (i + step) % n;
let j2 = (i + n - step) % n;
if !adj[i].contains(&j1) {
adj[i].push(j1);
}
if !adj[i].contains(&j2) {
adj[i].push(j2);
}
}
}
adj
}
pub fn paley_graph_13() -> Vec<Vec<usize>> {
let n = 13;
let residues: [usize; 6] = [1, 3, 4, 9, 10, 12];
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for &r in &residues {
let j = (i + r) % n;
if i != j && !adj[i].contains(&j) {
adj[i].push(j);
adj[j].push(i);
}
}
}
adj
}
pub fn is_13_regular(adj: &[Vec<usize>]) -> bool {
adj.len() >= 14
&& adj.iter().all(|neighbors| {
let mut unique: Vec<usize> = neighbors.clone();
unique.sort_unstable();
unique.dedup();
unique.len() == 13
})
}
pub fn degree_sequence(adj: &[Vec<usize>]) -> Vec<usize> {
adj.iter()
.map(|neighbors| {
let mut unique = neighbors.clone();
unique.sort_unstable();
unique.dedup();
unique.len()
})
.collect()
}
pub fn graph_cover_13(
base_adj: &[Vec<usize>],
voltages: &HashMap<(usize, usize), usize>,
) -> Vec<Vec<usize>> {
let n_base = base_adj.len();
let n_cover = n_base * 13;
let mut cover_adj: Vec<Vec<usize>> = vec![Vec::new(); n_cover];
for u in 0..n_base {
for &v in &base_adj[u] {
let voltage = *voltages.get(&(u, v)).unwrap_or(&0) % 13;
for i in 0..13 {
let src = u * 13 + i;
let dst = v * 13 + ((i + voltage) % 13);
if !cover_adj[src].contains(&dst) {
cover_adj[src].push(dst);
}
}
}
}
cover_adj
}
pub fn detect_c13_automorphism(adj: &[Vec<usize>]) -> (bool, Vec<Vec<usize>>) {
let n = adj.len();
if !n.is_multiple_of(13) {
return (false, Vec::new());
}
let n_base = n / 13;
let mut is_automorphism = true;
for u in 0..n_base {
for i in 0..13 {
let src = u * 13 + i;
let src_rot = u * 13 + ((i + 1) % 13);
let mut rot_neighbors: Vec<usize> = adj[src]
.iter()
.map(|&v| {
let base = v / 13;
let fiber = v % 13;
base * 13 + ((fiber + 1) % 13)
})
.collect();
rot_neighbors.sort_unstable();
let mut actual_neighbors = adj[src_rot].clone();
actual_neighbors.sort_unstable();
if rot_neighbors != actual_neighbors {
is_automorphism = false;
break;
}
}
if !is_automorphism {
break;
}
}
if !is_automorphism {
return (false, Vec::new());
}
let mut visited = vec![false; n];
let mut orbits = Vec::new();
for v in 0..n {
if visited[v] {
continue;
}
let mut orbit = Vec::new();
let base = v / 13;
let fiber = v % 13;
for k in 0..13 {
let u = base * 13 + ((fiber + k) % 13);
if !visited[u] {
visited[u] = true;
orbit.push(u);
}
}
if !orbit.is_empty() {
orbits.push(orbit);
}
}
(true, orbits)
}
pub fn check_13_cage(adj: &[Vec<usize>]) -> Option<(usize, &'static str)> {
let n = adj.len();
let deg_seq = degree_sequence(adj);
let is_regular_13 = deg_seq.iter().all(|&d| d == 13);
if !is_regular_13 {
return None;
}
if n == 14 {
for i in 0..n {
if adj[i].len() != 13 {
return None;
}
}
return Some((3, "K_14: the (13,3)-cage"));
}
if n == 26 {
let mut color = vec![None; n];
let mut is_bipartite = true;
for start in 0..n {
if color[start].is_some() {
continue;
}
color[start] = Some(0);
let mut stack = vec![start];
while let Some(u) = stack.pop() {
for &v in &adj[u] {
if color[v].is_none() {
color[v] = Some(1 - color[u].unwrap());
stack.push(v);
} else if color[v] == color[u] {
is_bipartite = false;
break;
}
}
if !is_bipartite {
break;
}
}
if !is_bipartite {
break;
}
}
if is_bipartite {
return Some((4, "K_13,13: the (13,4)-cage"));
}
}
None
}
pub fn analyze_13_symmetry_comprehensive(points: &[(f64, f64)]) -> (f64, f64, f64, f64) {
let (has_geo, chi, peak) = detect_13_fold_symmetry(points, 26.0);
let geo_score = if has_geo {
0.5 + 0.5 * peak.min(1.0)
} else {
(26.0 / (26.0 + chi)) * 0.3 };
let n = points.len();
let alg_score = if n.is_multiple_of(13) {
0.8 + 0.2 * (1.0 / (1.0 + (n / 13) as f64)) } else if n % 13 == 1 {
0.5 } else {
0.1
};
let nn_graph = build_nn_graph(points, 6);
let deg_seq = degree_sequence(&nn_graph);
let avg_deg = deg_seq.iter().sum::<usize>() as f64 / deg_seq.len() as f64;
let graph_score = if (avg_deg - 13.0).abs() < 1.0 {
0.7
} else if (avg_deg - 12.0).abs() < 2.0 {
0.4
} else {
0.1
};
let overall = (geo_score + alg_score + graph_score) / 3.0;
(geo_score, alg_score, graph_score, overall)
}
fn build_nn_graph(points: &[(f64, f64)], k: usize) -> Vec<Vec<usize>> {
let n = points.len();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
let mut dists: Vec<(f64, usize)> = Vec::with_capacity(n);
for j in 0..n {
if i == j {
continue;
}
let dx = points[i].0 - points[j].0;
let dy = points[i].1 - points[j].1;
dists.push((dx * dx + dy * dy, j));
}
dists.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
for (_, j) in dists.iter().take(k) {
adj[i].push(*j);
adj[*j].push(i);
}
}
adj
}
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
fn mod_pow(mut a: u64, mut b: u64, m: u64) -> u64 {
if m == 1 {
return 0;
}
let mut result = 1;
a %= m;
while b > 0 {
if b % 2 == 1 {
result = (result * a) % m;
}
a = (a * a) % m;
b /= 2;
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_regular_13_gon() {
let gon = regular_13_gon();
assert_eq!(gon.len(), 13);
for &(x, y) in &gon {
let r = (x * x + y * y).sqrt();
assert!((r - 1.0).abs() < 1e-10);
}
for k in 0..13 {
let rotated = c13_rotate(0, k);
assert_eq!(rotated, k % 13);
}
}
#[test]
fn test_dihedral_d13() {
let perms = dihedral_d13_permutations();
assert_eq!(perms.len(), 26);
let id: Vec<usize> = (0..13).collect();
assert_eq!(perms[0], id);
for axis in 0..13 {
let refl = &perms[13 + axis];
let mut double = vec![0; 13];
for i in 0..13 {
double[i] = refl[refl[i]];
}
assert_eq!(double, id, "reflection {} is not an involution", axis);
}
}
#[test]
fn test_star_polygon_13() {
let star1 = star_polygon_13(1).unwrap();
assert_eq!(star1, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
let star2 = star_polygon_13(2).unwrap();
assert_eq!(star2.len(), 13);
assert_eq!(star2[0], 0);
assert_eq!(star2[1], 2);
assert!(star_polygon_13(5).is_some());
assert!(star_polygon_13(0).is_none());
assert!(star_polygon_13(13).is_none());
}
#[test]
fn test_detect_13_fold_symmetry() {
let gon = regular_13_gon();
let (is_sym, chi, peak) = detect_13_fold_symmetry(&gon, 26.0);
assert!(is_sym, "regular 13-gon should have 13-fold symmetry");
assert!(chi < 26.0, "chi-squared should be low for regular polygon");
assert!(peak >= 0.5, "radial concentration should be high");
let random_pts: Vec<(f64, f64)> = (0..100)
.map(|i| (i as f64 * 0.1, (i * 7) as f64 * 0.1))
.collect();
let (is_sym_r, _, _) = detect_13_fold_symmetry(&random_pts, 26.0);
assert!(!is_sym_r, "random points should not have 13-fold symmetry");
}
#[test]
fn test_roots_of_unity_13() {
let roots = roots_of_unity_13();
assert_eq!(roots.len(), 13);
assert!((roots[0].0 - 1.0).abs() < 1e-10);
assert!(roots[0].1.abs() < 1e-10);
let sum_r: f64 = roots.iter().map(|(r, _)| r).sum();
let sum_i: f64 = roots.iter().map(|(_, i)| i).sum();
assert!(sum_r.abs() < 1e-10);
assert!(sum_i.abs() < 1e-10);
}
#[test]
fn test_cyclotomic_13() {
assert!((cyclotomic_13(1.0) - 13.0).abs() < 1e-10);
assert!((cyclotomic_13(0.0) - 1.0).abs() < 1e-10);
let theta = 2.0 * std::f64::consts::PI / 13.0;
let zeta_r = theta.cos();
let zeta_i = theta.sin();
let mut sum_r = 1.0;
let mut sum_i = 0.0;
let mut zr = 1.0;
let mut zi = 0.0;
for _ in 0..12 {
let new_r = zr * zeta_r - zi * zeta_i;
let new_i = zr * zeta_i + zi * zeta_r;
zr = new_r;
zi = new_i;
sum_r += zr;
sum_i += zi;
}
let mag = (sum_r * sum_r + sum_i * sum_i).sqrt();
assert!(mag < 1e-10, "|Φ(ζ)| = {} should be ~0", mag);
}
#[test]
fn test_real_subfield_basis_13() {
let basis = real_subfield_basis_13();
assert_eq!(basis.len(), 6);
for &eta in &basis {
assert!(eta > -2.0 && eta < 2.0);
}
let sum: f64 = basis.iter().sum();
assert!((sum + 1.0).abs() < 1e-10);
}
#[test]
fn test_gaussian_periods_cubic_13() {
let (eta0, eta1, eta2) = gaussian_periods_cubic_13();
let sum = eta0 + eta1 + eta2;
assert!((sum + 1.0).abs() < 0.5); }
#[test]
fn test_cusp_form_gamma0_13() {
let f = cusp_form_gamma0_13(0.5, 20);
assert!(f.is_finite());
assert!(f > 0.0); }
#[test]
fn test_hauptmodul_x0_13() {
let t = hauptmodul_x0_13(0.01, 5);
assert!(t.is_finite());
assert!(t > 0.0); }
#[test]
fn test_power_residue_symbol_13() {
let sym = power_residue_symbol_13(2, 53);
assert!(sym == 1 || sym == -1);
assert_eq!(power_residue_symbol_13(2, 17), -1);
}
#[test]
fn test_circulant_graph_13() {
let cycle = circulant_graph_13(&[1]);
assert_eq!(cycle.len(), 13);
for i in 0..13 {
assert_eq!(cycle[i].len(), 2); }
let g = circulant_graph_13(&[1, 3]);
for i in 0..13 {
assert_eq!(g[i].len(), 4);
}
}
#[test]
fn test_paley_graph_13() {
let paley = paley_graph_13();
assert_eq!(paley.len(), 13);
for i in 0..13 {
let mut unique = paley[i].clone();
unique.sort_unstable();
unique.dedup();
assert_eq!(unique.len(), 6, "vertex {} has wrong degree", i);
}
}
#[test]
fn test_is_13_regular() {
let mut k14: Vec<Vec<usize>> = vec![Vec::new(); 14];
for i in 0..14 {
for j in 0..14 {
if i != j {
k14[i].push(j);
}
}
}
assert!(is_13_regular(&k14));
let c13 = circulant_graph_13(&[1]);
assert!(!is_13_regular(&c13));
}
#[test]
fn test_graph_cover_13() {
let base = vec![vec![1], vec![0]];
let mut voltages = HashMap::new();
voltages.insert((0, 1), 1usize);
voltages.insert((1, 0), 12usize);
let cover = graph_cover_13(&base, &voltages);
assert_eq!(cover.len(), 26);
assert_eq!(cover[0].len(), 1);
assert_eq!(cover[13].len(), 1);
}
#[test]
fn test_detect_c13_automorphism() {
let c13 = circulant_graph_13(&[1]);
let (has_c13, orbits) = detect_c13_automorphism(&c13);
assert!(has_c13);
assert_eq!(orbits.len(), 1); assert_eq!(orbits[0].len(), 13);
}
#[test]
fn test_check_13_cage() {
let mut k14: Vec<Vec<usize>> = vec![Vec::new(); 14];
for i in 0..14 {
for j in 0..14 {
if i != j {
k14[i].push(j);
}
}
}
let cage = check_13_cage(&k14);
assert!(cage.is_some());
let (girth, desc) = cage.unwrap();
assert_eq!(girth, 3);
assert!(desc.contains("K_14"));
let c13 = circulant_graph_13(&[1]);
assert!(check_13_cage(&c13).is_none());
}
#[test]
fn test_comprehensive_analysis() {
let gon = regular_13_gon();
let (_geo, _alg, _graph, overall) = analyze_13_symmetry_comprehensive(&gon);
assert!(
overall > 0.5,
"regular 13-gon should have high 13-symmetry score"
);
let random: Vec<(f64, f64)> = (0..50)
.map(|i| ((i * 17) as f64 * 0.1, (i * 31) as f64 * 0.1))
.collect();
let (_, _, _, overall_r) = analyze_13_symmetry_comprehensive(&random);
assert!(overall_r < 0.5, "random points should have low score");
}
}