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::{CreateSpec, ProblemSchemaEntry};
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        category: crate::registry::ProblemCategory::Graph,
29        module_path: module_path!(),
30        description: "Determine whether an undirected lower-bounded flow of value at least R exists",
31        fields: UndirectedFlowLowerBoundsCreateSpec::FIELDS,
32    }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(try_from = "UndirectedFlowLowerBoundsCreateSpec")]
37pub struct UndirectedFlowLowerBounds {
38    graph: SimpleGraph,
39    capacities: Vec<i64>,
40    lower_bounds: Vec<i64>,
41    source: usize,
42    sink: usize,
43    requirement: i64,
44}
45
46#[derive(Debug, Deserialize, crate::CreateSpec)]
47struct UndirectedFlowLowerBoundsCreateSpec {
48    /// Undirected graph.
49    graph: SimpleGraph,
50    /// Upper capacities in graph edge order.
51    capacities: Vec<i64>,
52    /// Lower bounds in graph edge order.
53    lower_bounds: Vec<i64>,
54    /// Source vertex.
55    source: usize,
56    /// Sink vertex.
57    sink: usize,
58    /// Required net inflow at the sink.
59    requirement: i64,
60}
61impl TryFrom<UndirectedFlowLowerBoundsCreateSpec> for UndirectedFlowLowerBounds {
62    type Error = crate::registry::ConstructionError;
63    fn try_from(spec: UndirectedFlowLowerBoundsCreateSpec) -> Result<Self, Self::Error> {
64        Self::try_new(
65            spec.graph,
66            spec.capacities,
67            spec.lower_bounds,
68            spec.source,
69            spec.sink,
70            spec.requirement,
71        )
72    }
73}
74
75impl UndirectedFlowLowerBounds {
76    pub fn new(
77        graph: SimpleGraph,
78        capacities: Vec<i64>,
79        lower_bounds: Vec<i64>,
80        source: usize,
81        sink: usize,
82        requirement: i64,
83    ) -> Self {
84        Self::try_new(graph, capacities, lower_bounds, source, sink, requirement)
85            .unwrap_or_else(|error| panic!("{error}"))
86    }
87
88    fn try_new(
89        graph: SimpleGraph,
90        capacities: Vec<i64>,
91        lower_bounds: Vec<i64>,
92        source: usize,
93        sink: usize,
94        requirement: i64,
95    ) -> Result<Self, crate::registry::ConstructionError> {
96        if capacities.len() != graph.num_edges() {
97            return Err(crate::registry::ConstructionError::length_mismatch(
98                "capacities",
99                capacities.len(),
100                graph.num_edges(),
101            ));
102        }
103        if lower_bounds.len() != graph.num_edges() {
104            return Err(crate::registry::ConstructionError::length_mismatch(
105                "lower_bounds",
106                lower_bounds.len(),
107                graph.num_edges(),
108            ));
109        }
110
111        let num_vertices = graph.num_vertices();
112        if capacities.iter().any(|&capacity| capacity < 0) {
113            return Err("capacities must be nonnegative".into());
114        }
115        if source >= num_vertices {
116            return Err(format!("source must be less than num_vertices ({num_vertices})").into());
117        }
118        if sink >= num_vertices {
119            return Err(format!("sink must be less than num_vertices ({num_vertices})").into());
120        }
121        if source == sink {
122            return Err("source and sink must be distinct".into());
123        }
124        if requirement < 1 {
125            return Err("requirement must be at least 1".into());
126        }
127
128        for (edge_index, (&lower, &upper)) in lower_bounds.iter().zip(&capacities).enumerate() {
129            if lower < 0 || lower > upper {
130                return Err(format!(
131                    "lower bound at edge {edge_index} must be nonnegative and at most its capacity"
132                )
133                .into());
134            }
135        }
136
137        Ok(Self {
138            graph,
139            capacities,
140            lower_bounds,
141            source,
142            sink,
143            requirement,
144        })
145    }
146
147    pub fn graph(&self) -> &SimpleGraph {
148        &self.graph
149    }
150
151    pub fn capacities(&self) -> &[i64] {
152        &self.capacities
153    }
154
155    pub fn lower_bounds(&self) -> &[i64] {
156        &self.lower_bounds
157    }
158
159    pub fn source(&self) -> usize {
160        self.source
161    }
162
163    pub fn sink(&self) -> usize {
164        self.sink
165    }
166
167    pub fn requirement(&self) -> i64 {
168        self.requirement
169    }
170
171    /// Maximum bit length of the edge capacities, with a minimum of one.
172    pub fn max_capacity_bits(&self) -> u64 {
173        crate::types::max_numeric_magnitude_bits(self.capacities.iter().copied())
174    }
175
176    pub fn num_vertices(&self) -> usize {
177        self.graph.num_vertices()
178    }
179
180    pub fn num_edges(&self) -> usize {
181        self.graph.num_edges()
182    }
183
184    pub fn is_valid_solution(
185        &self,
186        config: &[bool],
187    ) -> Result<bool, crate::traits::EvaluationError> {
188        if config.len() != self.num_edges() {
189            return Err(crate::traits::EvaluationError::InvalidConfiguration(
190                "edge-orientation length does not match the graph".into(),
191            ));
192        }
193        self.has_feasible_orientation(config)
194    }
195
196    fn total_capacity(&self) -> Result<i64, crate::traits::EvaluationError> {
197        self.capacities.iter().try_fold(0_i64, |total, &capacity| {
198            total.checked_add(capacity).ok_or_else(|| {
199                crate::traits::EvaluationError::IntegerOverflow(
200                    "summing undirected flow capacities".into(),
201                )
202            })
203        })
204    }
205
206    fn has_feasible_orientation(
207        &self,
208        config: &[bool],
209    ) -> Result<bool, crate::traits::EvaluationError> {
210        if config.len() != self.num_edges() {
211            return Ok(false);
212        }
213
214        let total_capacity = self.total_capacity()?;
215        let requirement = self.requirement;
216        if requirement > total_capacity {
217            return Ok(false);
218        }
219
220        let node_count = self.num_vertices();
221        let super_source = node_count;
222        let super_sink = node_count + 1;
223        let mut network = ResidualNetwork::new(node_count + 2);
224        let mut balances = vec![0_i64; node_count];
225
226        for (edge_index, ((u, v), &orientation)) in self
227            .graph
228            .edges()
229            .into_iter()
230            .zip(config.iter())
231            .enumerate()
232        {
233            let (from, to) = if orientation { (v, u) } else { (u, v) };
234            let lower = self.lower_bounds[edge_index];
235            let upper = self.capacities[edge_index];
236            if !add_lower_bounded_edge(&mut network, &mut balances, from, to, lower, upper)? {
237                return Ok(false);
238            }
239        }
240
241        if !add_lower_bounded_edge(
242            &mut network,
243            &mut balances,
244            self.sink,
245            self.source,
246            requirement,
247            total_capacity,
248        )? {
249            return Ok(false);
250        }
251
252        let mut demand = 0_i64;
253        for (vertex, balance) in balances.into_iter().enumerate() {
254            if balance > 0 {
255                demand = match demand.checked_add(balance) {
256                    Some(value) => value,
257                    None => {
258                        return Err(crate::traits::EvaluationError::IntegerOverflow(
259                            "summing lower-bound flow demand".into(),
260                        ));
261                    }
262                };
263                network.add_edge(super_source, vertex, balance);
264            } else if balance < 0 {
265                network.add_edge(
266                    vertex,
267                    super_sink,
268                    balance.checked_neg().ok_or_else(|| {
269                        crate::traits::EvaluationError::IntegerOverflow(
270                            "negating lower-bound flow balance".into(),
271                        )
272                    })?,
273                );
274            }
275        }
276
277        Ok(network.max_flow(super_source, super_sink)? == demand)
278    }
279}
280
281impl Problem for UndirectedFlowLowerBounds {
282    const NAME: &'static str = "UndirectedFlowLowerBounds";
283    type Solution = Vec<bool>;
284    type Value = crate::types::Or;
285
286    crate::problem_parameters![
287        ("max_capacity_bits", max_capacity_bits),
288        ("num_edges", num_edges),
289        ("num_vertices", num_vertices),
290    ];
291
292    fn variant() -> Vec<(&'static str, &'static str)> {
293        crate::variant_params![]
294    }
295
296    fn evaluate(
297        &self,
298        config: &Self::Solution,
299    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
300        Ok(crate::types::Or(self.is_valid_solution(config)?))
301    }
302}
303
304impl crate::solvers::BruteForceProblem for UndirectedFlowLowerBounds {
305    fn dimensions(&self) -> Vec<usize> {
306        vec![2; self.num_edges()]
307    }
308}
309
310crate::declare_variants! {
311    default UndirectedFlowLowerBounds => "2^num_edges" create UndirectedFlowLowerBoundsCreateSpec,
312}
313
314crate::register_brute_force! {
315    UndirectedFlowLowerBounds decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
316}
317
318#[cfg(feature = "example-db")]
319pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
320    vec![crate::example_db::specs::ModelExampleSpec {
321        id: "undirected_flow_lower_bounds",
322        instance: Box::new(UndirectedFlowLowerBounds::new(
323            SimpleGraph::new(
324                6,
325                vec![(0, 1), (0, 2), (1, 3), (2, 3), (1, 4), (3, 5), (4, 5)],
326            ),
327            vec![2, 2, 2, 2, 1, 3, 2],
328            vec![1, 1, 0, 0, 1, 0, 1],
329            0,
330            5,
331            3,
332        )),
333        optimal_config: serde_json::json!(vec![false, false, false, false, false, false, false]),
334        optimal_value: serde_json::json!(true),
335    }]
336}
337
338#[derive(Debug, Clone)]
339struct ResidualEdge {
340    to: usize,
341    rev: usize,
342    capacity: i64,
343}
344
345#[derive(Debug, Clone)]
346struct ResidualNetwork {
347    adjacency: Vec<Vec<ResidualEdge>>,
348}
349
350impl ResidualNetwork {
351    fn new(num_vertices: usize) -> Self {
352        Self {
353            adjacency: vec![Vec::new(); num_vertices],
354        }
355    }
356
357    fn add_edge(&mut self, from: usize, to: usize, capacity: i64) {
358        let reverse_at_to = self.adjacency[to].len();
359        let reverse_at_from = self.adjacency[from].len();
360        self.adjacency[from].push(ResidualEdge {
361            to,
362            rev: reverse_at_to,
363            capacity,
364        });
365        self.adjacency[to].push(ResidualEdge {
366            to: from,
367            rev: reverse_at_from,
368            capacity: 0,
369        });
370    }
371
372    fn max_flow(
373        &mut self,
374        source: usize,
375        sink: usize,
376    ) -> Result<i64, crate::traits::EvaluationError> {
377        let mut total_flow = 0_i64;
378
379        loop {
380            let mut parent: Vec<Option<(usize, usize)>> = vec![None; self.adjacency.len()];
381            let mut queue = VecDeque::new();
382            queue.push_back(source);
383            parent[source] = Some((source, usize::MAX));
384
385            while let Some(vertex) = queue.pop_front() {
386                if vertex == sink {
387                    break;
388                }
389
390                for (edge_index, edge) in self.adjacency[vertex].iter().enumerate() {
391                    if edge.capacity == 0 || parent[edge.to].is_some() {
392                        continue;
393                    }
394                    parent[edge.to] = Some((vertex, edge_index));
395                    queue.push_back(edge.to);
396                }
397            }
398
399            if parent[sink].is_none() {
400                return Ok(total_flow);
401            }
402
403            let mut path_flow = i64::MAX;
404            let mut vertex = sink;
405            while vertex != source {
406                let (prev, edge_index) = parent[vertex].expect("sink is reachable");
407                path_flow = path_flow.min(self.adjacency[prev][edge_index].capacity);
408                vertex = prev;
409            }
410
411            let mut vertex = sink;
412            while vertex != source {
413                let (prev, edge_index) = parent[vertex].expect("sink is reachable");
414                let reverse_edge = self.adjacency[prev][edge_index].rev;
415                self.adjacency[prev][edge_index].capacity = self.adjacency[prev][edge_index]
416                    .capacity
417                    .checked_sub(path_flow)
418                    .ok_or_else(|| {
419                        crate::traits::EvaluationError::IntegerOverflow(
420                            "subtracting residual path flow".into(),
421                        )
422                    })?;
423                self.adjacency[vertex][reverse_edge].capacity = self.adjacency[vertex]
424                    [reverse_edge]
425                    .capacity
426                    .checked_add(path_flow)
427                    .ok_or_else(|| {
428                        crate::traits::EvaluationError::IntegerOverflow(
429                            "adding reverse residual path flow".into(),
430                        )
431                    })?;
432                vertex = prev;
433            }
434
435            total_flow = total_flow.checked_add(path_flow).ok_or_else(|| {
436                crate::traits::EvaluationError::IntegerOverflow("summing maximum flow".into())
437            })?;
438        }
439    }
440}
441
442fn add_lower_bounded_edge(
443    network: &mut ResidualNetwork,
444    balances: &mut [i64],
445    from: usize,
446    to: usize,
447    lower: i64,
448    upper: i64,
449) -> Result<bool, crate::traits::EvaluationError> {
450    if lower > upper {
451        return Ok(false);
452    }
453
454    let residual = upper.checked_sub(lower).ok_or_else(|| {
455        crate::traits::EvaluationError::IntegerOverflow(
456            "subtracting lower bound from flow capacity".into(),
457        )
458    })?;
459    if residual > 0 {
460        network.add_edge(from, to, residual);
461    }
462
463    balances[from] = balances[from].checked_sub(lower).ok_or_else(|| {
464        crate::traits::EvaluationError::IntegerOverflow(
465            "subtracting lower bound from source balance".into(),
466        )
467    })?;
468    balances[to] = balances[to].checked_add(lower).ok_or_else(|| {
469        crate::traits::EvaluationError::IntegerOverflow(
470            "adding lower bound to target balance".into(),
471        )
472    })?;
473    Ok(true)
474}
475
476#[cfg(test)]
477#[path = "../../unit_tests/models/graph/undirected_flow_lower_bounds.rs"]
478mod tests;