28#include "absl/algorithm/container.h"
29#include "absl/cleanup/cleanup.h"
30#include "absl/container/flat_hash_map.h"
31#include "absl/container/flat_hash_set.h"
32#include "absl/log/check.h"
33#include "absl/random/distributions.h"
34#include "absl/strings/str_cat.h"
35#include "absl/types/span.h"
69void ComputeMinLowerBoundOfSharedVariables(
70 const absl::flat_hash_map<IntegerVariable, IntegerValue>& new_lbs,
71 absl::flat_hash_map<IntegerVariable, IntegerValue>* current_lbs) {
72 if (new_lbs.empty()) current_lbs->clear();
73 for (
auto it = current_lbs->begin(); it != current_lbs->end();) {
74 const IntegerVariable var = it->first;
75 auto other_it = new_lbs.find(var);
76 if (other_it == new_lbs.end()) {
81 current_lbs->erase(it++);
84 it->second = std::min(it->second, other_it->second);
93 int num_nodes,
const std::vector<int>& tails,
const std::vector<int>& heads,
94 const std::vector<Literal>& literals,
Model* model)
98 binary_relation_repository_(
100 trail_(*model->GetOrCreate<
Trail>()),
102 in_subset_(num_nodes, false),
103 index_in_subset_(num_nodes, -1),
104 incoming_arc_indices_(num_nodes),
105 outgoing_arc_indices_(num_nodes),
106 reachable_(num_nodes, false),
107 next_reachable_(num_nodes, false),
108 node_var_lower_bounds_(num_nodes),
109 next_node_var_lower_bounds_(num_nodes) {}
112 absl::Span<const int> subset) {
113 DCHECK_GE(subset.size(), 1);
114 DCHECK(absl::c_all_of(in_subset_, [](
bool b) {
return !
b; }));
115 DCHECK(absl::c_all_of(incoming_arc_indices_,
116 [](
const auto& v) {
return v.empty(); }));
117 DCHECK(absl::c_all_of(reachable_, [](
bool b) {
return !
b; }));
118 DCHECK(absl::c_all_of(next_reachable_, [](
bool b) {
return !
b; }));
119 DCHECK(absl::c_all_of(node_var_lower_bounds_,
120 [](
const auto& m) {
return m.empty(); }));
121 DCHECK(absl::c_all_of(next_node_var_lower_bounds_,
122 [](
const auto& m) {
return m.empty(); }));
124 for (
const int n : subset) {
125 in_subset_[n] =
true;
127 reachable_[n] =
true;
129 const int num_arcs = tails_.size();
130 for (
int i = 0;
i < num_arcs; ++
i) {
131 if (in_subset_[tails_[
i]] && in_subset_[heads_[
i]] &&
132 heads_[
i] != tails_[
i]) {
133 incoming_arc_indices_[heads_[
i]].push_back(
i);
138 int longest_path_length = 1;
139 absl::flat_hash_map<IntegerVariable, IntegerValue> tmp_lbs;
140 while (longest_path_length < subset.size()) {
141 bool at_least_one_next_node_reachable =
false;
142 for (
const int head : subset) {
143 bool is_reachable =
false;
144 for (
const int incoming_arc_index : incoming_arc_indices_[head]) {
145 const int tail = tails_[incoming_arc_index];
146 const Literal lit = literals_[incoming_arc_index];
147 if (!reachable_[tail])
continue;
151 if (!binary_relation_repository_.PropagateLocalBounds(
152 integer_trail_, lit, node_var_lower_bounds_[tail], &tmp_lbs)) {
159 next_node_var_lower_bounds_[head] = tmp_lbs;
162 ComputeMinLowerBoundOfSharedVariables(
163 tmp_lbs, &next_node_var_lower_bounds_[head]);
167 next_reachable_[head] = is_reachable;
168 if (next_reachable_[head]) at_least_one_next_node_reachable =
true;
170 if (!at_least_one_next_node_reachable) {
173 std::swap(reachable_, next_reachable_);
174 std::swap(node_var_lower_bounds_, next_node_var_lower_bounds_);
175 for (
const int n : subset) {
176 next_node_var_lower_bounds_[n].clear();
178 ++longest_path_length;
182 int max_longest_paths = 0;
184 for (
const int n : subset) {
185 in_subset_[n] =
false;
186 incoming_arc_indices_[n].clear();
187 if (reachable_[n]) ++max_longest_paths;
188 reachable_[n] =
false;
189 next_reachable_[n] =
false;
190 node_var_lower_bounds_[n].clear();
191 next_node_var_lower_bounds_[n].clear();
193 return GetMinOutgoingFlow(subset.size(), longest_path_length,
197int MinOutgoingFlowHelper::GetMinOutgoingFlow(
int subset_size,
198 int longest_path_length,
199 int max_longest_paths) {
200 if (max_longest_paths * longest_path_length < subset_size) {
204 DCHECK_GT(longest_path_length, 1);
207 return max_longest_paths +
209 subset_size - max_longest_paths * longest_path_length,
210 longest_path_length - 1);
221 return node_set == p.node_set && last_node == p.last_node;
224 template <
typename H>
226 return H::combine(std::move(h), p.node_set, p.last_node);
230struct PathVariableBounds {
231 absl::flat_hash_set<int> incoming_arc_indices;
232 absl::flat_hash_map<IntegerVariable, absl::flat_hash_map<int, IntegerValue>>
233 lower_bound_by_var_and_arc_index;
238 absl::Span<const int> subset) {
239 DCHECK_GE(subset.size(), 1);
240 DCHECK_LE(subset.size(), 32);
241 DCHECK(absl::c_all_of(index_in_subset_, [](
int i) {
return i == -1; }));
242 DCHECK(absl::c_all_of(outgoing_arc_indices_,
243 [](
const auto& v) {
return v.empty(); }));
245 std::vector<int> longest_path_length_by_end_node(subset.size(), 1);
246 for (
int i = 0;
i < subset.size(); ++
i) {
247 index_in_subset_[subset[
i]] =
i;
249 for (
int i = 0;
i < tails_.size(); ++
i) {
250 if (index_in_subset_[tails_[
i]] != -1 &&
251 index_in_subset_[heads_[
i]] != -1 && heads_[
i] != tails_[
i]) {
252 outgoing_arc_indices_[tails_[
i]].push_back(
i);
256 absl::flat_hash_map<Path, PathVariableBounds> path_var_bounds;
257 std::vector<Path> paths;
258 std::vector<Path> next_paths;
259 for (
int i = 0;
i < subset.size(); ++
i) {
261 {.node_set =
static_cast<uint32_t
>(1 <<
i), .last_node = subset[
i]});
263 int longest_path_length = 1;
264 for (
int path_length = 1; path_length <= subset.size(); ++path_length) {
265 for (
const Path& path : paths) {
269 const auto& var_bounds = path_var_bounds[path];
270 absl::flat_hash_map<IntegerVariable, IntegerValue> lower_bound_by_var;
271 for (
const auto& [var, lower_bound_by_arc_index] :
272 var_bounds.lower_bound_by_var_and_arc_index) {
277 if (lower_bound_by_arc_index.size() !=
278 var_bounds.incoming_arc_indices.size()) {
281 IntegerValue lb = lower_bound_by_arc_index.begin()->second;
282 for (
const auto& [_, lower_bound] : lower_bound_by_arc_index) {
283 lb = std::min(lb, lower_bound);
285 if (lb > integer_trail_.LevelZeroLowerBound(var)) {
286 lower_bound_by_var[var] = lb;
289 path_var_bounds.erase(path);
290 auto get_lower_bound = [&](IntegerVariable var) {
291 const auto it = lower_bound_by_var.find(var);
292 if (it != lower_bound_by_var.end())
return it->second;
293 return integer_trail_.LevelZeroLowerBound(var);
295 auto get_upper_bound = [&](IntegerVariable var) {
298 bool feasible_path =
true;
299 for (
const auto& [var, lb] : lower_bound_by_var) {
300 if (get_upper_bound(var) < lb) {
301 feasible_path =
false;
305 if (!feasible_path)
continue;
307 longest_path_length = path_length;
308 longest_path_length_by_end_node[index_in_subset_[path.last_node]] =
313 for (
const int outgoing_arc_index :
314 outgoing_arc_indices_[path.last_node]) {
315 const int head = heads_[outgoing_arc_index];
316 const int head_index_in_subset = index_in_subset_[head];
317 DCHECK_NE(head_index_in_subset, -1);
318 if (path.node_set & (1 << head_index_in_subset))
continue;
319 const Path new_path = {
320 .node_set = path.node_set | (1 << head_index_in_subset),
322 if (!path_var_bounds.contains(new_path)) {
323 next_paths.push_back(new_path);
325 auto& new_var_bounds = path_var_bounds[new_path];
326 new_var_bounds.incoming_arc_indices.insert(outgoing_arc_index);
327 auto update_lower_bound_by_var_and_arc_index =
328 [&](IntegerVariable var,
int arc_index, IntegerValue lb) {
329 auto& lower_bound_by_arc_index =
330 new_var_bounds.lower_bound_by_var_and_arc_index[var];
331 auto it = lower_bound_by_arc_index.find(arc_index);
332 if (it != lower_bound_by_arc_index.end()) {
333 it->second = std::max(it->second, lb);
335 lower_bound_by_arc_index[arc_index] = lb;
338 auto update_upper_bound_by_var_and_arc_index =
339 [&](IntegerVariable var,
int arc_index, IntegerValue ub) {
340 update_lower_bound_by_var_and_arc_index(
NegationOf(var),
344 IntegerValue lhs, IntegerValue rhs) {
345 if (a.coeff == 0)
return;
346 a.MakeCoeffPositive();
347 b.MakeCoeffPositive();
350 lhs = lhs -
b.coeff * get_upper_bound(
b.var);
351 rhs = rhs -
b.coeff * get_lower_bound(
b.var);
352 update_lower_bound_by_var_and_arc_index(
354 update_upper_bound_by_var_and_arc_index(
357 const Literal lit = literals_[outgoing_arc_index];
358 for (
const int relation_index :
359 binary_relation_repository_.relation_indices(lit)) {
360 const auto& r = binary_relation_repository_.relation(relation_index);
361 update_var_bounds(outgoing_arc_index, r.a, r.b, r.lhs, r.rhs);
362 update_var_bounds(outgoing_arc_index, r.b, r.a, r.lhs, r.rhs);
366 std::swap(paths, next_paths);
370 int max_longest_paths = 0;
371 for (
int i = 0;
i < subset.size(); ++
i) {
372 if (longest_path_length_by_end_node[
i] == longest_path_length) {
377 for (
const int n : subset) {
378 index_in_subset_[n] = -1;
379 outgoing_arc_indices_[n].clear();
381 return GetMinOutgoingFlow(subset.size(), longest_path_length,
387class OutgoingCutHelper {
389 OutgoingCutHelper(
int num_nodes,
bool is_route_constraint, int64_t capacity,
390 absl::Span<const int64_t> demands,
391 absl::Span<const int> tails, absl::Span<const int> heads,
392 absl::Span<const Literal> literals,
Model* model)
393 : num_nodes_(num_nodes),
394 is_route_constraint_(is_route_constraint),
396 demands_(demands.begin(), demands.end()),
397 tails_(tails.begin(), tails.end()),
398 heads_(heads.begin(), heads.end()),
399 literals_(literals.begin(), literals.end()),
400 literal_lp_values_(literals.size()),
401 params_(*model->GetOrCreate<SatParameters>()),
402 trail_(*model->GetOrCreate<
Trail>()),
405 in_subset_(num_nodes, false),
406 min_outgoing_flow_helper_(num_nodes, tails_, heads_, literals_, model) {
409 for (
const int64_t demand : demands) total_demand_ += demand;
410 complement_of_subset_.reserve(num_nodes_);
413 int num_nodes()
const {
return num_nodes_; }
415 bool is_route_constraint()
const {
return is_route_constraint_; }
418 absl::Span<const ArcWithLpValue> relevant_arcs()
const {
419 return relevant_arcs_;
424 absl::Span<const std::pair<int, int>> ordered_arcs()
const {
425 return ordered_arcs_;
430 void InitializeForNewLpSolution(LinearConstraintManager* manager);
434 absl::Span<const ArcWithLpValue> SymmetrizedRelevantArcs() {
435 symmetrized_relevant_arcs_ = relevant_arcs_;
437 return symmetrized_relevant_arcs_;
441 bool TrySubsetCut(std::string name, absl::Span<const int> subset,
442 LinearConstraintManager* manager);
455 bool TryBlossomSubsetCut(std::string name,
456 absl::Span<const ArcWithLpValue> symmetrized_edges,
457 absl::Span<const int> subset,
458 LinearConstraintManager* manager);
463 void FilterFalseArcsAtLevelZero();
471 bool AddOutgoingCut(LinearConstraintManager* manager, std::string name,
472 int subset_size,
const std::vector<bool>& in_subset,
473 int64_t rhs_lower_bound,
int ignore_arcs_with_head);
475 const int num_nodes_;
476 const bool is_route_constraint_;
477 const int64_t capacity_;
478 std::vector<int64_t> demands_;
479 std::vector<int> tails_;
480 std::vector<int> heads_;
481 std::vector<Literal> literals_;
482 std::vector<double> literal_lp_values_;
483 std::vector<ArcWithLpValue> relevant_arcs_;
484 std::vector<ArcWithLpValue> symmetrized_relevant_arcs_;
485 std::vector<std::pair<int, int>> ordered_arcs_;
487 const SatParameters& params_;
489 ModelRandomGenerator* random_;
490 IntegerEncoder* encoder_;
492 int64_t total_demand_ = 0;
493 std::vector<bool> in_subset_;
494 std::vector<int> complement_of_subset_;
496 MaxBoundedSubsetSum max_bounded_subset_sum_;
497 MaxBoundedSubsetSumExact max_bounded_subset_sum_exact_;
498 MinOutgoingFlowHelper min_outgoing_flow_helper_;
501void OutgoingCutHelper::FilterFalseArcsAtLevelZero() {
505 const int size =
static_cast<int>(tails_.size());
506 const VariablesAssignment& assignment = trail_.
Assignment();
507 for (
int i = 0;
i < size; ++
i) {
508 if (assignment.LiteralIsFalse(literals_[i]))
continue;
509 tails_[new_size] = tails_[
i];
510 heads_[new_size] = heads_[
i];
511 literals_[new_size] = literals_[
i];
514 if (new_size < size) {
515 tails_.resize(new_size);
516 heads_.resize(new_size);
517 literals_.resize(new_size);
518 literal_lp_values_.resize(new_size);
522void OutgoingCutHelper::InitializeForNewLpSolution(
523 LinearConstraintManager* manager) {
524 FilterFalseArcsAtLevelZero();
528 relevant_arcs_.clear();
531 const auto& lp_values = manager->LpValues();
532 std::vector<std::pair<double, int>> relevant_arc_by_decreasing_lp_values;
533 for (
int i = 0;
i < literals_.size(); ++
i) {
535 const IntegerVariable direct_view = encoder_->
GetLiteralView(literals_[i]);
536 if (direct_view != kNoIntegerVariable) {
537 lp_value = lp_values[direct_view];
540 1.0 - lp_values[encoder_->
GetLiteralView(literals_[i].Negated())];
542 literal_lp_values_[
i] = lp_value;
544 if (lp_value < 1e-6)
continue;
545 relevant_arcs_.push_back({tails_[
i], heads_[
i], lp_value});
546 relevant_arc_by_decreasing_lp_values.push_back({lp_value,
i});
548 std::sort(relevant_arc_by_decreasing_lp_values.begin(),
549 relevant_arc_by_decreasing_lp_values.end(),
550 std::greater<std::pair<double, int>>());
552 ordered_arcs_.clear();
553 for (
const auto& [score, arc] : relevant_arc_by_decreasing_lp_values) {
554 ordered_arcs_.push_back({tails_[arc], heads_[arc]});
558bool OutgoingCutHelper::AddOutgoingCut(LinearConstraintManager* manager,
559 std::string name,
int subset_size,
560 const std::vector<bool>& in_subset,
561 int64_t rhs_lower_bound,
562 int ignore_arcs_with_head) {
569 int num_optional_nodes_in = 0;
570 int num_optional_nodes_out = 0;
571 int optional_loop_in = -1;
572 int optional_loop_out = -1;
573 for (
int i = 0;
i < tails_.size(); ++
i) {
574 if (tails_[i] != heads_[i])
continue;
575 if (in_subset[tails_[i]]) {
576 num_optional_nodes_in++;
577 if (optional_loop_in == -1 ||
578 literal_lp_values_[i] < literal_lp_values_[optional_loop_in]) {
579 optional_loop_in =
i;
582 num_optional_nodes_out++;
583 if (optional_loop_out == -1 ||
584 literal_lp_values_[i] < literal_lp_values_[optional_loop_out]) {
585 optional_loop_out =
i;
592 if (num_optional_nodes_in + num_optional_nodes_out > 0) {
593 CHECK_GE(rhs_lower_bound, 1);
595 ignore_arcs_with_head = -1;
600 LinearConstraintBuilder outgoing(encoder_, IntegerValue(rhs_lower_bound),
604 for (
int i = 0;
i < tails_.size(); ++
i) {
605 if (in_subset[tails_[i]] && !in_subset[heads_[i]]) {
606 if (heads_[i] == ignore_arcs_with_head)
continue;
607 CHECK(outgoing.AddLiteralTerm(literals_[i], IntegerValue(1)));
612 if (num_optional_nodes_in + num_optional_nodes_out > 0) {
614 if (num_optional_nodes_in == subset_size &&
615 (optional_loop_in == -1 ||
616 literal_lp_values_[optional_loop_in] > 1.0 - 1e-6)) {
619 if (num_optional_nodes_out == num_nodes_ - subset_size &&
620 (optional_loop_out == -1 ||
621 literal_lp_values_[optional_loop_out] > 1.0 - 1e-6)) {
626 if (num_optional_nodes_in == subset_size) {
627 CHECK(outgoing.AddLiteralTerm(literals_[optional_loop_in],
632 if (num_optional_nodes_out == num_nodes_ - subset_size) {
633 CHECK(outgoing.AddLiteralTerm(literals_[optional_loop_out],
638 return manager->AddCut(outgoing.Build(), name);
641bool OutgoingCutHelper::TrySubsetCut(std::string name,
642 absl::Span<const int> subset,
643 LinearConstraintManager* manager) {
644 DCHECK_GE(subset.size(), 1);
645 DCHECK_LT(subset.size(), num_nodes_);
648 bool contain_depot =
false;
649 for (
const int n : subset) {
650 in_subset_[n] =
true;
651 if (n == 0 && is_route_constraint_) {
652 contain_depot =
true;
661 int64_t min_outgoing_flow = 1;
669 const int subset_or_complement_size =
670 contain_depot ? num_nodes_ - subset.size() : subset.size();
671 if (subset_or_complement_size <=
672 params_.routing_cut_subset_size_for_binary_relation_bound() &&
673 subset_or_complement_size >=
674 params_.routing_cut_subset_size_for_tight_binary_relation_bound()) {
677 complement_of_subset_.clear();
678 for (
int i = 0;
i < num_nodes_; ++
i) {
679 if (!in_subset_[i]) complement_of_subset_.push_back(i);
682 complement_of_subset_);
686 if (bound > min_outgoing_flow) {
687 absl::StrAppend(&name,
"Automatic");
688 min_outgoing_flow =
bound;
691 if (subset_or_complement_size <
692 params_.routing_cut_subset_size_for_tight_binary_relation_bound()) {
695 complement_of_subset_.clear();
696 for (
int i = 0;
i < num_nodes_; ++
i) {
697 if (!in_subset_[i]) complement_of_subset_.push_back(i);
700 complement_of_subset_);
704 if (bound > min_outgoing_flow) {
705 absl::StrAppend(&name,
"AutomaticTight");
706 min_outgoing_flow =
bound;
711 std::vector<int> to_ignore_candidates;
712 if (!demands_.empty()) {
716 int64_t has_excessive_demands =
false;
717 int64_t has_negative_demands =
false;
718 int64_t sum_of_elements = 0;
719 std::vector<int64_t> elements;
720 const auto process_demand = [&](int64_t d) {
721 if (d < 0) has_negative_demands =
true;
722 if (d > capacity_) has_excessive_demands =
true;
723 sum_of_elements += d;
724 elements.push_back(d);
727 for (
int n = 0; n < num_nodes_; ++n) {
728 if (!in_subset_[n]) {
729 process_demand(demands_[n]);
733 for (
const int n : subset) {
734 process_demand(demands_[n]);
739 if (has_excessive_demands)
return false;
746 bool exact_was_used =
false;
747 int64_t tightened_capacity = capacity_;
748 if (!has_negative_demands && sum_of_elements > capacity_) {
749 max_bounded_subset_sum_.
Reset(capacity_);
750 for (
const int64_t e : elements) {
751 max_bounded_subset_sum_.
Add(e);
753 tightened_capacity = max_bounded_subset_sum_.
CurrentMax();
758 elements.size(), capacity_) < params_.routing_cut_dp_effort()) {
759 const int64_t exact =
760 max_bounded_subset_sum_exact_.
MaxSubsetSum(elements, capacity_);
761 CHECK_LE(exact, tightened_capacity);
762 if (exact < tightened_capacity) {
763 tightened_capacity = exact;
764 exact_was_used =
true;
769 const int64_t flow_lower_bound =
770 MathUtil::CeilOfRatio(sum_of_elements, tightened_capacity);
771 if (flow_lower_bound > min_outgoing_flow) {
772 min_outgoing_flow = flow_lower_bound;
773 absl::StrAppend(&name, exact_was_used ?
"Tightened" :
"Capacity");
776 if (!contain_depot && flow_lower_bound >= min_outgoing_flow) {
782 const int64_t last_bin_fillin =
783 sum_of_elements - (flow_lower_bound - 1) * tightened_capacity;
784 const int64_t space_left = capacity_ - last_bin_fillin;
785 DCHECK_GE(space_left, 0);
786 DCHECK_LT(space_left, capacity_);
809 for (
int n = 1; n < num_nodes_; ++n) {
810 if (in_subset_[n])
continue;
811 if (demands_[n] > space_left) {
812 to_ignore_candidates.push_back(n);
819 int ignore_arcs_with_head = -1;
820 if (!to_ignore_candidates.empty()) {
821 absl::StrAppend(&name,
"Lifted");
824 absl::flat_hash_map<int, double> candidate_weights;
825 for (
const int n : to_ignore_candidates) candidate_weights[n] = 0;
826 for (
const auto arc : relevant_arcs_) {
827 if (in_subset_[arc.tail] && !in_subset_[arc.head]) {
828 auto it = candidate_weights.find(arc.head);
829 if (it == candidate_weights.end())
continue;
830 it->second += arc.lp_value;
835 std::vector<int> bests;
836 double best_weight = 0.0;
837 for (
const int n : to_ignore_candidates) {
838 const double weight = candidate_weights.at(n);
839 if (bests.empty() || weight > best_weight) {
842 best_weight = weight;
843 }
else if (weight == best_weight) {
849 ignore_arcs_with_head =
852 : bests[absl::Uniform<int>(*random_, 0, bests.size())];
865 double outgoing_flow = 0.0;
866 for (
const auto arc : relevant_arcs_) {
867 if (in_subset_[arc.tail] && !in_subset_[arc.head]) {
868 if (arc.head == ignore_arcs_with_head)
continue;
869 outgoing_flow += arc.lp_value;
875 if (outgoing_flow + 1e-2 < min_outgoing_flow) {
876 result = AddOutgoingCut(manager, name, subset.size(), in_subset_,
878 ignore_arcs_with_head);
882 for (
const int n : subset) in_subset_[n] =
false;
887bool OutgoingCutHelper::TryBlossomSubsetCut(
888 std::string name, absl::Span<const ArcWithLpValue> symmetrized_edges,
889 absl::Span<const int> subset, LinearConstraintManager* manager) {
890 DCHECK_GE(subset.size(), 1);
891 DCHECK_LT(subset.size(), num_nodes_);
894 for (
const int n : subset) in_subset_[n] =
true;
895 auto cleanup = ::absl::MakeCleanup([subset,
this]() {
896 for (
const int n : subset) in_subset_[n] =
false;
901 absl::flat_hash_set<std::pair<int, int>> special_edges;
902 int num_inverted = 0;
903 double sum_inverted = 0.0;
905 double best_change = 1.0;
906 ArcWithLpValue
const* best_swap =
nullptr;
907 for (
const ArcWithLpValue& arc : symmetrized_edges) {
908 if (in_subset_[arc.tail] == in_subset_[arc.head])
continue;
910 if (arc.lp_value > 0.5) {
912 special_edges.insert({arc.tail, arc.head});
913 sum_inverted += 1.0 - arc.lp_value;
918 const double change = std::abs(2 * arc.lp_value - 1.0);
919 if (change < best_change) {
920 best_change = change;
927 if (num_inverted % 2 == 0) {
928 if (best_swap ==
nullptr)
return false;
929 if (special_edges.contains({best_swap->tail, best_swap->head})) {
931 special_edges.erase({best_swap->tail, best_swap->head});
932 sum_inverted -= (1.0 - best_swap->lp_value);
933 sum += best_swap->lp_value;
936 special_edges.insert({best_swap->tail, best_swap->head});
937 sum_inverted += (1.0 - best_swap->lp_value);
938 sum -= best_swap->lp_value;
941 if (sum + sum_inverted > 0.99)
return false;
945 if (!demands_.empty()) {
946 for (
const auto [tail, head] : special_edges) {
947 if (tail == 0)
return false;
954 int best_optional_index = -1;
955 if (special_edges.size() == 1) {
956 int num_other_optional = 0;
957 const auto [special_tail, special_head] = *special_edges.begin();
958 for (
int i = 0;
i < tails_.size(); ++
i) {
959 if (tails_[i] != heads_[i])
continue;
960 if (tails_[i] != special_head && tails_[i] != special_tail) {
961 ++num_other_optional;
962 if (best_optional_index == -1 ||
963 literal_lp_values_[i] < literal_lp_values_[best_optional_index]) {
964 best_optional_index =
i;
968 if (num_other_optional + 2 < num_nodes_) best_optional_index = -1;
975 int num_actual_inverted = 0;
976 absl::flat_hash_set<std::pair<int, int>> processed_arcs;
977 LinearConstraintBuilder builder(encoder_, IntegerValue(1), kMaxIntegerValue);
981 if (best_optional_index != -1) {
982 absl::StrAppend(&name,
"_opt");
987 CHECK(builder.AddLiteralTerm(literals_[best_optional_index],
991 for (
int i = 0;
i < tails_.size(); ++
i) {
992 if (tails_[i] == heads_[i])
continue;
993 if (in_subset_[tails_[i]] == in_subset_[heads_[i]])
continue;
995 const std::pair<int, int> key = {tails_[
i], heads_[
i]};
996 const std::pair<int, int> r_key = {heads_[
i], tails_[
i]};
997 const std::pair<int, int> s_key = std::min(key, r_key);
998 if (special_edges.contains(s_key) && !processed_arcs.contains(key)) {
999 processed_arcs.insert(key);
1000 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(-1)));
1001 if (!processed_arcs.contains(r_key)) {
1002 ++num_actual_inverted;
1008 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(1)));
1010 builder.AddConstant(IntegerValue(num_actual_inverted));
1011 if (num_actual_inverted % 2 == 0)
return false;
1013 return manager->AddCut(builder.Build(), name);
1019 absl::Span<
const std::pair<int, int>> arcs,
1020 int stop_at_num_components,
1021 std::vector<int>* subset_data,
1022 std::vector<absl::Span<const int>>* subsets) {
1031 int num_components = num_nodes;
1032 std::vector<int> parent(num_nodes);
1033 std::vector<int> root(num_nodes);
1034 for (
int i = 0;
i < num_nodes; ++
i) {
1038 auto get_root_and_compress_path = [&root](
int node) {
1040 while (root[r] != r) r = root[r];
1041 while (root[node] != r) {
1042 const int next = root[node];
1048 for (
const auto& [initial_tail, initial_head] : arcs) {
1049 if (num_components <= stop_at_num_components)
break;
1050 const int tail = get_root_and_compress_path(initial_tail);
1051 const int head = get_root_and_compress_path(initial_head);
1055 const int new_node = parent.size();
1056 parent.push_back(new_node);
1057 parent[head] = new_node;
1058 parent[tail] = new_node;
1062 root.push_back(new_node);
1063 root[head] = new_node;
1064 root[tail] = new_node;
1079 std::vector<int>* subset_data,
1080 std::vector<absl::Span<const int>>* subsets,
1085 const int num_nodes = parent.size();
1086 subset_data->resize(std::min(num_nodes, node_limit));
1091 for (
int i = 0;
i < num_nodes; ++
i) {
1092 if (parent[
i] !=
i) {
1100 std::vector<int> subtree_starts(num_nodes, -1);
1101 std::vector<int> stack;
1102 stack.reserve(num_nodes);
1103 for (
int i = 0;
i < parent.size(); ++
i) {
1104 if (parent[
i] !=
i)
continue;
1107 while (!stack.empty()) {
1108 const int node = stack.back();
1111 if (subtree_starts[node] >= 0) {
1113 if (node < node_limit) {
1114 (*subset_data)[out_index++] = node;
1116 const int start = subtree_starts[node];
1117 const int size = out_index - start;
1118 subsets->push_back(absl::MakeSpan(&(*subset_data)[start], size));
1123 subtree_starts[node] = out_index;
1124 for (
const int child : graph[node]) {
1125 stack.push_back(child);
1132 int num_nodes, absl::Span<const ArcWithLpValue> relevant_arcs) {
1136 for (
const auto& [tail, head, lp_value] : relevant_arcs) {
1143 std::vector<int> min_cut_subset;
1144 std::vector<int> parent(num_nodes, 0);
1145 for (
int s = 1; s < num_nodes; ++s) {
1146 const int t = parent[s];
1149 bool parent_of_t_in_subset =
false;
1150 for (
const int i : min_cut_subset) {
1151 if (
i == parent[t]) parent_of_t_in_subset =
true;
1152 if (
i != s && parent[
i] == t) parent[
i] = s;
1154 if (parent_of_t_in_subset) {
1155 parent[s] = parent[t];
1165 if (arc.tail <= arc.head)
continue;
1166 std::swap(arc.tail, arc.head);
1168 std::sort(arcs->begin(), arcs->end(),
1169 [](
const ArcWithLpValue& a,
const ArcWithLpValue&
b) {
1170 return std::tie(a.tail, a.head) < std::tie(b.tail, b.head);
1176 for (
const ArcWithLpValue& arc : *arcs) {
1177 if (arc.tail == last_tail && arc.head == last_head) {
1178 (*arcs)[new_size - 1].lp_value += arc.lp_value;
1181 (*arcs)[new_size++] = arc;
1182 last_tail = arc.tail;
1183 last_head = arc.head;
1185 arcs->resize(new_size);
1196 const int num_nodes = helper.num_nodes();
1197 if (num_nodes <= 2)
return;
1199 helper.InitializeForNewLpSolution(manager);
1201 std::vector<int> subset_data;
1202 std::vector<absl::Span<const int>> subsets;
1207 const int depot = 0;
1208 if (helper.is_route_constraint()) {
1211 subsets.push_back(absl::MakeSpan(&depot, 1));
1219 int last_added_start = -1;
1223 for (
const absl::Span<const int> subset : subsets) {
1224 if (subset.size() <= 1)
continue;
1225 const int start =
static_cast<int>(subset.data() - subset_data.data());
1226 if (start <= last_added_start)
continue;
1227 if (helper.TrySubsetCut(
"Circuit", subset, manager)) {
1229 last_added_start = start;
1247 if (num_added != 0)
return;
1248 absl::Span<const ArcWithLpValue> symmetrized_relevant_arcs =
1249 helper.SymmetrizedRelevantArcs();
1250 std::vector<int> parent =
1255 last_added_start = -1;
1256 for (
const absl::Span<const int> subset : subsets) {
1257 if (subset.size() <= 1)
continue;
1258 if (subset.size() == num_nodes)
continue;
1259 const int start =
static_cast<int>(subset.data() - subset_data.data());
1260 if (start <= last_added_start)
continue;
1261 if (helper.TrySubsetCut(
"CircuitExact", subset, manager)) {
1263 last_added_start = start;
1270 if (num_added != 0)
return;
1271 if (num_nodes <= 2)
return;
1272 std::vector<ArcWithLpValue> for_blossom;
1273 for_blossom.reserve(symmetrized_relevant_arcs.size());
1275 if (arc.lp_value > 0.5) arc.lp_value = 1.0 - arc.lp_value;
1276 if (arc.lp_value < 1e-6)
continue;
1277 for_blossom.push_back(arc);
1281 last_added_start = -1;
1282 for (
const absl::Span<const int> subset : subsets) {
1283 if (subset.size() <= 1)
continue;
1284 if (subset.size() == num_nodes)
continue;
1285 const int start =
static_cast<int>(subset.data() - subset_data.data());
1286 if (start <= last_added_start)
continue;
1287 if (helper.TryBlossomSubsetCut(
"CircuitBlossom", symmetrized_relevant_arcs,
1290 last_added_start = start;
1298std::vector<IntegerVariable> GetAssociatedVariables(
1299 absl::Span<const Literal> literals,
Model* model) {
1300 auto* encoder = model->GetOrCreate<IntegerEncoder>();
1301 std::vector<IntegerVariable> result;
1302 for (
const Literal l : literals) {
1303 const IntegerVariable direct_view = encoder->GetLiteralView(l);
1305 result.push_back(direct_view);
1307 result.push_back(encoder->GetLiteralView(l.Negated()));
1320 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
1321 absl::Span<const Literal> literals,
Model* model) {
1322 auto helper = std::make_unique<OutgoingCutHelper>(
1323 num_nodes,
false, 0,
1324 absl::Span<const int64_t>(), tails, heads, literals, model);
1326 result.
vars = GetAssociatedVariables(literals, model);
1336 absl::Span<const int> heads,
1337 absl::Span<const Literal> literals,
1338 absl::Span<const int64_t> demands,
1339 int64_t capacity,
Model* model) {
1340 auto helper = std::make_unique<OutgoingCutHelper>(
1341 num_nodes,
true, capacity, demands, tails, heads,
1344 result.
vars = GetAssociatedVariables(literals, model);
1356 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
1357 absl::Span<const AffineExpression> arc_capacities,
1358 std::function<
void(
const std::vector<bool>& in_subset,
1359 IntegerValue* min_incoming_flow,
1360 IntegerValue* min_outgoing_flow)>
1370 IntegerValue offset;
1372 std::vector<Arc> relevant_arcs;
1379 std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
1380 for (
int i = 0;
i < arc_capacities.size(); ++
i) {
1381 const double lp_value = arc_capacities[
i].LpValue(lp_values);
1382 if (!arc_capacities[
i].IsConstant()) {
1383 gcd = std::gcd(gcd, std::abs(arc_capacities[
i].coeff.value()));
1385 if (lp_value < 1e-6 && arc_capacities[
i].constant == 0)
continue;
1386 relevant_arcs.push_back(
1387 {tails[
i], heads[
i], lp_value, arc_capacities[
i].constant});
1388 arc_by_decreasing_lp_values.push_back({lp_value,
i});
1390 if (gcd == 0)
return;
1391 std::sort(arc_by_decreasing_lp_values.begin(),
1392 arc_by_decreasing_lp_values.end(),
1393 std::greater<std::pair<double, int>>());
1395 std::vector<std::pair<int, int>> ordered_arcs;
1396 for (
const auto& [score, arc] : arc_by_decreasing_lp_values) {
1397 if (tails[arc] == -1)
continue;
1398 if (heads[arc] == -1)
continue;
1399 ordered_arcs.push_back({tails[arc], heads[arc]});
1401 std::vector<int> subset_data;
1402 std::vector<absl::Span<const int>> subsets;
1408 std::vector<bool> in_subset(num_nodes,
false);
1409 for (
const absl::Span<const int> subset : subsets) {
1410 DCHECK(!subset.empty());
1411 DCHECK_LE(subset.size(), num_nodes);
1414 for (
const int n : subset) in_subset[n] =
true;
1416 IntegerValue min_incoming_flow;
1417 IntegerValue min_outgoing_flow;
1418 get_flows(in_subset, &min_incoming_flow, &min_outgoing_flow);
1422 IntegerValue incoming_offset(0);
1423 IntegerValue outgoing_offset(0);
1430 double lp_outgoing_flow = 0.0;
1431 double lp_incoming_flow = 0.0;
1432 for (
const auto arc : relevant_arcs) {
1433 if (arc.tail != -1 && in_subset[arc.tail]) {
1434 if (arc.head == -1 || !in_subset[arc.head]) {
1435 incoming_offset += arc.offset;
1436 lp_outgoing_flow += arc.lp_value;
1439 if (arc.head != -1 && in_subset[arc.head]) {
1440 outgoing_offset += arc.offset;
1441 lp_incoming_flow += arc.lp_value;
1452 const IntegerValue test_incoming = min_incoming_flow - incoming_offset;
1453 const IntegerValue new_incoming =
1454 CeilRatio(test_incoming, IntegerValue(gcd)) * IntegerValue(gcd);
1455 const IntegerValue incoming_delta = new_incoming - test_incoming;
1456 if (incoming_delta > 0) min_incoming_flow += incoming_delta;
1459 const IntegerValue test_outgoing = min_outgoing_flow - outgoing_offset;
1460 const IntegerValue new_outgoing =
1461 CeilRatio(test_outgoing, IntegerValue(gcd)) * IntegerValue(gcd);
1462 const IntegerValue outgoing_delta = new_outgoing - test_outgoing;
1463 if (outgoing_delta > 0) min_outgoing_flow += outgoing_delta;
1466 if (lp_incoming_flow <
ToDouble(min_incoming_flow) - 1e-6) {
1467 VLOG(2) <<
"INCOMING CUT " << lp_incoming_flow
1468 <<
" >= " << min_incoming_flow <<
" size " << subset.size()
1469 <<
" offset " << incoming_offset <<
" gcd " << gcd;
1471 for (
int i = 0;
i < tails.size(); ++
i) {
1472 if ((tails[
i] == -1 || !in_subset[tails[
i]]) &&
1473 (heads[
i] != -1 && in_subset[heads[
i]])) {
1474 cut.
AddTerm(arc_capacities[
i], 1.0);
1480 if (lp_outgoing_flow <
ToDouble(min_outgoing_flow) - 1e-6) {
1481 VLOG(2) <<
"OUGOING CUT " << lp_outgoing_flow
1482 <<
" >= " << min_outgoing_flow <<
" size " << subset.size()
1483 <<
" offset " << outgoing_offset <<
" gcd " << gcd;
1485 for (
int i = 0;
i < tails.size(); ++
i) {
1486 if ((tails[
i] != -1 && in_subset[tails[
i]]) &&
1487 (heads[
i] == -1 || !in_subset[heads[
i]])) {
1488 cut.
AddTerm(arc_capacities[
i], 1.0);
1495 for (
const int n : subset) in_subset[n] =
false;
1500 int num_nodes,
const std::vector<int>& tails,
const std::vector<int>& heads,
1501 const std::vector<AffineExpression>& arc_capacities,
1502 std::function<
void(
const std::vector<bool>& in_subset,
1503 IntegerValue* min_incoming_flow,
1504 IntegerValue* min_outgoing_flow)>
1509 if (!expr.IsConstant()) result.
vars.push_back(expr.var);
1513 manager->LpValues(), manager, model);
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
ArcIndex AddArcWithCapacity(NodeIndex tail, NodeIndex head, FlowQuantity capacity)
Status Solve(NodeIndex source, NodeIndex sink)
void GetSourceSideMinCut(std::vector< NodeIndex > *result)
IntegerVariable GetLiteralView(Literal lit) const
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, std::string extra_info="")
double ComplexityEstimate(int num_elements, int64_t bin_size)
int64_t MaxSubsetSum(absl::Span< const int64_t > elements, int64_t bin_size)
int64_t CurrentMax() const
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
void Reset(int64_t bound)
int ComputeTightMinOutgoingFlow(absl::Span< const int > subset)
int ComputeMinOutgoingFlow(absl::Span< const int > subset)
MinOutgoingFlowHelper(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, Model *model)
int CurrentDecisionLevel() const
const VariablesAssignment & Assignment() const
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
bool operator==(const BoolArgumentProto &lhs, const BoolArgumentProto &rhs)
std::vector< int > ComputeGomoryHuTree(int num_nodes, absl::Span< const ArcWithLpValue > relevant_arcs)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateCVRPCutGenerator(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, absl::Span< const int64_t > demands, int64_t capacity, Model *model)
CutGenerator CreateFlowCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< AffineExpression > &arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, Model *model)
H AbslHashValue(H h, const IntVar &i)
– ABSL HASHING SUPPORT --------------------------------------------------—
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)
void ExtractAllSubsetsFromForest(absl::Span< const int > parent, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets, int node_limit)
void GenerateInterestingSubsets(int num_nodes, absl::Span< const std::pair< int, int > > arcs, int stop_at_num_components, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets)
double ToDouble(IntegerValue value)
void SeparateSubtourInequalities(OutgoingCutHelper &helper, LinearConstraintManager *manager)
In SWIG mode, we don't want anything besides these top-level includes.
absl::AnyInvocable< bool(LinearConstraintManager *manager)> generate_cuts
std::vector< IntegerVariable > vars