2121#include < absl/strings/string_view.h>
2222
2323#include < algorithm>
24+ #include < iterator>
2425#include < random>
2526
27+ #include " ortools/base/stl_util.h"
2628#include " ortools/set_cover/base_types.h"
2729#include " ortools/set_cover/set_cover_cft.h"
2830#include " ortools/set_cover/set_cover_submodel.h"
@@ -146,13 +148,13 @@ BinPackingModel ReadCsp(absl::string_view filename) {
146148 return model;
147149}
148150
149- void BestFit (const BinPackingModel& model ,
151+ void BestFit (const ElementCostVector& weights, Cost bin_capacity ,
150152 const std::vector<ElementIndex>& items, PartialBins& bins_data) {
151153 for (ElementIndex item : items) {
152- Cost item_weight = model. weights () [item];
154+ Cost item_weight = weights[item];
153155 BaseInt selected_bin = bins_data.bins .size ();
154156 for (BaseInt bin = 0 ; bin < bins_data.bins .size (); ++bin) {
155- Cost max_load = model. bin_capacity () - item_weight;
157+ Cost max_load = bin_capacity - item_weight;
156158 if (bins_data.loads [bin] <= max_load &&
157159 (selected_bin == bins_data.bins .size () ||
158160 bins_data.loads [bin] > bins_data.loads [selected_bin])) {
@@ -232,9 +234,9 @@ void AddRandomizedBins(const BinPackingModel& model, BaseInt num_bins,
232234 // Generate bins all containing a specific item
233235 for (ElementIndex n : model.ItemRange ()) {
234236 BaseInt unique_bin_num = scp_model.full_model ().num_subsets ();
235- VLOG_EVERY_N_SEC (1 , 5 )
236- << " Generating bins: " << unique_bin_num << " / " << num_bins << " ( "
237- << 100.0 * unique_bin_num / num_bins << " %)" ;
237+ VLOG_EVERY_N_SEC (1 , 1 )
238+ << " [RGEN] Generating bins: " << unique_bin_num << " / " << num_bins
239+ << " ( " << 100.0 * unique_bin_num / num_bins << " %)" ;
238240 if (scp_model.full_model ().num_subsets () >= num_bins) {
239241 break ;
240242 }
@@ -251,7 +253,7 @@ void AddRandomizedBins(const BinPackingModel& model, BaseInt num_bins,
251253 bins_data.bins .push_back ({n});
252254 bins_data.loads .push_back (model.weights ()[n]);
253255 }
254- BestFit (model, items, bins_data);
256+ BestFit (model. weights (), model. bin_capacity () , items, bins_data);
255257 InsertBinsIntoModel (bins_data, scp_model);
256258
257259 items.push_back (n);
@@ -272,51 +274,86 @@ BinPackingSetCoverModel GenerateInitialBins(const BinPackingModel& model) {
272274 std::vector<ElementIndex> items (model.num_items ());
273275
274276 absl::c_iota (items, ElementIndex (0 ));
275- BestFit (model, items, bins_data);
277+ absl::c_sort (items, [&](auto i1, auto i2) {
278+ return model.weights ()[i1] > model.weights ()[i2];
279+ });
280+ BestFit (model.weights (), model.bin_capacity (), items, bins_data);
276281 InsertBinsIntoModel (bins_data, scp_model);
277- BaseInt solution_bin_num = bins_data.bins .size ();
278- VLOG (1 ) << " Best-fit solution: " << solution_bin_num << " bins" ;
279-
280- // Largest first
281- if (!absl::c_is_sorted (model.weights (), std::greater<>())) {
282- bins_data.bins .clear ();
283- bins_data.loads .clear ();
284- absl::c_sort (items, [&](auto i1, auto i2) {
285- return model.weights ()[i1] > model.weights ()[i2];
286- });
287- BestFit (model, items, bins_data);
288- InsertBinsIntoModel (bins_data, scp_model);
289- }
282+ VLOG (1 ) << " [BFIT] Largest first best-fit solution: " << bins_data.bins .size ()
283+ << " bins" ;
290284
291285 scp_model.CompleteModel ();
292286 return scp_model;
293287}
294288
295- void ExpKnap::Solve (const ElementCostVector& profits,
296- const ElementCostVector& weights, Cost capacity,
297- BaseInt bnb_nodes_limit) {
289+ void ExpKnap::SaveBin () {
290+ collected_bins_.back ().clear ();
291+ for (ElementIndex i : exceptions_) {
292+ break_selection_[i] = !break_selection_[i];
293+ }
294+ for (ElementIndex i : break_solution_) {
295+ if (break_selection_[i]) {
296+ collected_bins_.back ().push_back (i);
297+ }
298+ }
299+ for (ElementIndex i : exceptions_) {
300+ if (break_selection_[i]) {
301+ collected_bins_.back ().push_back (i);
302+ }
303+ break_selection_[i] = !break_selection_[i];
304+ }
305+ absl::c_sort (collected_bins_.back ());
306+ DCHECK (absl::c_adjacent_find (collected_bins_.back ()) ==
307+ collected_bins_.back ().end ());
308+ }
309+ void ExpKnap::InitSolver (const ElementCostVector& profits,
310+ const ElementCostVector& weights, Cost capacity,
311+ BaseInt bnb_nodes_limit) {
298312 capacity_ = capacity;
299- best_delta_ = .0 ;
300- exceptions_.clear ();
301- maximal_exceptions_.clear ();
302- break_solution_.assign (profits.size (), false );
303313 items_.resize (profits.size ());
304- bnb_node_countdown_ = bnb_nodes_limit;
305-
314+ collected_bins_.clear ();
306315 for (ElementIndex i; i < ElementIndex (items_.size ()); ++i) {
307- items_[i] = {std::max ( 1e-6 , profits[i]) , weights[i], i};
316+ items_[i] = {profits[i], weights[i], i};
308317 }
309-
310318 absl::c_sort (items_, [](Item i1, Item i2) {
311- return i1.profit / i1.weight > i2.profit / i2.weight ;
319+ return i1.profit / i1.weight < i2.profit / i2.weight ;
312320 });
313- Heuristic (items_);
314- maximal_exceptions_.push_back (exceptions_);
321+
322+ bnb_node_countdown_ = bnb_nodes_limit;
323+ best_delta_ = .0 ;
315324 exceptions_.clear ();
316- VLOG (5 ) << " [KPCG] Heuristic solution: cost "
317- << break_profit_sum_ + best_delta_;
325+ break_selection_.assign (profits.size (), false );
326+ break_solution_.clear ();
327+ }
328+
329+ void ExpKnap::FindGoodColumns (const ElementCostVector& profits,
330+ const ElementCostVector& weights, Cost capacity,
331+ BaseInt bnb_nodes_limit) {
332+ InitSolver (profits, weights, capacity, bnb_nodes_limit);
333+ Cost curr_best_cost = .0 ;
334+ PartialBins more_bins;
335+ std::vector<ElementIndex> remaining_items;
318336
319- EleBranch (.0 , break_weight_sum_ - capacity, break_it_ - 1 , break_it_ + 1 );
337+ bnb_node_countdown_ = bnb_nodes_limit;
338+ inserted_items_.assign (profits.size (), false );
339+ do {
340+ collected_bins_.emplace_back ();
341+ Heuristic ();
342+ VLOG (5 ) << " [KPCG] Heuristic solution: cost "
343+ << break_profit_sum_ + best_delta_;
344+ EleBranch ();
345+
346+ for (ElementIndex i : collected_bins_.back ()) {
347+ inserted_items_[i] = true ;
348+ }
349+ gtl::STLEraseAllFromSequenceIf (
350+ &items_, [&](Item item) { return inserted_items_[item.index ]; });
351+ } while (!items_.empty () && break_profit_sum_ + best_delta_ > 1.0 );
352+ }
353+
354+ bool ExpKnap::EleBranch () {
355+ exceptions_.clear ();
356+ return EleBranch (.0 , break_weight_sum_ - capacity_, break_it_ - 1 , break_it_);
320357}
321358
322359namespace {
@@ -331,6 +368,8 @@ static Cost BoundCheck(Cost best_delta, Cost profit_delta, Cost overweight,
331368// https://hjemmesider.diku.dk/~pisinger/expknap.c
332369bool ExpKnap::EleBranch (Cost profit_delta, Cost overweigth, ItemIt out_item,
333370 ItemIt in_item) {
371+ VLOG (6 ) << " [KPCG] EleBranch: profit_delta " << profit_delta << " overweigth "
372+ << overweigth;
334373 if (bnb_node_countdown_-- <= 0 ) {
335374 return false ;
336375 }
@@ -345,7 +384,7 @@ bool ExpKnap::EleBranch(Cost profit_delta, Cost overweigth, ItemIt out_item,
345384 }
346385
347386 bool maximal = true ;
348- while (bnb_node_countdown_ > 0 && in_item < items_.end () &&
387+ while (bnb_node_countdown_ > 0 && in_item < items_.rend () &&
349388 BoundCheck (best_delta_, profit_delta, overweigth, *in_item) >= 0 ) {
350389 exceptions_.push_back (in_item->index );
351390 Cost next_delta = profit_delta + in_item->profit ;
@@ -355,11 +394,11 @@ bool ExpKnap::EleBranch(Cost profit_delta, Cost overweigth, ItemIt out_item,
355394 }
356395
357396 if (improved && maximal) {
358- maximal_exceptions_. push_back (exceptions_ );
397+ SaveBin ( );
359398 }
360399 improved |= !maximal;
361400 } else {
362- while (bnb_node_countdown_ > 0 && out_item >= items_.begin () &&
401+ while (bnb_node_countdown_ > 0 && out_item >= items_.rbegin () &&
363402 BoundCheck (best_delta_, profit_delta, overweigth, *out_item) >= 0 ) {
364403 exceptions_.push_back (out_item->index );
365404 Cost next_delta = profit_delta - out_item->profit ;
@@ -371,17 +410,18 @@ bool ExpKnap::EleBranch(Cost profit_delta, Cost overweigth, ItemIt out_item,
371410 return improved;
372411}
373412
374- void ExpKnap::Heuristic (
375- const util_intops::StrongVector<ElementIndex, Item>& items) {
413+ void ExpKnap::Heuristic () {
414+ best_delta_ = break_profit_sum_ = break_weight_sum_ = .0 ;
415+ break_it_ = items_.rbegin ();
416+ break_selection_.assign (items_.size (), false );
417+ break_solution_.clear ();
376418 exceptions_.clear ();
377-
378- break_profit_sum_ = break_weight_sum_ = .0 ;
379- break_it_ = items.begin ();
380- while (break_it_ < items.end () &&
419+ while (break_it_ < items_.rend () &&
381420 break_it_->weight <= capacity_ - break_weight_sum_) {
382421 break_profit_sum_ += break_it_->profit ;
383422 break_weight_sum_ += break_it_->weight ;
384- break_solution_[break_it_->index ] = true ;
423+ break_selection_[break_it_->index ] = true ;
424+ break_solution_.push_back (break_it_->index );
385425 ++break_it_;
386426 }
387427 Cost residual = capacity_ - break_weight_sum_;
@@ -391,74 +431,71 @@ void ExpKnap::Heuristic(
391431
392432 Cost profit_delta_ub = residual * break_it_->profit / break_it_->weight ;
393433 if (profit_delta_ub == .0 ) {
434+ SaveBin ();
394435 return ;
395436 }
396437
397438 // Try filling the residual space with less efficient (maybe smaller) items
398439 best_delta_ = .0 ;
399- for (auto it = break_it_; it < items. end (); it++) {
440+ for (auto it = break_it_; it < items_. rend (); it++) {
400441 if (it->weight <= residual && it->profit > best_delta_) {
401442 exceptions_ = {it->index };
402443 best_delta_ = it->profit ;
403444 if (best_delta_ >= profit_delta_ub) {
445+ SaveBin ();
404446 return ;
405447 }
406448 }
407449 }
408450
409451 // Try removing an item and adding the break item
410452 Cost min_weight = break_it_->weight - residual;
411- for (auto it = break_it_ - 1 ; it >= items. begin (); it--) {
453+ for (auto it = break_it_ - 1 ; it >= items_. rbegin (); it--) {
412454 Cost profit_delta = break_it_->profit - it->profit ;
413455 if (it->weight >= min_weight && profit_delta > best_delta_) {
414456 exceptions_ = {break_it_->index , it->index };
415457 best_delta_ = profit_delta;
416458 if (best_delta_ >= profit_delta_ub) {
459+ SaveBin ();
417460 return ;
418461 }
419462 }
420463 }
464+ SaveBin ();
421465}
422466
423467bool BinPackingSetCoverModel::UpdateCore (
424468 Cost best_lower_bound, const ElementCostVector& best_multipliers,
425469 const scp::Solution& best_solution, bool force) {
426- if (--column_gen_countdown_ <= 0 && best_lower_bound != prev_lower_bound_) {
427- column_gen_countdown_ = column_gen_period_;
470+ if (!base::IsTimeToUpdate (best_lower_bound, force)) {
471+ return false ;
472+ }
473+
474+ Cost full_lower_bound = base::UpdateMultipliers (best_multipliers);
475+ if (scp::DivideIfGE0 (std::abs (full_lower_bound - best_lower_bound),
476+ best_lower_bound) < 0.01 &&
477+ best_lower_bound != prev_lower_bound_) {
428478 prev_lower_bound_ = best_lower_bound;
429- knapsack_solver_.Solve (best_multipliers, bpp_model_->weights (),
430- bpp_model_->bin_capacity (),
431- /* bnb_nodes_limit=*/ 10000 );
432- const auto & exception_list = knapsack_solver_.maximal_exceptions ();
433- ElementBoolVector solution = knapsack_solver_.break_solution ();
479+ knapsack_solver_.FindGoodColumns (best_multipliers, bpp_model_->weights (),
480+ bpp_model_->bin_capacity (),
481+ /* bnb_nodes_limit=*/ 1000 );
434482 BaseInt num_added_bins = 0 ;
435- SparseColumn bin;
436- for (const std::vector<ElementIndex>& exception : exception_list) {
437- for (ElementIndex i : exception) {
438- solution[i] = !solution[i];
439- }
440-
441- bin.clear ();
442- for (ElementIndex i : globals_.full_model .ElementRange ()) {
443- if (solution[i]) {
444- bin.push_back (i);
445- }
446- }
483+ for (const SparseColumn& bin : knapsack_solver_.collected_bins ()) {
447484 num_added_bins += AddBin (bin) ? 1 : 0 ;
448-
449- for (ElementIndex i : exception) {
450- solution[i] = !solution[i];
451- }
452485 }
453486 if (num_added_bins > 0 ) {
454487 VLOG (4 ) << " [KPCG] Added " << num_added_bins << " / "
455488 << globals_.full_model .num_subsets () << " bins" ;
456489 }
490+ base::SizeUpdate ();
491+ // TODO(user): add incremental update only for the new columns just added
492+ base::UpdateMultipliers (best_multipliers);
457493 }
458494
459- scp::FullToCoreModel::SizeUpdate ();
460- scp::FullToCoreModel::FullToSubModelInvariantCheck ();
461- return scp::FullToCoreModel::UpdateCore (best_lower_bound, best_multipliers,
462- best_solution, force);
495+ if (base::num_focus_subsets () < FixingFullModelView ().num_focus_subsets ()) {
496+ ComputeAndSetFocus (best_lower_bound, best_solution);
497+ return true ;
498+ }
499+ return false ;
463500}
464501} // namespace operations_research
0 commit comments