1use crate::graph::Graph;
2use crate::sheaf::SheafConfig;
3use crate::flow::FlowFn;
4use crate::spectrum::compute_spectrum;
5
6#[derive(Debug, Clone)]
10pub struct StabilityMap {
11 pub alphas: Vec<f64>,
13 pub times: Vec<f64>,
15 pub gap_values: Vec<f64>,
17}
18
19impl StabilityMap {
20 pub fn n_alphas(&self) -> usize {
21 self.alphas.len()
22 }
23
24 pub fn n_times(&self) -> usize {
25 self.times.len()
26 }
27
28 pub fn get(&self, alpha_idx: usize, time_idx: usize) -> f64 {
30 self.gap_values[alpha_idx * self.n_times() + time_idx]
31 }
32
33 pub fn row_variation(&self) -> Vec<f64> {
35 let n_t = self.n_times();
36 self.alphas.iter().enumerate().map(|(i, _)| {
37 let base = self.get(i, 0);
38 (1..n_t).map(|j| (self.get(i, j) - base).abs()).sum()
39 }).collect()
40 }
41}
42
43pub fn stability_map(
54 g: &Graph,
55 base_cfg: &SheafConfig,
56 flow: FlowFn,
57 alphas: &[f64],
58 times: &[f64],
59) -> StabilityMap {
60 let n_alphas = alphas.len();
61 let n_times = times.len();
62 let mut gap_values = vec![0.0; n_alphas * n_times];
63
64 for (i, &alpha) in alphas.iter().enumerate() {
65 let mut cfg = *base_cfg;
66 cfg.alpha = alpha;
67 for (j, &t) in times.iter().enumerate() {
68 let sp = compute_spectrum(g, &cfg, flow, t);
69 gap_values[i * n_times + j] = sp.lambda1;
70 }
71 }
72
73 StabilityMap {
74 alphas: alphas.to_vec(),
75 times: times.to_vec(),
76 gap_values,
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::graph::Graph;
84 use crate::flow::*;
85 use crate::sheaf::SheafConfig;
86
87 #[test]
88 fn test_stability_map_basic() {
89 let g = Graph::cycle(4);
90 let cfg = SheafConfig::linear(1.0, 0.0);
91
92 let alphas = vec![0.0, 0.5, 1.0];
93 let times = vec![0.0, 5.0, 10.0];
94
95 let sm = stability_map(&g, &cfg, flow_sinusoidal, &alphas, ×);
96
97 assert_eq!(sm.n_alphas(), 3);
98 assert_eq!(sm.n_times(), 3);
99 assert_eq!(sm.gap_values.len(), 9);
100
101 assert!((sm.get(0, 0) - sm.get(0, 1)).abs() < 0.01);
103 assert!((sm.get(0, 0) - sm.get(0, 2)).abs() < 0.01);
104
105 let any_diff = (0..3).any(|j| (sm.get(2, j) - sm.get(0, 0)).abs() > 0.01);
107 assert!(any_diff, "larger α changes gap");
108 }
109
110 #[test]
111 fn test_stability_map_increasing_alpha() {
112 let g = Graph::cycle(4);
113 let cfg = SheafConfig::linear(1.0, 0.0);
114
115 let alphas = vec![0.0, 2.0];
116 let times = vec![0.0, 3.0, 6.0, 9.0];
117
118 let sm = stability_map(&g, &cfg, flow_sinusoidal, &alphas, ×);
119
120 let var0 = (1..4).map(|j| (sm.get(0, j) - sm.get(0, 0)).abs()).sum::<f64>();
121 let var1 = (1..4).map(|j| (sm.get(1, j) - sm.get(1, 0)).abs()).sum::<f64>();
122
123 assert!(var0 < 0.01, "α=0: no variation");
124 assert!(var1 > var0, "α=2: more variation than α=0");
125 }
126
127 #[test]
128 fn test_stability_map_grid_dims() {
129 let g = Graph::cycle(4);
130 let cfg = SheafConfig::static_sheaf(1.0);
131
132 let alphas: Vec<f64> = (0..5).map(|i| i as f64 * 0.5).collect();
133 let times: Vec<f64> = (0..10).map(|i| i as f64).collect();
134
135 let sm = stability_map(&g, &cfg, flow_sinusoidal, &alphas, ×);
136 assert_eq!(sm.n_alphas(), 5);
137 assert_eq!(sm.n_times(), 10);
138 assert_eq!(sm.gap_values.len(), 50);
139 }
140
141 #[test]
142 fn test_stability_row_variation() {
143 let g = Graph::cycle(4);
144 let cfg = SheafConfig::linear(1.0, 0.0);
145 let alphas = vec![0.0, 1.0];
146 let times = vec![0.0, 2.0, 4.0, 6.0, 8.0];
147 let sm = stability_map(&g, &cfg, flow_sinusoidal, &alphas, ×);
148
149 let vars = sm.row_variation();
150 assert_eq!(vars.len(), 2);
151 assert!(vars[1] > vars[0], "larger alpha → more row variation");
152 }
153
154 #[test]
155 fn test_stability_map_with_constant_flow() {
156 let g = Graph::cycle(4);
157 let cfg = SheafConfig::linear(1.0, 0.5);
158 let alphas = vec![0.0, 1.0];
159 let times = vec![0.0, 5.0, 10.0];
160 let sm = stability_map(&g, &cfg, flow_constant, &alphas, ×);
161
162 assert!((sm.get(0, 0) - sm.get(0, 1)).abs() < 0.001);
164 assert!((sm.get(0, 0) - sm.get(0, 2)).abs() < 0.001);
165 assert!((sm.get(1, 0) - sm.get(1, 1)).abs() < 0.001);
166 assert!((sm.get(1, 0) - sm.get(1, 2)).abs() < 0.001);
167 }
168
169 #[test]
170 fn test_stability_map_all_positive() {
171 let g = Graph::cycle(5);
172 let cfg = SheafConfig::linear(1.0, 0.0);
173 let alphas = vec![0.0, 0.5, 1.0, 2.0];
174 let times = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
175 let sm = stability_map(&g, &cfg, flow_sinusoidal, &alphas, ×);
176
177 for &val in &sm.gap_values {
179 assert!(val > 0.0, "all gaps positive: got {}", val);
180 }
181 }
182}