1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::graph::TravelingSalesman;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::topology::{Graph, SimpleGraph};
13
14#[derive(Debug, Clone)]
16pub struct ReductionTSPToILP {
17 target: ILP<bool>,
18 num_vertices: usize,
20 source_edges: Vec<(usize, usize)>,
22}
23
24impl ReductionTSPToILP {
25 fn x_index(&self, v: usize, k: usize) -> usize {
27 v * self.num_vertices + k
28 }
29}
30
31impl ReductionResult for ReductionTSPToILP {
32 type Source = TravelingSalesman<SimpleGraph, i32>;
33 type Target = ILP<bool>;
34
35 fn target_problem(&self) -> &ILP<bool> {
36 &self.target
37 }
38
39 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
42 let n = self.num_vertices;
43
44 let mut tour = vec![0usize; n];
46 for k in 0..n {
47 for v in 0..n {
48 if target_solution[self.x_index(v, k)] == 1 {
49 tour[k] = v;
50 break;
51 }
52 }
53 }
54
55 let mut edge_selection = vec![0usize; self.source_edges.len()];
57 for k in 0..n {
58 let u = tour[k];
59 let v = tour[(k + 1) % n];
60 for (idx, &(a, b)) in self.source_edges.iter().enumerate() {
62 if (a == u && b == v) || (a == v && b == u) {
63 edge_selection[idx] = 1;
64 break;
65 }
66 }
67 }
68
69 edge_selection
70 }
71}
72
73#[reduction(
74 overhead = {
75 num_vars = "num_vertices^2 + 2 * num_vertices * num_edges",
76 num_constraints = "num_vertices^3 + -1 * num_vertices^2 + 2 * num_vertices + 4 * num_vertices * num_edges",
77 }
78)]
79impl ReduceTo<ILP<bool>> for TravelingSalesman<SimpleGraph, i32> {
80 type Result = ReductionTSPToILP;
81
82 fn reduce_to(&self) -> Self::Result {
83 let n = self.graph().num_vertices();
84 let graph = self.graph();
85 let edges_with_weights = self.edges();
86 let source_edges: Vec<(usize, usize)> =
87 edges_with_weights.iter().map(|&(u, v, _)| (u, v)).collect();
88 let edge_weights: Vec<f64> = edges_with_weights
89 .iter()
90 .map(|&(_, _, w)| w as f64)
91 .collect();
92 let m = source_edges.len();
93
94 let num_x = n * n;
101 let num_y = 2 * m * n;
102 let num_vars = num_x + num_y;
103
104 let x_idx = |v: usize, k: usize| -> usize { v * n + k };
105 let y_idx =
106 |edge: usize, k: usize, dir: usize| -> usize { num_x + edge * 2 * n + 2 * k + dir };
107
108 let mut constraints = Vec::new();
109
110 for v in 0..n {
112 let terms: Vec<(usize, f64)> = (0..n).map(|k| (x_idx(v, k), 1.0)).collect();
113 constraints.push(LinearConstraint::eq(terms, 1.0));
114 }
115
116 for k in 0..n {
118 let terms: Vec<(usize, f64)> = (0..n).map(|v| (x_idx(v, k), 1.0)).collect();
119 constraints.push(LinearConstraint::eq(terms, 1.0));
120 }
121
122 for v in 0..n {
126 for w in 0..n {
127 if v == w {
128 continue;
129 }
130 if graph.has_edge(v, w) {
131 continue;
132 }
133 for k in 0..n {
134 constraints.push(LinearConstraint::le(
135 vec![(x_idx(v, k), 1.0), (x_idx(w, (k + 1) % n), 1.0)],
136 1.0,
137 ));
138 }
139 }
140 }
141
142 for (e, &(u, v)) in source_edges.iter().enumerate() {
147 for k in 0..n {
148 let k_next = (k + 1) % n;
149
150 let y_fwd = y_idx(e, k, 0);
152 let xu = x_idx(u, k);
153 let xv_next = x_idx(v, k_next);
154 constraints.push(LinearConstraint::le(vec![(y_fwd, 1.0), (xu, -1.0)], 0.0));
155 constraints.push(LinearConstraint::le(
156 vec![(y_fwd, 1.0), (xv_next, -1.0)],
157 0.0,
158 ));
159 constraints.push(LinearConstraint::ge(
160 vec![(y_fwd, 1.0), (xu, -1.0), (xv_next, -1.0)],
161 -1.0,
162 ));
163
164 let y_rev = y_idx(e, k, 1);
166 let xv = x_idx(v, k);
167 let xu_next = x_idx(u, k_next);
168 constraints.push(LinearConstraint::le(vec![(y_rev, 1.0), (xv, -1.0)], 0.0));
169 constraints.push(LinearConstraint::le(
170 vec![(y_rev, 1.0), (xu_next, -1.0)],
171 0.0,
172 ));
173 constraints.push(LinearConstraint::ge(
174 vec![(y_rev, 1.0), (xv, -1.0), (xu_next, -1.0)],
175 -1.0,
176 ));
177 }
178 }
179
180 let mut objective: Vec<(usize, f64)> = Vec::new();
182 for (e, &w) in edge_weights.iter().enumerate() {
183 for k in 0..n {
184 objective.push((y_idx(e, k, 0), w));
185 objective.push((y_idx(e, k, 1), w));
186 }
187 }
188
189 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
190
191 ReductionTSPToILP {
192 target,
193 num_vertices: n,
194 source_edges,
195 }
196 }
197}
198
199#[cfg(test)]
200#[path = "../unit_tests/rules/travelingsalesman_ilp.rs"]
201mod tests;