OR-Tools  9.3
knapsack_solver.h
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_H_
15#define OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_H_
16
17#include <math.h>
18
19#include <memory>
20#include <string>
21#include <vector>
22
23#include "absl/memory/memory.h"
27#include "ortools/base/macros.h"
29
30namespace operations_research {
31
32class BaseKnapsackSolver;
33
118 public:
132
140
148
149#if defined(USE_CBC)
156#endif // USE_CBC
157
164
165#if defined(USE_SCIP)
172#endif // USE_SCIP
173
174#if defined(USE_XPRESS)
180 KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER = 7,
181#endif
182
183#if defined(USE_CPLEX)
189 KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER = 8,
190#endif
199 };
200
201 explicit KnapsackSolver(const std::string& solver_name);
202 KnapsackSolver(SolverType solver_type, const std::string& solver_name);
203 virtual ~KnapsackSolver();
204
208 void Init(const std::vector<int64_t>& profits,
209 const std::vector<std::vector<int64_t> >& weights,
210 const std::vector<int64_t>& capacities);
211
215 int64_t Solve();
216
220 bool BestSolutionContains(int item_id) const;
224 bool IsSolutionOptimal() const { return is_solution_optimal_; }
225 std::string GetName() const;
226
227 bool use_reduction() const { return use_reduction_; }
228 void set_use_reduction(bool use_reduction) { use_reduction_ = use_reduction; }
229
235 void set_time_limit(double time_limit_seconds) {
236 time_limit_seconds_ = time_limit_seconds;
237 time_limit_ = absl::make_unique<TimeLimit>(time_limit_seconds_);
238 }
239
240 private:
241 // Trivial reduction of capacity constraints when the capacity is higher than
242 // the sum of the weights of the items. Returns the number of reduced items.
243 int ReduceCapacities(int num_items,
244 const std::vector<std::vector<int64_t> >& weights,
245 const std::vector<int64_t>& capacities,
246 std::vector<std::vector<int64_t> >* reduced_weights,
247 std::vector<int64_t>* reduced_capacities);
248 int ReduceProblem(int num_items);
249 void ComputeAdditionalProfit(const std::vector<int64_t>& profits);
250 void InitReducedProblem(const std::vector<int64_t>& profits,
251 const std::vector<std::vector<int64_t> >& weights,
252 const std::vector<int64_t>& capacities);
253
254 std::unique_ptr<BaseKnapsackSolver> solver_;
255 std::vector<bool> known_value_;
256 std::vector<bool> best_solution_;
257 bool is_solution_optimal_ = false;
258 std::vector<int> mapping_reduced_item_id_;
259 bool is_problem_solved_;
260 int64_t additional_profit_;
261 bool use_reduction_;
262 double time_limit_seconds_;
263 std::unique_ptr<TimeLimit> time_limit_;
264
265 DISALLOW_COPY_AND_ASSIGN(KnapsackSolver);
266};
267
268#if !defined(SWIG)
269// The following code defines needed classes for the KnapsackGenericSolver
270// class which is the entry point to extend knapsack with new constraints such
271// as conflicts between items.
272//
273// Constraints are enforced using KnapsackPropagator objects, in the current
274// code there is one propagator per dimension (KnapsackCapacityPropagator).
275// One of those propagators, named master propagator, is used to guide the
276// search, i.e. decides which item should be assigned next.
277// Roughly speaking the search algorithm is:
278// - While not optimal
279// - Select next search node to expand
280// - Select next item_i to assign (using master propagator)
281// - Generate a new search node where item_i is in the knapsack
282// - Check validity of this new partial solution (using propagators)
283// - If valid, add this new search node to the search
284// - Generate a new search node where item_i is not in the knapsack
285// - Check validity of this new partial solution (using propagators)
286// - If valid, add this new search node to the search
287//
288// TODO(user): Add a new propagator class for conflict constraint.
289// TODO(user): Add a new propagator class used as a guide when the problem has
290// several dimensions.
291
292// ----- KnapsackAssignment -----
293// KnapsackAssignment is a small struct used to pair an item with its
294// assignment. It is mainly used for search nodes and updates.
296 KnapsackAssignment(int _item_id, bool _is_in)
297 : item_id(_item_id), is_in(_is_in) {}
299 bool is_in;
300};
301
302// ----- KnapsackItem -----
303// KnapsackItem is a small struct to pair an item weight with its
304// corresponding profit.
305// The aim of the knapsack problem is to pack as many valuable items as
306// possible. A straight forward heuristic is to take those with the greatest
307// profit-per-unit-weight. This ratio is called efficiency in this
308// implementation. So items will be grouped in vectors, and sorted by
309// decreasing efficiency.
310// Note that profits are duplicated for each dimension. This is done to
311// simplify the code, especially the GetEfficiency method and vector sorting.
312// As there usually are only few dimensions, the overhead should not be an
313// issue.
315 KnapsackItem(int _id, int64_t _weight, int64_t _profit)
316 : id(_id), weight(_weight), profit(_profit) {}
317 double GetEfficiency(int64_t profit_max) const {
318 return (weight > 0)
319 ? static_cast<double>(profit) / static_cast<double>(weight)
320 : static_cast<double>(profit_max);
321 }
322
323 // The 'id' field is used to retrieve the initial item in order to
324 // communicate with other propagators and state.
325 const int id;
326 const int64_t weight;
327 const int64_t profit;
328};
330
331// ----- KnapsackSearchNode -----
332// KnapsackSearchNode is a class used to describe a decision in the decision
333// search tree.
334// The node is defined by a pointer to the parent search node and an
335// assignment (see KnapsackAssignment).
336// As the current state is not explicitly stored in a search node, one should
337// go through the search tree to incrementally build a partial solution from
338// a previous search node.
340 public:
343 int depth() const { return depth_; }
344 const KnapsackSearchNode* const parent() const { return parent_; }
345 const KnapsackAssignment& assignment() const { return assignment_; }
346
347 int64_t current_profit() const { return current_profit_; }
348 void set_current_profit(int64_t profit) { current_profit_ = profit; }
349
350 int64_t profit_upper_bound() const { return profit_upper_bound_; }
351 void set_profit_upper_bound(int64_t profit) { profit_upper_bound_ = profit; }
352
353 int next_item_id() const { return next_item_id_; }
354 void set_next_item_id(int id) { next_item_id_ = id; }
355
356 private:
357 // 'depth' field is used to navigate efficiently through the search tree
358 // (see KnapsackSearchPath).
359 int depth_;
360 const KnapsackSearchNode* const parent_;
361 KnapsackAssignment assignment_;
362
363 // 'current_profit' and 'profit_upper_bound' fields are used to sort search
364 // nodes using a priority queue. That allows to pop the node with the best
365 // upper bound, and more importantly to stop the search when optimality is
366 // proved.
367 int64_t current_profit_;
368 int64_t profit_upper_bound_;
369
370 // 'next_item_id' field allows to avoid an O(number_of_items) scan to find
371 // next item to select. This is done for free by the upper bound computation.
372 int next_item_id_;
373
374 DISALLOW_COPY_AND_ASSIGN(KnapsackSearchNode);
375};
376
377// ----- KnapsackSearchPath -----
378// KnapsackSearchPath is a small class used to represent the path between a
379// node to another node in the search tree.
380// As the solution state is not stored for each search node, the state should
381// be rebuilt at each node. One simple solution is to apply all decisions
382// between the node 'to' and the root. This can be computed in
383// O(number_of_items).
384//
385// However, it is possible to achieve better average complexity. Two
386// consecutively explored nodes are usually close enough (i.e., much less than
387// number_of_items) to benefit from an incremental update from the node
388// 'from' to the node 'to'.
389//
390// The 'via' field is the common parent of 'from' field and 'to' field.
391// So the state can be built by reverting all decisions from 'from' to 'via'
392// and then applying all decisions from 'via' to 'to'.
394 public:
396 const KnapsackSearchNode& to);
397 void Init();
398 const KnapsackSearchNode& from() const { return from_; }
399 const KnapsackSearchNode& via() const { return *via_; }
400 const KnapsackSearchNode& to() const { return to_; }
402 int depth) const;
403
404 private:
405 const KnapsackSearchNode& from_;
406 const KnapsackSearchNode* via_; // Computed in 'Init'.
407 const KnapsackSearchNode& to_;
408
409 DISALLOW_COPY_AND_ASSIGN(KnapsackSearchPath);
410};
411
412// ----- KnapsackState -----
413// KnapsackState represents a partial solution to the knapsack problem.
415 public:
417
418 // Initializes vectors with number_of_items set to false (i.e. not bound yet).
419 void Init(int number_of_items);
420 // Updates the state by applying or reverting a decision.
421 // Returns false if fails, i.e. trying to apply an inconsistent decision
422 // to an already assigned item.
423 bool UpdateState(bool revert, const KnapsackAssignment& assignment);
424
425 int GetNumberOfItems() const { return is_bound_.size(); }
426 bool is_bound(int id) const { return is_bound_.at(id); }
427 bool is_in(int id) const { return is_in_.at(id); }
428
429 private:
430 // Vectors 'is_bound_' and 'is_in_' contain a boolean value for each item.
431 // 'is_bound_(item_i)' is false when there is no decision for item_i yet.
432 // When item_i is bound, 'is_in_(item_i)' represents the presence (true) or
433 // the absence (false) of item_i in the current solution.
434 std::vector<bool> is_bound_;
435 std::vector<bool> is_in_;
436
437 DISALLOW_COPY_AND_ASSIGN(KnapsackState);
438};
439
440// ----- KnapsackPropagator -----
441// KnapsackPropagator is the base class for modeling and propagating a
442// constraint given an assignment.
443//
444// When some work has to be done both by the base and the derived class,
445// a protected pure virtual method ending by 'Propagator' is defined.
446// For instance, 'Init' creates a vector of items, and then calls
447// 'InitPropagator' to let the derived class perform its own initialization.
449 public:
450 explicit KnapsackPropagator(const KnapsackState& state);
451 virtual ~KnapsackPropagator();
452
453 // Initializes data structure and then calls InitPropagator.
454 void Init(const std::vector<int64_t>& profits,
455 const std::vector<int64_t>& weights);
456
457 // Updates data structure and then calls UpdatePropagator.
458 // Returns false when failure.
459 bool Update(bool revert, const KnapsackAssignment& assignment);
460 // ComputeProfitBounds should set 'profit_lower_bound_' and
461 // 'profit_upper_bound_' which are constraint specific.
462 virtual void ComputeProfitBounds() = 0;
463 // Returns the id of next item to assign.
464 // Returns kNoSelection when all items are bound.
465 virtual int GetNextItemId() const = 0;
466
467 int64_t current_profit() const { return current_profit_; }
468 int64_t profit_lower_bound() const { return profit_lower_bound_; }
469 int64_t profit_upper_bound() const { return profit_upper_bound_; }
470
471 // Copies the current state into 'solution'.
472 // All unbound items are set to false (i.e. not in the knapsack).
473 // When 'has_one_propagator' is true, CopyCurrentSolutionPropagator is called
474 // to have a better solution. When there is only one propagator
475 // there is no need to check the solution with other propagators, so the
476 // partial solution can be smartly completed.
477 void CopyCurrentStateToSolution(bool has_one_propagator,
478 std::vector<bool>* solution) const;
479
480 protected:
481 // Initializes data structure. This method is called after initialization
482 // of KnapsackPropagator data structure.
483 virtual void InitPropagator() = 0;
484
485 // Updates internal data structure incrementally. This method is called
486 // after update of KnapsackPropagator data structure.
487 virtual bool UpdatePropagator(bool revert,
488 const KnapsackAssignment& assignment) = 0;
489
490 // Copies the current state into 'solution'.
491 // Only unbound items have to be copied as CopyCurrentSolution was already
492 // called with current state.
493 // This method is useful when a propagator is able to find a better solution
494 // than the blind instantiation to false of unbound items.
496 std::vector<bool>* solution) const = 0;
497
498 const KnapsackState& state() const { return state_; }
499 const std::vector<KnapsackItemPtr>& items() const { return items_; }
500
501 void set_profit_lower_bound(int64_t profit) { profit_lower_bound_ = profit; }
502 void set_profit_upper_bound(int64_t profit) { profit_upper_bound_ = profit; }
503
504 private:
505 std::vector<KnapsackItemPtr> items_;
506 int64_t current_profit_;
507 int64_t profit_lower_bound_;
508 int64_t profit_upper_bound_;
509 const KnapsackState& state_;
510
511 DISALLOW_COPY_AND_ASSIGN(KnapsackPropagator);
512};
513
514// ----- KnapsackCapacityPropagator -----
515// KnapsackCapacityPropagator is a KnapsackPropagator used to enforce
516// a capacity constraint.
517// As a KnapsackPropagator is supposed to compute profit lower and upper
518// bounds, and get the next item to select, it can be seen as a 0-1 Knapsack
519// solver. The most efficient way to compute the upper bound is to iterate on
520// items in profit-per-unit-weight decreasing order. The break item is
521// commonly defined as the first item for which there is not enough remaining
522// capacity. Selecting this break item as the next-item-to-assign usually
523// gives the best results (see Greenberg & Hegerich).
524//
525// This is exactly what is implemented in this class.
526//
527// When there is only one propagator, it is possible to compute a better
528// profit lower bound almost for free. During the scan to find the
529// break element all unbound items are added just as if they were part of
530// the current solution. This is used in both ComputeProfitBounds and
531// CopyCurrentSolutionPropagator.
532// For incrementality reasons, the ith item should be accessible in O(1). That's
533// the reason why the item vector has to be duplicated 'sorted_items_'.
535 public:
538 void ComputeProfitBounds() override;
539 int GetNextItemId() const override { return break_item_id_; }
540
541 protected:
542 // Initializes KnapsackCapacityPropagator (e.g., sort items in decreasing
543 // order).
544 void InitPropagator() override;
545 // Updates internal data structure incrementally (i.e., 'consumed_capacity_')
546 // to avoid a O(number_of_items) scan.
547 bool UpdatePropagator(bool revert,
548 const KnapsackAssignment& assignment) override;
550 std::vector<bool>* solution) const override;
551
552 private:
553 // An obvious additional profit upper bound corresponds to the linear
554 // relaxation: remaining_capacity * efficiency of the break item.
555 // It is possible to do better in O(1), using Martello-Toth bound U2.
556 // The main idea is to enforce integrality constraint on the break item,
557 // ie. either the break item is part of the solution, either it is not.
558 // So basically the linear relaxation is done on the item before the break
559 // item, or the one after the break item.
560 // This is what GetAdditionalProfit method implements.
561 int64_t GetAdditionalProfit(int64_t remaining_capacity,
562 int break_item_id) const;
563
564 const int64_t capacity_;
565 int64_t consumed_capacity_;
566 int break_item_id_;
567 std::vector<KnapsackItemPtr> sorted_items_;
568 int64_t profit_max_;
569
570 DISALLOW_COPY_AND_ASSIGN(KnapsackCapacityPropagator);
571};
572
573// ----- BaseKnapsackSolver -----
574// This is the base class for knapsack solvers.
576 public:
577 explicit BaseKnapsackSolver(const std::string& solver_name)
578 : solver_name_(solver_name) {}
580
581 // Initializes the solver and enters the problem to be solved.
582 virtual void Init(const std::vector<int64_t>& profits,
583 const std::vector<std::vector<int64_t> >& weights,
584 const std::vector<int64_t>& capacities) = 0;
585
586 // Gets the lower and upper bound when the item is in or out of the knapsack.
587 // To ensure objects are correctly initialized, this method should not be
588 // called before ::Init.
589 virtual void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in,
590 int64_t* lower_bound,
591 int64_t* upper_bound);
592
593 // Solves the problem and returns the profit of the optimal solution.
594 virtual int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) = 0;
595
596 // Returns true if the item 'item_id' is packed in the optimal knapsack.
597 virtual bool best_solution(int item_id) const = 0;
598
599 virtual std::string GetName() const { return solver_name_; }
600
601 private:
602 const std::string solver_name_;
603};
604
605// ----- KnapsackGenericSolver -----
606// KnapsackGenericSolver is the multi-dimensional knapsack solver class.
607// In the current implementation, the next item to assign is given by the
608// master propagator. Using SetMasterPropagator allows changing the default
609// (propagator of the first dimension), and selecting another dimension when
610// more constrained.
611// TODO(user): In the case of a multi-dimensional knapsack problem, implement
612// an aggregated propagator to combine all dimensions and give a better guide
613// to select the next item (see, for instance, Dobson's aggregated efficiency).
615 public:
616 explicit KnapsackGenericSolver(const std::string& solver_name);
617 ~KnapsackGenericSolver() override;
618
619 // Initializes the solver and enters the problem to be solved.
620 void Init(const std::vector<int64_t>& profits,
621 const std::vector<std::vector<int64_t> >& weights,
622 const std::vector<int64_t>& capacities) override;
623 int GetNumberOfItems() const { return state_.GetNumberOfItems(); }
624 void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in,
625 int64_t* lower_bound,
626 int64_t* upper_bound) override;
627
628 // Sets which propagator should be used to guide the search.
629 // 'master_propagator_id' should be in 0..p-1 with p the number of
630 // propagators.
631 void set_master_propagator_id(int master_propagator_id) {
632 master_propagator_id_ = master_propagator_id;
633 }
634
635 // Solves the problem and returns the profit of the optimal solution.
636 int64_t Solve(TimeLimit* time_limit, bool* is_solution_optimal) override;
637 // Returns true if the item 'item_id' is packed in the optimal knapsack.
638 bool best_solution(int item_id) const override {
639 return best_solution_.at(item_id);
640 }
641
642 private:
643 // Clears internal data structure.
644 void Clear();
645
646 // Updates all propagators reverting/applying all decision on the path.
647 // Returns true if fails. Note that, even if fails, all propagators should
648 // be updated to be in a stable state in order to stay incremental.
649 bool UpdatePropagators(const KnapsackSearchPath& path);
650 // Updates all propagators reverting/applying one decision.
651 // Return true if fails. Note that, even if fails, all propagators should
652 // be updated to be in a stable state in order to stay incremental.
653 bool IncrementalUpdate(bool revert, const KnapsackAssignment& assignment);
654 // Updates the best solution if the current solution has a better profit.
655 void UpdateBestSolution();
656
657 // Returns true if new relevant search node was added to the nodes array, that
658 // means this node should be added to the search queue too.
659 bool MakeNewNode(const KnapsackSearchNode& node, bool is_in);
660
661 // Gets the aggregated (min) profit upper bound among all propagators.
662 int64_t GetAggregatedProfitUpperBound() const;
663 bool HasOnePropagator() const { return propagators_.size() == 1; }
664 int64_t GetCurrentProfit() const {
665 return propagators_.at(master_propagator_id_)->current_profit();
666 }
667 int64_t GetNextItemId() const {
668 return propagators_.at(master_propagator_id_)->GetNextItemId();
669 }
670
671 std::vector<KnapsackPropagator*> propagators_;
672 int master_propagator_id_;
673 std::vector<KnapsackSearchNode*> search_nodes_;
674 KnapsackState state_;
675 int64_t best_solution_profit_;
676 std::vector<bool> best_solution_;
677
678 DISALLOW_COPY_AND_ASSIGN(KnapsackGenericSolver);
679};
680#endif // SWIG
681} // namespace operations_research
682
683#endif // OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_H_
virtual void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in, int64_t *lower_bound, int64_t *upper_bound)
virtual int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal)=0
virtual void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t > > &weights, const std::vector< int64_t > &capacities)=0
BaseKnapsackSolver(const std::string &solver_name)
virtual std::string GetName() const
virtual bool best_solution(int item_id) const =0
KnapsackCapacityPropagator(const KnapsackState &state, int64_t capacity)
bool UpdatePropagator(bool revert, const KnapsackAssignment &assignment) override
void CopyCurrentStateToSolutionPropagator(std::vector< bool > *solution) const override
KnapsackGenericSolver(const std::string &solver_name)
void set_master_propagator_id(int master_propagator_id)
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t > > &weights, const std::vector< int64_t > &capacities) override
int64_t Solve(TimeLimit *time_limit, bool *is_solution_optimal) override
bool best_solution(int item_id) const override
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< int64_t > &weights)
void CopyCurrentStateToSolution(bool has_one_propagator, std::vector< bool > *solution) const
virtual bool UpdatePropagator(bool revert, const KnapsackAssignment &assignment)=0
const std::vector< KnapsackItemPtr > & items() const
virtual int GetNextItemId() const =0
virtual void CopyCurrentStateToSolutionPropagator(std::vector< bool > *solution) const =0
KnapsackPropagator(const KnapsackState &state)
bool Update(bool revert, const KnapsackAssignment &assignment)
const KnapsackState & state() const
const KnapsackSearchNode *const parent() const
KnapsackSearchNode(const KnapsackSearchNode *const parent, const KnapsackAssignment &assignment)
const KnapsackAssignment & assignment() const
const KnapsackSearchNode * MoveUpToDepth(const KnapsackSearchNode &node, int depth) const
KnapsackSearchPath(const KnapsackSearchNode &from, const KnapsackSearchNode &to)
const KnapsackSearchNode & from() const
const KnapsackSearchNode & via() const
const KnapsackSearchNode & to() const
This library solves knapsack problems.
bool BestSolutionContains(int item_id) const
Returns true if the item 'item_id' is packed in the optimal knapsack.
KnapsackSolver(const std::string &solver_name)
void set_time_limit(double time_limit_seconds)
Time limit in seconds.
int64_t Solve()
Solves the problem and returns the profit of the optimal solution.
SolverType
Enum controlling which underlying algorithm is used.
@ KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER
SCIP based solver.
@ KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER
Generic Solver.
@ KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER
Dynamic Programming approach for single dimension problems.
@ KNAPSACK_DIVIDE_AND_CONQUER_SOLVER
Divide and Conquer approach for single dimension problems.
@ KNAPSACK_64ITEMS_SOLVER
Optimized method for single dimension small problems.
@ KNAPSACK_BRUTE_FORCE_SOLVER
Brute force method.
@ KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER
CBC Based Solver.
bool IsSolutionOptimal() const
Returns true if the solution was proven optimal.
void set_use_reduction(bool use_reduction)
void Init(const std::vector< int64_t > &profits, const std::vector< std::vector< int64_t > > &weights, const std::vector< int64_t > &capacities)
Initializes the solver and enters the problem to be solved.
void Init(int number_of_items)
bool UpdateState(bool revert, const KnapsackAssignment &assignment)
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
ModelSharedTimeLimit * time_limit
double upper_bound
double lower_bound
const int64_t profit_max
Collection of objects used to extend the Constraint Solver library.
KnapsackItem * KnapsackItemPtr
int64_t capacity
KnapsackAssignment(int _item_id, bool _is_in)
double GetEfficiency(int64_t profit_max) const
KnapsackItem(int _id, int64_t _weight, int64_t _profit)