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