26#ifndef ORTOOLS_GRAPH_CHRISTOFIDES_H_
27#define ORTOOLS_GRAPH_CHRISTOFIDES_H_
36#include "absl/status/status.h"
37#include "absl/status/statusor.h"
38#include "absl/strings/str_cat.h"
50using ::util::CompleteGraph;
53template <
typename CostType,
typename ArcIndex = int64_t,
60#if defined(USE_CBC) || defined(USE_SCIP)
93 if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
105 CompleteGraph<NodeIndex, ArcIndex> graph_;
108 const CostFunction costs_;
114 std::vector<NodeIndex> tsp_path_;
121template <
typename CostType,
typename WeightFunctionType,
typename GraphType>
122absl::StatusOr<std::vector<
123 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
125 const WeightFunctionType& weight) {
126 using ArcIndex =
typename GraphType::ArcIndex;
127 using NodeIndex =
typename GraphType::NodeIndex;
128 if constexpr (!std::is_integral_v<CostType>) {
129 DLOG(WARNING) <<
"Weights are being cast to int64_t. This might result "
130 "in loss of precision or overflows.";
133 for (
NodeIndex tail : graph.AllNodes()) {
134 for (
const ArcIndex arc : graph.OutgoingArcs(tail)) {
145 return absl::InvalidArgumentError(
146 absl::StrCat(
"Perfect matching failed: ", status));
148 std::vector<std::pair<NodeIndex, NodeIndex>> match;
149 for (
NodeIndex tail : graph.AllNodes()) {
152 match.emplace_back(tail, head);
158#if defined(USE_CBC) || defined(USE_SCIP)
163template <
typename WeightFunctionType,
typename GraphType>
164absl::StatusOr<std::vector<
165 std::pair<typename GraphType::NodeIndex, typename GraphType::NodeIndex>>>
167 const WeightFunctionType& weight) {
168 using ArcIndex =
typename GraphType::ArcIndex;
169 using NodeIndex =
typename GraphType::NodeIndex;
176 std::vector<int> variable_indices(graph.num_arcs(), -1);
177 for (
NodeIndex node : graph.AllNodes()) {
179 for (
const ArcIndex arc : graph.OutgoingArcs(node)) {
196 for (
NodeIndex node : graph.AllNodes()) {
197 for (
const ArcIndex arc : graph.OutgoingArcs(node)) {
200 const int arc_var = variable_indices[arc];
201 DCHECK_GE(arc_var, 0);
212 MPSolver mp_solver(
"MatchingWithSCIP",
214#elif defined(USE_CBC)
215 MPSolver mp_solver(
"MatchingWithCBC",
222 return absl::InvalidArgumentError(
"MIP-based matching failed");
226 std::vector<std::pair<NodeIndex, NodeIndex>> matching;
227 for (ArcIndex arc = 0; arc < variable_indices.size(); ++arc) {
228 const int arc_var = variable_indices[arc];
231 matching.emplace_back(graph.Tail(arc), graph.Head(arc));
238template <
typename CostType,
typename ArcIndex,
typename NodeIndex,
239 typename CostFunction>
244 costs_(
std::move(costs)),
248template <
typename CostType,
typename ArcIndex,
typename NodeIndex,
249 typename CostFunction>
253 const absl::Status status =
Solve();
254 if (!status.ok())
return status;
259template <
typename CostType,
typename ArcIndex,
typename NodeIndex,
260 typename CostFunction>
264 const absl::Status status =
Solve();
265 if (!status.ok())
return status;
270template <
typename CostType,
typename ArcIndex,
typename NodeIndex,
271 typename CostFunction>
274 const NodeIndex num_nodes = graph_.num_nodes();
277 if (num_nodes == 1) {
280 if (num_nodes <= 1) {
281 return absl::OkStatus();
284 const std::vector<ArcIndex> mst =
286 return costs_(graph_.Tail(arc), graph_.Head(arc));
289 std::vector<NodeIndex>
degrees(num_nodes, 0);
290 for (ArcIndex arc : mst) {
294 std::vector<NodeIndex> odd_degree_nodes;
295 for (
int i = 0; i <
degrees.size(); ++i) {
297 odd_degree_nodes.push_back(i);
301 const NodeIndex reduced_size = odd_degree_nodes.size();
302 DCHECK_NE(0, reduced_size);
304 std::vector<std::pair<NodeIndex, NodeIndex>> closure_arcs;
308 reduced_graph, [
this, &reduced_graph,
310 return costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
311 odd_degree_nodes[reduced_graph.
Head(arc)]);
314 return result.status();
316 result->swap(closure_arcs);
319#if defined(USE_CBC) || defined(USE_SCIP)
322 reduced_graph, [
this, &reduced_graph,
324 return costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
325 odd_degree_nodes[reduced_graph.
Head(arc)]);
328 return result.status();
330 result->swap(closure_arcs);
337 std::vector<ArcIndex> ordered_arcs(reduced_graph.
num_arcs());
338 std::vector<CostType> ordered_arc_costs(reduced_graph.
num_arcs(), 0);
340 ordered_arcs[arc] = arc;
341 ordered_arc_costs[arc] =
342 costs_(odd_degree_nodes[reduced_graph.
Tail(arc)],
343 odd_degree_nodes[reduced_graph.
Head(arc)]);
345 absl::c_sort(ordered_arcs,
346 [&ordered_arc_costs](ArcIndex arc_a, ArcIndex arc_b) {
347 return ordered_arc_costs[arc_a] < ordered_arc_costs[arc_b];
349 std::vector<bool> touched_nodes(reduced_size,
false);
350 for (ArcIndex arc_index = 0; closure_arcs.size() * 2 < reduced_size;
352 const ArcIndex arc = ordered_arcs[arc_index];
355 if (head != tail && !touched_nodes[tail] && !touched_nodes[head]) {
356 touched_nodes[tail] =
true;
357 touched_nodes[head] =
true;
358 closure_arcs.emplace_back(tail, head);
368 num_nodes, closure_arcs.size() + mst.size());
369 for (ArcIndex arc : mst) {
370 egraph.
AddArc(graph_.Tail(arc), graph_.Head(arc));
372 for (
const auto arc : closure_arcs) {
373 egraph.
AddArc(odd_degree_nodes[arc.first], odd_degree_nodes[arc.second]);
375 std::vector<bool> touched(num_nodes,
false);
378 if (touched[node])
continue;
379 touched[node] =
true;
380 tsp_cost_ = SafeAdd(tsp_cost_,
381 tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), node));
382 tsp_path_.push_back(node);
385 SafeAdd(tsp_cost_, tsp_path_.empty() ? 0 : costs_(tsp_path_.back(), 0));
386 tsp_path_.push_back(0);
388 return absl::OkStatus();
ChristofidesPathSolver(NodeIndex num_nodes, CostFunction costs)
void SetMatchingAlgorithm(MatchingAlgorithm matching)
absl::StatusOr< CostType > TravelingSalesmanCost()
absl::StatusOr< const std::vector< NodeIndex > & > TravelingSalesmanPath()
@ MINIMUM_WEIGHT_MATCHING_WITH_MIP
@ MINIMAL_WEIGHT_MATCHING
@ MINIMUM_WEIGHT_MATCHING
void set_lower_bound(double value)
void add_coefficient(double value)
void set_upper_bound(double value)
void add_var_index(::int32_t value)
int variable_size() const
void set_maximize(bool value)
::operations_research::MPConstraintProto *PROTOBUF_NONNULL add_constraint()
::operations_research::MPConstraintProto *PROTOBUF_NONNULL mutable_constraint(int index)
::operations_research::MPVariableProto *PROTOBUF_NONNULL add_variable()
double variable_value(int index) const
ResultStatus Solve()
Solves the problem using the default parameter values.
MPSolverResponseStatus LoadModelFromProto(const MPModelProto &input_model, std::string *error_message, bool clear_names=true)
@ SCIP_MIXED_INTEGER_PROGRAMMING
@ CBC_MIXED_INTEGER_PROGRAMMING
void FillSolutionResponseProto(MPSolutionResponse *response) const
Encodes the current solution in a solution response protocol buffer.
void set_upper_bound(double value)
void set_is_integer(bool value)
void set_objective_coefficient(double value)
void set_lower_bound(double value)
int Match(int node) const
void AddEdgeWithCost(int tail, int head, int64_t cost)
ABSL_MUST_USE_RESULT Status Solve()
IntegerRange< ArcIndex > AllForwardArcs() const
ArcIndexType num_arcs() const
NodeIndexType Tail(ArcIndexType arc) const
NodeIndexType Head(ArcIndexType arc) const
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
bool IsEulerianGraph(const Graph &graph, bool assume_connectivity=true)
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)
BlossomGraph::NodeIndex NodeIndex
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)
trees with all degrees equal w the current value of degrees