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