rust_igraph/algorithms/operators/
residual_graph.rs1use crate::core::{Graph, IgraphError, IgraphResult};
13
14#[derive(Debug, Clone)]
16pub struct ResidualGraphResult {
17 pub graph: Graph,
19 pub capacity: Vec<f64>,
21}
22
23pub fn residual_graph(
51 graph: &Graph,
52 capacity: &[f64],
53 flow: &[f64],
54) -> IgraphResult<ResidualGraphResult> {
55 let m = graph.ecount();
56
57 if capacity.len() != m {
58 return Err(IgraphError::InvalidArgument(format!(
59 "residual_graph: capacity length {} != edge count {m}",
60 capacity.len()
61 )));
62 }
63 if flow.len() != m {
64 return Err(IgraphError::InvalidArgument(format!(
65 "residual_graph: flow length {} != edge count {m}",
66 flow.len()
67 )));
68 }
69
70 let n = graph.vcount();
71 let mut new_graph = Graph::new(n, true)?;
72 let mut res_capacity: Vec<f64> = Vec::new();
73
74 for eid in 0..m {
75 let residual = capacity[eid] - flow[eid];
76 if residual > 0.0 {
77 let eid32 = u32::try_from(eid).map_err(|_| {
78 IgraphError::InvalidArgument("residual_graph: edge id overflow".into())
79 })?;
80 let (from, to) = graph.edge(eid32)?;
81 new_graph.add_edge(from, to)?;
82 res_capacity.push(residual);
83 }
84 }
85
86 Ok(ResidualGraphResult {
87 graph: new_graph,
88 capacity: res_capacity,
89 })
90}
91
92pub fn reverse_residual_graph(
121 graph: &Graph,
122 capacity: Option<&[f64]>,
123 flow: &[f64],
124) -> IgraphResult<Graph> {
125 let m = graph.ecount();
126
127 if let Some(cap) = capacity {
128 if cap.len() != m {
129 return Err(IgraphError::InvalidArgument(format!(
130 "reverse_residual_graph: capacity length {} != edge count {m}",
131 cap.len()
132 )));
133 }
134 }
135 if flow.len() != m {
136 return Err(IgraphError::InvalidArgument(format!(
137 "reverse_residual_graph: flow length {} != edge count {m}",
138 flow.len()
139 )));
140 }
141
142 let n = graph.vcount();
143 let mut new_graph = Graph::new(n, true)?;
144
145 for eid in 0..m {
146 let cap = capacity.map_or(1.0, |c| c[eid]);
147 let eid32 = u32::try_from(eid).map_err(|_| {
148 IgraphError::InvalidArgument("reverse_residual_graph: edge id overflow".into())
149 })?;
150 let (from, to) = graph.edge(eid32)?;
151
152 if flow[eid] > 0.0 {
153 new_graph.add_edge(from, to)?;
154 }
155 if flow[eid] < cap {
156 new_graph.add_edge(to, from)?;
157 }
158 }
159
160 Ok(new_graph)
161}
162
163#[cfg(test)]
164#[allow(clippy::float_cmp)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn residual_empty() {
170 let g = Graph::new(0, true).unwrap();
171 let result = residual_graph(&g, &[], &[]).unwrap();
172 assert_eq!(result.graph.vcount(), 0);
173 assert_eq!(result.graph.ecount(), 0);
174 }
175
176 #[test]
177 fn residual_no_flow() {
178 let mut g = Graph::new(3, true).unwrap();
179 g.add_edge(0, 1).unwrap();
180 g.add_edge(1, 2).unwrap();
181 let cap = vec![10.0, 5.0];
182 let flow = vec![0.0, 0.0];
183 let result = residual_graph(&g, &cap, &flow).unwrap();
184 assert_eq!(result.graph.ecount(), 2);
185 assert_eq!(result.capacity, vec![10.0, 5.0]);
186 }
187
188 #[test]
189 fn residual_saturated() {
190 let mut g = Graph::new(2, true).unwrap();
191 g.add_edge(0, 1).unwrap();
192 let cap = vec![5.0];
193 let flow = vec![5.0];
194 let result = residual_graph(&g, &cap, &flow).unwrap();
195 assert_eq!(result.graph.ecount(), 0);
196 assert!(result.capacity.is_empty());
197 }
198
199 #[test]
200 fn residual_partial_flow() {
201 let mut g = Graph::new(4, true).unwrap();
202 g.add_edge(0, 1).unwrap();
203 g.add_edge(1, 2).unwrap();
204 g.add_edge(0, 3).unwrap();
205 let cap = vec![10.0, 10.0, 5.0];
206 let flow = vec![7.0, 10.0, 3.0];
207 let result = residual_graph(&g, &cap, &flow).unwrap();
208 assert_eq!(result.graph.ecount(), 2);
210 assert_eq!(result.capacity, vec![3.0, 2.0]);
211 }
212
213 #[test]
214 fn residual_invalid_capacity_len() {
215 let mut g = Graph::new(2, true).unwrap();
216 g.add_edge(0, 1).unwrap();
217 assert!(residual_graph(&g, &[1.0, 2.0], &[0.0]).is_err());
218 }
219
220 #[test]
221 fn residual_invalid_flow_len() {
222 let mut g = Graph::new(2, true).unwrap();
223 g.add_edge(0, 1).unwrap();
224 assert!(residual_graph(&g, &[1.0], &[0.0, 0.0]).is_err());
225 }
226
227 #[test]
228 fn reverse_residual_empty() {
229 let g = Graph::new(0, true).unwrap();
230 let result = reverse_residual_graph(&g, Some(&[]), &[]).unwrap();
231 assert_eq!(result.vcount(), 0);
232 assert_eq!(result.ecount(), 0);
233 }
234
235 #[test]
236 fn reverse_residual_no_flow() {
237 let mut g = Graph::new(2, true).unwrap();
238 g.add_edge(0, 1).unwrap();
239 let cap = vec![5.0];
240 let flow = vec![0.0];
241 let result = reverse_residual_graph(&g, Some(&cap), &flow).unwrap();
242 assert_eq!(result.ecount(), 1);
244 assert_eq!(result.edge(0).unwrap(), (1, 0));
245 }
246
247 #[test]
248 fn reverse_residual_full_flow() {
249 let mut g = Graph::new(2, true).unwrap();
250 g.add_edge(0, 1).unwrap();
251 let cap = vec![5.0];
252 let flow = vec![5.0];
253 let result = reverse_residual_graph(&g, Some(&cap), &flow).unwrap();
254 assert_eq!(result.ecount(), 1);
256 assert_eq!(result.edge(0).unwrap(), (0, 1));
257 }
258
259 #[test]
260 fn reverse_residual_partial() {
261 let mut g = Graph::new(2, true).unwrap();
262 g.add_edge(0, 1).unwrap();
263 let cap = vec![10.0];
264 let flow = vec![7.0];
265 let result = reverse_residual_graph(&g, Some(&cap), &flow).unwrap();
266 assert_eq!(result.ecount(), 2);
268 assert_eq!(result.edge(0).unwrap(), (0, 1));
269 assert_eq!(result.edge(1).unwrap(), (1, 0));
270 }
271
272 #[test]
273 fn reverse_residual_no_capacity() {
274 let mut g = Graph::new(2, true).unwrap();
275 g.add_edge(0, 1).unwrap();
276 let flow = vec![0.5];
277 let result = reverse_residual_graph(&g, None, &flow).unwrap();
278 assert_eq!(result.ecount(), 2);
280 }
281
282 #[test]
283 fn reverse_residual_invalid_len() {
284 let mut g = Graph::new(2, true).unwrap();
285 g.add_edge(0, 1).unwrap();
286 assert!(reverse_residual_graph(&g, Some(&[1.0, 2.0]), &[0.0]).is_err());
287 assert!(reverse_residual_graph(&g, Some(&[1.0]), &[0.0, 0.0]).is_err());
288 }
289}