rust_igraph/algorithms/properties/
joint_degree_matrix.rs1use crate::core::{Graph, IgraphError, IgraphResult};
12
13pub fn joint_degree_matrix(graph: &Graph, weights: Option<&[f64]>) -> IgraphResult<Vec<Vec<f64>>> {
45 let n = graph.vcount();
46 let ecount = graph.ecount();
47
48 if let Some(w) = weights {
49 if w.len() != ecount {
50 return Err(IgraphError::InvalidArgument(format!(
51 "joint_degree_matrix: weights length ({}) does not match edge count ({ecount})",
52 w.len()
53 )));
54 }
55 }
56
57 if ecount == 0 {
58 return Ok(Vec::new());
59 }
60
61 let n_usize = n as usize;
62 let directed = graph.is_directed();
63
64 let m_u32 =
65 u32::try_from(ecount).map_err(|_| IgraphError::Internal("ecount exceeds u32::MAX"))?;
66
67 if directed {
68 let mut out_deg = vec![0u32; n_usize];
70 let mut in_deg = vec![0u32; n_usize];
71
72 for eid in 0..m_u32 {
73 let (from, to) = graph.edge(eid)?;
74 out_deg[from as usize] += 1;
75 in_deg[to as usize] += 1;
76 }
77
78 let max_out = out_deg.iter().copied().max().unwrap_or(0) as usize;
79 let max_in = in_deg.iter().copied().max().unwrap_or(0) as usize;
80
81 if max_out == 0 || max_in == 0 {
82 return Ok(Vec::new());
83 }
84
85 let mut jdm = vec![vec![0.0_f64; max_in]; max_out];
86
87 for eid in 0..m_u32 {
88 let (from, to) = graph.edge(eid)?;
89 let d_out = out_deg[from as usize] as usize;
90 let d_in = in_deg[to as usize] as usize;
91 let w = weights.map_or(1.0, |ws| ws[eid as usize]);
92 jdm[d_out - 1][d_in - 1] += w;
93 }
94
95 Ok(jdm)
96 } else {
97 let mut deg = vec![0u32; n_usize];
99
100 for eid in 0..m_u32 {
101 let (from, to) = graph.edge(eid)?;
102 deg[from as usize] += 1;
103 deg[to as usize] += 1;
104 }
105
106 let max_deg = deg.iter().copied().max().unwrap_or(0) as usize;
107
108 if max_deg == 0 {
109 return Ok(Vec::new());
110 }
111
112 let mut jdm = vec![vec![0.0_f64; max_deg]; max_deg];
113
114 for eid in 0..m_u32 {
115 let (from, to) = graph.edge(eid)?;
116 let d1 = deg[from as usize] as usize;
117 let d2 = deg[to as usize] as usize;
118 let w = weights.map_or(1.0, |ws| ws[eid as usize]);
119 jdm[d1 - 1][d2 - 1] += w;
120 if d1 != d2 {
121 jdm[d2 - 1][d1 - 1] += w;
122 }
123 }
124
125 Ok(jdm)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn close(a: f64, b: f64) -> bool {
134 (a - b).abs() < 1e-10
135 }
136
137 #[test]
138 fn empty_graph() {
139 let g = Graph::with_vertices(0);
140 let jdm = joint_degree_matrix(&g, None).unwrap();
141 assert!(jdm.is_empty());
142 }
143
144 #[test]
145 fn no_edges() {
146 let g = Graph::with_vertices(5);
147 let jdm = joint_degree_matrix(&g, None).unwrap();
148 assert!(jdm.is_empty());
149 }
150
151 #[test]
152 fn triangle_undirected() {
153 let mut g = Graph::with_vertices(3);
155 g.add_edge(0, 1).unwrap();
156 g.add_edge(1, 2).unwrap();
157 g.add_edge(2, 0).unwrap();
158 let jdm = joint_degree_matrix(&g, None).unwrap();
159 assert_eq!(jdm.len(), 2);
160 assert_eq!(jdm[0].len(), 2);
161 assert!(close(jdm[0][0], 0.0));
162 assert!(close(jdm[0][1], 0.0));
163 assert!(close(jdm[1][0], 0.0));
164 assert!(close(jdm[1][1], 3.0));
165 }
166
167 #[test]
168 fn path_3_undirected() {
169 let mut g = Graph::with_vertices(3);
174 g.add_edge(0, 1).unwrap();
175 g.add_edge(1, 2).unwrap();
176 let jdm = joint_degree_matrix(&g, None).unwrap();
177 assert_eq!(jdm.len(), 2);
178 assert!(close(jdm[0][0], 0.0));
179 assert!(close(jdm[0][1], 2.0));
180 assert!(close(jdm[1][0], 2.0));
181 assert!(close(jdm[1][1], 0.0));
182 }
183
184 #[test]
185 fn star_undirected() {
186 let mut g = Graph::with_vertices(4);
190 g.add_edge(0, 1).unwrap();
191 g.add_edge(0, 2).unwrap();
192 g.add_edge(0, 3).unwrap();
193 let jdm = joint_degree_matrix(&g, None).unwrap();
194 assert_eq!(jdm.len(), 3);
195 assert!(close(jdm[0][2], 3.0));
196 assert!(close(jdm[2][0], 3.0));
197 assert!(close(jdm[0][0], 0.0));
198 assert!(close(jdm[1][1], 0.0));
199 assert!(close(jdm[2][2], 0.0));
200 }
201
202 #[test]
203 fn directed_simple() {
204 let mut g = Graph::new(3, true).unwrap();
209 g.add_edge(0, 1).unwrap();
210 g.add_edge(1, 2).unwrap();
211 let jdm = joint_degree_matrix(&g, None).unwrap();
212 assert_eq!(jdm.len(), 1);
213 assert_eq!(jdm[0].len(), 1);
214 assert!(close(jdm[0][0], 2.0));
215 }
216
217 #[test]
218 fn directed_star() {
219 let mut g = Graph::new(4, true).unwrap();
225 g.add_edge(0, 1).unwrap();
226 g.add_edge(0, 2).unwrap();
227 g.add_edge(0, 3).unwrap();
228 let jdm = joint_degree_matrix(&g, None).unwrap();
229 assert_eq!(jdm.len(), 3);
230 assert_eq!(jdm[0].len(), 1);
231 assert!(close(jdm[2][0], 3.0));
232 assert!(close(jdm[0][0], 0.0));
233 assert!(close(jdm[1][0], 0.0));
234 }
235
236 #[test]
237 fn weighted_undirected() {
238 let mut g = Graph::with_vertices(3);
243 g.add_edge(0, 1).unwrap();
244 g.add_edge(1, 2).unwrap();
245 let jdm = joint_degree_matrix(&g, Some(&[3.0, 2.0])).unwrap();
246 assert!(close(jdm[0][1], 5.0));
247 assert!(close(jdm[1][0], 5.0));
248 assert!(close(jdm[0][0], 0.0));
249 assert!(close(jdm[1][1], 0.0));
250 }
251
252 #[test]
253 fn self_loop_undirected() {
254 let mut g = Graph::with_vertices(2);
258 g.add_edge(0, 0).unwrap();
259 g.add_edge(0, 1).unwrap();
260 let jdm = joint_degree_matrix(&g, None).unwrap();
261 assert_eq!(jdm.len(), 3);
262 assert!(close(jdm[2][2], 1.0));
263 assert!(close(jdm[2][0], 1.0));
264 assert!(close(jdm[0][2], 1.0));
265 }
266
267 #[test]
268 fn total_equals_edge_count_undirected() {
269 let mut g = Graph::with_vertices(5);
275 g.add_edge(0, 1).unwrap();
276 g.add_edge(1, 2).unwrap();
277 g.add_edge(2, 3).unwrap();
278 g.add_edge(3, 4).unwrap();
279 g.add_edge(4, 0).unwrap(); let jdm = joint_degree_matrix(&g, None).unwrap();
281 assert!(close(jdm[1][1], 5.0));
283 let total: f64 = jdm.iter().flat_map(|r| r.iter()).sum();
285 assert!(close(total, 5.0));
287 }
288
289 #[test]
290 fn weights_mismatch_errors() {
291 let mut g = Graph::with_vertices(2);
292 g.add_edge(0, 1).unwrap();
293 assert!(joint_degree_matrix(&g, Some(&[1.0, 2.0])).is_err());
294 }
295
296 #[test]
297 fn k4_undirected() {
298 let mut g = Graph::with_vertices(4);
300 for u in 0..4u32 {
301 for v in (u + 1)..4 {
302 g.add_edge(u, v).unwrap();
303 }
304 }
305 let jdm = joint_degree_matrix(&g, None).unwrap();
306 assert_eq!(jdm.len(), 3);
307 assert!(close(jdm[2][2], 6.0));
308 assert!(close(jdm[0][0], 0.0));
309 }
310}