C++ Reference

C++ Reference: Graph

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 
23 #include "ortools/base/integral_types.h"
24 #include "ortools/base/logging.h"
26 #include "ortools/graph/graph.h"
28 #include "ortools/graph/perfect_matching.h"
29 #include "ortools/linear_solver/linear_solver.h"
30 #include "ortools/linear_solver/linear_solver.pb.h"
31 #include "ortools/util/saturated_arithmetic.h"
32 
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) {
80  return CapAdd(a, b);
81  }
82 
83  // Matching algorithm to use.
84  MatchingAlgorithm matching_;
85 
86  // The complete graph on the nodes of the problem.
87  CompleteGraph<NodeIndex, ArcIndex> graph_;
88 
89  // Function returning the cost between nodes of the problem.
90  const CostFunction costs_;
91 
92  // The cost of the computed TSP path.
93  CostType tsp_cost_;
94 
95  // The path of the computed TSP,
96  std::vector<NodeIndex> tsp_path_;
97 
98  // True if the TSP has been solved, false otherwise.
99  bool solved_;
100 };
101 
102 // Computes a minimum weight perfect matching on an undirected graph.
103 template <typename WeightFunctionType, typename GraphType>
104 std::vector<
105  std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>
106 ComputeMinimumWeightMatching(const GraphType& graph,
107  const WeightFunctionType& weight) {
108  using ArcIndex = typename GraphType::ArcIndex;
109  using NodeIndex = typename GraphType::NodeIndex;
110  MinCostPerfectMatching matching(graph.num_nodes());
111  for (NodeIndex tail : graph.AllNodes()) {
112  for (const ArcIndex arc : graph.OutgoingArcs(tail)) {
113  const NodeIndex head = graph.Head(arc);
114  // Adding both arcs is redudant for MinCostPerfectMatching.
115  if (tail < head) {
116  matching.AddEdgeWithCost(tail, head, weight(arc));
117  }
118  }
119  }
120  MinCostPerfectMatching::Status status = matching.Solve();
121  DCHECK_EQ(status, MinCostPerfectMatching::OPTIMAL);
122  std::vector<std::pair<NodeIndex, NodeIndex>> match;
123  for (NodeIndex tail : graph.AllNodes()) {
124  const NodeIndex head = matching.Match(tail);
125  if (tail < head) { // Both arcs are matched for a given edge, we keep one.
126  match.emplace_back(tail, head);
127  }
128  }
129  return match;
130 }
131 
132 #if defined(USE_CBC) || defined(USE_SCIP)
133 // Computes a minimum weight perfect matching on an undirected graph using a
134 // Mixed Integer Programming model.
135 // TODO(user): Handle infeasible cases if this algorithm is used outside of
136 // Christofides.
137 template <typename WeightFunctionType, typename GraphType>
138 std::vector<
139  std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>
140 ComputeMinimumWeightMatchingWithMIP(const GraphType& graph,
141  const WeightFunctionType& weight) {
142  using ArcIndex = typename GraphType::ArcIndex;
143  using NodeIndex = typename GraphType::NodeIndex;
144  MPModelProto model;
145  model.set_maximize(false);
146  // The model is composed of Boolean decision variables to select matching arcs
147  // and constraints ensuring that each node appears in exactly one selected
148  // arc. The objective is to minimize the sum of the weights of selected arcs.
149  // It is assumed the graph is symmetrical.
150  std::vector<int> variable_indices(graph.num_arcs(), -1);
151  for (NodeIndex node : graph.AllNodes()) {
152  // Creating arc-selection Boolean variable.
153  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
154  const NodeIndex head = graph.Head(arc);
155  if (node < head) {
156  variable_indices[arc] = model.variable_size();
157  MPVariableProto* const arc_var = model.add_variable();
158  arc_var->set_lower_bound(0);
159  arc_var->set_upper_bound(1);
160  arc_var->set_is_integer(true);
161  arc_var->set_objective_coefficient(weight(arc));
162  }
163  }
164  // Creating matching constraint:
165  // for all node i, sum(j) arc(i,j) + sum(j) arc(j,i) = 1
166  MPConstraintProto* const one_of_ct = model.add_constraint();
167  one_of_ct->set_lower_bound(1);
168  one_of_ct->set_upper_bound(1);
169  }
170  for (NodeIndex node : graph.AllNodes()) {
171  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
172  const NodeIndex head = graph.Head(arc);
173  if (node < head) {
174  const int arc_var = variable_indices[arc];
175  DCHECK_GE(arc_var, 0);
176  MPConstraintProto* one_of_ct = model.mutable_constraint(node);
177  one_of_ct->add_var_index(arc_var);
178  one_of_ct->add_coefficient(1);
179  one_of_ct = model.mutable_constraint(head);
180  one_of_ct->add_var_index(arc_var);
181  one_of_ct->add_coefficient(1);
182  }
183  }
184  }
185 #if defined(USE_SCIP)
186  MPSolver mp_solver("MatchingWithSCIP",
187  MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
188 #elif defined(USE_CBC)
189  MPSolver mp_solver("MatchingWithCBC",
190  MPSolver::CBC_MIXED_INTEGER_PROGRAMMING);
191 #endif
192  std::string error;
193  mp_solver.LoadModelFromProto(model, &error);
194  MPSolver::ResultStatus status = mp_solver.Solve();
195  CHECK_EQ(status, MPSolver::OPTIMAL);
196  MPSolutionResponse response;
197  mp_solver.FillSolutionResponseProto(&response);
198  std::vector<std::pair<NodeIndex, NodeIndex>> matching;
199  for (ArcIndex arc = 0; arc < variable_indices.size(); ++arc) {
200  const int arc_var = variable_indices[arc];
201  if (arc_var >= 0 && response.variable_value(arc_var) > .9) {
202  DCHECK_GE(response.variable_value(arc_var), 1.0 - 1e-4);
203  matching.emplace_back(graph.Tail(arc), graph.Head(arc));
204  }
205  }
206  return matching;
207 }
208 #endif // defined(USE_CBC) || defined(USE_SCIP)
209 
210 template <typename CostType, typename ArcIndex, typename NodeIndex,
211  typename CostFunction>
213  ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
214  : matching_(MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING),
215  graph_(num_nodes),
216  costs_(std::move(costs)),
217  tsp_cost_(0),
218  solved_(false) {}
219 
220 template <typename CostType, typename ArcIndex, typename NodeIndex,
221  typename CostFunction>
222 CostType ChristofidesPathSolver<CostType, ArcIndex, NodeIndex,
223  CostFunction>::TravelingSalesmanCost() {
224  if (!solved_) {
225  Solve();
226  }
227  return tsp_cost_;
228 }
229 
230 template <typename CostType, typename ArcIndex, typename NodeIndex,
231  typename CostFunction>
232 std::vector<NodeIndex> ChristofidesPathSolver<
233  CostType, ArcIndex, NodeIndex, CostFunction>::TravelingSalesmanPath() {
234  if (!solved_) {
235  Solve();
236  }
237  return tsp_path_;
238 }
239 
240 template <typename CostType, typename ArcIndex, typename NodeIndex,
241  typename CostFunction>
243  CostFunction>::Solve() {
244  const NodeIndex num_nodes = graph_.num_nodes();
245  tsp_path_.clear();
246  tsp_cost_ = 0;
247  if (num_nodes == 1) {
248  tsp_path_ = {0, 0};
249  }
250  if (num_nodes <= 1) {
251  return;
252  }
253  // Compute Minimum Spanning Tree.
254  const std::vector<ArcIndex> mst =
255  BuildPrimMinimumSpanningTree(graph_, [this](ArcIndex arc) {
256  return costs_(graph_.Tail(arc), graph_.Head(arc));
257  });
258  // Detect odd degree nodes.
259  std::vector<NodeIndex> degrees(num_nodes, 0);
260  for (ArcIndex arc : mst) {
261  degrees[graph_.Tail(arc)]++;
262  degrees[graph_.Head(arc)]++;
263  }
264  std::vector<NodeIndex> odd_degree_nodes;
265  for (int i = 0; i < degrees.size(); ++i) {
266  if (degrees[i] % 2 != 0) {
267  odd_degree_nodes.push_back(i);
268  }
269  }
270  // Find minimum-weight perfect matching on odd-degree-node complete graph.
271  // TODO(user): Make this code available as an independent algorithm.
272  const NodeIndex reduced_size = odd_degree_nodes.size();
273  DCHECK_NE(0, reduced_size);
274  CompleteGraph<NodeIndex, ArcIndex> reduced_graph(reduced_size);
275  std::vector<std::pair<NodeIndex, NodeIndex>> closure_arcs;
276  switch (matching_) {
277  case MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING: {
278  closure_arcs = ComputeMinimumWeightMatching(
279  reduced_graph, [this, &reduced_graph,
280  &odd_degree_nodes](CompleteGraph<>::ArcIndex arc) {
281  return costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
282  odd_degree_nodes[reduced_graph.Head(arc)]);
283  });
284  break;
285  }
286 #if defined(USE_CBC) || defined(USE_SCIP)
287  case MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING_WITH_MIP: {
289  reduced_graph, [this, &reduced_graph,
290  &odd_degree_nodes](CompleteGraph<>::ArcIndex arc) {
291  return costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
292  odd_degree_nodes[reduced_graph.Head(arc)]);
293  });
294  break;
295  }
296 #endif // defined(USE_CBC) || defined(USE_SCIP)
297  case MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING: {
298  // TODO(user): Cost caching was added and can gain up to 20% but
299  // increases memory usage; see if we can avoid caching.
300  std::vector<ArcIndex> ordered_arcs(reduced_graph.num_arcs());
301  std::vector<CostType> ordered_arc_costs(reduced_graph.num_arcs(), 0);
302  for (const ArcIndex arc : reduced_graph.AllForwardArcs()) {
303  ordered_arcs[arc] = arc;
304  ordered_arc_costs[arc] =
305  costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
306  odd_degree_nodes[reduced_graph.Head(arc)]);
307  }
308  std::sort(ordered_arcs.begin(), ordered_arcs.end(),
309  [&ordered_arc_costs](ArcIndex arc_a, ArcIndex arc_b) {
310  return ordered_arc_costs[arc_a] < ordered_arc_costs[arc_b];
311  });
312  std::vector<bool> touched_nodes(reduced_size, false);
313  for (ArcIndex arc_index = 0; closure_arcs.size() * 2 < reduced_size;
314  ++arc_index) {
315  const ArcIndex arc = ordered_arcs[arc_index];
316  const NodeIndex tail = reduced_graph.Tail(arc);
317  const NodeIndex head = reduced_graph.Head(arc);
318  if (head != tail && !touched_nodes[tail] && !touched_nodes[head]) {
319  touched_nodes[tail] = true;
320  touched_nodes[head] = true;
321  closure_arcs.emplace_back(tail, head);
322  }
323  }
324  break;
325  }
326  }
327  // Build Eulerian path on minimum spanning tree + closing edges from matching
328  // and extract a solution to the Traveling Salesman from the path by skipping
329  // duplicate nodes.
331  num_nodes, closure_arcs.size() + mst.size());
332  for (ArcIndex arc : mst) {
333  egraph.AddArc(graph_.Tail(arc), graph_.Head(arc));
334  }
335  for (const auto arc : closure_arcs) {
336  egraph.AddArc(odd_degree_nodes[arc.first], odd_degree_nodes[arc.second]);
337  }
338  std::vector<bool> touched(num_nodes, false);
339  DCHECK(IsEulerianGraph(egraph));
340  for (const NodeIndex node : BuildEulerianTourFromNode(egraph, 0)) {
341  if (touched[node]) continue;
342  touched[node] = true;
343  tsp_cost_ = SafeAdd(tsp_cost_,
344  tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), node));
345  tsp_path_.push_back(node);
346  }
347  tsp_cost_ =
348  SafeAdd(tsp_cost_, tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), 0));
349  tsp_path_.push_back(0);
350  solved_ = true;
351 }
352 } // namespace operations_research
353 
354 #endif // OR_TOOLS_GRAPH_CHRISTOFIDES_H_
Definition: graph.h:460
@ MINIMUM_WEIGHT_MATCHING_WITH_MIP
bool IsEulerianGraph(const Graph &graph)
Definition: eulerian_path.h:40
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:59
std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:140
Definition: christofides.h:33
std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > ComputeMinimumWeightMatching(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:106
@ MINIMUM_WEIGHT_MATCHING
Definition: christofides.h:40
@ MINIMAL_WEIGHT_MATCHING
std::vector< typename Graph::ArcIndex > BuildPrimMinimumSpanningTree(const Graph &graph, const ArcValue &arc_value)
int32 ArcIndex
Definition: ebert_graph.h:201
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root)
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:233
MatchingAlgorithm
Definition: christofides.h:42
int32 NodeIndex
Definition: ebert_graph.h:192
CostType TravelingSalesmanCost()
Definition: christofides.h:223
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
Definition: christofides.h:213