rust_igraph/algorithms/operators/
bipartite_projection_size.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct BipartiteProjectionSize {
15 pub vcount1: u32,
17 pub ecount1: u32,
19 pub vcount2: u32,
21 pub ecount2: u32,
23}
24
25pub fn bipartite_projection_size(
69 graph: &Graph,
70 types: &[bool],
71) -> IgraphResult<BipartiteProjectionSize> {
72 let n = graph.vcount();
73 let n_us = n as usize;
74
75 if types.len() != n_us {
76 return Err(IgraphError::InvalidArgument(format!(
77 "bipartite_projection_size: types length ({}) != vcount ({n})",
78 types.len()
79 )));
80 }
81
82 let adj = build_undirected_adj(graph, n_us)?;
83
84 let mut vc1: u32 = 0;
85 let mut ec1: u32 = 0;
86 let mut vc2: u32 = 0;
87 let mut ec2: u32 = 0;
88
89 let mut added: Vec<u32> = vec![0; n_us];
92
93 for i in 0..n_us {
94 if types[i] {
95 vc2 = vc2.saturating_add(1);
96 } else {
97 vc1 = vc1.saturating_add(1);
98 }
99
100 #[allow(clippy::cast_possible_truncation)]
101 let marker = (i as u32).wrapping_add(1);
102
103 for &nei in &adj[i] {
104 let nei_us = nei as usize;
105 if types[nei_us] == types[i] {
106 return Err(IgraphError::InvalidArgument(format!(
107 "bipartite_projection_size: non-bipartite edge found ({i}-{nei})"
108 )));
109 }
110 for &nb2 in &adj[nei_us] {
111 if nb2 as usize <= i {
112 continue;
113 }
114 if added[nb2 as usize] == marker {
115 continue;
116 }
117 added[nb2 as usize] = marker;
118 if types[i] {
119 ec2 = ec2.saturating_add(1);
120 } else {
121 ec1 = ec1.saturating_add(1);
122 }
123 }
124 }
125 }
126
127 Ok(BipartiteProjectionSize {
128 vcount1: vc1,
129 ecount1: ec1,
130 vcount2: vc2,
131 ecount2: ec2,
132 })
133}
134
135fn build_undirected_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
136 let m =
137 u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
138 let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
139 for eid in 0..m {
140 let (from, to) = graph.edge(eid)?;
141 adj[from as usize].push(to);
142 if from != to {
143 adj[to as usize].push(from);
144 }
145 }
146 Ok(adj)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn k22() {
155 let mut g = Graph::with_vertices(4);
156 g.add_edge(0, 2).unwrap();
157 g.add_edge(0, 3).unwrap();
158 g.add_edge(1, 2).unwrap();
159 g.add_edge(1, 3).unwrap();
160 let types = [false, false, true, true];
161 let sz = bipartite_projection_size(&g, &types).unwrap();
162 assert_eq!(sz.vcount1, 2);
163 assert_eq!(sz.ecount1, 1);
164 assert_eq!(sz.vcount2, 2);
165 assert_eq!(sz.ecount2, 1);
166 }
167
168 #[test]
169 fn k23() {
170 let mut g = Graph::with_vertices(5);
171 g.add_edge(0, 2).unwrap();
172 g.add_edge(0, 3).unwrap();
173 g.add_edge(0, 4).unwrap();
174 g.add_edge(1, 2).unwrap();
175 g.add_edge(1, 3).unwrap();
176 g.add_edge(1, 4).unwrap();
177 let types = [false, false, true, true, true];
178 let sz = bipartite_projection_size(&g, &types).unwrap();
179 assert_eq!(sz.vcount1, 2);
180 assert_eq!(sz.ecount1, 1);
181 assert_eq!(sz.vcount2, 3);
182 assert_eq!(sz.ecount2, 3);
183 }
184
185 #[test]
186 fn star_bipartite() {
187 let mut g = Graph::with_vertices(4);
189 g.add_edge(0, 1).unwrap();
190 g.add_edge(0, 2).unwrap();
191 g.add_edge(0, 3).unwrap();
192 let types = [false, true, true, true];
193 let sz = bipartite_projection_size(&g, &types).unwrap();
194 assert_eq!(sz.vcount1, 1);
195 assert_eq!(sz.ecount1, 0); assert_eq!(sz.vcount2, 3);
197 assert_eq!(sz.ecount2, 3); }
199
200 #[test]
201 fn no_edges() {
202 let g = Graph::with_vertices(4);
203 let types = [false, false, true, true];
204 let sz = bipartite_projection_size(&g, &types).unwrap();
205 assert_eq!(sz.vcount1, 2);
206 assert_eq!(sz.ecount1, 0);
207 assert_eq!(sz.vcount2, 2);
208 assert_eq!(sz.ecount2, 0);
209 }
210
211 #[test]
212 fn empty_graph() {
213 let g = Graph::with_vertices(0);
214 let types: [bool; 0] = [];
215 let sz = bipartite_projection_size(&g, &types).unwrap();
216 assert_eq!(sz.vcount1, 0);
217 assert_eq!(sz.ecount1, 0);
218 assert_eq!(sz.vcount2, 0);
219 assert_eq!(sz.ecount2, 0);
220 }
221
222 #[test]
223 fn types_mismatch() {
224 let g = Graph::with_vertices(3);
225 let types = [false, true];
226 assert!(bipartite_projection_size(&g, &types).is_err());
227 }
228
229 #[test]
230 fn non_bipartite_edge() {
231 let mut g = Graph::with_vertices(3);
232 g.add_edge(0, 1).unwrap();
233 let types = [false, false, true];
234 assert!(bipartite_projection_size(&g, &types).is_err());
235 }
236
237 #[test]
238 fn path_bipartite() {
239 let mut g = Graph::with_vertices(5);
241 g.add_edge(0, 1).unwrap();
242 g.add_edge(1, 2).unwrap();
243 g.add_edge(2, 3).unwrap();
244 g.add_edge(3, 4).unwrap();
245 let types = [false, true, false, true, false];
246 let sz = bipartite_projection_size(&g, &types).unwrap();
247 assert_eq!(sz.vcount1, 3); assert_eq!(sz.ecount1, 2); assert_eq!(sz.vcount2, 2); assert_eq!(sz.ecount2, 1); }
252
253 #[test]
254 fn directed_treated_as_undirected() {
255 let mut g = Graph::new(4, true).unwrap();
256 g.add_edge(0, 2).unwrap();
257 g.add_edge(3, 0).unwrap();
258 g.add_edge(1, 2).unwrap();
259 g.add_edge(3, 1).unwrap();
260 let types = [false, false, true, true];
261 let sz = bipartite_projection_size(&g, &types).unwrap();
262 assert_eq!(sz.vcount1, 2);
263 assert_eq!(sz.ecount1, 1);
264 assert_eq!(sz.vcount2, 2);
265 assert_eq!(sz.ecount2, 1);
266 }
267}