rust_igraph/algorithms/constructors/
full_bipartite.rs1pub use crate::algorithms::games::bipartite::BipartiteMode;
25use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
26
27#[derive(Debug, Clone)]
29pub struct FullBipartite {
30 pub graph: Graph,
32 pub types: Vec<bool>,
34}
35
36pub fn full_bipartite(
68 n1: u32,
69 n2: u32,
70 directed: bool,
71 mode: BipartiteMode,
72) -> IgraphResult<FullBipartite> {
73 let total = n1.checked_add(n2).ok_or_else(|| {
74 IgraphError::InvalidArgument(format!(
75 "full_bipartite: total vertex count {n1} + {n2} overflows u32"
76 ))
77 })?;
78
79 if total == 0 {
80 let graph = Graph::new(0, directed)?;
81 return Ok(FullBipartite {
82 graph,
83 types: Vec::new(),
84 });
85 }
86
87 let n1_us = n1 as usize;
88 let n2_us = n2 as usize;
89 let base_edges = n1_us.checked_mul(n2_us).ok_or_else(|| {
90 IgraphError::InvalidArgument("full_bipartite: edge count overflows usize".to_string())
91 })?;
92 let num_edges = if directed && mode == BipartiteMode::All {
93 base_edges.checked_mul(2).ok_or_else(|| {
94 IgraphError::InvalidArgument(
95 "full_bipartite: mutual edge count overflows usize".to_string(),
96 )
97 })?
98 } else {
99 base_edges
100 };
101
102 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(num_edges);
103
104 match mode {
105 BipartiteMode::Out | BipartiteMode::All if !directed => {
106 for i in 0..n1 {
107 for j in 0..n2 {
108 edges.push((i, n1 + j));
109 }
110 }
111 }
112 BipartiteMode::Out => {
113 for i in 0..n1 {
114 for j in 0..n2 {
115 edges.push((i, n1 + j));
116 }
117 }
118 }
119 BipartiteMode::In => {
120 for i in 0..n1 {
121 for j in 0..n2 {
122 edges.push((n1 + j, i));
123 }
124 }
125 }
126 BipartiteMode::All => {
127 for i in 0..n1 {
128 for j in 0..n2 {
129 edges.push((i, n1 + j));
130 edges.push((n1 + j, i));
131 }
132 }
133 }
134 }
135
136 let mut graph = Graph::new(total, directed)?;
137 graph.add_edges(edges)?;
138
139 let mut types = vec![false; total as usize];
140 for i in n1..total {
141 types[i as usize] = true;
142 }
143
144 Ok(FullBipartite { graph, types })
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn empty_graph() {
153 let r = full_bipartite(0, 0, false, BipartiteMode::All).unwrap();
154 assert_eq!(r.graph.vcount(), 0);
155 assert_eq!(r.graph.ecount(), 0);
156 assert!(r.types.is_empty());
157 }
158
159 #[test]
160 fn one_partition_empty() {
161 let r = full_bipartite(3, 0, false, BipartiteMode::All).unwrap();
162 assert_eq!(r.graph.vcount(), 3);
163 assert_eq!(r.graph.ecount(), 0);
164 assert_eq!(r.types, vec![false, false, false]);
165 }
166
167 #[test]
168 fn other_partition_empty() {
169 let r = full_bipartite(0, 4, false, BipartiteMode::All).unwrap();
170 assert_eq!(r.graph.vcount(), 4);
171 assert_eq!(r.graph.ecount(), 0);
172 assert_eq!(r.types, vec![true, true, true, true]);
173 }
174
175 #[test]
176 fn k23_undirected() {
177 let r = full_bipartite(2, 3, false, BipartiteMode::All).unwrap();
178 assert_eq!(r.graph.vcount(), 5);
179 assert_eq!(r.graph.ecount(), 6);
180 assert!(!r.graph.is_directed());
181 assert_eq!(r.types, vec![false, false, true, true, true]);
182 }
183
184 #[test]
185 fn k11_undirected() {
186 let r = full_bipartite(1, 1, false, BipartiteMode::All).unwrap();
187 assert_eq!(r.graph.vcount(), 2);
188 assert_eq!(r.graph.ecount(), 1);
189 assert_eq!(r.types, vec![false, true]);
190 }
191
192 #[test]
193 fn k33_undirected() {
194 let r = full_bipartite(3, 3, false, BipartiteMode::All).unwrap();
195 assert_eq!(r.graph.vcount(), 6);
196 assert_eq!(r.graph.ecount(), 9);
197 }
198
199 #[test]
200 fn directed_out() {
201 let r = full_bipartite(2, 3, true, BipartiteMode::Out).unwrap();
202 assert!(r.graph.is_directed());
203 assert_eq!(r.graph.ecount(), 6);
204 for eid in 0..r.graph.ecount() {
206 #[allow(clippy::cast_possible_truncation)]
207 let e = eid as u32;
208 let (s, t) = r.graph.edge(e).unwrap();
209 assert!(!r.types[s as usize], "source should be partition 1");
210 assert!(r.types[t as usize], "target should be partition 2");
211 }
212 }
213
214 #[test]
215 fn directed_in() {
216 let r = full_bipartite(2, 3, true, BipartiteMode::In).unwrap();
217 assert!(r.graph.is_directed());
218 assert_eq!(r.graph.ecount(), 6);
219 for eid in 0..r.graph.ecount() {
221 #[allow(clippy::cast_possible_truncation)]
222 let e = eid as u32;
223 let (s, t) = r.graph.edge(e).unwrap();
224 assert!(r.types[s as usize], "source should be partition 2");
225 assert!(!r.types[t as usize], "target should be partition 1");
226 }
227 }
228
229 #[test]
230 fn directed_mutual() {
231 let r = full_bipartite(2, 3, true, BipartiteMode::All).unwrap();
232 assert!(r.graph.is_directed());
233 assert_eq!(r.graph.ecount(), 12); }
235
236 #[test]
237 fn types_correctness() {
238 let r = full_bipartite(4, 2, false, BipartiteMode::All).unwrap();
239 assert_eq!(r.types, vec![false, false, false, false, true, true]);
240 }
241
242 #[test]
243 fn bipartite_structure_no_within_partition_edges() {
244 let r = full_bipartite(3, 4, false, BipartiteMode::All).unwrap();
245 for eid in 0..r.graph.ecount() {
246 #[allow(clippy::cast_possible_truncation)]
247 let e = eid as u32;
248 let (s, t) = r.graph.edge(e).unwrap();
249 assert_ne!(
250 r.types[s as usize], r.types[t as usize],
251 "edge ({s}, {t}) connects same partition"
252 );
253 }
254 }
255
256 #[test]
257 fn overflow_vertex_count_errors() {
258 let result = full_bipartite(u32::MAX, 1, false, BipartiteMode::All);
259 assert!(result.is_err());
260 }
261}