1use crate::core::error::IgraphError;
10use crate::core::{Graph, IgraphResult, VertexId};
11
12use super::realize_degree_sequence::RealizeDegseqMethod;
13
14pub fn realize_bipartite_degree_sequence(
47 degrees1: &[u32],
48 degrees2: &[u32],
49 method: RealizeDegseqMethod,
50) -> IgraphResult<Graph> {
51 let n1 = degrees1.len();
52 let n2 = degrees2.len();
53 let n = n1.checked_add(n2).ok_or_else(|| {
54 IgraphError::InvalidArgument("total vertex count overflows usize".to_string())
55 })?;
56
57 if n == 0 {
58 return Graph::new(0, false);
59 }
60
61 let n_u32 = u32::try_from(n)
62 .map_err(|_| IgraphError::InvalidArgument("vertex count exceeds u32::MAX".to_string()))?;
63
64 let sum1: u64 = degrees1.iter().map(|&d| u64::from(d)).sum();
65 let sum2: u64 = degrees2.iter().map(|&d| u64::from(d)).sum();
66
67 if sum1 != sum2 {
68 return Err(IgraphError::InvalidArgument(
69 "degree sums of the two partitions must be equal".to_string(),
70 ));
71 }
72
73 if sum1 == 0 {
74 return Graph::new(n_u32, false);
75 }
76
77 #[allow(clippy::cast_possible_truncation)]
78 let num_edges = sum1 as usize;
79
80 let offset = u32::try_from(n1)
81 .map_err(|_| IgraphError::InvalidArgument("partition 1 too large".to_string()))?;
82
83 let mut vd1: Vec<(u32, u32)> = degrees1
86 .iter()
87 .enumerate()
88 .map(|(i, &d)| {
89 #[allow(clippy::cast_possible_truncation)]
90 let idx = i as u32;
91 (idx, d)
92 })
93 .collect();
94
95 let mut vd2: Vec<(u32, u32)> = degrees2
96 .iter()
97 .enumerate()
98 .map(|(i, &d)| {
99 #[allow(clippy::cast_possible_truncation)]
100 let idx = (i as u32)
101 .checked_add(offset)
102 .ok_or_else(|| IgraphError::InvalidArgument("vertex index overflow".to_string()));
103 (idx.unwrap_or(0), d)
104 })
105 .collect();
106
107 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(num_edges);
108
109 match method {
110 RealizeDegseqMethod::Largest => {
111 realize_largest_or_smallest(&mut vd1, &mut vd2, &mut edges, true)?;
112 }
113 RealizeDegseqMethod::Smallest => {
114 realize_largest_or_smallest(&mut vd1, &mut vd2, &mut edges, false)?;
115 }
116 RealizeDegseqMethod::Index => realize_index(&mut vd1, &mut vd2, &mut edges)?,
117 }
118
119 let mut graph = Graph::new(n_u32, false)?;
120 graph.add_edges(edges)?;
121 Ok(graph)
122}
123
124fn realize_largest_or_smallest(
125 vd1: &mut Vec<(u32, u32)>,
126 vd2: &mut Vec<(u32, u32)>,
127 edges: &mut Vec<(VertexId, VertexId)>,
128 largest: bool,
129) -> IgraphResult<()> {
130 while !vd1.is_empty() && !vd2.is_empty() {
131 vd1.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
133 vd2.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
134
135 let (src_vs, dest_vs) = if largest {
137 let max1 = vd1[0].1;
138 let max2 = vd2[0].1;
139 if max1 >= max2 {
140 (vd1 as &mut Vec<(u32, u32)>, vd2 as &mut Vec<(u32, u32)>)
141 } else {
142 (vd2 as &mut Vec<(u32, u32)>, vd1 as &mut Vec<(u32, u32)>)
143 }
144 } else {
145 let min1 = vd1.last().map_or(0, |v| v.1);
146 let min2 = vd2.last().map_or(0, |v| v.1);
147 if min1 <= min2 {
148 (vd1 as &mut Vec<(u32, u32)>, vd2 as &mut Vec<(u32, u32)>)
149 } else {
150 (vd2 as &mut Vec<(u32, u32)>, vd1 as &mut Vec<(u32, u32)>)
151 }
152 };
153
154 let hub = if largest {
155 let h = src_vs[0];
156 src_vs.remove(0);
157 h
158 } else {
159 src_vs.pop().unwrap_or((0, 0))
160 };
161
162 if hub.1 == 0 {
163 continue;
164 }
165
166 let d = hub.1 as usize;
167 if d > dest_vs.len() {
168 return Err(IgraphError::InvalidArgument(
169 "bidegree sequence is not bigraphical (not realizable as simple bipartite graph)"
170 .to_string(),
171 ));
172 }
173
174 for item in dest_vs.iter_mut().take(d) {
177 if item.1 == 0 {
178 return Err(IgraphError::InvalidArgument(
179 "bidegree sequence is not bigraphical (not realizable as simple bipartite graph)"
180 .to_string(),
181 ));
182 }
183 edges.push((hub.0, item.0));
184 item.1 -= 1;
185 }
186 }
187
188 for vs in [&*vd1, &*vd2] {
190 for &(v, d) in vs {
191 if d != 0 {
192 return Err(IgraphError::InvalidArgument(format!(
193 "bidegree sequence not bigraphical: vertex {v} has {d} remaining degree"
194 )));
195 }
196 }
197 }
198
199 Ok(())
200}
201
202fn realize_index(
203 vd1: &mut Vec<(u32, u32)>,
204 vd2: &mut Vec<(u32, u32)>,
205 edges: &mut Vec<(VertexId, VertexId)>,
206) -> IgraphResult<()> {
207 while !vd1.is_empty() && !vd2.is_empty() {
210 vd2.sort_unstable_by_key(|x| std::cmp::Reverse(x.1));
212
213 let hub = vd1.remove(0);
214
215 if hub.1 == 0 {
216 continue;
217 }
218
219 let d = hub.1 as usize;
220 if d > vd2.len() {
221 return Err(IgraphError::InvalidArgument(
222 "bidegree sequence is not bigraphical (not realizable as simple bipartite graph)"
223 .to_string(),
224 ));
225 }
226
227 for item in vd2.iter_mut().take(d) {
228 if item.1 == 0 {
229 return Err(IgraphError::InvalidArgument(
230 "bidegree sequence is not bigraphical (not realizable as simple bipartite graph)"
231 .to_string(),
232 ));
233 }
234 edges.push((hub.0, item.0));
235 item.1 -= 1;
236 }
237 }
238
239 for vs in [&*vd1, &*vd2] {
241 for &(v, d) in vs {
242 if d != 0 {
243 return Err(IgraphError::InvalidArgument(format!(
244 "bidegree sequence not bigraphical: vertex {v} has {d} remaining degree"
245 )));
246 }
247 }
248 }
249
250 Ok(())
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 fn verify_bipartite_degrees(graph: &Graph, expected1: &[u32], expected2: &[u32]) {
258 let n1 = expected1.len();
259 let n2 = expected2.len();
260 assert_eq!(graph.vcount() as usize, n1 + n2);
261
262 for (i, &exp) in expected1.iter().enumerate() {
263 #[allow(clippy::cast_possible_truncation)]
264 let v = i as u32;
265 let deg = graph.degree(v).unwrap();
266 assert_eq!(
267 deg, exp as usize,
268 "partition 1 vertex {v}: got degree {deg}, expected {exp}"
269 );
270 }
271 for (i, &exp) in expected2.iter().enumerate() {
272 #[allow(clippy::cast_possible_truncation)]
273 let v = (i + n1) as u32;
274 let deg = graph.degree(v).unwrap();
275 assert_eq!(
276 deg, exp as usize,
277 "partition 2 vertex {v}: got degree {deg}, expected {exp}"
278 );
279 }
280 }
281
282 fn verify_bipartite_structure(graph: &Graph, n1: usize) {
283 for eid in 0..graph.ecount() {
285 #[allow(clippy::cast_possible_truncation)]
286 let eid_u32 = eid as u32;
287 let (s, t) = graph.edge(eid_u32).unwrap();
288 let s_in_p1 = (s as usize) < n1;
289 let t_in_p1 = (t as usize) < n1;
290 assert_ne!(
291 s_in_p1, t_in_p1,
292 "edge ({s}, {t}) does not cross partitions (n1={n1})"
293 );
294 }
295 }
296
297 #[test]
298 fn empty_sequences() {
299 let g = realize_bipartite_degree_sequence(&[], &[], RealizeDegseqMethod::Largest).unwrap();
300 assert_eq!(g.vcount(), 0);
301 assert_eq!(g.ecount(), 0);
302 }
303
304 #[test]
305 fn one_partition_empty() {
306 let g =
307 realize_bipartite_degree_sequence(&[0, 0], &[], RealizeDegseqMethod::Largest).unwrap();
308 assert_eq!(g.vcount(), 2);
309 assert_eq!(g.ecount(), 0);
310 }
311
312 #[test]
313 fn all_zeros() {
314 let g =
315 realize_bipartite_degree_sequence(&[0, 0], &[0, 0, 0], RealizeDegseqMethod::Largest)
316 .unwrap();
317 assert_eq!(g.vcount(), 5);
318 assert_eq!(g.ecount(), 0);
319 }
320
321 #[test]
322 fn single_edge() {
323 let g =
324 realize_bipartite_degree_sequence(&[1], &[1], RealizeDegseqMethod::Largest).unwrap();
325 assert_eq!(g.vcount(), 2);
326 assert_eq!(g.ecount(), 1);
327 verify_bipartite_degrees(&g, &[1], &[1]);
328 verify_bipartite_structure(&g, 1);
329 }
330
331 #[test]
332 fn complete_bipartite_k23() {
333 let g =
334 realize_bipartite_degree_sequence(&[3, 3], &[2, 2, 2], RealizeDegseqMethod::Largest)
335 .unwrap();
336 assert_eq!(g.vcount(), 5);
337 assert_eq!(g.ecount(), 6);
338 verify_bipartite_degrees(&g, &[3, 3], &[2, 2, 2]);
339 verify_bipartite_structure(&g, 2);
340 }
341
342 #[test]
343 fn complete_bipartite_k33() {
344 let g =
345 realize_bipartite_degree_sequence(&[3, 3, 3], &[3, 3, 3], RealizeDegseqMethod::Largest)
346 .unwrap();
347 assert_eq!(g.vcount(), 6);
348 assert_eq!(g.ecount(), 9);
349 verify_bipartite_degrees(&g, &[3, 3, 3], &[3, 3, 3]);
350 verify_bipartite_structure(&g, 3);
351 }
352
353 #[test]
354 fn star_bipartite() {
355 let g =
357 realize_bipartite_degree_sequence(&[4], &[1, 1, 1, 1], RealizeDegseqMethod::Largest)
358 .unwrap();
359 assert_eq!(g.vcount(), 5);
360 assert_eq!(g.ecount(), 4);
361 verify_bipartite_degrees(&g, &[4], &[1, 1, 1, 1]);
362 verify_bipartite_structure(&g, 1);
363 }
364
365 #[test]
366 fn unequal_sums_fails() {
367 let result =
368 realize_bipartite_degree_sequence(&[2, 2], &[1, 1], RealizeDegseqMethod::Largest);
369 assert!(result.is_err());
370 }
371
372 #[test]
373 fn not_bigraphical_fails() {
374 let result = realize_bipartite_degree_sequence(&[4], &[2, 2], RealizeDegseqMethod::Largest);
377 assert!(result.is_err());
378 }
379
380 #[test]
381 fn method_smallest() {
382 let g = realize_bipartite_degree_sequence(
383 &[2, 2, 1],
384 &[1, 2, 2],
385 RealizeDegseqMethod::Smallest,
386 )
387 .unwrap();
388 assert_eq!(g.ecount(), 5);
389 verify_bipartite_degrees(&g, &[2, 2, 1], &[1, 2, 2]);
390 verify_bipartite_structure(&g, 3);
391 }
392
393 #[test]
394 fn method_index() {
395 let g =
396 realize_bipartite_degree_sequence(&[2, 1, 1], &[2, 1, 1], RealizeDegseqMethod::Index)
397 .unwrap();
398 assert_eq!(g.ecount(), 4);
399 verify_bipartite_degrees(&g, &[2, 1, 1], &[2, 1, 1]);
400 verify_bipartite_structure(&g, 3);
401 }
402
403 #[test]
404 fn larger_bipartite() {
405 let g = realize_bipartite_degree_sequence(
406 &[3, 3, 2, 2],
407 &[2, 2, 2, 2, 2],
408 RealizeDegseqMethod::Largest,
409 )
410 .unwrap();
411 assert_eq!(g.vcount(), 9);
412 assert_eq!(g.ecount(), 10);
413 verify_bipartite_degrees(&g, &[3, 3, 2, 2], &[2, 2, 2, 2, 2]);
414 verify_bipartite_structure(&g, 4);
415 }
416
417 #[test]
418 fn asymmetric_partitions() {
419 let g =
420 realize_bipartite_degree_sequence(&[5], &[1, 1, 1, 1, 1], RealizeDegseqMethod::Largest)
421 .unwrap();
422 assert_eq!(g.vcount(), 6);
423 assert_eq!(g.ecount(), 5);
424 verify_bipartite_degrees(&g, &[5], &[1, 1, 1, 1, 1]);
425 verify_bipartite_structure(&g, 1);
426 }
427}