1#![allow(
9 clippy::cast_possible_truncation,
10 clippy::cast_precision_loss,
11 clippy::many_single_char_names,
12 clippy::needless_range_loop,
13 clippy::too_many_lines
14)]
15
16use crate::algorithms::paths::radii::{EccMode, eccentricity_with_mode};
17use crate::core::{Graph, IgraphResult, VertexId};
18
19pub fn graph_periphery(graph: &Graph, mode: EccMode) -> IgraphResult<Vec<VertexId>> {
38 let n = graph.vcount();
39 if n == 0 {
40 return Ok(Vec::new());
41 }
42
43 let ecc = eccentricity_with_mode(graph, mode)?;
44
45 let max_ecc = ecc.iter().copied().max().unwrap_or(0);
46
47 let periphery: Vec<VertexId> = ecc
48 .iter()
49 .enumerate()
50 .filter(|(_, e)| **e == max_ecc)
51 .map(|(i, _)| i as VertexId)
52 .collect();
53
54 Ok(periphery)
55}
56
57pub fn eccentricity_classes(graph: &Graph, mode: EccMode) -> IgraphResult<EccentricityClasses> {
75 let n = graph.vcount();
76 if n == 0 {
77 return Ok(EccentricityClasses {
78 eccentricities: Vec::new(),
79 center: Vec::new(),
80 periphery: Vec::new(),
81 radius: 0,
82 diameter: 0,
83 });
84 }
85
86 let ecc = eccentricity_with_mode(graph, mode)?;
87
88 let min_ecc = ecc.iter().copied().min().unwrap_or(0);
89 let max_ecc = ecc.iter().copied().max().unwrap_or(0);
90
91 let center: Vec<VertexId> = ecc
92 .iter()
93 .enumerate()
94 .filter(|(_, e)| **e == min_ecc)
95 .map(|(i, _)| i as VertexId)
96 .collect();
97
98 let periphery: Vec<VertexId> = ecc
99 .iter()
100 .enumerate()
101 .filter(|(_, e)| **e == max_ecc)
102 .map(|(i, _)| i as VertexId)
103 .collect();
104
105 Ok(EccentricityClasses {
106 eccentricities: ecc,
107 center,
108 periphery,
109 radius: min_ecc,
110 diameter: max_ecc,
111 })
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct EccentricityClasses {
117 pub eccentricities: Vec<u32>,
119 pub center: Vec<VertexId>,
121 pub periphery: Vec<VertexId>,
123 pub radius: u32,
125 pub diameter: u32,
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn path5() -> Graph {
134 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
135 }
136
137 fn path6() -> Graph {
138 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], false, Some(6)).unwrap()
139 }
140
141 fn k4() -> Graph {
142 Graph::from_edges(
143 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
144 false,
145 Some(4),
146 )
147 .unwrap()
148 }
149
150 fn cycle5() -> Graph {
151 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
152 }
153
154 fn star5() -> Graph {
155 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
156 }
157
158 #[test]
161 fn gp_empty() {
162 let g = Graph::with_vertices(0);
163 assert!(graph_periphery(&g, EccMode::All).unwrap().is_empty());
164 }
165
166 #[test]
167 fn gp_single() {
168 let g = Graph::with_vertices(1);
169 assert_eq!(graph_periphery(&g, EccMode::All).unwrap(), vec![0]);
170 }
171
172 #[test]
173 fn gp_path5() {
174 let peri = graph_periphery(&path5(), EccMode::All).unwrap();
176 assert_eq!(peri, vec![0, 4]);
177 }
178
179 #[test]
180 fn gp_path6() {
181 let peri = graph_periphery(&path6(), EccMode::All).unwrap();
182 assert_eq!(peri, vec![0, 5]);
183 }
184
185 #[test]
186 fn gp_k4() {
187 let peri = graph_periphery(&k4(), EccMode::All).unwrap();
189 assert_eq!(peri, vec![0, 1, 2, 3]);
190 }
191
192 #[test]
193 fn gp_cycle5() {
194 let peri = graph_periphery(&cycle5(), EccMode::All).unwrap();
196 assert_eq!(peri, vec![0, 1, 2, 3, 4]);
197 }
198
199 #[test]
200 fn gp_star5() {
201 let peri = graph_periphery(&star5(), EccMode::All).unwrap();
203 assert_eq!(peri, vec![1, 2, 3, 4]);
204 }
205
206 #[test]
207 fn gp_no_edges() {
208 let g = Graph::with_vertices(3);
210 let peri = graph_periphery(&g, EccMode::All).unwrap();
211 assert_eq!(peri, vec![0, 1, 2]);
212 }
213
214 #[test]
217 fn ec_path5() {
218 let ec = eccentricity_classes(&path5(), EccMode::All).unwrap();
219 assert_eq!(ec.radius, 2);
220 assert_eq!(ec.diameter, 4);
221 assert_eq!(ec.center, vec![2]);
222 assert_eq!(ec.periphery, vec![0, 4]);
223 assert_eq!(ec.eccentricities, vec![4, 3, 2, 3, 4]);
224 }
225
226 #[test]
227 fn ec_k4() {
228 let ec = eccentricity_classes(&k4(), EccMode::All).unwrap();
229 assert_eq!(ec.radius, 1);
230 assert_eq!(ec.diameter, 1);
231 assert_eq!(ec.center, vec![0, 1, 2, 3]);
232 assert_eq!(ec.periphery, vec![0, 1, 2, 3]);
233 }
234
235 #[test]
236 fn ec_star5() {
237 let ec = eccentricity_classes(&star5(), EccMode::All).unwrap();
238 assert_eq!(ec.radius, 1);
239 assert_eq!(ec.diameter, 2);
240 assert_eq!(ec.center, vec![0]);
241 assert_eq!(ec.periphery, vec![1, 2, 3, 4]);
242 }
243
244 #[test]
245 fn ec_empty() {
246 let g = Graph::with_vertices(0);
247 let ec = eccentricity_classes(&g, EccMode::All).unwrap();
248 assert_eq!(ec.radius, 0);
249 assert_eq!(ec.diameter, 0);
250 assert!(ec.center.is_empty());
251 assert!(ec.periphery.is_empty());
252 }
253
254 #[test]
255 fn ec_single() {
256 let g = Graph::with_vertices(1);
257 let ec = eccentricity_classes(&g, EccMode::All).unwrap();
258 assert_eq!(ec.radius, 0);
259 assert_eq!(ec.diameter, 0);
260 assert_eq!(ec.center, vec![0]);
261 assert_eq!(ec.periphery, vec![0]);
262 }
263
264 #[test]
267 fn center_and_periphery_cover_self_regular() {
268 let ec = eccentricity_classes(&cycle5(), EccMode::All).unwrap();
270 assert_eq!(ec.center, ec.periphery);
271 }
272
273 #[test]
274 fn radius_leq_diameter() {
275 for g in &[path5(), path6(), k4(), cycle5(), star5()] {
276 let ec = eccentricity_classes(g, EccMode::All).unwrap();
277 assert!(ec.radius <= ec.diameter);
278 }
279 }
280
281 #[test]
282 fn diameter_leq_2_radius() {
283 for g in &[path5(), path6(), k4(), cycle5(), star5()] {
285 let ec = eccentricity_classes(g, EccMode::All).unwrap();
286 assert!(ec.diameter <= 2 * ec.radius);
287 }
288 }
289
290 #[test]
291 fn center_periphery_partition() {
292 let ec = eccentricity_classes(&path5(), EccMode::All).unwrap();
294 let center_set: std::collections::HashSet<_> = ec.center.iter().collect();
295 let peri_set: std::collections::HashSet<_> = ec.periphery.iter().collect();
296 let overlap: Vec<_> = center_set.intersection(&peri_set).collect();
297 if ec.radius == ec.diameter {
298 assert!(!overlap.is_empty());
299 } else {
300 assert!(overlap.is_empty());
301 }
302 }
303}