rust_igraph/algorithms/properties/
running_mean.rs1use crate::core::{IgraphError, IgraphResult};
9
10pub fn running_mean(data: &[f64], binwidth: usize) -> IgraphResult<Vec<f64>> {
34 if binwidth < 1 {
35 return Err(IgraphError::InvalidArgument(
36 "running_mean: binwidth must be at least 1".into(),
37 ));
38 }
39
40 if data.len() < binwidth {
41 return Err(IgraphError::InvalidArgument(format!(
42 "running_mean: data length {} is smaller than binwidth {binwidth}",
43 data.len()
44 )));
45 }
46
47 let result_len = data.len() - binwidth + 1;
48 let mut result: Vec<f64> = Vec::with_capacity(result_len);
49
50 #[allow(clippy::cast_precision_loss)]
51 let bw_f64 = binwidth as f64;
52
53 let mut sum: f64 = data[..binwidth].iter().sum();
54 result.push(sum / bw_f64);
55
56 for i in 1..result_len {
57 sum -= data[i - 1];
58 sum += data[i + binwidth - 1];
59 result.push(sum / bw_f64);
60 }
61
62 Ok(result)
63}
64
65pub fn expand_path_to_pairs(path: &[u32]) -> Vec<(u32, u32)> {
83 if path.len() < 2 {
84 return Vec::new();
85 }
86 path.windows(2).map(|w| (w[0], w[1])).collect()
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn running_mean_basic() {
95 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
96 let r = running_mean(&data, 3).unwrap();
97 assert_eq!(r.len(), 3);
98 assert!((r[0] - 2.0).abs() < 1e-10);
99 assert!((r[1] - 3.0).abs() < 1e-10);
100 assert!((r[2] - 4.0).abs() < 1e-10);
101 }
102
103 #[test]
104 fn running_mean_binwidth_1() {
105 let data = vec![5.0, 3.0, 7.0];
106 let r = running_mean(&data, 1).unwrap();
107 assert_eq!(r.len(), 3);
108 assert!((r[0] - 5.0).abs() < 1e-10);
109 assert!((r[1] - 3.0).abs() < 1e-10);
110 assert!((r[2] - 7.0).abs() < 1e-10);
111 }
112
113 #[test]
114 fn running_mean_binwidth_equals_len() {
115 let data = vec![1.0, 2.0, 3.0];
116 let r = running_mean(&data, 3).unwrap();
117 assert_eq!(r.len(), 1);
118 assert!((r[0] - 2.0).abs() < 1e-10);
119 }
120
121 #[test]
122 fn running_mean_binwidth_too_large() {
123 let data = vec![1.0, 2.0];
124 let err = running_mean(&data, 3).unwrap_err();
125 assert!(matches!(err, IgraphError::InvalidArgument(_)));
126 }
127
128 #[test]
129 fn running_mean_binwidth_zero() {
130 let data = vec![1.0, 2.0, 3.0];
131 let err = running_mean(&data, 0).unwrap_err();
132 assert!(matches!(err, IgraphError::InvalidArgument(_)));
133 }
134
135 #[test]
136 fn running_mean_constant() {
137 let data = vec![4.0; 10];
138 let r = running_mean(&data, 5).unwrap();
139 assert_eq!(r.len(), 6);
140 for v in &r {
141 assert!((*v - 4.0).abs() < 1e-10);
142 }
143 }
144
145 #[test]
146 fn expand_pairs_basic() {
147 let pairs = expand_path_to_pairs(&[0, 1, 2, 3]);
148 assert_eq!(pairs, vec![(0, 1), (1, 2), (2, 3)]);
149 }
150
151 #[test]
152 fn expand_pairs_single_edge() {
153 let pairs = expand_path_to_pairs(&[5, 10]);
154 assert_eq!(pairs, vec![(5, 10)]);
155 }
156
157 #[test]
158 fn expand_pairs_empty() {
159 let pairs = expand_path_to_pairs(&[]);
160 assert!(pairs.is_empty());
161 }
162
163 #[test]
164 fn expand_pairs_single_vertex() {
165 let pairs = expand_path_to_pairs(&[7]);
166 assert!(pairs.is_empty());
167 }
168
169 #[test]
170 fn expand_pairs_cycle() {
171 let pairs = expand_path_to_pairs(&[0, 1, 2, 0]);
172 assert_eq!(pairs, vec![(0, 1), (1, 2), (2, 0)]);
173 }
174}