1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
8use crate::models::graph::SteinerTree;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::topology::{Graph, SimpleGraph};
12
13#[derive(Debug, Clone)]
16pub struct ReductionSteinerTreeToILP {
17 target: ILP<bool>,
18 num_edges: usize,
19}
20
21impl ReductionResult for ReductionSteinerTreeToILP {
22 type Source = SteinerTree<SimpleGraph, i64>;
23 type Target = ILP<bool>;
24
25 fn target_problem(&self) -> &ILP<bool> {
26 &self.target
27 }
28
29 fn extract_solution(
30 &self,
31 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
32 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
33 if crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?
34 .value
35 .is_none()
36 {
37 return Err(crate::rules::ExtractionError::invalid(
38 "target ILP assignment is infeasible",
39 ));
40 }
41 Ok(target_solution[..self.num_edges]
42 .iter()
43 .map(|&value| value == 1)
44 .collect())
45 }
46}
47
48#[reduction(
49 transform = exact {
50 num_vars = "num_edges + num_vertices + 2 * num_edges * (num_vertices - 1)",
51 num_constraints = "num_vertices * (num_vertices - 1) + 2 * num_edges * num_vertices + num_terminals + 1",
52 },
53 unavailable = {
54 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
55 }
56)]
57impl ReduceTo<ILP<bool>> for SteinerTree<SimpleGraph, i64> {
58 type Result = ReductionSteinerTreeToILP;
59
60 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
61 let n = self.num_vertices();
62 let m = self.num_edges();
63 let (num_vars, num_constraints) = tree_ilp_sizes(n, m, self.terminals().len())?;
64 let root = self.terminals()[0];
66 let edges = self.graph().edges();
67 let vertex_var = |v: usize| m + v;
68 let flow_var = |commodity: usize, edge: usize, dir: usize| {
69 m + n + commodity * (2 * m) + 2 * edge + dir
70 };
71 let mut constraints = Vec::with_capacity(num_constraints);
72
73 for (e, &(u, v)) in edges.iter().enumerate() {
74 for endpoint in [u, v] {
75 constraints.push(LinearConstraint::le(
76 vec![(e, 1), (vertex_var(endpoint), -1)],
77 0,
78 ));
79 }
80 }
81 for &terminal in self.terminals() {
82 constraints.push(LinearConstraint::eq(vec![(vertex_var(terminal), 1)], 1));
83 }
84 let cardinality = (0..m)
85 .map(|e| (e, 1))
86 .chain((0..n).map(|v| (vertex_var(v), -1)))
87 .collect();
88 constraints.push(LinearConstraint::eq(cardinality, -1));
89
90 for (commodity, sink) in (0..n).filter(|&v| v != root).enumerate() {
91 for vertex in 0..n {
92 let mut terms = Vec::new();
93 for (edge, &(u, v)) in edges.iter().enumerate() {
94 if vertex == u {
95 terms.push((flow_var(commodity, edge, 0), -1));
96 terms.push((flow_var(commodity, edge, 1), 1));
97 }
98 if vertex == v {
99 terms.push((flow_var(commodity, edge, 0), 1));
100 terms.push((flow_var(commodity, edge, 1), -1));
101 }
102 }
103 if vertex == root {
105 terms.push((vertex_var(sink), 1));
106 } else if vertex == sink {
107 terms.push((vertex_var(sink), -1));
108 }
109 constraints.push(LinearConstraint::eq(terms, 0));
110 }
111 for edge in 0..m {
112 for dir in 0..2 {
113 constraints.push(LinearConstraint::le(
114 vec![(flow_var(commodity, edge, dir), 1), (edge, -1)],
115 0,
116 ));
117 }
118 }
119 }
120 let objective = self
121 .edge_weights()
122 .iter()
123 .enumerate()
124 .map(|(e, &w)| (e, w))
125 .collect();
126 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
127 .map_err(Self::target_construction)?;
128 Ok(ReductionSteinerTreeToILP {
129 target,
130 num_edges: m,
131 })
132 }
133}
134
135fn tree_ilp_sizes(
137 n: usize,
138 m: usize,
139 k: usize,
140) -> Result<(usize, usize), crate::rules::ReductionError> {
141 let overflow = || {
142 crate::rules::ReductionError::integer_overflow::<SteinerTree<SimpleGraph, i64>, ILP<bool>>(
143 "counting Steiner tree ILP variables and constraints",
144 )
145 };
146 let non_root = n.checked_sub(1).ok_or_else(overflow)?;
147 let arcs = m.checked_mul(2).ok_or_else(overflow)?;
148 let vars = arcs
149 .checked_mul(non_root)
150 .and_then(|x| x.checked_add(m))
151 .and_then(|x| x.checked_add(n))
152 .ok_or_else(overflow)?;
153 let rows = n
154 .checked_mul(non_root)
155 .and_then(|x| arcs.checked_mul(n).and_then(|a| x.checked_add(a)))
156 .and_then(|x| x.checked_add(k))
157 .and_then(|x| x.checked_add(1))
158 .ok_or_else(overflow)?;
159 Ok((vars, rows))
160}
161
162#[cfg(feature = "example-db")]
163pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
164 vec![crate::example_db::specs::RuleExampleSpec {
165 id: "steinertree_to_ilp",
166 build: || {
167 let source = SteinerTree::new(
168 SimpleGraph::new(
169 5,
170 vec![(0, 1), (1, 2), (1, 3), (3, 4), (0, 3), (3, 2), (2, 4)],
171 ),
172 vec![2, 2, 1, 1, 5, 5, 6],
173 vec![0, 2, 4],
174 );
175 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
176 },
177 }]
178}
179
180#[cfg(test)]
181#[path = "../unit_tests/rules/steinertree_ilp.rs"]
182mod tests;