Google OR-Tools v9.14
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 <algorithm>
20#include <functional>
21#include <limits>
22#include <memory>
23#include <ostream>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/container/flat_hash_map.h"
29#include "absl/log/check.h"
30#include "absl/strings/str_cat.h"
31#include "absl/types/span.h"
34#include "ortools/sat/cuts.h"
35#include "ortools/sat/integer.h"
38#include "ortools/sat/model.h"
42#include "ortools/sat/util.h"
43
44namespace operations_research {
45namespace sat {
46
47// Helper to recover the mapping between nodes and "cumul" expressions in simple
48// cases of route constraints (at most one expression per node and "dimension"
49// -- such as time or load, and at most one relation per arc and dimension).
50//
51// This returns an empty vector and num_dimensions == 0 if nothing is detected.
52// Otherwise it returns one expression per node and dimensions.
54 const AffineExpression& GetNodeExpression(int node, int dimension) const {
55 return flat_node_dim_expressions[node * num_dimensions + dimension];
56 }
57
59 std::vector<AffineExpression> flat_node_dim_expressions;
60};
62 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
63 absl::Span<const Literal> literals,
64 const BinaryRelationRepository& binary_relation_repository);
65
66// A coeff * var + offset affine expression, where `var` is always a positive
67// reference (contrary to AffineExpression, where the coefficient is always
68// positive).
70 IntegerVariable var;
71 IntegerValue coeff;
72 IntegerValue offset;
73
75
76 NodeExpression(IntegerVariable var, IntegerValue coeff, IntegerValue offset)
77 : var(var), coeff(coeff), offset(offset) {}
78
79 explicit NodeExpression(const AffineExpression& expr) {
80 if (expr.var == kNoIntegerVariable || VariableIsPositive(expr.var)) {
81 var = expr.var;
82 coeff = expr.coeff;
83 } else {
84 var = PositiveVariable(expr.var);
85 coeff = -expr.coeff;
86 }
87 offset = expr.constant;
88 }
89
90 bool IsEmpty() const { return var == kNoIntegerVariable; }
91
92 IntegerValue ValueAt(IntegerValue x) const { return coeff * x + offset; }
93
95 return NodeExpression(var, -coeff, -offset);
96 }
97};
98
99// Returns some bounds on y_expr - x_expr, based on the given relation and the
100// given variable bounds. r.a (resp. r.b) must have the same variable as x_expr
101// (resp. y_expr), which must not be kNoIntegerVariable. Moreover, the
102// coefficients of these variables in x_expr, y_expr and r must not be zero.
103//
104// The returned bounds are generally not the best possible ones (computing them
105// is equivalent to solving a MIP -- min(y_expr - x_expr) subject to r, x.var in
106// x_var_bounds and y.var in y_var_bounds).
107std::pair<IntegerValue, IntegerValue> GetDifferenceBounds(
108 const NodeExpression& x_expr, const NodeExpression& y_expr,
109 const sat::Relation& r,
110 const std::pair<IntegerValue, IntegerValue>& x_var_bounds,
111 const std::pair<IntegerValue, IntegerValue>& y_var_bounds);
112
113// Helper to store the result of DetectDimensionsAndCumulExpressions() and also
114// recover and store bounds on (node_expr[head] - node_expr[tail]) for each arc
115// (tail -> head) assuming the arc is taken.
117 public:
118 // Visible for testing.
120
121 // Creates a RouteRelationsHelper for the given RoutesConstraint and
122 // associated binary relations. The vector `flat_node_dim_expressions` should
123 // be the result of DetectDimensionsAndCumulExpressions(). If it is empty,
124 // this returns nullptr.
125 //
126 // Otherwise this stores it and also computes the tightest bounds we can on
127 // (node_expr[head] - node_expr[tail]) for each arc, assuming the arc is taken
128 // (its literal is true).
129 static std::unique_ptr<RouteRelationsHelper> Create(
130 int num_nodes, const std::vector<int>& tails,
131 const std::vector<int>& heads, const std::vector<Literal>& literals,
132 absl::Span<const AffineExpression> flat_node_dim_expressions,
133 const BinaryRelationRepository& binary_relation_repository, Model* model);
134
135 // Returns the number of "dimensions", such as time or vehicle load.
136 int num_dimensions() const { return num_dimensions_; }
137
138 int num_nodes() const {
139 return flat_node_dim_expressions_.size() / num_dimensions_;
140 }
141
142 int num_arcs() const {
143 return flat_arc_dim_relations_.size() / num_dimensions_;
144 }
145
146 // Returns the expression associated with the given node and dimension.
147 const AffineExpression& GetNodeExpression(int node, int dimension) const {
148 return flat_node_dim_expressions_[node * num_dimensions_ + dimension];
149 }
150
151 // Each arc stores the bound on expr[head] - expr[tail] \in [lhs, rhs]. Note
152 // that we interpret kMin/kMax integer values as not set. Such bounds will
153 // still be valid though because we have a precondition on the input model
154 // variables to be within [kMin/2, kMax/2].
156 IntegerValue lhs = kMinIntegerValue;
157 IntegerValue rhs = kMaxIntegerValue;
158
159 bool operator==(const HeadMinusTailBounds& r) const {
160 return lhs == r.lhs && rhs == r.rhs;
161 }
162 };
163 const HeadMinusTailBounds& GetArcRelation(int arc, int dimension) const {
164 DCHECK_GE(dimension, 0);
165 DCHECK_LT(dimension, num_dimensions_);
166 DCHECK_GE(arc, 0);
167 return flat_arc_dim_relations_[arc * num_dimensions_ + dimension];
168 }
169
171 return !flat_shortest_path_lbs_.empty();
172 }
173
174 // If any of the bound is at the maximum, then there is no path between tail
175 // and head.
176 bool PathExists(int tail, int head) const {
177 if (flat_shortest_path_lbs_.empty()) return true;
178 const int num_nodes_squared = num_nodes_ * num_nodes_;
179 for (int dim = 0; dim < num_dimensions_; ++dim) {
180 if (flat_shortest_path_lbs_[dim * num_nodes_squared + tail * num_nodes_ +
181 head] ==
182 std::numeric_limits<IntegerValue>::max()) {
183 return false;
184 }
185 }
186 return true;
187 }
188
190 const IntegerTrail& integer_trail, int tail, int head,
191 const absl::flat_hash_map<IntegerVariable, IntegerValue>& input,
192 absl::flat_hash_map<IntegerVariable, IntegerValue>* output) const {
193 if (flat_shortest_path_lbs_.empty()) return true;
194 const int num_nodes_squared = num_nodes_ * num_nodes_;
195 for (int dim = 0; dim < num_dimensions_; ++dim) {
196 const AffineExpression& tail_expr = GetNodeExpression(tail, dim);
197 const AffineExpression& head_expr = GetNodeExpression(head, dim);
198 const IntegerValue head_minus_tail_lb =
199 flat_shortest_path_lbs_[dim * num_nodes_squared + tail * num_nodes_ +
200 head];
201 if (head_expr.var == kNoIntegerVariable) continue;
202
203 IntegerValue tail_lb = integer_trail.LevelZeroLowerBound(tail_expr);
204 if (tail_expr.var != kNoIntegerVariable) {
205 auto it = input.find(tail_expr.var);
206 if (it != input.end()) {
207 tail_lb = std::max(tail_lb, tail_expr.ValueAt(it->second));
208 }
209 }
210
211 // head_expr >= tail_lb + head_minus_tail_lb;
212 const IntegerLiteral i_lit =
213 head_expr.GreaterOrEqual(CapAddI(tail_lb, head_minus_tail_lb));
214 if (i_lit.bound > integer_trail.LevelZeroUpperBound(i_lit.var)) {
215 return false; // not possible.
216 }
217 auto [it, inserted] = output->insert({i_lit.var, i_lit.bound});
218 if (!inserted) {
219 it->second = std::max(it->second, i_lit.bound);
220 }
221 }
222 return true;
223 }
224
225 // Returns the level zero lower bound of the offset between the (optionally
226 // negated) expressions associated with the head and tail of the given arc,
227 // and the given dimension.
228 IntegerValue GetArcOffsetLowerBound(int arc, int dimension,
229 bool negate_expressions) const;
230
231 void RemoveArcs(absl::Span<const int> sorted_arc_indices);
232
233 private:
235 std::vector<AffineExpression> flat_node_dim_expressions,
236 std::vector<HeadMinusTailBounds> flat_arc_dim_relations,
237 std::vector<IntegerValue> flat_shortest_path_lbs);
238
239 void LogStats() const;
240
241 const int num_nodes_ = 0;
242 const int num_dimensions_ = 0;
243
244 // The expression associated with node n and dimension d is at index n *
245 // num_dimensions_ + d.
246 std::vector<AffineExpression> flat_node_dim_expressions_;
247
248 // The relation associated with arc a and dimension d is at index a *
249 // num_dimensions_ + d.
250 std::vector<HeadMinusTailBounds> flat_arc_dim_relations_;
251
252 // flat_shortest_path_lbs_[dim * num_nodes^2 + i * num_nodes + j] is a lower
253 // bounds on node_expression[dim][j] - node_expression[dim][i] whatever the
254 // path used to join node i to node j (not using the root 0). It only make
255 // sense for i and j != 0.
256 std::vector<IntegerValue> flat_shortest_path_lbs_;
257};
258
259// Computes and fills the node expressions of all the routes constraints in
260// `output_model` that don't have them, if possible. The node expressions are
261// inferred from the binary relations in `input_model`. Both models must have
262// the same variables (they can reference the same underlying object).
263// Returns the number of constraints that were filled, and the total number of
264// dimensions added to them.
266 const CpModelProto& input_model, CpModelProto& output_model);
267
269 public:
271 explicit SpecialBinPackingHelper(double dp_effort) : dp_effort_(dp_effort) {}
272
273 // See below.
275 MUST_BE_ITEM = 0, // capacity will be set at zero.
277 MUST_BE_BIN = 2, // demand will be set at zero.
278 };
279
280 // This is used to represent a "special bin packing" problem where we have
281 // objects that can either be items with a given demand or bins with a given
282 // capacity. The problem is to choose the minimum number of objects that will
283 // be bins, such that the other objects (items) can be packed inside.
284 struct ItemOrBin {
285 // The initial routing node that correspond to this object.
286 int node = 0;
287
288 // Only one option will apply, this can either be an item with given demand
289 // or a bin with given capacity.
290 //
291 // Important: We support negative demands and negative capacity. We just
292 // need that the sum of demand <= capacity for the item in that bin.
293 IntegerValue demand = 0;
294 IntegerValue capacity = 0;
295
296 // We described the problem where each object can be an item or a bin, but
297 // in practice we might have restriction on what object can be which, and we
298 // use this field to indicate that.
299 //
300 // The numerical order is important as we use that in the greedy algorithm.
301 // See ComputeMinNumberOfBins() code.
303 };
304
305 // Given a "special bin packing" problem as decribed above, return a lower
306 // bound on the number of bins that needs to be taken.
307 //
308 // This simply sorts the object according to a greedy criteria and minimize
309 // the number of bins such that the "demands <= capacities" constraint is
310 // satisfied.
311 //
312 // If the problem is infeasible, this will return object.size() + 1, which is
313 // a trivially infeasible bound.
314 //
315 // TODO(user): Use fancier DP to derive tighter bound. Also, when there are
316 // many dimensions, the choice of which item go to which bin is correlated,
317 // can we exploit this?
319 absl::Span<ItemOrBin> objects,
320 std::vector<int>& objects_that_cannot_be_bin_and_reach_minimum,
321 std::string& info);
322
323 // Visible for testing.
324 //
325 // Returns true if we can greedily pack items with indices [num_bins, size)
326 // into the first num_bins bins (with indices [0, num_bins)). This function
327 // completely ignores the ItemOrBin types, and just uses the first num_bins
328 // objects as bins and the rest as items. It is up to the caller to make sure
329 // this is okay.
330 //
331 // This is just used to quickly assess if a more precise lower bound on the
332 // number of bins could gain something. It works in O(num_bins * num_items).
333 bool GreedyPackingWorks(int num_bins, absl::Span<const ItemOrBin> objects);
334
335 // Visible for testing.
336 //
337 // If we look at all the possible sum of item demands, it is possible that
338 // some value can never be reached. We use dynamic programming to compute the
339 // set of reachable values and tighten the capacities accordingly.
340 //
341 // Returns true iff we tightened something.
342 bool UseDpToTightenCapacities(absl::Span<ItemOrBin> objects);
343
344 private:
345 // Note that this will sort the objects so that the "best" bins are first.
346 // See implementation.
347 int ComputeMinNumberOfBinsInternal(
348 absl::Span<ItemOrBin> objects,
349 std::vector<int>& objects_that_cannot_be_bin_and_reach_minimum);
350
351 const double dp_effort_ = 1e8;
352 std::vector<IntegerValue> tmp_capacities_;
353 std::vector<int64_t> tmp_demands_;
354 MaxBoundedSubsetSumExact max_bounded_subset_sum_exact_;
355};
356
357inline std::ostream& operator<<(std::ostream& os,
359 os << absl::StrCat("d=", o.demand, " c=", o.capacity, " t=", o.type);
360 return os;
361}
362
363// Keep the best min outgoing/incoming flow out of a subset.
365 public:
366 BestBoundHelper() = default;
367 explicit BestBoundHelper(std::string base_name) : base_name_(base_name) {}
368
369 void Update(int new_bound, std::string name,
370 absl::Span<const int> cannot_be_first = {},
371 absl::Span<const int> cannot_be_last = {}) {
372 if (new_bound < bound_) return;
373 if (new_bound > bound_) {
374 bound_ = new_bound;
375 name_ = name;
376 cannot_be_last_.clear();
377 cannot_be_first_.clear();
378 }
379 cannot_be_first_.insert(cannot_be_first_.begin(), cannot_be_first.begin(),
380 cannot_be_first.end());
381 cannot_be_last_.insert(cannot_be_last_.begin(), cannot_be_last.begin(),
382 cannot_be_last.end());
383 gtl::STLSortAndRemoveDuplicates(&cannot_be_first_);
384 gtl::STLSortAndRemoveDuplicates(&cannot_be_last_);
385 }
386
387 std::string name() const { return absl::StrCat(base_name_, name_); }
388 int bound() const { return bound_; }
389
390 // To serve the current subset with bound() num vehicles, one cannot exit
391 // the subset with nodes in CannotBeLast() and one cannot enter the subset
392 // with node in CannotBeFirst(). Moreover, one cannot enter from or leave to
393 // nodes in OutsideNodeThatCannotBeConnected().
394 absl::Span<const int> CannotBeLast() const { return cannot_be_last_; }
395 absl::Span<const int> CannotBeFirst() const { return cannot_be_first_; }
396
397 private:
398 int bound_ = 0;
399 std::string base_name_;
400 std::string name_;
401 std::vector<int> tmp_;
402 std::vector<int> cannot_be_last_;
403 std::vector<int> cannot_be_first_;
404};
405
406// Helper to compute the minimum flow going out of a subset of nodes, for a
407// given RoutesConstraint.
409 public:
410 // Warning: The underlying tails/heads/literals might be resized from one
411 // call to the next of one of the functions here, so be careful.
412 MinOutgoingFlowHelper(int num_nodes, const std::vector<int>& tails,
413 const std::vector<int>& heads,
414 const std::vector<Literal>& literals, Model* model);
415
417
418 // Returns the minimum flow going out of `subset`, based on a generalization
419 // of the CVRP "rounded capacity inequalities", by using the given helper, if
420 // possible. The complexity is O((subset.size() + num_arcs()) *
421 // num_dimensions()).
422 int ComputeDimensionBasedMinOutgoingFlow(absl::Span<const int> subset,
423 const RouteRelationsHelper& helper,
424 BestBoundHelper* best_bound);
425
426 // Returns the minimum flow going out of `subset`, based on a conservative
427 // estimate of the maximum number of nodes of a feasible path inside this
428 // subset. `subset` must not be empty and must not contain the depot (node 0).
429 // Paths are approximated by their length and their last node, and can thus
430 // contain cycles. The complexity is O(subset.size() ^ 3).
431 int ComputeMinOutgoingFlow(absl::Span<const int> subset);
432
433 // Same as above, but uses less conservative estimates (paths are approximated
434 // by their set of nodes and their last node -- hence they can't contain
435 // cycles). The complexity is O(2 ^ subset.size()).
436 int ComputeTightMinOutgoingFlow(absl::Span<const int> subset);
437
438 // Returns false if the given subset CANNOT be served by k routes.
439 // Returns true if we have a route or we don't know for sure (if we abort).
440 // The parameter k must be positive.
441 //
442 // Even more costly algo in O(n!/k!*k^(n-k)) that answers the question exactly
443 // given the available enforced linear1 and linear2 constraints. However it
444 // can stop as soon as one solution is found.
445 //
446 // If special_node is non-negative, we will only look for routes that
447 // - Start at this special_node if use_forward_direction = true
448 // - End at this special_node if use_forward_direction = false
449 //
450 // If the RouteRelationsHelper is non null, then we will use "shortest path"
451 // bounds instead of recomputing them from the binary relation of the model.
452 //
453 // TODO(user): the complexity also depends on the longest route and improves
454 // if routes fail quickly. Give a better estimate?
456 int k, absl::Span<const int> subset,
457 RouteRelationsHelper* helper = nullptr,
458 LinearConstraintManager* manager = nullptr, int special_node = -1,
459 bool use_forward_direction = true);
460
461 // Advanced. If non-empty, and one of the functions above proved that a subset
462 // needs at least k vehicles to serve it, then these vector list the nodes
463 // that cannot be first (resp. last) in one of the solution with k routes. If
464 // a node is listed here, it means we will need at least k + 1 routes to serve
465 // the subset and enter (resp. leave) from that node.
466 //
467 // This can be used to either reduce the size of the subset and still need
468 // k vehicles, or generate stronger cuts.
469 absl::Span<const int> CannotBeLast() const { return cannot_be_last_; };
470 absl::Span<const int> CannotBeFirst() const { return cannot_be_first_; };
471
472 // Just for stats reporting.
473 void ReportDpSkip() { num_full_dp_skips_++; }
474
475 private:
476 void PrecomputeDataForSubset(absl::Span<const int> subset);
477
478 // Given a subset S to serve in a route constraint, returns a special bin
479 // packing problem (defined above) where the minimum number of bins will
480 // correspond to the minimum number of vehicles needed to serve this subset.
481 //
482 // One way to derive such reduction is as follow.
483 //
484 // If we look at a path going through the subset, it will touch in order the
485 // nodes P = {n_0, ..., n_e}. It will enter S at a "start" node n_0 and leave
486 // at a "end" node n_e.
487 //
488 // We assume (see the RouteRelationsHelper) that each node n has an
489 // associated variable X_n, and that each arc t->h has an associated relation
490 // lhs(t,h) <= X_h - X_t. Summing all these inequalities along the path above
491 // we get:
492 // Sum_{i \in [1..e]} lhs(n_i, n_(i+1)) <= X_(n_e) - X_(n_0)
493 // introducing:
494 // - d(n) = min_(i \in S) lhs(i, n) [minimum incoming weight in subset S]
495 // - UB = max_(i \in S) upper_bound(X_i)
496 // We get:
497 // Sum_{n \in P \ n_0} d(n) <= UB - lower_bound(n_0)
498 //
499 // Here we can see that the "starting node" n0 is on the "capacity" side and
500 // will serve the role of a bin with capacity (UB - lower_bound(n_0)), whereas
501 // the other nodes n will be seen as "item" with demands d(i).
502 //
503 // Given that the set of paths going through S must be disjoint and serve all
504 // the nodes, we get exactly the special bin packing problem described above
505 // where the starting nodes are the bins and the other inner-nodes are the
506 // items.
507 //
508 // Note that if a node has no incoming arc from within S, it must be a start
509 // (i.e. a bin). And if a node has no incoming arcs from outside S, it cannot
510 // be a start an must be an inner node (i.e. an item). We can exploit this to
511 // derive better bounds.
512 //
513 // We just explained the reduction using incoming arcs and starts of route,
514 // but we can do the same with outgoing arcs and ends of route. We can also
515 // change the dimension (the X_i) and variable direction used in the
516 // RouteRelationsHelper to exploit relations X_h - X_t <= rhs(t,h) instead.
517 //
518 // We provide a reduction for the cross product of:
519 // - Each possible dimension in the RouteRelationsHelper.
520 // - lhs or rhs (when negate_expressions = true) in X - Y \in [lhs, rhs].
521 // - (start and incoming arcs) or (ends and outgoing arcs).
522 //
523 // Warning: the returned Span<> is only valid until the next call to this
524 // function.
525 //
526 // TODO(user): Given the info for a subset, we can derive bounds for any
527 // smaller set included in it. We just have to ignore the MUST_BE_ITEM
528 // type as this might no longer be true. That might be interesting.
529 absl::Span<SpecialBinPackingHelper::ItemOrBin>
530 RelaxIntoSpecialBinPackingProblem(absl::Span<const int> subset, int dimension,
531 bool negate_expressions, bool use_incoming,
532 const RouteRelationsHelper& helper);
533
534 // Returns the minimum flow going out of a subset of size `subset_size`,
535 // assuming that the longest feasible path inside this subset has
536 // `longest_path_length` nodes and that there are at most `max_longest_paths`
537 // such paths.
538 int GetMinOutgoingFlow(int subset_size, int longest_path_length,
539 int max_longest_paths);
540
541 const std::vector<int>& tails_;
542 const std::vector<int>& heads_;
543 const std::vector<Literal>& literals_;
544 const BinaryRelationRepository& binary_relation_repository_;
545 const Trail& trail_;
546 const IntegerTrail& integer_trail_;
547 const IntegerEncoder& integer_encoder_;
548 SharedStatistics* shared_stats_;
549
550 // Temporary data used by ComputeMinOutgoingFlow(). Always contain default
551 // values, except while ComputeMinOutgoingFlow() is running.
552 // ComputeMinOutgoingFlow() computes, for each i in [0, |subset|), whether
553 // each node n in the subset could appear at position i in a feasible path.
554 // It computes whether n can appear at position i based on which nodes can
555 // appear at position i-1, and based on the arc literals and some linear
556 // constraints they enforce. To save memory, it only stores data about two
557 // consecutive positions at a time: a "current" position i, and a "next"
558 // position i+1.
559
560 std::vector<int> tmp_subset_;
561 std::vector<bool> in_subset_;
562 std::vector<int> index_in_subset_;
563
564 // For each node n, the indices (in tails_, heads_) of the m->n and n->m arcs
565 // inside the subset (self arcs excepted).
566 std::vector<std::vector<int>> incoming_arc_indices_;
567 std::vector<std::vector<int>> outgoing_arc_indices_;
568
569 // This can only be true for node in the current subset. If a node 'n' has no
570 // incoming arcs from outside the subset, the part of a route serving node 'n'
571 // in a subset cannot start at that node. And if it has no outoing arc leaving
572 // the subset, it cannot end at that node. This can be used to derive tighter
573 // bounds.
574 std::vector<bool> has_incoming_arcs_from_outside_;
575 std::vector<bool> has_outgoing_arcs_to_outside_;
576
577 // If a subset has an unique arc arriving or leaving at a given node, we can
578 // derive tighter bounds.
579 class UniqueArc {
580 public:
581 bool IsUnique() const { return num_seen_ == 1; }
582 int Get() const { return arc_; }
583
584 void Add(int arc) {
585 ++num_seen_;
586 arc_ = arc;
587 }
588
589 private:
590 int num_seen_ = 0;
591 int arc_ = 0;
592 };
593 std::vector<UniqueArc> incoming_arcs_from_outside_;
594 std::vector<UniqueArc> outgoing_arcs_to_outside_;
595
596 // For each node n, whether it can appear at the current and next position in
597 // a feasible path.
598 std::vector<bool> reachable_;
599 std::vector<bool> next_reachable_;
600 // For each node n, the lower bound of each variable (appearing in a linear
601 // constraint enforced by some incoming arc literal), if n appears at the
602 // current and next position in a feasible path. Variables not appearing in
603 // these maps have no tighter lower bound that the one from the IntegerTrail
604 // (at decision level 0).
605 std::vector<absl::flat_hash_map<IntegerVariable, IntegerValue>>
606 node_var_lower_bounds_;
607 std::vector<absl::flat_hash_map<IntegerVariable, IntegerValue>>
608 next_node_var_lower_bounds_;
609
610 SpecialBinPackingHelper bin_packing_helper_;
611 std::vector<SpecialBinPackingHelper::ItemOrBin> tmp_bin_packing_problem_;
612
613 std::vector<int> objects_that_cannot_be_bin_and_reach_minimum_;
614
615 std::vector<int> cannot_be_last_;
616 std::vector<int> cannot_be_first_;
617
618 // Statistics.
619 int64_t num_full_dp_skips_ = 0;
620 int64_t num_full_dp_calls_ = 0;
621 int64_t num_full_dp_early_abort_ = 0;
622 int64_t num_full_dp_work_abort_ = 0;
623 int64_t num_full_dp_rc_skip_ = 0;
624 int64_t num_full_dp_unique_arc_ = 0;
625 absl::flat_hash_map<std::string, int> num_by_type_;
626};
627
628// Given a graph with nodes in [0, num_nodes) and a set of arcs (the order is
629// important), this will:
630// - Start with each nodes in separate "subsets".
631// - Consider the arc in order, and each time one connects two separate
632// subsets, merge the two subsets into a new one.
633// - Stops when there is only 'stop_at_num_components' subset left.
634// - Output all subsets generated this way (at most 2 * num_nodes). The
635// subsets spans will point in the subset_data vector (which will be of size
636// exactly num_nodes).
637//
638// This is an heuristic to generate interesting cuts for TSP or other graph
639// based constraints. We roughly follow the algorithm described in section 6 of
640// "The Traveling Salesman Problem, A computational Study", David L. Applegate,
641// Robert E. Bixby, Vasek Chvatal, William J. Cook.
642//
643// Note that this is mainly a "symmetric" case algo, but it does still work for
644// the asymmetric case.
645//
646// TODO(user): Returns the tree instead and let caller call
647// ExtractAllSubsetsFromForest().
648void GenerateInterestingSubsets(int num_nodes,
649 absl::Span<const std::pair<int, int>> arcs,
650 int stop_at_num_components,
651 std::vector<int>* subset_data,
652 std::vector<absl::Span<const int>>* subsets);
653
654// Given a set of rooted tree on n nodes represented by the parent vector,
655// returns the n sets of nodes corresponding to all the possible subtree. Note
656// that the output memory is just n as all subset will point into the same
657// vector.
658//
659// This assumes no cycles, otherwise it will not crash but the result will not
660// be correct.
661//
662// In the TSP context, if the tree is a Gomory-Hu cut tree, this will returns
663// a set of "min-cut" that contains a min-cut for all node pairs.
664//
665// TODO(user): This also allocate O(n) memory internally, we could reuse it from
666// call to call if needed.
668 absl::Span<const int> parent, std::vector<int>* subset_data,
669 std::vector<absl::Span<const int>>* subsets,
670 int node_limit = std::numeric_limits<int>::max());
671
672// In the routing context, we usually always have lp_value in [0, 1] and only
673// looks at arcs with a lp_value that is not too close to zero.
675 int tail;
676 int head;
677 double lp_value;
678
679 bool operator==(const ArcWithLpValue& o) const {
680 return tail == o.tail && head == o.head && lp_value == o.lp_value;
681 }
682};
683
684// Regroups and sum the lp values on duplicate arcs or reversed arcs
685// (tail->head) and (head->tail). As a side effect, we will always
686// have tail <= head.
687void SymmetrizeArcs(std::vector<ArcWithLpValue>* arcs);
688
689// Given a set of arcs on a directed graph with n nodes (in [0, num_nodes)),
690// returns a "parent" vector of size n encoding a rooted Gomory-Hu tree.
691//
692// Note that usually each edge in the tree is attached a max-flow value (its
693// weight), but we don't need it here. It can be added if needed. This tree as
694// the property that for all (s, t) pair of nodes, if you take the minimum
695// weight edge on the path from s to t and split the tree in two, then this is a
696// min-cut for that pair.
697//
698// IMPORTANT: This algorithm currently "symmetrize" the graph, so we will
699// actually have all the min-cuts that minimize sum incoming + sum outgoing lp
700// values. The algo do not work as is on an asymmetric graph. Note however that
701// because of flow conservation, our outgoing lp values should be the same as
702// our incoming one on a circuit/route constraint.
703//
704// We use a simple implementation described in "Very Simple Methods for All
705// Pairs Network Flow Analysis", Dan Gusfield, 1990,
706// https://ranger.uta.edu/~weems/NOTES5311/LAB/LAB2SPR21/gusfield.huGomory.pdf
707std::vector<int> ComputeGomoryHuTree(
708 int num_nodes, absl::Span<const ArcWithLpValue> relevant_arcs);
709
710// Cut generator for the circuit constraint, where in any feasible solution, the
711// arcs that are present (variable at 1) must form a circuit through all the
712// nodes of the graph. Self arc are forbidden in this case.
713//
714// In more generality, this currently enforce the resulting graph to be strongly
715// connected. Note that we already assume basic constraint to be in the lp, so
716// we do not add any cuts for components of size 1.
718 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
719 absl::Span<const Literal> literals, Model* model);
720
721// Almost the same as CreateStronglyConnectedGraphCutGenerator() but for each
722// components, computes the demand needed to serves it, and depending on whether
723// it contains the depot (node zero) or not, compute the minimum number of
724// vehicle that needs to cross the component border.
725// `flat_node_dim_expressions` must have num_dimensions (possibly 0) times
726// num_nodes elements, with the expression associated with node n and dimension
727// d at index n * num_dimensions + d.
728CutGenerator CreateCVRPCutGenerator(
729 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
730 absl::Span<const Literal> literals,
731 absl::Span<const AffineExpression> flat_node_dim_expressions, Model* model);
732
733// Try to find a subset where the current LP capacity of the outgoing or
734// incoming arc is not enough to satisfy the demands.
735//
736// We support the special value -1 for tail or head that means that the arc
737// comes from (or is going to) outside the nodes in [0, num_nodes). Such arc
738// must still have a capacity assigned to it.
739//
740// TODO(user): Support general linear expression for capacities.
741// TODO(user): Some model applies the same capacity to both an arc and its
742// reverse. Also support this case.
743CutGenerator CreateFlowCutGenerator(
744 int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
745 const std::vector<AffineExpression>& arc_capacities,
746 std::function<void(const std::vector<bool>& in_subset,
747 IntegerValue* min_incoming_flow,
748 IntegerValue* min_outgoing_flow)>
749 get_flows,
750 Model* model);
751
752} // namespace sat
753} // namespace operations_research
754
755#endif // OR_TOOLS_SAT_ROUTING_CUTS_H_
Definition model.h:341
Keep the best min outgoing/incoming flow out of a subset.
absl::Span< const int > CannotBeFirst() const
absl::Span< const int > CannotBeLast() const
void Update(int new_bound, std::string name, absl::Span< const int > cannot_be_first={}, absl::Span< const int > cannot_be_last={})
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1419
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Returns globally valid lower/upper bound on the given integer variable.
Definition integer.h:1412
Similar to MaxBoundedSubsetSum() above but use a different algo.
Definition util.h:542
int ComputeTightMinOutgoingFlow(absl::Span< const int > subset)
void ReportDpSkip()
Just for stats reporting.
absl::Span< const int > CannotBeLast() const
int ComputeMinOutgoingFlow(absl::Span< const int > subset)
bool SubsetMightBeServedWithKRoutes(int k, absl::Span< const int > subset, RouteRelationsHelper *helper=nullptr, LinearConstraintManager *manager=nullptr, int special_node=-1, bool use_forward_direction=true)
absl::Span< const int > CannotBeFirst() const
int ComputeDimensionBasedMinOutgoingFlow(absl::Span< const int > subset, const RouteRelationsHelper &helper, BestBoundHelper *best_bound)
MinOutgoingFlowHelper(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, Model *model)
bool PathExists(int tail, int head) const
const HeadMinusTailBounds & GetArcRelation(int arc, int dimension) const
IntegerValue GetArcOffsetLowerBound(int arc, int dimension, bool negate_expressions) const
const AffineExpression & GetNodeExpression(int node, int dimension) const
Returns the expression associated with the given node and dimension.
static std::unique_ptr< RouteRelationsHelper > Create(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, absl::Span< const AffineExpression > flat_node_dim_expressions, const BinaryRelationRepository &binary_relation_repository, Model *model)
bool PropagateLocalBoundsUsingShortestPaths(const IntegerTrail &integer_trail, int tail, int head, const absl::flat_hash_map< IntegerVariable, IntegerValue > &input, absl::flat_hash_map< IntegerVariable, IntegerValue > *output) const
int num_dimensions() const
Returns the number of "dimensions", such as time or vehicle load.
void RemoveArcs(absl::Span< const int > sorted_arc_indices)
RouteRelationsHelper()=default
Visible for testing.
Simple class to add statistics by name and print them at the end.
int ComputeMinNumberOfBins(absl::Span< ItemOrBin > objects, std::vector< int > &objects_that_cannot_be_bin_and_reach_minimum, std::string &info)
bool UseDpToTightenCapacities(absl::Span< ItemOrBin > objects)
bool GreedyPackingWorks(int num_bins, absl::Span< const ItemOrBin > objects)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
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)
std::pair< int, int > MaybeFillMissingRoutesConstraintNodeExpressions(const CpModelProto &input_model, CpModelProto &output_model)
RoutingCumulExpressions DetectDimensionsAndCumulExpressions(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, const BinaryRelationRepository &binary_relation_repository)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
std::pair< IntegerValue, IntegerValue > GetDifferenceBounds(const NodeExpression &x_expr, const NodeExpression &y_expr, const sat::Relation &r, const std::pair< IntegerValue, IntegerValue > &x_var_bounds, const std::pair< IntegerValue, IntegerValue > &y_var_bounds)
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
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)
IntegerValue CapAddI(IntegerValue a, IntegerValue b)
CutGenerator CreateCVRPCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, absl::Span< const AffineExpression > flat_node_dim_expressions, 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)
bool VariableIsPositive(IntegerVariable i)
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.
void LogStats(const SetCoverModel &model)
static int input(yyscan_t yyscanner)
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.
IntegerValue ValueAt(IntegerValue var_value) const
Returns the value of this affine expression given its variable value.
bool operator==(const ArcWithLpValue &o) const
IntegerValue ValueAt(IntegerValue x) const
NodeExpression(IntegerVariable var, IntegerValue coeff, IntegerValue offset)
NodeExpression(const AffineExpression &expr)
std::vector< AffineExpression > flat_node_dim_expressions
const AffineExpression & GetNodeExpression(int node, int dimension) const
int node
The initial routing node that correspond to this object.