Skip to main content

problemreductions/rules/
minimumfeedbackarcset_ilp.rs

1//! Reduction from MinimumFeedbackArcSet to ILP (Integer Linear Programming).
2//!
3//! Uses MTZ-style topological ordering constraints on arcs:
4//! - Variables: |A| binary y_a (arc removal) + |V| integer o_v (topological order)
5//! - Constraints:
6//!   - For each arc a=(u→v): o_v - o_u + n*y_a >= 1
7//!   - Binary bounds: y_a <= 1 for all arcs
8//!   - Order bounds: o_v <= n-1 for all vertices
9//! - Objective: Minimize Σ w_a * y_a
10//! - Variable layout: first |A| are y_a, next |V| are o_v
11
12use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
13use crate::models::graph::MinimumFeedbackArcSet;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16
17/// Result of reducing MinimumFeedbackArcSet to ILP.
18///
19/// The ILP uses integer variables (`ILP<i32>`) because it needs both
20/// binary arc-removal variables (y_a) and integer ordering variables (o_v).
21///
22/// Variable layout:
23/// - `y_a` at index `a` for `a in 0..m`: binary (0 or 1), arc removal indicator
24/// - `o_v` at index `m + v` for `v in 0..n`: integer in {0, ..., n-1}, topological order
25#[derive(Debug, Clone)]
26pub struct ReductionFASToILP {
27    target: ILP<i32>,
28    /// Number of arcs in the source graph (needed for solution extraction).
29    num_arcs: usize,
30}
31
32impl ReductionResult for ReductionFASToILP {
33    type Source = MinimumFeedbackArcSet<i32>;
34    type Target = ILP<i32>;
35
36    fn target_problem(&self) -> &ILP<i32> {
37        &self.target
38    }
39
40    /// Extract solution from ILP back to MinimumFeedbackArcSet.
41    ///
42    /// The first m variables of the ILP solution are the binary y_a values,
43    /// which directly correspond to the FAS configuration (1 = removed).
44    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
45        target_solution[..self.num_arcs].to_vec()
46    }
47}
48
49#[reduction(
50    overhead = {
51        num_vars = "num_arcs + num_vertices",
52        num_constraints = "num_arcs + num_arcs + num_vertices",
53    }
54)]
55impl ReduceTo<ILP<i32>> for MinimumFeedbackArcSet<i32> {
56    type Result = ReductionFASToILP;
57
58    fn reduce_to(&self) -> Self::Result {
59        let n = self.num_vertices();
60        let m = self.num_arcs();
61        let arcs = self.graph().arcs();
62        let num_vars = m + n;
63
64        // Variable indices:
65        // y_a = a         (binary: arc a removed?)
66        // o_v = m + v     (integer: topological order of vertex v)
67
68        let mut constraints = Vec::new();
69
70        // Binary bounds: y_a <= 1 for a in 0..m
71        for a in 0..m {
72            constraints.push(LinearConstraint::le(vec![(a, 1.0)], 1.0));
73        }
74
75        // Order bounds: o_v <= n - 1 for v in 0..n
76        for v in 0..n {
77            constraints.push(LinearConstraint::le(vec![(m + v, 1.0)], (n - 1) as f64));
78        }
79
80        // Arc constraints: for each arc a = (u -> v):
81        //   o_v - o_u >= 1 - n * y_a
82        // Rearranged: o_v - o_u + n * y_a >= 1
83        let n_f64 = n as f64;
84        for (a, &(u, v)) in arcs.iter().enumerate() {
85            let terms = vec![
86                (m + v, 1.0),  // o_v
87                (m + u, -1.0), // -o_u
88                (a, n_f64),    // n * y_a
89            ];
90            constraints.push(LinearConstraint::ge(terms, 1.0));
91        }
92
93        // Objective: minimize sum w_a * y_a
94        let objective: Vec<(usize, f64)> = self
95            .weights()
96            .iter()
97            .enumerate()
98            .map(|(a, &w)| (a, w as f64))
99            .collect();
100
101        let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
102
103        ReductionFASToILP {
104            target,
105            num_arcs: m,
106        }
107    }
108}
109
110#[cfg(feature = "example-db")]
111pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
112    use crate::topology::DirectedGraph;
113
114    vec![crate::example_db::specs::RuleExampleSpec {
115        id: "minimumfeedbackarcset_to_ilp",
116        build: || {
117            // Simple cycle: 0 -> 1 -> 2 -> 0 (FAS = 1 arc)
118            // 3 arcs, 3 vertices: 6 total variables
119            // Remove arc 2 (2->0): source_config = [0, 0, 1]
120            // ILP solution: y_0=0, y_1=0, y_2=1, o_0=0, o_1=1, o_2=2
121            let graph = DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]);
122            let source = MinimumFeedbackArcSet::new(graph, vec![1i32; 3]);
123            crate::example_db::specs::rule_example_via_ilp::<_, i32>(source)
124        },
125    }]
126}
127
128#[cfg(test)]
129#[path = "../unit_tests/rules/minimumfeedbackarcset_ilp.rs"]
130mod tests;