1use std::collections::VecDeque;
13
14use crate::algorithms::connectivity::separators::minimum_size_separators;
15use crate::algorithms::constructors::create::create;
16use crate::algorithms::flow::vertex_connectivity::vertex_connectivity;
17use crate::algorithms::operators::induced_subgraph::induced_subgraph;
18use crate::algorithms::properties::degree::{DegreeMode, max_degree};
19use crate::algorithms::properties::is_simple::is_simple;
20use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
21
22#[derive(Debug, Clone)]
28pub struct CohesiveBlocks {
29 pub blocks: Vec<Vec<VertexId>>,
32 pub cohesion: Vec<i64>,
34 pub parent: Vec<i64>,
37 pub block_tree: Graph,
40}
41
42fn cb_components(graph: &Graph, excluded: &[bool]) -> IgraphResult<Vec<Vec<VertexId>>> {
50 let n = graph.vcount() as usize;
51 let mut compid = vec![0usize; n];
52 let mut comps: Vec<Vec<VertexId>> = Vec::new();
53 let mut cno = 0usize;
54 let mut q: VecDeque<VertexId> = VecDeque::new();
55
56 for i in 0..n {
57 if compid[i] != 0 || excluded[i] {
58 continue;
59 }
60 cno += 1;
61 let mut comp: Vec<VertexId> = Vec::new();
62 let start = u32::try_from(i)
63 .map_err(|_| IgraphError::InvalidArgument("cohesive_blocks: vertex overflow".into()))?;
64 q.push_back(start);
65 comp.push(start);
66 compid[i] = cno;
67
68 while let Some(node) = q.pop_front() {
69 for v in graph.neighbors_iter(node)? {
70 let vi = v as usize;
71 if excluded[vi] {
72 if compid[vi] != cno {
73 compid[vi] = cno;
74 comp.push(v);
75 }
76 } else if compid[vi] == 0 {
77 compid[vi] = cno;
78 comp.push(v);
79 q.push_back(v);
80 }
81 }
82 }
83 comps.push(comp);
84 }
85
86 Ok(comps)
87}
88
89fn cb_isin(needle: &[VertexId], haystack: &[VertexId]) -> bool {
93 if haystack.len() < needle.len() {
94 return false;
95 }
96 let mut np = 0;
97 let mut hp = 0;
98 while np < needle.len() && hp < haystack.len() {
99 match needle[np].cmp(&haystack[hp]) {
100 std::cmp::Ordering::Equal => {
101 np += 1;
102 hp += 1;
103 }
104 std::cmp::Ordering::Less => return false,
105 std::cmp::Ordering::Greater => hp += 1,
106 }
107 }
108 np == needle.len()
109}
110
111fn qidx(p: i64) -> IgraphResult<usize> {
113 usize::try_from(p)
114 .map_err(|_| IgraphError::InvalidArgument("cohesive_blocks: negative queue index".into()))
115}
116
117#[allow(clippy::too_many_lines)]
157pub fn cohesive_blocks(graph: &Graph) -> IgraphResult<CohesiveBlocks> {
158 if graph.is_directed() {
159 return Err(IgraphError::InvalidArgument(
160 "cohesive_blocks: only works on undirected graphs".into(),
161 ));
162 }
163 if !is_simple(graph)? {
164 return Err(IgraphError::InvalidArgument(
165 "cohesive_blocks: only works on simple graphs".into(),
166 ));
167 }
168
169 let mut q_graph: Vec<Option<Graph>> = Vec::new();
176 let mut q_mapping: Vec<Vec<VertexId>> = Vec::new();
177 let mut q_parent: Vec<i64> = Vec::new();
178 let mut q_cohesion: Vec<i64> = Vec::new();
179 let mut q_check: Vec<bool> = Vec::new();
180
181 q_graph.push(Some(graph.clone()));
182 q_mapping.push(Vec::new());
183 q_parent.push(-1);
184 q_cohesion.push(vertex_connectivity(graph, true)?);
185 q_check.push(false);
186
187 let mut qptr = 0usize;
188 while qptr < q_graph.len() {
189 let mygraph = q_graph[qptr]
190 .take()
191 .ok_or_else(|| IgraphError::InvalidArgument("cohesive_blocks: queue slot".into()))?;
192 let mycheck = q_check[qptr];
193 let my_cohesion = q_cohesion[qptr];
194 let mynodes = mygraph.vcount() as usize;
195
196 let separators = minimum_size_separators(&mygraph)?;
198
199 let mut marked = vec![false; mynodes];
201 let mut nsepv = 0usize;
202 for sep in &separators {
203 for &vv in sep {
204 let vi = vv as usize;
205 if !marked[vi] {
206 nsepv += 1;
207 marked[vi] = true;
208 }
209 }
210 }
211
212 let mut components = cb_components(&mygraph, &marked)?;
215
216 let addedsep = nsepv != mynodes;
219 if addedsep {
220 let sep_comp: Vec<VertexId> = (0..mynodes)
221 .filter(|&i| marked[i])
222 .map(u32::try_from)
223 .collect::<Result<Vec<_>, _>>()
224 .map_err(|_| {
225 IgraphError::InvalidArgument("cohesive_blocks: vertex overflow".into())
226 })?;
227 components.push(sep_comp);
228 }
229
230 for compvertices in components {
231 let sub = induced_subgraph(&mygraph, &compvertices)?;
232 let maxdeg = i64::from(max_degree(&sub.graph, DegreeMode::All)?);
233 if maxdeg > my_cohesion {
234 let newconn = vertex_connectivity(&sub.graph, true)?;
235 q_graph.push(Some(sub.graph));
236 q_mapping.push(sub.invmap);
237 q_cohesion.push(newconn);
238 q_parent.push(i64::try_from(qptr).map_err(|_| {
239 IgraphError::InvalidArgument("cohesive_blocks: queue overflow".into())
240 })?);
241 q_check.push(mycheck || addedsep);
242 }
243 }
244
245 qptr += 1;
246 }
247
248 let noblocks_full = qptr;
249 let mut removed = vec![false; noblocks_full];
250 let mut rewritemap = vec![0i64; noblocks_full];
251 let mut badblocks = 0usize;
252
253 for i in 1..noblocks_full {
255 let mut p = qidx(q_parent[i])?;
256 while removed[p] {
257 p = qidx(q_parent[p])?;
258 }
259 if q_cohesion[p] >= q_cohesion[i] {
260 removed[i] = true;
261 badblocks += 1;
262 }
263 }
264
265 for i in 1..noblocks_full {
270 let p = qidx(q_parent[i])?;
271 if p == 0 {
272 continue;
273 }
274 let pmapping = q_mapping[p].clone();
275 for v in &mut q_mapping[i] {
276 *v = pmapping[*v as usize];
277 }
278 }
279
280 for i in 1..noblocks_full {
284 if !q_check[i] || removed[i] {
285 continue;
286 }
287 let ic = q_cohesion[i];
288 for j in 1..noblocks_full {
289 if j == i || !q_check[j] || removed[j] {
290 continue;
291 }
292 if q_cohesion[j] >= ic && cb_isin(&q_mapping[i], &q_mapping[j]) {
293 badblocks += 1;
294 removed[i] = true;
295 break;
296 }
297 }
298 }
299
300 let noblocks = noblocks_full - badblocks;
301
302 let mut blocks: Vec<Vec<VertexId>> = vec![Vec::new(); noblocks];
303 let mut cohesion = vec![0i64; noblocks];
304 let mut parent = vec![0i64; noblocks];
305
306 let mut resptr = 0usize;
307 for i in 0..noblocks_full {
308 if removed[i] {
309 continue;
310 }
311 rewritemap[i] = i64::try_from(resptr)
312 .map_err(|_| IgraphError::InvalidArgument("cohesive_blocks: block overflow".into()))?;
313 cohesion[resptr] = q_cohesion[i];
314
315 let mut p = q_parent[i];
316 while p >= 0 && removed[qidx(p)?] {
317 p = q_parent[qidx(p)?];
318 }
319 if p >= 0 {
320 p = rewritemap[qidx(p)?];
321 }
322 q_parent[i] = p;
323 parent[resptr] = p;
324
325 blocks[resptr] = std::mem::take(&mut q_mapping[i]);
326 resptr += 1;
327 }
328
329 blocks[0] = (0..graph.vcount()).collect();
331
332 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(noblocks.saturating_sub(1));
334 for i in 1..noblocks_full {
335 if removed[i] {
336 continue;
337 }
338 let p = u32::try_from(q_parent[i])
339 .map_err(|_| IgraphError::InvalidArgument("cohesive_blocks: tree parent".into()))?;
340 let c = u32::try_from(rewritemap[i])
341 .map_err(|_| IgraphError::InvalidArgument("cohesive_blocks: tree child".into()))?;
342 edges.push((p, c));
343 }
344 let block_tree = create(&edges, u32::try_from(noblocks).unwrap_or(0), true)?;
345
346 Ok(CohesiveBlocks {
347 blocks,
348 cohesion,
349 parent,
350 block_tree,
351 })
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 fn ug(n: u32, edges: &[(u32, u32)]) -> Graph {
359 let mut g = Graph::with_vertices(n);
360 for &(a, b) in edges {
361 g.add_edge(a, b).expect("edge in range");
362 }
363 g
364 }
365
366 fn canon(cb: &CohesiveBlocks) -> Vec<(Vec<VertexId>, i64)> {
369 let mut v: Vec<(Vec<VertexId>, i64)> = cb
370 .blocks
371 .iter()
372 .zip(&cb.cohesion)
373 .map(|(b, &c)| {
374 let mut bb = b.clone();
375 bb.sort_unstable();
376 (bb, c)
377 })
378 .collect();
379 v.sort();
380 v
381 }
382
383 #[test]
384 fn rejects_directed() {
385 let mut g = Graph::new(3, true).expect("graph");
386 g.add_edge(0, 1).expect("edge");
387 assert!(cohesive_blocks(&g).is_err());
388 }
389
390 #[test]
391 fn rejects_non_simple() {
392 let mut g = Graph::with_vertices(3);
393 g.add_edge(0, 1).expect("edge");
394 g.add_edge(0, 1).expect("edge"); assert!(cohesive_blocks(&g).is_err());
396 }
397
398 #[test]
399 fn single_block_clique() {
400 let g = ug(4, &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]);
402 let cb = cohesive_blocks(&g).expect("cohesive_blocks");
403 assert_eq!(cb.blocks.len(), 1);
404 assert_eq!(cb.blocks[0], vec![0, 1, 2, 3]);
405 assert_eq!(cb.cohesion, vec![3]);
406 assert_eq!(cb.parent, vec![-1]);
407 }
408
409 #[test]
410 fn moody_white() {
411 let g = ug(
413 23,
414 &[
415 (0, 1),
416 (0, 2),
417 (0, 3),
418 (0, 4),
419 (0, 5),
420 (1, 2),
421 (1, 3),
422 (1, 4),
423 (1, 6),
424 (2, 3),
425 (2, 5),
426 (2, 6),
427 (3, 4),
428 (3, 5),
429 (3, 6),
430 (4, 5),
431 (4, 6),
432 (4, 20),
433 (5, 6),
434 (6, 7),
435 (6, 10),
436 (6, 13),
437 (6, 18),
438 (7, 8),
439 (7, 10),
440 (7, 13),
441 (8, 9),
442 (9, 11),
443 (9, 12),
444 (10, 11),
445 (10, 13),
446 (11, 15),
447 (12, 15),
448 (13, 14),
449 (14, 15),
450 (16, 17),
451 (16, 18),
452 (16, 19),
453 (17, 19),
454 (17, 20),
455 (18, 19),
456 (18, 21),
457 (18, 22),
458 (19, 20),
459 (20, 21),
460 (20, 22),
461 (21, 22),
462 ],
463 );
464 let cb = cohesive_blocks(&g).expect("cohesive_blocks");
465 let got = canon(&cb);
466 let mut expected = vec![
467 ((0..23).collect::<Vec<_>>(), 1),
468 (vec![0, 1, 2, 3, 4, 5, 6, 16, 17, 18, 19, 20, 21, 22], 2),
469 (vec![6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 2),
470 (vec![0, 1, 2, 3, 4, 5, 6], 5),
471 (vec![6, 7, 10, 13], 3),
472 ];
473 expected.sort();
474 assert_eq!(got, expected);
475 }
476
477 #[test]
478 fn tricky_separators_form_a_block() {
479 let g = ug(
480 8,
481 &[
482 (0, 1),
483 (0, 4),
484 (0, 5),
485 (1, 2),
486 (1, 4),
487 (1, 5),
488 (1, 6),
489 (2, 3),
490 (2, 5),
491 (2, 6),
492 (2, 7),
493 (3, 6),
494 (3, 7),
495 (4, 5),
496 (5, 6),
497 (6, 7),
498 ],
499 );
500 let cb = cohesive_blocks(&g).expect("cohesive_blocks");
501 let got = canon(&cb);
502 let mut expected = vec![
503 ((0..8).collect::<Vec<_>>(), 2),
504 (vec![0, 1, 4, 5], 3),
505 (vec![2, 3, 6, 7], 3),
506 (vec![1, 2, 5, 6], 3),
507 ];
508 expected.sort();
509 assert_eq!(got, expected);
510 }
511
512 #[test]
513 fn science_camp() {
514 let g = ug(
515 18,
516 &[
517 (0, 1),
518 (0, 2),
519 (0, 3),
520 (1, 2),
521 (1, 3),
522 (1, 16),
523 (1, 17),
524 (2, 3),
525 (3, 17),
526 (4, 5),
527 (4, 6),
528 (4, 7),
529 (4, 8),
530 (5, 6),
531 (5, 7),
532 (6, 7),
533 (6, 8),
534 (7, 8),
535 (7, 16),
536 (8, 9),
537 (8, 10),
538 (9, 11),
539 (9, 12),
540 (9, 13),
541 (9, 14),
542 (10, 11),
543 (10, 12),
544 (10, 13),
545 (11, 14),
546 (12, 13),
547 (12, 14),
548 (12, 15),
549 (15, 16),
550 (15, 17),
551 (16, 17),
552 ],
553 );
554 let cb = cohesive_blocks(&g).expect("cohesive_blocks");
555 let got = canon(&cb);
556 let mut expected = vec![
557 ((0..18).collect::<Vec<_>>(), 2),
558 (vec![0, 1, 2, 3], 3),
559 (vec![4, 5, 6, 7, 8], 3),
560 (vec![9, 10, 11, 12, 13, 14], 3),
561 ];
562 expected.sort();
563 assert_eq!(got, expected);
564 }
565
566 #[test]
567 fn root_is_whole_graph_and_tree_consistent() {
568 let g = ug(
569 6,
570 &[
571 (0, 1),
572 (0, 2),
573 (0, 3),
574 (1, 2),
575 (1, 3),
576 (2, 3),
577 (3, 4),
578 (4, 5),
579 ],
580 );
581 let cb = cohesive_blocks(&g).expect("cohesive_blocks");
582 assert_eq!(cb.blocks[0], (0..6).collect::<Vec<_>>());
583 assert_eq!(cb.parent[0], -1);
584 assert_eq!(cb.block_tree.vcount() as usize, cb.blocks.len());
586 let non_root = cb.parent.iter().filter(|&&p| p >= 0).count();
587 assert_eq!(cb.block_tree.ecount() as usize, non_root);
588 }
589}