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
45using ::util::CompleteGraph;
46
47template <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.
114template <typename WeightFunctionType, typename GraphType>
115absl::StatusOr<std::vector<
116 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
117ComputeMinimumWeightMatching(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.
150template <typename WeightFunctionType, typename GraphType>
151absl::StatusOr<std::vector<
152 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
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
225template <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
235template <typename CostType, typename ArcIndex, typename NodeIndex,
236 typename CostFunction>
237CostType 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
246template <typename CostType, typename ArcIndex, typename NodeIndex,
247 typename CostFunction>
248std::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
257template <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_
std::vector< NodeIndex > TravelingSalesmanPath()
Definition: christofides.h:249
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
Definition: christofides.h:228
void SetMatchingAlgorithm(MatchingAlgorithm matching)
Definition: christofides.h:69
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition: graph.h:1507
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:153
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root)
std::vector< typename Graph::ArcIndex > BuildPrimMinimumSpanningTree(const Graph &graph, const ArcValue &arc_value)
bool IsEulerianGraph(const Graph &graph)
Definition: eulerian_path.h:40
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatching(const GraphType &graph, const WeightFunctionType &weight)
Definition: christofides.h:117