fso_graph/
triangle_count.rs1use crate::{prelude::*, DEFAULT_PARALLELISM};
2
3use log::info;
4use num_format::{Locale, ToFormattedString};
5
6use std::sync::atomic::AtomicU64;
7use std::thread::available_parallelism;
8use std::{sync::atomic::Ordering, time::Instant};
9
10const CHUNK_SIZE: usize = 64;
11
12pub fn relabel_graph<NI, G, EV>(graph: &mut G)
13where
14 NI: Idx,
15 G: RelabelByDegreeOp<NI, EV>,
16{
17 let start = Instant::now();
18 graph.make_degree_ordered();
19 info!("Relabeled graph in {:?}", start.elapsed());
20}
21
22pub fn global_triangle_count<NI, G>(graph: &G) -> u64
23where
24 NI: Idx,
25 G: Graph<NI> + UndirectedNeighbors<NI> + Sync,
26{
27 let start = Instant::now();
28
29 let next_chunk = Atomic::new(NI::zero());
30 let total_triangles = AtomicU64::new(0);
31
32 std::thread::scope(|s| {
33 let num_threads = available_parallelism().map_or(DEFAULT_PARALLELISM, |p| p.get());
34
35 for _ in 0..num_threads {
36 s.spawn(|| {
37 let mut triangles = 0;
38
39 loop {
40 let start = NI::fetch_add(&next_chunk, NI::new(CHUNK_SIZE), Ordering::AcqRel);
41 if start >= graph.node_count() {
42 break;
43 }
44
45 let end = (start + NI::new(CHUNK_SIZE)).min(graph.node_count());
46
47 for u in start.range(end) {
48 for &v in graph.neighbors(u) {
49 if v > u {
50 break;
51 }
52
53 let mut it = put_back_iterator(graph.neighbors(u));
54
55 for &w in graph.neighbors(v) {
56 if w > v {
57 break;
58 }
59 while let Some(x) = it.next() {
60 if x >= &w {
61 if x == &w {
62 triangles += 1;
63 }
64 it.put_back(x);
65 break;
66 }
67 }
68 }
69 }
70 }
71 }
72 total_triangles.fetch_add(triangles, Ordering::AcqRel);
73 });
74 }
75 });
76
77 let tc = total_triangles.load(Ordering::SeqCst);
78
79 info!(
80 "Computed {} triangles in {:?}",
81 tc.to_formatted_string(&Locale::en),
82 start.elapsed()
83 );
84
85 tc
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::prelude::{CsrLayout, GraphBuilder, UndirectedCsrGraph};
92
93 #[test]
94 fn test_tc_two_components() {
95 let gdl = "(a)-->()-->()<--(a),(b)-->()-->()<--(b)";
96
97 let graph: UndirectedCsrGraph<usize> = GraphBuilder::new()
98 .csr_layout(CsrLayout::Deduplicated)
99 .gdl_str::<usize, _>(gdl)
100 .build()
101 .unwrap();
102
103 assert_eq!(global_triangle_count(&graph), 2);
104 }
105
106 #[test]
107 fn test_tc_connected_triangles() {
108 let gdl = "(a)-->()-->()<--(a),(a)-->()-->()<--(a)";
109
110 let graph: UndirectedCsrGraph<usize> = GraphBuilder::new()
111 .csr_layout(CsrLayout::Deduplicated)
112 .gdl_str::<usize, _>(gdl)
113 .build()
114 .unwrap();
115
116 assert_eq!(global_triangle_count(&graph), 2);
117 }
118
119 #[test]
120 fn test_tc_diamond() {
121 let gdl = "(a)-->(b)-->(c)<--(a),(b)-->(d)<--(c)";
122
123 let graph: UndirectedCsrGraph<usize> = GraphBuilder::new()
124 .csr_layout(CsrLayout::Deduplicated)
125 .gdl_str::<usize, _>(gdl)
126 .build()
127 .unwrap();
128
129 assert_eq!(global_triangle_count(&graph), 2);
130 }
131}