1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
28use crate::models::graph::UndirectedTwoCommodityIntegralFlow;
29use crate::reduction;
30use crate::rules::traits::{ReduceTo, ReductionResult};
31use crate::topology::Graph;
32
33#[derive(Debug, Clone)]
40pub struct ReductionU2CIFToILP {
41 target: ILP<i32>,
42 num_edges: usize,
43}
44
45impl ReductionResult for ReductionU2CIFToILP {
46 type Source = UndirectedTwoCommodityIntegralFlow;
47 type Target = ILP<i32>;
48
49 fn target_problem(&self) -> &ILP<i32> {
50 &self.target
51 }
52
53 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
55 target_solution[..4 * self.num_edges].to_vec()
56 }
57}
58
59#[reduction(
60 overhead = {
61 num_vars = "6 * num_edges",
62 num_constraints = "7 * num_edges + 2 * num_nonterminal_vertices + 2",
63 }
64)]
65impl ReduceTo<ILP<i32>> for UndirectedTwoCommodityIntegralFlow {
66 type Result = ReductionU2CIFToILP;
67
68 fn reduce_to(&self) -> Self::Result {
69 let edges = self.graph().edges();
70 let e = edges.len();
71 let n = self.num_vertices();
72 let num_vars = 6 * e;
74
75 let f1_uv = |edge: usize| 4 * edge;
77 let f1_vu = |edge: usize| 4 * edge + 1;
78 let f2_uv = |edge: usize| 4 * edge + 2;
79 let f2_vu = |edge: usize| 4 * edge + 3;
80 let d1 = |edge: usize| 4 * e + 2 * edge;
81 let d2 = |edge: usize| 4 * e + 2 * edge + 1;
82
83 let mut constraints = Vec::with_capacity(7 * e + 2 * self.num_nonterminal_vertices() + 2);
84
85 for (edge_idx, (_u, _v)) in edges.iter().enumerate() {
86 let cap = self.capacities()[edge_idx] as f64;
87
88 constraints.push(LinearConstraint::le(vec![(d1(edge_idx), 1.0)], 1.0));
90 constraints.push(LinearConstraint::le(vec![(d2(edge_idx), 1.0)], 1.0));
91
92 constraints.push(LinearConstraint::le(
95 vec![(f1_uv(edge_idx), 1.0), (d1(edge_idx), -cap)],
96 0.0,
97 ));
98 constraints.push(LinearConstraint::le(
100 vec![(f1_vu(edge_idx), 1.0), (d1(edge_idx), cap)],
101 cap,
102 ));
103
104 constraints.push(LinearConstraint::le(
106 vec![(f2_uv(edge_idx), 1.0), (d2(edge_idx), -cap)],
107 0.0,
108 ));
109 constraints.push(LinearConstraint::le(
111 vec![(f2_vu(edge_idx), 1.0), (d2(edge_idx), cap)],
112 cap,
113 ));
114
115 constraints.push(LinearConstraint::le(
117 vec![
118 (f1_uv(edge_idx), 1.0),
119 (f1_vu(edge_idx), 1.0),
120 (f2_uv(edge_idx), 1.0),
121 (f2_vu(edge_idx), 1.0),
122 ],
123 cap,
124 ));
125 }
126
127 let terminals = [
129 self.source_1(),
130 self.sink_1(),
131 self.source_2(),
132 self.sink_2(),
133 ];
134
135 for vertex in 0..n {
136 if terminals.contains(&vertex) {
137 continue;
138 }
139
140 let mut terms_c1: Vec<(usize, f64)> = Vec::new();
142 let mut terms_c2: Vec<(usize, f64)> = Vec::new();
144
145 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
146 if vertex == u {
147 terms_c1.push((f1_uv(edge_idx), -1.0));
149 terms_c1.push((f1_vu(edge_idx), 1.0));
150 terms_c2.push((f2_uv(edge_idx), -1.0));
151 terms_c2.push((f2_vu(edge_idx), 1.0));
152 } else if vertex == v {
153 terms_c1.push((f1_uv(edge_idx), 1.0));
155 terms_c1.push((f1_vu(edge_idx), -1.0));
156 terms_c2.push((f2_uv(edge_idx), 1.0));
157 terms_c2.push((f2_vu(edge_idx), -1.0));
158 }
159 }
160
161 if !terms_c1.is_empty() {
162 constraints.push(LinearConstraint::eq(terms_c1, 0.0));
163 }
164 if !terms_c2.is_empty() {
165 constraints.push(LinearConstraint::eq(terms_c2, 0.0));
166 }
167 }
168
169 let sink_1 = self.sink_1();
172 let mut sink1_terms: Vec<(usize, f64)> = Vec::new();
173 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
174 if sink_1 == v {
175 sink1_terms.push((f1_uv(edge_idx), 1.0));
176 sink1_terms.push((f1_vu(edge_idx), -1.0));
177 } else if sink_1 == u {
178 sink1_terms.push((f1_uv(edge_idx), -1.0));
179 sink1_terms.push((f1_vu(edge_idx), 1.0));
180 }
181 }
182 constraints.push(LinearConstraint::ge(
183 sink1_terms,
184 self.requirement_1() as f64,
185 ));
186
187 let sink_2 = self.sink_2();
189 let mut sink2_terms: Vec<(usize, f64)> = Vec::new();
190 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
191 if sink_2 == v {
192 sink2_terms.push((f2_uv(edge_idx), 1.0));
193 sink2_terms.push((f2_vu(edge_idx), -1.0));
194 } else if sink_2 == u {
195 sink2_terms.push((f2_uv(edge_idx), -1.0));
196 sink2_terms.push((f2_vu(edge_idx), 1.0));
197 }
198 }
199 constraints.push(LinearConstraint::ge(
200 sink2_terms,
201 self.requirement_2() as f64,
202 ));
203
204 ReductionU2CIFToILP {
205 target: ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize),
206 num_edges: e,
207 }
208 }
209}
210
211#[cfg(feature = "example-db")]
212pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
213 use crate::export::SolutionPair;
214 use crate::topology::SimpleGraph;
215
216 vec![crate::example_db::specs::RuleExampleSpec {
217 id: "undirectedtwocommodityintegralflow_to_ilp",
218 build: || {
219 let source = UndirectedTwoCommodityIntegralFlow::new(
223 SimpleGraph::new(4, vec![(0, 2), (1, 2), (2, 3)]),
224 vec![1, 1, 2],
225 0,
226 3,
227 1,
228 3,
229 1,
230 1,
231 );
232 let reduction: ReductionU2CIFToILP = ReduceTo::<ILP<i32>>::reduce_to(&source);
233 let solver = crate::solvers::ILPSolver::new();
234 let target_config = solver
235 .solve(reduction.target_problem())
236 .expect("canonical example should be feasible");
237 let source_config = reduction.extract_solution(&target_config);
238 crate::example_db::specs::rule_example_with_witness::<_, ILP<i32>>(
239 source,
240 SolutionPair {
241 source_config,
242 target_config,
243 },
244 )
245 },
246 }]
247}
248
249#[cfg(test)]
250#[path = "../unit_tests/rules/undirectedtwocommodityintegralflow_ilp.rs"]
251mod tests;