OR-Tools  9.0
knapsack_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <limits>
19 #include <queue>
20 #include <string>
21 #include <vector>
22 
23 #include "absl/memory/memory.h"
24 #include "ortools/base/stl_util.h"
26 #include "ortools/util/bitset.h"
28 
29 namespace operations_research {
30 
31 namespace {
32 const int kNoSelection = -1;
33 const int kMasterPropagatorId = 0;
34 const int kMaxNumberOfBruteForceItems = 30;
35 const int kMaxNumberOf64Items = 64;
36 
37 // Comparator used to sort item in decreasing efficiency order
38 // (see KnapsackCapacityPropagator).
39 struct CompareKnapsackItemsInDecreasingEfficiencyOrder {
40  explicit CompareKnapsackItemsInDecreasingEfficiencyOrder(int64_t _profit_max)
41  : profit_max(_profit_max) {}
42  bool operator()(const KnapsackItemPtr& item1,
43  const KnapsackItemPtr& item2) const {
44  return item1->GetEfficiency(profit_max) > item2->GetEfficiency(profit_max);
45  }
46  const int64_t profit_max;
47 };
48 
49 // Comparator used to sort search nodes in the priority queue in order
50 // to pop first the node with the highest profit upper bound
51 // (see KnapsackSearchNode). When two nodes have the same upper bound, we
52 // prefer the one with the highest current profit, ie. usually the one closer
53 // to a leaf. In practice, the main advantage is to have smaller path.
54 struct CompareKnapsackSearchNodePtrInDecreasingUpperBoundOrder {
55  bool operator()(const KnapsackSearchNode* node_1,
56  const KnapsackSearchNode* node_2) const {
57  const int64_t profit_upper_bound_1 = node_1->profit_upper_bound();
58  const int64_t profit_upper_bound_2 = node_2->profit_upper_bound();
59  if (profit_upper_bound_1 == profit_upper_bound_2) {
60  return node_1->current_profit() < node_2->current_profit();
61  }
62  return profit_upper_bound_1 < profit_upper_bound_2;
63  }
64 };
65 
66 typedef std::priority_queue<
67  KnapsackSearchNode*, std::vector<KnapsackSearchNode*>,
68  CompareKnapsackSearchNodePtrInDecreasingUpperBoundOrder>
69  SearchQueue;
70 
71 // Returns true when value_1 * value_2 may overflow int64_t.
72 inline bool WillProductOverflow(int64_t value_1, int64_t value_2) {
73  const int MostSignificantBitPosition1 = MostSignificantBitPosition64(value_1);
74  const int MostSignificantBitPosition2 = MostSignificantBitPosition64(value_2);
75  // The sum should be less than 61 to be safe as we are only considering the
76  // most significant bit and dealing with int64_t instead of uint64_t.
77  const int kOverflow = 61;
78  return MostSignificantBitPosition1 + MostSignificantBitPosition2 > kOverflow;
79 }
80 
81 // Returns an upper bound of (numerator_1 * numerator_2) / denominator
82 int64_t UpperBoundOfRatio(int64_t numerator_1, int64_t numerator_2,
83  int64_t denominator) {
84  DCHECK_GT(denominator, int64_t{0});
85  if (!WillProductOverflow(numerator_1, numerator_2)) {
86  const int64_t numerator = numerator_1 * numerator_2;
87  // Round to zero.
88  const int64_t result = numerator / denominator;
89  return result;
90  } else {
91  const double ratio =
92  (static_cast<double>(numerator_1) * static_cast<double>(numerator_2)) /
93  static_cast<double>(denominator);
94  // Round near.
95  const int64_t result = static_cast<int64_t>(floor(ratio + 0.5));
96  return result;
97  }
98 }
99 
100 } // namespace
101 
102 // ----- KnapsackSearchNode -----
104  const KnapsackAssignment& assignment)
105  : depth_((parent == nullptr) ? 0 : parent->depth() + 1),
106  parent_(parent),
107  assignment_(assignment),
108  current_profit_(0),
109  profit_upper_bound_(std::numeric_limits<int64_t>::max()),
110  next_item_id_(kNoSelection) {}
111 
112 // ----- KnapsackSearchPath -----
114  const KnapsackSearchNode& to)
115  : from_(from), via_(nullptr), to_(to) {}
116 
118  const KnapsackSearchNode* node_from = MoveUpToDepth(from_, to_.depth());
119  const KnapsackSearchNode* node_to = MoveUpToDepth(to_, from_.depth());
120  CHECK_EQ(node_from->depth(), node_to->depth());
121 
122  // Find common parent.
123  while (node_from != node_to) {
124  node_from = node_from->parent();
125  node_to = node_to->parent();
126  }
127  via_ = node_from;
128 }
129 
131  const KnapsackSearchNode& node, int depth) const {
132  const KnapsackSearchNode* current_node = &node;
133  while (current_node->depth() > depth) {
134  current_node = current_node->parent();
135  }
136  return current_node;
137 }
138 
139 // ----- KnapsackState -----
140 KnapsackState::KnapsackState() : is_bound_(), is_in_() {}
141 
142 void KnapsackState::Init(int number_of_items) {
143  is_bound_.assign(number_of_items, false);
144  is_in_.assign(number_of_items, false);
145 }
146 
147 // Returns false when the state is invalid.
148 bool KnapsackState::UpdateState(bool revert,
149  const KnapsackAssignment& assignment) {
150  if (revert) {
151  is_bound_[assignment.item_id] = false;
152  } else {
153  if (is_bound_[assignment.item_id] &&
154  is_in_[assignment.item_id] != assignment.is_in) {
155  return false;
156  }
157  is_bound_[assignment.item_id] = true;
158  is_in_[assignment.item_id] = assignment.is_in;
159  }
160  return true;
161 }
162 
163 // ----- KnapsackPropagator -----
165  : items_(),
166  current_profit_(0),
167  profit_lower_bound_(0),
168  profit_upper_bound_(std::numeric_limits<int64_t>::max()),
169  state_(state) {}
170 
172 
173 void KnapsackPropagator::Init(const std::vector<int64_t>& profits,
174  const std::vector<int64_t>& weights) {
175  const int number_of_items = profits.size();
176  items_.assign(number_of_items, static_cast<KnapsackItemPtr>(nullptr));
177  for (int i = 0; i < number_of_items; ++i) {
178  items_[i] = new KnapsackItem(i, weights[i], profits[i]);
179  }
180  current_profit_ = 0;
181  profit_lower_bound_ = std::numeric_limits<int64_t>::min();
182  profit_upper_bound_ = std::numeric_limits<int64_t>::max();
183  InitPropagator();
184 }
185 
186 bool KnapsackPropagator::Update(bool revert,
187  const KnapsackAssignment& assignment) {
188  if (assignment.is_in) {
189  if (revert) {
190  current_profit_ -= items_[assignment.item_id]->profit;
191  } else {
192  current_profit_ += items_[assignment.item_id]->profit;
193  }
194  }
195  return UpdatePropagator(revert, assignment);
196 }
197 
199  bool has_one_propagator, std::vector<bool>* solution) const {
200  CHECK(solution != nullptr);
201  for (const KnapsackItem* const item : items_) {
202  const int item_id = item->id;
203  (*solution)[item_id] = state_.is_bound(item_id) && state_.is_in(item_id);
204  }
205  if (has_one_propagator) {
207  }
208 }
209 
210 // ----- KnapsackCapacityPropagator -----
212  const KnapsackState& state, int64_t capacity)
213  : KnapsackPropagator(state),
214  capacity_(capacity),
215  consumed_capacity_(0),
216  break_item_id_(kNoSelection),
217  sorted_items_(),
218  profit_max_(0) {}
219 
221 
222 // TODO(user): Make it more incremental, by saving the break item in a
223 // search node for instance.
226  break_item_id_ = kNoSelection;
227 
228  int64_t remaining_capacity = capacity_ - consumed_capacity_;
229  int break_sorted_item_id = kNoSelection;
230  const int number_of_sorted_items = sorted_items_.size();
231  for (int sorted_id = 0; sorted_id < number_of_sorted_items; ++sorted_id) {
232  const KnapsackItem* const item = sorted_items_[sorted_id];
233  if (!state().is_bound(item->id)) {
234  break_item_id_ = item->id;
235 
236  if (remaining_capacity >= item->weight) {
237  remaining_capacity -= item->weight;
239  } else {
240  break_sorted_item_id = sorted_id;
241  break;
242  }
243  }
244  }
245 
247 
248  if (break_sorted_item_id != kNoSelection) {
249  const int64_t additional_profit =
250  GetAdditionalProfit(remaining_capacity, break_sorted_item_id);
251  set_profit_upper_bound(profit_upper_bound() + additional_profit);
252  }
253 }
254 
256  consumed_capacity_ = 0;
257  break_item_id_ = kNoSelection;
258  sorted_items_ = items();
259  profit_max_ = 0;
260  for (const KnapsackItem* const item : sorted_items_) {
261  profit_max_ = std::max(profit_max_, item->profit);
262  }
263  ++profit_max_;
264  CompareKnapsackItemsInDecreasingEfficiencyOrder compare_object(profit_max_);
265  std::sort(sorted_items_.begin(), sorted_items_.end(), compare_object);
266 }
267 
268 // Returns false when the propagator fails.
270  bool revert, const KnapsackAssignment& assignment) {
271  if (assignment.is_in) {
272  if (revert) {
273  consumed_capacity_ -= items()[assignment.item_id]->weight;
274  } else {
275  consumed_capacity_ += items()[assignment.item_id]->weight;
276  if (consumed_capacity_ > capacity_) {
277  return false;
278  }
279  }
280  }
281  return true;
282 }
283 
285  std::vector<bool>* solution) const {
286  CHECK(solution != nullptr);
287  int64_t remaining_capacity = capacity_ - consumed_capacity_;
288  for (const KnapsackItem* const item : sorted_items_) {
289  if (!state().is_bound(item->id)) {
290  if (remaining_capacity >= item->weight) {
291  remaining_capacity -= item->weight;
292  (*solution)[item->id] = true;
293  } else {
294  return;
295  }
296  }
297  }
298 }
299 
300 int64_t KnapsackCapacityPropagator::GetAdditionalProfit(
301  int64_t remaining_capacity, int break_item_id) const {
302  const int after_break_item_id = break_item_id + 1;
303  int64_t additional_profit_when_no_break_item = 0;
304  if (after_break_item_id < sorted_items_.size()) {
305  // As items are sorted by decreasing profit / weight ratio, and the current
306  // weight is non-zero, the next_weight is non-zero too.
307  const int64_t next_weight = sorted_items_[after_break_item_id]->weight;
308  const int64_t next_profit = sorted_items_[after_break_item_id]->profit;
309  additional_profit_when_no_break_item =
310  UpperBoundOfRatio(remaining_capacity, next_profit, next_weight);
311  }
312 
313  const int before_break_item_id = break_item_id - 1;
314  int64_t additional_profit_when_break_item = 0;
315  if (before_break_item_id >= 0) {
316  const int64_t previous_weight = sorted_items_[before_break_item_id]->weight;
317  // Having previous_weight == 0 means the total capacity is smaller than
318  // the weight of the current item. In such a case the item cannot be part
319  // of a solution of the local one dimension problem.
320  if (previous_weight != 0) {
321  const int64_t previous_profit =
322  sorted_items_[before_break_item_id]->profit;
323  const int64_t overused_capacity =
324  sorted_items_[break_item_id]->weight - remaining_capacity;
325  const int64_t ratio = UpperBoundOfRatio(overused_capacity,
326  previous_profit, previous_weight);
327  additional_profit_when_break_item =
328  sorted_items_[break_item_id]->profit - ratio;
329  }
330  }
331 
332  const int64_t additional_profit = std::max(
333  additional_profit_when_no_break_item, additional_profit_when_break_item);
334  CHECK_GE(additional_profit, 0);
335  return additional_profit;
336 }
337 
338 // ----- KnapsackGenericSolver -----
339 KnapsackGenericSolver::KnapsackGenericSolver(const std::string& solver_name)
340  : BaseKnapsackSolver(solver_name),
341  propagators_(),
342  master_propagator_id_(kMasterPropagatorId),
343  search_nodes_(),
344  state_(),
345  best_solution_profit_(0),
346  best_solution_() {}
347 
349 
351  const std::vector<int64_t>& profits,
352  const std::vector<std::vector<int64_t>>& weights,
353  const std::vector<int64_t>& capacities) {
354  CHECK_EQ(capacities.size(), weights.size());
355 
356  Clear();
357  const int number_of_items = profits.size();
358  const int number_of_dimensions = weights.size();
359  state_.Init(number_of_items);
360  best_solution_.assign(number_of_items, false);
361  for (int i = 0; i < number_of_dimensions; ++i) {
362  CHECK_EQ(number_of_items, weights[i].size());
363 
364  KnapsackCapacityPropagator* propagator =
365  new KnapsackCapacityPropagator(state_, capacities[i]);
366  propagator->Init(profits, weights[i]);
367  propagators_.push_back(propagator);
368  }
369  master_propagator_id_ = kMasterPropagatorId;
370 }
371 
373  int item_id, bool is_item_in, int64_t* lower_bound, int64_t* upper_bound) {
374  CHECK(lower_bound != nullptr);
375  CHECK(upper_bound != nullptr);
376  KnapsackAssignment assignment(item_id, is_item_in);
377  const bool fail = !IncrementalUpdate(false, assignment);
378  if (fail) {
379  *lower_bound = 0LL;
380  *upper_bound = 0LL;
381  } else {
382  *lower_bound =
383  (HasOnePropagator())
384  ? propagators_[master_propagator_id_]->profit_lower_bound()
385  : 0LL;
386  *upper_bound = GetAggregatedProfitUpperBound();
387  }
388 
389  const bool fail_revert = !IncrementalUpdate(true, assignment);
390  if (fail_revert) {
391  *lower_bound = 0LL;
392  *upper_bound = 0LL;
393  }
394 }
395 
397  bool* is_solution_optimal) {
398  DCHECK(time_limit != nullptr);
399  DCHECK(is_solution_optimal != nullptr);
400  best_solution_profit_ = 0LL;
401  *is_solution_optimal = true;
402 
403  SearchQueue search_queue;
404  const KnapsackAssignment assignment(kNoSelection, true);
405  KnapsackSearchNode* root_node = new KnapsackSearchNode(nullptr, assignment);
406  root_node->set_current_profit(GetCurrentProfit());
407  root_node->set_profit_upper_bound(GetAggregatedProfitUpperBound());
408  root_node->set_next_item_id(GetNextItemId());
409  search_nodes_.push_back(root_node);
410 
411  if (MakeNewNode(*root_node, false)) {
412  search_queue.push(search_nodes_.back());
413  }
414  if (MakeNewNode(*root_node, true)) {
415  search_queue.push(search_nodes_.back());
416  }
417 
418  KnapsackSearchNode* current_node = root_node;
419  while (!search_queue.empty() &&
420  search_queue.top()->profit_upper_bound() > best_solution_profit_) {
421  if (time_limit->LimitReached()) {
422  *is_solution_optimal = false;
423  break;
424  }
425  KnapsackSearchNode* const node = search_queue.top();
426  search_queue.pop();
427 
428  if (node != current_node) {
429  KnapsackSearchPath path(*current_node, *node);
430  path.Init();
431  const bool no_fail = UpdatePropagators(path);
432  current_node = node;
433  CHECK_EQ(no_fail, true);
434  }
435 
436  if (MakeNewNode(*node, false)) {
437  search_queue.push(search_nodes_.back());
438  }
439  if (MakeNewNode(*node, true)) {
440  search_queue.push(search_nodes_.back());
441  }
442  }
443  return best_solution_profit_;
444 }
445 
446 void KnapsackGenericSolver::Clear() {
447  gtl::STLDeleteElements(&propagators_);
448  gtl::STLDeleteElements(&search_nodes_);
449 }
450 
451 // Returns false when at least one propagator fails.
452 bool KnapsackGenericSolver::UpdatePropagators(const KnapsackSearchPath& path) {
453  bool no_fail = true;
454  // Revert previous changes.
455  const KnapsackSearchNode* node = &path.from();
456  const KnapsackSearchNode* via = &path.via();
457  while (node != via) {
458  no_fail = IncrementalUpdate(true, node->assignment()) && no_fail;
459  node = node->parent();
460  }
461  // Apply current changes.
462  node = &path.to();
463  while (node != via) {
464  no_fail = IncrementalUpdate(false, node->assignment()) && no_fail;
465  node = node->parent();
466  }
467  return no_fail;
468 }
469 
470 int64_t KnapsackGenericSolver::GetAggregatedProfitUpperBound() const {
472  for (KnapsackPropagator* const prop : propagators_) {
473  prop->ComputeProfitBounds();
474  const int64_t propagator_upper_bound = prop->profit_upper_bound();
475  upper_bound = std::min(upper_bound, propagator_upper_bound);
476  }
477  return upper_bound;
478 }
479 
480 bool KnapsackGenericSolver::MakeNewNode(const KnapsackSearchNode& node,
481  bool is_in) {
482  if (node.next_item_id() == kNoSelection) {
483  return false;
484  }
485  KnapsackAssignment assignment(node.next_item_id(), is_in);
486  KnapsackSearchNode new_node(&node, assignment);
487 
488  KnapsackSearchPath path(node, new_node);
489  path.Init();
490  const bool no_fail = UpdatePropagators(path);
491  if (no_fail) {
492  new_node.set_current_profit(GetCurrentProfit());
493  new_node.set_profit_upper_bound(GetAggregatedProfitUpperBound());
494  new_node.set_next_item_id(GetNextItemId());
495  UpdateBestSolution();
496  }
497 
498  // Revert to be able to create another node from parent.
499  KnapsackSearchPath revert_path(new_node, node);
500  revert_path.Init();
501  UpdatePropagators(revert_path);
502 
503  if (!no_fail || new_node.profit_upper_bound() < best_solution_profit_) {
504  return false;
505  }
506 
507  // The node is relevant.
508  KnapsackSearchNode* relevant_node = new KnapsackSearchNode(&node, assignment);
509  relevant_node->set_current_profit(new_node.current_profit());
510  relevant_node->set_profit_upper_bound(new_node.profit_upper_bound());
511  relevant_node->set_next_item_id(new_node.next_item_id());
512  search_nodes_.push_back(relevant_node);
513 
514  return true;
515 }
516 
517 bool KnapsackGenericSolver::IncrementalUpdate(
518  bool revert, const KnapsackAssignment& assignment) {
519  // Do not stop on a failure: To be able to be incremental on the update,
520  // partial solution (state) and propagators must all be in the same state.
521  bool no_fail = state_.UpdateState(revert, assignment);
522  for (KnapsackPropagator* const prop : propagators_) {
523  no_fail = prop->Update(revert, assignment) && no_fail;
524  }
525  return no_fail;
526 }
527 
528 void KnapsackGenericSolver::UpdateBestSolution() {
529  const int64_t profit_lower_bound =
530  (HasOnePropagator())
531  ? propagators_[master_propagator_id_]->profit_lower_bound()
532  : propagators_[master_propagator_id_]->current_profit();
533 
534  if (best_solution_profit_ < profit_lower_bound) {
535  best_solution_profit_ = profit_lower_bound;
536  propagators_[master_propagator_id_]->CopyCurrentStateToSolution(
537  HasOnePropagator(), &best_solution_);
538  }
539 }
540 
541 // ----- KnapsackBruteForceSolver -----
542 // KnapsackBruteForceSolver solves the 0-1 knapsack problem when the number of
543 // items is less or equal to 30 with brute force, ie. explores all states.
544 // Experiments show better results than KnapsackGenericSolver when the
545 // number of items is less than 15.
547  public:
548  explicit KnapsackBruteForceSolver(const std::string& solver_name);
549 
550  // Initializes the solver and enters the problem to be solved.
551  void Init(const std::vector<int64_t>& profits,
552  const std::vector<std::vector<int64_t>>& weights,
553  const std::vector<int64_t>& capacities) override;
554 
555  // Solves the problem and returns the profit of the optimal solution.
556  int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
557 
558  // Returns true if the item 'item_id' is packed in the optimal knapsack.
559  bool best_solution(int item_id) const override {
560  return (best_solution_ & OneBit32(item_id)) != 0U;
561  }
562 
563  private:
564  int num_items_;
565  int64_t profits_weights_[kMaxNumberOfBruteForceItems * 2];
566  int64_t capacity_;
567  int64_t best_solution_profit_;
568  uint32_t best_solution_;
569 
570  DISALLOW_COPY_AND_ASSIGN(KnapsackBruteForceSolver);
571 };
572 
574  const std::string& solver_name)
575  : BaseKnapsackSolver(solver_name),
576  num_items_(0),
577  capacity_(0LL),
578  best_solution_profit_(0LL),
579  best_solution_(0U) {}
580 
582  const std::vector<int64_t>& profits,
583  const std::vector<std::vector<int64_t>>& weights,
584  const std::vector<int64_t>& capacities) {
585  // TODO(user): Implement multi-dimensional brute force solver.
586  CHECK_EQ(weights.size(), 1)
587  << "Brute force solver only works with one dimension.";
588  CHECK_EQ(capacities.size(), weights.size());
589 
590  num_items_ = profits.size();
591  CHECK_EQ(num_items_, weights.at(0).size());
592  CHECK_LE(num_items_, kMaxNumberOfBruteForceItems)
593  << "To use KnapsackBruteForceSolver the number of items should be "
594  << "less than " << kMaxNumberOfBruteForceItems
595  << ". Current value: " << num_items_ << ".";
596 
597  for (int i = 0; i < num_items_; ++i) {
598  profits_weights_[i * 2] = profits.at(i);
599  profits_weights_[i * 2 + 1] = weights.at(0).at(i);
600  }
601  capacity_ = capacities.at(0);
602 }
603 
605  bool* is_solution_optimal) {
606  DCHECK(is_solution_optimal != nullptr);
607  *is_solution_optimal = true;
608  best_solution_profit_ = 0LL;
609  best_solution_ = 0U;
610 
611  const uint32_t num_states = OneBit32(num_items_);
612  uint32_t prev_state = 0U;
613  uint64_t sum_profit = 0ULL;
614  uint64_t sum_weight = 0ULL;
615  uint32_t diff_state = 0U;
616  uint32_t local_state = 0U;
617  int item_id = 0;
618  // This loop starts at 1, because state = 0 was already considered previously,
619  // ie. when no items are in, sum_profit = 0.
620  for (uint32_t state = 1U; state < num_states; ++state, ++prev_state) {
621  diff_state = state ^ prev_state;
622  local_state = state;
623  item_id = 0;
624  while (diff_state) {
625  if (diff_state & 1U) { // There is a diff.
626  if (local_state & 1U) { // This item is now in the knapsack.
627  sum_profit += profits_weights_[item_id];
628  sum_weight += profits_weights_[item_id + 1];
629  CHECK_LT(item_id + 1, 2 * num_items_);
630  } else { // This item has been removed of the knapsack.
631  sum_profit -= profits_weights_[item_id];
632  sum_weight -= profits_weights_[item_id + 1];
633  CHECK_LT(item_id + 1, 2 * num_items_);
634  }
635  }
636  item_id += 2;
637  local_state = local_state >> 1;
638  diff_state = diff_state >> 1;
639  }
640 
641  if (sum_weight <= capacity_ && best_solution_profit_ < sum_profit) {
642  best_solution_profit_ = sum_profit;
643  best_solution_ = state;
644  }
645  }
646 
647  return best_solution_profit_;
648 }
649 
650 // ----- KnapsackItemWithEfficiency -----
651 // KnapsackItem is a small struct to pair an item weight with its
652 // corresponding profit.
653 // This struct is used by Knapsack64ItemsSolver. As this solver deals only
654 // with one dimension, that's more efficient to store 'efficiency' than
655 // computing it on the fly.
657  KnapsackItemWithEfficiency(int _id, int64_t _profit, int64_t _weight,
658  int64_t _profit_max)
659  : id(_id),
660  profit(_profit),
661  weight(_weight),
662  efficiency((weight > 0) ? static_cast<double>(_profit) /
663  static_cast<double>(_weight)
664  : static_cast<double>(_profit_max)) {}
665 
666  int id;
667  int64_t profit;
668  int64_t weight;
669  double efficiency;
670 };
671 
672 // ----- Knapsack64ItemsSolver -----
673 // Knapsack64ItemsSolver solves the 0-1 knapsack problem when the number of
674 // items is less or equal to 64. This implementation is about 4 times faster
675 // than KnapsackGenericSolver.
677  public:
678  explicit Knapsack64ItemsSolver(const std::string& solver_name);
679 
680  // Initializes the solver and enters the problem to be solved.
681  void Init(const std::vector<int64_t>& profits,
682  const std::vector<std::vector<int64_t>>& weights,
683  const std::vector<int64_t>& capacities) override;
684 
685  // Solves the problem and returns the profit of the optimal solution.
686  int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
687 
688  // Returns true if the item 'item_id' is packed in the optimal knapsack.
689  bool best_solution(int item_id) const override {
690  return (best_solution_ & OneBit64(item_id)) != 0ULL;
691  }
692 
693  private:
694  int GetBreakItemId(int64_t capacity) const;
695  void GetLowerAndUpperBound(int64_t* lower_bound, int64_t* upper_bound) const;
696  void GoToNextState(bool has_failed);
697  void BuildBestSolution();
698 
699  std::vector<KnapsackItemWithEfficiency> sorted_items_;
700  std::vector<int64_t> sum_profits_;
701  std::vector<int64_t> sum_weights_;
702  int64_t capacity_;
703  uint64_t state_;
704  int state_depth_;
705 
706  int64_t best_solution_profit_;
707  uint64_t best_solution_;
708  int best_solution_depth_;
709 
710  // Sum of weights of included item in state.
711  int64_t state_weight_;
712  // Sum of profits of non included items in state.
713  int64_t rejected_items_profit_;
714  // Sum of weights of non included items in state.
715  int64_t rejected_items_weight_;
716 };
717 
718 // Comparator used to sort item in decreasing efficiency order
720  const KnapsackItemWithEfficiency& item1,
721  const KnapsackItemWithEfficiency& item2) {
722  return item1.efficiency > item2.efficiency;
723 }
724 
725 // ----- Knapsack64ItemsSolver -----
726 Knapsack64ItemsSolver::Knapsack64ItemsSolver(const std::string& solver_name)
727  : BaseKnapsackSolver(solver_name),
728  sorted_items_(),
729  sum_profits_(),
730  sum_weights_(),
731  capacity_(0LL),
732  state_(0ULL),
733  state_depth_(0),
734  best_solution_profit_(0LL),
735  best_solution_(0ULL),
736  best_solution_depth_(0),
737  state_weight_(0LL),
738  rejected_items_profit_(0LL),
739  rejected_items_weight_(0LL) {}
740 
742  const std::vector<int64_t>& profits,
743  const std::vector<std::vector<int64_t>>& weights,
744  const std::vector<int64_t>& capacities) {
745  CHECK_EQ(weights.size(), 1)
746  << "Brute force solver only works with one dimension.";
747  CHECK_EQ(capacities.size(), weights.size());
748 
749  sorted_items_.clear();
750  sum_profits_.clear();
751  sum_weights_.clear();
752 
753  capacity_ = capacities[0];
754  const int num_items = profits.size();
755  CHECK_LE(num_items, kMaxNumberOf64Items)
756  << "To use Knapsack64ItemsSolver the number of items should be "
757  << "less than " << kMaxNumberOf64Items << ". Current value: " << num_items
758  << ".";
759  int64_t profit_max = *std::max_element(profits.begin(), profits.end());
760 
761  for (int i = 0; i < num_items; ++i) {
762  sorted_items_.push_back(
763  KnapsackItemWithEfficiency(i, profits[i], weights[0][i], profit_max));
764  }
765 
766  std::sort(sorted_items_.begin(), sorted_items_.end(),
768 
769  int64_t sum_profit = 0;
770  int64_t sum_weight = 0;
771  sum_profits_.push_back(sum_profit);
772  sum_weights_.push_back(sum_weight);
773  for (int i = 0; i < num_items; ++i) {
774  sum_profit += sorted_items_[i].profit;
775  sum_weight += sorted_items_[i].weight;
776 
777  sum_profits_.push_back(sum_profit);
778  sum_weights_.push_back(sum_weight);
779  }
780 }
781 
783  bool* is_solution_optimal) {
784  DCHECK(is_solution_optimal != nullptr);
785  *is_solution_optimal = true;
786  const int num_items = sorted_items_.size();
787  state_ = 1ULL;
788  state_depth_ = 0;
789  state_weight_ = sorted_items_[0].weight;
790  rejected_items_profit_ = 0LL;
791  rejected_items_weight_ = 0LL;
792  best_solution_profit_ = 0LL;
793  best_solution_ = 0ULL;
794  best_solution_depth_ = 0;
795 
796  int64_t lower_bound = 0LL;
797  int64_t upper_bound = 0LL;
798  bool fail = false;
799  while (state_depth_ >= 0) {
800  fail = false;
801  if (state_weight_ > capacity_ || state_depth_ >= num_items) {
802  fail = true;
803  } else {
804  GetLowerAndUpperBound(&lower_bound, &upper_bound);
805  if (best_solution_profit_ < lower_bound) {
806  best_solution_profit_ = lower_bound;
807  best_solution_ = state_;
808  best_solution_depth_ = state_depth_;
809  }
810  }
811  fail = fail || best_solution_profit_ >= upper_bound;
812  GoToNextState(fail);
813  }
814 
815  BuildBestSolution();
816  return best_solution_profit_;
817 }
818 
819 int Knapsack64ItemsSolver::GetBreakItemId(int64_t capacity) const {
820  std::vector<int64_t>::const_iterator binary_search_iterator =
821  std::upper_bound(sum_weights_.begin(), sum_weights_.end(), capacity);
822  return static_cast<int>(binary_search_iterator - sum_weights_.begin()) - 1;
823 }
824 
825 // This method is called for each possible state.
826 // Lower and upper bounds can be equal from one state to another.
827 // For instance state 1010???? and state 101011?? have exactly the same
828 // bounds. So it sounds like a good idea to cache those bounds.
829 // Unfortunately, experiments show equivalent results with or without this
830 // code optimization (only 1/7 of calls can be reused).
831 // In order to simplify the code, this optimization is not implemented.
832 void Knapsack64ItemsSolver::GetLowerAndUpperBound(int64_t* lower_bound,
833  int64_t* upper_bound) const {
834  const int64_t available_capacity = capacity_ + rejected_items_weight_;
835  const int break_item_id = GetBreakItemId(available_capacity);
836  const int num_items = sorted_items_.size();
837  if (break_item_id >= num_items) {
838  *lower_bound = sum_profits_[num_items] - rejected_items_profit_;
840  return;
841  }
842 
843  *lower_bound = sum_profits_[break_item_id] - rejected_items_profit_;
845  const int64_t consumed_capacity = sum_weights_[break_item_id];
846  const int64_t remaining_capacity = available_capacity - consumed_capacity;
847  const double efficiency = sorted_items_[break_item_id].efficiency;
848  const int64_t additional_profit =
849  static_cast<int64_t>(remaining_capacity * efficiency);
850  *upper_bound += additional_profit;
851 }
852 
853 // As state_depth_ is the position of the most significant bit on state_
854 // it is possible to remove the loop and so be in O(1) instead of O(depth).
855 // In such a case rejected_items_profit_ is computed using sum_profits_ array.
856 // Unfortunately experiments show smaller computation time using the 'while'
857 // (10% speed-up). That's the reason why the loop version is implemented.
858 void Knapsack64ItemsSolver::GoToNextState(bool has_failed) {
859  uint64_t mask = OneBit64(state_depth_);
860  if (!has_failed) { // Go to next item.
861  ++state_depth_;
862  state_ = state_ | (mask << 1);
863  state_weight_ += sorted_items_[state_depth_].weight;
864  } else {
865  // Backtrack to last item in.
866  while ((state_ & mask) == 0ULL && state_depth_ >= 0) {
867  const KnapsackItemWithEfficiency& item = sorted_items_[state_depth_];
868  rejected_items_profit_ -= item.profit;
869  rejected_items_weight_ -= item.weight;
870  --state_depth_;
871  mask = mask >> 1ULL;
872  }
873 
874  if (state_ & mask) { // Item was in, remove it.
875  state_ = state_ & ~mask;
876  const KnapsackItemWithEfficiency& item = sorted_items_[state_depth_];
877  rejected_items_profit_ += item.profit;
878  rejected_items_weight_ += item.weight;
879  state_weight_ -= item.weight;
880  }
881  }
882 }
883 
884 void Knapsack64ItemsSolver::BuildBestSolution() {
885  int64_t remaining_capacity = capacity_;
886  int64_t check_profit = 0LL;
887 
888  // Compute remaining capacity at best_solution_depth_ to be able to redo
889  // the GetLowerAndUpperBound computation.
890  for (int i = 0; i <= best_solution_depth_; ++i) {
891  if (best_solution_ & OneBit64(i)) {
892  remaining_capacity -= sorted_items_[i].weight;
893  check_profit += sorted_items_[i].profit;
894  }
895  }
896 
897  // Add all items till the break item.
898  const int num_items = sorted_items_.size();
899  for (int i = best_solution_depth_ + 1; i < num_items; ++i) {
900  int64_t weight = sorted_items_[i].weight;
901  if (remaining_capacity >= weight) {
902  remaining_capacity -= weight;
903  check_profit += sorted_items_[i].profit;
904  best_solution_ = best_solution_ | OneBit64(i);
905  } else {
906  best_solution_ = best_solution_ & ~OneBit64(i);
907  }
908  }
909  CHECK_EQ(best_solution_profit_, check_profit);
910 
911  // Items were sorted by efficiency, solution should be unsorted to be
912  // in user order.
913  // Note that best_solution_ will not be in the same order than other data
914  // structures anymore.
915  uint64_t tmp_solution = 0ULL;
916  for (int i = 0; i < num_items; ++i) {
917  if (best_solution_ & OneBit64(i)) {
918  const int original_id = sorted_items_[i].id;
919  tmp_solution = tmp_solution | OneBit64(original_id);
920  }
921  }
922 
923  best_solution_ = tmp_solution;
924 }
925 
926 // ----- KnapsackDynamicProgrammingSolver -----
927 // KnapsackDynamicProgrammingSolver solves the 0-1 knapsack problem
928 // using dynamic programming. The time complexity is O(capacity *
929 // number_of_items^2) and the space complexity is O(capacity + number_of_items).
930 // The implemented algorithm is 'DP-3' in "Knapsack problems", Hans Kellerer,
931 // Ulrich Pferschy and David Pisinger, Springer book (ISBN 978-3540402862).
933  public:
934  explicit KnapsackDynamicProgrammingSolver(const std::string& solver_name);
935 
936  // Initializes the solver and enters the problem to be solved.
937  void Init(const std::vector<int64_t>& profits,
938  const std::vector<std::vector<int64_t>>& weights,
939  const std::vector<int64_t>& capacities) override;
940 
941  // Solves the problem and returns the profit of the optimal solution.
942  int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
943 
944  // Returns true if the item 'item_id' is packed in the optimal knapsack.
945  bool best_solution(int item_id) const override {
946  return best_solution_.at(item_id);
947  }
948 
949  private:
950  int64_t SolveSubProblem(int64_t capacity, int num_items);
951 
952  std::vector<int64_t> profits_;
953  std::vector<int64_t> weights_;
954  int64_t capacity_;
955  std::vector<int64_t> computed_profits_;
956  std::vector<int> selected_item_ids_;
957  std::vector<bool> best_solution_;
958 };
959 
960 // ----- KnapsackDynamicProgrammingSolver -----
962  const std::string& solver_name)
963  : BaseKnapsackSolver(solver_name),
964  profits_(),
965  weights_(),
966  capacity_(0),
967  computed_profits_(),
968  selected_item_ids_(),
969  best_solution_() {}
970 
972  const std::vector<int64_t>& profits,
973  const std::vector<std::vector<int64_t>>& weights,
974  const std::vector<int64_t>& capacities) {
975  CHECK_EQ(weights.size(), 1)
976  << "Current implementation of the dynamic programming solver only deals"
977  << " with one dimension.";
978  CHECK_EQ(capacities.size(), weights.size());
979 
980  profits_ = profits;
981  weights_ = weights[0];
982  capacity_ = capacities[0];
983 }
984 
985 int64_t KnapsackDynamicProgrammingSolver::SolveSubProblem(int64_t capacity,
986  int num_items) {
987  const int64_t capacity_plus_1 = capacity + 1;
988  std::fill_n(selected_item_ids_.begin(), capacity_plus_1, 0);
989  std::fill_n(computed_profits_.begin(), capacity_plus_1, int64_t{0});
990  for (int item_id = 0; item_id < num_items; ++item_id) {
991  const int64_t item_weight = weights_[item_id];
992  const int64_t item_profit = profits_[item_id];
993  for (int64_t used_capacity = capacity; used_capacity >= item_weight;
994  --used_capacity) {
995  if (computed_profits_[used_capacity - item_weight] + item_profit >
996  computed_profits_[used_capacity]) {
997  computed_profits_[used_capacity] =
998  computed_profits_[used_capacity - item_weight] + item_profit;
999  selected_item_ids_[used_capacity] = item_id;
1000  }
1001  }
1002  }
1003  return selected_item_ids_.at(capacity);
1004 }
1005 
1007  bool* is_solution_optimal) {
1008  DCHECK(is_solution_optimal != nullptr);
1009  *is_solution_optimal = true;
1010  const int64_t capacity_plus_1 = capacity_ + 1;
1011  selected_item_ids_.assign(capacity_plus_1, 0);
1012  computed_profits_.assign(capacity_plus_1, 0LL);
1013 
1014  int64_t remaining_capacity = capacity_;
1015  int num_items = profits_.size();
1016  best_solution_.assign(num_items, false);
1017 
1018  while (remaining_capacity > 0 && num_items > 0) {
1019  const int selected_item_id = SolveSubProblem(remaining_capacity, num_items);
1020  remaining_capacity -= weights_[selected_item_id];
1021  num_items = selected_item_id;
1022  if (remaining_capacity >= 0) {
1023  best_solution_[selected_item_id] = true;
1024  }
1025  }
1026 
1027  return computed_profits_[capacity_];
1028 }
1029 // ----- KnapsackDivideAndConquerSolver -----
1030 // KnapsackDivideAndConquerSolver solves the 0-1 knapsack problem (KP)
1031 // using divide and conquer and dynamic programming.
1032 // By using one-dimensional vectors it keeps a complexity of O(capacity *
1033 // number_of_items) in time, but reduces the space complexity to O(capacity +
1034 // number_of_items) and is therefore suitable for large hard to solve
1035 // (KP)/(SSP). The implemented algorithm is based on 'DP-2' and Divide and
1036 // Conquer for storage reduction from [Hans Kellerer et al., "Knapsack problems"
1037 // (DOI 10.1007/978-3-540-24777-7)].
1039  public:
1040  explicit KnapsackDivideAndConquerSolver(const std::string& solver_name);
1041 
1042  // Initializes the solver and enters the problem to be solved.
1043  void Init(const std::vector<int64_t>& profits,
1044  const std::vector<std::vector<int64_t>>& weights,
1045  const std::vector<int64_t>& capacities) override;
1046 
1047  // Solves the problem and returns the profit of the optimal solution.
1048  int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
1049 
1050  // Returns true if the item 'item_id' is packed in the optimal knapsack.
1051  bool best_solution(int item_id) const override {
1052  return best_solution_.at(item_id);
1053  }
1054 
1055  private:
1056  // 'DP 2' computes solution 'z' for 0 up to capacitiy
1057  void SolveSubProblem(bool first_storage, int64_t capacity, int start_item,
1058  int end_item);
1059 
1060  // Calculates best_solution_ and return 'z' from first instance
1061  int64_t DivideAndConquer(int64_t capacity, int start_item, int end_item);
1062 
1063  std::vector<int64_t> profits_;
1064  std::vector<int64_t> weights_;
1065  int64_t capacity_;
1066  std::vector<int64_t> computed_profits_storage1_;
1067  std::vector<int64_t> computed_profits_storage2_;
1068  std::vector<bool> best_solution_;
1069 };
1070 
1071 // ----- KnapsackDivideAndConquerSolver -----
1073  const std::string& solver_name)
1074  : BaseKnapsackSolver(solver_name),
1075  profits_(),
1076  weights_(),
1077  capacity_(0),
1078  computed_profits_storage1_(),
1079  computed_profits_storage2_(),
1080  best_solution_() {}
1081 
1083  const std::vector<int64_t>& profits,
1084  const std::vector<std::vector<int64_t>>& weights,
1085  const std::vector<int64_t>& capacities) {
1086  CHECK_EQ(weights.size(), 1)
1087  << "Current implementation of the divide and conquer solver only deals"
1088  << " with one dimension.";
1089  CHECK_EQ(capacities.size(), weights.size());
1090 
1091  profits_ = profits;
1092  weights_ = weights[0];
1093  capacity_ = capacities[0];
1094 }
1095 
1096 void KnapsackDivideAndConquerSolver::SolveSubProblem(bool first_storage,
1097  int64_t capacity,
1098  int start_item,
1099  int end_item) {
1100  std::vector<int64_t>& computed_profits_storage_ =
1101  (first_storage) ? computed_profits_storage1_ : computed_profits_storage2_;
1102  const int64_t capacity_plus_1 = capacity + 1;
1103  std::fill_n(computed_profits_storage_.begin(), capacity_plus_1, 0LL);
1104  for (int item_id = start_item; item_id < end_item; ++item_id) {
1105  const int64_t item_weight = weights_[item_id];
1106  const int64_t item_profit = profits_[item_id];
1107  for (int64_t used_capacity = capacity; used_capacity >= item_weight;
1108  --used_capacity) {
1109  if (computed_profits_storage_[used_capacity - item_weight] + item_profit >
1110  computed_profits_storage_[used_capacity]) {
1111  computed_profits_storage_[used_capacity] =
1112  computed_profits_storage_[used_capacity - item_weight] +
1113  item_profit;
1114  }
1115  }
1116  }
1117 }
1118 
1119 int64_t KnapsackDivideAndConquerSolver::DivideAndConquer(int64_t capacity,
1120  int start_item,
1121  int end_item) {
1122  const int64_t capacity_plus_1 = capacity_ + 1;
1123  int item_boundary_ = start_item + ((end_item - start_item) / 2);
1124 
1125  SolveSubProblem(true, capacity, start_item, item_boundary_);
1126  SolveSubProblem(false, capacity, item_boundary_, end_item);
1127 
1128  int64_t max_solution_ = 0, capacity1_ = 0, capacity2_ = 0;
1129 
1130  for (int64_t capacity_id = 0; capacity_id <= capacity; capacity_id++) {
1131  if ((computed_profits_storage1_[capacity_id] +
1132  computed_profits_storage2_[(capacity - capacity_id)]) >
1133  max_solution_) {
1134  capacity1_ = capacity_id;
1135  capacity2_ = capacity - capacity_id;
1136  max_solution_ = (computed_profits_storage1_[capacity_id] +
1137  computed_profits_storage2_[(capacity - capacity_id)]);
1138  }
1139  }
1140 
1141  if ((item_boundary_ - start_item) == 1) {
1142  if (weights_[start_item] <= capacity1_) best_solution_[start_item] = true;
1143  } else if ((item_boundary_ - start_item) > 1)
1144  DivideAndConquer(capacity1_, start_item, item_boundary_);
1145 
1146  if ((end_item - item_boundary_) == 1) {
1147  if (weights_[item_boundary_] <= capacity2_)
1148  best_solution_[item_boundary_] = true;
1149  } else if ((end_item - item_boundary_) > 1)
1150  DivideAndConquer(capacity2_, item_boundary_, end_item);
1151 
1152  return max_solution_;
1153 }
1154 
1156  bool* is_solution_optimal) {
1157  DCHECK(is_solution_optimal != nullptr);
1158  *is_solution_optimal = true;
1159  const int64_t capacity_plus_1 = capacity_ + 1;
1160  computed_profits_storage1_.assign(capacity_plus_1, 0LL);
1161  computed_profits_storage2_.assign(capacity_plus_1, 0LL);
1162  best_solution_.assign(profits_.size(), false);
1163 
1164  return DivideAndConquer(capacity_, 0, profits_.size());
1165 }
1166 // ----- KnapsackMIPSolver -----
1168  public:
1170  const std::string& solver_name);
1171 
1172  // Initializes the solver and enters the problem to be solved.
1173  void Init(const std::vector<int64_t>& profits,
1174  const std::vector<std::vector<int64_t>>& weights,
1175  const std::vector<int64_t>& capacities) override;
1176 
1177  // Solves the problem and returns the profit of the optimal solution.
1178  int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
1179 
1180  // Returns true if the item 'item_id' is packed in the optimal knapsack.
1181  bool best_solution(int item_id) const override {
1182  return best_solution_.at(item_id);
1183  }
1184 
1185  private:
1186  MPSolver::OptimizationProblemType problem_type_;
1187  std::vector<int64_t> profits_;
1188  std::vector<std::vector<int64_t>> weights_;
1189  std::vector<int64_t> capacities_;
1190  std::vector<bool> best_solution_;
1191 };
1192 
1195  const std::string& solver_name)
1196  : BaseKnapsackSolver(solver_name),
1197  problem_type_(problem_type),
1198  profits_(),
1199  weights_(),
1200  capacities_(),
1201  best_solution_() {}
1202 
1203 void KnapsackMIPSolver::Init(const std::vector<int64_t>& profits,
1204  const std::vector<std::vector<int64_t>>& weights,
1205  const std::vector<int64_t>& capacities) {
1206  profits_ = profits;
1207  weights_ = weights;
1208  capacities_ = capacities;
1209 }
1210 
1212  bool* is_solution_optimal) {
1213  DCHECK(is_solution_optimal != nullptr);
1214  *is_solution_optimal = true;
1215  MPSolver solver(GetName(), problem_type_);
1216 
1217  const int num_items = profits_.size();
1218  std::vector<MPVariable*> variables;
1219  solver.MakeBoolVarArray(num_items, "x", &variables);
1220 
1221  // Add constraints.
1222  const int num_dimensions = capacities_.size();
1223  CHECK(weights_.size() == num_dimensions)
1224  << "Weights should be vector of num_dimensions (" << num_dimensions
1225  << ") vectors of size num_items (" << num_items << ").";
1226  for (int i = 0; i < num_dimensions; ++i) {
1227  MPConstraint* const ct = solver.MakeRowConstraint(0LL, capacities_.at(i));
1228  for (int j = 0; j < num_items; ++j) {
1229  ct->SetCoefficient(variables.at(j), weights_.at(i).at(j));
1230  }
1231  }
1232 
1233  // Define objective to minimize. Minimization is used instead of maximization
1234  // because of an issue with CBC solver which does not always find the optimal
1235  // solution on maximization problems.
1236  MPObjective* const objective = solver.MutableObjective();
1237  for (int j = 0; j < num_items; ++j) {
1238  objective->SetCoefficient(variables.at(j), -profits_.at(j));
1239  }
1240  objective->SetMinimization();
1241 
1242  solver.SuppressOutput();
1243  solver.Solve();
1244 
1245  // Store best solution.
1246  const float kRoundNear = 0.5;
1247  best_solution_.assign(num_items, false);
1248  for (int j = 0; j < num_items; ++j) {
1249  const double value = variables.at(j)->solution_value();
1250  best_solution_.at(j) = value >= kRoundNear;
1251  }
1252 
1253  return -objective->Value() + kRoundNear;
1254 }
1255 
1256 // ----- KnapsackSolver -----
1257 KnapsackSolver::KnapsackSolver(const std::string& solver_name)
1258  : KnapsackSolver(KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,
1259  solver_name) {}
1260 
1262  const std::string& solver_name)
1263  : solver_(),
1264  known_value_(),
1265  best_solution_(),
1266  mapping_reduced_item_id_(),
1267  is_problem_solved_(false),
1268  additional_profit_(0LL),
1269  use_reduction_(true),
1270  time_limit_seconds_(std::numeric_limits<double>::infinity()) {
1271  switch (solver_type) {
1273  solver_ = absl::make_unique<KnapsackBruteForceSolver>(solver_name);
1274  break;
1276  solver_ = absl::make_unique<Knapsack64ItemsSolver>(solver_name);
1277  break;
1279  solver_ =
1280  absl::make_unique<KnapsackDynamicProgrammingSolver>(solver_name);
1281  break;
1283  solver_ = absl::make_unique<KnapsackGenericSolver>(solver_name);
1284  break;
1286  solver_ = absl::make_unique<KnapsackDivideAndConquerSolver>(solver_name);
1287  break;
1288 #if defined(USE_CBC)
1290  solver_ = absl::make_unique<KnapsackMIPSolver>(
1292  break;
1293 #endif // USE_CBC
1294 #if defined(USE_SCIP)
1296  solver_ = absl::make_unique<KnapsackMIPSolver>(
1298  break;
1299 #endif // USE_SCIP
1300 #if defined(USE_XPRESS)
1301  case KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER:
1302  solver_ = absl::make_unique<KnapsackMIPSolver>(
1304  break;
1305 #endif
1306 #if defined(USE_CPLEX)
1307  case KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER:
1308  solver_ = absl::make_unique<KnapsackMIPSolver>(
1310  break;
1311 #endif
1312  default:
1313  LOG(FATAL) << "Unknown knapsack solver type.";
1314  }
1315 }
1316 
1318 
1319 void KnapsackSolver::Init(const std::vector<int64_t>& profits,
1320  const std::vector<std::vector<int64_t>>& weights,
1321  const std::vector<int64_t>& capacities) {
1322  for (const std::vector<int64_t>& w : weights) {
1323  CHECK_EQ(profits.size(), w.size())
1324  << "Profits and inner weights must have the same size (#items)";
1325  }
1326  CHECK_EQ(capacities.size(), weights.size())
1327  << "Capacities and weights must have the same size (#bins)";
1328  time_limit_ = absl::make_unique<TimeLimit>(time_limit_seconds_);
1329  is_solution_optimal_ = false;
1330  additional_profit_ = 0LL;
1331  is_problem_solved_ = false;
1332 
1333  const int num_items = profits.size();
1334  std::vector<std::vector<int64_t>> reduced_weights;
1335  std::vector<int64_t> reduced_capacities;
1336  if (use_reduction_) {
1337  const int num_reduced_items = ReduceCapacities(
1338  num_items, weights, capacities, &reduced_weights, &reduced_capacities);
1339  if (num_reduced_items > 0) {
1340  ComputeAdditionalProfit(profits);
1341  }
1342  } else {
1343  reduced_weights = weights;
1344  reduced_capacities = capacities;
1345  }
1346  if (!is_problem_solved_) {
1347  solver_->Init(profits, reduced_weights, reduced_capacities);
1348  if (use_reduction_) {
1349  const int num_reduced_items = ReduceProblem(num_items);
1350 
1351  if (num_reduced_items > 0) {
1352  ComputeAdditionalProfit(profits);
1353  }
1354 
1355  if (num_reduced_items > 0 && num_reduced_items < num_items) {
1356  InitReducedProblem(profits, reduced_weights, reduced_capacities);
1357  }
1358  }
1359  }
1360  if (is_problem_solved_) {
1361  is_solution_optimal_ = true;
1362  }
1363 }
1364 
1365 int KnapsackSolver::ReduceCapacities(
1366  int num_items, const std::vector<std::vector<int64_t>>& weights,
1367  const std::vector<int64_t>& capacities,
1368  std::vector<std::vector<int64_t>>* reduced_weights,
1369  std::vector<int64_t>* reduced_capacities) {
1370  known_value_.assign(num_items, false);
1371  best_solution_.assign(num_items, false);
1372  mapping_reduced_item_id_.assign(num_items, 0);
1373  std::vector<bool> active_capacities(weights.size(), true);
1374  int number_of_active_capacities = 0;
1375  for (int i = 0; i < weights.size(); ++i) {
1376  int64_t max_weight = 0;
1377  for (int64_t weight : weights[i]) {
1378  max_weight += weight;
1379  }
1380  if (max_weight <= capacities[i]) {
1381  active_capacities[i] = false;
1382  } else {
1383  ++number_of_active_capacities;
1384  }
1385  }
1386  reduced_weights->reserve(number_of_active_capacities);
1387  reduced_capacities->reserve(number_of_active_capacities);
1388  for (int i = 0; i < weights.size(); ++i) {
1389  if (active_capacities[i]) {
1390  reduced_weights->push_back(weights[i]);
1391  reduced_capacities->push_back(capacities[i]);
1392  }
1393  }
1394  if (reduced_capacities->empty()) {
1395  // There are no capacity constraints in the problem so we can reduce all
1396  // items and just add them to the best solution.
1397  for (int item_id = 0; item_id < num_items; ++item_id) {
1398  known_value_[item_id] = true;
1399  best_solution_[item_id] = true;
1400  }
1401  is_problem_solved_ = true;
1402  // All items are reduced.
1403  return num_items;
1404  }
1405  // There are still capacity constraints so no item reduction is done.
1406  return 0;
1407 }
1408 
1409 int KnapsackSolver::ReduceProblem(int num_items) {
1410  known_value_.assign(num_items, false);
1411  best_solution_.assign(num_items, false);
1412  mapping_reduced_item_id_.assign(num_items, 0);
1413  additional_profit_ = 0LL;
1414 
1415  for (int item_id = 0; item_id < num_items; ++item_id) {
1416  mapping_reduced_item_id_[item_id] = item_id;
1417  }
1418 
1419  int64_t best_lower_bound = 0LL;
1420  std::vector<int64_t> J0_upper_bounds(num_items,
1422  std::vector<int64_t> J1_upper_bounds(num_items,
1424  for (int item_id = 0; item_id < num_items; ++item_id) {
1425  if (time_limit_->LimitReached()) {
1426  break;
1427  }
1428  int64_t lower_bound = 0LL;
1430  solver_->GetLowerAndUpperBoundWhenItem(item_id, false, &lower_bound,
1431  &upper_bound);
1432  J1_upper_bounds.at(item_id) = upper_bound;
1433  best_lower_bound = std::max(best_lower_bound, lower_bound);
1434 
1435  solver_->GetLowerAndUpperBoundWhenItem(item_id, true, &lower_bound,
1436  &upper_bound);
1437  J0_upper_bounds.at(item_id) = upper_bound;
1438  best_lower_bound = std::max(best_lower_bound, lower_bound);
1439  }
1440 
1441  int num_reduced_items = 0;
1442  for (int item_id = 0; item_id < num_items; ++item_id) {
1443  if (best_lower_bound > J0_upper_bounds[item_id]) {
1444  known_value_[item_id] = true;
1445  best_solution_[item_id] = false;
1446  ++num_reduced_items;
1447  } else if (best_lower_bound > J1_upper_bounds[item_id]) {
1448  known_value_[item_id] = true;
1449  best_solution_[item_id] = true;
1450  ++num_reduced_items;
1451  }
1452  }
1453 
1454  is_problem_solved_ = num_reduced_items == num_items;
1455  return num_reduced_items;
1456 }
1457 
1458 void KnapsackSolver::ComputeAdditionalProfit(
1459  const std::vector<int64_t>& profits) {
1460  const int num_items = profits.size();
1461  additional_profit_ = 0LL;
1462  for (int item_id = 0; item_id < num_items; ++item_id) {
1463  if (known_value_[item_id] && best_solution_[item_id]) {
1464  additional_profit_ += profits[item_id];
1465  }
1466  }
1467 }
1468 
1469 void KnapsackSolver::InitReducedProblem(
1470  const std::vector<int64_t>& profits,
1471  const std::vector<std::vector<int64_t>>& weights,
1472  const std::vector<int64_t>& capacities) {
1473  const int num_items = profits.size();
1474  const int num_dimensions = capacities.size();
1475 
1476  std::vector<int64_t> reduced_profits;
1477  for (int item_id = 0; item_id < num_items; ++item_id) {
1478  if (!known_value_[item_id]) {
1479  mapping_reduced_item_id_[item_id] = reduced_profits.size();
1480  reduced_profits.push_back(profits[item_id]);
1481  }
1482  }
1483 
1484  std::vector<std::vector<int64_t>> reduced_weights;
1485  std::vector<int64_t> reduced_capacities = capacities;
1486  for (int dim = 0; dim < num_dimensions; ++dim) {
1487  const std::vector<int64_t>& one_dimension_weights = weights[dim];
1488  std::vector<int64_t> one_dimension_reduced_weights;
1489  for (int item_id = 0; item_id < num_items; ++item_id) {
1490  if (known_value_[item_id]) {
1491  if (best_solution_[item_id]) {
1492  reduced_capacities[dim] -= one_dimension_weights[item_id];
1493  }
1494  } else {
1495  one_dimension_reduced_weights.push_back(one_dimension_weights[item_id]);
1496  }
1497  }
1498  reduced_weights.push_back(one_dimension_reduced_weights);
1499  }
1500  solver_->Init(reduced_profits, reduced_weights, reduced_capacities);
1501 }
1502 
1504  return additional_profit_ +
1505  ((is_problem_solved_)
1506  ? 0
1507  : solver_->Solve(time_limit_.get(), &is_solution_optimal_));
1508 }
1509 
1510 bool KnapsackSolver::BestSolutionContains(int item_id) const {
1511  const int mapped_item_id =
1512  (use_reduction_) ? mapping_reduced_item_id_[item_id] : item_id;
1513  return (use_reduction_ && known_value_[item_id])
1514  ? best_solution_[item_id]
1515  : solver_->best_solution(mapped_item_id);
1516 }
1517 
1518 std::string KnapsackSolver::GetName() const { return solver_->GetName(); }
1519 
1520 // ----- BaseKnapsackSolver -----
1522  bool is_item_in,
1523  int64_t* lower_bound,
1524  int64_t* upper_bound) {
1525  CHECK(lower_bound != nullptr);
1526  CHECK(upper_bound != nullptr);
1527  *lower_bound = 0LL;
1529 }
1530 
1531 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_LT(val1, val2)
Definition: base/logging.h:708
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GE(val1, val2)
Definition: base/logging.h:709
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:898
#define LOG(severity)
Definition: base/logging.h:423
#define DCHECK(condition)
Definition: base/logging.h:892
#define CHECK_LE(val1, val2)
Definition: base/logging.h:707
virtual void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in, int64_t *lower_bound, int64_t *upper_bound)
virtual std::string GetName() const
Knapsack64ItemsSolver(const std::string &solver_name)
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t >> &weights, const std::vector< int64_t > &capacities) override
bool best_solution(int item_id) const override
KnapsackBruteForceSolver(const std::string &solver_name)
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t >> &weights, const std::vector< int64_t > &capacities) override
bool best_solution(int item_id) const override
KnapsackCapacityPropagator(const KnapsackState &state, int64_t capacity)
bool UpdatePropagator(bool revert, const KnapsackAssignment &assignment) override
void CopyCurrentStateToSolutionPropagator(std::vector< bool > *solution) const override
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
KnapsackDivideAndConquerSolver(const std::string &solver_name)
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t >> &weights, const std::vector< int64_t > &capacities) override
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t >> &weights, const std::vector< int64_t > &capacities) override
KnapsackDynamicProgrammingSolver(const std::string &solver_name)
KnapsackGenericSolver(const std::string &solver_name)
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t > > &weights, const std::vector< int64_t > &capacities) override
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in, int64_t *lower_bound, int64_t *upper_bound) override
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t >> &weights, const std::vector< int64_t > &capacities) override
KnapsackMIPSolver(MPSolver::OptimizationProblemType problem_type, const std::string &solver_name)
bool best_solution(int item_id) const override
void Init(const std::vector< int64_t > &profits, const std::vector< int64_t > &weights)
void CopyCurrentStateToSolution(bool has_one_propagator, std::vector< bool > *solution) const
virtual bool UpdatePropagator(bool revert, const KnapsackAssignment &assignment)=0
const KnapsackState & state() const
const std::vector< KnapsackItemPtr > & items() const
virtual void CopyCurrentStateToSolutionPropagator(std::vector< bool > *solution) const =0
KnapsackPropagator(const KnapsackState &state)
bool Update(bool revert, const KnapsackAssignment &assignment)
KnapsackSearchNode(const KnapsackSearchNode *const parent, const KnapsackAssignment &assignment)
const KnapsackSearchNode *const parent() const
const KnapsackSearchNode * MoveUpToDepth(const KnapsackSearchNode &node, int depth) const
KnapsackSearchPath(const KnapsackSearchNode &from, const KnapsackSearchNode &to)
This library solves knapsack problems.
bool BestSolutionContains(int item_id) const
Returns true if the item 'item_id' is packed in the optimal knapsack.
KnapsackSolver(const std::string &solver_name)
int64_t Solve()
Solves the problem and returns the profit of the optimal solution.
SolverType
Enum controlling which underlying algorithm is used.
@ KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER
SCIP based solver.
@ KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER
Generic Solver.
@ KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER
Dynamic Programming approach for single dimension problems.
@ KNAPSACK_DIVIDE_AND_CONQUER_SOLVER
Divide and Conquer approach for single dimension problems.
@ KNAPSACK_64ITEMS_SOLVER
Optimized method for single dimension small problems.
@ KNAPSACK_BRUTE_FORCE_SOLVER
Brute force method.
@ KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER
CBC Based Solver.
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t > > &weights, const std::vector< int64_t > &capacities)
Initializes the solver and enters the problem to be solved.
void Init(int number_of_items)
bool UpdateState(bool revert, const KnapsackAssignment &assignment)
The class for constraints of a Mathematical Programming (MP) model.
A class to express a linear objective.
void SetCoefficient(const MPVariable *const var, double coeff)
Sets the coefficient of the variable in the objective.
double Value() const
Returns the objective value of the best solution found so far.
void SetMinimization()
Sets the optimization direction to minimize.
This mathematical programming (MP) solver class is the main class though which users build and solve ...
void MakeBoolVarArray(int nb, const std::string &name, std::vector< MPVariable * > *vars)
Creates an array of boolean variables.
MPObjective * MutableObjective()
Returns the mutable objective object.
MPConstraint * MakeRowConstraint(double lb, double ub)
Creates a linear constraint with given bounds.
OptimizationProblemType
The type of problems (LP or MIP) that will be solved and the underlying solver (GLOP,...
ResultStatus Solve()
Solves the problem using the default parameter values.
void SuppressOutput()
Suppresses solver logging.
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:105
SharedTimeLimit * time_limit
const Constraint * ct
int64_t value
double upper_bound
double lower_bound
const int64_t profit_max
MPSolver::OptimizationProblemType problem_type
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
const int FATAL
Definition: log_severity.h:32
void STLDeleteElements(T *container)
Definition: stl_util.h:372
Collection of objects used to extend the Constraint Solver library.
bool CompareKnapsackItemWithEfficiencyInDecreasingEfficiencyOrder(const KnapsackItemWithEfficiency &item1, const KnapsackItemWithEfficiency &item2)
uint32_t OneBit32(int pos)
Definition: bitset.h:39
uint64_t OneBit64(int pos)
Definition: bitset.h:38
KnapsackItem * KnapsackItemPtr
int MostSignificantBitPosition64(uint64_t n)
Definition: bitset.h:231
int64_t weight
Definition: pack.cc:510
Fractional ratio
int64_t capacity
KnapsackItemWithEfficiency(int _id, int64_t _profit, int64_t _weight, int64_t _profit_max)