Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
routing_cuts.cc
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
15
16#include <algorithm>
17#include <atomic>
18#include <cmath>
19#include <cstdint>
20#include <cstdlib>
21#include <functional>
22#include <limits>
23#include <memory>
24#include <numeric>
25#include <queue>
26#include <stack>
27#include <string>
28#include <tuple>
29#include <utility>
30#include <vector>
31
32#include "absl/algorithm/container.h"
33#include "absl/cleanup/cleanup.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/flags/flag.h"
37#include "absl/log/check.h"
38#include "absl/log/log.h"
39#include "absl/log/vlog_is_on.h"
40#include "absl/numeric/bits.h"
41#include "absl/numeric/int128.h"
42#include "absl/random/distributions.h"
43#include "absl/strings/str_cat.h"
44#include "absl/types/span.h"
50#include "ortools/graph/graph.h"
52#include "ortools/sat/clause.h"
55#include "ortools/sat/cuts.h"
57#include "ortools/sat/integer.h"
61#include "ortools/sat/model.h"
67#include "ortools/sat/util.h"
69
70ABSL_FLAG(bool, cp_model_dump_routes_support_graphs, false,
71 "DEBUG ONLY. When set to true, SolveCpModel() dumps the arcs with "
72 "non-zero LP values of the routes constraints, at decision level 0, "
73 "which are used to subsequently generate cuts. The values are "
74 "written as a SupportGraphProto in text format to "
75 "'FLAGS_cp_model_dump_prefix'support_graph_{counter}.pb.txt.");
76
77namespace operations_research {
78namespace sat {
79
80namespace {
81
82// If we have many sets of (var >= values relations) and we know at least one
83// set is true, then we can derive valid global lower bounds on the variable
84// that appear in all such sets.
85//
86// This represents "one step" of computing such bounds by adding the sets one at
87// the time. When we process a set 'new_lbs':
88// - For the first set, then 'new_lbs' is globally valid.
89// - For a new set, we need to erase variables not appearing in new_lbs and
90// take the min for the ones appearing in both.
91//
92// TODO(user): The operation is symmetric, so we could swap both hash_map if
93// new_lbs is smaller than current_lbs as a small optimization.
94void ComputeMinLowerBoundOfSharedVariables(
95 const absl::flat_hash_map<IntegerVariable, IntegerValue>& new_lbs,
96 absl::flat_hash_map<IntegerVariable, IntegerValue>* current_lbs) {
97 if (new_lbs.empty()) current_lbs->clear();
98 for (auto it = current_lbs->begin(); it != current_lbs->end();) {
99 const IntegerVariable var = it->first;
100 auto other_it = new_lbs.find(var);
101 if (other_it == new_lbs.end()) {
102 // We have no bounds about var in the new_fact, we need to erase it.
103 //
104 // Note: it = current_lbs->erase(it) does not work for flat_hash_map, this
105 // is the recommended way.
106 current_lbs->erase(it++);
107 } else {
108 // Update.
109 it->second = std::min(it->second, other_it->second);
110 ++it;
111 }
112 }
113}
114
115} // namespace
116
118 int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
119 const std::vector<Literal>& literals, Model* model)
120 : tails_(tails),
121 heads_(heads),
122 literals_(literals),
123 binary_relation_repository_(
124 *model->GetOrCreate<ConditionalLinear2Bounds>()),
125 implied_bounds_(*model->GetOrCreate<ImpliedBounds>()),
126 trail_(*model->GetOrCreate<Trail>()),
127 integer_trail_(*model->GetOrCreate<IntegerTrail>()),
128 integer_encoder_(*model->GetOrCreate<IntegerEncoder>()),
129 root_level_bounds_(*model->GetOrCreate<RootLevelLinear2Bounds>()),
130 shared_stats_(model->GetOrCreate<SharedStatistics>()),
131 in_subset_(num_nodes, false),
132 index_in_subset_(num_nodes, -1),
133 incoming_arc_indices_(num_nodes),
134 outgoing_arc_indices_(num_nodes),
135 reachable_(num_nodes, false),
136 next_reachable_(num_nodes, false),
137 node_var_lower_bounds_(num_nodes),
138 next_node_var_lower_bounds_(num_nodes),
139 bin_packing_helper_(
140 model->GetOrCreate<SatParameters>()->routing_cut_dp_effort()) {}
141
143 if (!VLOG_IS_ON(1)) return;
144 std::vector<std::pair<std::string, int64_t>> stats;
145 stats.push_back({"RoutingDp/num_full_dp_calls", num_full_dp_calls_});
146 stats.push_back({"RoutingDp/num_full_dp_skips", num_full_dp_skips_});
147 stats.push_back(
148 {"RoutingDp/num_full_dp_early_abort", num_full_dp_early_abort_});
149 stats.push_back(
150 {"RoutingDp/num_full_dp_work_abort", num_full_dp_work_abort_});
151 stats.push_back({"RoutingDp/num_full_dp_rc_skip", num_full_dp_rc_skip_});
152 stats.push_back(
153 {"RoutingDp/num_full_dp_unique_arc", num_full_dp_unique_arc_});
154 for (const auto& [name, count] : num_by_type_) {
155 stats.push_back({absl::StrCat("RoutingDp/num_bounds_", name), count});
156 }
157 shared_stats_->AddStats(stats);
158}
159
160// TODO(user): This is costly and we might do it more than once per subset.
161// fix.
162void MinOutgoingFlowHelper::PrecomputeDataForSubset(
163 absl::Span<const int> subset) {
164 cannot_be_first_.clear();
165 cannot_be_last_.clear();
166
167 const int num_nodes = in_subset_.size();
168 in_subset_.assign(num_nodes, false);
169 index_in_subset_.assign(num_nodes, -1);
170 for (int i = 0; i < subset.size(); ++i) {
171 const int n = subset[i];
172 in_subset_[n] = true;
173 index_in_subset_[n] = i;
174 }
175
176 has_incoming_arcs_from_outside_.assign(num_nodes, false);
177 has_outgoing_arcs_to_outside_.assign(num_nodes, false);
178 incoming_arcs_from_outside_.assign(num_nodes, {});
179 outgoing_arcs_to_outside_.assign(num_nodes, {});
180
181 for (auto& v : incoming_arc_indices_) v.clear();
182 for (auto& v : outgoing_arc_indices_) v.clear();
183 for (int i = 0; i < tails_.size(); ++i) {
184 const int tail = tails_[i];
185 const int head = heads_[i];
186
187 // we always ignore self-arcs here.
188 if (tail == head) continue;
189 const bool tail_in = in_subset_[tail];
190 const bool head_in = in_subset_[head];
191
192 if (tail_in && head_in) {
193 outgoing_arc_indices_[tail].push_back(i);
194 incoming_arc_indices_[head].push_back(i);
195 } else if (tail_in && !head_in) {
196 outgoing_arcs_to_outside_[tail].Add(i);
197 has_outgoing_arcs_to_outside_[tail] = true;
198 } else if (!tail_in && head_in) {
199 incoming_arcs_from_outside_[head].Add(i);
200 has_incoming_arcs_from_outside_[head] = true;
201 }
202 }
203}
204
206 absl::Span<const int> subset, const RouteRelationsHelper& helper,
207 BestBoundHelper* best_bound) {
208 PrecomputeDataForSubset(subset);
209 DCHECK_EQ(helper.num_nodes(), in_subset_.size());
210 DCHECK_EQ(helper.num_arcs(), tails_.size());
211
212 int min_outgoing_flow = 1;
213 std::string best_name;
214 for (int d = 0; d < helper.num_dimensions(); ++d) {
215 for (const bool negate_expressions : {false, true}) {
216 for (const bool use_incoming : {true, false}) {
217 std::string info;
218 const absl::Span<SpecialBinPackingHelper::ItemOrBin> objects =
219 RelaxIntoSpecialBinPackingProblem(subset, d, negate_expressions,
220 use_incoming, helper);
221 const int bound = bin_packing_helper_.ComputeMinNumberOfBins(
222 objects, objects_that_cannot_be_bin_and_reach_minimum_, info);
223 if (bound >= min_outgoing_flow) {
224 min_outgoing_flow = bound;
225 best_name = absl::StrCat((use_incoming ? "in" : "out"), info,
226 (negate_expressions ? "_neg" : ""), "_", d);
227 cannot_be_first_.clear();
228 cannot_be_last_.clear();
229 auto& vec = use_incoming ? cannot_be_first_ : cannot_be_last_;
230 for (const int i : objects_that_cannot_be_bin_and_reach_minimum_) {
231 vec.push_back(objects[i].node);
232 }
233 best_bound->Update(bound, "AutomaticDimension", cannot_be_first_,
234 cannot_be_last_);
235 }
236 }
237 }
238 }
239
240 if (min_outgoing_flow > 1) num_by_type_[best_name]++;
241 return min_outgoing_flow;
242}
243
244absl::Span<SpecialBinPackingHelper::ItemOrBin>
245MinOutgoingFlowHelper::RelaxIntoSpecialBinPackingProblem(
246 absl::Span<const int> subset, int dimension, bool negate_expressions,
247 bool use_incoming, const RouteRelationsHelper& helper) {
248 tmp_bin_packing_problem_.clear();
249
250 // Computes UB/LB. The derivation in the .h used a simple version, but we can
251 // be a bit tighter. We explain for the use_incoming case only:
252 //
253 // In this setting, recall that bins correspond to the first node of each
254 // route covering the subset. The max_upper_bound is a maximum over the last
255 // node of each route, and as such it does not need to consider nodes with no
256 // arc leaving the subset: such nodes cannot be last.
257 //
258 // Moreover, if the bin is not empty, then the bound for that bin does not
259 // need to consider the variable of the "bin" node itself. If the new bound is
260 // non-negative, then it is also valid for the empty bin. If it is negative we
261 // have an issue as using it will force the bin to not be empty. But we can
262 // always use std::max(0, new_bound) which is always valid and still tighter
263 // than the bound explained in the .h which is always non-negative.
264 //
265 // This way we can have a different bound for each bin, which is slightly
266 // stronger than using the same bound for all of them.
267 const int num_nodes = in_subset_.size();
268 std::vector<IntegerValue> min_lower_bound_of_others(num_nodes,
270 std::vector<IntegerValue> max_upper_bound_of_others(num_nodes,
272
273 // Lower bound on the node expression when it is first to serve the subset.
274 const auto lb_when_first = [&helper, this](int n, int dimension,
275 bool negate_expressions) {
276 AffineExpression expr = helper.GetNodeExpression(n, dimension);
277 if (negate_expressions) expr = expr.Negated();
278 IntegerValue lb = integer_trail_.LevelZeroLowerBound(expr);
279 if (incoming_arcs_from_outside_[n].IsUnique()) {
280 const int a = incoming_arcs_from_outside_[n].Get();
281 AffineExpression tail_expr =
282 helper.GetNodeExpression(tails_[a], dimension);
283 if (negate_expressions) tail_expr = tail_expr.Negated();
284 const IntegerValue offset =
285 helper.GetArcOffsetLowerBound(a, dimension, negate_expressions);
286 lb = std::max(lb, integer_trail_.LevelZeroLowerBound(tail_expr) + offset);
287 }
288 return lb;
289 };
290
291 // Upper bound on the node expression when it is last to serve the subset.
292 const auto ub_when_last = [&helper, this](int n, int dimension,
293 bool negate_expressions) {
294 AffineExpression expr = helper.GetNodeExpression(n, dimension);
295 if (negate_expressions) expr = expr.Negated();
296 IntegerValue ub = integer_trail_.LevelZeroUpperBound(expr);
297 if (outgoing_arcs_to_outside_[n].IsUnique()) {
298 const int a = outgoing_arcs_to_outside_[n].Get();
299 AffineExpression head_expr =
300 helper.GetNodeExpression(heads_[a], dimension);
301 if (negate_expressions) head_expr = head_expr.Negated();
302 const IntegerValue offset =
303 helper.GetArcOffsetLowerBound(a, dimension, negate_expressions);
304 ub = std::min(ub, integer_trail_.LevelZeroUpperBound(head_expr) - offset);
305 }
306 return ub;
307 };
308
309 // We do a forward and a backward pass in order to compute the min/max
310 // of all the other nodes for each node.
311 for (const bool forward_pass : {true, false}) {
312 IntegerValue local_min = kMaxIntegerValue;
313 IntegerValue local_max = kMinIntegerValue;
314 const int size = subset.size();
315 for (int i = 0; i < size; ++i) {
316 const int n = forward_pass ? subset[i] : subset[size - 1 - i];
317
318 // The local_min/max contains the min/max of all nodes strictly before 'n'
319 // in the forward pass (resp. striclty after). So after two passes,
320 // min/max_..._of_others[n] will contains the min/max of all nodes
321 // different from n.
322 min_lower_bound_of_others[n] =
323 std::min(min_lower_bound_of_others[n], local_min);
324 max_upper_bound_of_others[n] =
325 std::max(max_upper_bound_of_others[n], local_max);
326
327 // Update local_min/max.
328 if (has_incoming_arcs_from_outside_[n]) {
329 local_min = std::min(local_min,
330 lb_when_first(n, dimension, negate_expressions));
331 }
332 if (has_outgoing_arcs_to_outside_[n]) {
333 local_max =
334 std::max(local_max, ub_when_last(n, dimension, negate_expressions));
335 }
336 }
337 }
338
339 for (const int n : subset) {
340 const absl::Span<const int> arcs =
341 use_incoming ? incoming_arc_indices_[n] : outgoing_arc_indices_[n];
342 IntegerValue demand = kMaxIntegerValue;
343 for (const int a : arcs) {
344 demand = std::min(demand, helper.GetArcOffsetLowerBound(
345 a, dimension, negate_expressions));
346 }
347
348 SpecialBinPackingHelper::ItemOrBin obj;
349 obj.node = n;
350 obj.demand = demand;
351
352 // We compute the capacity like this to avoid overflow and always have
353 // a non-negative capacity (see above).
354 obj.capacity = 0;
355 if (use_incoming) {
356 const IntegerValue lb = lb_when_first(n, dimension, negate_expressions);
357 if (max_upper_bound_of_others[n] > lb) {
358 obj.capacity = max_upper_bound_of_others[n] - lb;
359 }
360 } else {
361 const IntegerValue ub = ub_when_last(n, dimension, negate_expressions);
362 if (ub > min_lower_bound_of_others[n]) {
363 obj.capacity = ub - min_lower_bound_of_others[n];
364 }
365 }
366
367 // Note that we don't explicitly deal with the corner case of a subset node
368 // with no arcs. This corresponds to INFEASIBLE problem and should be dealt
369 // with elsewhere. Being "less restrictive" will still result in a valid
370 // bound and that is enough here.
371 if ((use_incoming && !has_incoming_arcs_from_outside_[n]) ||
372 (!use_incoming && !has_outgoing_arcs_to_outside_[n])) {
374 obj.capacity = 0;
375 } else if (arcs.empty()) {
377 obj.demand = 0; // We don't want kMaxIntegerValue !
378 } else {
380 }
381
382 tmp_bin_packing_problem_.push_back(obj);
383 }
384
385 return absl::MakeSpan(tmp_bin_packing_problem_);
386}
387
389 absl::Span<ItemOrBin> objects,
390 std::vector<int>& objects_that_cannot_be_bin_and_reach_minimum,
391 std::string& info) {
392 objects_that_cannot_be_bin_and_reach_minimum.clear();
393 if (objects.empty()) return 0;
394
395 IntegerValue sum_of_demands(0);
396 int64_t gcd = 0;
397 int64_t max_capacity = 0;
398 int max_num_items = 0;
399 bool all_demands_are_non_negative = true;
400 for (const ItemOrBin& obj : objects) {
401 sum_of_demands = CapAddI(sum_of_demands, obj.demand);
402 if (obj.type != MUST_BE_BIN) {
403 ++max_num_items;
404 if (obj.demand < 0) {
405 all_demands_are_non_negative = false;
406 }
407 gcd = std::gcd(gcd, std::abs(obj.demand.value()));
408 }
409 if (obj.type != MUST_BE_ITEM) {
410 max_capacity = std::max(max_capacity, obj.capacity.value());
411 }
412 }
413
414 // TODO(user): we can probably handle a couple of extra case rather than just
415 // bailing out here and below.
416 if (AtMinOrMaxInt64I(sum_of_demands)) return 0;
417
418 // If the gcd of all the demands term is positive, we can divide everything.
419 if (gcd > 1) {
420 absl::StrAppend(&info, "_gcd");
421 for (ItemOrBin& obj : objects) {
422 obj.demand /= gcd;
423 obj.capacity = FloorRatio(obj.capacity, gcd);
424 }
425 max_capacity = MathUtil::FloorOfRatio(max_capacity, gcd);
426 }
427
428 const int result = ComputeMinNumberOfBinsInternal(
429 objects, objects_that_cannot_be_bin_and_reach_minimum);
430
431 // We only try DP if it can help.
432 //
433 // Tricky: For the GreedyPackingWorks() to make sense, we use the fact that
434 // ComputeMinNumberOfBinsInternal() moves the best bins first. In any case
435 // the code will still be correct if it is not the case as we just use this
436 // as an heuristic to do less work.
437 if (result > 1 && all_demands_are_non_negative &&
438 max_bounded_subset_sum_exact_.ComplexityEstimate(
439 max_num_items, max_capacity) < dp_effort_ &&
440 !GreedyPackingWorks(result, objects) &&
441 UseDpToTightenCapacities(objects)) {
442 const int new_result = ComputeMinNumberOfBinsInternal(
443 objects, objects_that_cannot_be_bin_and_reach_minimum);
444 CHECK_GE(new_result, result);
445 if (new_result > result) {
446 absl::StrAppend(&info, "_dp");
447 }
448 return new_result;
449 }
450
451 return result;
452}
453
454// TODO(user): We could be more tight here and also compute the max_reachable
455// under smaller capacity than the max_capacity.
457 absl::Span<ItemOrBin> objects) {
458 tmp_demands_.clear();
459 int64_t max_capacity = 0;
460 for (const ItemOrBin& obj : objects) {
461 if (obj.type != MUST_BE_BIN) {
462 tmp_demands_.push_back(obj.demand.value());
463 }
464 if (obj.type != MUST_BE_ITEM) {
465 max_capacity = std::max(max_capacity, obj.capacity.value());
466 }
467 }
468 const int64_t max_reachable =
469 max_bounded_subset_sum_exact_.MaxSubsetSum(tmp_demands_, max_capacity);
470 bool some_change = false;
471 if (max_reachable < max_capacity) {
472 for (ItemOrBin& obj : objects) {
473 if (obj.capacity > max_reachable) {
474 some_change = true;
475 obj.capacity = max_reachable;
476 }
477 }
478 }
479 return some_change;
480}
481
482int SpecialBinPackingHelper::ComputeMinNumberOfBinsInternal(
483 absl::Span<ItemOrBin> objects,
484 std::vector<int>& objects_that_cannot_be_bin_and_reach_minimum) {
485 objects_that_cannot_be_bin_and_reach_minimum.clear();
486
487 // For a given choice of bins (set B), a feasible problem must satisfy.
488 // sum_{i \notin B} demands_i <= sum_{i \in B} capacity_i.
489 //
490 // Using this we can compute a lower bound on the number of bins needed
491 // using a greedy algorithm that chooses B in order to make the above
492 // inequality as "loose" as possible.
493 //
494 // This puts 'a' before 'b' if we get more unused capacity by using 'a' as a
495 // bin and 'b' as an item rather than the other way around. If we call the
496 // other items demands D and capacities C, the two options are:
497 // - option1: a.demand + D <= b.capacity + C
498 // - option2: b.demand + D <= a.capacity + C
499 //
500 // The option2 above leads to more unused capacity:
501 // (a.capacity - b.demand) > (b.capacity - a.demand).
502 std::stable_sort(objects.begin(), objects.end(),
503 [](const ItemOrBin& a, const ItemOrBin& b) {
504 if (a.type != b.type) {
505 // We want in order:
506 // MUST_BE_BIN, ITEM_OR_BIN, MUST_BE_ITEM.
507 return a.type > b.type;
508 }
509 return a.capacity + a.demand > b.capacity + b.demand;
510 });
511
512 // Note that we already checked for overflow.
513 IntegerValue sum_of_demands(0);
514 for (const ItemOrBin& obj : objects) {
515 sum_of_demands += obj.demand;
516 }
517
518 // We start with no bins (sum_of_demands=everything, sum_of_capacity=0) and
519 // add the best bins one by one until we have sum_of_demands <=
520 // sum_of_capacity.
521 int num_bins = 0;
522 IntegerValue sum_of_capacity(0);
523 for (; num_bins < objects.size(); ++num_bins) {
524 // Use obj as a bin instead of as an item, if possible (i.e., unless
525 // obj.type is MUST_BE_ITEM).
526 const ItemOrBin& obj = objects[num_bins];
527 if (obj.type != MUST_BE_BIN && sum_of_demands <= sum_of_capacity) {
528 break;
529 }
530 if (obj.type == MUST_BE_ITEM) {
531 // Because of our order, we only have objects of type MUST_BE_ITEM left.
532 // Hence we can no longer change items to bins, and since the demands
533 // exceed the capacity, the problem is infeasible. We just return the
534 // (number of objects + 1) in this case (any bound is valid).
535 DCHECK_GT(sum_of_demands, sum_of_capacity);
536 return objects.size() + 1;
537 }
538 sum_of_capacity = CapAddI(sum_of_capacity, obj.capacity);
539 if (AtMinOrMaxInt64I(sum_of_capacity)) return num_bins;
540 sum_of_demands -= obj.demand;
541 }
542
543 // We can identify items that cannot be a bin in a solution reaching the
544 // minimum 'num_bins'.
545 //
546 // TODO(user): Somewhat related are the items that can be removed while still
547 // having the same required 'num_bins' to solve the problem.
548 if (num_bins > 0 && num_bins != objects.size()) {
549 const int worst_used_bin = num_bins - 1;
550 if (objects[worst_used_bin].type == MUST_BE_BIN) {
551 // All first object must be bins, we don't have a choice if we want to
552 // reach the lower bound. The others cannot be bins.
553 for (int i = num_bins; i < objects.size(); ++i) {
554 objects_that_cannot_be_bin_and_reach_minimum.push_back(i);
555 }
556 } else {
557 // We revert the worst_used_bin and tries to use the other instead.
558 CHECK_EQ(objects[worst_used_bin].type, ITEM_OR_BIN);
559 sum_of_demands += objects[worst_used_bin].demand;
560 sum_of_capacity -= objects[worst_used_bin].capacity;
561 const IntegerValue slack = sum_of_demands - sum_of_capacity;
562 CHECK_GE(slack, 0) << num_bins << " " << objects.size() << " "
563 << objects[worst_used_bin].type;
564
565 for (int i = num_bins; i < objects.size(); ++i) {
566 if (objects[i].type == MUST_BE_ITEM) continue;
567
568 // If we use this as a bin instead of "worst_used_bin" can we still
569 // fulfill the demands?
570 const IntegerValue new_demand = sum_of_demands - objects[i].demand;
571 const IntegerValue new_capacity = sum_of_capacity + objects[i].capacity;
572 if (new_demand > new_capacity) {
573 objects_that_cannot_be_bin_and_reach_minimum.push_back(i);
574 }
575 }
576 }
577 }
578
579 return num_bins;
580}
581
583 int num_bins, absl::Span<const ItemOrBin> objects) {
584 if (num_bins >= objects.size()) return true;
585 CHECK_GE(num_bins, 1);
586 tmp_capacities_.resize(num_bins);
587 for (int i = 0; i < num_bins; ++i) {
588 tmp_capacities_[i] = objects[i].capacity;
589 }
590
591 // We place the objects in order in the first bin that fits.
592 for (const ItemOrBin object : objects.subspan(num_bins)) {
593 bool placed = false;
594 for (int b = 0; b < num_bins; ++b) {
595 if (object.demand <= tmp_capacities_[b]) {
596 placed = true;
597 tmp_capacities_[b] -= object.demand;
598 break;
599 }
600 }
601 if (!placed) return false;
602 }
603 return true;
604}
605
607 absl::Span<const int> subset) {
608 DCHECK_GE(subset.size(), 1);
609 DCHECK(absl::c_all_of(next_reachable_, [](bool b) { return !b; }));
610 DCHECK(absl::c_all_of(node_var_lower_bounds_,
611 [](const auto& m) { return m.empty(); }));
612 DCHECK(absl::c_all_of(next_node_var_lower_bounds_,
613 [](const auto& m) { return m.empty(); }));
614 PrecomputeDataForSubset(subset);
615
616 // Conservatively assume that each subset node is reachable from outside.
617 // TODO(user): use has_incoming_arcs_from_outside_[] to be more precise.
618 reachable_ = in_subset_;
619
620 // Maximum number of nodes of a feasible path inside the subset.
621 int longest_path_length = 1;
622 absl::flat_hash_map<IntegerVariable, IntegerValue> tmp_lbs;
623 while (longest_path_length < subset.size()) {
624 bool at_least_one_next_node_reachable = false;
625 for (const int head : subset) {
626 bool is_reachable = false;
627 for (const int incoming_arc_index : incoming_arc_indices_[head]) {
628 const int tail = tails_[incoming_arc_index];
629 const Literal lit = literals_[incoming_arc_index];
630 if (!reachable_[tail]) continue;
631
632 // If this arc cannot be taken skip.
633 tmp_lbs.clear();
635 integer_trail_, root_level_bounds_, binary_relation_repository_,
636 implied_bounds_, lit, node_var_lower_bounds_[tail], &tmp_lbs)) {
637 continue;
638 }
639
640 if (!is_reachable) {
641 // This is the first arc that reach this node.
642 is_reachable = true;
643 next_node_var_lower_bounds_[head] = tmp_lbs;
644 } else {
645 // Combine information with previous one.
646 ComputeMinLowerBoundOfSharedVariables(
647 tmp_lbs, &next_node_var_lower_bounds_[head]);
648 }
649 }
650
651 next_reachable_[head] = is_reachable;
652 if (next_reachable_[head]) at_least_one_next_node_reachable = true;
653 }
654 if (!at_least_one_next_node_reachable) {
655 break;
656 }
657 std::swap(reachable_, next_reachable_);
658 std::swap(node_var_lower_bounds_, next_node_var_lower_bounds_);
659 for (const int n : subset) {
660 next_node_var_lower_bounds_[n].clear();
661 }
662 ++longest_path_length;
663 }
664
665 // The maximum number of distinct paths of length `longest_path_length`.
666 int max_longest_paths = 0;
667 for (const int n : subset) {
668 if (reachable_[n]) ++max_longest_paths;
669
670 // Reset the temporary data structures for the next call.
671 next_reachable_[n] = false;
672 node_var_lower_bounds_[n].clear();
673 next_node_var_lower_bounds_[n].clear();
674 }
675 return GetMinOutgoingFlow(subset.size(), longest_path_length,
676 max_longest_paths);
677}
678
679int MinOutgoingFlowHelper::GetMinOutgoingFlow(int subset_size,
680 int longest_path_length,
681 int max_longest_paths) {
682 if (max_longest_paths * longest_path_length < subset_size) {
683 // If `longest_path_length` is 1, there should be one such path per node,
684 // and thus `max_longest_paths` should be the subset size. This branch can
685 // thus not be taken in this case.
686 DCHECK_GT(longest_path_length, 1);
687 // TODO(user): Consider using information about the number of shorter
688 // paths to derive an even better bound.
689 return max_longest_paths +
691 subset_size - max_longest_paths * longest_path_length,
692 longest_path_length - 1);
693 }
694 return MathUtil::CeilOfRatio(subset_size, longest_path_length);
695}
696
697namespace {
698struct Path {
699 uint32_t node_set; // Bit i is set iif node subset[i] is in the path.
700 int last_node; // The last node in the path.
701
702 bool operator==(const Path& p) const {
703 return node_set == p.node_set && last_node == p.last_node;
704 }
705
706 template <typename H>
707 friend H AbslHashValue(H h, const Path& p) {
708 return H::combine(std::move(h), p.node_set, p.last_node);
709 }
710};
711
712} // namespace
713
715 absl::Span<const int> subset) {
716 DCHECK_GE(subset.size(), 1);
717 DCHECK_LE(subset.size(), 32);
718 PrecomputeDataForSubset(subset);
719
720 std::vector<int> longest_path_length_by_end_node(subset.size(), 1);
721
722 absl::flat_hash_map<IntegerVariable, IntegerValue> tmp_lbs;
723 absl::flat_hash_map<Path, absl::flat_hash_map<IntegerVariable, IntegerValue>>
724 path_var_bounds;
725 std::vector<Path> paths;
726 std::vector<Path> next_paths;
727 for (int i = 0; i < subset.size(); ++i) {
728 // TODO(user): use has_incoming_arcs_from_outside_[] to skip some nodes.
729 paths.push_back(
730 {.node_set = static_cast<uint32_t>(1 << i), .last_node = subset[i]});
731 path_var_bounds[paths.back()] = {}; // LevelZero bounds.
732 }
733 int longest_path_length = 1;
734 for (int path_length = 1; path_length <= subset.size(); ++path_length) {
735 for (const Path& path : paths) {
736 // We remove it from the hash_map since this entry should no longer be
737 // used as we create path of increasing length.
738 DCHECK(path_var_bounds.contains(path));
739 const absl::flat_hash_map<IntegerVariable, IntegerValue> path_bounds =
740 std::move(path_var_bounds.extract(path).mapped());
741
742 // We have found a feasible path, update the path length statistics...
743 longest_path_length = path_length;
744 longest_path_length_by_end_node[index_in_subset_[path.last_node]] =
745 path_length;
746
747 // ... and try to extend the path with each arc going out of its last
748 // node.
749 for (const int outgoing_arc_index :
750 outgoing_arc_indices_[path.last_node]) {
751 const int head = heads_[outgoing_arc_index];
752 const int head_index_in_subset = index_in_subset_[head];
753 DCHECK_NE(head_index_in_subset, -1);
754 if (path.node_set & (1 << head_index_in_subset)) continue;
755 const Path new_path = {
756 .node_set = path.node_set | (1 << head_index_in_subset),
757 .last_node = head};
758
759 // If this arc cannot be taken skip.
760 tmp_lbs.clear();
761 if (!PropagateLocalBounds(integer_trail_, root_level_bounds_,
762 binary_relation_repository_, implied_bounds_,
763 literals_[outgoing_arc_index], path_bounds,
764 &tmp_lbs)) {
765 continue;
766 }
767
768 const auto [it, inserted] = path_var_bounds.insert({new_path, tmp_lbs});
769 if (inserted) {
770 // We have a feasible path to a new state.
771 next_paths.push_back(new_path);
772 } else {
773 // We found another way to reach this state, only keep common best
774 // bounds.
775 ComputeMinLowerBoundOfSharedVariables(tmp_lbs, &it->second);
776 }
777 }
778 }
779 std::swap(paths, next_paths);
780 next_paths.clear();
781 }
782
783 int max_longest_paths = 0;
784 for (int i = 0; i < subset.size(); ++i) {
785 if (longest_path_length_by_end_node[i] == longest_path_length) {
786 ++max_longest_paths;
787 }
788 }
789
790 return GetMinOutgoingFlow(subset.size(), longest_path_length,
791 max_longest_paths);
792}
793
795 int k, absl::Span<const int> subset, RouteRelationsHelper* helper,
796 LinearConstraintManager* manager, int special_node,
797 bool use_forward_direction) {
798 cannot_be_first_.clear();
799 cannot_be_last_.clear();
800 if (k >= subset.size()) return true;
801 if (subset.size() > 31) return true;
802
803 // If we have a "special node" from which we must start, make sure it is
804 // always first. This simplifies the code a bit, and we don't care about the
805 // order of nodes in subset.
806 if (special_node >= 0) {
807 tmp_subset_.assign(subset.begin(), subset.end());
808 bool seen = false;
809 for (int i = 0; i < tmp_subset_.size(); ++i) {
810 if (tmp_subset_[i] == special_node) {
811 seen = true;
812 std::swap(tmp_subset_[0], tmp_subset_[i]);
813 break;
814 }
815 }
816 CHECK(seen);
817
818 // Make the span point to the new order.
819 subset = tmp_subset_;
820 }
821
822 PrecomputeDataForSubset(subset);
823 ++num_full_dp_calls_;
824
825 // We need a special graph here to handle both options.
826 // TODO(user): Maybe we should abstract that in a better way.
828 adjacency.reserve(subset.size());
829 for (int i = 0; i < subset.size(); ++i) {
830 adjacency.Add({});
831 const int node = subset[i];
832 if (helper != nullptr) {
833 CHECK(use_forward_direction);
834 for (int j = 0; j < subset.size(); ++j) {
835 if (i == j) continue;
836 if (helper->PathExists(node, subset[j])) {
837 adjacency.AppendToLastVector({subset[j], Literal(kNoLiteralIndex)});
838 }
839 }
840 } else {
841 if (use_forward_direction) {
842 for (const int arc : outgoing_arc_indices_[node]) {
843 adjacency.AppendToLastVector({heads_[arc], literals_[arc]});
844 }
845 } else {
846 for (const int arc : incoming_arc_indices_[node]) {
847 adjacency.AppendToLastVector({tails_[arc], literals_[arc]});
848 }
849 }
850 }
851 }
852
853 struct State {
854 // Bit i is set iif node subset[i] is in one of the current routes.
855 uint32_t node_set;
856
857 // The last nodes of each of the k routes. If the hamming weight is less
858 // that k, then at least one route is still empty.
859 uint32_t last_nodes_set;
860
861 // Valid lower bounds for this state.
862 //
863 // Note that unlike the other algorithm here, we keep the collective bounds
864 // of all the nodes so far, so this is likely in
865 // O(longest_route * num_dimensions) which can take quite a lot of space.
866 //
867 // By "dimensions", we mean the number of variables appearing in binary
868 // relation controlled by an arc literal. See for instance
869 // RouteRelationsHelper that also uses a similar definition.
870 //
871 // Hopefully the DFS order limit the number of entry to O(n^2 * k), so still
872 // somewhat reasonable for small values.
873 absl::flat_hash_map<IntegerVariable, IntegerValue> lbs;
874
875 // The sum of the reduced costs of all the literals selected to form the
876 // route(s) encoded by this state. If this becomes too large we know that
877 // this state can never be used to get to a better solution than the current
878 // best one and is thus "infeasible" for the problem of finding a better
879 // solution.
880 absl::int128 sum_of_reduced_costs = 0;
881 };
882
883 const int size = subset.size();
884 const uint32_t final_mask = (1 << size) - 1;
885
886 const auto& can_be_last_set = use_forward_direction
887 ? has_outgoing_arcs_to_outside_
888 : has_incoming_arcs_from_outside_;
889 const auto& can_be_first_set = use_forward_direction
890 ? has_incoming_arcs_from_outside_
891 : has_outgoing_arcs_to_outside_;
892
893 uint32_t can_be_last_mask = 0;
894 for (int i = 0; i < subset.size(); ++i) {
895 if (can_be_last_set[subset[i]]) {
896 can_be_last_mask |= (1 << i);
897 }
898 }
899
900 // This is also correlated to the work done, and we abort if we starts to
901 // do too much work on one instance.
902 int64_t allocated_memory_estimate = 0;
903
904 // We just do a DFS from the initial state.
905 std::vector<State> states;
906 states.push_back(State());
907
908 // If a state has an unique arc to enter/leave it, we can update it further.
909 const auto update_using_unique_arc = [manager, this](UniqueArc unique_arc,
910 State& state) {
911 if (!unique_arc.IsUnique()) return true;
912 if (manager == nullptr) return true;
913
914 ++num_full_dp_unique_arc_;
915 const Literal unique_lit = literals_[unique_arc.Get()];
916 state.sum_of_reduced_costs += manager->GetLiteralReducedCost(unique_lit);
917 if (state.sum_of_reduced_costs > manager->ReducedCostsGap()) {
918 ++num_full_dp_rc_skip_;
919 return false;
920 }
921
922 absl::flat_hash_map<IntegerVariable, IntegerValue> copy = state.lbs;
923 return PropagateLocalBounds(integer_trail_, root_level_bounds_,
924 binary_relation_repository_, implied_bounds_,
925 unique_lit, copy, &state.lbs);
926 };
927
928 // We always start with the first node in this case.
929 if (special_node >= 0) {
930 states.back().node_set = 1;
931 states.back().last_nodes_set = 1;
932
933 // If there is a unique way to enter (resp. leave) that subset, we can start
934 // with tighter bounds!
935 const UniqueArc unique_arc = use_forward_direction
936 ? incoming_arcs_from_outside_[subset[0]]
937 : outgoing_arcs_to_outside_[subset[0]];
938 if (!update_using_unique_arc(unique_arc, states.back())) {
939 return false; // We can't even start!
940 }
941 }
942
943 while (!states.empty()) {
944 if (allocated_memory_estimate > 1e7) {
945 ++num_full_dp_work_abort_;
946 return true; // Abort.
947 }
948 const State from_state = std::move(states.back());
949 states.pop_back();
950
951 // The number of routes is the hamming weight of from_state.last_nodes_set.
952 const int num_routes = absl::popcount(from_state.last_nodes_set);
953
954 // We start by choosing the first k starts (in increasing order).
955 // For that we only add after the maximum position already chosen.
956 if (num_routes < k) {
957 const int num_extra = k - num_routes - 1;
958 for (int i = 0; i + num_extra < size; ++i) {
959 if (from_state.node_set >> i) continue;
960 if (!can_be_first_set[subset[i]]) continue;
961
962 // All "initial-state" start with empty hash-map that correspond to
963 // the level zero bounds.
964 State to_state;
965 const uint32_t head_mask = (1 << i);
966 to_state.node_set = from_state.node_set | head_mask;
967 to_state.last_nodes_set = from_state.last_nodes_set | head_mask;
968 to_state.lbs = from_state.lbs;
969 to_state.sum_of_reduced_costs = from_state.sum_of_reduced_costs;
970
971 const UniqueArc unique_arc =
972 use_forward_direction ? incoming_arcs_from_outside_[subset[i]]
973 : outgoing_arcs_to_outside_[subset[i]];
974 if (!update_using_unique_arc(unique_arc, to_state)) {
975 continue;
976 }
977
978 if (to_state.node_set == final_mask) {
979 ++num_full_dp_early_abort_;
980 return true; // All served!
981 }
982 states.push_back(std::move(to_state));
983 }
984 continue;
985 }
986
987 // We have k routes, extend one of the last nodes.
988 for (int i = 0; i < size; ++i) {
989 const uint32_t tail_mask = 1 << i;
990 if ((from_state.last_nodes_set & tail_mask) == 0) continue;
991 const int tail = subset[i];
992 for (const auto [head, literal] : adjacency[i]) {
993 const uint32_t head_mask = (1 << index_in_subset_[head]);
994 if (from_state.node_set & head_mask) continue;
995
996 State to_state;
997
998 // Use reduced costs to exclude solutions that cannot improve our
999 // current best solution.
1000 if (manager != nullptr) {
1001 DCHECK_EQ(helper, nullptr);
1002 to_state.sum_of_reduced_costs =
1003 from_state.sum_of_reduced_costs +
1004 manager->GetLiteralReducedCost(literal);
1005 if (to_state.sum_of_reduced_costs > manager->ReducedCostsGap()) {
1006 ++num_full_dp_rc_skip_;
1007 continue;
1008 }
1009 }
1010
1011 // We start from the old bounds and update them.
1012 to_state.lbs = from_state.lbs;
1013 if (helper != nullptr) {
1015 integer_trail_, tail, head, from_state.lbs, &to_state.lbs)) {
1016 continue;
1017 }
1018 } else {
1019 if (!PropagateLocalBounds(integer_trail_, root_level_bounds_,
1020 binary_relation_repository_,
1021 implied_bounds_, literal, from_state.lbs,
1022 &to_state.lbs)) {
1023 continue;
1024 }
1025 }
1026
1027 to_state.node_set = from_state.node_set | head_mask;
1028 to_state.last_nodes_set = from_state.last_nodes_set | head_mask;
1029 to_state.last_nodes_set ^= tail_mask;
1030 allocated_memory_estimate += to_state.lbs.size();
1031 if (to_state.node_set == final_mask) {
1032 // One of the last node has no arc to outside, this is not possible.
1033 if (to_state.last_nodes_set & ~can_be_last_mask) continue;
1034
1035 // Add the constraints implied by each last node that has an unique
1036 // way to leave the subset.
1037 bool infeasible = false;
1038 int last_mask = to_state.last_nodes_set;
1039 for (int j = 0; last_mask; j++, last_mask /= 2) {
1040 if ((last_mask & 1) == 0) continue;
1041
1042 const UniqueArc unique_arc =
1043 use_forward_direction ? outgoing_arcs_to_outside_[subset[j]]
1044 : incoming_arcs_from_outside_[subset[j]];
1045 if (!update_using_unique_arc(unique_arc, to_state)) {
1046 infeasible = true;
1047 break;
1048 }
1049 }
1050 if (infeasible) {
1051 continue;
1052 }
1053
1054 ++num_full_dp_early_abort_;
1055 return true;
1056 }
1057 states.push_back(std::move(to_state));
1058 }
1059 }
1060 }
1061
1062 // We explored everything, no way to serve this with only k routes!
1063 return false;
1064}
1065
1066namespace {
1067
1068// Represents the relation tail_coeff.X + head_coeff.Y \in [lhs, rhs] between
1069// the X and Y expressions associated with the tail and head of the given arc,
1070// respectively, and the given dimension (head_coeff is always positive).
1071//
1072// An "empty" struct with all fields set to 0 is used to represent no relation.
1073struct LocalRelation {
1074 IntegerValue tail_coeff = 0;
1075 IntegerValue head_coeff = 0;
1076 IntegerValue lhs;
1077 IntegerValue rhs;
1078
1079 bool empty() const { return tail_coeff == 0 && head_coeff == 0; }
1080 bool operator==(const LocalRelation& r) const {
1081 return tail_coeff == r.tail_coeff && head_coeff == r.head_coeff &&
1082 lhs == r.lhs && rhs == r.rhs;
1083 }
1084};
1085
1086IntegerVariable UniqueSharedVariable(const sat::Relation& r1,
1087 const sat::Relation& r2) {
1088 const IntegerVariable X[2] = {PositiveVariable(r1.expr.vars[0]),
1089 PositiveVariable(r1.expr.vars[1])};
1090 const IntegerVariable Y[2] = {PositiveVariable(r2.expr.vars[0]),
1091 PositiveVariable(r2.expr.vars[1])};
1092 DCHECK_NE(X[0], X[1]);
1093 DCHECK_NE(Y[0], Y[1]);
1094 if (X[0] == Y[0] && X[1] != Y[1]) return X[0];
1095 if (X[0] == Y[1] && X[1] != Y[0]) return X[0];
1096 if (X[1] == Y[0] && X[0] != Y[1]) return X[1];
1097 if (X[1] == Y[1] && X[0] != Y[0]) return X[1];
1098 return kNoIntegerVariable;
1099}
1100
1101class RouteRelationsBuilder {
1102 public:
1103 using HeadMinusTailBounds = RouteRelationsHelper::HeadMinusTailBounds;
1104
1105 RouteRelationsBuilder(
1106 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
1107 absl::Span<const Literal> literals,
1108 absl::Span<const AffineExpression> flat_node_dim_expressions,
1109 const ConditionalLinear2Bounds& binary_relation_repository)
1110 : num_nodes_(num_nodes),
1111 num_arcs_(tails.size()),
1112 tails_(tails),
1113 heads_(heads),
1114 literals_(literals),
1115 binary_relation_repository_(binary_relation_repository) {
1116 if (!flat_node_dim_expressions.empty()) {
1117 DCHECK_EQ(flat_node_dim_expressions.size() % num_nodes, 0);
1118 num_dimensions_ = flat_node_dim_expressions.size() / num_nodes;
1119 flat_node_dim_expressions_.assign(flat_node_dim_expressions.begin(),
1120 flat_node_dim_expressions.end());
1121 }
1122 }
1123
1124 int num_dimensions() const { return num_dimensions_; }
1125
1126 std::vector<AffineExpression>& flat_node_dim_expressions() {
1127 return flat_node_dim_expressions_;
1128 }
1129
1130 std::vector<HeadMinusTailBounds>& flat_arc_dim_relations() {
1131 return flat_arc_dim_relations_;
1132 }
1133
1134 std::vector<IntegerValue>& flat_shortest_path_lbs() {
1135 return flat_shortest_path_lbs_;
1136 }
1137
1138 bool BuildNodeExpressions() {
1139 if (flat_node_dim_expressions_.empty()) {
1140 // Step 1: find the number of dimensions (as the number of connected
1141 // components in the graph of binary relations), and find to which
1142 // dimension each variable belongs.
1143 ComputeDimensionOfEachVariable();
1144 if (num_dimensions_ == 0) return false;
1145
1146 // Step 2: find the variables which can be unambiguously associated with a
1147 // node and dimension.
1148 // - compute the indices of the binary relations which can be
1149 // unambiguously associated with the incoming and outgoing arcs of each
1150 // node, per dimension.
1151 ComputeAdjacentRelationsPerNodeAndDimension();
1152 // - find variable associations by using variables which are uniquely
1153 // shared by two adjacent relations of a node.
1154 std::queue<std::pair<int, int>> node_dim_pairs =
1155 ComputeVarAssociationsFromSharedVariableOfAdjacentRelations();
1156 if (node_dim_pairs.empty()) return false;
1157 // - find more variable associations by using arcs from nodes with
1158 // an associated variable, whose other end has no associated variable, and
1159 // where only one variable can be associated with it.
1160 ComputeVarAssociationsFromRelationsWithSingleFreeVar(node_dim_pairs);
1161 }
1162 return true;
1163 }
1164
1165 void BuildArcRelations(Model* model) {
1166 // Step 3: compute the relation for each arc and dimension, now that the
1167 // variables associated with each node and dimension are known.
1168 ComputeArcRelations(model);
1169 }
1170
1171 // We only consider lower bound for now.
1172 //
1173 // TODO(user): can we be smarter and compute a "scheduling" like lower bound
1174 // that exploit the [lb, ub] of the node expression even more?
1175 void BuildShortestPaths(Model* model) {
1176 if (num_nodes_ >= 100) return;
1177
1178 auto& integer_trail = *model->GetOrCreate<IntegerTrail>();
1179 std::vector<IntegerValue> flat_trivial_lbs(num_nodes_ * num_nodes_);
1180 const auto trivial = [this, &flat_trivial_lbs](int i,
1181 int j) -> IntegerValue& {
1182 CHECK_NE(i, j);
1183 CHECK_NE(i, 0);
1184 CHECK_NE(j, 0);
1185 return flat_trivial_lbs[i * num_nodes_ + j];
1186 };
1187 flat_shortest_path_lbs_.resize(num_nodes_ * num_nodes_ * num_dimensions_,
1188 std::numeric_limits<IntegerValue>::max());
1189
1190 for (int dim = 0; dim < num_dimensions_; ++dim) {
1191 // We can never beat trivial bound relations, starts by filling them.
1192 for (int i = 1; i < num_nodes_; ++i) {
1193 const AffineExpression& i_expr = node_expression(i, dim);
1194 const IntegerValue i_ub = integer_trail.LevelZeroUpperBound(i_expr);
1195 for (int j = 1; j < num_nodes_; ++j) {
1196 if (i == j) continue;
1197 const AffineExpression& j_expr = node_expression(j, dim);
1198 trivial(i, j) = integer_trail.LevelZeroLowerBound(j_expr) - i_ub;
1199 }
1200 }
1201
1202 // Starts by the known arc relations.
1203 const auto path = [this](int dim, int i, int j) -> IntegerValue& {
1204 return flat_shortest_path_lbs_[dim * num_nodes_ * num_nodes_ +
1205 i * num_nodes_ + j];
1206 };
1207 for (int arc = 0; arc < tails_.size(); ++arc) {
1208 const int tail = tails_[arc];
1209 const int head = heads_[arc];
1210 if (tail == 0 || head == 0 || tail == head) continue;
1211 path(dim, tail, head) =
1212 std::max(trivial(tail, head),
1213 flat_arc_dim_relations_[arc * num_dimensions_ + dim].lhs);
1214 }
1215
1216 // We do floyd-warshall to complete them into shortest path.
1217 for (int k = 1; k < num_nodes_; ++k) {
1218 for (int i = 1; i < num_nodes_; ++i) {
1219 if (i == k) continue;
1220 for (int j = 1; j < num_nodes_; ++j) {
1221 if (j == k || j == i) continue;
1222 path(dim, i, j) =
1223 std::min(path(dim, i, j),
1224 std::max(trivial(i, j),
1225 CapAddI(path(dim, i, k), path(dim, k, j))));
1226 }
1227 }
1228 }
1229 }
1230 }
1231
1232 private:
1233 AffineExpression& node_expression(int node, int dimension) {
1234 return flat_node_dim_expressions_[node * num_dimensions_ + dimension];
1235 };
1236
1237 void ProcessNewArcConstantRelation(int arc_index, int dimension,
1238 IntegerValue head_minus_tail) {
1239 HeadMinusTailBounds& relation =
1240 flat_arc_dim_relations_[arc_index * num_dimensions_ + dimension];
1241 relation.lhs = std::max(relation.lhs, head_minus_tail);
1242 relation.rhs = std::min(relation.rhs, head_minus_tail);
1243 }
1244
1245 void ProcessNewArcRelation(int arc_index, int dimension, LocalRelation r) {
1246 if (r.empty()) return;
1247 if (r.head_coeff < 0) {
1248 r.tail_coeff = -r.tail_coeff;
1249 r.head_coeff = -r.head_coeff;
1250 r.lhs = -r.lhs;
1251 r.rhs = -r.rhs;
1252 std::swap(r.lhs, r.rhs);
1253 }
1254 if (r.head_coeff != 1 || r.tail_coeff != -1) return;
1255
1256 // Combine the relation information.
1257 HeadMinusTailBounds& relation =
1258 flat_arc_dim_relations_[arc_index * num_dimensions_ + dimension];
1259 relation.lhs = std::max(relation.lhs, r.lhs);
1260 relation.rhs = std::min(relation.rhs, r.rhs);
1261 }
1262
1263 void ComputeDimensionOfEachVariable() {
1264 // Step 1: find the number of dimensions (as the number of connected
1265 // components in the graph of binary relations).
1266 // TODO(user): see if we can use a shared
1267 // DenseConnectedComponentsFinder with one node per variable of the whole
1268 // model instead.
1269 ConnectedComponentsFinder<IntegerVariable> cc_finder;
1270 for (int i = 0; i < num_arcs_; ++i) {
1271 if (tails_[i] == heads_[i]) continue;
1272 num_arcs_per_literal_[literals_[i]]++;
1273 for (const int relation_index :
1274 binary_relation_repository_.IndicesOfRelationsEnforcedBy(
1275 literals_[i])) {
1276 const auto& r = binary_relation_repository_.relation(relation_index);
1277 DCHECK_NE(r.expr.vars[0], kNoIntegerVariable);
1278 DCHECK_NE(r.expr.vars[1], kNoIntegerVariable);
1279 cc_finder.AddEdge(PositiveVariable(r.expr.vars[0]),
1280 PositiveVariable(r.expr.vars[1]));
1281 }
1282 }
1283 const std::vector<std::vector<IntegerVariable>> connected_components =
1284 cc_finder.FindConnectedComponents();
1285 for (int i = 0; i < connected_components.size(); ++i) {
1286 for (const IntegerVariable var : connected_components[i]) {
1287 dimension_by_var_[GetPositiveOnlyIndex(var)] = i;
1288 }
1289 }
1290 num_dimensions_ = connected_components.size();
1291 }
1292
1293 void ComputeAdjacentRelationsPerNodeAndDimension() {
1294 adjacent_relation_indices_ = std::vector<std::vector<std::vector<int>>>(
1295 num_dimensions_, std::vector<std::vector<int>>(num_nodes_));
1296 for (int i = 0; i < num_arcs_; ++i) {
1297 if (tails_[i] == heads_[i]) continue;
1298 // If a literal is associated with more than one arc, a relation
1299 // associated with this literal cannot be unambiguously associated with an
1300 // arc.
1301 if (num_arcs_per_literal_[literals_[i]] > 1) continue;
1302 for (const int relation_index :
1303 binary_relation_repository_.IndicesOfRelationsEnforcedBy(
1304 literals_[i])) {
1305 const auto& r = binary_relation_repository_.relation(relation_index);
1306 DCHECK_NE(r.expr.vars[0], kNoIntegerVariable);
1307 DCHECK_NE(r.expr.vars[1], kNoIntegerVariable);
1308 const int dimension = dimension_by_var_[GetPositiveOnlyIndex(
1309 PositiveVariable(r.expr.vars[0]))];
1310 adjacent_relation_indices_[dimension][tails_[i]].push_back(
1311 relation_index);
1312 adjacent_relation_indices_[dimension][heads_[i]].push_back(
1313 relation_index);
1314 }
1315 }
1316 }
1317
1318 // Returns the (node, dimension) pairs for which a variable association has
1319 // been found.
1320 std::queue<std::pair<int, int>>
1321 ComputeVarAssociationsFromSharedVariableOfAdjacentRelations() {
1322 flat_node_dim_expressions_ = std::vector<AffineExpression>(
1323 num_nodes_ * num_dimensions_, AffineExpression());
1324 std::queue<std::pair<int, int>> result;
1325 for (int n = 0; n < num_nodes_; ++n) {
1326 for (int d = 0; d < num_dimensions_; ++d) {
1327 // If two relations on incoming or outgoing arcs of n have a unique
1328 // shared variable, such as in the case of l <-X,Y-> n <-Y,Z-> m (i.e. a
1329 // relation between X and Y on the (l,n) arc, and a relation between Y
1330 // and Z on the (n,m) arc), then this variable is necessarily associated
1331 // with n.
1332 for (const int r1_index : adjacent_relation_indices_[d][n]) {
1333 const auto& r1 = binary_relation_repository_.relation(r1_index);
1334 for (const int r2_index : adjacent_relation_indices_[d][n]) {
1335 if (r1_index == r2_index) continue;
1336 const auto& r2 = binary_relation_repository_.relation(r2_index);
1337 const IntegerVariable shared_var = UniqueSharedVariable(r1, r2);
1338 if (shared_var == kNoIntegerVariable) continue;
1339 DCHECK_EQ(dimension_by_var_[GetPositiveOnlyIndex(shared_var)], d);
1340 AffineExpression& node_expr = node_expression(n, d);
1341 if (node_expr.IsConstant()) {
1342 result.push({n, d});
1343 } else if (node_expr.var != shared_var) {
1344 VLOG(2) << "Several vars per node and dimension in route with "
1345 << num_nodes_ << " nodes and " << num_arcs_ << " arcs";
1346 return {};
1347 }
1348 node_expr = shared_var;
1349 }
1350 }
1351 }
1352 }
1353 return result;
1354 }
1355
1356 void ComputeVarAssociationsFromRelationsWithSingleFreeVar(
1357 std::queue<std::pair<int, int>>& node_dim_pairs_to_process) {
1358 std::vector<std::vector<int>> adjacent_arcs_per_node(num_nodes_);
1359 for (int i = 0; i < num_arcs_; ++i) {
1360 if (tails_[i] == heads_[i]) continue;
1361 adjacent_arcs_per_node[tails_[i]].push_back(i);
1362 adjacent_arcs_per_node[heads_[i]].push_back(i);
1363 }
1364 while (!node_dim_pairs_to_process.empty()) {
1365 const auto [node, dimension] = node_dim_pairs_to_process.front();
1366 const AffineExpression node_expr = node_expression(node, dimension);
1367 DCHECK(!node_expr.IsConstant());
1368 node_dim_pairs_to_process.pop();
1369 for (const int arc_index : adjacent_arcs_per_node[node]) {
1370 const int tail = tails_[arc_index];
1371 const int head = heads_[arc_index];
1372 DCHECK(node == tail || node == head);
1373 int other_node = node == tail ? head : tail;
1374 if (!node_expression(other_node, dimension).IsConstant()) {
1375 continue;
1376 }
1377 IntegerVariable candidate_var = kNoIntegerVariable;
1378 bool candidate_var_is_unique = true;
1379 for (const int relation_index :
1380 binary_relation_repository_.IndicesOfRelationsEnforcedBy(
1381 literals_[arc_index])) {
1382 const auto& r = binary_relation_repository_.relation(relation_index);
1383 DCHECK_NE(r.expr.vars[0], kNoIntegerVariable);
1384 DCHECK_NE(r.expr.vars[1], kNoIntegerVariable);
1385 const IntegerVariable var0 = PositiveVariable(r.expr.vars[0]);
1386 const IntegerVariable var1 = PositiveVariable(r.expr.vars[1]);
1387 if (var0 == node_expr.var) {
1388 if (candidate_var != kNoIntegerVariable && candidate_var != var1) {
1389 candidate_var_is_unique = false;
1390 break;
1391 }
1392 candidate_var = var1;
1393 }
1394 if (var1 == node_expr.var) {
1395 if (candidate_var != kNoIntegerVariable && candidate_var != var0) {
1396 candidate_var_is_unique = false;
1397 break;
1398 }
1399 candidate_var = var0;
1400 }
1401 }
1402 if (candidate_var != kNoIntegerVariable && candidate_var_is_unique) {
1403 node_expression(other_node, dimension) = candidate_var;
1404 node_dim_pairs_to_process.push({other_node, dimension});
1405 }
1406 }
1407 }
1408 }
1409
1410 void ComputeArcRelations(Model* model) {
1411 auto& binary_implication_graph =
1412 *model->GetOrCreate<BinaryImplicationGraph>();
1413 const auto& integer_encoder = *model->GetOrCreate<IntegerEncoder>();
1414 const auto& trail = *model->GetOrCreate<Trail>();
1415 const auto& integer_trail = *model->GetOrCreate<IntegerTrail>();
1416 const auto& root_level_bounds =
1417 *model->GetOrCreate<RootLevelLinear2Bounds>();
1418 const auto& implied_bounds = *model->GetOrCreate<ImpliedBounds>();
1419 DCHECK_EQ(trail.CurrentDecisionLevel(), 0);
1420
1421 flat_arc_dim_relations_ = std::vector<HeadMinusTailBounds>(
1422 num_arcs_ * num_dimensions_, HeadMinusTailBounds());
1423 binary_implication_graph.ResetWorkDone();
1424 for (int i = 0; i < num_arcs_; ++i) {
1425 const int tail = tails_[i];
1426 const int head = heads_[i];
1427 if (tail == head) continue;
1428 // The literals directly or indirectly implied by the arc literal,
1429 // including the arc literal itself.
1430 const Literal arc_literal_singleton[1] = {literals_[i]};
1431 absl::Span<const Literal> implied_literals =
1432 binary_implication_graph.WorkDone() < 1e8
1433 ? binary_implication_graph.GetAllImpliedLiterals(literals_[i])
1434 : absl::MakeSpan(arc_literal_singleton);
1435 // The integer view of the implied literals (resp. of their negation), and
1436 // the variable lower bounds implied directly or indirectly by the arc
1437 // literal.
1438 absl::flat_hash_set<IntegerVariable> implied_views;
1439 absl::flat_hash_set<IntegerVariable> negated_implied_views;
1440 absl::flat_hash_map<IntegerVariable, IntegerValue> implied_lower_bounds;
1441 for (const Literal implied : implied_literals) {
1442 implied_views.insert(integer_encoder.GetLiteralView(implied));
1443 negated_implied_views.insert(
1444 integer_encoder.GetLiteralView(implied.Negated()));
1445 for (const auto& [var, lb] :
1446 integer_encoder.GetIntegerLiterals(implied)) {
1447 auto it = implied_lower_bounds.find(var);
1448 if (it == implied_lower_bounds.end()) {
1449 implied_lower_bounds[var] = lb;
1450 } else {
1451 it->second = std::max(it->second, lb);
1452 }
1453 }
1454 }
1455 // Returns the bounds of the given expression, assuming that all the
1456 // literals implied by the arc literal are 1.
1457 auto get_bounds = [&](const NodeExpression& expr) {
1458 if (expr.var != kNoIntegerVariable) {
1459 if (implied_views.contains(expr.var)) {
1460 IntegerValue constant_value = expr.coeff + expr.offset;
1461 return std::make_pair(constant_value, constant_value);
1462 }
1463 if (implied_views.contains(NegationOf(expr.var))) {
1464 IntegerValue constant_value = -expr.coeff + expr.offset;
1465 return std::make_pair(constant_value, constant_value);
1466 }
1467 if (negated_implied_views.contains(expr.var) ||
1468 negated_implied_views.contains(NegationOf(expr.var))) {
1469 return std::make_pair(expr.offset, expr.offset);
1470 }
1471 }
1472 const AffineExpression e(expr.var, expr.coeff, expr.offset);
1473 IntegerValue lb = integer_trail.LevelZeroLowerBound(e);
1474 auto it = implied_lower_bounds.find(e.var);
1475 if (it != implied_lower_bounds.end()) {
1476 lb = std::max(lb, e.ValueAt(it->second));
1477 }
1478 IntegerValue ub = integer_trail.LevelZeroUpperBound(e);
1479 it = implied_lower_bounds.find(NegationOf(e.var));
1480 if (it != implied_lower_bounds.end()) {
1481 ub = std::min(ub, e.ValueAt(-it->second));
1482 }
1483 return std::make_pair(lb, ub);
1484 };
1485 // Changes `expr` to a constant expression if possible, and returns true.
1486 // Otherwise, returns false.
1487 auto to_constant = [&](NodeExpression& expr) {
1488 if (expr.var == kNoIntegerVariable) return true;
1489 if (integer_trail.IsFixed(expr.var)) {
1490 expr.offset += integer_trail.FixedValue(expr.var) * expr.coeff;
1491 expr.var = kNoIntegerVariable;
1492 expr.coeff = 0;
1493 return true;
1494 }
1495 const auto [min, max] = get_bounds(expr);
1496 if (min == max) {
1497 expr = NodeExpression(kNoIntegerVariable, 0, min);
1498 return true;
1499 }
1500 return false;
1501 };
1502 for (int dimension = 0; dimension < num_dimensions_; ++dimension) {
1503 NodeExpression tail_expr(node_expression(tail, dimension));
1504 NodeExpression head_expr(node_expression(head, dimension));
1505 NodeExpression tail_const_expr = tail_expr;
1506 NodeExpression head_const_expr = head_expr;
1507 const bool tail_const = to_constant(tail_const_expr);
1508 const bool head_const = to_constant(head_const_expr);
1509 if (tail_const && head_const) {
1510 ProcessNewArcConstantRelation(
1511 i, dimension, head_const_expr.offset - tail_const_expr.offset);
1512 continue;
1513 }
1514 for (const Literal implied_lit : implied_literals) {
1515 if (tail_const || head_const) {
1516 const IntegerVariable var =
1517 head_const ? tail_expr.var : head_expr.var;
1518 DCHECK(var != kNoIntegerVariable);
1519 auto [lb, ub] = implied_bounds.GetImpliedBounds(implied_lit, var);
1520 if (lb != kMinIntegerValue || ub != kMaxIntegerValue) {
1521 lb = std::max(lb, integer_trail.LevelZeroLowerBound(var));
1522 ub = std::min(ub, integer_trail.LevelZeroUpperBound(var));
1523 Relation r = {
1524 .enforcement = implied_lit,
1525 .expr = LinearExpression2(var, kNoIntegerVariable, 1, 0),
1526 .lhs = lb,
1527 .rhs = ub};
1528 DCHECK(VariableIsPositive(var));
1529 if (var == tail_const_expr.var) {
1530 std::swap(r.expr.vars[0], r.expr.vars[1]);
1531 std::swap(r.expr.coeffs[0], r.expr.coeffs[1]);
1532 }
1533 ComputeArcRelation(i, dimension, tail_const_expr, head_const_expr,
1534 r, integer_trail);
1535 }
1536 }
1537 if (tail_expr.var != kNoIntegerVariable &&
1538 head_expr.var != kNoIntegerVariable) {
1539 for (const int relation_index :
1540 binary_relation_repository_.IndicesOfRelationsEnforcedBy(
1541 implied_lit)) {
1542 Relation r = binary_relation_repository_.relation(relation_index);
1543 r.expr.MakeVariablesPositive();
1544 // Try to match the relation variables with the node expression
1545 // variables. First swap the relation terms if needed (this does
1546 // not change the relation bounds).
1547 if (r.expr.vars[0] == head_expr.var ||
1548 r.expr.vars[1] == tail_expr.var) {
1549 std::swap(r.expr.vars[0], r.expr.vars[1]);
1550 std::swap(r.expr.coeffs[0], r.expr.coeffs[1]);
1551 }
1552 // If the relation and node expression variables do not match, we
1553 // cannot use this relation for this arc.
1554 if (!((tail_expr.var == r.expr.vars[0] &&
1555 head_expr.var == r.expr.vars[1]) ||
1556 (tail_expr.var == r.expr.vars[1] &&
1557 head_expr.var == r.expr.vars[0]))) {
1558 continue;
1559 }
1560 ComputeArcRelation(i, dimension, tail_expr, head_expr, r,
1561 integer_trail);
1562 }
1563 }
1564 }
1565
1566 // Check if we can use non-enforced relations to improve the relations.
1567 if (!tail_expr.IsEmpty() && !head_expr.IsEmpty()) {
1568 for (const auto& [expr, lb, ub] :
1569 root_level_bounds.GetAllBoundsContainingVariables(
1570 tail_expr.var, head_expr.var)) {
1571 DCHECK_EQ(expr.vars[0], tail_expr.var);
1572 DCHECK_EQ(expr.vars[1], head_expr.var);
1573 ComputeArcRelation(i, dimension, tail_expr, head_expr,
1574 Relation{Literal(kNoLiteralIndex), expr, lb, ub},
1575 integer_trail);
1576 }
1577 }
1578
1579 // Check if we can use the default relation to improve the relations.
1580 // Compute the bounds of X_head - X_tail as [lb(X_head) - ub(X_tail),
1581 // ub(X_head) - lb(X_tail)], which is always true. Note that by our
1582 // overflow precondition, the difference of any two variable bounds
1583 // should fit on an int64_t.
1584 std::pair<IntegerValue, IntegerValue> bounds;
1585 if (head_expr.var == tail_expr.var) {
1586 const NodeExpression offset_expr(head_expr.var,
1587 head_expr.coeff - tail_expr.coeff,
1588 head_expr.offset - tail_expr.offset);
1589 bounds = get_bounds(offset_expr);
1590 } else {
1591 const auto [tail_min, tail_max] = get_bounds(tail_expr);
1592 const auto [head_min, head_max] = get_bounds(head_expr);
1593 bounds = {head_min - tail_max, head_max - tail_min};
1594 }
1595 ProcessNewArcRelation(i, dimension,
1596 {.tail_coeff = -1,
1597 .head_coeff = 1,
1598 .lhs = bounds.first,
1599 .rhs = bounds.second});
1600 }
1601 }
1602 }
1603
1604 // Infers a relation between two given expressions which is equivalent to a
1605 // given relation `r` between the variables used in these expressions.
1606 void ComputeArcRelation(int arc_index, int dimension,
1607 const NodeExpression& tail_expr,
1608 const NodeExpression& head_expr, sat::Relation r,
1609 const IntegerTrail& integer_trail) {
1610 DCHECK(
1611 (r.expr.vars[0] == tail_expr.var && r.expr.vars[1] == head_expr.var) ||
1612 (r.expr.vars[0] == head_expr.var && r.expr.vars[1] == tail_expr.var));
1613 if (r.expr.vars[0] != tail_expr.var) {
1614 std::swap(r.expr.vars[0], r.expr.vars[1]);
1615 std::swap(r.expr.coeffs[0], r.expr.coeffs[1]);
1616 }
1617 if (r.expr.coeffs[0] == 0 || tail_expr.coeff == 0) {
1618 LocalRelation result = ComputeArcUnaryRelation(
1619 head_expr, tail_expr, r.expr.coeffs[1], r.lhs, r.rhs);
1620 std::swap(result.tail_coeff, result.head_coeff);
1621 ProcessNewArcRelation(arc_index, dimension, result);
1622 return;
1623 }
1624 if (r.expr.coeffs[1] == 0 || head_expr.coeff == 0) {
1625 ProcessNewArcRelation(
1626 arc_index, dimension,
1627 ComputeArcUnaryRelation(tail_expr, head_expr, r.expr.coeffs[0], r.lhs,
1628 r.rhs));
1629 return;
1630 }
1631 const auto [lhs, rhs] =
1632 GetDifferenceBounds(tail_expr, head_expr, r,
1633 {integer_trail.LowerBound(tail_expr.var),
1634 integer_trail.UpperBound(tail_expr.var)},
1635 {integer_trail.LowerBound(head_expr.var),
1636 integer_trail.UpperBound(head_expr.var)});
1637 ProcessNewArcRelation(
1638 arc_index, dimension,
1639 {.tail_coeff = -1, .head_coeff = 1, .lhs = lhs, .rhs = rhs});
1640 }
1641
1642 // Returns a relation between two given expressions which is equivalent to a
1643 // given relation "coeff * X \in [lhs, rhs]", where X is the variable used in
1644 // `tail_expression` (or an empty relation if this is not possible).
1645 LocalRelation ComputeArcUnaryRelation(const NodeExpression& tail_expr,
1646 const NodeExpression& head_expr,
1647 IntegerValue coeff, IntegerValue lhs,
1648 IntegerValue rhs) {
1649 DCHECK_NE(coeff, 0);
1650 // A relation a * X \in [lhs, rhs] is equivalent to a (k.a/A) * (A.X+α) + k'
1651 // * (B.Y+β) \in [k.lhs+ɣ, k.rhs+ɣ] relation between the A.X+α and B.Y+β
1652 // terms if A != 0, if the division is exact, and if k' = 0 or B = 0, where
1653 // ɣ=(k.a/A)*α+k'*β. The smallest k > 0 such that k.a/A is integer is
1654 // |A|/gcd(a, A).
1655 if (tail_expr.coeff == 0) return {};
1656 const int64_t k = std::abs(tail_expr.coeff.value()) /
1657 std::gcd(tail_expr.coeff.value(), coeff.value());
1658 // If B = 0 we can use any value for k' = head_coeff. We use the opposite of
1659 // tail_coeff to get a relation with +1/-1 coefficients if possible.
1660 const IntegerValue tail_coeff = (k * coeff) / tail_expr.coeff;
1661 const IntegerValue head_coeff = head_expr.coeff != 0 ? 0 : -tail_coeff;
1662 const IntegerValue domain_offset =
1663 CapAddI(CapProdI(tail_coeff, tail_expr.offset),
1664 CapProdI(head_coeff, head_expr.offset));
1665 if (AtMinOrMaxInt64I(domain_offset)) {
1666 return {};
1667 }
1668 const IntegerValue lhs_offset = CapAddI(CapProdI(k, lhs), domain_offset);
1669 const IntegerValue rhs_offset = CapAddI(CapProdI(k, rhs), domain_offset);
1670 if (AtMinOrMaxInt64I(lhs_offset) || AtMinOrMaxInt64I(rhs_offset)) {
1671 return {};
1672 }
1673 return {
1674 .tail_coeff = tail_coeff,
1675 .head_coeff = head_coeff,
1676 .lhs = lhs_offset,
1677 .rhs = rhs_offset,
1678 };
1679 }
1680
1681 const int num_nodes_;
1682 const int num_arcs_;
1683 absl::Span<const int> tails_;
1684 absl::Span<const int> heads_;
1685 absl::Span<const Literal> literals_;
1686 const ConditionalLinear2Bounds& binary_relation_repository_;
1687
1688 int num_dimensions_;
1689 absl::flat_hash_map<PositiveOnlyIndex, int> dimension_by_var_;
1690 absl::flat_hash_map<Literal, int> num_arcs_per_literal_;
1691
1692 // The indices of the binary relations associated with the incoming and
1693 // outgoing arcs of each node, per dimension.
1694 std::vector<std::vector<std::vector<int>>> adjacent_relation_indices_;
1695
1696 // See comments for the same fields in RouteRelationsHelper() that this
1697 // builder creates.
1698 std::vector<AffineExpression> flat_node_dim_expressions_;
1699 std::vector<HeadMinusTailBounds> flat_arc_dim_relations_;
1700 std::vector<IntegerValue> flat_shortest_path_lbs_;
1701};
1702
1703} // namespace
1704
1706 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
1707 absl::Span<const Literal> literals,
1708 const ConditionalLinear2Bounds& binary_relation_repository) {
1710 RouteRelationsBuilder builder(num_nodes, tails, heads, literals, {},
1711 binary_relation_repository);
1712 if (!builder.BuildNodeExpressions()) return result;
1713 result.num_dimensions = builder.num_dimensions();
1715 std::move(builder.flat_node_dim_expressions());
1716 return result;
1717}
1718
1719namespace {
1720// Returns a lower bound of y_expr - x_expr.
1721IntegerValue GetDifferenceLowerBound(
1722 const NodeExpression& x_expr, const NodeExpression& y_expr,
1723 const sat::Relation& r,
1724 const std::pair<IntegerValue, IntegerValue>& x_var_bounds,
1725 const std::pair<IntegerValue, IntegerValue>& y_var_bounds) {
1726 // Let's note x_expr = A.x+α, y_expr = B.x+β, and r = "b.y-a.x in [lhs, rhs]".
1727 // We have x in [lb(x), ub(x)] and y in [lb(y), ub(y)], and we want to find
1728 // the lower bound of y_expr - x_expr = lb(B.x - A.y) + β - α. We have:
1729 // B.y - A.x = [(B-k.b).y + k.b.y] - [(A-k.a).x + k.a.x]
1730 // = (B+k.b).y + (k.a-A).x + k.(b.y - a.x)
1731 // for any k. A lower bound on the first term is (B-k.b).lb(y) if B-k.b >= 0,
1732 // and (B-k.b).ub(y) otherwise. This can be written as:
1733 // (B-k.b).y >= max(0, B-k.b).lb(y) + min(0, B-k.b).ub(y)
1734 // Similarly:
1735 // (k.a-A).x >= max(0, k.a-A).lb(x) + min(0, k.a-A).ub(x)
1736 // And:
1737 // k.(b.y - a.x) >= max(0, k).lhs + min(0, k).rhs
1738 // Hence we get:
1739 // B.y - A.x >= max(0, B-k.b).lb(y) + min(0, B-k.b).ub(y) +
1740 // max(0, k.a-A).lb(x) + min(0, k.a-A).ub(x) +
1741 // max(0, k).lhs + min(0, k).rhs
1742 // The derivative of this expression with respect to k is piecewise constant
1743 // and can only change value around 0, A/a, and B/b. Hence we can compute an
1744 // "interesting" lower bound by taking the maximum of this expression around
1745 // these points, instead of over all k values.
1746 // TODO(user): overflows could happen if the node expressions are
1747 // provided by the user in the model proto.
1748 auto lower_bound = [&](IntegerValue k) {
1749 const IntegerValue y_coeff = y_expr.coeff - k * r.expr.coeffs[1];
1750 const IntegerValue x_coeff = k * (-r.expr.coeffs[0]) - x_expr.coeff;
1751 return y_coeff * (y_coeff >= 0 ? y_var_bounds.first : y_var_bounds.second) +
1752 x_coeff * (x_coeff >= 0 ? x_var_bounds.first : x_var_bounds.second) +
1753 k * (k >= 0 ? r.lhs : r.rhs);
1754 };
1755 const IntegerValue k_x =
1756 MathUtil::FloorOfRatio(x_expr.coeff, -r.expr.coeffs[0]);
1757 const IntegerValue k_y =
1758 MathUtil::FloorOfRatio(y_expr.coeff, r.expr.coeffs[1]);
1759 IntegerValue result = lower_bound(0);
1760 result = std::max(result, lower_bound(k_x));
1761 result = std::max(result, lower_bound(k_x + 1));
1762 result = std::max(result, lower_bound(k_y));
1763 result = std::max(result, lower_bound(k_y + 1));
1764 return result + y_expr.offset - x_expr.offset;
1765}
1766} // namespace
1767
1768std::pair<IntegerValue, IntegerValue> GetDifferenceBounds(
1769 const NodeExpression& x_expr, const NodeExpression& y_expr,
1770 const sat::Relation& r,
1771 const std::pair<IntegerValue, IntegerValue>& x_var_bounds,
1772 const std::pair<IntegerValue, IntegerValue>& y_var_bounds) {
1773 DCHECK_EQ(x_expr.var, r.expr.vars[0]);
1774 DCHECK_EQ(y_expr.var, r.expr.vars[1]);
1775 DCHECK_NE(x_expr.var, kNoIntegerVariable);
1776 DCHECK_NE(y_expr.var, kNoIntegerVariable);
1777 DCHECK_NE(x_expr.coeff, 0);
1778 DCHECK_NE(y_expr.coeff, 0);
1779 DCHECK_NE(r.expr.coeffs[0], 0);
1780 DCHECK_NE(r.expr.coeffs[1], 0);
1781 const IntegerValue lb =
1782 GetDifferenceLowerBound(x_expr, y_expr, r, x_var_bounds, y_var_bounds);
1783 const IntegerValue ub = -GetDifferenceLowerBound(
1784 x_expr.Negated(), y_expr.Negated(), r, x_var_bounds, y_var_bounds);
1785 return {lb, ub};
1786}
1787
1788std::unique_ptr<RouteRelationsHelper> RouteRelationsHelper::Create(
1789 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
1790 absl::Span<const Literal> literals,
1791 absl::Span<const AffineExpression> flat_node_dim_expressions,
1792 const ConditionalLinear2Bounds& binary_relation_repository, Model* model) {
1793 CHECK(model != nullptr);
1794 if (flat_node_dim_expressions.empty()) return nullptr;
1795 RouteRelationsBuilder builder(num_nodes, tails, heads, literals,
1796 flat_node_dim_expressions,
1797 binary_relation_repository);
1798 builder.BuildArcRelations(model);
1799 builder.BuildShortestPaths(model);
1800 std::unique_ptr<RouteRelationsHelper> helper(new RouteRelationsHelper(
1801 builder.num_dimensions(), std::move(builder.flat_node_dim_expressions()),
1802 std::move(builder.flat_arc_dim_relations()),
1803 std::move(builder.flat_shortest_path_lbs())));
1804 if (VLOG_IS_ON(2)) helper->LogStats();
1805 return helper;
1806}
1807
1809 int num_dimensions, std::vector<AffineExpression> flat_node_dim_expressions,
1810 std::vector<HeadMinusTailBounds> flat_arc_dim_relations,
1811 std::vector<IntegerValue> flat_shortest_path_lbs)
1812 : num_nodes_(flat_node_dim_expressions.size() / num_dimensions),
1813 num_dimensions_(num_dimensions),
1814 flat_node_dim_expressions_(std::move(flat_node_dim_expressions)),
1815 flat_arc_dim_relations_(std::move(flat_arc_dim_relations)),
1816 flat_shortest_path_lbs_(std::move(flat_shortest_path_lbs)) {
1817 DCHECK_GE(num_dimensions_, 1);
1818 DCHECK_EQ(flat_node_dim_expressions_.size(), num_nodes_ * num_dimensions_);
1819}
1820
1822 int arc, int dimension, bool negate_expressions) const {
1823 // TODO(user): If level zero bounds got tighter since we precomputed the
1824 // relation, we might want to recompute it again.
1825 const auto& r = GetArcRelation(arc, dimension);
1826 return negate_expressions ? -r.rhs : r.lhs;
1827}
1828
1830 absl::Span<const int> sorted_arc_indices) {
1831 int new_size = 0;
1832 const int num_arcs = this->num_arcs();
1833 for (int i = 0; i < num_arcs; ++i) {
1834 if (!sorted_arc_indices.empty() && sorted_arc_indices.front() == i) {
1835 sorted_arc_indices.remove_prefix(1);
1836 continue;
1837 }
1838 for (int d = 0; d < num_dimensions_; ++d) {
1839 flat_arc_dim_relations_[new_size++] =
1840 flat_arc_dim_relations_[i * num_dimensions_ + d];
1841 }
1842 }
1843 flat_arc_dim_relations_.resize(new_size);
1844}
1845
1846void RouteRelationsHelper::LogStats() const {
1847 const int num_nodes = this->num_nodes();
1848 const int num_arcs = this->num_arcs();
1849 LOG(INFO) << "Route with " << num_nodes << " nodes and " << num_arcs
1850 << " arcs";
1851 for (int d = 0; d < num_dimensions_; ++d) {
1852 int num_vars = 0;
1853 int num_relations = 0;
1854 for (int i = 0; i < num_nodes; ++i) {
1855 if (!GetNodeExpression(i, d).IsConstant()) ++num_vars;
1856 }
1857 for (int i = 0; i < num_arcs; ++i) {
1858 if (GetArcRelation(i, d).lhs != kMinIntegerValue ||
1859 GetArcRelation(i, d).rhs != kMaxIntegerValue) {
1860 ++num_relations;
1861 }
1862 }
1863 LOG(INFO) << "dimension " << d << ": " << num_vars << " vars and "
1864 << num_relations << " relations";
1865 }
1866}
1867
1868namespace {
1869// Converts a literal index in a model proto to a Literal.
1870Literal ToLiteral(int lit) {
1871 return Literal(BooleanVariable(PositiveRef(lit)), RefIsPositive(lit));
1872}
1873
1874// Converts a variable index in a NodeVariables proto to an IntegerVariable.
1875IntegerVariable ToPositiveIntegerVariable(int i) {
1876 return IntegerVariable(PositiveRef(i) << 1);
1877}
1878
1879// Converts an IntegerVariable to variable indices in a NodeVariables proto.
1880int ToNodeVariableIndex(IntegerVariable var) {
1881 DCHECK(VariableIsPositive(var));
1882 return var.value() >> 1;
1883}
1884
1885// Returns a repository containing a partial view (i.e. without coefficients or
1886// domains) of the enforced linear constraints (of size 2 only) in `model`. This
1887// is the only information needed to infer the mapping from variables to nodes
1888// in routes constraints.
1889ConditionalLinear2Bounds ComputePartialConditionalLinear2Bounds(
1890 const CpModelProto& model) {
1891 Model empty_model;
1892 ConditionalLinear2Bounds repository(&empty_model);
1893 for (const ConstraintProto& ct : model.constraints()) {
1894 if (ct.constraint_case() != ConstraintProto::kLinear) continue;
1895 const absl::Span<const int> vars = ct.linear().vars();
1896 if (ct.enforcement_literal().size() != 1 || vars.size() != 2) continue;
1897 if (vars[0] == vars[1]) continue;
1898 repository.AddPartialRelation(ToLiteral(ct.enforcement_literal(0)),
1899 ToPositiveIntegerVariable(vars[0]),
1900 ToPositiveIntegerVariable(vars[1]));
1901 }
1902 repository.Build();
1903 return repository;
1904}
1905
1906// Returns the number of dimensions added to the constraint.
1907int MaybeFillRoutesConstraintNodeExpressions(
1908 RoutesConstraintProto& routes, const ConditionalLinear2Bounds& repository) {
1909 int max_node = 0;
1910 for (const int node : routes.tails()) {
1911 max_node = std::max(max_node, node);
1912 }
1913 for (const int node : routes.heads()) {
1914 max_node = std::max(max_node, node);
1915 }
1916 const int num_nodes = max_node + 1;
1917 std::vector<int> tails(routes.tails().begin(), routes.tails().end());
1918 std::vector<int> heads(routes.heads().begin(), routes.heads().end());
1919 std::vector<Literal> literals;
1920 literals.reserve(routes.literals_size());
1921 for (int lit : routes.literals()) {
1922 literals.push_back(ToLiteral(lit));
1923 }
1924
1926 num_nodes, tails, heads, literals, repository);
1927 if (cumuls.num_dimensions == 0) return 0;
1928
1929 for (int d = 0; d < cumuls.num_dimensions; ++d) {
1931 *routes.add_dimensions();
1932 for (int n = 0; n < num_nodes; ++n) {
1933 const AffineExpression expr = cumuls.GetNodeExpression(n, d);
1934 LinearExpressionProto& node_expr = *dimension.add_exprs();
1935 if (expr.var != kNoIntegerVariable) {
1936 node_expr.add_vars(ToNodeVariableIndex(PositiveVariable(expr.var)));
1937 node_expr.add_coeffs(VariableIsPositive(expr.var)
1938 ? expr.coeff.value()
1939 : -expr.coeff.value());
1940 }
1941 node_expr.set_offset(expr.constant.value());
1942 }
1943 }
1944 return cumuls.num_dimensions;
1945}
1946
1947} // namespace
1948
1950 const CpModelProto& input_model, CpModelProto& output_model) {
1951 std::vector<RoutesConstraintProto*> routes_to_fill;
1952 for (ConstraintProto& ct : *output_model.mutable_constraints()) {
1953 if (ct.constraint_case() != ConstraintProto::kRoutes) continue;
1954 if (!ct.routes().dimensions().empty()) continue;
1955 routes_to_fill.push_back(ct.mutable_routes());
1956 }
1957 if (routes_to_fill.empty()) return {0, 0};
1958
1959 int total_num_dimensions = 0;
1960 const ConditionalLinear2Bounds partial_repository =
1961 ComputePartialConditionalLinear2Bounds(input_model);
1962 for (RoutesConstraintProto* routes : routes_to_fill) {
1963 total_num_dimensions +=
1964 MaybeFillRoutesConstraintNodeExpressions(*routes, partial_repository);
1965 }
1966 return {static_cast<int>(routes_to_fill.size()), total_num_dimensions};
1967}
1968
1969namespace {
1970
1971class RoutingCutHelper {
1972 public:
1973 RoutingCutHelper(int num_nodes, bool is_route_constraint,
1974 absl::Span<const AffineExpression> flat_node_dim_expressions,
1975 absl::Span<const int> tails, absl::Span<const int> heads,
1976 absl::Span<const Literal> literals, Model* model)
1977 : num_nodes_(num_nodes),
1978 is_route_constraint_(is_route_constraint),
1979 tails_(tails.begin(), tails.end()),
1980 heads_(heads.begin(), heads.end()),
1981 literals_(literals.begin(), literals.end()),
1982 params_(*model->GetOrCreate<SatParameters>()),
1983 trail_(*model->GetOrCreate<Trail>()),
1984 integer_trail_(*model->GetOrCreate<IntegerTrail>()),
1985 binary_relation_repository_(
1986 *model->GetOrCreate<ConditionalLinear2Bounds>()),
1987 implied_bounds_(*model->GetOrCreate<ImpliedBounds>()),
1988 random_(model->GetOrCreate<ModelRandomGenerator>()),
1989 encoder_(model->GetOrCreate<IntegerEncoder>()),
1990 root_level_bounds_(*model->GetOrCreate<RootLevelLinear2Bounds>()),
1991 in_subset_(num_nodes, false),
1992 self_arc_literal_(num_nodes_),
1993 self_arc_lp_value_(num_nodes_),
1994 nodes_incoming_weight_(num_nodes_),
1995 nodes_outgoing_weight_(num_nodes_),
1996 min_outgoing_flow_helper_(num_nodes, tails_, heads_, literals_, model),
1997 route_relations_helper_(RouteRelationsHelper::Create(
1998 num_nodes, tails_, heads_, literals_, flat_node_dim_expressions,
1999 *model->GetOrCreate<ConditionalLinear2Bounds>(), model)) {}
2000
2001 int num_nodes() const { return num_nodes_; }
2002
2003 bool is_route_constraint() const { return is_route_constraint_; }
2004
2005 // Returns the arcs computed by InitializeForNewLpSolution().
2006 absl::Span<const ArcWithLpValue> relevant_arcs() const {
2007 return relevant_arcs_;
2008 }
2009
2010 // Returns the arcs computed in InitializeForNewLpSolution(), sorted by
2011 // decreasing lp_value.
2012 //
2013 // Note: If is_route_constraint() is true, we do not include arcs from/to
2014 // the depot here.
2015 absl::Span<const std::pair<int, int>> ordered_arcs() const {
2016 return ordered_arcs_;
2017 }
2018
2019 // This should be called each time a new LP solution is available, before
2020 // using the other methods.
2021 void InitializeForNewLpSolution(LinearConstraintManager* manager);
2022
2023 // Merge the relevant arcs (tail, head) and (head, tail) into a single (tail,
2024 // head) arc, with the sum of their lp_values.
2025 absl::Span<const ArcWithLpValue> SymmetrizedRelevantArcs() {
2026 symmetrized_relevant_arcs_ = relevant_arcs_;
2027 SymmetrizeArcs(&symmetrized_relevant_arcs_);
2028
2029 // We exclude arcs from/to the depot when we have a route constraint.
2030 if (is_route_constraint_) {
2031 int new_size = 0;
2032 for (const ArcWithLpValue& arc : symmetrized_relevant_arcs_) {
2033 if (arc.tail == 0 || arc.head == 0) continue;
2034 symmetrized_relevant_arcs_[new_size++] = arc;
2035 }
2036 symmetrized_relevant_arcs_.resize(new_size);
2037 }
2038
2039 return symmetrized_relevant_arcs_;
2040 }
2041
2042 // Try to add an outgoing cut from the given subset.
2043 bool TrySubsetCut(int known_bound, std::string name,
2044 absl::Span<const int> subset,
2045 LinearConstraintManager* manager);
2046
2047 // Returns a bound on the number of vehicle that is valid for this subset and
2048 // all superset. This takes a current valid bound. It might do nothing
2049 // depening on the parameters and just return the initial bound.
2050 int ShortestPathBound(int bound, absl::Span<const int> subset);
2051
2052 // If we look at the symmetrized version (tail <-> head = tail->head +
2053 // head->tail) and we split all the edges between a subset of nodes S and the
2054 // outside into a set A and the other d(S)\A, and |A| is odd, we have a
2055 // constraint of the form:
2056 // "all edge of A at 1" => sum other edges >= 1.
2057 // This is because a cycle or multiple-cycle must go in/out an even number
2058 // of time. This enforced constraint simply linearize to:
2059 // sum_d(S)\A x_e + sum_A (1 - x_e) >= 1.
2060 //
2061 // Given a subset of nodes, it is easy to identify the best subset A of edge
2062 // to consider.
2063 bool TryBlossomSubsetCut(std::string name,
2064 absl::Span<const ArcWithLpValue> symmetrized_edges,
2065 absl::Span<const int> subset,
2066 LinearConstraintManager* manager);
2067
2068 // Try adding cuts to exclude paths in the residual graph corresponding to the
2069 // LP solution which are actually infeasible.
2070 void TryInfeasiblePathCuts(LinearConstraintManager* manager);
2071
2072 private:
2073 // If bool is true it is a "incoming" cut, otherwise "outgoing" one.
2074 struct BestChoice {
2075 int node;
2076 bool incoming;
2077 double weight;
2078 };
2079 BestChoice PickBestNode(absl::Span<const int> cannot_be_first,
2080 absl::Span<const int> cannot_be_last);
2081
2082 // Removes the arcs with a literal fixed at false at level zero. This is
2083 // especially useful to remove fixed self loop.
2084 void FilterFalseArcsAtLevelZero();
2085
2086 // Add a cut of the form Sum_{outgoing arcs from S} lp >= rhs_lower_bound.
2087 //
2088 // Note that we used to also add the same cut for the incoming arcs, but
2089 // because of flow conservation on these problems, the outgoing flow is always
2090 // the same as the incoming flow, so adding this extra cut doesn't seem
2091 // relevant.
2092 bool AddOutgoingCut(LinearConstraintManager* manager, std::string name,
2093 int subset_size, const std::vector<bool>& in_subset,
2094 int64_t rhs_lower_bound);
2095
2096 bool MaybeAddStrongerFlowCut(LinearConstraintManager* manager,
2097 std::string name, int k,
2098 absl::Span<const int> cannot_be_first,
2099 absl::Span<const int> cannot_be_last,
2100 const std::vector<bool>& in_subset);
2101
2102 void GenerateCutsForInfeasiblePaths(
2103 int start_node, int max_path_length,
2104 const CompactVectorVector<int, std::pair<int, double>>&
2105 outgoing_arcs_with_lp_values,
2106 LinearConstraintManager* manager, TopNCuts& top_n_cuts);
2107
2108 const int num_nodes_;
2109 const bool is_route_constraint_;
2110 std::vector<int> tails_;
2111 std::vector<int> heads_;
2112 std::vector<Literal> literals_;
2113 std::vector<ArcWithLpValue> relevant_arcs_;
2114 std::vector<std::pair<int, double>> relevant_arc_indices_;
2115 std::vector<ArcWithLpValue> symmetrized_relevant_arcs_;
2116 std::vector<std::pair<int, int>> ordered_arcs_;
2117
2118 const SatParameters& params_;
2119 const Trail& trail_;
2120 const IntegerTrail& integer_trail_;
2121 const ConditionalLinear2Bounds& binary_relation_repository_;
2122 const ImpliedBounds& implied_bounds_;
2123 ModelRandomGenerator* random_;
2124 IntegerEncoder* encoder_;
2125 const RootLevelLinear2Bounds& root_level_bounds_;
2126
2127 std::vector<bool> in_subset_;
2128
2129 // Self-arc information, indexed in [0, num_nodes_)
2130 std::vector<int> nodes_with_self_arc_;
2131 std::vector<Literal> self_arc_literal_;
2132 std::vector<double> self_arc_lp_value_;
2133
2134 // Initialized by TrySubsetCut() for each new subset.
2135 std::vector<double> nodes_incoming_weight_;
2136 std::vector<double> nodes_outgoing_weight_;
2137
2138 // Helper to compute bounds on the minimum number of vehicles to serve a
2139 // subset.
2140 MaxBoundedSubsetSum max_bounded_subset_sum_;
2141 MaxBoundedSubsetSumExact max_bounded_subset_sum_exact_;
2142 MinOutgoingFlowHelper min_outgoing_flow_helper_;
2143 std::unique_ptr<RouteRelationsHelper> route_relations_helper_;
2144};
2145
2146void RoutingCutHelper::FilterFalseArcsAtLevelZero() {
2147 if (trail_.CurrentDecisionLevel() != 0) return;
2148
2149 int new_size = 0;
2150 const int size = static_cast<int>(tails_.size());
2151 const VariablesAssignment& assignment = trail_.Assignment();
2152 std::vector<int> removed_arcs;
2153 for (int i = 0; i < size; ++i) {
2154 if (assignment.LiteralIsFalse(literals_[i])) {
2155 removed_arcs.push_back(i);
2156 continue;
2157 }
2158 tails_[new_size] = tails_[i];
2159 heads_[new_size] = heads_[i];
2160 literals_[new_size] = literals_[i];
2161 ++new_size;
2162 }
2163 if (new_size < size) {
2164 tails_.resize(new_size);
2165 heads_.resize(new_size);
2166 literals_.resize(new_size);
2167 if (route_relations_helper_ != nullptr) {
2168 route_relations_helper_->RemoveArcs(removed_arcs);
2169 }
2170 }
2171}
2172
2173void RoutingCutHelper::InitializeForNewLpSolution(
2174 LinearConstraintManager* manager) {
2175 FilterFalseArcsAtLevelZero();
2176
2177 // We will collect only the arcs with a positive lp_values to speed up some
2178 // computation below.
2179 relevant_arcs_.clear();
2180 relevant_arc_indices_.clear();
2181 nodes_with_self_arc_.clear();
2182
2183 // Sort the arcs by non-increasing lp_values.
2184 const auto& lp_values = manager->LpValues();
2185 std::vector<std::pair<double, int>> relevant_arc_by_decreasing_lp_values;
2186 for (int i = 0; i < literals_.size(); ++i) {
2187 const IntegerVariable direct_view = encoder_->GetLiteralView(literals_[i]);
2188 const double lp_value =
2189 direct_view != kNoIntegerVariable
2190 ? lp_values[direct_view]
2191 : 1.0 - lp_values[encoder_->GetLiteralView(literals_[i].Negated())];
2192
2193 // We treat self-edge separately.
2194 // Note also that we do not need to include them in relevant_arcs_.
2195 //
2196 // TODO(user): If there are multiple self-arc, the code should still
2197 // work, but is not ideal.
2198 if (tails_[i] == heads_[i]) {
2199 const int node = tails_[i];
2200 nodes_with_self_arc_.push_back(node);
2201 self_arc_lp_value_[node] = lp_value;
2202 self_arc_literal_[node] = literals_[i];
2203 continue;
2204 }
2205
2206 if (lp_value < 1e-6) continue;
2207 relevant_arcs_.push_back({tails_[i], heads_[i], lp_value});
2208 relevant_arc_indices_.push_back({i, lp_value});
2209 relevant_arc_by_decreasing_lp_values.push_back({lp_value, i});
2210 }
2211 std::sort(relevant_arc_by_decreasing_lp_values.begin(),
2212 relevant_arc_by_decreasing_lp_values.end(),
2213 std::greater<std::pair<double, int>>());
2214
2215 gtl::STLSortAndRemoveDuplicates(&nodes_with_self_arc_);
2216
2217 ordered_arcs_.clear();
2218 for (const auto& [score, arc] : relevant_arc_by_decreasing_lp_values) {
2219 if (is_route_constraint_) {
2220 if (tails_[arc] == 0 || heads_[arc] == 0) continue;
2221 }
2222 ordered_arcs_.push_back({tails_[arc], heads_[arc]});
2223 }
2224
2225 if (absl::GetFlag(FLAGS_cp_model_dump_routes_support_graphs) &&
2226 trail_.CurrentDecisionLevel() == 0) {
2227 static std::atomic<int> counter = 0;
2228 const std::string name =
2229 absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix),
2230 "support_graph_", counter++, ".pb.txt");
2231 LOG(INFO) << "Dumping routes support graph to '" << name << "'.";
2232 RoutesSupportGraphProto support_graph_proto;
2233 for (auto& [tail, head, lp_value] : relevant_arcs_) {
2234 auto* arc = support_graph_proto.add_arc_lp_values();
2235 arc->set_tail(tail);
2236 arc->set_head(head);
2237 arc->set_lp_value(lp_value);
2238 }
2239 CHECK(WriteModelProtoToFile(support_graph_proto, name));
2240 }
2241}
2242
2243namespace {
2244
2245// Compute the current outgoing/incoming flow out of the subset.
2246// In many cases this will be the same, but not with outside_node_to_ignore
2247// or in case our LP does not contain all the constraints.
2248//
2249// Looping over all arcs can take a significant portion of the running time,
2250// it is why it is faster to do it only on arcs with non-zero lp values which
2251// should be in linear number rather than the total number of arc which can be
2252// quadratic.
2253//
2254// TODO(user): For the symmetric case there is an even faster algo. See if
2255// it can be generalized to the asymmetric one if become needed.
2256// Reference is algo 6.4 of the "The Traveling Salesman Problem" book
2257// mentioned above.
2258std::pair<double, double> GetIncomingAndOutgoingLpFlow(
2259 absl::Span<const ArcWithLpValue> relevant_arcs,
2260 const std::vector<bool>& in_subset, int outside_node_to_ignore = -1) {
2261 double outgoing_flow = 0.0;
2262 double incoming_flow = 0.0;
2263 for (const auto arc : relevant_arcs) {
2264 const bool tail_in = in_subset[arc.tail];
2265 const bool head_in = in_subset[arc.head];
2266 if (tail_in && !head_in) {
2267 if (arc.head == outside_node_to_ignore) continue;
2268 outgoing_flow += arc.lp_value;
2269 } else if (!tail_in && head_in) {
2270 if (arc.tail == outside_node_to_ignore) continue;
2271 incoming_flow += arc.lp_value;
2272 }
2273 }
2274 return {incoming_flow, outgoing_flow};
2275}
2276
2277} // namespace
2278
2279RoutingCutHelper::BestChoice RoutingCutHelper::PickBestNode(
2280 absl::Span<const int> cannot_be_first,
2281 absl::Span<const int> cannot_be_last) {
2282 // Pick the set of candidates with the highest lp weight.
2283 std::vector<BestChoice> bests;
2284 double best_weight = 0.0;
2285 for (const int n : cannot_be_first) {
2286 const double weight = nodes_incoming_weight_[n];
2287 if (bests.empty() || weight > best_weight) {
2288 bests.clear();
2289 bests.push_back({n, true, weight});
2290 best_weight = weight;
2291 } else if (weight == best_weight) {
2292 bests.push_back({n, true, weight});
2293 }
2294 }
2295 for (const int n : cannot_be_last) {
2296 const double weight = nodes_outgoing_weight_[n];
2297 if (bests.empty() || weight > best_weight) {
2298 bests.clear();
2299 bests.push_back({n, false, weight});
2300 best_weight = weight;
2301 } else if (weight == best_weight) {
2302 bests.push_back({n, false, weight});
2303 }
2304 }
2305 if (bests.empty()) return {-1, true};
2306 return bests.size() == 1
2307 ? bests[0]
2308 : bests[absl::Uniform<int>(*random_, 0, bests.size())];
2309}
2310
2311bool RoutingCutHelper::MaybeAddStrongerFlowCut(
2312 LinearConstraintManager* manager, std::string name, int k,
2313 absl::Span<const int> cannot_be_first, absl::Span<const int> cannot_be_last,
2314 const std::vector<bool>& in_subset) {
2315 if (cannot_be_first.empty() && cannot_be_last.empty()) return false;
2316
2317 // We pick the best node out of cannot_be_first/cannot_be_last to derive
2318 // a stronger cut.
2319 const BestChoice best_choice = PickBestNode(cannot_be_first, cannot_be_last);
2320 const int best_node = best_choice.node;
2321 const bool incoming = best_choice.incoming;
2322 CHECK_GE(best_node, 0);
2323
2324 const auto consider_arc = [incoming, &in_subset](int t, int h) {
2325 return incoming ? !in_subset[t] && in_subset[h]
2326 : in_subset[t] && !in_subset[h];
2327 };
2328
2329 // We consider the incoming (resp. outgoing_flow) not arriving at (resp. not
2330 // leaving from) best_node.
2331 //
2332 // This flow must be >= k.
2333 double flow = 0.0;
2334 for (const auto arc : relevant_arcs_) {
2335 if (!consider_arc(arc.tail, arc.head)) continue;
2336 if (arc.tail != best_node && arc.head != best_node) {
2337 flow += arc.lp_value;
2338 }
2339 }
2340
2341 // Add cut flow >= k.
2342 const double kTolerance = 1e-4;
2343 if (flow < static_cast<double>(k) - kTolerance) {
2344 LinearConstraintBuilder cut_builder(encoder_, IntegerValue(k),
2345 kMaxIntegerValue);
2346 for (int i = 0; i < tails_.size(); ++i) {
2347 if (!consider_arc(tails_[i], heads_[i])) continue;
2348 if (tails_[i] != best_node && heads_[i] != best_node) {
2349 CHECK(cut_builder.AddLiteralTerm(literals_[i], IntegerValue(1)));
2350 }
2351 }
2352 return manager->AddCut(cut_builder.Build(), name);
2353 }
2354 return false;
2355}
2356
2357bool RoutingCutHelper::AddOutgoingCut(LinearConstraintManager* manager,
2358 std::string name, int subset_size,
2359 const std::vector<bool>& in_subset,
2360 int64_t rhs_lower_bound) {
2361 // Skip cut if it is not violated.
2362 const auto [in_flow, out_flow] =
2363 GetIncomingAndOutgoingLpFlow(relevant_arcs_, in_subset);
2364 const double out_violation = static_cast<double>(rhs_lower_bound) - out_flow;
2365 const double in_violation = static_cast<double>(rhs_lower_bound) - in_flow;
2366 if (out_violation <= 1e-3 && in_violation <= 1e-3) return false;
2367
2368 // We create the cut and rely on AddCut() for computing its efficacy and
2369 // rejecting it if it is bad.
2370 LinearConstraintBuilder outgoing(encoder_, IntegerValue(rhs_lower_bound),
2371 kMaxIntegerValue);
2372 LinearConstraintBuilder incoming(encoder_, IntegerValue(rhs_lower_bound),
2373 kMaxIntegerValue);
2374
2375 // Rather than doing two loops, we initialize the cuts right away, even if
2376 // only one of them will be used.
2377 for (int i = 0; i < tails_.size(); ++i) {
2378 const bool tail_in = in_subset[tails_[i]];
2379 const bool head_in = in_subset[heads_[i]];
2380 if (tail_in && !head_in) {
2381 CHECK(outgoing.AddLiteralTerm(literals_[i], IntegerValue(1)));
2382 } else if (!tail_in && head_in) {
2383 CHECK(incoming.AddLiteralTerm(literals_[i], IntegerValue(1)));
2384 }
2385 }
2386
2387 // As arcs get fixed (this happens a lot in LNS subproblems), even if the
2388 // incoming flow is the same as the outgoing flow, the number of incoming arcs
2389 // might be widely different from the one of outgoing arcs. We prefer to pick
2390 // the sparser cut.
2391 const double out_efficacy = out_violation / std::sqrt(outgoing.NumTerms());
2392 const double in_efficacy = in_violation / std::sqrt(incoming.NumTerms());
2393
2394 // Select the best option between outgoing and incoming.
2395 LinearConstraintBuilder& cut_builder =
2396 (out_efficacy >= in_efficacy) ? outgoing : incoming;
2397
2398 // A node is said to be optional if it can be excluded from the subcircuit,
2399 // in which case there is a self-loop on that node.
2400 // If there are optional nodes, use extended formula:
2401 // sum(cut) >= 1 - optional_loop_in - optional_loop_out
2402 // where optional_loop_in's node is in subset, optional_loop_out's is out.
2403 // TODO(user): Favor optional loops fixed to zero at root.
2404 int num_optional_nodes_in = 0;
2405 int num_optional_nodes_out = 0;
2406 int optional_loop_in = -1;
2407 int optional_loop_out = -1;
2408 for (const int n : nodes_with_self_arc_) {
2409 if (in_subset[n]) {
2410 num_optional_nodes_in++;
2411 if (optional_loop_in == -1 ||
2412 self_arc_lp_value_[n] < self_arc_lp_value_[optional_loop_in]) {
2413 optional_loop_in = n;
2414 }
2415 } else {
2416 num_optional_nodes_out++;
2417 if (optional_loop_out == -1 ||
2418 self_arc_lp_value_[n] < self_arc_lp_value_[optional_loop_out]) {
2419 optional_loop_out = n;
2420 }
2421 }
2422 }
2423
2424 // This just makes sure we don't call this with a bound > 1 if there is
2425 // optional node inside the subset.
2426 CHECK(rhs_lower_bound == 1 || num_optional_nodes_in == 0);
2427
2428 // Support optional nodes if any.
2429 if (num_optional_nodes_in + num_optional_nodes_out > 0) {
2430 // When all optionals of one side are excluded in lp solution, no cut.
2431 if (num_optional_nodes_in == subset_size &&
2432 (optional_loop_in == -1 ||
2433 self_arc_lp_value_[optional_loop_in] > 1.0 - 1e-6)) {
2434 return false;
2435 }
2436 if (num_optional_nodes_out == num_nodes_ - subset_size &&
2437 (optional_loop_out == -1 ||
2438 self_arc_lp_value_[optional_loop_out] > 1.0 - 1e-6)) {
2439 return false;
2440 }
2441
2442 // There is no mandatory node in subset, add optional_loop_in.
2443 if (num_optional_nodes_in == subset_size) {
2444 CHECK_EQ(rhs_lower_bound, 1);
2445 CHECK(cut_builder.AddLiteralTerm(self_arc_literal_[optional_loop_in],
2446 IntegerValue(1)));
2447 }
2448
2449 // There is no mandatory node out of subset, add optional_loop_out.
2450 if (num_optional_nodes_out == num_nodes_ - subset_size) {
2451 CHECK_EQ(rhs_lower_bound, 1);
2452 CHECK(cut_builder.AddLiteralTerm(self_arc_literal_[optional_loop_out],
2453 IntegerValue(1)));
2454 }
2455 }
2456
2457 return manager->AddCut(cut_builder.Build(), name);
2458}
2459
2460int RoutingCutHelper::ShortestPathBound(int bound,
2461 absl::Span<const int> subset) {
2462 if (!is_route_constraint_) return bound;
2463 if (!nodes_with_self_arc_.empty()) return bound;
2464 if (route_relations_helper_ == nullptr) return bound;
2465 if (!route_relations_helper_->HasShortestPathsInformation()) return bound;
2466 if (subset.size() >
2468 return bound;
2469 }
2470
2471 while (bound < subset.size()) {
2472 if (min_outgoing_flow_helper_.SubsetMightBeServedWithKRoutes(
2473 bound, subset, route_relations_helper_.get())) {
2474 break;
2475 }
2476 bound += 1;
2477 }
2478
2479 return bound;
2480}
2481
2482bool RoutingCutHelper::TrySubsetCut(int known_bound, std::string name,
2483 absl::Span<const int> subset,
2484 LinearConstraintManager* manager) {
2485 DCHECK_GE(subset.size(), 1);
2486 DCHECK_LT(subset.size(), num_nodes_);
2487
2488 // Do some initialization.
2489 in_subset_.assign(num_nodes_, false);
2490 for (const int n : subset) {
2491 in_subset_[n] = true;
2492
2493 // We should aways generate subset without the depot for the route
2494 // constraint. This is because we remove arcs from/to the depot in the
2495 // tree based heuristics to generate all the subsets we try.
2496 if (is_route_constraint_) CHECK_NE(n, 0);
2497 }
2498
2499 // For now we can only apply fancy route cuts if all nodes in subset are
2500 // mandatory.
2501 bool all_subset_nodes_are_mandatory = true;
2502 if (is_route_constraint_) {
2503 for (const int n : nodes_with_self_arc_) {
2504 if (in_subset_[n]) {
2505 all_subset_nodes_are_mandatory = false;
2506 break;
2507 }
2508 }
2509 }
2510
2511 // The TSP case is "easy".
2512 //
2513 // TODO(user): Turn on some of the automatic detection for circuit constraint.
2514 // Even if we are looking for a full circuit of the mandatory nodes, some
2515 // side-constraint might require to go in and out of a subset more than once.
2516 //
2517 // TODO(user): deal with non-mandatory node in the route constraint?
2518 if (!is_route_constraint_ || !all_subset_nodes_are_mandatory) {
2519 return AddOutgoingCut(manager, name, subset.size(), in_subset_,
2520 /*rhs_lower_bound=*/1);
2521 }
2522
2523 // Compute the LP weight from/to the subset, and also the LP weight incoming
2524 // from outside to a given node in a subset or from the subset to a given node
2525 // outside (similarly for the outgoing case).
2526 double total_outgoing_weight = 0.0;
2527 double total_incoming_weight = 0.0;
2528 nodes_incoming_weight_.assign(num_nodes_, 0);
2529 nodes_outgoing_weight_.assign(num_nodes_, 0);
2530 for (const auto arc : relevant_arcs_) {
2531 if (in_subset_[arc.tail] != in_subset_[arc.head]) {
2532 if (in_subset_[arc.tail]) {
2533 total_outgoing_weight += arc.lp_value;
2534 } else {
2535 total_incoming_weight += arc.lp_value;
2536 }
2537 nodes_outgoing_weight_[arc.tail] += arc.lp_value;
2538 nodes_incoming_weight_[arc.head] += arc.lp_value;
2539 }
2540 }
2541
2542 BestBoundHelper best_bound(name);
2543 best_bound.Update(1, "");
2544 best_bound.Update(known_bound, "Hierarchical");
2545
2546 // Bounds inferred automatically from the enforced binary relation of the
2547 // model.
2548 //
2549 // TODO(user): This is still not as good as the "capacity" bounds below in
2550 // some cases. Fix! we should be able to use the same relation to infer the
2551 // capacity bounds somehow.
2552 if (route_relations_helper_ != nullptr) {
2553 min_outgoing_flow_helper_.ComputeDimensionBasedMinOutgoingFlow(
2554 subset, *route_relations_helper_, &best_bound);
2555 }
2556 if (subset.size() <
2558 const int bound =
2559 min_outgoing_flow_helper_.ComputeTightMinOutgoingFlow(subset);
2560 best_bound.Update(bound, "AutomaticTight");
2561 } else if (subset.size() <
2563 const int bound = min_outgoing_flow_helper_.ComputeMinOutgoingFlow(subset);
2564 best_bound.Update(bound, "Automatic");
2565 }
2566
2567 if (subset.size() <=
2569 // Note that we only look for violated cuts, but also in order to not try
2570 // all nodes from the subset, we only look at large enough incoming/outgoing
2571 // weight.
2572 //
2573 // TODO(user): We could easily look for an arc that cannot be used to enter
2574 // or leave a subset rather than a node. This should lead to tighter bound,
2575 // and more cuts (that would just ignore that arc rather than all the arcs
2576 // entering/leaving from a node).
2577 const double threshold = static_cast<double>(best_bound.bound()) - 1e-4;
2578 for (const int n : subset) {
2579 if (nodes_incoming_weight_[n] > 0.1 &&
2580 total_incoming_weight - nodes_incoming_weight_[n] < threshold &&
2581 !min_outgoing_flow_helper_.SubsetMightBeServedWithKRoutes(
2582 best_bound.bound(), subset, nullptr, manager, /*special_node=*/n,
2583 /*use_forward_direction=*/true)) {
2584 best_bound.Update(best_bound.bound(), "", {n}, {});
2585 }
2586 if (nodes_outgoing_weight_[n] > 0.1 &&
2587 total_outgoing_weight - nodes_outgoing_weight_[n] < threshold &&
2588 !min_outgoing_flow_helper_.SubsetMightBeServedWithKRoutes(
2589 best_bound.bound(), subset, nullptr, manager, /*special_node=*/n,
2590 /*use_forward_direction=*/false)) {
2591 best_bound.Update(best_bound.bound(), "", {}, {n});
2592 }
2593 }
2594 }
2595
2596 if (MaybeAddStrongerFlowCut(manager,
2597 absl::StrCat(best_bound.name(), "Lifted"),
2598 best_bound.bound(), best_bound.CannotBeFirst(),
2599 best_bound.CannotBeLast(), in_subset_)) {
2600 return true;
2601 }
2602
2603 return AddOutgoingCut(manager, best_bound.name(), subset.size(), in_subset_,
2604 /*rhs_lower_bound=*/best_bound.bound());
2605}
2606
2607bool RoutingCutHelper::TryBlossomSubsetCut(
2608 std::string name, absl::Span<const ArcWithLpValue> symmetrized_edges,
2609 absl::Span<const int> subset, LinearConstraintManager* manager) {
2610 DCHECK_GE(subset.size(), 1);
2611 DCHECK_LT(subset.size(), num_nodes_);
2612
2613 // Initialize "in_subset".
2614 for (const int n : subset) in_subset_[n] = true;
2615 auto cleanup = ::absl::MakeCleanup([subset, this]() {
2616 for (const int n : subset) in_subset_[n] = false;
2617 });
2618
2619 // The heuristic assumes non-duplicates arcs, otherwise they are all bundled
2620 // together in the same symmetric edge, and the result is probably wrong.
2621 absl::flat_hash_set<std::pair<int, int>> special_edges;
2622 int num_inverted = 0;
2623 double sum_inverted = 0.0;
2624 double sum = 0.0;
2625 double best_change = 1.0;
2626 ArcWithLpValue const* best_swap = nullptr;
2627 for (const ArcWithLpValue& arc : symmetrized_edges) {
2628 if (in_subset_[arc.tail] == in_subset_[arc.head]) continue;
2629
2630 if (arc.lp_value > 0.5) {
2631 ++num_inverted;
2632 special_edges.insert({arc.tail, arc.head});
2633 sum_inverted += 1.0 - arc.lp_value;
2634 } else {
2635 sum += arc.lp_value;
2636 }
2637
2638 const double change = std::abs(2 * arc.lp_value - 1.0);
2639 if (change < best_change) {
2640 best_change = change;
2641 best_swap = &arc;
2642 }
2643 }
2644
2645 // If the we don't have an odd number, we move the best edge from one set to
2646 // the other.
2647 if (num_inverted % 2 == 0) {
2648 if (best_swap == nullptr) return false;
2649 if (special_edges.contains({best_swap->tail, best_swap->head})) {
2650 --num_inverted;
2651 special_edges.erase({best_swap->tail, best_swap->head});
2652 sum_inverted -= (1.0 - best_swap->lp_value);
2653 sum += best_swap->lp_value;
2654 } else {
2655 ++num_inverted;
2656 special_edges.insert({best_swap->tail, best_swap->head});
2657 sum_inverted += (1.0 - best_swap->lp_value);
2658 sum -= best_swap->lp_value;
2659 }
2660 }
2661 if (sum + sum_inverted > 0.99) return false;
2662
2663 // For the route constraint, it is actually allowed to have circuit of size
2664 // 2, so the reasoning is wrong if one of the edges touches the depot.
2665 if (!is_route_constraint_) {
2666 for (const auto [tail, head] : special_edges) {
2667 if (tail == 0) return false;
2668 }
2669 }
2670
2671 // If there is just one special edge, and all other node can be ignored, then
2672 // the reasonning is wrong too since we can have a 2-cycle. In that case
2673 // we enforce the constraint when an extra self-loop literal is at zero.
2674 int best_optional_index = -1;
2675 if (special_edges.size() == 1) {
2676 int num_other_optional = 0;
2677 const auto [special_tail, special_head] = *special_edges.begin();
2678 for (const int n : nodes_with_self_arc_) {
2679 if (n != special_head && n != special_tail) {
2680 ++num_other_optional;
2681 if (best_optional_index == -1 ||
2682 self_arc_lp_value_[n] < self_arc_lp_value_[best_optional_index]) {
2683 best_optional_index = n;
2684 }
2685 }
2686 }
2687 if (num_other_optional + 2 < num_nodes_) best_optional_index = -1;
2688 }
2689
2690 // Try to generate the cut.
2691 //
2692 // We deal with the corner case with duplicate arcs, or just one side of a
2693 // "symmetric" edge present.
2694 int num_actual_inverted = 0;
2695 absl::flat_hash_set<std::pair<int, int>> processed_arcs;
2696 LinearConstraintBuilder builder(encoder_, IntegerValue(1), kMaxIntegerValue);
2697
2698 // Add extra self-loop at zero enforcement if needed.
2699 // TODO(user): Can we deal with the 2-cycle case in a better way?
2700 if (best_optional_index != -1) {
2701 absl::StrAppend(&name, "_opt");
2702
2703 // This is tricky: The normal cut assume x_e <= 1, but in case of a single
2704 // 2 cycle, x_e can be equal to 2. So we need a coeff of 2 to disable that
2705 // cut.
2706 CHECK(builder.AddLiteralTerm(self_arc_literal_[best_optional_index],
2707 IntegerValue(2)));
2708 }
2709
2710 for (int i = 0; i < tails_.size(); ++i) {
2711 if (tails_[i] == heads_[i]) continue;
2712 if (in_subset_[tails_[i]] == in_subset_[heads_[i]]) continue;
2713
2714 const std::pair<int, int> key = {tails_[i], heads_[i]};
2715 const std::pair<int, int> r_key = {heads_[i], tails_[i]};
2716 const std::pair<int, int> s_key = std::min(key, r_key);
2717 if (special_edges.contains(s_key) && !processed_arcs.contains(key)) {
2718 processed_arcs.insert(key);
2719 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(-1)));
2720 if (!processed_arcs.contains(r_key)) {
2721 ++num_actual_inverted;
2722 }
2723 continue;
2724 }
2725
2726 // Normal edge.
2727 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(1)));
2728 }
2729 builder.AddConstant(IntegerValue(num_actual_inverted));
2730 if (num_actual_inverted % 2 == 0) return false;
2731
2732 return manager->AddCut(builder.Build(), name);
2733}
2734
2735void RoutingCutHelper::TryInfeasiblePathCuts(LinearConstraintManager* manager) {
2736 const int max_path_length = params_.routing_cut_max_infeasible_path_length();
2737 if (max_path_length <= 0) return;
2738
2739 // The outgoing arcs of the residual graph, per tail node, except self arcs
2740 // and arcs to or from the depot.
2741 std::vector<int> keys;
2742 std::vector<std::pair<int, double>> values;
2743 for (const auto [arc_index, lp_value] : relevant_arc_indices_) {
2744 const int tail = tails_[arc_index];
2745 const int head = heads_[arc_index];
2746
2747 // Self arcs are filtered out in InitializeForNewLpSolution().
2748 DCHECK_NE(tail, head);
2749 if (tail == 0 || head == 0) continue;
2750 keys.push_back(tail);
2751 values.push_back({arc_index, lp_value});
2752 }
2753 CompactVectorVector<int, std::pair<int, double>> outgoing_arcs_with_lp_values;
2754 outgoing_arcs_with_lp_values.ResetFromFlatMapping(keys, values, num_nodes_);
2755
2756 TopNCuts top_n_cuts(5);
2757 for (int i = 1; i < num_nodes_; ++i) {
2758 GenerateCutsForInfeasiblePaths(
2759 i, max_path_length, outgoing_arcs_with_lp_values, manager, top_n_cuts);
2760 }
2761 top_n_cuts.TransferToManager(manager);
2762}
2763
2764// Note that it makes little sense to use reduced cost here as the LP solution
2765// should likely satisfy the current LP optimality equation.
2766void RoutingCutHelper::GenerateCutsForInfeasiblePaths(
2767 int start_node, int max_path_length,
2768 const CompactVectorVector<int, std::pair<int, double>>&
2769 outgoing_arcs_with_lp_values,
2770 LinearConstraintManager* manager, TopNCuts& top_n_cuts) {
2771 // Performs a DFS on the residual graph, starting from the given node. To save
2772 // stack space, we use a manual stack on the heap instead of using recursion.
2773 // Each stack element corresponds to a feasible path from `start_node` to
2774 // `last_node`, included.
2775 struct State {
2776 // The last node of the path.
2777 int last_node;
2778
2779 // The sum of the lp values of the path's arcs.
2780 double sum_of_lp_values = 0.0;
2781
2782 // Variable lower bounds that can be inferred by assuming that the path's
2783 // arc literals are true (with the binary relation repository).
2784 absl::flat_hash_map<IntegerVariable, IntegerValue> bounds;
2785
2786 // The index of the next outgoing arc of `last_node` to explore (in
2787 // outgoing_arcs_with_lp_values[last_node]).
2788 int iteration = 0;
2789 };
2790 std::stack<State> states;
2791 // Whether each node is on the current path. The current path is the one
2792 // corresponding to the top stack element.
2793 std::vector<bool> path_nodes(num_nodes_, false);
2794 // The literals corresponding to the current path's arcs.
2795 std::vector<Literal> path_literals;
2796
2797 path_nodes[start_node] = true;
2798 states.push({start_node});
2799 while (!states.empty()) {
2800 State& state = states.top();
2801 DCHECK_EQ(states.size(), path_literals.size() + 1);
2802 // The number of arcs in the current path.
2803 const int path_length = static_cast<int>(path_literals.size());
2804 const auto& outgoing_arcs = outgoing_arcs_with_lp_values[state.last_node];
2805
2806 bool new_state_pushed = false;
2807 while (state.iteration < outgoing_arcs.size()) {
2808 const auto [arc_index, lp_value] = outgoing_arcs[state.iteration++];
2809 State next_state;
2810 next_state.last_node = heads_[arc_index];
2811 next_state.sum_of_lp_values = state.sum_of_lp_values + lp_value;
2812 next_state.iteration = 0;
2813 // We only consider simple paths.
2814 if (path_nodes[next_state.last_node]) continue;
2815 // If a path of length l is infeasible, we can exclude it by adding a cut
2816 // saying the sum of its arcs literals must be strictly less than l. But
2817 // this is only beneficial if the sum of the corresponding LP values is
2818 // currently greater than l. If it is not, and since it cannot become
2819 // greater for some extension of the path (the LP sum increases at most by
2820 // 1 for each arc, but l increases exactly by 1), we can stop the search
2821 // here.
2822 // Note: "<= path_length" is equivalent to "< l" (l = path_length + 1).
2823 if (next_state.sum_of_lp_values <=
2824 static_cast<double>(path_length) + 1e-4) {
2825 continue;
2826 }
2827
2828 const Literal next_literal = literals_[arc_index];
2829 next_state.bounds = state.bounds;
2830 if (PropagateLocalBounds(integer_trail_, root_level_bounds_,
2831 binary_relation_repository_, implied_bounds_,
2832 next_literal, state.bounds,
2833 &next_state.bounds)) {
2834 // Do not explore "long" paths to keep the search time bounded.
2835 if (path_length < max_path_length) {
2836 path_nodes[next_state.last_node] = true;
2837 path_literals.push_back(literals_[arc_index]);
2838 states.push(std::move(next_state));
2839 new_state_pushed = true;
2840 break;
2841 }
2842 } else if (path_length > 0) {
2843 LinearConstraintBuilder cut_builder(encoder_, 0, path_length);
2844 for (const Literal literal : path_literals) {
2845 CHECK(cut_builder.AddLiteralTerm(literal, IntegerValue(1)));
2846 }
2847 CHECK(cut_builder.AddLiteralTerm(next_literal, IntegerValue(1)));
2848 top_n_cuts.AddCut(cut_builder.Build(), "CircuitInfeasiblePath",
2849 manager->LpValues());
2850 }
2851 }
2852 if (!new_state_pushed) {
2853 if (!path_literals.empty()) {
2854 path_literals.pop_back();
2855 }
2856 path_nodes[state.last_node] = false;
2857 states.pop();
2858 }
2859 }
2860}
2861
2862} // namespace
2863
2865 absl::Span<const std::pair<int, int>> arcs,
2866 int stop_at_num_components,
2867 std::vector<int>* subset_data,
2868 std::vector<absl::Span<const int>>* subsets) {
2869 // We will do a union-find by adding one by one the arc of the lp solution
2870 // in the order above. Every intermediate set during this construction will
2871 // be a candidate for a cut.
2872 //
2873 // In parallel to the union-find, to efficiently reconstruct these sets (at
2874 // most num_nodes), we construct a "decomposition forest" of the different
2875 // connected components. Note that we don't exploit any asymmetric nature of
2876 // the graph here. This is exactly the algo 6.3 in the book above.
2877 int num_components = num_nodes;
2878 std::vector<int> parent(num_nodes);
2879 std::vector<int> root(num_nodes);
2880 for (int i = 0; i < num_nodes; ++i) {
2881 parent[i] = i;
2882 root[i] = i;
2883 }
2884 auto get_root_and_compress_path = [&root](int node) {
2885 int r = node;
2886 while (root[r] != r) r = root[r];
2887 while (root[node] != r) {
2888 const int next = root[node];
2889 root[node] = r;
2890 node = next;
2891 }
2892 return r;
2893 };
2894 for (const auto& [initial_tail, initial_head] : arcs) {
2895 if (num_components <= stop_at_num_components) break;
2896 const int tail = get_root_and_compress_path(initial_tail);
2897 const int head = get_root_and_compress_path(initial_head);
2898 if (tail != head) {
2899 // Update the decomposition forest, note that the number of nodes is
2900 // growing.
2901 const int new_node = parent.size();
2902 parent.push_back(new_node);
2903 parent[head] = new_node;
2904 parent[tail] = new_node;
2905 --num_components;
2906
2907 // It is important that the union-find representative is the same node.
2908 root.push_back(new_node);
2909 root[head] = new_node;
2910 root[tail] = new_node;
2911 }
2912 }
2913
2914 // For each node in the decomposition forest, try to add a cut for the set
2915 // formed by the nodes and its children. To do that efficiently, we first
2916 // order the nodes so that for each node in a tree, the set of children forms
2917 // a consecutive span in the subset_data vector. This vector just lists the
2918 // nodes in the "pre-order" graph traversal order. The Spans will point inside
2919 // the subset_data vector, it is why we initialize it once and for all.
2920 ExtractAllSubsetsFromForest(parent, subset_data, subsets,
2921 /*node_limit=*/num_nodes);
2922}
2923
2924void ExtractAllSubsetsFromForest(absl::Span<const int> parent,
2925 std::vector<int>* subset_data,
2926 std::vector<absl::Span<const int>>* subsets,
2927 int node_limit) {
2928 // To not reallocate memory since we need the span to point inside this
2929 // vector, we resize subset_data right away.
2930 int out_index = 0;
2931 const int num_nodes = parent.size();
2932 subset_data->resize(std::min(num_nodes, node_limit));
2933 subsets->clear();
2934
2935 // Starts by creating the corresponding graph and find the root.
2936 util::StaticGraph<int> graph(num_nodes, num_nodes - 1);
2937 for (int i = 0; i < num_nodes; ++i) {
2938 if (parent[i] != i) {
2939 graph.AddArc(parent[i], i);
2940 }
2941 }
2942 graph.Build();
2943
2944 // Perform a dfs on the rooted tree.
2945 // The subset_data will just be the node in post-order.
2946 std::vector<int> subtree_starts(num_nodes, -1);
2947 std::vector<int> stack;
2948 stack.reserve(num_nodes);
2949 for (int i = 0; i < parent.size(); ++i) {
2950 if (parent[i] != i) continue;
2951
2952 stack.push_back(i); // root.
2953 while (!stack.empty()) {
2954 const int node = stack.back();
2955
2956 // The node was already explored, output its subtree and pop it.
2957 if (subtree_starts[node] >= 0) {
2958 stack.pop_back();
2959 if (node < node_limit) {
2960 (*subset_data)[out_index++] = node;
2961 }
2962 const int start = subtree_starts[node];
2963 const int size = out_index - start;
2964 subsets->push_back(absl::MakeSpan(&(*subset_data)[start], size));
2965 continue;
2966 }
2967
2968 // Explore.
2969 subtree_starts[node] = out_index;
2970 for (const int child : graph[node]) {
2971 stack.push_back(child);
2972 }
2973 }
2974 }
2975}
2976
2977std::vector<int> ComputeGomoryHuTree(
2978 int num_nodes, absl::Span<const ArcWithLpValue> relevant_arcs) {
2979 // Initialize the graph. Note that we use only arcs with a relevant lp
2980 // value, so this should be small in practice.
2981 SimpleMaxFlow max_flow;
2982 for (const auto& [tail, head, lp_value] : relevant_arcs) {
2983 max_flow.AddArcWithCapacity(tail, head, std::round(1.0e6 * lp_value));
2984 max_flow.AddArcWithCapacity(head, tail, std::round(1.0e6 * lp_value));
2985 }
2986
2987 // Compute an equivalent max-flow tree, according to the paper.
2988 // This version should actually produce a Gomory-Hu cut tree.
2989 std::vector<int> min_cut_subset;
2990 std::vector<int> parent(num_nodes, 0);
2991 for (int s = 1; s < num_nodes; ++s) {
2992 const int t = parent[s];
2993 if (max_flow.Solve(s, t) != SimpleMaxFlow::OPTIMAL) break;
2994 max_flow.GetSourceSideMinCut(&min_cut_subset);
2995 bool parent_of_t_in_subset = false;
2996 for (const int i : min_cut_subset) {
2997 if (i == parent[t]) parent_of_t_in_subset = true;
2998 if (i != s && parent[i] == t) parent[i] = s;
2999 }
3000 if (parent_of_t_in_subset) {
3001 parent[s] = parent[t];
3002 parent[t] = s;
3003 }
3004 }
3005
3006 return parent;
3007}
3008
3009void SymmetrizeArcs(std::vector<ArcWithLpValue>* arcs) {
3010 for (ArcWithLpValue& arc : *arcs) {
3011 if (arc.tail <= arc.head) continue;
3012 std::swap(arc.tail, arc.head);
3013 }
3014 std::sort(arcs->begin(), arcs->end(),
3015 [](const ArcWithLpValue& a, const ArcWithLpValue& b) {
3016 return std::tie(a.tail, a.head) < std::tie(b.tail, b.head);
3017 });
3018
3019 int new_size = 0;
3020 int last_tail = -1;
3021 int last_head = -1;
3022 for (const ArcWithLpValue& arc : *arcs) {
3023 if (arc.tail == last_tail && arc.head == last_head) {
3024 (*arcs)[new_size - 1].lp_value += arc.lp_value;
3025 continue;
3026 }
3027 (*arcs)[new_size++] = arc;
3028 last_tail = arc.tail;
3029 last_head = arc.head;
3030 }
3031 arcs->resize(new_size);
3032}
3033
3034// Processes each subsets and add any violated cut.
3035// Returns the number of added cuts.
3036int TryAllSubsets(std::string cut_name, absl::Span<const int> subset_data,
3037 std::vector<absl::Span<const int>> subsets,
3038 RoutingCutHelper& helper, LinearConstraintManager* manager) {
3039 const int num_nodes = subset_data.size();
3040
3041 // This exploit the fact that all subsets point into subset_data of size
3042 // num_nodes. Moreover, if S1 is included in S2, we will always process S1
3043 // before S2.
3044 //
3045 // TODO(user): There is probably a better way than doing a linear scan per
3046 // subset.
3047 std::vector<bool> cut_was_added(num_nodes, false);
3048 std::vector<int> shortest_path_lb(num_nodes, 0);
3049
3050 int num_added = 0;
3051 for (const absl::Span<const int> subset : subsets) {
3052 if (subset.size() <= 1) continue;
3053 if (subset.size() == num_nodes) continue;
3054
3055 // we exploit the tree structure of the subsets to not add a cut
3056 // for a larger subset if we added a cut from one included in it. Also,
3057 // since the "shortest path" lower bound is valid for any superset, we use
3058 // it to have a starting lb for the number of vehicles.
3059 //
3060 // TODO(user): Currently if we add too many not so relevant cuts, our
3061 // generic MIP cut heuritic are way too slow on TSP/VRP problems.
3062 int lb_for_that_subset = 1;
3063 bool included_cut_was_added = false;
3064 const int start = static_cast<int>(subset.data() - subset_data.data());
3065 for (int i = start; i < subset.size(); ++i) {
3066 lb_for_that_subset = std::max(lb_for_that_subset, shortest_path_lb[i]);
3067 if (cut_was_added[i]) {
3068 included_cut_was_added = true;
3069 }
3070 }
3071
3072 // If the subset is small enough and the parameters ask for it, compute
3073 // a lower bound on the number of vehicle for that subset. This uses
3074 // "shortest path bounds" and thus that bounds will also be valid for
3075 // any superset !
3076 lb_for_that_subset = helper.ShortestPathBound(lb_for_that_subset, subset);
3077 shortest_path_lb[start] = lb_for_that_subset;
3078
3079 // Note that we still try the num_nodes - 1 subset cut as that gives
3080 // directly a lower bound on the number of vehicles.
3081 if (included_cut_was_added && subset.size() + 1 < num_nodes) continue;
3082 if (helper.TrySubsetCut(lb_for_that_subset, cut_name, subset, manager)) {
3083 ++num_added;
3084 cut_was_added[start] = true;
3085 }
3086 }
3087
3088 return num_added;
3089}
3090
3091// We roughly follow the algorithm described in section 6 of "The Traveling
3092// Salesman Problem, A computational Study", David L. Applegate, Robert E.
3093// Bixby, Vasek Chvatal, William J. Cook.
3094//
3095// Note that this is mainly a "symmetric" case algo, but it does still work for
3096// the asymmetric case.
3097void SeparateSubtourInequalities(RoutingCutHelper& helper,
3098 LinearConstraintManager* manager) {
3099 const int num_nodes = helper.num_nodes();
3100 if (num_nodes <= 2) return;
3101
3102 std::vector<int> subset_data;
3103 std::vector<absl::Span<const int>> subsets;
3104 GenerateInterestingSubsets(num_nodes, helper.ordered_arcs(),
3105 /*stop_at_num_components=*/2, &subset_data,
3106 &subsets);
3107
3108 // For a routing problem, we always try with all nodes but the root as this
3109 // gives a global lower bound on the number of vehicles. Note that usually
3110 // the arcs with non-zero lp values should connect everything, but that only
3111 // happen after many cuts on large problems.
3112 if (helper.is_route_constraint()) {
3113 subsets.push_back(absl::MakeSpan(&subset_data[1], num_nodes - 1));
3114 }
3115
3116 int num_added =
3117 TryAllSubsets("Circuit", subset_data, subsets, helper, manager);
3118
3119 // If there were no cut added by the heuristic above, we try exact separation.
3120 //
3121 // With n-1 max_flow from a source to all destination, we can get the global
3122 // min-cut. Here, we use a slightly more advanced algorithm that will find a
3123 // min-cut for all possible pair of nodes. This is achieved by computing a
3124 // Gomory-Hu tree, still with n-1 max flow call.
3125 //
3126 // Note(user): Compared to any min-cut, these cut have some nice properties
3127 // since they are "included" in each other. This might help with combining
3128 // them within our generic IP cuts framework.
3129 //
3130 // TODO(user): I had an older version that tried the n-cuts generated during
3131 // the course of the algorithm. This could also be interesting. But it is
3132 // hard to tell with our current benchmark setup.
3133 if (num_added != 0) return;
3134 absl::Span<const ArcWithLpValue> symmetrized_relevant_arcs =
3135 helper.SymmetrizedRelevantArcs();
3136 std::vector<int> parent =
3137 ComputeGomoryHuTree(num_nodes, symmetrized_relevant_arcs);
3138
3139 // Try all interesting subset from the Gomory-Hu tree.
3140 ExtractAllSubsetsFromForest(parent, &subset_data, &subsets);
3141 num_added +=
3142 TryAllSubsets("CircuitExact", subset_data, subsets, helper, manager);
3143
3144 // Exact separation of symmetric Blossom cut. We use the algorithm in the
3145 // paper: "A Faster Exact Separation Algorithm for Blossom Inequalities", Adam
3146 // N. Letchford, Gerhard Reinelt, Dirk Oliver Theis, 2004.
3147 if (num_added != 0) return;
3148 if (num_nodes <= 2) return;
3149 std::vector<ArcWithLpValue> for_blossom;
3150 for_blossom.reserve(symmetrized_relevant_arcs.size());
3151 for (ArcWithLpValue arc : symmetrized_relevant_arcs) {
3152 if (arc.lp_value > 0.5) arc.lp_value = 1.0 - arc.lp_value;
3153 if (arc.lp_value < 1e-6) continue;
3154 for_blossom.push_back(arc);
3155 }
3156 parent = ComputeGomoryHuTree(num_nodes, for_blossom);
3157 ExtractAllSubsetsFromForest(parent, &subset_data, &subsets);
3158 int last_added_start = -1;
3159 for (const absl::Span<const int> subset : subsets) {
3160 if (subset.size() <= 1) continue;
3161 if (subset.size() == num_nodes) continue;
3162 const int start = static_cast<int>(subset.data() - subset_data.data());
3163 if (start <= last_added_start) continue;
3164 if (helper.TryBlossomSubsetCut("CircuitBlossom", symmetrized_relevant_arcs,
3165 subset, manager)) {
3166 ++num_added;
3167 last_added_start = start;
3168 }
3169 }
3170}
3171
3172namespace {
3173
3174// Returns for each literal its integer view, or the view of its negation.
3175std::vector<IntegerVariable> GetAssociatedVariables(
3176 absl::Span<const Literal> literals, Model* model) {
3177 auto* encoder = model->GetOrCreate<IntegerEncoder>();
3178 std::vector<IntegerVariable> result;
3179 for (const Literal l : literals) {
3180 const IntegerVariable direct_view = encoder->GetLiteralView(l);
3181 if (direct_view != kNoIntegerVariable) {
3182 result.push_back(direct_view);
3183 } else {
3184 result.push_back(encoder->GetLiteralView(l.Negated()));
3185 DCHECK_NE(result.back(), kNoIntegerVariable);
3186 }
3187 }
3188 return result;
3189}
3190
3191} // namespace
3192
3193// We use a basic algorithm to detect components that are not connected to the
3194// rest of the graph in the LP solution, and add cuts to force some arcs to
3195// enter and leave this component from outside.
3197 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
3198 absl::Span<const Literal> literals, Model* model) {
3199 auto helper = std::make_unique<RoutingCutHelper>(
3200 num_nodes, /*is_route_constraint=*/false,
3201 /*flat_node_dim_expressions=*/
3202 absl::Span<const AffineExpression>{}, tails, heads, literals, model);
3203 CutGenerator result;
3204 result.vars = GetAssociatedVariables(literals, model);
3205 result.generate_cuts =
3206 [helper = std::move(helper)](LinearConstraintManager* manager) {
3207 helper->InitializeForNewLpSolution(manager);
3208 SeparateSubtourInequalities(*helper, manager);
3209 return true;
3210 };
3211 return result;
3212}
3213
3215 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
3216 absl::Span<const Literal> literals,
3217 absl::Span<const AffineExpression> flat_node_dim_expressions,
3218 Model* model) {
3219 auto helper = std::make_unique<RoutingCutHelper>(
3220 num_nodes, /*is_route_constraint=*/true, flat_node_dim_expressions, tails,
3221 heads, literals, model);
3222 CutGenerator result;
3223 result.vars = GetAssociatedVariables(literals, model);
3224 result.generate_cuts =
3225 [helper = std::move(helper)](LinearConstraintManager* manager) {
3226 manager->CacheReducedCostsInfo();
3227 helper->InitializeForNewLpSolution(manager);
3228 SeparateSubtourInequalities(*helper, manager);
3229 helper->TryInfeasiblePathCuts(manager);
3230 return true;
3231 };
3232 return result;
3233}
3234
3235// This is really similar to SeparateSubtourInequalities, see the reference
3236// there.
3238 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
3239 absl::Span<const AffineExpression> arc_capacities,
3240 std::function<void(const std::vector<bool>& in_subset,
3241 IntegerValue* min_incoming_flow,
3242 IntegerValue* min_outgoing_flow)>
3243 get_flows,
3245 LinearConstraintManager* manager, Model* model) {
3246 // We will collect only the arcs with a positive lp capacity value to speed up
3247 // some computation below.
3248 struct Arc {
3249 int tail;
3250 int head;
3251 double lp_value;
3252 IntegerValue offset;
3253 };
3254 std::vector<Arc> relevant_arcs;
3255
3256 // Often capacities have a coeff > 1.
3257 // We currently exploit this if all coeff have a gcd > 1.
3258 int64_t gcd = 0;
3259
3260 // Sort the arcs by non-increasing lp_values.
3261 std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
3262 for (int i = 0; i < arc_capacities.size(); ++i) {
3263 const double lp_value = arc_capacities[i].LpValue(lp_values);
3264 if (!arc_capacities[i].IsConstant()) {
3265 gcd = std::gcd(gcd, std::abs(arc_capacities[i].coeff.value()));
3266 }
3267 if (lp_value < 1e-6 && arc_capacities[i].constant == 0) continue;
3268 relevant_arcs.push_back(
3269 {tails[i], heads[i], lp_value, arc_capacities[i].constant});
3270 arc_by_decreasing_lp_values.push_back({lp_value, i});
3271 }
3272 if (gcd == 0) return;
3273 std::sort(arc_by_decreasing_lp_values.begin(),
3274 arc_by_decreasing_lp_values.end(),
3275 std::greater<std::pair<double, int>>());
3276
3277 std::vector<std::pair<int, int>> ordered_arcs;
3278 for (const auto& [score, arc] : arc_by_decreasing_lp_values) {
3279 if (tails[arc] == -1) continue;
3280 if (heads[arc] == -1) continue;
3281 ordered_arcs.push_back({tails[arc], heads[arc]});
3282 }
3283 std::vector<int> subset_data;
3284 std::vector<absl::Span<const int>> subsets;
3285 GenerateInterestingSubsets(num_nodes, ordered_arcs,
3286 /*stop_at_num_components=*/1, &subset_data,
3287 &subsets);
3288
3289 // Process each subsets and add any violated cut.
3290 std::vector<bool> in_subset(num_nodes, false);
3291 for (const absl::Span<const int> subset : subsets) {
3292 DCHECK(!subset.empty());
3293 DCHECK_LE(subset.size(), num_nodes);
3294
3295 // Initialize "in_subset" and the subset demands.
3296 for (const int n : subset) in_subset[n] = true;
3297
3298 IntegerValue min_incoming_flow;
3299 IntegerValue min_outgoing_flow;
3300 get_flows(in_subset, &min_incoming_flow, &min_outgoing_flow);
3301
3302 // We will sum the offset of all incoming/outgoing arc capacities.
3303 // Note that all arcs with a non-zero offset are part of relevant_arcs.
3304 IntegerValue incoming_offset(0);
3305 IntegerValue outgoing_offset(0);
3306
3307 // Compute the current flow in and out of the subset.
3308 //
3309 // This can take a significant portion of the running time, it is why it is
3310 // faster to do it only on arcs with non-zero lp values which should be in
3311 // linear number rather than the total number of arc which can be quadratic.
3312 double lp_outgoing_flow = 0.0;
3313 double lp_incoming_flow = 0.0;
3314 for (const auto arc : relevant_arcs) {
3315 if (arc.tail != -1 && in_subset[arc.tail]) {
3316 if (arc.head == -1 || !in_subset[arc.head]) {
3317 incoming_offset += arc.offset;
3318 lp_outgoing_flow += arc.lp_value;
3319 }
3320 } else {
3321 if (arc.head != -1 && in_subset[arc.head]) {
3322 outgoing_offset += arc.offset;
3323 lp_incoming_flow += arc.lp_value;
3324 }
3325 }
3326 }
3327
3328 // If the gcd is greater than one, because all variables are integer we
3329 // can round the flow lower bound to the next multiple of the gcd.
3330 //
3331 // TODO(user): Alternatively, try MIR heuristics if the coefficients in
3332 // the capacities are not all the same.
3333 if (gcd > 1) {
3334 const IntegerValue test_incoming = min_incoming_flow - incoming_offset;
3335 const IntegerValue new_incoming =
3336 CeilRatio(test_incoming, IntegerValue(gcd)) * IntegerValue(gcd);
3337 const IntegerValue incoming_delta = new_incoming - test_incoming;
3338 if (incoming_delta > 0) min_incoming_flow += incoming_delta;
3339 }
3340 if (gcd > 1) {
3341 const IntegerValue test_outgoing = min_outgoing_flow - outgoing_offset;
3342 const IntegerValue new_outgoing =
3343 CeilRatio(test_outgoing, IntegerValue(gcd)) * IntegerValue(gcd);
3344 const IntegerValue outgoing_delta = new_outgoing - test_outgoing;
3345 if (outgoing_delta > 0) min_outgoing_flow += outgoing_delta;
3346 }
3347
3348 if (lp_incoming_flow < ToDouble(min_incoming_flow) - 1e-6) {
3349 VLOG(2) << "INCOMING CUT " << lp_incoming_flow
3350 << " >= " << min_incoming_flow << " size " << subset.size()
3351 << " offset " << incoming_offset << " gcd " << gcd;
3352 LinearConstraintBuilder cut(model, min_incoming_flow, kMaxIntegerValue);
3353 for (int i = 0; i < tails.size(); ++i) {
3354 if ((tails[i] == -1 || !in_subset[tails[i]]) &&
3355 (heads[i] != -1 && in_subset[heads[i]])) {
3356 cut.AddTerm(arc_capacities[i], 1.0);
3357 }
3358 }
3359 manager->AddCut(cut.Build(), "IncomingFlow");
3360 }
3361
3362 if (lp_outgoing_flow < ToDouble(min_outgoing_flow) - 1e-6) {
3363 VLOG(2) << "OUGOING CUT " << lp_outgoing_flow
3364 << " >= " << min_outgoing_flow << " size " << subset.size()
3365 << " offset " << outgoing_offset << " gcd " << gcd;
3366 LinearConstraintBuilder cut(model, min_outgoing_flow, kMaxIntegerValue);
3367 for (int i = 0; i < tails.size(); ++i) {
3368 if ((tails[i] != -1 && in_subset[tails[i]]) &&
3369 (heads[i] == -1 || !in_subset[heads[i]])) {
3370 cut.AddTerm(arc_capacities[i], 1.0);
3371 }
3372 }
3373 manager->AddCut(cut.Build(), "OutgoingFlow");
3374 }
3375
3376 // Sparse clean up.
3377 for (const int n : subset) in_subset[n] = false;
3378 }
3379}
3380
3382 int num_nodes, const std::vector<int>& tails, const std::vector<int>& heads,
3383 const std::vector<AffineExpression>& arc_capacities,
3384 std::function<void(const std::vector<bool>& in_subset,
3385 IntegerValue* min_incoming_flow,
3386 IntegerValue* min_outgoing_flow)>
3387 get_flows,
3388 Model* model) {
3389 CutGenerator result;
3390 for (const AffineExpression expr : arc_capacities) {
3391 if (!expr.IsConstant()) result.vars.push_back(expr.var);
3392 }
3393 result.generate_cuts = [=](LinearConstraintManager* manager) {
3394 SeparateFlowInequalities(num_nodes, tails, heads, arc_capacities, get_flows,
3395 manager->LpValues(), manager, model);
3396 return true;
3397 };
3398 return result;
3399}
3400
3401} // namespace sat
3402} // namespace operations_research
std::vector< std::vector< T > > FindConnectedComponents()
bool AddEdge(T node1, T node2)
Definition model.h:345
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:39
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:53
ArcIndex AddArcWithCapacity(NodeIndex tail, NodeIndex head, FlowQuantity capacity)
Definition max_flow.cc:27
Status Solve(NodeIndex source, NodeIndex sink)
Definition max_flow.cc:60
void GetSourceSideMinCut(std::vector< NodeIndex > *result)
Definition max_flow.cc:119
void Update(int new_bound, std::string name, absl::Span< const int > cannot_be_first={}, absl::Span< const int > cannot_be_last={})
void AppendToLastVector(const V &value)
Definition util.h:944
int Add(absl::Span< const V > values)
Definition util.h:935
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
IntegerVariable GetLiteralView(Literal lit) const
Definition integer.h:265
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
Definition integer.h:1657
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
Definition integer.h:1650
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, std::string extra_info="")
int ComputeTightMinOutgoingFlow(absl::Span< const int > subset)
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)
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
static std::unique_ptr< RouteRelationsHelper > Create(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, const ConditionalLinear2Bounds &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
void RemoveArcs(absl::Span< const int > sorted_arc_indices)
RoutesConstraintProto_NodeExpressions NodeExpressions
::int32_t routing_cut_subset_size_for_shortest_paths_bound() const
::int32_t routing_cut_subset_size_for_exact_binary_relation_bound() const
::int32_t routing_cut_subset_size_for_binary_relation_bound() const
::int32_t routing_cut_subset_size_for_tight_binary_relation_bound() const
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)
const VariablesAssignment & Assignment() const
Definition sat_base.h:654
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition graph.h:1436
void Build(std::vector< ArcIndexType > *permutation) final
Definition graph.h:1483
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
H AbslHashValue(H h, std::shared_ptr< Variable > i)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
RoutingCumulExpressions DetectDimensionsAndCumulExpressions(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, const ConditionalLinear2Bounds &binary_relation_repository)
bool PropagateLocalBounds(const IntegerTrail &integer_trail, const RootLevelLinear2Bounds &root_level_bounds, const ConditionalLinear2Bounds &repository, const ImpliedBounds &implied_bounds, Literal lit, const absl::flat_hash_map< IntegerVariable, IntegerValue > &input, absl::flat_hash_map< IntegerVariable, IntegerValue > *output)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
std::vector< int > ComputeGomoryHuTree(int num_nodes, absl::Span< const ArcWithLpValue > relevant_arcs)
const LiteralIndex kNoLiteralIndex(-1)
std::pair< int, int > MaybeFillMissingRoutesConstraintNodeExpressions(const CpModelProto &input_model, CpModelProto &output_model)
int TryAllSubsets(std::string cut_name, absl::Span< const int > subset_data, std::vector< absl::Span< const int > > subsets, RoutingCutHelper &helper, LinearConstraintManager *manager)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
bool WriteModelProtoToFile(const M &proto, absl::string_view filename)
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)
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)
void SeparateSubtourInequalities(RoutingCutHelper &helper, LinearConstraintManager *manager)
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 SeparateFlowInequalities(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const AffineExpression > arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, const util_intops::StrongVector< IntegerVariable, double > &lp_values, LinearConstraintManager *manager, Model *model)
void SymmetrizeArcs(std::vector< ArcWithLpValue > *arcs)
PositiveOnlyIndex GetPositiveOnlyIndex(IntegerVariable var)
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)
bool AtMinOrMaxInt64I(IntegerValue t)
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)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
double ToDouble(IntegerValue value)
OR-Tools root namespace.
LinearRange operator==(const LinearExpr &lhs, const LinearExpr &rhs)
ClosedInterval::Iterator end(ClosedInterval interval)
ClosedInterval::Iterator begin(ClosedInterval interval)
STL namespace.
ABSL_FLAG(bool, cp_model_dump_routes_support_graphs, false, "DEBUG ONLY. When set to true, SolveCpModel() dumps the arcs with " "non-zero LP values of the routes constraints, at decision level 0, " "which are used to subsequently generate cuts. The values are " "written as a SupportGraphProto in text format to " "'FLAGS_cp_model_dump_prefix'support_graph_{counter}.pb.txt.")
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
Definition cuts.h:60
std::vector< IntegerVariable > vars
Definition cuts.h:59
std::vector< AffineExpression > flat_node_dim_expressions