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