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