rust_igraph/algorithms/constructors/
biadjacency.rs1use crate::algorithms::games::bipartite::BipartiteMode;
12use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
13
14#[derive(Debug, Clone)]
16pub struct BiadjacencyResult {
17 pub graph: Graph,
19 pub types: Vec<bool>,
21}
22
23pub fn biadjacency(
67 matrix: &[&[f64]],
68 directed: bool,
69 mode: BipartiteMode,
70 multiple: bool,
71) -> IgraphResult<BiadjacencyResult> {
72 let n1 = matrix.len();
73 let n2 = if n1 == 0 { 0 } else { matrix[0].len() };
74
75 for (row_idx, row) in matrix.iter().enumerate() {
76 if row.len() != n2 {
77 return Err(IgraphError::InvalidArgument(format!(
78 "biadjacency: row {row_idx} has length {} but expected {n2}",
79 row.len()
80 )));
81 }
82 }
83
84 let total = n1.checked_add(n2).ok_or_else(|| {
85 IgraphError::InvalidArgument("biadjacency: total vertex count overflows usize".to_string())
86 })?;
87 let total_u32 = u32::try_from(total).map_err(|_| {
88 IgraphError::InvalidArgument("biadjacency: total vertex count exceeds u32::MAX".to_string())
89 })?;
90 let n1_u32 = u32::try_from(n1).map_err(|_| {
92 IgraphError::InvalidArgument("biadjacency: n1 exceeds u32::MAX".to_string())
93 })?;
94
95 let n2_u32 = u32::try_from(n2).map_err(|_| {
96 IgraphError::InvalidArgument("biadjacency: n2 exceeds u32::MAX".to_string())
97 })?;
98
99 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
100
101 if multiple {
102 if n1 > 0 && n2 > 0 {
103 for row in matrix {
104 for &val in *row {
105 if val < 0.0 {
106 return Err(IgraphError::InvalidArgument(format!(
107 "biadjacency: matrix elements must be non-negative when multiple=true, found {val}"
108 )));
109 }
110 }
111 }
112 }
113
114 for j in 0..n2_u32 {
115 for i in 0..n1_u32 {
116 #[allow(clippy::cast_possible_truncation)]
117 let elem = matrix[i as usize][j as usize] as i64;
118 if elem == 0 {
119 continue;
120 }
121
122 let (from, to) = if mode == BipartiteMode::In {
123 (n1_u32 + j, i)
124 } else {
125 (i, n1_u32 + j)
126 };
127
128 if mode != BipartiteMode::All || !directed {
129 for _ in 0..elem {
130 edges.push((from, to));
131 }
132 } else {
133 for _ in 0..elem {
134 edges.push((from, to));
135 edges.push((to, from));
136 }
137 }
138 }
139 }
140 } else {
141 for j in 0..n2_u32 {
142 for i in 0..n1_u32 {
143 if matrix[i as usize][j as usize] != 0.0 {
144 let (from, to) = if mode == BipartiteMode::In {
145 (n1_u32 + j, i)
146 } else {
147 (i, n1_u32 + j)
148 };
149
150 if mode != BipartiteMode::All || !directed {
151 edges.push((from, to));
152 } else {
153 edges.push((from, to));
154 edges.push((to, from));
155 }
156 }
157 }
158 }
159 }
160
161 let mut graph = Graph::new(total_u32, directed)?;
162 if !edges.is_empty() {
163 graph.add_edges(edges)?;
164 }
165
166 let mut types = vec![false; total];
167 for t in types.iter_mut().skip(n1) {
168 *t = true;
169 }
170
171 Ok(BiadjacencyResult { graph, types })
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn empty_matrix() {
180 let matrix: &[&[f64]] = &[];
181 let r = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
182 assert_eq!(r.graph.vcount(), 0);
183 assert_eq!(r.graph.ecount(), 0);
184 assert!(r.types.is_empty());
185 }
186
187 #[test]
188 fn single_entry_nonzero() {
189 let matrix: &[&[f64]] = &[&[1.0]];
190 let r = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
191 assert_eq!(r.graph.vcount(), 2);
192 assert_eq!(r.graph.ecount(), 1);
193 assert_eq!(r.types, vec![false, true]);
194 }
195
196 #[test]
197 fn single_entry_zero() {
198 let matrix: &[&[f64]] = &[&[0.0]];
199 let r = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
200 assert_eq!(r.graph.vcount(), 2);
201 assert_eq!(r.graph.ecount(), 0);
202 }
203
204 #[test]
205 fn two_by_three_no_multiple() {
206 let matrix: &[&[f64]] = &[&[1.0, 0.0, 3.0], &[0.0, 1.0, 0.0]];
207 let r = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
208 assert_eq!(r.graph.vcount(), 5);
209 assert_eq!(r.graph.ecount(), 3);
210 assert_eq!(r.types, vec![false, false, true, true, true]);
211 }
212
213 #[test]
214 fn two_by_three_with_multiple() {
215 let matrix: &[&[f64]] = &[&[1.0, 0.0, 2.0], &[0.0, 1.0, 0.0]];
216 let r = biadjacency(matrix, false, BipartiteMode::All, true).unwrap();
217 assert_eq!(r.graph.vcount(), 5);
218 assert_eq!(r.graph.ecount(), 4); }
220
221 #[test]
222 fn directed_out() {
223 let matrix: &[&[f64]] = &[&[1.0, 1.0], &[1.0, 0.0]];
224 let r = biadjacency(matrix, true, BipartiteMode::Out, false).unwrap();
225 assert!(r.graph.is_directed());
226 assert_eq!(r.graph.ecount(), 3);
227 for eid in 0..r.graph.ecount() {
228 #[allow(clippy::cast_possible_truncation)]
229 let (s, t) = r.graph.edge(eid as u32).unwrap();
230 assert!(!r.types[s as usize], "source should be row (partition 1)");
231 assert!(r.types[t as usize], "target should be col (partition 2)");
232 }
233 }
234
235 #[test]
236 fn directed_in() {
237 let matrix: &[&[f64]] = &[&[1.0, 1.0], &[1.0, 0.0]];
238 let r = biadjacency(matrix, true, BipartiteMode::In, false).unwrap();
239 assert!(r.graph.is_directed());
240 assert_eq!(r.graph.ecount(), 3);
241 for eid in 0..r.graph.ecount() {
242 #[allow(clippy::cast_possible_truncation)]
243 let (s, t) = r.graph.edge(eid as u32).unwrap();
244 assert!(r.types[s as usize], "source should be col (partition 2)");
245 assert!(!r.types[t as usize], "target should be row (partition 1)");
246 }
247 }
248
249 #[test]
250 fn directed_all_mutual() {
251 let matrix: &[&[f64]] = &[&[1.0]];
252 let r = biadjacency(matrix, true, BipartiteMode::All, false).unwrap();
253 assert!(r.graph.is_directed());
254 assert_eq!(r.graph.ecount(), 2); }
256
257 #[test]
258 fn multiple_with_mutual() {
259 let matrix: &[&[f64]] = &[&[2.0]];
260 let r = biadjacency(matrix, true, BipartiteMode::All, true).unwrap();
261 assert_eq!(r.graph.ecount(), 4); }
263
264 #[test]
265 fn rejects_negative_in_multiple_mode() {
266 let matrix: &[&[f64]] = &[&[-1.0]];
267 let result = biadjacency(matrix, false, BipartiteMode::All, true);
268 assert!(result.is_err());
269 }
270
271 #[test]
272 fn negative_allowed_in_non_multiple_mode() {
273 let matrix: &[&[f64]] = &[&[-1.0]];
274 let r = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
275 assert_eq!(r.graph.ecount(), 1); }
277
278 #[test]
279 fn ragged_matrix_rejected() {
280 let matrix: &[&[f64]] = &[&[1.0, 2.0], &[3.0]];
281 let result = biadjacency(matrix, false, BipartiteMode::All, false);
282 assert!(result.is_err());
283 }
284
285 #[test]
286 fn undirected_ignores_mode_out() {
287 let matrix: &[&[f64]] = &[&[1.0]];
288 let r = biadjacency(matrix, false, BipartiteMode::Out, false).unwrap();
289 assert!(!r.graph.is_directed());
290 assert_eq!(r.graph.ecount(), 1);
291 }
292}