OR-Tools  9.3
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"
26#include "ortools/util/bitset.h"
28
29namespace operations_research {
30
31namespace {
32const int kNoSelection = -1;
33const int kMasterPropagatorId = 0;
34const int kMaxNumberOfBruteForceItems = 30;
35const int kMaxNumberOf64Items = 64;
36
37// Comparator used to sort item in decreasing efficiency order
38// (see KnapsackCapacityPropagator).
39struct 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.
54struct 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
66typedef 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.
72inline 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
82int64_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 -----
140KnapsackState::KnapsackState() : is_bound_(), is_in_() {}
141
142void 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.
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
173void 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();
184}
185
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::stable_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
300int64_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 -----
339KnapsackGenericSolver::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
446void KnapsackGenericSolver::Clear() {
447 gtl::STLDeleteElements(&propagators_);
448 gtl::STLDeleteElements(&search_nodes_);
449}
450
451// Returns false when at least one propagator fails.
452bool 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
470int64_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
480bool 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
517bool 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
528void 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;
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 -----
726Knapsack64ItemsSolver::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
819int 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.
832void 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.
858void 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
884void 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. This algorithm is pseudo-polynomial because it
929// depends on capacity, ie. the time and space complexity is
930// O(capacity * number_of_items).
931// The implemented algorithm is 'DP-3' in "Knapsack problems", Hans Kellerer,
932// Ulrich Pferschy and David Pisinger, Springer book (ISBN 978-3540402862).
934 public:
935 explicit KnapsackDynamicProgrammingSolver(const std::string& solver_name);
936
937 // Initializes the solver and enters the problem to be solved.
938 void Init(const std::vector<int64_t>& profits,
939 const std::vector<std::vector<int64_t>>& weights,
940 const std::vector<int64_t>& capacities) override;
941
942 // Solves the problem and returns the profit of the optimal solution.
943 int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
944
945 // Returns true if the item 'item_id' is packed in the optimal knapsack.
946 bool best_solution(int item_id) const override {
947 return best_solution_.at(item_id);
948 }
949
950 private:
951 int64_t SolveSubProblem(int64_t capacity, int num_items);
952
953 std::vector<int64_t> profits_;
954 std::vector<int64_t> weights_;
955 int64_t capacity_;
956 std::vector<int64_t> computed_profits_;
957 std::vector<int> selected_item_ids_;
958 std::vector<bool> best_solution_;
959};
960
961// ----- KnapsackDynamicProgrammingSolver -----
963 const std::string& solver_name)
964 : BaseKnapsackSolver(solver_name),
965 profits_(),
966 weights_(),
967 capacity_(0),
968 computed_profits_(),
969 selected_item_ids_(),
970 best_solution_() {}
971
973 const std::vector<int64_t>& profits,
974 const std::vector<std::vector<int64_t>>& weights,
975 const std::vector<int64_t>& capacities) {
976 CHECK_EQ(weights.size(), 1)
977 << "Current implementation of the dynamic programming solver only deals"
978 << " with one dimension.";
979 CHECK_EQ(capacities.size(), weights.size());
980
981 profits_ = profits;
982 weights_ = weights[0];
983 capacity_ = capacities[0];
984}
985
986int64_t KnapsackDynamicProgrammingSolver::SolveSubProblem(int64_t capacity,
987 int num_items) {
988 const int64_t capacity_plus_1 = capacity + 1;
989 std::fill_n(selected_item_ids_.begin(), capacity_plus_1, 0);
990 std::fill_n(computed_profits_.begin(), capacity_plus_1, int64_t{0});
991 for (int item_id = 0; item_id < num_items; ++item_id) {
992 const int64_t item_weight = weights_[item_id];
993 const int64_t item_profit = profits_[item_id];
994 for (int64_t used_capacity = capacity; used_capacity >= item_weight;
995 --used_capacity) {
996 if (computed_profits_[used_capacity - item_weight] + item_profit >
997 computed_profits_[used_capacity]) {
998 computed_profits_[used_capacity] =
999 computed_profits_[used_capacity - item_weight] + item_profit;
1000 selected_item_ids_[used_capacity] = item_id;
1001 }
1002 }
1003 }
1004 return selected_item_ids_.at(capacity);
1005}
1006
1008 bool* is_solution_optimal) {
1009 DCHECK(is_solution_optimal != nullptr);
1010 *is_solution_optimal = true;
1011 const int64_t capacity_plus_1 = capacity_ + 1;
1012 selected_item_ids_.assign(capacity_plus_1, 0);
1013 computed_profits_.assign(capacity_plus_1, 0LL);
1014
1015 int64_t remaining_capacity = capacity_;
1016 int num_items = profits_.size();
1017 best_solution_.assign(num_items, false);
1018
1019 while (remaining_capacity > 0 && num_items > 0) {
1020 const int selected_item_id = SolveSubProblem(remaining_capacity, num_items);
1021 remaining_capacity -= weights_[selected_item_id];
1022 num_items = selected_item_id;
1023 if (remaining_capacity >= 0) {
1024 best_solution_[selected_item_id] = true;
1025 }
1026 }
1027
1028 return computed_profits_[capacity_];
1029}
1030// ----- KnapsackDivideAndConquerSolver -----
1031// KnapsackDivideAndConquerSolver solves the 0-1 knapsack problem (KP)
1032// using divide and conquer and dynamic programming.
1033// By using one-dimensional vectors it keeps a complexity of O(capacity *
1034// number_of_items) in time, but reduces the space complexity to O(capacity +
1035// number_of_items) and is therefore suitable for large hard to solve
1036// (KP)/(SSP). The implemented algorithm is based on 'DP-2' and Divide and
1037// Conquer for storage reduction from [Hans Kellerer et al., "Knapsack problems"
1038// (DOI 10.1007/978-3-540-24777-7)].
1040 public:
1041 explicit KnapsackDivideAndConquerSolver(const std::string& solver_name);
1042
1043 // Initializes the solver and enters the problem to be solved.
1044 void Init(const std::vector<int64_t>& profits,
1045 const std::vector<std::vector<int64_t>>& weights,
1046 const std::vector<int64_t>& capacities) override;
1047
1048 // Solves the problem and returns the profit of the optimal solution.
1049 int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
1050
1051 // Returns true if the item 'item_id' is packed in the optimal knapsack.
1052 bool best_solution(int item_id) const override {
1053 return best_solution_.at(item_id);
1054 }
1055
1056 private:
1057 // 'DP 2' computes solution 'z' for 0 up to capacitiy
1058 void SolveSubProblem(bool first_storage, int64_t capacity, int start_item,
1059 int end_item);
1060
1061 // Calculates best_solution_ and return 'z' from first instance
1062 int64_t DivideAndConquer(int64_t capacity, int start_item, int end_item);
1063
1064 std::vector<int64_t> profits_;
1065 std::vector<int64_t> weights_;
1066 int64_t capacity_;
1067 std::vector<int64_t> computed_profits_storage1_;
1068 std::vector<int64_t> computed_profits_storage2_;
1069 std::vector<bool> best_solution_;
1070};
1071
1072// ----- KnapsackDivideAndConquerSolver -----
1074 const std::string& solver_name)
1075 : BaseKnapsackSolver(solver_name),
1076 profits_(),
1077 weights_(),
1078 capacity_(0),
1079 computed_profits_storage1_(),
1080 computed_profits_storage2_(),
1081 best_solution_() {}
1082
1084 const std::vector<int64_t>& profits,
1085 const std::vector<std::vector<int64_t>>& weights,
1086 const std::vector<int64_t>& capacities) {
1087 CHECK_EQ(weights.size(), 1)
1088 << "Current implementation of the divide and conquer solver only deals"
1089 << " with one dimension.";
1090 CHECK_EQ(capacities.size(), weights.size());
1091
1092 profits_ = profits;
1093 weights_ = weights[0];
1094 capacity_ = capacities[0];
1095}
1096
1097void KnapsackDivideAndConquerSolver::SolveSubProblem(bool first_storage,
1098 int64_t capacity,
1099 int start_item,
1100 int end_item) {
1101 std::vector<int64_t>& computed_profits_storage_ =
1102 (first_storage) ? computed_profits_storage1_ : computed_profits_storage2_;
1103 const int64_t capacity_plus_1 = capacity + 1;
1104 std::fill_n(computed_profits_storage_.begin(), capacity_plus_1, 0LL);
1105 for (int item_id = start_item; item_id < end_item; ++item_id) {
1106 const int64_t item_weight = weights_[item_id];
1107 const int64_t item_profit = profits_[item_id];
1108 for (int64_t used_capacity = capacity; used_capacity >= item_weight;
1109 --used_capacity) {
1110 if (computed_profits_storage_[used_capacity - item_weight] + item_profit >
1111 computed_profits_storage_[used_capacity]) {
1112 computed_profits_storage_[used_capacity] =
1113 computed_profits_storage_[used_capacity - item_weight] +
1114 item_profit;
1115 }
1116 }
1117 }
1118}
1119
1120int64_t KnapsackDivideAndConquerSolver::DivideAndConquer(int64_t capacity,
1121 int start_item,
1122 int end_item) {
1123 const int64_t capacity_plus_1 = capacity_ + 1;
1124 int item_boundary_ = start_item + ((end_item - start_item) / 2);
1125
1126 SolveSubProblem(true, capacity, start_item, item_boundary_);
1127 SolveSubProblem(false, capacity, item_boundary_, end_item);
1128
1129 int64_t max_solution_ = 0, capacity1_ = 0, capacity2_ = 0;
1130
1131 for (int64_t capacity_id = 0; capacity_id <= capacity; capacity_id++) {
1132 if ((computed_profits_storage1_[capacity_id] +
1133 computed_profits_storage2_[(capacity - capacity_id)]) >
1134 max_solution_) {
1135 capacity1_ = capacity_id;
1136 capacity2_ = capacity - capacity_id;
1137 max_solution_ = (computed_profits_storage1_[capacity_id] +
1138 computed_profits_storage2_[(capacity - capacity_id)]);
1139 }
1140 }
1141
1142 if ((item_boundary_ - start_item) == 1) {
1143 if (weights_[start_item] <= capacity1_) best_solution_[start_item] = true;
1144 } else if ((item_boundary_ - start_item) > 1)
1145 DivideAndConquer(capacity1_, start_item, item_boundary_);
1146
1147 if ((end_item - item_boundary_) == 1) {
1148 if (weights_[item_boundary_] <= capacity2_)
1149 best_solution_[item_boundary_] = true;
1150 } else if ((end_item - item_boundary_) > 1)
1151 DivideAndConquer(capacity2_, item_boundary_, end_item);
1152
1153 return max_solution_;
1154}
1155
1157 bool* is_solution_optimal) {
1158 DCHECK(is_solution_optimal != nullptr);
1159 *is_solution_optimal = true;
1160 const int64_t capacity_plus_1 = capacity_ + 1;
1161 computed_profits_storage1_.assign(capacity_plus_1, 0LL);
1162 computed_profits_storage2_.assign(capacity_plus_1, 0LL);
1163 best_solution_.assign(profits_.size(), false);
1164
1165 return DivideAndConquer(capacity_, 0, profits_.size());
1166}
1167// ----- KnapsackMIPSolver -----
1169 public:
1171 const std::string& solver_name);
1172
1173 // Initializes the solver and enters the problem to be solved.
1174 void Init(const std::vector<int64_t>& profits,
1175 const std::vector<std::vector<int64_t>>& weights,
1176 const std::vector<int64_t>& capacities) override;
1177
1178 // Solves the problem and returns the profit of the optimal solution.
1179 int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
1180
1181 // Returns true if the item 'item_id' is packed in the optimal knapsack.
1182 bool best_solution(int item_id) const override {
1183 return best_solution_.at(item_id);
1184 }
1185
1186 private:
1188 std::vector<int64_t> profits_;
1189 std::vector<std::vector<int64_t>> weights_;
1190 std::vector<int64_t> capacities_;
1191 std::vector<bool> best_solution_;
1192};
1193
1196 const std::string& solver_name)
1197 : BaseKnapsackSolver(solver_name),
1198 problem_type_(problem_type),
1199 profits_(),
1200 weights_(),
1201 capacities_(),
1202 best_solution_() {}
1203
1204void KnapsackMIPSolver::Init(const std::vector<int64_t>& profits,
1205 const std::vector<std::vector<int64_t>>& weights,
1206 const std::vector<int64_t>& capacities) {
1207 profits_ = profits;
1208 weights_ = weights;
1209 capacities_ = capacities;
1210}
1211
1213 bool* is_solution_optimal) {
1214 DCHECK(is_solution_optimal != nullptr);
1215 *is_solution_optimal = true;
1216 MPSolver solver(GetName(), problem_type_);
1217
1218 const int num_items = profits_.size();
1219 std::vector<MPVariable*> variables;
1220 solver.MakeBoolVarArray(num_items, "x", &variables);
1221
1222 // Add constraints.
1223 const int num_dimensions = capacities_.size();
1224 CHECK(weights_.size() == num_dimensions)
1225 << "Weights should be vector of num_dimensions (" << num_dimensions
1226 << ") vectors of size num_items (" << num_items << ").";
1227 for (int i = 0; i < num_dimensions; ++i) {
1228 MPConstraint* const ct = solver.MakeRowConstraint(0LL, capacities_.at(i));
1229 for (int j = 0; j < num_items; ++j) {
1230 ct->SetCoefficient(variables.at(j), weights_.at(i).at(j));
1231 }
1232 }
1233
1234 // Define objective to minimize. Minimization is used instead of maximization
1235 // because of an issue with CBC solver which does not always find the optimal
1236 // solution on maximization problems.
1237 MPObjective* const objective = solver.MutableObjective();
1238 for (int j = 0; j < num_items; ++j) {
1239 objective->SetCoefficient(variables.at(j), -profits_.at(j));
1240 }
1241 objective->SetMinimization();
1242
1243 solver.SuppressOutput();
1244 solver.Solve();
1245
1246 // Store best solution.
1247 const float kRoundNear = 0.5;
1248 best_solution_.assign(num_items, false);
1249 for (int j = 0; j < num_items; ++j) {
1250 const double value = variables.at(j)->solution_value();
1251 best_solution_.at(j) = value >= kRoundNear;
1252 }
1253
1254 return -objective->Value() + kRoundNear;
1255}
1256
1257// ----- KnapsackSolver -----
1258KnapsackSolver::KnapsackSolver(const std::string& solver_name)
1259 : KnapsackSolver(KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER,
1260 solver_name) {}
1261
1263 const std::string& solver_name)
1264 : solver_(),
1265 known_value_(),
1266 best_solution_(),
1267 mapping_reduced_item_id_(),
1268 is_problem_solved_(false),
1269 additional_profit_(0LL),
1270 use_reduction_(true),
1271 time_limit_seconds_(std::numeric_limits<double>::infinity()) {
1272 switch (solver_type) {
1274 solver_ = absl::make_unique<KnapsackBruteForceSolver>(solver_name);
1275 break;
1277 solver_ = absl::make_unique<Knapsack64ItemsSolver>(solver_name);
1278 break;
1280 solver_ =
1281 absl::make_unique<KnapsackDynamicProgrammingSolver>(solver_name);
1282 break;
1284 solver_ = absl::make_unique<KnapsackGenericSolver>(solver_name);
1285 break;
1287 solver_ = absl::make_unique<KnapsackDivideAndConquerSolver>(solver_name);
1288 break;
1289#if defined(USE_CBC)
1291 solver_ = absl::make_unique<KnapsackMIPSolver>(
1293 break;
1294#endif // USE_CBC
1295#if defined(USE_SCIP)
1297 solver_ = absl::make_unique<KnapsackMIPSolver>(
1299 break;
1300#endif // USE_SCIP
1301#if defined(USE_XPRESS)
1302 case KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER:
1303 solver_ = absl::make_unique<KnapsackMIPSolver>(
1305 break;
1306#endif
1307#if defined(USE_CPLEX)
1308 case KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER:
1309 solver_ = absl::make_unique<KnapsackMIPSolver>(
1311 break;
1312#endif
1313 default:
1314 LOG(FATAL) << "Unknown knapsack solver type.";
1315 }
1316}
1317
1319
1320void KnapsackSolver::Init(const std::vector<int64_t>& profits,
1321 const std::vector<std::vector<int64_t>>& weights,
1322 const std::vector<int64_t>& capacities) {
1323 for (const std::vector<int64_t>& w : weights) {
1324 CHECK_EQ(profits.size(), w.size())
1325 << "Profits and inner weights must have the same size (#items)";
1326 }
1327 CHECK_EQ(capacities.size(), weights.size())
1328 << "Capacities and weights must have the same size (#bins)";
1329 time_limit_ = absl::make_unique<TimeLimit>(time_limit_seconds_);
1330 is_solution_optimal_ = false;
1331 additional_profit_ = 0LL;
1332 is_problem_solved_ = false;
1333
1334 const int num_items = profits.size();
1335 std::vector<std::vector<int64_t>> reduced_weights;
1336 std::vector<int64_t> reduced_capacities;
1337 if (use_reduction_) {
1338 const int num_reduced_items = ReduceCapacities(
1339 num_items, weights, capacities, &reduced_weights, &reduced_capacities);
1340 if (num_reduced_items > 0) {
1341 ComputeAdditionalProfit(profits);
1342 }
1343 } else {
1344 reduced_weights = weights;
1345 reduced_capacities = capacities;
1346 }
1347 if (!is_problem_solved_) {
1348 solver_->Init(profits, reduced_weights, reduced_capacities);
1349 if (use_reduction_) {
1350 const int num_reduced_items = ReduceProblem(num_items);
1351
1352 if (num_reduced_items > 0) {
1353 ComputeAdditionalProfit(profits);
1354 }
1355
1356 if (num_reduced_items > 0 && num_reduced_items < num_items) {
1357 InitReducedProblem(profits, reduced_weights, reduced_capacities);
1358 }
1359 }
1360 }
1361 if (is_problem_solved_) {
1362 is_solution_optimal_ = true;
1363 }
1364}
1365
1366int KnapsackSolver::ReduceCapacities(
1367 int num_items, const std::vector<std::vector<int64_t>>& weights,
1368 const std::vector<int64_t>& capacities,
1369 std::vector<std::vector<int64_t>>* reduced_weights,
1370 std::vector<int64_t>* reduced_capacities) {
1371 known_value_.assign(num_items, false);
1372 best_solution_.assign(num_items, false);
1373 mapping_reduced_item_id_.assign(num_items, 0);
1374 std::vector<bool> active_capacities(weights.size(), true);
1375 int number_of_active_capacities = 0;
1376 for (int i = 0; i < weights.size(); ++i) {
1377 int64_t max_weight = 0;
1378 for (int64_t weight : weights[i]) {
1379 max_weight += weight;
1380 }
1381 if (max_weight <= capacities[i]) {
1382 active_capacities[i] = false;
1383 } else {
1384 ++number_of_active_capacities;
1385 }
1386 }
1387 reduced_weights->reserve(number_of_active_capacities);
1388 reduced_capacities->reserve(number_of_active_capacities);
1389 for (int i = 0; i < weights.size(); ++i) {
1390 if (active_capacities[i]) {
1391 reduced_weights->push_back(weights[i]);
1392 reduced_capacities->push_back(capacities[i]);
1393 }
1394 }
1395 if (reduced_capacities->empty()) {
1396 // There are no capacity constraints in the problem so we can reduce all
1397 // items and just add them to the best solution.
1398 for (int item_id = 0; item_id < num_items; ++item_id) {
1399 known_value_[item_id] = true;
1400 best_solution_[item_id] = true;
1401 }
1402 is_problem_solved_ = true;
1403 // All items are reduced.
1404 return num_items;
1405 }
1406 // There are still capacity constraints so no item reduction is done.
1407 return 0;
1408}
1409
1410int KnapsackSolver::ReduceProblem(int num_items) {
1411 known_value_.assign(num_items, false);
1412 best_solution_.assign(num_items, false);
1413 mapping_reduced_item_id_.assign(num_items, 0);
1414 additional_profit_ = 0LL;
1415
1416 for (int item_id = 0; item_id < num_items; ++item_id) {
1417 mapping_reduced_item_id_[item_id] = item_id;
1418 }
1419
1420 int64_t best_lower_bound = 0LL;
1421 std::vector<int64_t> J0_upper_bounds(num_items,
1423 std::vector<int64_t> J1_upper_bounds(num_items,
1425 for (int item_id = 0; item_id < num_items; ++item_id) {
1426 if (time_limit_->LimitReached()) {
1427 break;
1428 }
1429 int64_t lower_bound = 0LL;
1431 solver_->GetLowerAndUpperBoundWhenItem(item_id, false, &lower_bound,
1432 &upper_bound);
1433 J1_upper_bounds.at(item_id) = upper_bound;
1434 best_lower_bound = std::max(best_lower_bound, lower_bound);
1435
1436 solver_->GetLowerAndUpperBoundWhenItem(item_id, true, &lower_bound,
1437 &upper_bound);
1438 J0_upper_bounds.at(item_id) = upper_bound;
1439 best_lower_bound = std::max(best_lower_bound, lower_bound);
1440 }
1441
1442 int num_reduced_items = 0;
1443 for (int item_id = 0; item_id < num_items; ++item_id) {
1444 if (best_lower_bound > J0_upper_bounds[item_id]) {
1445 known_value_[item_id] = true;
1446 best_solution_[item_id] = false;
1447 ++num_reduced_items;
1448 } else if (best_lower_bound > J1_upper_bounds[item_id]) {
1449 known_value_[item_id] = true;
1450 best_solution_[item_id] = true;
1451 ++num_reduced_items;
1452 }
1453 }
1454
1455 is_problem_solved_ = num_reduced_items == num_items;
1456 return num_reduced_items;
1457}
1458
1459void KnapsackSolver::ComputeAdditionalProfit(
1460 const std::vector<int64_t>& profits) {
1461 const int num_items = profits.size();
1462 additional_profit_ = 0LL;
1463 for (int item_id = 0; item_id < num_items; ++item_id) {
1464 if (known_value_[item_id] && best_solution_[item_id]) {
1465 additional_profit_ += profits[item_id];
1466 }
1467 }
1468}
1469
1470void KnapsackSolver::InitReducedProblem(
1471 const std::vector<int64_t>& profits,
1472 const std::vector<std::vector<int64_t>>& weights,
1473 const std::vector<int64_t>& capacities) {
1474 const int num_items = profits.size();
1475 const int num_dimensions = capacities.size();
1476
1477 std::vector<int64_t> reduced_profits;
1478 for (int item_id = 0; item_id < num_items; ++item_id) {
1479 if (!known_value_[item_id]) {
1480 mapping_reduced_item_id_[item_id] = reduced_profits.size();
1481 reduced_profits.push_back(profits[item_id]);
1482 }
1483 }
1484
1485 std::vector<std::vector<int64_t>> reduced_weights;
1486 std::vector<int64_t> reduced_capacities = capacities;
1487 for (int dim = 0; dim < num_dimensions; ++dim) {
1488 const std::vector<int64_t>& one_dimension_weights = weights[dim];
1489 std::vector<int64_t> one_dimension_reduced_weights;
1490 for (int item_id = 0; item_id < num_items; ++item_id) {
1491 if (known_value_[item_id]) {
1492 if (best_solution_[item_id]) {
1493 reduced_capacities[dim] -= one_dimension_weights[item_id];
1494 }
1495 } else {
1496 one_dimension_reduced_weights.push_back(one_dimension_weights[item_id]);
1497 }
1498 }
1499 reduced_weights.push_back(one_dimension_reduced_weights);
1500 }
1501 solver_->Init(reduced_profits, reduced_weights, reduced_capacities);
1502}
1503
1505 return additional_profit_ +
1506 ((is_problem_solved_)
1507 ? 0
1508 : solver_->Solve(time_limit_.get(), &is_solution_optimal_));
1509}
1510
1512 const int mapped_item_id =
1513 (use_reduction_) ? mapping_reduced_item_id_[item_id] : item_id;
1514 return (use_reduction_ && known_value_[item_id])
1515 ? best_solution_[item_id]
1516 : solver_->best_solution(mapped_item_id);
1517}
1518
1519std::string KnapsackSolver::GetName() const { return solver_->GetName(); }
1520
1521// ----- BaseKnapsackSolver -----
1523 bool is_item_in,
1524 int64_t* lower_bound,
1525 int64_t* upper_bound) {
1526 CHECK(lower_bound != nullptr);
1527 CHECK(upper_bound != nullptr);
1528 *lower_bound = 0LL;
1530}
1531
1532} // 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:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:896
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
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)
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
bool best_solution(int item_id) const override
KnapsackBruteForceSolver(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
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
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
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
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
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
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 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)
const KnapsackState & state() const
const KnapsackSearchNode *const parent() const
KnapsackSearchNode(const KnapsackSearchNode *const parent, const KnapsackAssignment &assignment)
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.
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.
MPObjective * MutableObjective()
Returns the mutable objective object.
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
ModelSharedTimeLimit * 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
STL namespace.
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)