OR-Tools  9.6
tsp_mo.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 // A minimal TSP solver using MathOpt.
15 //
16 // In the Euclidean Traveling Salesperson Problem (TSP), you are given a list of
17 // n cities, each with an (x, y) coordinate, and you must find an order to visit
18 // the cities in to minimize the (Euclidean) travel distance.
19 //
20 // The MIP "cutset" formulation for the problem is as follows:
21 // * Data:
22 // n: An integer, the number of cities
23 // (x_i, y_i): a pair of floats for each i in 1..n, the location of each
24 // city
25 // d_ij for all (i, j) pairs of cities, the distance between city i and j.
26 // * Decision variables:
27 // x_ij: A binary variable, indicates if the edge connecting i and j is
28 // used. Note that x_ij == x_ji, because the problem is symmetric. We
29 // only create variables for i < j, and have x_ji as an alias for
30 // x_ij.
31 // * MIP model:
32 // minimize sum_{i=1}^n sum_{j=1, j < i}^n d_ij * x_ij
33 // s.t. sum_{j=1, j != i}^n x_ij = 2 for all i = 1..n
34 // sum_{i in S} sum_{j not in S} x_ij >= 2 for all S subset {1,...,n}
35 // |S| >= 3, |S| <= n - 3
36 // x_ij in {0, 1}
37 // The first set of constraints are called the degree constraints, and the
38 // second set of constraints are called the cutset constraints. There are
39 // exponentially many cutset, so we cannot add them all at the start of the
40 // solve. Instead, we will use a solver callback to view each integer solution
41 // and add any violated cutset constraints that exist.
42 //
43 // Note that, while there are exponentially many cutset constraints, we can
44 // quickly identify violated ones by exploiting that the solution is integer
45 // and the degree constraints are all already in the model and satisfied. As a
46 // result, the graph n nodes and edges when x_ij = 1 will be a degree two graph,
47 // so it will be a collection of cycles. If it is a single large cycle, then the
48 // solution is feasible, and if there multiple cycles, then taking the nodes of
49 // any cycle as S produces a violated cutset constraint.
50 //
51 // Note that this is a minimal TSP solution, more sophisticated MIP methods are
52 // possible.
53 
54 #include <iostream>
55 #include <optional>
56 
57 #include "absl/flags/flag.h"
58 #include "absl/random/random.h"
59 #include "absl/random/uniform_real_distribution.h"
60 #include "absl/strings/str_cat.h"
61 #include "absl/strings/str_join.h"
62 #include "ortools/base/file.h"
64 #include "ortools/base/logging.h"
69 
70 ABSL_FLAG(int, num_cities, 50, "Number of cities in random TSP.");
71 ABSL_FLAG(std::string, output, "",
72  "Write a svg of the solution here, or to standard out if empty.");
73 ABSL_FLAG(bool, test_instance, false,
74  "Solve the test TSP instead of a random instance.");
75 ABSL_FLAG(int, threads, 0,
76  "How many threads to solve with, or solver default if <= 0.");
77 ABSL_FLAG(bool, solve_logs, false,
78  "Have the solver print logs to standard out.");
79 
80 namespace {
81 
83 using Cycle = std::vector<int>;
84 
85 // Creates variables modeling the undirected edges for the TSP. For every (i, j)
86 // pair in [0,n) * [0, n), a variable is created only for j < i, but querying
87 // for the variable x_ij with j > i returns x_ji. Querying for x_ii (which does
88 // not exist) gives a CHECK failure.
89 //
90 // The Model object passed in to create EdgeVariables must outlive this.
91 class EdgeVariables {
92  public:
93  EdgeVariables(math_opt::Model& model, const int n) {
94  variables_.resize(n);
95  for (int i = 0; i < n; ++i) {
96  variables_[i].reserve(i);
97  for (int j = 0; j < i; ++j) {
98  variables_[i].push_back(
99  model.AddBinaryVariable(absl::StrCat("e_", i, "_", j)));
100  }
101  }
102  }
103 
104  math_opt::Variable get(const int i, const int j) const {
105  CHECK_NE(i, j);
106  return i > j ? variables_[i][j] : variables_[j][i];
107  }
108 
109  int num_cities() const { return variables_.size(); }
110 
111  private:
112  std::vector<std::vector<math_opt::Variable>> variables_;
113 };
114 
115 // Produces a random TSP problem where cities have random locations that are
116 // I.I.D Uniform [0, 1].
117 std::vector<std::pair<double, double>> RandomCities(int num_cities) {
118  absl::BitGen rand;
119  std::vector<std::pair<double, double>> cities;
120  for (int i = 0; i < num_cities; ++i) {
121  cities.push_back({absl::Uniform<double>(rand, 0.0, 1.0),
122  absl::Uniform<double>(rand, 0.0, 1.0)});
123  }
124  return cities;
125 }
126 
127 std::vector<std::pair<double, double>> TestCities() {
128  return {{0, 0}, {0, 0.1}, {0.1, 0}, {0.1, 0.1},
129  {1, 0}, {1, 0.1}, {0.9, 0}, {0.9, 0.1}};
130 }
131 
132 // Given an n city TSP instance, computes the n by n distance matrix using the
133 // Euclidean distance.
134 std::vector<std::vector<double>> DistanceMatrix(
135  const std::vector<std::pair<double, double>>& cities) {
136  const int num_cities = cities.size();
137  std::vector<std::vector<double>> distance_matrix(
138  num_cities, std::vector<double>(num_cities, 0.0));
139  for (int i = 0; i < num_cities; ++i) {
140  for (int j = 0; j < num_cities; ++j) {
141  if (i != j) {
142  const double dx = cities[i].first - cities[j].first;
143  const double dy = cities[i].second - cities[j].second;
144  distance_matrix[i][j] = std::sqrt(dx * dx + dy * dy);
145  }
146  }
147  }
148  return distance_matrix;
149 }
150 
151 // Given the EdgeVariables and a var_values containing the value of each edge in
152 // a solution, returns an n by n boolean matrix of which edges are used (with
153 // false diagonal elements). It is assumed that var_values are approximately 0-1
154 // integer.
155 std::vector<std::vector<bool>> EdgeValues(
156  const EdgeVariables& edge_vars,
157  const math_opt::VariableMap<double>& var_values) {
158  const int n = edge_vars.num_cities();
159  std::vector<std::vector<bool>> edge_values(n, std::vector<bool>(n, false));
160  for (int i = 0; i < n; ++i) {
161  for (int j = 0; j < n; ++j) {
162  if (i != j) {
163  edge_values[i][j] = var_values.at(edge_vars.get(i, j)) > 0.5;
164  }
165  }
166  }
167  return edge_values;
168 }
169 
170 // Given an n by n boolean matrix of edge values, returns a cycle decomposition.
171 // it is assumed that edge values respects the degree constraints (each row has
172 // only two true entries). Each cycle is represented as a list of cities with
173 // no repeats.
174 std::vector<Cycle> FindCycles(
175  const std::vector<std::vector<bool>>& edge_values) {
176  // Algorithm: maintain a "visited" bit for each city indicating if we have
177  // formed a cycle containing this city. Consider the cities in order. When you
178  // find an unvisited city, start a new cycle beginning at this city. Then,
179  // build the cycle by finding an unvisited neighbor until no such neighbor
180  // exists (every city will have two neighbors, but eventually both will be
181  // visited). To find the "unvisited neighbor", we simply do a linear scan
182  // over the cities, checking both the adjacency matrix and the visited bit.
183  //
184  // Note that for this algorithm, in each cycle, the city with lowest index
185  // will be first, and the cycles will be sorted by their city of lowest index.
186  // This is an implementation detail and should not be relied upon.
187  const int n = edge_values.size();
188  std::vector<Cycle> result;
189  std::vector<bool> visited(n, false);
190  for (int i = 0; i < n; ++i) {
191  if (visited[i]) {
192  continue;
193  }
194  std::vector<int> cycle;
195  std::optional<int> next = i;
196  while (next.has_value()) {
197  cycle.push_back(*next);
198  visited[*next] = true;
199  int current = *next;
200  next = std::nullopt;
201  // Scan for an unvisited neighbor. We can start at i+1 since we know that
202  // everything from i back is visited.
203  for (int j = i + 1; j < n; ++j) {
204  if (!visited[j] && edge_values[current][j]) {
205  next = j;
206  break;
207  }
208  }
209  }
210  result.push_back(cycle);
211  }
212  return result;
213 }
214 
215 // Given a cycle and an EdgeVariables, returns the cutset constraint for the set
216 // of nodes in cycle.
217 math_opt::BoundedLinearExpression CutsetConstraint(
218  const Cycle& cycle, const EdgeVariables& edge_vars) {
219  const int n = edge_vars.num_cities();
220  const absl::flat_hash_set<int> cycle_as_set(cycle.begin(), cycle.end());
221  std::vector<int> not_in_cycle;
222  for (int i = 0; i < n; ++i) {
223  if (!cycle_as_set.contains(i)) {
224  not_in_cycle.push_back(i);
225  }
226  }
227  math_opt::LinearExpression cutset_edges;
228  for (const int in_cycle : cycle) {
229  for (const int out_of_cycle : not_in_cycle) {
230  cutset_edges += edge_vars.get(in_cycle, out_of_cycle);
231  }
232  }
233  return cutset_edges >= 2;
234 }
235 
236 // Solves the TSP by returning the ordering of the cities that minimizes travel
237 // distance.
238 absl::StatusOr<Cycle> SolveTsp(
239  const std::vector<std::pair<double, double>>& cities) {
240  const int n = cities.size();
241  const std::vector<std::vector<double>> distance_matrix =
242  DistanceMatrix(cities);
243  CHECK_GE(n, 3);
244  math_opt::Model model("tsp");
245  const EdgeVariables edge_vars(model, n);
246  math_opt::LinearExpression edge_cost;
247  for (int i = 0; i < n; ++i) {
248  for (int j = i + 1; j < n; ++j) {
249  edge_cost += edge_vars.get(i, j) * distance_matrix[i][j];
250  }
251  }
252  model.Minimize(edge_cost);
253 
254  // Add the degree constraints
255  for (int i = 0; i < n; ++i) {
256  math_opt::LinearExpression neighbors;
257  for (int j = 0; j < n; ++j) {
258  if (i != j) {
259  neighbors += edge_vars.get(i, j);
260  }
261  }
262  model.AddLinearConstraint(neighbors == 2, absl::StrCat("n_", i));
263  }
265  args.parameters.enable_output = absl::GetFlag(FLAGS_solve_logs);
266  const int threads = absl::GetFlag(FLAGS_threads);
267  if (threads > 0) {
268  args.parameters.threads = threads;
269  }
270  args.callback_registration.events.insert(
271  math_opt::CallbackEvent::kMipSolution);
273  args.callback = [&edge_vars](const math_opt::CallbackData& cb_data) {
274  // At event CallbackEvent::kMipSolution, a solution is always present.
275  CHECK(cb_data.solution.has_value());
276  const std::vector<Cycle> cycles =
277  FindCycles(EdgeValues(edge_vars, *cb_data.solution));
279  if (cycles.size() > 1) {
280  for (const Cycle& cycle : cycles) {
281  result.AddLazyConstraint(CutsetConstraint(cycle, edge_vars));
282  }
283  }
284  return result;
285  };
287  math_opt::Solve(model, math_opt::SolverType::kGurobi, args));
288  if (result.termination.reason != math_opt::TerminationReason::kOptimal) {
290  << "Expected TSP solve terminate with reason optimal, found: "
291  << result.termination;
292  }
293  std::cout << "Route length: " << result.objective_value() << std::endl;
294  const std::vector<Cycle> cycles =
295  FindCycles(EdgeValues(edge_vars, result.variable_values()));
296  CHECK_EQ(cycles.size(), 1);
297  CHECK_EQ(cycles[0].size(), n);
298  return cycles[0];
299 }
300 
301 // Produces an SVG to draw a route for a TSP.
302 std::string RouteSvg(const std::vector<std::pair<double, double>>& cities,
303  const Cycle& cycle) {
304  constexpr int image_px = 1000;
305  constexpr int r = 5;
306  constexpr int image_plus_border = image_px + 2 * r;
307  std::vector<std::string> svg_lines;
308  svg_lines.push_back(absl::StrCat("<svg width=\"", image_plus_border,
309  "\" height=\"", image_plus_border, "\">"));
310  std::vector<std::string> polygon_coords;
311  for (const int city : cycle) {
312  const int x =
313  static_cast<int>(std::round(cities[city].first * image_px)) + r;
314  const int y =
315  static_cast<int>(std::round(cities[city].second * image_px)) + r;
316  svg_lines.push_back(absl::StrCat("<circle cx=\"", x, "\" cy=\"", y,
317  "\" r=\"", r, "\" fill=\"blue\" />"));
318  polygon_coords.push_back(absl::StrCat(x, ",", y));
319  }
320  std::string polygon_coords_string = absl::StrJoin(polygon_coords, " ");
321  svg_lines.push_back(
322  absl::StrCat("<polygon fill=\"none\" stroke=\"blue\" points=\"",
323  polygon_coords_string, "\" />"));
324  svg_lines.push_back("</svg>");
325  return absl::StrJoin(svg_lines, "\n");
326 }
327 
328 void RealMain() {
329  std::vector<std::pair<double, double>> cities;
330  if (absl::GetFlag(FLAGS_test_instance)) {
331  cities = TestCities();
332  } else {
333  cities = RandomCities(absl::GetFlag(FLAGS_num_cities));
334  }
335  absl::StatusOr<Cycle> solution = SolveTsp(cities);
336  if (!solution.ok()) {
337  LOG(QFATAL) << solution.status();
338  }
339  const std::string svg = RouteSvg(cities, *solution);
340  if (absl::GetFlag(FLAGS_output).empty()) {
341  std::cout << svg << std::endl;
342  } else {
343  QCHECK_OK(
344  file::SetContents(absl::GetFlag(FLAGS_output), svg, file::Defaults()));
345  }
346 }
347 
348 } // namespace
349 
350 int main(int argc, char** argv) {
351  InitGoogle(argv[0], &argc, &argv, true);
352  RealMain();
353  return 0;
354 }
#define ASSIGN_OR_RETURN(lhs, rexpr)
const V & at(const K &k) const
Definition: id_map.h:497
Block * next
GRBmodel * model
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
Definition: init_google.h:34
Options Defaults()
Definition: base/file.h:123
absl::Status SetContents(const absl::string_view &filename, const absl::string_view &contents, int flags)
Definition: base/file.cc:205
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
StatusBuilder InternalErrorBuilder()
absl::flat_hash_set< CallbackEvent > events
Definition: callback.h:168
void AddLazyConstraint(BoundedLinearExpression linear_constraint)
Definition: callback.h:253
const VariableMap< double > & variable_values() const
int main(int argc, char **argv)
Definition: tsp_mo.cc:350
ABSL_FLAG(int, num_cities, 50, "Number of cities in random TSP.")