C++ Reference

C++ Reference: Graph

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"
33 #include "ortools/base/integral_types.h"
34 #include "ortools/base/logging.h"
36 #include "ortools/graph/graph.h"
38 #include "ortools/graph/perfect_matching.h"
39 #include "ortools/linear_solver/linear_solver.h"
40 #include "ortools/linear_solver/linear_solver.pb.h"
41 #include "ortools/util/saturated_arithmetic.h"
42 
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;
157  MPModelProto model;
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",
200  MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
201 #elif defined(USE_CBC)
202  MPSolver mp_solver("MatchingWithCBC",
203  MPSolver::CBC_MIXED_INTEGER_PROGRAMMING);
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  }
211  MPSolutionResponse response;
212  mp_solver.FillSolutionResponseProto(&response);
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_
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
Definition: christofides.h:228
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:249
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root)
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:69
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
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:153
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition: graph.h:1507