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<i64>,
42 num_edges: usize,
43}
44
45impl ReductionResult for ReductionU2CIFToILP {
46 type Source = UndirectedTwoCommodityIntegralFlow;
47 type Target = ILP<i64>;
48
49 fn target_problem(&self) -> &ILP<i64> {
50 &self.target
51 }
52
53 fn extract_solution(
55 &self,
56 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
57 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
58 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
59
60 crate::rules::ilp_helpers::decode_usize_values(&target_solution[..4 * self.num_edges])
61 }
62}
63
64#[reduction(
65 transform = exact {
66 num_vars = "6 * num_edges",
67 num_constraints = "7 * num_edges + num_conservation_constraints + 2",
68 },
69 unavailable = {
70 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
71 }
72)]
73impl ReduceTo<ILP<i64>> for UndirectedTwoCommodityIntegralFlow {
74 type Result = ReductionU2CIFToILP;
75
76 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
77 let edges = self.graph().edges();
78 let e = edges.len();
79 let n = self.num_vertices();
80 let num_vars = 6 * e;
82 let f1_uv = |edge: usize| 4 * edge;
84 let f1_vu = |edge: usize| 4 * edge + 1;
85 let f2_uv = |edge: usize| 4 * edge + 2;
86 let f2_vu = |edge: usize| 4 * edge + 3;
87 let d1 = |edge: usize| 4 * e + 2 * edge;
88 let d2 = |edge: usize| 4 * e + 2 * edge + 1;
89
90 let mut constraints = Vec::with_capacity(7 * e + self.num_conservation_constraints() + 2);
91
92 for (edge_idx, (_u, _v)) in edges.iter().enumerate() {
93 let cap = self.capacities()[edge_idx];
94
95 constraints.push(LinearConstraint::le(vec![(d1(edge_idx), 1)], 1));
97 constraints.push(LinearConstraint::le(vec![(d2(edge_idx), 1)], 1));
98
99 constraints.push(LinearConstraint::le(
102 vec![(f1_uv(edge_idx), 1), (d1(edge_idx), -cap)],
103 0,
104 ));
105 constraints.push(LinearConstraint::le(
107 vec![(f1_vu(edge_idx), 1), (d1(edge_idx), cap)],
108 cap,
109 ));
110
111 constraints.push(LinearConstraint::le(
113 vec![(f2_uv(edge_idx), 1), (d2(edge_idx), -cap)],
114 0,
115 ));
116 constraints.push(LinearConstraint::le(
118 vec![(f2_vu(edge_idx), 1), (d2(edge_idx), cap)],
119 cap,
120 ));
121
122 constraints.push(LinearConstraint::le(
124 vec![
125 (f1_uv(edge_idx), 1),
126 (f1_vu(edge_idx), 1),
127 (f2_uv(edge_idx), 1),
128 (f2_vu(edge_idx), 1),
129 ],
130 cap,
131 ));
132 }
133
134 for (commodity, source, sink) in [
136 (1, self.source_1(), self.sink_1()),
137 (2, self.source_2(), self.sink_2()),
138 ] {
139 for vertex in 0..n {
140 if vertex == source || vertex == sink {
141 continue;
142 }
143
144 let mut terms = Vec::new();
145 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
146 let (uv, vu) = if commodity == 1 {
147 (f1_uv(edge_idx), f1_vu(edge_idx))
148 } else {
149 (f2_uv(edge_idx), f2_vu(edge_idx))
150 };
151 if vertex == u {
152 terms.push((uv, -1));
153 terms.push((vu, 1));
154 } else if vertex == v {
155 terms.push((uv, 1));
156 terms.push((vu, -1));
157 }
158 }
159 constraints.push(LinearConstraint::eq(terms, 0));
160 }
161 }
162
163 let sink_1 = self.sink_1();
166 let mut sink1_terms: Vec<(usize, i64)> = Vec::new();
167 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
168 if sink_1 == v {
169 sink1_terms.push((f1_uv(edge_idx), 1));
170 sink1_terms.push((f1_vu(edge_idx), -1));
171 } else if sink_1 == u {
172 sink1_terms.push((f1_uv(edge_idx), -1));
173 sink1_terms.push((f1_vu(edge_idx), 1));
174 }
175 }
176 constraints.push(LinearConstraint::ge(sink1_terms, self.requirement_1()));
177
178 let sink_2 = self.sink_2();
180 let mut sink2_terms: Vec<(usize, i64)> = Vec::new();
181 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
182 if sink_2 == v {
183 sink2_terms.push((f2_uv(edge_idx), 1));
184 sink2_terms.push((f2_vu(edge_idx), -1));
185 } else if sink_2 == u {
186 sink2_terms.push((f2_uv(edge_idx), -1));
187 sink2_terms.push((f2_vu(edge_idx), 1));
188 }
189 }
190 constraints.push(LinearConstraint::ge(sink2_terms, self.requirement_2()));
191
192 Ok(ReductionU2CIFToILP {
193 target: ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
194 .map_err(Self::target_construction)?,
195 num_edges: e,
196 })
197 }
198}
199
200#[cfg(feature = "example-db")]
201pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
202 use crate::export::SolutionPair;
203 use crate::topology::SimpleGraph;
204
205 vec![crate::example_db::specs::RuleExampleSpec {
206 id: "undirectedtwocommodityintegralflow_to_ilp",
207 build: || {
208 let source = UndirectedTwoCommodityIntegralFlow::new(
212 SimpleGraph::new(4, vec![(0, 2), (1, 2), (2, 3)]),
213 vec![1, 1, 2],
214 0,
215 3,
216 1,
217 3,
218 1,
219 1,
220 );
221 let reduction: ReductionU2CIFToILP =
222 ReduceTo::<ILP<i64>>::reduce_to(&source).expect("reduction should succeed");
223 let solver = crate::solvers::ILPSolver::new();
224 let target_config = solver
225 .solve(reduction.target_problem())
226 .expect("canonical example should be feasible");
227 let source_config = reduction.extract_solution(&target_config).unwrap();
228 crate::example_db::specs::rule_example_with_witness::<_, ILP<i64>>(
229 source,
230 SolutionPair {
231 source_config: serde_json::to_value(source_config)
232 .expect("solution serialization must succeed"),
233 target_config: serde_json::to_value(target_config)
234 .expect("solution serialization must succeed"),
235 },
236 )
237 },
238 }]
239}
240
241#[cfg(test)]
242#[path = "../unit_tests/rules/undirectedtwocommodityintegralflow_ilp.rs"]
243mod tests;