5#ifndef IDOL_KNAPSACKCOVER_H
6#define IDOL_KNAPSACKCOVER_H
8#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallbackFactory.h"
9#include "idol/mixed-integer/optimizers/branch-and-bound/callbacks/BranchAndBoundCallback.h"
10#include "idol/mixed-integer/optimizers/branch-and-bound/nodes/DefaultNodeInfo.h"
13 template<
class NodeInfoT>
class KnapsackCover;
16template<
class NodeInfoT =
idol::DefaultNodeInfo>
18 std::optional<bool> m_lifting;
19 std::optional<bool> m_apply_to_tree_nodes;
20 std::optional<unsigned int> m_max_pass_root_node;
21 std::optional<double> m_max_cuts_factor;
36template<
class NodeInfoT>
39 if (m_max_pass_root_node.has_value()) {
40 throw Exception(
"Maximum pass at root node has already been configured.");
43 m_max_pass_root_node = t_value;
48template<
class NodeInfoT>
51 if (m_max_cuts_factor.has_value()) {
52 throw Exception(
"Maximum cuts factor has already been configured.");
55 m_max_cuts_factor = t_value;
60template<
class NodeInfoT>
63 if (m_apply_to_tree_nodes.has_value()) {
64 throw Exception(
"Tree node cuts have already been configured.");
67 m_apply_to_tree_nodes = t_value;
72template<
class NodeInfoT>
75 if (m_lifting.has_value()) {
76 throw Exception(
"Lifting has already been configured.");
84template<
class NodeInfoT>
87 struct KnapsackStructure {
92 double current_value = -1.;
93 std::optional<int> cut_coefficient;
94 Item(
const Var& t_var,
int t_weight);
99 Item& operator=(
const Item&) =
default;
103 std::vector<Item> items;
105 KnapsackStructure(std::vector<Item> t_items,
int t_capacity);
108 using ItemIterator =
typename std::vector<typename KnapsackStructure::Item>::iterator;
111 ItemIterator m_begin;
114 SetOfItems(ItemIterator t_begin, ItemIterator t_end) : m_begin(t_begin), m_end(t_end) {}
116 ItemIterator begin()
const {
return m_begin; }
117 ItemIterator end()
const {
return m_end; }
118 unsigned int size()
const {
return std::distance(m_begin, m_end); }
120 static SetOfItems merge_consecutive(
const SetOfItems& t_a,
const SetOfItems& t_b);
123 std::list<KnapsackStructure> m_knapsack_structures;
124 const bool m_lifting;
125 const bool m_apply_to_tree_nodes;
126 const unsigned int m_max_pass_root_node;
127 const double m_max_cuts_factor;
128 unsigned int m_max_cuts = 0;
129 unsigned int m_n_cuts = 0;
130 unsigned int m_n_separations = 0;
133 void detect_knapsack_structure(
const Ctr& t_ctr);
134 void add_knapsack_structure(
const Row& t_row, CtrType t_type);
135 bool has_only_binary_variables(
const Row& t_row);
136 bool has_only_single_signed_coefficients(
const Row& t_row);
139 void separate_cut(
const KnapsackStructure& t_knapsack_structure);
140 std::tuple<SetOfItems, SetOfItems, SetOfItems> fix_variables_equal_to_one_or_zero(
const SetOfItems& t_set_of_items);
141 void set_current_values(
const SetOfItems& t_set_of_items);
142 int sum_of_weights(
const SetOfItems& t_set_of_items);
143 std::pair<SetOfItems, SetOfItems> compute_initial_cover(
const SetOfItems& t_N_1,
const SetOfItems& t_N_free,
const SetOfItems& t_N_0,
int t_capacity);
144 std::pair<SetOfItems, SetOfItems> solve_knapsack_approximately(
const SetOfItems& t_set_of_items,
int t_capacity);
145 std::pair<SetOfItems, SetOfItems> swap_items(
const SetOfItems& t_a,
const SetOfItems& t_b);
146 std::pair<SetOfItems, SetOfItems> compute_minimal_cover(
const SetOfItems& t_initial_cover,
int t_capacity);
147 std::pair<SetOfItems, SetOfItems> partition_minimal_cover(
const SetOfItems& t_minimal_cover);
148 std::pair<SetOfItems, SetOfItems> partition_remaining_items(
const SetOfItems& t_remaining_items);
149 void sort_by_non_decreasing_weights(
const SetOfItems& t_set_of_items);
150 void sort_by_non_increasing_weights(
const SetOfItems& t_set_of_items);
151 int sequential_up_and_down_lifting(
const SetOfItems& t_C1,
const SetOfItems& t_C2,
const SetOfItems &t_F,
const SetOfItems& t_R,
int t_capacity);
152 double compute_cut_activity(
const SetOfItems& t_set_of_items,
int t_right_hand_side);
153 TempCtr create_cut(
const SetOfItems& t_set_of_items,
int t_right_hand_side);
155 void debug(
const std::string& t_name,
const SetOfItems& t_set_of_items,
bool t_with_values =
false,
bool t_with_cut =
false);
157 void initialize()
override;
159 void operator()(CallbackEvent t_event)
override;
161 void log_after_termination()
override;
164 Strategy(
bool t_use_lifting,
165 bool t_apply_to_tree_nodes,
166 unsigned int t_max_pass_root_node,
167 double t_max_cuts_factor);
170template<
class NodeInfoT>
173 BranchAndBoundCallback<NodeInfoT>::log_after_termination();
175 std::cout <<
"\tKnapsack Cover Cuts: " << m_n_cuts << std::endl;
178template<
class NodeInfoT>
180 bool t_apply_to_tree_nodes,
181 unsigned int t_max_pass_root_node,
182 double t_max_cuts_factor)
183 : m_lifting(t_use_lifting),
184 m_apply_to_tree_nodes(t_apply_to_tree_nodes),
185 m_max_pass_root_node(t_max_pass_root_node),
186 m_max_cuts_factor(t_max_cuts_factor) {
190template<
class NodeInfoT>
192 const SetOfItems &t_set_of_items,
196 std::cout << t_name <<
": ";
197 for (
const auto& item : t_set_of_items) {
199 if (item.cut_coefficient.has_value()) {
200 std::cout << item.cut_coefficient.value();
206 std::cout << item.variable;
208 std::cout <<
"(" << item.current_value <<
")";
212 std::cout << std::endl;
216template<
class NodeInfoT>
219 const SetOfItems &t_b) {
221 if (t_a.end() != t_b.begin()) {
222 throw Exception(
"Trying to merge non-consecutive sets of items while calling merge_consecutive.");
232template<
class NodeInfoT>
234 : items(std::move(t_items)), capacity(t_capacity) {
238template<
class NodeInfoT>
240 : variable(t_var), weight(t_weight) {
244template<
class NodeInfoT>
248 m_knapsack_structures.clear();
254 for (
const auto& ctr : model.ctrs()) {
255 detect_knapsack_structure(ctr);
258 m_max_cuts = m_max_cuts_factor * model.ctrs().size();
260 std::cout <<
"Lifted Minimal Cover Cuts 1, " << m_knapsack_structures.size() <<
" candidates found" << std::endl;
263template<
class NodeInfoT>
267 const auto& row = model.get_ctr_row(t_ctr);
269 if (!row.quadratic().empty()) {
273 if (!has_only_binary_variables(row)) {
277 if (!has_only_single_signed_coefficients(row)) {
281 const auto type = model.get_ctr_type(t_ctr);
287 add_knapsack_structure(row, type);
291template<
class NodeInfoT>
294 const unsigned int n_items = t_row.linear().size();
295 const double factor = (t_type == LessOrEqual ? 1. : -1.);
298 copy.scale_to_integers(Tolerance::Digits);
300 std::vector<typename KnapsackStructure::Item> items;
301 items.reserve(n_items);
303 for (
const auto& [var, coefficient] : copy.linear()) {
304 items.emplace_back(var, factor * coefficient.as_numerical());
307 m_knapsack_structures.emplace_back(std::move(items), factor * copy.rhs().as_numerical());
311template<
class NodeInfoT>
316 for (
const auto& [var, coefficient] : t_row.linear()) {
318 if (model.get_var_type(var) != Binary) {
327template<
class NodeInfoT>
330 std::optional<bool> are_all_non_negative;
332 for (
const auto& [var, coefficient] : t_row.linear()) {
334 bool is_non_negative = coefficient.as_numerical() >= 0;
336 if (!are_all_non_negative.has_value()) [[unlikely]] {
337 are_all_non_negative = is_non_negative;
341 if (is_non_negative != are_all_non_negative) {
350template<
class NodeInfoT>
353 if (t_event != InvalidSolution) {
357 if (m_n_cuts >= m_max_cuts) {
361 if (this->
node().level() == 0) {
363 if (m_n_separations > m_max_pass_root_node) {
369 if (!m_apply_to_tree_nodes && this->
node().level() != 0) {
373 for (
const auto& knapsack_structure : m_knapsack_structures) {
374 separate_cut(knapsack_structure);
380template<
class NodeInfoT>
383 if (m_n_cuts >= m_max_cuts) {
387 auto knapsack_structure = t_knapsack_structure;
388 SetOfItems N(knapsack_structure.items.begin(), knapsack_structure.items.end());
390 set_current_values(N);
392 const auto [N_1, N_free, N_0] = fix_variables_equal_to_one_or_zero(N);
393 const int sum_weights_of_variables_not_fixed_to_zero = sum_of_weights(N_1) + sum_of_weights(N_free);
395 if (sum_weights_of_variables_not_fixed_to_zero < knapsack_structure.capacity + 1) {
400 auto [initial_cover, not_initial_cover] = compute_initial_cover(
404 sum_weights_of_variables_not_fixed_to_zero - (knapsack_structure.capacity + 1)
408 auto [C, others] = compute_minimal_cover(initial_cover, t_knapsack_structure.capacity);
415 auto not_in_C = SetOfItems::merge_consecutive(others, not_initial_cover);
416 auto [C1, C2] = partition_minimal_cover(C);
419 auto [F, R] = partition_remaining_items(not_in_C);
420 sort_by_non_decreasing_weights(C1);
421 sort_by_non_increasing_weights(C2);
422 sort_by_non_increasing_weights(F);
423 sort_by_non_increasing_weights(R);
425 rhs = sequential_up_and_down_lifting(C1, C2, F, R, knapsack_structure.capacity);
430 for (
auto& item : C) {
431 item.cut_coefficient = 1.;
436 const double activity = compute_cut_activity(N, rhs);
442 auto cut = create_cut(N, rhs);
449template<
class NodeInfoT>
454 for (
const auto& item : t_set_of_items) {
456 if (!item.cut_coefficient.has_value()) {
460 result += item.cut_coefficient.value() * item.variable;
464 return result <= t_right_hand_side;
467template<
class NodeInfoT>
472 for (
const auto& item : t_set_of_items) {
474 if (!item.cut_coefficient.has_value()) {
478 result += item.cut_coefficient.value() * item.current_value;
482 return result - t_right_hand_side;
485template<
class NodeInfoT>
487 const SetOfItems &t_C2,
488 const SetOfItems &t_F,
489 const SetOfItems &t_R,
493 for (
auto& item : t_C1) {
494 item.cut_coefficient = 1;
497 const auto initialize_min_weights = [](
const SetOfItems& t_C1) {
499 const unsigned int n_C1 = t_C1.size();
501 std::vector<int> result;
502 result.reserve(n_C1 + 1);
503 result.emplace_back(0);
505 auto it = t_C1.begin();
506 for (
unsigned int w = 0 ; w < n_C1 ; ++w) {
507 result.emplace_back(result[w] + it->weight);
514 auto min_weights = initialize_min_weights(t_C1);
516 int alpha_0 = t_C1.size() - 1;
517 int lift_rhs = alpha_0;
519 int fixed_ones_weight = sum_of_weights(t_C2);
521 assert(fixed_ones_weight >= 0);
524 for (
auto& item : t_F) {
527 if (t_capacity - fixed_ones_weight - item.weight < 0) {
529 }
else if (min_weights[alpha_0] <= t_capacity - fixed_ones_weight - item.weight) {
534 int ub = lift_rhs + 1;
535 while (lb < ub - 1) {
536 const int middle = (lb + ub) / 2;
537 if ( min_weights[middle] <= t_capacity - fixed_ones_weight - item.weight ) {
544 assert(lb == ub - 1);
545 assert(0 <= lb && lb < min_weights.size());
546 assert(min_weights[lb] <= t_capacity - fixed_ones_weight - item.weight);
547 assert(lb == min_weights.size() - 1 || min_weights[lb + 1] > t_capacity - fixed_ones_weight - item.weight);
551 assert(z <= lift_rhs);
555 const int alpha_j = alpha_0 - z;
557 item.cut_coefficient = alpha_j;
563 min_weights.resize(min_weights.size() + alpha_j, std::numeric_limits<int>::infinity());
565 for (
int w = min_weights.size() - 1 ; w >= 0 ; w--) {
568 min_weights[w] = std::min(min_weights[w], item.weight);
570 min_weights[w] = std::min(min_weights[w], min_weights[w - alpha_j] + item.weight);
578 for (
auto& item : t_C2) {
581 int ub = min_weights.size();
582 while (lb < ub - 1) {
583 int middle = (lb + ub) / 2;
584 if ( min_weights[middle] <= t_capacity - fixed_ones_weight + item.weight ) {
592 const int alpha_j = z - alpha_0;
594 item.cut_coefficient = alpha_j;
598 fixed_ones_weight -= item.weight;
605 min_weights.resize(min_weights.size() + alpha_j, std::numeric_limits<int>::infinity());
608 for (
int w = min_weights.size() - 1 ; w >= 0 ; w--) {
611 min_weights[w] = std::min(min_weights[w], item.weight);
613 min_weights[w] = std::min(min_weights[w], min_weights[w - alpha_j] + item.weight);
621 assert(fixed_ones_weight == 0);
624 for (
auto& item : t_R) {
627 if (min_weights[alpha_0] <= t_capacity - item.weight) {
632 int ub = alpha_0 + 1;
633 while (lb < ub - 1) {
634 int middle = (lb + ub) / 2;
635 if ( min_weights[middle] <= t_capacity - item.weight ) {
645 const int alpha_j = alpha_0 - z;
647 item.cut_coefficient = alpha_j;
653 for (
int w = min_weights.size() - 1 ; w >= 0 ; w--) {
656 min_weights[w] = std::min(min_weights[w], item.weight);
658 min_weights[w] = std::min(min_weights[w], min_weights[w - alpha_j] + item.weight);
670template<
class NodeInfoT>
673 std::sort(t_set_of_items.begin(), t_set_of_items.end(), [](
const auto& t_a,
const auto& t_b) {
674 return t_a.weight >= t_b.weight;
679template<
class NodeInfoT>
682 std::sort(t_set_of_items.begin(), t_set_of_items.end(), [](
const auto& t_a,
const auto& t_b) {
683 return t_a.weight <= t_b.weight;
688template<
class NodeInfoT>
691 const auto& primal_solution = this->
node().info().primal_solution();
693 for (
auto& item : t_set_of_items) {
694 item.current_value = primal_solution.get(item.variable);
699template<
class NodeInfoT>
707 auto end_of_fixed_to_one = t_set_of_items.begin();
708 auto begin_of_fixed_to_zero = t_set_of_items.end();
710 for (
auto it = end_of_fixed_to_one ; it != begin_of_fixed_to_zero && end_of_fixed_to_one != begin_of_fixed_to_zero ; ) {
713 --begin_of_fixed_to_zero;
714 std::iter_swap(it, begin_of_fixed_to_zero);
719 std::iter_swap(it, end_of_fixed_to_one);
720 ++end_of_fixed_to_one;
730 { t_set_of_items.begin(), end_of_fixed_to_one },
731 { end_of_fixed_to_one, begin_of_fixed_to_zero },
732 { begin_of_fixed_to_zero, t_set_of_items.end() },
736template<
class NodeInfoT>
741 for (
auto it = t_set_of_items.begin(), end = t_set_of_items.end() ; it != end ; ++it) {
742 result += it->weight;
748template<
class NodeInfoT>
754 const SetOfItems &t_N_free,
755 const SetOfItems &t_N_0,
758 auto [ones, zeroes] = solve_knapsack_approximately(t_N_free, t_capacity);
760 assert(ones.end() == zeroes.begin());
762 auto [flipped_zeroes, flipped_ones] = swap_items(ones, zeroes);
764 auto initial_cover = SetOfItems::merge_consecutive(t_N_1, flipped_zeroes);
765 auto not_initial_cover = SetOfItems::merge_consecutive(flipped_ones, t_N_0);
767 return { initial_cover, not_initial_cover };
771template<
class NodeInfoT>
778 auto end_of_C1 = t_minimal_cover.end();
780 for (
auto it = t_minimal_cover.begin() ; it != end_of_C1 ; ) {
788 std::iter_swap(it, end_of_C1);
793 { t_minimal_cover.begin(), end_of_C1 },
794 { end_of_C1, t_minimal_cover.end() }
798template<
class NodeInfoT>
805 auto end_of_F = t_remaining_items.end();
807 for (
auto it = t_remaining_items.begin() ; it != end_of_F ; ) {
815 std::iter_swap(it, end_of_F);
820 { t_remaining_items.begin(), end_of_F },
821 { end_of_F, t_remaining_items.end() },
825template<
class NodeInfoT>
833 std::sort(t_set_of_items.begin(),
834 t_set_of_items.end(),
835 [&](
const auto& t_a,
const auto& t_b) {
837 const double score_a = (1. - t_a.current_value) / t_a.weight;
838 const double score_b = (1. - t_b.current_value) / t_b.weight;
840 return score_a >= score_b;
844 int weight_of_packed_items = 0.;
845 auto it = t_set_of_items.begin();
846 auto end = t_set_of_items.end();
848 auto begin_zero = it;
850 for ( ; it != end ; ++it) {
852 if (weight_of_packed_items + it->weight > t_capacity) {
858 std::iter_swap(it, begin_zero);
861 weight_of_packed_items += it->weight;
866 { t_set_of_items.begin(), begin_zero },
867 { begin_zero, t_set_of_items.end() },
872template<
class NodeInfoT>
878 const SetOfItems &t_b) {
880 const auto min_size = std::min(t_a.size(), t_b.size());
881 const auto max_size = std::max(t_a.size(), t_b.size());
883 std::swap_ranges(t_a.begin(), t_a.begin() + min_size, t_a.begin() + max_size);
885 ItemIterator split = t_a.begin() + t_b.size();
888 { t_a.begin(), split },
893template<
class NodeInfoT>
901 std::sort(t_initial_cover.begin(), t_initial_cover.end(), [](
const auto& t_a,
const auto& t_b) {
903 const double score_a = (1. - t_a.current_value) / t_a.weight;
904 const double score_b = (1. - t_b.current_value) / t_b.weight;
906 if (equals(score_a, score_b, Tolerance::Sparsity)) {
907 return t_a.weight <= t_b.weight;
910 return score_a >= score_b;
913 int sum_of_weights_in_C = sum_of_weights(t_initial_cover);
914 auto end = t_initial_cover.end();
916 for (
auto it = t_initial_cover.begin() ; it != end ; ) {
918 if (sum_of_weights_in_C - it->weight <= t_capacity) {
923 sum_of_weights_in_C -= it->weight;
925 std::iter_swap(it, end);
930 { t_initial_cover.begin(), end },
931 { end, t_initial_cover.end() },
936template<
class NodeInfoT>
938 return new KnapsackCover<NodeInfoT>(*
this);
941template<
class NodeInfoT>
944 m_lifting.has_value() ? m_lifting.value() : true,
945 m_apply_to_tree_nodes.has_value() ? m_apply_to_tree_nodes.value() : true,
946 m_max_pass_root_node.has_value() ? m_max_pass_root_node.value() : 200,
947 m_max_cuts_factor.has_value() ? m_max_cuts_factor.value() : 1.5
void add_user_cut(const TempCtr &t_cut)
const Node< NodeInfoT > & node() const
virtual void initialize()
const Model & original_model() const
void operator()(CallbackEvent t_event) override
void initialize() override
static double Feasibility