OR-Tools  9.6
christofides.h
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 // ChristofidesPathSolver computes an approximate solution to the Traveling
15 // Salesman Problen using the Christofides algorithm (c.f.
16 // https://en.wikipedia.org/wiki/Christofides_algorithm).
17 // Note that the algorithm guarantees finding a solution within 3/2 of the
18 // optimum when using minimum weight perfect matching in the matching phase.
19 // The complexity of the algorithm is dominated by the complexity of the
20 // matching algorithm: O(n^2 * log(n)) if minimal matching is used, or at least
21 // O(n^3) or O(nmlog(n)) otherwise, depending on the implementation of the
22 // perfect matching algorithm used, where n is the number of nodes and m is the
23 // number of edges of the subgraph induced by odd-degree nodes of the minimum
24 // spanning tree.
25 
26 #ifndef OR_TOOLS_GRAPH_CHRISTOFIDES_H_
27 #define OR_TOOLS_GRAPH_CHRISTOFIDES_H_
28 
29 #include <cstdint>
30 #include <string>
31 #include <vector>
32 
33 #include "absl/status/status.h"
34 #include "absl/status/statusor.h"
36 #include "ortools/base/logging.h"
38 #include "ortools/graph/graph.h"
42 #include "ortools/linear_solver/linear_solver.pb.h"
44 
45 namespace operations_research {
46 
47 using ::util::CompleteGraph;
48 
49 template <typename CostType, typename ArcIndex = int64_t,
50  typename NodeIndex = int32_t,
51  typename CostFunction = std::function<CostType(NodeIndex, NodeIndex)>>
53  public:
54  enum class MatchingAlgorithm {
56 #if defined(USE_CBC) || defined(USE_SCIP)
58 #endif // defined(USE_CBC) || defined(USE_SCIP)
60  };
61  ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs);
62 
63  // Sets the matching algorithm to use. A minimum weight perfect matching
64  // (MINIMUM_WEIGHT_MATCHING) guarantees the 3/2 upper bound to the optimal
65  // solution. A minimal weight perfect matching (MINIMAL_WEIGHT_MATCHING)
66  // finds a locally minimal weight matching which does not offer any bound
67  // guarantee but, as of 1/2017, is orders of magnitude faster than the
68  // minimum matching.
69  // By default, MINIMAL_WEIGHT_MATCHING is selected.
70  // TODO(user): Change the default when minimum matching gets faster.
72  matching_ = matching;
73  }
74 
75  // Returns the cost of the approximate TSP tour.
76  CostType TravelingSalesmanCost();
77 
78  // Returns the approximate TSP tour.
79  std::vector<NodeIndex> TravelingSalesmanPath();
80 
81  // Runs the Christofides algorithm. Returns true if a solution was found,
82  // false otherwise.
83  bool Solve();
84 
85  private:
86  int64_t SafeAdd(int64_t a, int64_t b) { return CapAdd(a, b); }
87 
88  // Matching algorithm to use.
89  MatchingAlgorithm matching_;
90 
91  // The complete graph on the nodes of the problem.
92  CompleteGraph<NodeIndex, ArcIndex> graph_;
93 
94  // Function returning the cost between nodes of the problem.
95  const CostFunction costs_;
96 
97  // The cost of the computed TSP path.
98  CostType tsp_cost_;
99 
100  // The path of the computed TSP,
101  std::vector<NodeIndex> tsp_path_;
102 
103  // True if the TSP has been solved, false otherwise.
104  bool solved_;
105 };
106 
107 // Computes a minimum weight perfect matching on an undirected graph.
108 template <typename WeightFunctionType, typename GraphType>
109 absl::StatusOr<std::vector<
110  std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
111 ComputeMinimumWeightMatching(const GraphType& graph,
112  const WeightFunctionType& weight) {
113  using ArcIndex = typename GraphType::ArcIndex;
114  using NodeIndex = typename GraphType::NodeIndex;
115  MinCostPerfectMatching matching(graph.num_nodes());
116  for (NodeIndex tail : graph.AllNodes()) {
117  for (const ArcIndex arc : graph.OutgoingArcs(tail)) {
118  const NodeIndex head = graph.Head(arc);
119  // Adding both arcs is redudant for MinCostPerfectMatching.
120  if (tail < head) {
121  matching.AddEdgeWithCost(tail, head, weight(arc));
122  }
123  }
124  }
127  return absl::InvalidArgumentError("Perfect matching failed");
128  }
129  std::vector<std::pair<NodeIndex, NodeIndex>> match;
130  for (NodeIndex tail : graph.AllNodes()) {
131  const NodeIndex head = matching.Match(tail);
132  if (tail < head) { // Both arcs are matched for a given edge, we keep one.
133  match.emplace_back(tail, head);
134  }
135  }
136  return match;
137 }
138 
139 #if defined(USE_CBC) || defined(USE_SCIP)
140 // Computes a minimum weight perfect matching on an undirected graph using a
141 // Mixed Integer Programming model.
142 // TODO(user): Handle infeasible cases if this algorithm is used outside of
143 // Christofides.
144 template <typename WeightFunctionType, typename GraphType>
145 absl::StatusOr<std::vector<
146  std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
147 ComputeMinimumWeightMatchingWithMIP(const GraphType& graph,
148  const WeightFunctionType& weight) {
149  using ArcIndex = typename GraphType::ArcIndex;
150  using NodeIndex = typename GraphType::NodeIndex;
151  MPModelProto model;
152  model.set_maximize(false);
153  // The model is composed of Boolean decision variables to select matching arcs
154  // and constraints ensuring that each node appears in exactly one selected
155  // arc. The objective is to minimize the sum of the weights of selected arcs.
156  // It is assumed the graph is symmetrical.
157  std::vector<int> variable_indices(graph.num_arcs(), -1);
158  for (NodeIndex node : graph.AllNodes()) {
159  // Creating arc-selection Boolean variable.
160  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
161  const NodeIndex head = graph.Head(arc);
162  if (node < head) {
163  variable_indices[arc] = model.variable_size();
164  MPVariableProto* const arc_var = model.add_variable();
165  arc_var->set_lower_bound(0);
166  arc_var->set_upper_bound(1);
167  arc_var->set_is_integer(true);
168  arc_var->set_objective_coefficient(weight(arc));
169  }
170  }
171  // Creating matching constraint:
172  // for all node i, sum(j) arc(i,j) + sum(j) arc(j,i) = 1
173  MPConstraintProto* const one_of_ct = model.add_constraint();
174  one_of_ct->set_lower_bound(1);
175  one_of_ct->set_upper_bound(1);
176  }
177  for (NodeIndex node : graph.AllNodes()) {
178  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
179  const NodeIndex head = graph.Head(arc);
180  if (node < head) {
181  const int arc_var = variable_indices[arc];
182  DCHECK_GE(arc_var, 0);
183  MPConstraintProto* one_of_ct = model.mutable_constraint(node);
184  one_of_ct->add_var_index(arc_var);
185  one_of_ct->add_coefficient(1);
186  one_of_ct = model.mutable_constraint(head);
187  one_of_ct->add_var_index(arc_var);
188  one_of_ct->add_coefficient(1);
189  }
190  }
191  }
192 #if defined(USE_SCIP)
193  MPSolver mp_solver("MatchingWithSCIP",
195 #elif defined(USE_CBC)
196  MPSolver mp_solver("MatchingWithCBC",
198 #endif
199  std::string error;
200  mp_solver.LoadModelFromProto(model, &error);
201  MPSolver::ResultStatus status = mp_solver.Solve();
202  if (status != MPSolver::OPTIMAL) {
203  return absl::InvalidArgumentError("MIP-based matching failed");
204  }
205  MPSolutionResponse response;
207  std::vector<std::pair<NodeIndex, NodeIndex>> matching;
208  for (ArcIndex arc = 0; arc < variable_indices.size(); ++arc) {
209  const int arc_var = variable_indices[arc];
210  if (arc_var >= 0 && response.variable_value(arc_var) > .9) {
211  DCHECK_GE(response.variable_value(arc_var), 1.0 - 1e-4);
212  matching.emplace_back(graph.Tail(arc), graph.Head(arc));
213  }
214  }
215  return matching;
216 }
217 #endif // defined(USE_CBC) || defined(USE_SCIP)
218 
219 template <typename CostType, typename ArcIndex, typename NodeIndex,
220  typename CostFunction>
222  ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
223  : matching_(MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING),
224  graph_(num_nodes),
225  costs_(std::move(costs)),
226  tsp_cost_(0),
227  solved_(false) {}
228 
229 template <typename CostType, typename ArcIndex, typename NodeIndex,
230  typename CostFunction>
231 CostType ChristofidesPathSolver<CostType, ArcIndex, NodeIndex,
232  CostFunction>::TravelingSalesmanCost() {
233  if (!solved_) {
234  bool const ok = Solve();
235  DCHECK(ok);
236  }
237  return tsp_cost_;
238 }
239 
240 template <typename CostType, typename ArcIndex, typename NodeIndex,
241  typename CostFunction>
242 std::vector<NodeIndex> ChristofidesPathSolver<
243  CostType, ArcIndex, NodeIndex, CostFunction>::TravelingSalesmanPath() {
244  if (!solved_) {
245  const bool ok = Solve();
246  DCHECK(ok);
247  }
248  return tsp_path_;
249 }
250 
251 template <typename CostType, typename ArcIndex, typename NodeIndex,
252  typename CostFunction>
254  CostFunction>::Solve() {
255  const NodeIndex num_nodes = graph_.num_nodes();
256  tsp_path_.clear();
257  tsp_cost_ = 0;
258  if (num_nodes == 1) {
259  tsp_path_ = {0, 0};
260  }
261  if (num_nodes <= 1) {
262  return true;
263  }
264  // Compute Minimum Spanning Tree.
265  const std::vector<ArcIndex> mst =
266  BuildPrimMinimumSpanningTree(graph_, [this](ArcIndex arc) {
267  return costs_(graph_.Tail(arc), graph_.Head(arc));
268  });
269  // Detect odd degree nodes.
270  std::vector<NodeIndex> degrees(num_nodes, 0);
271  for (ArcIndex arc : mst) {
272  degrees[graph_.Tail(arc)]++;
273  degrees[graph_.Head(arc)]++;
274  }
275  std::vector<NodeIndex> odd_degree_nodes;
276  for (int i = 0; i < degrees.size(); ++i) {
277  if (degrees[i] % 2 != 0) {
278  odd_degree_nodes.push_back(i);
279  }
280  }
281  // Find minimum-weight perfect matching on odd-degree-node complete graph.
282  // TODO(user): Make this code available as an independent algorithm.
283  const NodeIndex reduced_size = odd_degree_nodes.size();
284  DCHECK_NE(0, reduced_size);
285  CompleteGraph<NodeIndex, ArcIndex> reduced_graph(reduced_size);
286  std::vector<std::pair<NodeIndex, NodeIndex>> closure_arcs;
287  switch (matching_) {
288  case MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING: {
289  auto result = ComputeMinimumWeightMatching(
290  reduced_graph, [this, &reduced_graph,
291  &odd_degree_nodes](CompleteGraph<>::ArcIndex arc) {
292  return costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
293  odd_degree_nodes[reduced_graph.Head(arc)]);
294  });
295  if (!result.ok()) {
296  return false;
297  }
298  result->swap(closure_arcs);
299  break;
300  }
301 #if defined(USE_CBC) || defined(USE_SCIP)
302  case MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING_WITH_MIP: {
304  reduced_graph, [this, &reduced_graph,
305  &odd_degree_nodes](CompleteGraph<>::ArcIndex arc) {
306  return costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
307  odd_degree_nodes[reduced_graph.Head(arc)]);
308  });
309  if (!result.ok()) {
310  return false;
311  }
312  result->swap(closure_arcs);
313  break;
314  }
315 #endif // defined(USE_CBC) || defined(USE_SCIP)
316  case MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING: {
317  // TODO(user): Cost caching was added and can gain up to 20% but
318  // increases memory usage; see if we can avoid caching.
319  std::vector<ArcIndex> ordered_arcs(reduced_graph.num_arcs());
320  std::vector<CostType> ordered_arc_costs(reduced_graph.num_arcs(), 0);
321  for (const ArcIndex arc : reduced_graph.AllForwardArcs()) {
322  ordered_arcs[arc] = arc;
323  ordered_arc_costs[arc] =
324  costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
325  odd_degree_nodes[reduced_graph.Head(arc)]);
326  }
327  std::sort(ordered_arcs.begin(), ordered_arcs.end(),
328  [&ordered_arc_costs](ArcIndex arc_a, ArcIndex arc_b) {
329  return ordered_arc_costs[arc_a] < ordered_arc_costs[arc_b];
330  });
331  std::vector<bool> touched_nodes(reduced_size, false);
332  for (ArcIndex arc_index = 0; closure_arcs.size() * 2 < reduced_size;
333  ++arc_index) {
334  const ArcIndex arc = ordered_arcs[arc_index];
335  const NodeIndex tail = reduced_graph.Tail(arc);
336  const NodeIndex head = reduced_graph.Head(arc);
337  if (head != tail && !touched_nodes[tail] && !touched_nodes[head]) {
338  touched_nodes[tail] = true;
339  touched_nodes[head] = true;
340  closure_arcs.emplace_back(tail, head);
341  }
342  }
343  break;
344  }
345  }
346  // Build Eulerian path on minimum spanning tree + closing edges from matching
347  // and extract a solution to the Traveling Salesman from the path by skipping
348  // duplicate nodes.
350  num_nodes, closure_arcs.size() + mst.size());
351  for (ArcIndex arc : mst) {
352  egraph.AddArc(graph_.Tail(arc), graph_.Head(arc));
353  }
354  for (const auto arc : closure_arcs) {
355  egraph.AddArc(odd_degree_nodes[arc.first], odd_degree_nodes[arc.second]);
356  }
357  std::vector<bool> touched(num_nodes, false);
358  DCHECK(IsEulerianGraph(egraph));
359  for (const NodeIndex node : BuildEulerianTourFromNode(egraph, 0)) {
360  if (touched[node]) continue;
361  touched[node] = true;
362  tsp_cost_ = SafeAdd(tsp_cost_,
363  tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), node));
364  tsp_path_.push_back(node);
365  }
366  tsp_cost_ =
367  SafeAdd(tsp_cost_, tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), 0));
368  tsp_path_.push_back(0);
369  solved_ = true;
370  return true;
371 }
372 } // namespace operations_research
373 
374 #endif // OR_TOOLS_GRAPH_CHRISTOFIDES_H_
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:243
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
Definition: christofides.h:222
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:71
This mathematical programming (MP) solver class is the main class though which users build and solve ...
void FillSolutionResponseProto(MPSolutionResponse *response) const
Encodes the current solution in a solution response protocol buffer.
ResultStatus
The status of solving the problem.
MPSolverResponseStatus LoadModelFromProto(const MPModelProto &input_model, std::string *error_message)
Loads model from protocol buffer.
ResultStatus Solve()
Solves the problem using the default parameter values.
void AddEdgeWithCost(int tail, int head, int64_t cost)
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition: graph.h:1544
int64_t b
int64_t a
SharedResponseManager * response
absl::Status status
Definition: g_gurobi.cc:41
GRBmodel * model
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
int arc
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
Collection of objects used to extend the Constraint Solver library.
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatching(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:111
bool IsEulerianGraph(const Graph &graph, bool assume_connectivity=true)
Definition: eulerian_path.h:45
int64_t CapAdd(int64_t x, int64_t y)
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root, bool assume_connectivity=true)
std::vector< typename Graph::ArcIndex > BuildPrimMinimumSpanningTree(const Graph &graph, const ArcValue &arc_value)
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:147
int64_t weight
Definition: pack.cc:510
int64_t tail
int64_t head