rust_igraph/algorithms/constructors/
weighted_biadjacency.rs1use crate::algorithms::games::bipartite::BipartiteMode;
10use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
11
12#[derive(Debug, Clone)]
14pub struct WeightedBiadjacencyResult {
15 pub graph: Graph,
17 pub types: Vec<bool>,
19 pub weights: Vec<f64>,
21}
22
23pub fn weighted_biadjacency(
63 matrix: &[&[f64]],
64 directed: bool,
65 mode: BipartiteMode,
66) -> IgraphResult<WeightedBiadjacencyResult> {
67 let n1 = matrix.len();
68 let n2 = if n1 == 0 { 0 } else { matrix[0].len() };
69
70 for (row_idx, row) in matrix.iter().enumerate() {
71 if row.len() != n2 {
72 return Err(IgraphError::InvalidArgument(format!(
73 "weighted_biadjacency: row {row_idx} has length {} but expected {n2}",
74 row.len()
75 )));
76 }
77 }
78
79 let total = n1.checked_add(n2).ok_or_else(|| {
80 IgraphError::InvalidArgument(
81 "weighted_biadjacency: total vertex count overflows usize".to_string(),
82 )
83 })?;
84 let total_u32 = u32::try_from(total).map_err(|_| {
85 IgraphError::InvalidArgument(
86 "weighted_biadjacency: total vertex count exceeds u32::MAX".to_string(),
87 )
88 })?;
89 let n1_u32 = u32::try_from(n1).map_err(|_| {
90 IgraphError::InvalidArgument("weighted_biadjacency: n1 exceeds u32::MAX".to_string())
91 })?;
92 let n2_u32 = u32::try_from(n2).map_err(|_| {
93 IgraphError::InvalidArgument("weighted_biadjacency: n2 exceeds u32::MAX".to_string())
94 })?;
95
96 let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
97 let mut weights: Vec<f64> = Vec::new();
98
99 for j in 0..n2_u32 {
100 for i in 0..n1_u32 {
101 let weight = matrix[i as usize][j as usize];
102 if weight == 0.0 {
103 continue;
104 }
105
106 let (from, to) = if mode == BipartiteMode::In {
107 (n1_u32 + j, i)
108 } else {
109 (i, n1_u32 + j)
110 };
111
112 if mode != BipartiteMode::All || !directed {
113 edges.push((from, to));
114 weights.push(weight);
115 } else {
116 edges.push((from, to));
117 weights.push(weight);
118 edges.push((to, from));
119 weights.push(weight);
120 }
121 }
122 }
123
124 let mut graph = Graph::new(total_u32, directed)?;
125 if !edges.is_empty() {
126 graph.add_edges(edges)?;
127 }
128
129 let mut types = vec![false; total];
130 for t in types.iter_mut().skip(n1) {
131 *t = true;
132 }
133
134 Ok(WeightedBiadjacencyResult {
135 graph,
136 types,
137 weights,
138 })
139}
140
141#[cfg(test)]
142#[allow(clippy::float_cmp)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn empty_matrix() {
148 let matrix: &[&[f64]] = &[];
149 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
150 assert_eq!(r.graph.vcount(), 0);
151 assert_eq!(r.graph.ecount(), 0);
152 assert!(r.weights.is_empty());
153 assert!(r.types.is_empty());
154 }
155
156 #[test]
157 fn single_nonzero() {
158 let matrix: &[&[f64]] = &[&[3.5]];
159 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
160 assert_eq!(r.graph.vcount(), 2);
161 assert_eq!(r.graph.ecount(), 1);
162 assert_eq!(r.weights, vec![3.5]);
163 assert_eq!(r.types, vec![false, true]);
164 }
165
166 #[test]
167 fn single_zero() {
168 let matrix: &[&[f64]] = &[&[0.0]];
169 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
170 assert_eq!(r.graph.vcount(), 2);
171 assert_eq!(r.graph.ecount(), 0);
172 assert!(r.weights.is_empty());
173 }
174
175 #[test]
176 fn two_by_two() {
177 let matrix: &[&[f64]] = &[&[1.5, 0.0], &[0.0, 2.5]];
178 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
179 assert_eq!(r.graph.vcount(), 4);
180 assert_eq!(r.graph.ecount(), 2);
181 assert_eq!(r.weights, vec![1.5, 2.5]);
182 assert_eq!(r.types, vec![false, false, true, true]);
183 }
184
185 #[test]
186 fn negative_weight() {
187 let matrix: &[&[f64]] = &[&[-2.0]];
188 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
189 assert_eq!(r.graph.ecount(), 1);
190 assert_eq!(r.weights, vec![-2.0]);
191 }
192
193 #[test]
194 fn directed_out() {
195 let matrix: &[&[f64]] = &[&[1.0, 2.0]];
196 let r = weighted_biadjacency(matrix, true, BipartiteMode::Out).unwrap();
197 assert!(r.graph.is_directed());
198 assert_eq!(r.graph.ecount(), 2);
199 for eid in 0..r.graph.ecount() {
200 #[allow(clippy::cast_possible_truncation)]
201 let (s, t) = r.graph.edge(eid as u32).unwrap();
202 assert!(!r.types[s as usize], "source should be row");
203 assert!(r.types[t as usize], "target should be col");
204 }
205 assert_eq!(r.weights, vec![1.0, 2.0]);
206 }
207
208 #[test]
209 fn directed_in() {
210 let matrix: &[&[f64]] = &[&[1.0, 2.0]];
211 let r = weighted_biadjacency(matrix, true, BipartiteMode::In).unwrap();
212 assert!(r.graph.is_directed());
213 assert_eq!(r.graph.ecount(), 2);
214 for eid in 0..r.graph.ecount() {
215 #[allow(clippy::cast_possible_truncation)]
216 let (s, t) = r.graph.edge(eid as u32).unwrap();
217 assert!(r.types[s as usize], "source should be col");
218 assert!(!r.types[t as usize], "target should be row");
219 }
220 }
221
222 #[test]
223 fn directed_all_mutual() {
224 let matrix: &[&[f64]] = &[&[5.0]];
225 let r = weighted_biadjacency(matrix, true, BipartiteMode::All).unwrap();
226 assert!(r.graph.is_directed());
227 assert_eq!(r.graph.ecount(), 2);
228 assert_eq!(r.weights, vec![5.0, 5.0]);
229 }
230
231 #[test]
232 fn undirected_ignores_mode() {
233 let matrix: &[&[f64]] = &[&[1.0]];
234 let r_out = weighted_biadjacency(matrix, false, BipartiteMode::Out).unwrap();
235 let r_all = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
236 assert_eq!(r_out.graph.ecount(), 1);
237 assert_eq!(r_all.graph.ecount(), 1);
238 }
239
240 #[test]
241 fn ragged_matrix_rejected() {
242 let matrix: &[&[f64]] = &[&[1.0, 2.0], &[3.0]];
243 let result = weighted_biadjacency(matrix, false, BipartiteMode::All);
244 assert!(result.is_err());
245 }
246
247 #[test]
248 fn larger_matrix() {
249 let matrix: &[&[f64]] = &[&[1.0, 0.0, 3.0], &[0.0, 2.0, 0.0], &[4.0, 0.0, 0.0]];
250 let r = weighted_biadjacency(matrix, false, BipartiteMode::All).unwrap();
251 assert_eq!(r.graph.vcount(), 6); assert_eq!(r.graph.ecount(), 4); assert_eq!(r.weights.len(), 4);
254 assert_eq!(r.types, vec![false, false, false, true, true, true]);
255 }
256}