Skip to main content

problemreductions/rules/
minimumcostmaximumflow_minimumcostcirculation.rs

1//! Reduction from MinimumCostMaximumFlow to MinimumCostCirculation.
2//!
3//! Standard textbook equivalence: a minimum-cost maximum-flow instance
4//! `(G, s, t, c, cost)` can be solved as a minimum-cost circulation on
5//! the augmented graph `G' = G + (t -> s)` where the new return arc has
6//! capacity `U = sum_{e in delta^+(s)} u_e` and cost `-B` with
7//! `B = 1 + sum_{e in E} c_e`.
8//!
9//! Because `B` strictly exceeds the cost of any feasible `s-t` flow,
10//! the negative return arc forces the circulation to push the flow
11//! value `|f|` as large as possible (lex priority), and ties on the
12//! flow value are broken by minimizing the original arc cost — exactly
13//! the lexicographic objective of MinimumCostMaximumFlow.
14//!
15//! Reference: MIT 6.854 Course Staff, "Min-cost flow algorithms",
16//! <https://courses.csail.mit.edu/6.854/21/Scribe/s10-minCostFlowAlg/s10-minCostFlowAlg.html>.
17
18use crate::models::graph::{MinimumCostCirculation, MinimumCostMaximumFlow};
19use crate::reduction;
20use crate::rules::traits::{ReduceTo, ReductionResult};
21use crate::topology::DirectedGraph;
22
23/// Result of reducing MinimumCostMaximumFlow to MinimumCostCirculation.
24///
25/// The target circulation graph keeps every original arc in order and
26/// appends a single return arc `(t, s)` at the end. The original arc
27/// count `num_original_arcs` is the number of flow variables to recover
28/// when extracting a witness configuration.
29#[derive(Debug, Clone)]
30pub struct ReductionMCMFToMCC {
31    target: MinimumCostCirculation,
32    num_original_arcs: usize,
33}
34
35impl ReductionResult for ReductionMCMFToMCC {
36    type Source = MinimumCostMaximumFlow;
37    type Target = MinimumCostCirculation;
38
39    fn target_problem(&self) -> &MinimumCostCirculation {
40        &self.target
41    }
42
43    /// Extract the source flow by discarding the return arc: the first
44    /// `num_original_arcs` entries of the circulation are exactly the
45    /// flow values on the original arcs.
46    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
47        target_solution[..self.num_original_arcs].to_vec()
48    }
49}
50
51#[reduction(
52    overhead = {
53        num_vertices = "num_vertices",
54        num_arcs = "num_arcs + 1",
55    }
56)]
57impl ReduceTo<MinimumCostCirculation> for MinimumCostMaximumFlow {
58    type Result = ReductionMCMFToMCC;
59
60    fn reduce_to(&self) -> Self::Result {
61        let n = self.num_vertices();
62        let m = self.num_arcs();
63        let source = self.source();
64        let sink = self.sink();
65
66        // U = sum of capacities of arcs leaving the source.
67        let u_bound: i64 = self
68            .graph()
69            .arcs()
70            .iter()
71            .zip(self.capacities().iter())
72            .filter_map(|(&(u, _), &cap)| if u == source { Some(cap) } else { None })
73            .sum();
74
75        // B = 1 + sum of all original arc costs. Strictly exceeds any
76        // simple s-t path cost, so the return arc's negative cost
77        // dominates all positive original costs lexicographically.
78        let b_const: i64 = 1 + self.costs().iter().sum::<i64>();
79
80        // Keep every original arc and append the return arc (t, s).
81        let mut arcs = self.graph().arcs();
82        arcs.push((sink, source));
83
84        let mut capacities = self.capacities().to_vec();
85        capacities.push(u_bound);
86
87        let mut costs = self.costs().to_vec();
88        costs.push(-b_const);
89
90        let target = MinimumCostCirculation::new(DirectedGraph::new(n, arcs), capacities, costs);
91
92        ReductionMCMFToMCC {
93            target,
94            num_original_arcs: m,
95        }
96    }
97}
98
99#[cfg(feature = "example-db")]
100pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
101    use crate::export::SolutionPair;
102
103    vec![crate::example_db::specs::RuleExampleSpec {
104        id: "minimumcostmaximumflow_to_minimumcostcirculation",
105        build: || {
106            // Canonical 4-vertex diamond from issue #1029/#1031.
107            // Optimal source flow: [2, 1, 1, 1, 2] with value 3, cost 7.
108            // Target circulation appends return arc (3 -> 0) with
109            // flow value 3, giving config [2, 1, 1, 1, 2, 3].
110            let source = MinimumCostMaximumFlow::new(
111                DirectedGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]),
112                0,
113                3,
114                vec![2, 1, 1, 1, 2],
115                vec![1, 0, 0, 1, 2],
116            );
117            crate::example_db::specs::rule_example_with_witness::<_, MinimumCostCirculation>(
118                source,
119                SolutionPair {
120                    source_config: vec![2, 1, 1, 1, 2],
121                    target_config: vec![2, 1, 1, 1, 2, 3],
122                },
123            )
124        },
125    }]
126}
127
128#[cfg(test)]
129#[path = "../unit_tests/rules/minimumcostmaximumflow_minimumcostcirculation.rs"]
130mod tests;