problemreductions/models/graph/
traveling_salesman.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Min, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "TravelingSalesman",
16 display_name: "Traveling Salesman",
17 aliases: &["TSP"],
18 dimensions: &[
19 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20 VariantDimension::new("weight", "i32", &["i32"]),
21 ],
22 module_path: module_path!(),
23 description: "Find minimum weight Hamiltonian cycle in a graph (Traveling Salesman Problem)",
24 fields: &[
25 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
26 FieldInfo { name: "edge_weights", type_name: "Vec<W>", description: "Edge weights w: E -> R" },
27 ],
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TravelingSalesman<G, W> {
54 graph: G,
56 edge_weights: Vec<W>,
58}
59
60impl<G: Graph, W: Clone + Default> TravelingSalesman<G, W> {
61 pub fn new(graph: G, edge_weights: Vec<W>) -> Self {
63 assert_eq!(
64 edge_weights.len(),
65 graph.num_edges(),
66 "edge_weights length must match num_edges"
67 );
68 Self {
69 graph,
70 edge_weights,
71 }
72 }
73
74 pub fn unit_weights(graph: G) -> Self
76 where
77 W: From<i32>,
78 {
79 let edge_weights = vec![W::from(1); graph.num_edges()];
80 Self {
81 graph,
82 edge_weights,
83 }
84 }
85
86 pub fn graph(&self) -> &G {
88 &self.graph
89 }
90
91 pub fn edges(&self) -> Vec<(usize, usize, W)> {
93 self.graph
94 .edges()
95 .into_iter()
96 .zip(self.edge_weights.iter().cloned())
97 .map(|((u, v), w)| (u, v, w))
98 .collect()
99 }
100
101 pub fn set_weights(&mut self, weights: Vec<W>) {
103 assert_eq!(weights.len(), self.graph.num_edges());
104 self.edge_weights = weights;
105 }
106
107 pub fn weights(&self) -> Vec<W> {
109 self.edge_weights.clone()
110 }
111
112 pub fn is_weighted(&self) -> bool
114 where
115 W: WeightElement,
116 {
117 !W::IS_UNIT
118 }
119
120 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
122 self.is_valid_hamiltonian_cycle(config)
123 }
124
125 fn is_valid_hamiltonian_cycle(&self, config: &[usize]) -> bool {
127 if config.len() != self.graph.num_edges() {
128 return false;
129 }
130 let selected: Vec<bool> = config.iter().map(|&s| s == 1).collect();
131 is_hamiltonian_cycle(&self.graph, &selected)
132 }
133}
134
135impl<G: Graph, W: WeightElement> TravelingSalesman<G, W> {
136 pub fn num_vertices(&self) -> usize {
138 self.graph().num_vertices()
139 }
140
141 pub fn num_edges(&self) -> usize {
143 self.graph().num_edges()
144 }
145}
146
147impl<G, W> Problem for TravelingSalesman<G, W>
148where
149 G: Graph + crate::variant::VariantParam,
150 W: WeightElement + crate::variant::VariantParam,
151{
152 const NAME: &'static str = "TravelingSalesman";
153 type Value = Min<W::Sum>;
154
155 fn variant() -> Vec<(&'static str, &'static str)> {
156 crate::variant_params![G, W]
157 }
158
159 fn dims(&self) -> Vec<usize> {
160 vec![2; self.graph.num_edges()]
161 }
162
163 fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
164 if !self.is_valid_hamiltonian_cycle(config) {
165 return Min(None);
166 }
167 let mut total = W::Sum::zero();
168 for (idx, &selected) in config.iter().enumerate() {
169 if selected == 1 {
170 if let Some(w) = self.edge_weights.get(idx) {
171 total += w.to_sum();
172 }
173 }
174 }
175 Min(Some(total))
176 }
177}
178
179pub(crate) fn is_hamiltonian_cycle<G: Graph>(graph: &G, selected: &[bool]) -> bool {
184 assert_eq!(
185 selected.len(),
186 graph.num_edges(),
187 "selected length must match num_edges"
188 );
189
190 let n = graph.num_vertices();
191 let edges = graph.edges();
192 let mut degree = vec![0usize; n];
193 let mut selected_count = 0;
194 let mut first_vertex = None;
195
196 for (idx, &sel) in selected.iter().enumerate() {
197 if sel {
198 let (u, v) = edges[idx];
199 degree[u] += 1;
200 degree[v] += 1;
201 selected_count += 1;
202 if first_vertex.is_none() {
203 first_vertex = Some(u);
204 }
205 }
206 }
207
208 if selected_count != n {
209 return false;
210 }
211
212 if degree.iter().any(|&d| d != 2) {
213 return false;
214 }
215
216 let first = match first_vertex {
217 Some(v) => v,
218 None => return false,
219 };
220
221 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
222 for (idx, &sel) in selected.iter().enumerate() {
223 if sel {
224 let (u, v) = edges[idx];
225 adj[u].push(v);
226 adj[v].push(u);
227 }
228 }
229
230 let mut visited = vec![false; n];
231 let mut queue = std::collections::VecDeque::new();
232 visited[first] = true;
233 queue.push_back(first);
234 let mut visit_count = 1;
235
236 while let Some(node) = queue.pop_front() {
237 for &neighbor in &adj[node] {
238 if !visited[neighbor] {
239 visited[neighbor] = true;
240 visit_count += 1;
241 queue.push_back(neighbor);
242 }
243 }
244 }
245
246 visit_count == n
247}
248
249#[cfg(feature = "example-db")]
250pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
251 vec![crate::example_db::specs::ModelExampleSpec {
252 id: "traveling_salesman_simplegraph_i32",
253 instance: Box::new(TravelingSalesman::new(
254 SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]),
255 vec![1, 3, 2, 2, 3, 1],
256 )),
257 optimal_config: vec![1, 0, 1, 1, 0, 1],
258 optimal_value: serde_json::json!(6),
259 }]
260}
261
262crate::declare_variants! {
263 default TravelingSalesman<SimpleGraph, i32> => "2^num_vertices",
264}
265
266#[cfg(test)]
267#[path = "../../unit_tests/models/graph/traveling_salesman.rs"]
268mod tests;