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