Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_cuts.h
Go to the documentation of this file.
1// Copyright 2010-2025 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#ifndef OR_TOOLS_SAT_ROUTING_CUTS_H_
15#define OR_TOOLS_SAT_ROUTING_CUTS_H_
16
17#include <stdint.h>
18
19#include <functional>
20#include <limits>
21#include <utility>
22#include <vector>
23
24#include "absl/container/flat_hash_map.h"
25#include "absl/types/span.h"
26#include "ortools/sat/cp_model.pb.h"
27#include "ortools/sat/cuts.h"
28#include "ortools/sat/integer.h"
30#include "ortools/sat/model.h"
33
34namespace operations_research {
35namespace sat {
36
37// Helper to compute the minimum flow going out of a subset of nodes, for a
38// given RoutesConstraint.
40 public:
41 MinOutgoingFlowHelper(int num_nodes, const std::vector<int>& tails,
42 const std::vector<int>& heads,
43 const std::vector<Literal>& literals, Model* model);
44
45 // Returns the minimum flow going out of `subset`, based on a conservative
46 // estimate of the maximum number of nodes of a feasible path inside this
47 // subset. `subset` must not be empty and must not contain the depot (node 0).
48 // Paths are approximated by their length and their last node, and can thus
49 // contain cycles. The complexity is O(subset.size() ^ 3).
50 int ComputeMinOutgoingFlow(absl::Span<const int> subset);
51
52 // Same as above, but uses less conservative estimates (paths are approximated
53 // by their set of nodes and their last node -- hence they can't contain
54 // cycles). The complexity is O(2 ^ subset.size()).
55 int ComputeTightMinOutgoingFlow(absl::Span<const int> subset);
56
57 private:
58 // Returns the minimum flow going out of a subset of size `subset_size`,
59 // assuming that the longest feasible path inside this subset has
60 // `longest_path_length` nodes and that there are at most `max_longest_paths`
61 // such paths.
62 int GetMinOutgoingFlow(int subset_size, int longest_path_length,
63 int max_longest_paths);
64
65 const std::vector<int>& tails_;
66 const std::vector<int>& heads_;
67 const std::vector<Literal>& literals_;
68 const BinaryRelationRepository& binary_relation_repository_;
69 const Trail& trail_;
70 const IntegerTrail& integer_trail_;
71
72 // Temporary data used by ComputeMinOutgoingFlow(). Always contain default
73 // values, except while ComputeMinOutgoingFlow() is running.
74 // ComputeMinOutgoingFlow() computes, for each i in [0, |subset|), whether
75 // each node n in the subset could appear at position i in a feasible path.
76 // It computes whether n can appear at position i based on which nodes can
77 // appear at position i-1, and based on the arc literals and some linear
78 // constraints they enforce. To save memory, it only stores data about two
79 // consecutive positions at a time: a "current" position i, and a "next"
80 // position i+1.
81
82 std::vector<bool> in_subset_;
83 std::vector<int> index_in_subset_;
84 // For each node n, the indices (in tails_, heads_) of the m->n and n->m arcs
85 // inside the subset (self arcs excepted).
86 std::vector<std::vector<int>> incoming_arc_indices_;
87 std::vector<std::vector<int>> outgoing_arc_indices_;
88 // For each node n, whether it can appear at the current and next position in
89 // a feasible path.
90 std::vector<bool> reachable_;
91 std::vector<bool> next_reachable_;
92 // For each node n, the lower bound of each variable (appearing in a linear
93 // constraint enforced by some incoming arc literal), if n appears at the
94 // current and next position in a feasible path. Variables not appearing in
95 // these maps have no tighter lower bound that the one from the IntegerTrail
96 // (at decision level 0).
97 std::vector<absl::flat_hash_map<IntegerVariable, IntegerValue>>
98 node_var_lower_bounds_;
99 std::vector<absl::flat_hash_map<IntegerVariable, IntegerValue>>
100 next_node_var_lower_bounds_;
101};
102
103// Given a graph with nodes in [0, num_nodes) and a set of arcs (the order is
104// important), this will:
105// - Start with each nodes in separate "subsets".
106// - Consider the arc in order, and each time one connects two separate
107// subsets, merge the two subsets into a new one.
108// - Stops when there is only 2 subset left.
109// - Output all subsets generated this way (at most 2 * num_nodes). The
110// subsets spans will point in the subset_data vector (which will be of size
111// exactly num_nodes).
112//
113// This is an heuristic to generate interesting cuts for TSP or other graph
114// based constraints. We roughly follow the algorithm described in section 6 of
115// "The Traveling Salesman Problem, A computational Study", David L. Applegate,
116// Robert E. Bixby, Vasek Chvatal, William J. Cook.
117//
118// Note that this is mainly a "symmetric" case algo, but it does still work for
119// the asymmetric case.
120void GenerateInterestingSubsets(int num_nodes,
121 absl::Span<const std::pair<int, int>> arcs,
122 int stop_at_num_components,
123 std::vector<int>* subset_data,
124 std::vector<absl::Span<const int>>* subsets);
125
126// Given a set of rooted tree on n nodes represented by the parent vector,
127// returns the n sets of nodes corresponding to all the possible subtree. Note
128// that the output memory is just n as all subset will point into the same
129// vector.
130//
131// This assumes no cycles, otherwise it will not crash but the result will not
132// be correct.
133//
134// In the TSP context, if the tree is a Gomory-Hu cut tree, this will returns
135// a set of "min-cut" that contains a min-cut for all node pairs.
136//
137// TODO(user): This also allocate O(n) memory internally, we could reuse it from
138// call to call if needed.
140 absl::Span<const int> parent, std::vector<int>* subset_data,
141 std::vector<absl::Span<const int>>* subsets,
142 int node_limit = std::numeric_limits<int>::max());
143
144// In the routing context, we usually always have lp_value in [0, 1] and only
145// looks at arcs with a lp_value that is not too close to zero.
147 int tail;
148 int head;
149 double lp_value;
150
151 bool operator==(const ArcWithLpValue& o) const {
152 return tail == o.tail && head == o.head && lp_value == o.lp_value;
153 }
154};
155
156// Regroups and sum the lp values on duplicate arcs or reversed arcs
157// (tail->head) and (head->tail). As a side effect, we will always
158// have tail <= head.
159void SymmetrizeArcs(std::vector<ArcWithLpValue>* arcs);
160
161// Given a set of arcs on a directed graph with n nodes (in [0, num_nodes)),
162// returns a "parent" vector of size n encoding a rooted Gomory-Hu tree.
163//
164// Note that usually each edge in the tree is attached a max-flow value (its
165// weight), but we don't need it here. It can be added if needed. This tree as
166// the property that for all (s, t) pair of nodes, if you take the minimum
167// weight edge on the path from s to t and split the tree in two, then this is a
168// min-cut for that pair.
169//
170// IMPORTANT: This algorithm currently "symmetrize" the graph, so we will
171// actually have all the min-cuts that minimize sum incoming + sum outgoing lp
172// values. The algo do not work as is on an asymmetric graph. Note however that
173// because of flow conservation, our outgoing lp values should be the same as
174// our incoming one on a circuit/route constraint.
175//
176// We use a simple implementation described in "Very Simple Methods for All
177// Pairs Network Flow Analysis", Dan Gusfield, 1990,
178// https://ranger.uta.edu/~weems/NOTES5311/LAB/LAB2SPR21/gusfield.huGomory.pdf
179std::vector<int> ComputeGomoryHuTree(
180 int num_nodes, absl::Span<const ArcWithLpValue> relevant_arcs);
181
182// Cut generator for the circuit constraint, where in any feasible solution, the
183// arcs that are present (variable at 1) must form a circuit through all the
184// nodes of the graph. Self arc are forbidden in this case.
185//
186// In more generality, this currently enforce the resulting graph to be strongly
187// connected. Note that we already assume basic constraint to be in the lp, so
188// we do not add any cuts for components of size 1.
190 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
191 absl::Span<const Literal> literals, Model* model);
192
193// Almost the same as CreateStronglyConnectedGraphCutGenerator() but for each
194// components, computes the demand needed to serves it, and depending on whether
195// it contains the depot (node zero) or not, compute the minimum number of
196// vehicle that needs to cross the component border.
197CutGenerator CreateCVRPCutGenerator(int num_nodes, absl::Span<const int> tails,
198 absl::Span<const int> heads,
199 absl::Span<const Literal> literals,
200 absl::Span<const int64_t> demands,
201 int64_t capacity, Model* model);
202
203// Try to find a subset where the current LP capacity of the outgoing or
204// incoming arc is not enough to satisfy the demands.
205//
206// We support the special value -1 for tail or head that means that the arc
207// comes from (or is going to) outside the nodes in [0, num_nodes). Such arc
208// must still have a capacity assigned to it.
209//
210// TODO(user): Support general linear expression for capacities.
211// TODO(user): Some model applies the same capacity to both an arc and its
212// reverse. Also support this case.
213CutGenerator CreateFlowCutGenerator(
214 int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
215 const std::vector<AffineExpression>& arc_capacities,
216 std::function<void(const std::vector<bool>& in_subset,
217 IntegerValue* min_incoming_flow,
218 IntegerValue* min_outgoing_flow)>
219 get_flows,
220 Model* model);
221
222} // namespace sat
223} // namespace operations_research
224
225#endif // OR_TOOLS_SAT_ROUTING_CUTS_H_
Definition model.h:341
int ComputeTightMinOutgoingFlow(absl::Span< const int > subset)
int ComputeMinOutgoingFlow(absl::Span< const int > subset)
MinOutgoingFlowHelper(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, Model *model)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model)
std::vector< int > ComputeGomoryHuTree(int num_nodes, absl::Span< const ArcWithLpValue > relevant_arcs)
CutGenerator CreateCVRPCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, absl::Span< const int64_t > demands, int64_t capacity, Model *model)
CutGenerator CreateFlowCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< AffineExpression > &arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, Model *model)
void SymmetrizeArcs(std::vector< ArcWithLpValue > *arcs)
void ExtractAllSubsetsFromForest(absl::Span< const int > parent, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets, int node_limit)
void GenerateInterestingSubsets(int num_nodes, absl::Span< const std::pair< int, int > > arcs, int stop_at_num_components, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets)
In SWIG mode, we don't want anything besides these top-level includes.
bool operator==(const ArcWithLpValue &o) const