rust_igraph/algorithms/properties/
rwpe.rs1#![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
13
14use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
15
16pub fn rwpe(graph: &Graph, k_steps: usize) -> IgraphResult<Vec<Vec<f64>>> {
48 let nv = graph.vcount() as usize;
49
50 if k_steps == 0 {
51 return Ok(vec![Vec::new(); nv]);
52 }
53
54 if graph.is_directed() {
55 return Err(IgraphError::InvalidArgument(
56 "rwpe is defined for undirected graphs only".to_string(),
57 ));
58 }
59
60 let mut degrees: Vec<usize> = Vec::with_capacity(nv);
62 for v in 0..nv {
63 degrees.push(graph.degree(v as VertexId)?);
64 }
65
66 let mut result: Vec<Vec<f64>> = Vec::with_capacity(nv);
69
70 for src in 0..nv {
71 let mut pe = Vec::with_capacity(k_steps);
72
73 let mut prob: Vec<f64> = vec![0.0; nv];
75 prob[src] = 1.0;
76
77 for _ in 0..k_steps {
78 let mut next_prob: Vec<f64> = vec![0.0; nv];
79
80 for v in 0..nv {
81 if prob[v] == 0.0 {
82 continue;
83 }
84 let deg = degrees[v];
85 if deg == 0 {
86 next_prob[v] += prob[v];
88 continue;
89 }
90 let transition = prob[v] / deg as f64;
91 let neighbors = graph.neighbors(v as VertexId)?;
92 for &nei in &neighbors {
93 next_prob[nei as usize] += transition;
94 }
95 }
96
97 pe.push(next_prob[src]);
98 prob = next_prob;
99 }
100
101 result.push(pe);
102 }
103
104 Ok(result)
105}
106
107pub fn rwpe_vertices(
120 graph: &Graph,
121 vertices: &[VertexId],
122 k_steps: usize,
123) -> IgraphResult<Vec<Vec<f64>>> {
124 let nv = graph.vcount() as usize;
125
126 if k_steps == 0 {
127 return Ok(vec![Vec::new(); vertices.len()]);
128 }
129
130 if graph.is_directed() {
131 return Err(IgraphError::InvalidArgument(
132 "rwpe is defined for undirected graphs only".to_string(),
133 ));
134 }
135
136 for &v in vertices {
137 if v >= graph.vcount() {
138 return Err(IgraphError::VertexOutOfRange {
139 id: v,
140 n: graph.vcount(),
141 });
142 }
143 }
144
145 let mut degrees: Vec<usize> = Vec::with_capacity(nv);
146 for v in 0..nv {
147 degrees.push(graph.degree(v as VertexId)?);
148 }
149
150 let mut result: Vec<Vec<f64>> = Vec::with_capacity(vertices.len());
151
152 for &src in vertices {
153 let mut pe = Vec::with_capacity(k_steps);
154 let mut prob: Vec<f64> = vec![0.0; nv];
155 prob[src as usize] = 1.0;
156
157 for _ in 0..k_steps {
158 let mut next_prob: Vec<f64> = vec![0.0; nv];
159
160 for v in 0..nv {
161 if prob[v] == 0.0 {
162 continue;
163 }
164 let deg = degrees[v];
165 if deg == 0 {
166 next_prob[v] += prob[v];
167 continue;
168 }
169 let transition = prob[v] / deg as f64;
170 let neighbors = graph.neighbors(v as VertexId)?;
171 for &nei in &neighbors {
172 next_prob[nei as usize] += transition;
173 }
174 }
175
176 pe.push(next_prob[src as usize]);
177 prob = next_prob;
178 }
179
180 result.push(pe);
181 }
182
183 Ok(result)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 fn triangle() -> Graph {
191 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
192 }
193
194 fn cycle4() -> Graph {
195 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
196 }
197
198 fn path3() -> Graph {
199 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
200 }
201
202 #[test]
203 fn triangle_step1_no_return() {
204 let g = triangle();
205 let pe = rwpe(&g, 1).unwrap();
206 for row in &pe {
207 assert!((row[0] - 0.0).abs() < 1e-10);
208 }
209 }
210
211 #[test]
212 fn triangle_step2_return() {
213 let g = triangle();
214 let pe = rwpe(&g, 2).unwrap();
215 for row in &pe {
216 assert!((row[1] - 0.5).abs() < 1e-10);
217 }
218 }
219
220 #[test]
221 fn cycle4_return_probs() {
222 let g = cycle4();
223 let pe = rwpe(&g, 4).unwrap();
224 assert!((pe[0][0] - 0.0).abs() < 1e-10);
227 assert!((pe[0][1] - 0.5).abs() < 1e-10);
231 assert!((pe[0][2] - 0.0).abs() < 1e-10);
233 assert!(pe[0][3] > 0.0);
235 }
236
237 #[test]
238 fn isolated_vertex() {
239 let g = Graph::with_vertices(3);
240 let pe = rwpe(&g, 3).unwrap();
241 for row in &pe {
242 for &val in row {
243 assert!((val - 1.0).abs() < 1e-10);
244 }
245 }
246 }
247
248 #[test]
249 fn path_endpoints_differ() {
250 let g = path3(); let pe = rwpe(&g, 3).unwrap();
252 assert_ne!(pe[0], pe[1]);
254 assert_eq!(pe[0], pe[2]);
256 }
257
258 #[test]
259 fn zero_steps() {
260 let g = triangle();
261 let pe = rwpe(&g, 0).unwrap();
262 assert_eq!(pe.len(), 3);
263 for row in &pe {
264 assert!(row.is_empty());
265 }
266 }
267
268 #[test]
269 fn directed_error() {
270 let g = Graph::from_edges(&[(0, 1), (1, 2)], true, Some(3)).unwrap();
271 assert!(rwpe(&g, 2).is_err());
272 }
273
274 #[test]
275 fn rwpe_vertices_subset() {
276 let g = cycle4();
277 let full = rwpe(&g, 3).unwrap();
278 let subset = rwpe_vertices(&g, &[0, 2], 3).unwrap();
279 assert_eq!(subset.len(), 2);
280 assert_eq!(subset[0], full[0]);
281 assert_eq!(subset[1], full[2]);
282 }
283
284 #[test]
285 fn rwpe_vertices_invalid() {
286 let g = cycle4();
287 assert!(rwpe_vertices(&g, &[10], 2).is_err());
288 }
289
290 #[test]
291 fn probabilities_bounded() {
292 let g = cycle4();
293 let pe = rwpe(&g, 10).unwrap();
294 for row in &pe {
295 for &val in row {
296 assert!(val >= 0.0);
297 assert!(val <= 1.0);
298 }
299 }
300 }
301
302 #[test]
303 fn all_vertices_same_in_regular_graph() {
304 let g = cycle4();
305 let pe = rwpe(&g, 5).unwrap();
306 for row in pe.iter().skip(1) {
307 for (k, &val) in row.iter().enumerate() {
308 assert!((pe[0][k] - val).abs() < 1e-10);
309 }
310 }
311 }
312}