25#include "absl/log/check.h"
26#include "absl/numeric/bits.h"
27#include "absl/random/distributions.h"
28#include "absl/types/span.h"
41 if (!VLOG_IS_ON(1))
return;
42 std::vector<std::pair<std::string, int64_t>> stats;
44 {
"OrthogonalPackingInfeasibilityDetector/called", num_calls_});
46 {
"OrthogonalPackingInfeasibilityDetector/conflicts", num_conflicts_});
47 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/dff0_conflicts",
48 num_conflicts_dff0_});
49 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/dff2_conflicts",
50 num_conflicts_dff2_});
51 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/trivial_conflicts",
52 num_trivial_conflicts_});
53 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/conflicts_two_items",
54 num_conflicts_two_items_});
55 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/no_energy_conflict",
56 num_scheduling_possible_});
57 stats.push_back({
"OrthogonalPackingInfeasibilityDetector/brute_force_calls",
58 num_brute_force_calls_});
60 {
"OrthogonalPackingInfeasibilityDetector/brute_force_conflicts",
61 num_brute_force_conflicts_});
63 {
"OrthogonalPackingInfeasibilityDetector/brute_force_relaxations",
64 num_brute_force_relaxation_});
66 shared_stats_->AddStats(stats);
70std::optional<std::pair<int, int>> FindPairwiseConflict(
71 absl::Span<const IntegerValue> sizes_x,
72 absl::Span<const IntegerValue> sizes_y,
73 std::pair<IntegerValue, IntegerValue> bounding_box_size,
74 absl::Span<const int> index_by_decreasing_x_size,
75 absl::Span<const int> index_by_decreasing_y_size) {
80 while (x_idx < index_by_decreasing_x_size.size() &&
81 y_idx < index_by_decreasing_y_size.size()) {
82 if (index_by_decreasing_x_size[x_idx] ==
83 index_by_decreasing_y_size[y_idx]) {
84 if (sizes_x[index_by_decreasing_x_size[x_idx]] >
85 sizes_y[index_by_decreasing_x_size[x_idx]]) {
92 const bool overlap_on_x = (sizes_x[index_by_decreasing_x_size[x_idx]] +
93 sizes_x[index_by_decreasing_y_size[y_idx]] >
94 bounding_box_size.first);
95 const bool overlap_on_y = (sizes_y[index_by_decreasing_x_size[x_idx]] +
96 sizes_y[index_by_decreasing_y_size[y_idx]] >
97 bounding_box_size.second);
98 if (overlap_on_x && overlap_on_y) {
99 return std::make_pair(index_by_decreasing_x_size[x_idx],
100 index_by_decreasing_y_size[y_idx]);
101 }
else if (overlap_on_x) {
103 }
else if (overlap_on_y) {
112IntegerValue RoundingLowestInverse(IntegerValue y, IntegerValue c_k,
113 IntegerValue max_x, IntegerValue k) {
115 DCHECK_LE(y, 2 * c_k);
116 IntegerValue ret = std::numeric_limits<IntegerValue>::max();
119 if (y <= c_k && (max_x.value() & 1) == 0) {
120 const IntegerValue inverse_mid = max_x / 2;
121 ret = std::min(ret, inverse_mid);
122 if (y == c_k && y.value() & 1) {
132 const IntegerValue inverse_high = max_x - k * (c_k - y / 2);
133 if (2 * inverse_high > max_x) {
137 const IntegerValue lowest_inverse_high =
138 std::max(max_x / 2 + 1, inverse_high - k + 1);
139 ret = std::min(ret, lowest_inverse_high);
143 const IntegerValue inverse_low = k * y / 2;
144 if (2 * inverse_low < max_x) {
145 ret = std::min(ret, inverse_low);
152 return RoundingLowestInverse(y, c_k_, max_x_, k_);
156 IntegerValue y)
const {
157 return RoundingLowestInverse(y, c_k_, max_x_, IntegerValue(1) << log2_k_);
174 absl::Span<const IntegerValue> sizes_x,
175 absl::Span<const IntegerValue> sizes_y,
176 absl::Span<const int> index_by_decreasing_x_size,
177 absl::Span<const IntegerValue> g_x, IntegerValue g_max,
178 IntegerValue x_bb_size, IntegerValue total_energy, IntegerValue bb_area,
179 IntegerValue* best_k) {
183 int num_items = sizes_x.size();
184 auto build_result = [&sizes_x, &sizes_y, num_items, &x_bb_size, &bb_area,
185 &g_max, &g_x](
const IntegerValue k) {
186 std::vector<std::pair<int, IntegerValue>> index_to_energy;
187 index_to_energy.reserve(num_items);
188 for (
int i = 0;
i < num_items;
i++) {
189 IntegerValue point_value;
190 if (sizes_x[
i] > x_bb_size - k) {
192 }
else if (sizes_x[
i] < k) {
195 point_value = g_x[
i];
197 index_to_energy.push_back({
i, point_value * sizes_y[
i]});
199 std::sort(index_to_energy.begin(), index_to_energy.end(),
200 [](
const std::pair<int, IntegerValue>& a,
201 const std::pair<int, IntegerValue>&
b) {
202 return a.second > b.second;
204 IntegerValue recomputed_energy = 0;
205 for (
int i = 0;
i < index_to_energy.size();
i++) {
206 recomputed_energy += index_to_energy[
i].second;
207 if (recomputed_energy > bb_area) {
208 OrthogonalPackingResult result(
210 result.conflict_type_ = OrthogonalPackingResult::ConflictType::DFF_F0;
211 result.items_participating_on_conflict_.resize(
i + 1);
212 for (
int j = 0; j <=
i; j++) {
213 const int index = index_to_energy[j].first;
214 result.items_participating_on_conflict_[j] = {
216 .size_x = sizes_x[index],
217 .size_y = sizes_y[index]};
223 LOG(FATAL) <<
"build_result called with no conflict";
233 IntegerValue current_energy = total_energy;
234 OrthogonalPackingResult best_result;
235 if (current_energy > bb_area) {
236 best_result = build_result(0);
241 int removing_item_index = index_by_decreasing_x_size.size() - 1;
242 int enlarging_item_index = 0;
243 while (enlarging_item_index < index_by_decreasing_x_size.size()) {
244 int index = index_by_decreasing_x_size[enlarging_item_index];
245 IntegerValue size = sizes_x[index];
249 const IntegerValue k = x_bb_size - size + 1;
250 if (2 * k > x_bb_size) {
257 index = index_by_decreasing_x_size[enlarging_item_index];
258 size = sizes_x[index];
259 current_energy += (g_max - g_x[index]) * sizes_y[index];
260 enlarging_item_index++;
261 }
while (enlarging_item_index < index_by_decreasing_x_size.size() &&
262 sizes_x[index_by_decreasing_x_size[enlarging_item_index]] == size);
266 while (removing_item_index >= 0 &&
267 sizes_x[index_by_decreasing_x_size[removing_item_index]] < k) {
268 const int remove_idx = index_by_decreasing_x_size[removing_item_index];
269 current_energy -= g_x[remove_idx] * sizes_y[remove_idx];
270 removing_item_index--;
273 if (current_energy > bb_area) {
274 OrthogonalPackingResult current_result = build_result(k);
275 if (current_result.IsBetterThan(best_result)) {
276 best_result = current_result;
308bool FindHeuristicSchedulingSolution(
309 absl::Span<const IntegerValue> sizes,
310 absl::Span<const IntegerValue> demands,
311 absl::Span<const int> heuristic_order, IntegerValue global_end_max,
312 IntegerValue capacity_max,
313 std::vector<std::pair<IntegerValue, IntegerValue>>& profile,
314 std::vector<std::pair<IntegerValue, IntegerValue>>& new_profile) {
321 for (
int i = 0;
i < heuristic_order.size();
i++) {
322 const IntegerValue event_size = sizes[heuristic_order[
i]];
323 const IntegerValue event_demand = demands[heuristic_order[
i]];
324 const IntegerValue event_start_min = 0;
325 const IntegerValue event_start_max = global_end_max - event_size;
326 const IntegerValue start_min =
327 std::max(event_start_min, start_of_previous_task);
332 while (profile[current + 1].first <= start_min ||
333 profile[current].second < event_demand) {
337 const IntegerValue actual_start =
338 std::max(start_min, profile[current].first);
339 start_of_previous_task = actual_start;
342 if (actual_start > event_start_max)
return false;
344 const IntegerValue actual_end = actual_start + event_size;
347 if (
i == heuristic_order.size() - 1)
break;
351 new_profile.push_back(
352 {actual_start, profile[current].second - event_demand});
355 while (profile[current].first < actual_end) {
356 new_profile.push_back(
357 {profile[current].first, profile[current].second - event_demand});
361 if (profile[current].first > actual_end) {
362 new_profile.push_back(
363 {actual_end, new_profile.back().second + event_demand});
365 while (current < profile.size()) {
366 new_profile.push_back(profile[current]);
369 profile.swap(new_profile);
397void OrthogonalPackingInfeasibilityDetector::GetAllCandidatesForKForDff2(
398 absl::Span<const IntegerValue> sizes, IntegerValue bb_size,
399 IntegerValue sqrt_bb_size, Bitset64<IntegerValue>& candidates) {
401 candidates.ClearAndResize(bb_size / 2 + 2);
404 for (IntegerValue
i = 2;
i <= sqrt_bb_size;
i++) {
407 for (
int i = 1;
i <= sqrt_bb_size;
i++) {
408 const ::util::math::ConstantDivisor<uint16_t> div(
i);
410 candidates.Set(bb_size.value() / div);
412 for (
int k = 0; k < sizes.size(); k++) {
413 IntegerValue size = sizes[k];
414 if (2 * size > bb_size && size < bb_size) {
415 candidates.Set((bb_size.value() - size.value() + 1) / div);
416 }
else if (2 * size < bb_size) {
417 candidates.Set(size.value() / div);
435 candidates.Resize(bb_size / 4 + 1);
436 candidates.Resize(bb_size / 3 + 2);
437 candidates.Set(bb_size / 4 + 1);
439 candidates.Set(bb_size / 3 + 1);
454OrthogonalPackingInfeasibilityDetector::CheckFeasibilityWithDualFunction2(
455 absl::Span<const IntegerValue> sizes_x,
456 absl::Span<const IntegerValue> sizes_y,
457 absl::Span<const int> index_by_decreasing_x_size, IntegerValue x_bb_size,
458 IntegerValue y_bb_size,
int max_number_of_parameters_to_check) {
459 if (x_bb_size == 1) {
460 return OrthogonalPackingResult();
462 std::vector<IntegerValue> sizes_x_rescaled;
463 if (x_bb_size >= std::numeric_limits<uint16_t>::max()) {
468 absl::bit_width(
static_cast<uint64_t
>(x_bb_size.value() + 1)) - 16 + 1;
469 const RoundingDualFeasibleFunctionPowerOfTwo dff(x_bb_size, log2_k);
470 sizes_x_rescaled.resize(sizes_x.size());
471 for (
int i = 0;
i < sizes_x.size();
i++) {
472 sizes_x_rescaled[
i] = dff(sizes_x[
i]);
474 x_bb_size = dff(x_bb_size);
475 CHECK_LT(x_bb_size, std::numeric_limits<uint16_t>::max());
476 sizes_x = sizes_x_rescaled;
479 Bitset64<IntegerValue> candidates;
481 int num_items = sizes_x.size();
482 const IntegerValue max_possible_number_of_parameters =
483 std::min(x_bb_size / 4 + 1, sqrt_bb_size * num_items);
484 if (5ull * max_number_of_parameters_to_check <
485 max_possible_number_of_parameters) {
489 candidates.Resize(x_bb_size / 4 + 1);
490 int num_candidates = 0;
491 while (num_candidates < max_number_of_parameters_to_check) {
492 const IntegerValue pick =
493 absl::Uniform(random_, 1, x_bb_size.value() / 4);
494 if (!candidates.IsSet(pick)) {
495 candidates.Set(pick);
500 GetAllCandidatesForKForDff2(sizes_x, x_bb_size, sqrt_bb_size, candidates);
502 if (max_number_of_parameters_to_check < max_possible_number_of_parameters) {
506 for (
auto it = candidates.begin(); it != candidates.end(); ++it) {
509 if (count > max_number_of_parameters_to_check) {
510 std::vector<IntegerValue> sampled_candidates(
511 max_number_of_parameters_to_check);
512 std::sample(candidates.begin(), candidates.end(),
513 sampled_candidates.begin(),
514 max_number_of_parameters_to_check, random_);
515 candidates.ClearAll();
516 for (
const IntegerValue k : sampled_candidates) {
522 OrthogonalPackingResult best_result;
525 std::vector<IntegerValue> modified_sizes(num_items);
526 for (
const IntegerValue k : candidates) {
527 const RoundingDualFeasibleFunction dff(x_bb_size, k);
528 IntegerValue energy = 0;
529 for (
int i = 0;
i < num_items;
i++) {
530 modified_sizes[
i] = dff(sizes_x[
i]);
531 energy += modified_sizes[
i] * sizes_y[
i];
533 const IntegerValue modified_x_bb_size = dff(x_bb_size);
536 GetDffConflict(sizes_x, sizes_y, index_by_decreasing_x_size,
537 modified_sizes, modified_x_bb_size, x_bb_size, energy,
538 modified_x_bb_size * y_bb_size, &dff0_k);
542 DFFComposedF2F0 composed_dff(x_bb_size, dff0_k, k);
543 dff0_res.conflict_type_ = OrthogonalPackingResult::ConflictType::DFF_F2;
544 for (
auto& item : dff0_res.items_participating_on_conflict_) {
546 composed_dff.LowestInverse(composed_dff(sizes_x[item.index]));
550 DCHECK_EQ(composed_dff(item.size_x), composed_dff(sizes_x[item.index]));
551 DCHECK_LE(item.size_x, sizes_x[item.index]);
553 item.size_y = sizes_y[item.index];
555 if (dff0_res.IsBetterThan(best_result)) {
556 best_result = dff0_res;
563bool OrthogonalPackingInfeasibilityDetector::RelaxConflictWithBruteForce(
565 std::pair<IntegerValue, IntegerValue> bounding_box_size,
566 int brute_force_threshold) {
567 const int num_items_originally =
568 result.items_participating_on_conflict_.size();
569 if (num_items_originally > 2 * brute_force_threshold) {
573 std::vector<IntegerValue> sizes_x;
574 std::vector<IntegerValue> sizes_y;
575 std::vector<int> indexes;
576 std::vector<bool> to_be_removed(num_items_originally,
false);
578 sizes_x.reserve(num_items_originally - 1);
579 sizes_y.reserve(num_items_originally - 1);
580 for (
int i = 0;
i < num_items_originally;
i++) {
584 for (
int j = 0; j < num_items_originally; j++) {
585 if (
i == j || to_be_removed[j]) {
588 sizes_x.push_back(result.items_participating_on_conflict_[j].size_x);
589 sizes_y.push_back(result.items_participating_on_conflict_[j].size_y);
592 sizes_x, sizes_y, bounding_box_size, brute_force_threshold);
595 to_be_removed[
i] =
true;
598 if (!std::any_of(to_be_removed.begin(), to_be_removed.end(),
599 [](
bool b) { return b; })) {
602 OrthogonalPackingResult original = result;
604 result.conflict_type_ = OrthogonalPackingResult::ConflictType::BRUTE_FORCE;
606 result.items_participating_on_conflict_.clear();
607 for (
int i = 0;
i < num_items_originally;
i++) {
608 if (to_be_removed[
i]) {
611 result.items_participating_on_conflict_.push_back(
612 original.items_participating_on_conflict_[
i]);
618OrthogonalPackingInfeasibilityDetector::TestFeasibilityImpl(
619 absl::Span<const IntegerValue> sizes_x,
620 absl::Span<const IntegerValue> sizes_y,
621 std::pair<IntegerValue, IntegerValue> bounding_box_size,
623 using ConflictType = OrthogonalPackingResult::ConflictType;
625 const int num_items = sizes_x.size();
626 DCHECK_EQ(num_items, sizes_y.size());
627 const IntegerValue bb_area =
628 bounding_box_size.first * bounding_box_size.second;
629 IntegerValue total_energy = 0;
631 auto make_item = [sizes_x, sizes_y](
int i) {
632 return OrthogonalPackingResult::Item{
633 .index =
i, .size_x = sizes_x[
i], .size_y = sizes_y[
i]};
636 index_by_decreasing_x_size_.resize(num_items);
637 index_by_decreasing_y_size_.resize(num_items);
638 for (
int i = 0;
i < num_items;
i++) {
639 total_energy += sizes_x[
i] * sizes_y[
i];
640 index_by_decreasing_x_size_[
i] =
i;
641 index_by_decreasing_y_size_[
i] =
i;
642 if (sizes_x[
i] > bounding_box_size.first ||
643 sizes_y[
i] > bounding_box_size.second) {
644 OrthogonalPackingResult result(
646 result.conflict_type_ = ConflictType::TRIVIAL;
647 result.items_participating_on_conflict_ = {make_item(
i)};
652 if (num_items <= 1) {
656 std::sort(index_by_decreasing_x_size_.begin(),
657 index_by_decreasing_x_size_.end(),
658 [&sizes_x, &sizes_y](
int a,
int b) {
660 return std::tie(sizes_x[a], sizes_y[a]) >
661 std::tie(sizes_x[b], sizes_y[b]);
663 std::sort(index_by_decreasing_y_size_.begin(),
664 index_by_decreasing_y_size_.end(),
665 [&sizes_y, &sizes_x](
int a,
int b) {
666 return std::tie(sizes_y[a], sizes_x[a]) >
667 std::tie(sizes_y[b], sizes_x[b]);
671 if (options.use_pairwise) {
672 if (
auto pair = FindPairwiseConflict(sizes_x, sizes_y, bounding_box_size,
673 index_by_decreasing_x_size_,
674 index_by_decreasing_y_size_);
676 OrthogonalPackingResult result(
678 result.conflict_type_ = ConflictType::PAIRWISE;
679 result.items_participating_on_conflict_ = {
680 make_item(pair.value().first), make_item(pair.value().second)};
683 if (num_items == 2) {
689 if (total_energy > bb_area) {
690 result.conflict_type_ = ConflictType::TRIVIAL;
692 std::vector<std::pair<int, IntegerValue>> index_to_energy;
693 index_to_energy.reserve(num_items);
694 for (
int i = 0;
i < num_items;
i++) {
695 index_to_energy.push_back({
i, sizes_x[
i] * sizes_y[
i]});
697 std::sort(index_to_energy.begin(), index_to_energy.end(),
698 [](
const std::pair<int, IntegerValue>& a,
699 const std::pair<int, IntegerValue>&
b) {
700 return a.second > b.second;
702 IntegerValue recomputed_energy = 0;
703 for (
int i = 0;
i < index_to_energy.size();
i++) {
704 recomputed_energy += index_to_energy[
i].second;
705 if (recomputed_energy > bb_area) {
706 result.items_participating_on_conflict_.resize(
i + 1);
707 for (
int j = 0; j <=
i; j++) {
708 result.items_participating_on_conflict_[j] =
709 make_item(index_to_energy[j].first);
711 result.slack_ = recomputed_energy - bb_area - 1;
717 const int minimum_conflict_size = options.use_pairwise ? 3 : 2;
718 if (result.items_participating_on_conflict_.size() == minimum_conflict_size) {
722 if (options.use_dff_f0) {
731 GetDffConflict(sizes_x, sizes_y, index_by_decreasing_x_size_, sizes_x,
732 bounding_box_size.first, bounding_box_size.first,
733 total_energy, bb_area, &best_k);
734 if (conflict.IsBetterThan(result)) {
739 GetDffConflict(sizes_y, sizes_x, index_by_decreasing_y_size_, sizes_y,
740 bounding_box_size.second, bounding_box_size.second,
741 total_energy, bb_area, &best_k);
742 for (
auto& item : conflict.items_participating_on_conflict_) {
743 std::swap(item.size_x, item.size_y);
745 if (conflict.IsBetterThan(result)) {
750 if (result.items_participating_on_conflict_.size() == minimum_conflict_size) {
754 bool found_scheduling_solution =
false;
755 if (options.use_dff_f2) {
759 if (FindHeuristicSchedulingSolution(
760 sizes_x, sizes_y, index_by_decreasing_x_size_,
761 bounding_box_size.first, bounding_box_size.second,
762 scheduling_profile_, new_scheduling_profile_) ||
763 FindHeuristicSchedulingSolution(
764 sizes_y, sizes_x, index_by_decreasing_y_size_,
765 bounding_box_size.second, bounding_box_size.first,
766 scheduling_profile_, new_scheduling_profile_)) {
767 num_scheduling_possible_++;
769 found_scheduling_solution =
true;
773 if (!found_scheduling_solution && options.use_dff_f2) {
776 auto conflict = CheckFeasibilityWithDualFunction2(
777 sizes_x, sizes_y, index_by_decreasing_x_size_, bounding_box_size.first,
778 bounding_box_size.second,
779 options.dff2_max_number_of_parameters_to_check);
780 if (conflict.IsBetterThan(result)) {
784 if (result.items_participating_on_conflict_.size() ==
785 minimum_conflict_size) {
788 conflict = CheckFeasibilityWithDualFunction2(
789 sizes_y, sizes_x, index_by_decreasing_y_size_, bounding_box_size.second,
790 bounding_box_size.first,
791 options.dff2_max_number_of_parameters_to_check);
792 for (
auto& item : conflict.items_participating_on_conflict_) {
793 std::swap(item.size_x, item.size_y);
795 if (conflict.IsBetterThan(result)) {
802 sizes_x, sizes_y, bounding_box_size, options.brute_force_threshold);
803 num_brute_force_calls_ +=
806 result.conflict_type_ = ConflictType::BRUTE_FORCE;
808 result.items_participating_on_conflict_.resize(num_items);
809 for (
int i = 0;
i < num_items;
i++) {
810 result.items_participating_on_conflict_[
i] = make_item(
i);
818 num_brute_force_relaxation_ += RelaxConflictWithBruteForce(
819 result, bounding_box_size, options.brute_force_threshold);
826 absl::Span<const IntegerValue> sizes_x,
827 absl::Span<const IntegerValue> sizes_y,
828 std::pair<IntegerValue, IntegerValue> bounding_box_size,
830 using ConflictType = OrthogonalPackingResult::ConflictType;
834 TestFeasibilityImpl(sizes_x, sizes_y, bounding_box_size, options);
838 switch (result.conflict_type_) {
839 case ConflictType::DFF_F0:
840 num_conflicts_dff0_++;
842 case ConflictType::DFF_F2:
843 num_conflicts_dff2_++;
845 case ConflictType::PAIRWISE:
846 num_conflicts_two_items_++;
848 case ConflictType::TRIVIAL:
850 num_trivial_conflicts_++;
852 case ConflictType::BRUTE_FORCE:
853 num_brute_force_conflicts_++;
855 case ConflictType::NO_CONFLICT:
856 LOG(FATAL) <<
"Should never happen";
864 int i,
Coord coord, IntegerValue lower_bound) {
865 Item& item = items_participating_on_conflict_[
i];
867 const IntegerValue orthogonal_size =
870 if (size <= lower_bound || orthogonal_size > slack_) {
873 const IntegerValue new_size =
874 std::max(lower_bound, size - slack_ / orthogonal_size);
875 slack_ -= (size - new_size) * orthogonal_size;
876 DCHECK_NE(size, new_size);
877 DCHECK_GE(slack_, 0);
OrthogonalPackingResult TestFeasibility(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, const OrthogonalPackingOptions &options=OrthogonalPackingOptions())
~OrthogonalPackingInfeasibilityDetector()
bool TryUseSlackToReduceItemSize(int i, Coord coord, IntegerValue lower_bound=0)
IntegerValue LowestInverse(IntegerValue y) const
IntegerValue LowestInverse(IntegerValue y) const
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
BruteForceResult BruteForceOrthogonalPacking(absl::Span< const IntegerValue > sizes_x, absl::Span< const IntegerValue > sizes_y, std::pair< IntegerValue, IntegerValue > bounding_box_size, int max_complexity)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid