rust_igraph/algorithms/properties/
get_biadjacency.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
10
11#[derive(Debug, Clone)]
13pub struct GetBiadjacencyResult {
14 pub matrix: Vec<Vec<f64>>,
18 pub row_ids: Vec<VertexId>,
20 pub col_ids: Vec<VertexId>,
22}
23
24pub fn get_biadjacency_matrix(graph: &Graph, types: &[bool]) -> IgraphResult<GetBiadjacencyResult> {
62 let vcount = graph.vcount();
63
64 if types.len() != vcount as usize {
65 return Err(IgraphError::InvalidArgument(format!(
66 "get_biadjacency_matrix: types length ({}) != vcount ({})",
67 types.len(),
68 vcount
69 )));
70 }
71
72 let mut n1: usize = 0;
74 let mut n2: usize = 0;
75 let mut row_ids: Vec<VertexId> = Vec::new();
76 let mut col_ids: Vec<VertexId> = Vec::new();
77 let mut vertex_to_idx: Vec<u32> = vec![0; vcount as usize];
79
80 for v in 0..vcount {
81 if types[v as usize] {
82 #[allow(clippy::cast_possible_truncation)]
83 let idx = n2 as u32;
84 vertex_to_idx[v as usize] = idx;
85 col_ids.push(v);
86 n2 += 1;
87 } else {
88 #[allow(clippy::cast_possible_truncation)]
89 let idx = n1 as u32;
90 vertex_to_idx[v as usize] = idx;
91 row_ids.push(v);
92 n1 += 1;
93 }
94 }
95
96 let mut matrix = vec![vec![0.0_f64; n2]; n1];
98
99 let ecount = graph.ecount();
101 for eid in 0..ecount {
102 #[allow(clippy::cast_possible_truncation)]
103 let e = eid as u32;
104 let (src, tgt) = graph.edge(e)?;
105
106 let src_type = types[src as usize];
107 let tgt_type = types[tgt as usize];
108
109 if src_type == tgt_type {
111 continue;
112 }
113
114 let (row_v, col_v) = if src_type { (tgt, src) } else { (src, tgt) };
116
117 let ri = vertex_to_idx[row_v as usize] as usize;
118 let ci = vertex_to_idx[col_v as usize] as usize;
119 matrix[ri][ci] += 1.0;
120 }
121
122 Ok(GetBiadjacencyResult {
123 matrix,
124 row_ids,
125 col_ids,
126 })
127}
128
129#[cfg(test)]
130#[allow(clippy::float_cmp)]
131mod tests {
132 use super::*;
133 use crate::algorithms::constructors::biadjacency::biadjacency;
134 use crate::algorithms::games::bipartite::BipartiteMode;
135
136 #[test]
137 fn empty_graph() {
138 let g = Graph::new(0, false).unwrap();
139 let r = get_biadjacency_matrix(&g, &[]).unwrap();
140 assert!(r.matrix.is_empty());
141 assert!(r.row_ids.is_empty());
142 assert!(r.col_ids.is_empty());
143 }
144
145 #[test]
146 fn single_vertex_row() {
147 let g = Graph::new(1, false).unwrap();
148 let r = get_biadjacency_matrix(&g, &[false]).unwrap();
149 assert_eq!(r.matrix.len(), 1);
150 assert!(r.matrix[0].is_empty()); assert_eq!(r.row_ids, vec![0]);
152 assert!(r.col_ids.is_empty());
153 }
154
155 #[test]
156 fn single_vertex_col() {
157 let g = Graph::new(1, false).unwrap();
158 let r = get_biadjacency_matrix(&g, &[true]).unwrap();
159 assert!(r.matrix.is_empty()); assert!(r.row_ids.is_empty());
161 assert_eq!(r.col_ids, vec![0]);
162 }
163
164 #[test]
165 fn types_length_mismatch() {
166 let g = Graph::new(3, false).unwrap();
167 let result = get_biadjacency_matrix(&g, &[false, true]);
168 assert!(result.is_err());
169 }
170
171 #[test]
172 fn roundtrip_simple() {
173 let matrix: &[&[f64]] = &[&[1.0, 0.0, 1.0], &[0.0, 1.0, 0.0]];
174 let built = biadjacency(matrix, false, BipartiteMode::All, false).unwrap();
175 let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
176
177 assert_eq!(extracted.matrix.len(), 2);
178 assert_eq!(extracted.matrix[0].len(), 3);
179 assert_eq!(extracted.matrix[0][0], 1.0);
180 assert_eq!(extracted.matrix[0][1], 0.0);
181 assert_eq!(extracted.matrix[0][2], 1.0);
182 assert_eq!(extracted.matrix[1][0], 0.0);
183 assert_eq!(extracted.matrix[1][1], 1.0);
184 assert_eq!(extracted.matrix[1][2], 0.0);
185 }
186
187 #[test]
188 fn roundtrip_with_multiple() {
189 let matrix: &[&[f64]] = &[&[1.0, 0.0, 2.0], &[0.0, 1.0, 0.0]];
190 let built = biadjacency(matrix, false, BipartiteMode::All, true).unwrap();
191 let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
192
193 assert_eq!(extracted.matrix[0][0], 1.0);
194 assert_eq!(extracted.matrix[0][2], 2.0);
195 assert_eq!(extracted.matrix[1][1], 1.0);
196 }
197
198 #[test]
199 fn directed_out() {
200 let matrix: &[&[f64]] = &[&[1.0, 1.0]];
201 let built = biadjacency(matrix, true, BipartiteMode::Out, false).unwrap();
202 let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
203 assert_eq!(extracted.matrix[0][0], 1.0);
204 assert_eq!(extracted.matrix[0][1], 1.0);
205 }
206
207 #[test]
208 fn directed_in() {
209 let matrix: &[&[f64]] = &[&[1.0, 1.0]];
210 let built = biadjacency(matrix, true, BipartiteMode::In, false).unwrap();
211 let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
212 assert_eq!(extracted.matrix[0][0], 1.0);
214 assert_eq!(extracted.matrix[0][1], 1.0);
215 }
216
217 #[test]
218 fn directed_all_mutual() {
219 let matrix: &[&[f64]] = &[&[1.0]];
220 let built = biadjacency(matrix, true, BipartiteMode::All, false).unwrap();
221 let extracted = get_biadjacency_matrix(&built.graph, &built.types).unwrap();
223 assert_eq!(extracted.matrix[0][0], 2.0);
224 }
225
226 #[test]
227 fn ignores_same_partition_edges() {
228 let mut g = Graph::new(4, false).unwrap();
230 g.add_edges(vec![(0, 2), (0, 1), (1, 3)]).unwrap();
232 let types = vec![false, false, true, true];
233 let r = get_biadjacency_matrix(&g, &types).unwrap();
234 assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[0][1], 0.0);
237 assert_eq!(r.matrix[1][0], 0.0);
238 assert_eq!(r.matrix[1][1], 1.0); }
240
241 #[test]
242 fn row_col_ids_ordering() {
243 let mut g = Graph::new(4, false).unwrap();
245 g.add_edges(vec![(1, 0), (3, 2)]).unwrap();
246 let types = vec![true, false, true, false];
247 let r = get_biadjacency_matrix(&g, &types).unwrap();
248 assert_eq!(r.row_ids, vec![1, 3]);
249 assert_eq!(r.col_ids, vec![0, 2]);
250 assert_eq!(r.matrix.len(), 2); assert_eq!(r.matrix[0].len(), 2); assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[1][1], 1.0); }
255
256 #[test]
257 fn no_edges() {
258 let g = Graph::new(4, false).unwrap();
259 let types = vec![false, false, true, true];
260 let r = get_biadjacency_matrix(&g, &types).unwrap();
261 assert_eq!(r.matrix, vec![vec![0.0, 0.0], vec![0.0, 0.0]]);
262 }
263
264 #[test]
265 fn self_loop_ignored() {
266 let mut g = Graph::new(3, false).unwrap();
267 g.add_edges(vec![(0, 0), (0, 2)]).unwrap();
268 let types = vec![false, false, true];
269 let r = get_biadjacency_matrix(&g, &types).unwrap();
270 assert_eq!(r.matrix[0][0], 1.0); assert_eq!(r.matrix[1][0], 0.0);
273 }
274}