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 "absl/container/flat_hash_map.h"
24 #include "ortools/base/integral_types.h"
25 #include "ortools/base/logging.h"
27 #include "ortools/graph/graph.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 {
43 #if defined(USE_CBC) || defined(USE_SCIP)
45 #endif // defined(USE_CBC) || defined(USE_SCIP)
47  };
48  ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs);
49 
50  // Sets the matching algorith to use. A minimum weight perfect matching
51  // (MINIMUM_WEIGHT_MATCHING) guarantees the 3/2 upper bound to the optimal
52  // solution. A minimal weight perfect matching (MINIMAL_WEIGHT_MATCHING)
53  // finds a locally minimal weight matching which does not offer any bound
54  // guarantee but, as of 1/2017, is orders of magnitude faster than the
55  // minimum matching.
56  // By default, MINIMAL_WEIGHT_MATCHING is selected.
57  // TODO(user): Change the default when minimum matching gets faster.
59  matching_ = matching;
60  }
61 
62  // Returns the cost of the approximate TSP tour.
63  CostType TravelingSalesmanCost();
64 
65  // Returns the approximate TSP tour.
66  std::vector<NodeIndex> TravelingSalesmanPath();
67 
68  private:
69  // Runs the Christofides algorithm.
70  void Solve();
71 
72  // Safe addition operator to avoid overflows when possible.
73  // template <typename T>
74  // T SafeAdd(T a, T b) {
75  // return a + b;
76  // }
77  //template <>
78  int64 SafeAdd(int64 a, int64 b) {
79  return CapAdd(a, b);
80  }
81 
82  // Matching algorithm to use.
83  MatchingAlgorithm matching_;
84 
85  // The complete graph on the nodes of the problem.
86  CompleteGraph<NodeIndex, ArcIndex> graph_;
87 
88  // Function returning the cost between nodes of the problem.
89  const CostFunction costs_;
90 
91  // The cost of the computed TSP path.
92  CostType tsp_cost_;
93 
94  // The path of the computed TSP,
95  std::vector<NodeIndex> tsp_path_;
96 
97  // True if the TSP has been solved, false otherwise.
98  bool solved_;
99 };
100 
101 #if defined(USE_CBC) || defined(USE_SCIP)
102 // Computes a minimum weight perfect matching on an undirected graph using a
103 // Mixed Integer Programming model.
104 // TODO(user): Handle infeasible cases if this algorithm is used outside of
105 // Christofides.
106 template <typename WeightFunctionType, typename GraphType>
107 std::vector<typename GraphType::ArcIndex> ComputeMinimumWeightMatchingWithMIP(
108  const GraphType& graph, const WeightFunctionType& weight) {
109  using ArcIndex = typename GraphType::ArcIndex;
110  using NodeIndex = typename GraphType::NodeIndex;
111  MPModelProto model;
112  model.set_maximize(false);
113  // The model is composed of Boolean decision variables to select matching arcs
114  // and constraints ensuring that each node appears in exactly one selected
115  // arc. The objective is to minimize the sum of the weights of selected arcs.
116  // It is assumed the graph is symmetrical.
117  absl::flat_hash_map<ArcIndex, ArcIndex> variable_indices;
118  for (NodeIndex node : graph.AllNodes()) {
119  // Creating arc-selection Boolean variable.
120  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
121  const NodeIndex head = graph.Head(arc);
122  if (node < head) {
123  variable_indices[arc] = model.variable_size();
124  MPVariableProto* const arc_var = model.add_variable();
125  arc_var->set_lower_bound(0);
126  arc_var->set_upper_bound(1);
127  arc_var->set_is_integer(true);
128  arc_var->set_objective_coefficient(weight(arc));
129  }
130  }
131  // Creating matching constraint:
132  // for all node i, sum(j) arc(i,j) + sum(j) arc(j,i) = 1
133  MPConstraintProto* const one_of_ct = model.add_constraint();
134  one_of_ct->set_lower_bound(1);
135  one_of_ct->set_upper_bound(1);
136  }
137  for (NodeIndex node : graph.AllNodes()) {
138  for (const ArcIndex arc : graph.OutgoingArcs(node)) {
139  const NodeIndex head = graph.Head(arc);
140  if (node < head) {
141  MPConstraintProto* one_of_ct = model.mutable_constraint(node);
142  one_of_ct->add_var_index(variable_indices[arc]);
143  one_of_ct->add_coefficient(1);
144  one_of_ct = model.mutable_constraint(head);
145  one_of_ct->add_var_index(variable_indices[arc]);
146  one_of_ct->add_coefficient(1);
147  }
148  }
149  }
150 #if defined(USE_SCIP)
151  MPSolver mp_solver("MatchingWithSCIP",
152  MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
153 #elif defined(USE_CBC)
154  MPSolver mp_solver("MatchingWithCBC",
155  MPSolver::CBC_MIXED_INTEGER_PROGRAMMING);
156 #endif
157  std::string error;
158  mp_solver.LoadModelFromProto(model, &error);
159  MPSolver::ResultStatus status = mp_solver.Solve();
160  CHECK_EQ(status, MPSolver::OPTIMAL);
161  MPSolutionResponse response;
162  mp_solver.FillSolutionResponseProto(&response);
163  std::vector<ArcIndex> matching;
164  for (auto arc : variable_indices) {
165  if (response.variable_value(arc.second) > .9) {
166  DCHECK_GE(response.variable_value(arc.second), 1.0 - 1e-4);
167  matching.push_back(arc.first);
168  }
169  }
170  return matching;
171 }
172 #endif // defined(USE_CBC) || defined(USE_SCIP)
173 
174 template <typename CostType, typename ArcIndex, typename NodeIndex,
175  typename CostFunction>
177  ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
178  : matching_(MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING),
179  graph_(num_nodes),
180  costs_(std::move(costs)),
181  tsp_cost_(0),
182  solved_(false) {}
183 
184 template <typename CostType, typename ArcIndex, typename NodeIndex,
185  typename CostFunction>
186 CostType ChristofidesPathSolver<CostType, ArcIndex, NodeIndex,
187  CostFunction>::TravelingSalesmanCost() {
188  if (!solved_) {
189  Solve();
190  }
191  return tsp_cost_;
192 }
193 
194 template <typename CostType, typename ArcIndex, typename NodeIndex,
195  typename CostFunction>
196 std::vector<NodeIndex> ChristofidesPathSolver<
197  CostType, ArcIndex, NodeIndex, CostFunction>::TravelingSalesmanPath() {
198  if (!solved_) {
199  Solve();
200  }
201  return tsp_path_;
202 }
203 
204 template <typename CostType, typename ArcIndex, typename NodeIndex,
205  typename CostFunction>
207  CostFunction>::Solve() {
208  const NodeIndex num_nodes = graph_.num_nodes();
209  tsp_path_.clear();
210  tsp_cost_ = 0;
211  if (num_nodes == 1) {
212  tsp_path_ = {0, 0};
213  }
214  if (num_nodes <= 1) {
215  return;
216  }
217  // Compute Minimum Spanning Tree.
218  const std::vector<ArcIndex> mst =
219  BuildPrimMinimumSpanningTree(graph_, [this](ArcIndex arc) {
220  return costs_(graph_.Tail(arc), graph_.Head(arc));
221  });
222  // Detect odd degree nodes.
223  std::vector<NodeIndex> degrees(num_nodes, 0);
224  for (ArcIndex arc : mst) {
225  degrees[graph_.Tail(arc)]++;
226  degrees[graph_.Head(arc)]++;
227  }
228  std::vector<NodeIndex> odd_degree_nodes;
229  for (int i = 0; i < degrees.size(); ++i) {
230  if (degrees[i] % 2 != 0) {
231  odd_degree_nodes.push_back(i);
232  }
233  }
234  // Find minimum-weight perfect matching on odd-degree-node complete graph.
235  // TODO(user): Make this code available as an independent algorithm.
236  const NodeIndex reduced_size = odd_degree_nodes.size();
237  DCHECK_NE(0, reduced_size);
238  CompleteGraph<NodeIndex, ArcIndex> reduced_graph(reduced_size);
239  std::vector<ArcIndex> closure_arcs;
240  switch (matching_) {
241 #if defined(USE_CBC) || defined(USE_SCIP)
242  case MatchingAlgorithm::MINIMUM_WEIGHT_MATCHING: {
244  reduced_graph, [this, &reduced_graph,
245  &odd_degree_nodes](CompleteGraph<>::ArcIndex arc) {
246  return costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
247  odd_degree_nodes[reduced_graph.Head(arc)]);
248  });
249  break;
250  }
251 #endif // defined(USE_CBC) || defined(USE_SCIP)
252  case MatchingAlgorithm::MINIMAL_WEIGHT_MATCHING: {
253  // TODO(user): Cost caching was added and can gain up to 20% but
254  // increases memory usage; see if we can avoid caching.
255  std::vector<ArcIndex> ordered_arcs(reduced_graph.num_arcs());
256  std::vector<CostType> ordered_arc_costs(reduced_graph.num_arcs(), 0);
257  for (const ArcIndex arc : reduced_graph.AllForwardArcs()) {
258  ordered_arcs[arc] = arc;
259  ordered_arc_costs[arc] =
260  costs_(odd_degree_nodes[reduced_graph.Tail(arc)],
261  odd_degree_nodes[reduced_graph.Head(arc)]);
262  }
263  std::sort(ordered_arcs.begin(), ordered_arcs.end(),
264  [&ordered_arc_costs](ArcIndex arc_a, ArcIndex arc_b) {
265  return ordered_arc_costs[arc_a] < ordered_arc_costs[arc_b];
266  });
267  std::vector<bool> touched_nodes(reduced_size, false);
268  for (ArcIndex arc_index = 0; closure_arcs.size() * 2 < reduced_size;
269  ++arc_index) {
270  const ArcIndex arc = ordered_arcs[arc_index];
271  const NodeIndex tail = reduced_graph.Tail(arc);
272  const NodeIndex head = reduced_graph.Head(arc);
273  if (head != tail && !touched_nodes[tail] && !touched_nodes[head]) {
274  touched_nodes[tail] = true;
275  touched_nodes[head] = true;
276  closure_arcs.push_back(arc);
277  }
278  }
279  break;
280  }
281  }
282  // Build Eulerian path on minimum spanning tree + closing edges from matching
283  // and extract a solution to the Traveling Salesman from the path by skipping
284  // duplicate nodes.
286  num_nodes, closure_arcs.size() + mst.size());
287  for (ArcIndex arc : mst) {
288  const NodeIndex tail = graph_.Tail(arc);
289  const NodeIndex head = graph_.Head(arc);
290  egraph.AddArc(tail, head);
291  }
292  for (ArcIndex arc : closure_arcs) {
293  const NodeIndex tail = odd_degree_nodes[reduced_graph.Tail(arc)];
294  const NodeIndex head = odd_degree_nodes[reduced_graph.Head(arc)];
295  egraph.AddArc(tail, head);
296  }
297  std::vector<bool> touched(num_nodes, false);
298  DCHECK(IsEulerianGraph(egraph));
299  for (const NodeIndex node : BuildEulerianTourFromNode(egraph, 0)) {
300  if (touched[node]) continue;
301  touched[node] = true;
302  tsp_cost_ = SafeAdd(tsp_cost_,
303  tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), node));
304  tsp_path_.push_back(node);
305  }
306  tsp_cost_ =
307  SafeAdd(tsp_cost_, tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), 0));
308  tsp_path_.push_back(0);
309  solved_ = true;
310 }
311 } // namespace operations_research
312 
313 #endif // OR_TOOLS_GRAPH_CHRISTOFIDES_H_
std::vector< typename GraphType::ArcIndex > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:107
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
Definition: christofides.h:177
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root)
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:58
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:197
bool IsEulerianGraph(const Graph &graph)
Definition: eulerian_path.h:40
std::vector< typename Graph::ArcIndex > BuildPrimMinimumSpanningTree(const Graph &graph, const ArcValue &arc_value)