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