26#ifndef OR_TOOLS_GRAPH_CHRISTOFIDES_H_
27#define OR_TOOLS_GRAPH_CHRISTOFIDES_H_
36#include "absl/status/status.h"
37#include "absl/status/statusor.h"
44#include "ortools/linear_solver/linear_solver.pb.h"
49using ::util::CompleteGraph;
51template <
typename CostType,
typename ArcIndex = int64_t,
58#if defined(USE_CBC) || defined(USE_SCIP)
92 if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
104 CompleteGraph<NodeIndex, ArcIndex> graph_;
107 const CostFunction costs_;
113 std::vector<NodeIndex> tsp_path_;
120template <
typename WeightFunctionType,
typename GraphType>
121absl::StatusOr<std::vector<
122 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
124 const WeightFunctionType& weight) {
125 using ArcIndex =
typename GraphType::ArcIndex;
126 using NodeIndex =
typename GraphType::NodeIndex;
128 for (
NodeIndex tail : graph.AllNodes()) {
129 for (
const ArcIndex arc : graph.OutgoingArcs(tail)) {
139 return absl::InvalidArgumentError(
"Perfect matching failed");
141 std::vector<std::pair<NodeIndex, NodeIndex>> match;
142 for (
NodeIndex tail : graph.AllNodes()) {
145 match.emplace_back(tail, head);
151#if defined(USE_CBC) || defined(USE_SCIP)
156template <
typename WeightFunctionType,
typename GraphType>
157absl::StatusOr<std::vector<
158 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
160 const WeightFunctionType& weight) {
161 using ArcIndex =
typename GraphType::ArcIndex;
162 using NodeIndex =
typename GraphType::NodeIndex;
164 model.set_maximize(
false);
169 std::vector<int> variable_indices(graph.num_arcs(), -1);
170 for (
NodeIndex node : graph.AllNodes()) {
172 for (
const ArcIndex arc : graph.OutgoingArcs(node)) {
175 variable_indices[arc] = model.variable_size();
176 MPVariableProto*
const arc_var = model.add_variable();
177 arc_var->set_lower_bound(0);
178 arc_var->set_upper_bound(1);
179 arc_var->set_is_integer(
true);
180 arc_var->set_objective_coefficient(weight(arc));
185 MPConstraintProto*
const one_of_ct = model.add_constraint();
186 one_of_ct->set_lower_bound(1);
187 one_of_ct->set_upper_bound(1);
189 for (
NodeIndex node : graph.AllNodes()) {
190 for (
const ArcIndex arc : graph.OutgoingArcs(node)) {
193 const int arc_var = variable_indices[arc];
194 DCHECK_GE(arc_var, 0);
195 MPConstraintProto* one_of_ct = model.mutable_constraint(node);
196 one_of_ct->add_var_index(arc_var);
197 one_of_ct->add_coefficient(1);
198 one_of_ct = model.mutable_constraint(head);
199 one_of_ct->add_var_index(arc_var);
200 one_of_ct->add_coefficient(1);
205 MPSolver mp_solver(
"MatchingWithSCIP",
207#elif defined(USE_CBC)
208 MPSolver mp_solver(
"MatchingWithCBC",
215 return absl::InvalidArgumentError(
"MIP-based matching failed");
217 MPSolutionResponse response;
219 std::vector<std::pair<NodeIndex, NodeIndex>> matching;
220 for (
ArcIndex arc = 0; arc < variable_indices.size(); ++arc) {
221 const int arc_var = variable_indices[arc];
222 if (arc_var >= 0 && response.variable_value(arc_var) > .9) {
223 DCHECK_GE(response.variable_value(arc_var), 1.0 - 1e-4);
224 matching.emplace_back(graph.Tail(arc), graph.Head(arc));
232 typename CostFunction>
237 costs_(
std::move(costs)),
242 typename CostFunction>
246 bool const ok =
Solve();
253 typename CostFunction>
257 const bool ok =
Solve();
264 typename CostFunction>
267 const NodeIndex num_nodes = graph_.num_nodes();
270 if (num_nodes == 1) {
273 if (num_nodes <= 1) {
277 const std::vector<ArcIndex> mst =
279 return costs_(graph_.Tail(arc), graph_.Head(arc));
282 std::vector<NodeIndex>
degrees(num_nodes, 0);
287 std::vector<NodeIndex> odd_degree_nodes;
288 for (
int i = 0; i <
degrees.size(); ++i) {
290 odd_degree_nodes.push_back(i);
295 const NodeIndex reduced_size = odd_degree_nodes.size();
296 DCHECK_NE(0, reduced_size);
298 std::vector<std::pair<NodeIndex, NodeIndex>> closure_arcs;
302 reduced_graph, [
this, &reduced_graph,
304 return costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
305 odd_degree_nodes[reduced_graph.
Head(arc)]);
310 result->swap(closure_arcs);
313#if defined(USE_CBC) || defined(USE_SCIP)
316 reduced_graph, [
this, &reduced_graph,
318 return costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
319 odd_degree_nodes[reduced_graph.
Head(arc)]);
324 result->swap(closure_arcs);
331 std::vector<ArcIndex> ordered_arcs(reduced_graph.
num_arcs());
332 std::vector<CostType> ordered_arc_costs(reduced_graph.
num_arcs(), 0);
334 ordered_arcs[arc] = arc;
335 ordered_arc_costs[arc] =
336 costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
337 odd_degree_nodes[reduced_graph.
Head(arc)]);
339 std::sort(ordered_arcs.begin(), ordered_arcs.end(),
341 return ordered_arc_costs[arc_a] < ordered_arc_costs[arc_b];
343 std::vector<bool> touched_nodes(reduced_size,
false);
344 for (
ArcIndex arc_index = 0; closure_arcs.size() * 2 < reduced_size;
346 const ArcIndex arc = ordered_arcs[arc_index];
349 if (head != tail && !touched_nodes[tail] && !touched_nodes[head]) {
350 touched_nodes[tail] =
true;
351 touched_nodes[head] =
true;
352 closure_arcs.emplace_back(tail, head);
362 num_nodes, closure_arcs.size() + mst.size());
364 egraph.
AddArc(graph_.Tail(arc), graph_.Head(arc));
366 for (
const auto arc : closure_arcs) {
367 egraph.
AddArc(odd_degree_nodes[arc.first], odd_degree_nodes[arc.second]);
369 std::vector<bool> touched(num_nodes,
false);
372 if (touched[node])
continue;
373 touched[node] =
true;
374 tsp_cost_ = SafeAdd(tsp_cost_,
375 tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), node));
376 tsp_path_.push_back(node);
379 SafeAdd(tsp_cost_, tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), 0));
380 tsp_path_.push_back(0);
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
void SetMatchingAlgorithm(MatchingAlgorithm matching)
std::vector< NodeIndex > TravelingSalesmanPath()
Returns the approximate TSP tour.
@ MINIMUM_WEIGHT_MATCHING_WITH_MIP
@ MINIMAL_WEIGHT_MATCHING
@ MINIMUM_WEIGHT_MATCHING
CostType TravelingSalesmanCost()
Returns the cost of the approximate TSP tour.
ResultStatus Solve()
Solves the problem using the default parameter values.
MPSolverResponseStatus LoadModelFromProto(const MPModelProto &input_model, std::string *error_message, bool clear_names=true)
--— Methods using protocol buffers --—
@ SCIP_MIXED_INTEGER_PROGRAMMING
Recommended default value for MIP problems.
@ CBC_MIXED_INTEGER_PROGRAMMING
void FillSolutionResponseProto(MPSolutionResponse *response) const
Encodes the current solution in a solution response protocol buffer.
int Match(int node) const
void AddEdgeWithCost(int tail, int head, int64_t cost)
@ OPTIMAL
A perfect matching with min-cost has been found.
ABSL_MUST_USE_RESULT Status Solve()
IntegerRange< ArcIndex > AllForwardArcs() const
ArcIndexType num_arcs() const
Returns the number of valid arcs in the graph.
NodeIndexType Tail(ArcIndexType arc) const
NodeIndexType Head(ArcIndexType arc) const
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
In SWIG mode, we don't want anything besides these top-level includes.
bool IsEulerianGraph(const Graph &graph, bool assume_connectivity=true)
Returns true if a graph is Eulerian, aka all its nodes are of even degree.
int64_t CapAdd(int64_t x, int64_t y)
absl::StatusOr< std::vector< std::pair< typename GraphType::NodeIndex, typename GraphType::NodeIndex > > > ComputeMinimumWeightMatchingWithMIP(const GraphType &graph, const WeightFunctionType &weight)
std::vector< NodeIndex > BuildEulerianTourFromNode(const Graph &graph, NodeIndex root, bool assume_connectivity=true)
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)
Computes a minimum weight perfect matching on an undirected graph.
trees with all degrees equal w the current value of degrees