Skip to main content

problemreductions/models/graph/
undirected_flow_lower_bounds.rs

1//! Undirected flow with lower bounds problem implementation.
2//!
3//! Given an undirected graph with per-edge lower and upper capacities, a
4//! source, a sink, and a required net flow value, determine whether there
5//! exists an orientation and feasible directed flow meeting all bounds.
6//!
7//! The configuration space stores one binary orientation choice per edge in the
8//! graph's edge order:
9//! - `0` means orient the stored edge `(u, v)` as `u -> v`
10//! - `1` means orient it as `v -> u`
11//!
12//! For a fixed orientation, feasibility reduces to a directed circulation with
13//! lower bounds, so the registered exact complexity matches brute-force
14//! enumeration over the `2^|E|` edge orientations.
15
16use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
17use crate::topology::{Graph, SimpleGraph};
18use crate::traits::Problem;
19use serde::{Deserialize, Serialize};
20use std::collections::VecDeque;
21
22inventory::submit! {
23    ProblemSchemaEntry {
24        name: "UndirectedFlowLowerBounds",
25        display_name: "Undirected Flow with Lower Bounds",
26        aliases: &[],
27        dimensions: &[],
28        module_path: module_path!(),
29        description: "Determine whether an undirected lower-bounded flow of value at least R exists",
30        fields: &[
31            FieldInfo { name: "graph", type_name: "SimpleGraph", description: "Undirected graph G=(V,E)" },
32            FieldInfo { name: "capacities", type_name: "Vec<u64>", description: "Upper capacities c(e) in graph edge order" },
33            FieldInfo { name: "lower_bounds", type_name: "Vec<u64>", description: "Lower bounds l(e) in graph edge order" },
34            FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
35            FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
36            FieldInfo { name: "requirement", type_name: "u64", description: "Required net inflow R at sink t" },
37        ],
38    }
39}
40
41inventory::submit! {
42    ProblemSizeFieldEntry {
43        name: "UndirectedFlowLowerBounds",
44        fields: &["num_vertices", "num_edges"],
45    }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct UndirectedFlowLowerBounds {
50    graph: SimpleGraph,
51    capacities: Vec<u64>,
52    lower_bounds: Vec<u64>,
53    source: usize,
54    sink: usize,
55    requirement: u64,
56}
57
58impl UndirectedFlowLowerBounds {
59    pub fn new(
60        graph: SimpleGraph,
61        capacities: Vec<u64>,
62        lower_bounds: Vec<u64>,
63        source: usize,
64        sink: usize,
65        requirement: u64,
66    ) -> Self {
67        assert_eq!(
68            capacities.len(),
69            graph.num_edges(),
70            "capacities length must match graph num_edges"
71        );
72        assert_eq!(
73            lower_bounds.len(),
74            graph.num_edges(),
75            "lower_bounds length must match graph num_edges"
76        );
77
78        let num_vertices = graph.num_vertices();
79        assert!(
80            source < num_vertices,
81            "source must be less than num_vertices ({num_vertices})"
82        );
83        assert!(
84            sink < num_vertices,
85            "sink must be less than num_vertices ({num_vertices})"
86        );
87        assert!(source != sink, "source and sink must be distinct");
88        assert!(requirement >= 1, "requirement must be at least 1");
89
90        for (edge_index, (&lower, &upper)) in lower_bounds.iter().zip(&capacities).enumerate() {
91            assert!(
92                lower <= upper,
93                "lower bound at edge {edge_index} must be at most its capacity"
94            );
95        }
96
97        Self {
98            graph,
99            capacities,
100            lower_bounds,
101            source,
102            sink,
103            requirement,
104        }
105    }
106
107    pub fn graph(&self) -> &SimpleGraph {
108        &self.graph
109    }
110
111    pub fn capacities(&self) -> &[u64] {
112        &self.capacities
113    }
114
115    pub fn lower_bounds(&self) -> &[u64] {
116        &self.lower_bounds
117    }
118
119    pub fn source(&self) -> usize {
120        self.source
121    }
122
123    pub fn sink(&self) -> usize {
124        self.sink
125    }
126
127    pub fn requirement(&self) -> u64 {
128        self.requirement
129    }
130
131    pub fn num_vertices(&self) -> usize {
132        self.graph.num_vertices()
133    }
134
135    pub fn num_edges(&self) -> usize {
136        self.graph.num_edges()
137    }
138
139    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
140        self.evaluate(config).0
141    }
142
143    fn total_capacity(&self) -> Option<u128> {
144        self.capacities.iter().try_fold(0_u128, |acc, &capacity| {
145            acc.checked_add(u128::from(capacity))
146        })
147    }
148
149    fn has_feasible_orientation(&self, config: &[usize]) -> bool {
150        if config.len() != self.num_edges() {
151            return false;
152        }
153
154        let Some(total_capacity) = self.total_capacity() else {
155            return false;
156        };
157        let requirement = u128::from(self.requirement);
158        if requirement > total_capacity {
159            return false;
160        }
161
162        let node_count = self.num_vertices();
163        let super_source = node_count;
164        let super_sink = node_count + 1;
165        let mut network = ResidualNetwork::new(node_count + 2);
166        let mut balances = vec![0_i128; node_count];
167
168        for (edge_index, ((u, v), &orientation)) in self
169            .graph
170            .edges()
171            .into_iter()
172            .zip(config.iter())
173            .enumerate()
174        {
175            let (from, to) = match orientation {
176                0 => (u, v),
177                1 => (v, u),
178                _ => return false,
179            };
180            let lower = u128::from(self.lower_bounds[edge_index]);
181            let upper = u128::from(self.capacities[edge_index]);
182            if !add_lower_bounded_edge(&mut network, &mut balances, from, to, lower, upper) {
183                return false;
184            }
185        }
186
187        if !add_lower_bounded_edge(
188            &mut network,
189            &mut balances,
190            self.sink,
191            self.source,
192            requirement,
193            total_capacity,
194        ) {
195            return false;
196        }
197
198        let mut demand = 0_u128;
199        for (vertex, balance) in balances.into_iter().enumerate() {
200            if balance > 0 {
201                let needed = u128::try_from(balance).expect("positive i128 balance fits u128");
202                demand = match demand.checked_add(needed) {
203                    Some(value) => value,
204                    None => return false,
205                };
206                network.add_edge(super_source, vertex, needed);
207            } else if balance < 0 {
208                let needed = u128::try_from(-balance).expect("negative i128 balance fits u128");
209                network.add_edge(vertex, super_sink, needed);
210            }
211        }
212
213        network.max_flow(super_source, super_sink) == demand
214    }
215}
216
217impl Problem for UndirectedFlowLowerBounds {
218    const NAME: &'static str = "UndirectedFlowLowerBounds";
219    type Value = crate::types::Or;
220
221    fn variant() -> Vec<(&'static str, &'static str)> {
222        crate::variant_params![]
223    }
224
225    fn dims(&self) -> Vec<usize> {
226        vec![2; self.num_edges()]
227    }
228
229    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
230        crate::types::Or(self.has_feasible_orientation(config))
231    }
232}
233
234crate::declare_variants! {
235    default UndirectedFlowLowerBounds => "2^num_edges",
236}
237
238#[cfg(feature = "example-db")]
239pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
240    vec![crate::example_db::specs::ModelExampleSpec {
241        id: "undirected_flow_lower_bounds",
242        instance: Box::new(UndirectedFlowLowerBounds::new(
243            SimpleGraph::new(
244                6,
245                vec![(0, 1), (0, 2), (1, 3), (2, 3), (1, 4), (3, 5), (4, 5)],
246            ),
247            vec![2, 2, 2, 2, 1, 3, 2],
248            vec![1, 1, 0, 0, 1, 0, 1],
249            0,
250            5,
251            3,
252        )),
253        optimal_config: vec![0, 0, 0, 0, 0, 0, 0],
254        optimal_value: serde_json::json!(true),
255    }]
256}
257
258#[derive(Debug, Clone)]
259struct ResidualEdge {
260    to: usize,
261    rev: usize,
262    capacity: u128,
263}
264
265#[derive(Debug, Clone)]
266struct ResidualNetwork {
267    adjacency: Vec<Vec<ResidualEdge>>,
268}
269
270impl ResidualNetwork {
271    fn new(num_vertices: usize) -> Self {
272        Self {
273            adjacency: vec![Vec::new(); num_vertices],
274        }
275    }
276
277    fn add_edge(&mut self, from: usize, to: usize, capacity: u128) {
278        let reverse_at_to = self.adjacency[to].len();
279        let reverse_at_from = self.adjacency[from].len();
280        self.adjacency[from].push(ResidualEdge {
281            to,
282            rev: reverse_at_to,
283            capacity,
284        });
285        self.adjacency[to].push(ResidualEdge {
286            to: from,
287            rev: reverse_at_from,
288            capacity: 0,
289        });
290    }
291
292    fn max_flow(&mut self, source: usize, sink: usize) -> u128 {
293        let mut total_flow = 0_u128;
294
295        loop {
296            let mut parent: Vec<Option<(usize, usize)>> = vec![None; self.adjacency.len()];
297            let mut queue = VecDeque::new();
298            queue.push_back(source);
299            parent[source] = Some((source, usize::MAX));
300
301            while let Some(vertex) = queue.pop_front() {
302                if vertex == sink {
303                    break;
304                }
305
306                for (edge_index, edge) in self.adjacency[vertex].iter().enumerate() {
307                    if edge.capacity == 0 || parent[edge.to].is_some() {
308                        continue;
309                    }
310                    parent[edge.to] = Some((vertex, edge_index));
311                    queue.push_back(edge.to);
312                }
313            }
314
315            if parent[sink].is_none() {
316                return total_flow;
317            }
318
319            let mut path_flow = u128::MAX;
320            let mut vertex = sink;
321            while vertex != source {
322                let (prev, edge_index) = parent[vertex].expect("sink is reachable");
323                path_flow = path_flow.min(self.adjacency[prev][edge_index].capacity);
324                vertex = prev;
325            }
326
327            let mut vertex = sink;
328            while vertex != source {
329                let (prev, edge_index) = parent[vertex].expect("sink is reachable");
330                let reverse_edge = self.adjacency[prev][edge_index].rev;
331                self.adjacency[prev][edge_index].capacity -= path_flow;
332                self.adjacency[vertex][reverse_edge].capacity += path_flow;
333                vertex = prev;
334            }
335
336            total_flow += path_flow;
337        }
338    }
339}
340
341fn add_lower_bounded_edge(
342    network: &mut ResidualNetwork,
343    balances: &mut [i128],
344    from: usize,
345    to: usize,
346    lower: u128,
347    upper: u128,
348) -> bool {
349    if lower > upper {
350        return false;
351    }
352
353    let residual = upper - lower;
354    if residual > 0 {
355        network.add_edge(from, to, residual);
356    }
357
358    let Ok(lower_signed) = i128::try_from(lower) else {
359        return false;
360    };
361    balances[from] -= lower_signed;
362    balances[to] += lower_signed;
363    true
364}
365
366#[cfg(test)]
367#[path = "../../unit_tests/models/graph/undirected_flow_lower_bounds.rs"]
368mod tests;