1use std::f64::consts::PI;
4
5pub struct BasicStats {
7 pub n_nodes: usize,
8 pub n_edges: usize,
9 pub total_length: f64,
10 pub mean_length: f64,
11 pub mean_out_degree: f64,
12 pub self_loops: usize,
13}
14
15pub fn basic_stats(from: &[usize], to: &[usize], weight: &[f64], n_nodes: usize) -> BasicStats {
17 let n_edges = from.len();
18 let total_length: f64 = weight.iter().sum();
19 let mean_length = if n_edges > 0 {
20 total_length / n_edges as f64
21 } else {
22 0.0
23 };
24 let mean_out_degree = if n_nodes > 0 {
25 n_edges as f64 / n_nodes as f64
26 } else {
27 0.0
28 };
29 let self_loops = from.iter().zip(to).filter(|(u, v)| u == v).count();
30 BasicStats {
31 n_nodes,
32 n_edges,
33 total_length,
34 mean_length,
35 mean_out_degree,
36 self_loops,
37 }
38}
39
40pub fn orientation_entropy(bearings: &[f64], num_bins: usize) -> f64 {
44 if bearings.is_empty() || num_bins == 0 {
45 return 0.0;
46 }
47 let mut counts = vec![0.0f64; num_bins];
48 let bin_width = 360.0 / num_bins as f64;
49 for &b in bearings {
50 let bb = b.rem_euclid(360.0);
51 let mut idx = (bb / bin_width).floor() as usize;
52 if idx >= num_bins {
53 idx = num_bins - 1;
54 }
55 counts[idx] += 1.0;
56 }
57 let total: f64 = counts.iter().sum();
58 let mut h = 0.0;
59 for c in counts {
60 if c > 0.0 {
61 let p = c / total;
62 h -= p * p.ln();
63 }
64 }
65 h
66}
67
68pub fn bearing(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
71 let (phi1, phi2) = (lat1.to_radians(), lat2.to_radians());
72 let dlon = (lon2 - lon1).to_radians();
73 let y = dlon.sin() * phi2.cos();
74 let x = phi1.cos() * phi2.sin() - phi1.sin() * phi2.cos() * dlon.cos();
75 let theta = y.atan2(x);
76 (theta * 180.0 / PI).rem_euclid(360.0)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn perfect_grid_has_low_entropy() {
85 let grid = vec![0.0, 90.0, 180.0, 270.0, 0.0, 90.0, 180.0, 270.0];
87 let uniform = vec![0.0, 45.0, 90.0, 135.0, 180.0, 225.0, 270.0, 315.0];
88 let h_grid = orientation_entropy(&grid, 8);
89 let h_uniform = orientation_entropy(&uniform, 8);
90 assert!(h_grid < h_uniform);
91 }
92
93 #[test]
94 fn bearing_due_east_is_90() {
95 let b = bearing(0.0, 0.0, 0.0, 1.0);
96 assert!((b - 90.0).abs() < 1e-6);
97 }
98}