26#include "absl/cleanup/cleanup.h"
27#include "absl/container/inlined_vector.h"
28#include "absl/log/check.h"
29#include "absl/strings/str_cat.h"
30#include "absl/types/span.h"
50class OutgoingCutHelper {
52 OutgoingCutHelper(
int num_nodes, int64_t capacity,
53 absl::Span<const int64_t> demands,
54 const std::vector<int>& tails,
55 const std::vector<int>& heads,
56 const std::vector<Literal>& literals,
57 const std::vector<double>& literal_lp_values,
58 const std::vector<ArcWithLpValue>& relevant_arcs,
59 LinearConstraintManager* manager, Model*
model)
60 : num_nodes_(num_nodes),
66 literal_lp_values_(literal_lp_values),
67 relevant_arcs_(relevant_arcs),
69 encoder_(
model->GetOrCreate<IntegerEncoder>()) {
70 in_subset_.assign(num_nodes,
false);
74 for (
const int64_t
demand : demands) total_demand_ +=
demand;
78 bool TrySubsetCut(std::string
name, absl::Span<const int> subset);
91 bool TryBlossomSubsetCut(std::string
name,
92 absl::Span<const ArcWithLpValue> symmetrized_edges,
93 absl::Span<const int> subset);
102 bool AddOutgoingCut(std::string
name,
int subset_size,
103 const std::vector<bool>& in_subset,
104 int64_t rhs_lower_bound);
106 const int num_nodes_;
107 const int64_t capacity_;
108 const absl::Span<const int64_t> demands_;
109 const std::vector<int>& tails_;
110 const std::vector<int>& heads_;
111 const std::vector<Literal>& literals_;
112 const std::vector<double>& literal_lp_values_;
113 const std::vector<ArcWithLpValue>& relevant_arcs_;
114 LinearConstraintManager* manager_;
115 IntegerEncoder* encoder_;
117 int64_t total_demand_ = 0;
118 std::vector<bool> in_subset_;
121bool OutgoingCutHelper::AddOutgoingCut(std::string
name,
int subset_size,
122 const std::vector<bool>& in_subset,
123 int64_t rhs_lower_bound) {
130 int num_optional_nodes_in = 0;
131 int num_optional_nodes_out = 0;
132 int optional_loop_in = -1;
133 int optional_loop_out = -1;
134 for (
int i = 0;
i < tails_.size(); ++
i) {
135 if (tails_[i] != heads_[i])
continue;
136 if (in_subset[tails_[i]]) {
137 num_optional_nodes_in++;
138 if (optional_loop_in == -1 ||
139 literal_lp_values_[i] < literal_lp_values_[optional_loop_in]) {
140 optional_loop_in =
i;
143 num_optional_nodes_out++;
144 if (optional_loop_out == -1 ||
145 literal_lp_values_[i] < literal_lp_values_[optional_loop_out]) {
146 optional_loop_out =
i;
153 if (num_optional_nodes_in + num_optional_nodes_out > 0) {
154 CHECK_GE(rhs_lower_bound, 1);
160 LinearConstraintBuilder outgoing(encoder_, IntegerValue(rhs_lower_bound),
164 for (
int i = 0;
i < tails_.size(); ++
i) {
165 if (in_subset[tails_[i]] && !in_subset[heads_[i]]) {
166 CHECK(outgoing.AddLiteralTerm(literals_[i], IntegerValue(1)));
171 if (num_optional_nodes_in + num_optional_nodes_out > 0) {
173 if (num_optional_nodes_in == subset_size &&
174 (optional_loop_in == -1 ||
175 literal_lp_values_[optional_loop_in] > 1.0 - 1e-6)) {
178 if (num_optional_nodes_out == num_nodes_ - subset_size &&
179 (optional_loop_out == -1 ||
180 literal_lp_values_[optional_loop_out] > 1.0 - 1e-6)) {
185 if (num_optional_nodes_in == subset_size) {
186 CHECK(outgoing.AddLiteralTerm(literals_[optional_loop_in],
191 if (num_optional_nodes_out == num_nodes_ - subset_size) {
192 CHECK(outgoing.AddLiteralTerm(literals_[optional_loop_out],
197 return manager_->
AddCut(outgoing.Build(),
name);
200bool OutgoingCutHelper::TrySubsetCut(std::string
name,
201 absl::Span<const int> subset) {
202 DCHECK_GE(subset.size(), 1);
203 DCHECK_LT(subset.size(), num_nodes_);
206 bool contain_depot =
false;
207 int64_t subset_demand = 0;
210 for (
const int n : subset) {
211 in_subset_[n] =
true;
212 if (!demands_.empty()) {
213 if (n == 0) contain_depot =
true;
214 subset_demand += demands_[n];
231 int64_t min_outgoing_flow = 1;
232 if (!demands_.empty()) {
234 contain_depot ?
CeilOfRatio(total_demand_ - subset_demand, capacity_)
241 min_outgoing_flow = std::max(min_outgoing_flow, int64_t{1});
253 double outgoing_flow = 0.0;
254 for (
const auto arc : relevant_arcs_) {
255 if (in_subset_[
arc.tail] && !in_subset_[
arc.head]) {
256 outgoing_flow +=
arc.lp_value;
262 if (outgoing_flow + 1e-2 < min_outgoing_flow) {
263 result = AddOutgoingCut(
name, subset.size(), in_subset_,
268 for (
const int n : subset) in_subset_[n] =
false;
273bool OutgoingCutHelper::TryBlossomSubsetCut(
274 std::string
name, absl::Span<const ArcWithLpValue> symmetrized_edges,
275 absl::Span<const int> subset) {
276 DCHECK_GE(subset.size(), 1);
277 DCHECK_LT(subset.size(), num_nodes_);
280 for (
const int n : subset) in_subset_[n] =
true;
281 auto cleanup = ::absl::MakeCleanup([subset,
this]() {
282 for (
const int n : subset) in_subset_[n] =
false;
287 absl::flat_hash_set<std::pair<int, int>> special_edges;
288 int num_inverted = 0;
289 double sum_inverted = 0.0;
291 double best_change = 1.0;
292 ArcWithLpValue
const* best_swap =
nullptr;
293 for (
const ArcWithLpValue&
arc : symmetrized_edges) {
294 if (in_subset_[
arc.tail] == in_subset_[
arc.head])
continue;
296 if (
arc.lp_value > 0.5) {
298 special_edges.insert({
arc.tail,
arc.head});
299 sum_inverted += 1.0 -
arc.lp_value;
304 const double change = std::abs(2 *
arc.lp_value - 1.0);
305 if (change < best_change) {
306 best_change = change;
313 if (num_inverted % 2 == 0) {
314 if (best_swap ==
nullptr)
return false;
315 if (special_edges.contains({best_swap->tail, best_swap->head})) {
317 special_edges.erase({best_swap->tail, best_swap->head});
318 sum_inverted -= (1.0 - best_swap->lp_value);
319 sum += best_swap->lp_value;
322 special_edges.insert({best_swap->tail, best_swap->head});
323 sum_inverted += (1.0 - best_swap->lp_value);
324 sum -= best_swap->lp_value;
327 if (sum + sum_inverted > 0.99)
return false;
331 if (!demands_.empty()) {
332 for (
const auto [
tail,
head] : special_edges) {
333 if (
tail == 0)
return false;
340 int best_optional_index = -1;
341 if (special_edges.size() == 1) {
342 int num_other_optional = 0;
343 const auto [special_tail, special_head] = *special_edges.begin();
344 for (
int i = 0;
i < tails_.size(); ++
i) {
345 if (tails_[i] != heads_[i])
continue;
346 if (tails_[i] != special_head && tails_[i] != special_tail) {
347 ++num_other_optional;
348 if (best_optional_index == -1 ||
349 literal_lp_values_[i] < literal_lp_values_[best_optional_index]) {
350 best_optional_index =
i;
354 if (num_other_optional + 2 < num_nodes_) best_optional_index = -1;
361 int num_actual_inverted = 0;
362 absl::flat_hash_set<std::pair<int, int>> processed_arcs;
363 LinearConstraintBuilder builder(encoder_, IntegerValue(1), kMaxIntegerValue);
367 if (best_optional_index != -1) {
368 absl::StrAppend(&
name,
"_opt");
373 CHECK(builder.AddLiteralTerm(literals_[best_optional_index],
377 for (
int i = 0;
i < tails_.size(); ++
i) {
378 if (tails_[i] == heads_[i])
continue;
379 if (in_subset_[tails_[i]] == in_subset_[heads_[i]])
continue;
381 const std::pair<int, int> key = {tails_[
i], heads_[
i]};
382 const std::pair<int, int> r_key = {heads_[
i], tails_[
i]};
383 const std::pair<int, int> s_key = std::min(key, r_key);
384 if (special_edges.contains(s_key) && !processed_arcs.contains(key)) {
385 processed_arcs.insert(key);
386 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(-1)));
387 if (!processed_arcs.contains(r_key)) {
388 ++num_actual_inverted;
394 CHECK(builder.AddLiteralTerm(literals_[i], IntegerValue(1)));
396 builder.AddConstant(IntegerValue(num_actual_inverted));
397 if (num_actual_inverted % 2 == 0)
return false;
399 return manager_->
AddCut(builder.Build(),
name);
405 const std::vector<std::pair<int, int>>& arcs,
406 int stop_at_num_components,
407 std::vector<int>* subset_data,
408 std::vector<absl::Span<const int>>* subsets) {
417 int num_components = num_nodes;
418 std::vector<int> parent(num_nodes);
419 std::vector<int> root(num_nodes);
420 for (
int i = 0;
i < num_nodes; ++
i) {
424 auto get_root_and_compress_path = [&root](
int node) {
426 while (root[r] != r) r = root[r];
427 while (root[node] != r) {
428 const int next = root[node];
434 for (
const auto& [initial_tail, initial_head] : arcs) {
435 if (num_components <= stop_at_num_components)
break;
436 const int tail = get_root_and_compress_path(initial_tail);
437 const int head = get_root_and_compress_path(initial_head);
441 const int new_node = parent.size();
442 parent.push_back(new_node);
443 parent[
head] = new_node;
444 parent[
tail] = new_node;
448 root.push_back(new_node);
449 root[
head] = new_node;
450 root[
tail] = new_node;
465 std::vector<int>* subset_data,
466 std::vector<absl::Span<const int>>* subsets,
471 const int num_nodes = parent.size();
472 subset_data->resize(std::min(num_nodes, node_limit));
477 for (
int i = 0;
i < num_nodes; ++
i) {
478 if (parent[
i] !=
i) {
486 std::vector<int> subtree_starts(num_nodes, -1);
487 std::vector<int> stack;
488 stack.reserve(num_nodes);
489 for (
int i = 0;
i < parent.size(); ++
i) {
490 if (parent[
i] !=
i)
continue;
493 while (!stack.empty()) {
494 const int node = stack.back();
497 if (subtree_starts[node] >= 0) {
499 if (node < node_limit) {
500 (*subset_data)[out_index++] = node;
502 const int start = subtree_starts[node];
504 subsets->push_back(absl::MakeSpan(&(*subset_data)[
start],
size));
509 subtree_starts[node] = out_index;
510 for (
const int child :
graph[node]) {
511 stack.push_back(child);
518 int num_nodes,
const std::vector<ArcWithLpValue>& relevant_arcs) {
522 for (
const auto& [
tail,
head, lp_value] : relevant_arcs) {
529 std::vector<int> min_cut_subset;
530 std::vector<int> parent(num_nodes, 0);
531 for (
int s = 1; s < num_nodes; ++s) {
532 const int t = parent[s];
535 bool parent_of_t_in_subset =
false;
536 for (
const int i : min_cut_subset) {
537 if (
i == parent[t]) parent_of_t_in_subset =
true;
538 if (
i != s && parent[
i] == t) parent[
i] = s;
540 if (parent_of_t_in_subset) {
541 parent[s] = parent[t];
551 if (
arc.tail <=
arc.head)
continue;
552 std::swap(
arc.tail,
arc.head);
554 std::sort(arcs->begin(), arcs->end(),
556 return std::tie(a.tail, a.head) < std::tie(b.tail, b.head);
563 if (
arc.tail == last_tail &&
arc.head == last_head) {
564 (*arcs)[new_size - 1].lp_value +=
arc.lp_value;
567 (*arcs)[new_size++] =
arc;
568 last_tail =
arc.tail;
569 last_head =
arc.head;
571 arcs->resize(new_size);
581 int num_nodes,
const std::vector<int>& tails,
const std::vector<int>& heads,
582 const std::vector<Literal>& literals, absl::Span<const int64_t> demands,
584 if (num_nodes <= 2)
return;
588 std::vector<ArcWithLpValue> relevant_arcs;
591 const auto& lp_values = manager->
LpValues();
592 std::vector<double> literal_lp_values(literals.size());
593 std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
595 for (
int i = 0;
i < literals.size(); ++
i) {
597 const IntegerVariable direct_view = encoder->GetLiteralView(literals[
i]);
599 lp_value = lp_values[direct_view];
602 1.0 - lp_values[encoder->GetLiteralView(literals[
i].Negated())];
604 literal_lp_values[
i] = lp_value;
606 if (lp_value < 1e-6)
continue;
607 relevant_arcs.push_back({tails[
i], heads[
i], lp_value});
608 arc_by_decreasing_lp_values.push_back({lp_value,
i});
610 std::sort(arc_by_decreasing_lp_values.begin(),
611 arc_by_decreasing_lp_values.end(),
612 std::greater<std::pair<double, int>>());
614 std::vector<std::pair<int, int>> ordered_arcs;
615 for (
const auto& [score,
arc] : arc_by_decreasing_lp_values) {
616 ordered_arcs.push_back({tails[
arc], heads[
arc]});
618 std::vector<int> subset_data;
619 std::vector<absl::Span<const int>> subsets;
625 if (!demands.empty()) {
628 subsets.push_back(absl::MakeSpan(&depot, 1));
631 OutgoingCutHelper helper(num_nodes, capacity, demands, tails, heads, literals,
632 literal_lp_values, relevant_arcs, manager,
model);
639 int last_added_start = -1;
643 for (
const absl::Span<const int> subset : subsets) {
644 if (subset.size() <= 1)
continue;
645 const int start =
static_cast<int>(subset.data() - subset_data.data());
646 if (
start <= last_added_start)
continue;
647 if (helper.TrySubsetCut(
"Circuit", subset)) {
649 last_added_start =
start;
667 if (num_added != 0)
return;
673 last_added_start = -1;
674 for (
const absl::Span<const int> subset : subsets) {
675 if (subset.size() <= 1)
continue;
676 if (subset.size() == num_nodes)
continue;
677 const int start =
static_cast<int>(subset.data() - subset_data.data());
678 if (
start <= last_added_start)
continue;
679 if (helper.TrySubsetCut(
"CircuitExact", subset)) {
681 last_added_start =
start;
690 if (num_added != 0)
return;
691 if (num_nodes <= 2)
return;
692 std::vector<ArcWithLpValue> for_blossom;
693 for_blossom.reserve(relevant_arcs.size());
695 if (
arc.lp_value > 0.5)
arc.lp_value = 1.0 -
arc.lp_value;
696 if (
arc.lp_value < 1e-6)
continue;
697 for_blossom.push_back(
arc);
701 last_added_start = -1;
702 for (
const absl::Span<const int> subset : subsets) {
703 if (subset.size() <= 1)
continue;
704 if (subset.size() == num_nodes)
continue;
705 const int start =
static_cast<int>(subset.data() - subset_data.data());
706 if (
start <= last_added_start)
continue;
707 if (helper.TryBlossomSubsetCut(
"CircuitBlossom", relevant_arcs, subset)) {
709 last_added_start =
start;
717std::vector<IntegerVariable> GetAssociatedVariables(
718 absl::Span<const Literal> literals, Model*
model) {
719 auto* encoder =
model->GetOrCreate<IntegerEncoder>();
720 std::vector<IntegerVariable> result;
721 for (
const Literal l : literals) {
722 const IntegerVariable direct_view = encoder->GetLiteralView(l);
724 result.push_back(direct_view);
726 result.push_back(encoder->GetLiteralView(l.Negated()));
734void FilterFalseArcsAtLevelZero(std::vector<int>& tails,
735 std::vector<int>& heads,
736 std::vector<Literal>& literals, Model*
model) {
737 const Trail& trail = *
model->GetOrCreate<Trail>();
738 if (trail.CurrentDecisionLevel() != 0)
return;
741 const int size =
static_cast<int>(tails.size());
742 const VariablesAssignment& assignment = trail.Assignment();
743 for (
int i = 0;
i <
size; ++
i) {
744 if (assignment.LiteralIsFalse(literals[
i]))
continue;
745 tails[new_size] = tails[
i];
746 heads[new_size] = heads[
i];
747 literals[new_size] = literals[
i];
750 if (new_size <
size) {
751 tails.resize(new_size);
752 heads.resize(new_size);
753 literals.resize(new_size);
763 int num_nodes, std::vector<int> tails, std::vector<int> heads,
764 std::vector<Literal> literals,
Model*
model) {
766 result.
vars = GetAssociatedVariables(literals,
model);
768 FilterFalseArcsAtLevelZero(tails, heads, literals,
model);
770 {}, 0, manager,
model);
777 std::vector<int> heads,
778 std::vector<Literal> literals,
779 std::vector<int64_t> demands,
782 result.
vars = GetAssociatedVariables(literals,
model);
784 FilterFalseArcsAtLevelZero(tails, heads, literals,
model);
786 capacity, manager,
model);
795 int num_nodes, absl::Span<const int> tails, absl::Span<const int> heads,
796 absl::Span<const AffineExpression> arc_capacities,
797 std::function<
void(
const std::vector<bool>& in_subset,
798 IntegerValue* min_incoming_flow,
799 IntegerValue* min_outgoing_flow)>
811 std::vector<Arc> relevant_arcs;
818 std::vector<std::pair<double, int>> arc_by_decreasing_lp_values;
819 for (
int i = 0;
i < arc_capacities.size(); ++
i) {
820 const double lp_value = arc_capacities[
i].LpValue(lp_values);
821 if (!arc_capacities[
i].IsConstant()) {
824 if (lp_value < 1e-6 && arc_capacities[
i].constant == 0)
continue;
825 relevant_arcs.push_back(
826 {tails[
i], heads[
i], lp_value, arc_capacities[
i].constant});
827 arc_by_decreasing_lp_values.push_back({lp_value,
i});
829 if (gcd == 0)
return;
830 std::sort(arc_by_decreasing_lp_values.begin(),
831 arc_by_decreasing_lp_values.end(),
832 std::greater<std::pair<double, int>>());
834 std::vector<std::pair<int, int>> ordered_arcs;
835 for (
const auto& [score,
arc] : arc_by_decreasing_lp_values) {
836 if (tails[
arc] == -1)
continue;
837 if (heads[
arc] == -1)
continue;
838 ordered_arcs.push_back({tails[
arc], heads[
arc]});
840 std::vector<int> subset_data;
841 std::vector<absl::Span<const int>> subsets;
847 std::vector<bool> in_subset(num_nodes,
false);
848 for (
const absl::Span<const int> subset : subsets) {
849 DCHECK(!subset.empty());
850 DCHECK_LE(subset.size(), num_nodes);
853 for (
const int n : subset) in_subset[n] =
true;
855 IntegerValue min_incoming_flow;
856 IntegerValue min_outgoing_flow;
857 get_flows(in_subset, &min_incoming_flow, &min_outgoing_flow);
861 IntegerValue incoming_offset(0);
862 IntegerValue outgoing_offset(0);
869 double lp_outgoing_flow = 0.0;
870 double lp_incoming_flow = 0.0;
871 for (
const auto arc : relevant_arcs) {
872 if (
arc.tail != -1 && in_subset[
arc.tail]) {
873 if (
arc.head == -1 || !in_subset[
arc.head]) {
874 incoming_offset +=
arc.offset;
875 lp_outgoing_flow +=
arc.lp_value;
878 if (
arc.head != -1 && in_subset[
arc.head]) {
879 outgoing_offset +=
arc.offset;
880 lp_incoming_flow +=
arc.lp_value;
891 const IntegerValue test_incoming = min_incoming_flow - incoming_offset;
892 const IntegerValue new_incoming =
893 CeilRatio(test_incoming, IntegerValue(gcd)) * IntegerValue(gcd);
894 const IntegerValue incoming_delta = new_incoming - test_incoming;
895 if (incoming_delta > 0) min_incoming_flow += incoming_delta;
898 const IntegerValue test_outgoing = min_outgoing_flow - outgoing_offset;
899 const IntegerValue new_outgoing =
900 CeilRatio(test_outgoing, IntegerValue(gcd)) * IntegerValue(gcd);
901 const IntegerValue outgoing_delta = new_outgoing - test_outgoing;
902 if (outgoing_delta > 0) min_outgoing_flow += outgoing_delta;
905 if (lp_incoming_flow <
ToDouble(min_incoming_flow) - 1e-6) {
906 VLOG(2) <<
"INCOMING CUT " << lp_incoming_flow
907 <<
" >= " << min_incoming_flow <<
" size " << subset.size()
908 <<
" offset " << incoming_offset <<
" gcd " << gcd;
910 for (
int i = 0;
i < tails.size(); ++
i) {
911 if ((tails[
i] == -1 || !in_subset[tails[
i]]) &&
912 (heads[
i] != -1 && in_subset[heads[
i]])) {
913 cut.
AddTerm(arc_capacities[
i], 1.0);
919 if (lp_outgoing_flow <
ToDouble(min_outgoing_flow) - 1e-6) {
920 VLOG(2) <<
"OUGOING CUT " << lp_outgoing_flow
921 <<
" >= " << min_outgoing_flow <<
" size " << subset.size()
922 <<
" offset " << outgoing_offset <<
" gcd " << gcd;
924 for (
int i = 0;
i < tails.size(); ++
i) {
925 if ((tails[
i] != -1 && in_subset[tails[
i]]) &&
926 (heads[
i] == -1 || !in_subset[heads[
i]])) {
927 cut.
AddTerm(arc_capacities[
i], 1.0);
934 for (
const int n : subset) in_subset[n] =
false;
939 int num_nodes,
const std::vector<int>& tails,
const std::vector<int>& heads,
940 const std::vector<AffineExpression>& arc_capacities,
941 std::function<
void(
const std::vector<bool>& in_subset,
942 IntegerValue* min_incoming_flow,
943 IntegerValue* min_outgoing_flow)>
948 if (!expr.IsConstant()) result.
vars.push_back(expr.var);
952 manager->LpValues(), manager,
model);
static int64_t GCD64(int64_t x, int64_t y)
ArcIndex AddArcWithCapacity(NodeIndex tail, NodeIndex head, FlowQuantity capacity)
Status Solve(NodeIndex source, NodeIndex sink)
void GetSourceSideMinCut(std::vector< NodeIndex > *result)
void AddTerm(IntegerVariable var, IntegerValue coeff)
bool AddCut(LinearConstraint ct, std::string type_name, std::string extra_info="")
const util_intops::StrongVector< IntegerVariable, double > & LpValues()
To simplify CutGenerator api.
const std::string name
A name for logging purposes.
std::vector< int > ComputeGomoryHuTree(int num_nodes, const std::vector< ArcWithLpValue > &relevant_arcs)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
CutGenerator CreateCVRPCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, std::vector< int64_t > demands, int64_t capacity, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
void SeparateSubtourInequalities(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, absl::Span< const int64_t > demands, int64_t capacity, LinearConstraintManager *manager, Model *model)
IntType CeilOfRatio(IntType numerator, IntType denominator)
CutGenerator CreateStronglyConnectedGraphCutGenerator(int num_nodes, std::vector< int > tails, std::vector< int > heads, std::vector< Literal > literals, Model *model)
const IntegerVariable kNoIntegerVariable(-1)
CutGenerator CreateFlowCutGenerator(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< AffineExpression > &arc_capacities, std::function< void(const std::vector< bool > &in_subset, IntegerValue *min_incoming_flow, IntegerValue *min_outgoing_flow)> get_flows, Model *model)
void ExtractAllSubsetsFromForest(const std::vector< int > &parent, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets, int node_limit)
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 GenerateInterestingSubsets(int num_nodes, const std::vector< std::pair< int, int > > &arcs, int stop_at_num_components, std::vector< int > *subset_data, std::vector< absl::Span< const int > > *subsets)
void SymmetrizeArcs(std::vector< ArcWithLpValue > *arcs)
double ToDouble(IntegerValue value)
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< IntegerVariable > vars
std::function< bool(LinearConstraintManager *manager)> generate_cuts