rust_igraph/algorithms/operators/
bipartite_projection.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
11
12#[derive(Debug, Clone)]
14pub struct BipartiteProjection {
15 pub graph: Graph,
17 pub vertex_map: Vec<VertexId>,
20 pub multiplicity: Vec<u32>,
23}
24
25pub fn bipartite_projection(
69 graph: &Graph,
70 types: &[bool],
71 project_type: bool,
72) -> IgraphResult<BipartiteProjection> {
73 let n = graph.vcount();
74 let n_us = n as usize;
75
76 if types.len() != n_us {
77 return Err(IgraphError::InvalidArgument(format!(
78 "bipartite_projection: types length ({}) != vcount ({n})",
79 types.len()
80 )));
81 }
82
83 let adj = build_undirected_adj(graph, n_us)?;
85
86 let mut vertex_map: Vec<VertexId> = Vec::new();
88 let mut new_index: Vec<Option<u32>> = vec![None; n_us];
89 for (v, &t) in types.iter().enumerate() {
90 if t == project_type {
91 #[allow(clippy::cast_possible_truncation)]
92 let new_id = vertex_map.len() as u32;
93 new_index[v] = Some(new_id);
94 #[allow(clippy::cast_possible_truncation)]
95 let v_id = v as u32;
96 vertex_map.push(v_id);
97 }
98 }
99
100 let proj_n = vertex_map.len();
101 let mut edges: Vec<(u32, u32)> = Vec::new();
102 let mut multiplicity: Vec<u32> = Vec::new();
103
104 let mut added: Vec<u32> = vec![0; n_us];
108 let mut mult_count: Vec<u32> = vec![0; n_us];
109
110 for (idx, &orig_i) in vertex_map.iter().enumerate() {
111 let i_us = orig_i as usize;
112 #[allow(clippy::cast_possible_truncation)]
113 let marker = (idx as u32) + 1; for &nei in &adj[i_us] {
116 let nei_us = nei as usize;
117 if types[nei_us] == project_type {
118 return Err(IgraphError::InvalidArgument(format!(
119 "bipartite_projection: non-bipartite edge found ({orig_i}-{nei})"
120 )));
121 }
122 for &nei2 in &adj[nei_us] {
124 let nb2_idx = nei2 as usize;
125 if nei2 <= orig_i {
126 continue; }
128 if types[nb2_idx] != project_type {
129 continue; }
131 if added[nb2_idx] == marker {
132 mult_count[nb2_idx] += 1;
133 continue;
134 }
135 added[nb2_idx] = marker;
136 mult_count[nb2_idx] = 1;
137 }
138 }
139
140 for &nei in &adj[i_us] {
142 let nei_us = nei as usize;
143 for &nei2 in &adj[nei_us] {
144 let nb2_idx = nei2 as usize;
145 if nei2 <= orig_i || types[nb2_idx] != project_type {
146 continue;
147 }
148 if added[nb2_idx] == marker {
149 #[allow(clippy::cast_possible_truncation)]
151 let new_i = idx as u32;
152 let new_j = new_index[nb2_idx].unwrap_or(0);
153 edges.push((new_i, new_j));
154 multiplicity.push(mult_count[nb2_idx]);
155 added[nb2_idx] = 0; }
157 }
158 }
159 }
160
161 #[allow(clippy::cast_possible_truncation)]
163 let proj_n_u32 = proj_n as u32;
164 let mut proj_graph = Graph::with_vertices(proj_n_u32);
165 for &(u, v) in &edges {
166 proj_graph.add_edge(u, v)?;
167 }
168
169 Ok(BipartiteProjection {
170 graph: proj_graph,
171 vertex_map,
172 multiplicity,
173 })
174}
175
176fn build_undirected_adj(graph: &Graph, n_us: usize) -> IgraphResult<Vec<Vec<VertexId>>> {
177 let m =
178 u32::try_from(graph.ecount()).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
179 let mut adj: Vec<Vec<VertexId>> = vec![Vec::new(); n_us];
180 for eid in 0..m {
181 let (from, to) = graph.edge(eid)?;
182 adj[from as usize].push(to);
183 if from != to {
184 adj[to as usize].push(from);
185 }
186 }
187 Ok(adj)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn simple_complete_bipartite() {
196 let mut g = Graph::with_vertices(4);
198 g.add_edge(0, 2).unwrap();
199 g.add_edge(0, 3).unwrap();
200 g.add_edge(1, 2).unwrap();
201 g.add_edge(1, 3).unwrap();
202 let types = [false, false, true, true];
203
204 let p0 = bipartite_projection(&g, &types, false).unwrap();
205 assert_eq!(p0.graph.vcount(), 2);
206 assert_eq!(p0.graph.ecount(), 1);
207 assert_eq!(p0.multiplicity[0], 2);
208 assert_eq!(p0.vertex_map, vec![0, 1]);
209
210 let p1 = bipartite_projection(&g, &types, true).unwrap();
211 assert_eq!(p1.graph.vcount(), 2);
212 assert_eq!(p1.graph.ecount(), 1);
213 assert_eq!(p1.multiplicity[0], 2);
214 assert_eq!(p1.vertex_map, vec![2, 3]);
215 }
216
217 #[test]
218 fn star_bipartite() {
219 let mut g = Graph::with_vertices(4);
222 g.add_edge(0, 1).unwrap();
223 g.add_edge(0, 2).unwrap();
224 g.add_edge(0, 3).unwrap();
225 let types = [false, true, true, true];
226
227 let p = bipartite_projection(&g, &types, true).unwrap();
228 assert_eq!(p.graph.vcount(), 3);
229 assert_eq!(p.graph.ecount(), 3); for &m in &p.multiplicity {
231 assert_eq!(m, 1); }
233 }
234
235 #[test]
236 fn no_edges() {
237 let g = Graph::with_vertices(4);
239 let types = [false, false, true, true];
240 let p = bipartite_projection(&g, &types, false).unwrap();
241 assert_eq!(p.graph.vcount(), 2);
242 assert_eq!(p.graph.ecount(), 0);
243 assert!(p.multiplicity.is_empty());
244 }
245
246 #[test]
247 fn single_type() {
248 let mut g = Graph::with_vertices(3);
250 g.add_edge(0, 1).unwrap();
251 let types = [false, false, false];
252 assert!(bipartite_projection(&g, &types, false).is_err());
253 }
254
255 #[test]
256 fn non_bipartite_edge_error() {
257 let mut g = Graph::with_vertices(3);
259 g.add_edge(0, 1).unwrap();
260 g.add_edge(0, 2).unwrap();
261 let types = [false, false, true];
262 assert!(bipartite_projection(&g, &types, false).is_err());
263 }
264
265 #[test]
266 fn types_length_mismatch() {
267 let g = Graph::with_vertices(3);
268 let types = [false, true];
269 assert!(bipartite_projection(&g, &types, false).is_err());
270 }
271
272 #[test]
273 fn path_bipartite() {
274 let mut g = Graph::with_vertices(5);
277 g.add_edge(0, 1).unwrap();
278 g.add_edge(1, 2).unwrap();
279 g.add_edge(2, 3).unwrap();
280 g.add_edge(3, 4).unwrap();
281 let types = [false, true, false, true, false];
282
283 let p = bipartite_projection(&g, &types, false).unwrap();
284 assert_eq!(p.graph.vcount(), 3); assert_eq!(p.graph.ecount(), 2); assert_eq!(p.vertex_map, vec![0, 2, 4]);
287 for &m in &p.multiplicity {
288 assert_eq!(m, 1);
289 }
290 }
291
292 #[test]
293 fn multiplicity_multiple() {
294 let mut g = Graph::with_vertices(5);
297 g.add_edge(0, 2).unwrap();
298 g.add_edge(0, 3).unwrap();
299 g.add_edge(1, 2).unwrap();
300 g.add_edge(1, 3).unwrap();
301 g.add_edge(1, 4).unwrap();
302 let types = [false, false, true, true, true];
303
304 let p = bipartite_projection(&g, &types, false).unwrap();
305 assert_eq!(p.graph.vcount(), 2);
306 assert_eq!(p.graph.ecount(), 1);
307 assert_eq!(p.multiplicity[0], 2); }
309
310 #[test]
311 fn empty_graph() {
312 let g = Graph::with_vertices(0);
313 let types: [bool; 0] = [];
314 let p = bipartite_projection(&g, &types, false).unwrap();
315 assert_eq!(p.graph.vcount(), 0);
316 assert_eq!(p.graph.ecount(), 0);
317 }
318
319 #[test]
320 fn directed_treated_as_undirected() {
321 let mut g = Graph::new(4, true).unwrap();
323 g.add_edge(0, 2).unwrap();
324 g.add_edge(3, 0).unwrap();
325 g.add_edge(1, 2).unwrap();
326 g.add_edge(3, 1).unwrap();
327 let types = [false, false, true, true];
328
329 let p = bipartite_projection(&g, &types, false).unwrap();
330 assert_eq!(p.graph.vcount(), 2);
331 assert_eq!(p.graph.ecount(), 1);
332 assert_eq!(p.multiplicity[0], 2);
333 }
334}