problemreductions/rules/
minimumfeedbackarcset_ilp.rs1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
13use crate::models::graph::MinimumFeedbackArcSet;
14use crate::reduction;
15use crate::rules::traits::{ReduceTo, ReductionResult};
16
17#[derive(Debug, Clone)]
26pub struct ReductionFASToILP {
27 target: ILP<i64, i64, Bounded>,
28 num_arcs: usize,
30}
31
32impl ReductionResult for ReductionFASToILP {
33 type Source = MinimumFeedbackArcSet<i64>;
34 type Target = ILP<i64, i64, Bounded>;
35
36 fn target_problem(&self) -> &ILP<i64, i64, Bounded> {
37 &self.target
38 }
39
40 fn extract_solution(
45 &self,
46 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
47 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
48 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
49
50 Ok(target_solution[..self.num_arcs]
51 .iter()
52 .map(|&value| value == 1)
53 .collect())
54 }
55}
56
57#[reduction(transform = {
58 exact {
59 num_vars = "num_arcs + num_vertices",
60 num_constraints = "num_arcs + num_arcs + num_vertices",
61 },
62 upper_bound {
63 max_constraint_magnitude_bits = "num_vertices + 1",
64 num_nonzeros = "(num_arcs + num_vertices) * (num_arcs + num_arcs + num_vertices)",
65 },
66})]
67impl ReduceTo<ILP<i64, i64, Bounded>> for MinimumFeedbackArcSet<i64> {
68 type Result = ReductionFASToILP;
69
70 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
71 let n = self.num_vertices();
72 let m = self.num_arcs();
73 let arcs = self.graph().arcs();
74 let num_vars = m + n;
75
76 let mut constraints = Vec::new();
81 let n_i64 = Self::exact_i64(n, "encoding the topological order")?;
82
83 for a in 0..m {
85 constraints.push(LinearConstraint::le(vec![(a, 1)], 1));
86 }
87
88 for v in 0..n {
90 constraints.push(LinearConstraint::le(vec![(m + v, 1)], n_i64 - 1));
91 }
92
93 for (a, &(u, v)) in arcs.iter().enumerate() {
97 let terms = vec![
98 (m + v, 1), (m + u, -1), (a, n_i64), ];
102 constraints.push(LinearConstraint::ge(terms, 1));
103 }
104
105 let objective: Vec<(usize, i64)> = self
107 .weights()
108 .iter()
109 .enumerate()
110 .map(|(arc, &weight)| (arc, weight))
111 .collect();
112
113 let mut variables = vec![IntegerVariable::binary(); num_vars];
114 variables[m..].fill(
115 IntegerVariable::new(Some(0), Some((n_i64 - 1).max(0)))
116 .map_err(Self::target_construction)?,
117 );
118
119 let target =
120 ILP::with_variables(variables, constraints, objective, ObjectiveSense::Minimize)
121 .map_err(Self::target_construction)?;
122
123 Ok(ReductionFASToILP {
124 target,
125 num_arcs: m,
126 })
127 }
128}
129
130#[cfg(feature = "example-db")]
131pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
132 use crate::topology::DirectedGraph;
133
134 vec![crate::example_db::specs::RuleExampleSpec {
135 id: "minimumfeedbackarcset_to_ilp",
136 build: || {
137 let graph = DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]);
142 let source = MinimumFeedbackArcSet::new(graph, vec![1i64; 3]);
143 crate::example_db::specs::rule_example_via_bounded_ilp::<_>(source)
144 },
145 }]
146}
147
148#[cfg(test)]
149#[path = "../unit_tests/rules/minimumfeedbackarcset_ilp.rs"]
150mod tests;