OR-Tools  9.6
knapsack_solver_for_cuts.h
Go to the documentation of this file.
1 // Copyright 2010-2022 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 // This library solves 0-1 one-dimensional knapsack problems with fractional
15 // profits and weights using the branch and bound algorithm. Note that
16 // algorithms/knapsack_solver uses 'int64_t' for the profits and the weights.
17 // TODO(user): Merge this code with algorithms/knapsack_solver.
18 //
19 // Given n items, each with a profit and a weight and a knapsack of
20 // capacity c, the goal is to find a subset of the items which fits inside c
21 // and maximizes the total profit.
22 // Without loss of generality, profits and weights are assumed to be positive.
23 //
24 // From a mathematical point of view, the one-dimensional knapsack problem
25 // can be modeled by linear constraint:
26 // Sum(i:1..n)(weight_i * item_i) <= c,
27 // where item_i is a 0-1 integer variable.
28 // The goal is to maximize: Sum(i:1..n)(profit_i * item_i).
29 //
30 // Example Usage:
31 // std::vector<double> profits = {0, 0.5, 0.4, 1, 1, 1.1};
32 // std::vector<double> weights = {9, 6, 2, 1.5, 1.5, 1.5};
33 // KnapsackSolverForCuts solver("solver");
34 // solver.Init(profits, weights, capacity);
35 // bool is_solution_optimal = false;
36 // std::unique_ptr<TimeLimit> time_limit =
37 // std::make_unique<TimeLimit>(time_limit_seconds); // Set the time limit.
38 // const double profit = solver.Solve(time_limit.get(), &is_solution_optimal);
39 // const int number_of_items(profits.size());
40 // for (int item_id(0); item_id < number_of_items; ++item_id) {
41 // solver.best_solution(item_id); // Access the solution.
42 // }
43 
44 #ifndef OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
45 #define OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
46 
47 #include <cstdint>
48 #include <limits>
49 #include <memory>
50 #include <string>
51 #include <vector>
52 
53 #include "absl/memory/memory.h"
54 #include "ortools/base/int_type.h"
55 #include "ortools/base/logging.h"
57 
58 namespace operations_research {
59 
60 // ----- KnapsackAssignmentForCuts -----
61 // KnapsackAssignmentForCuts is a small struct used to pair an item with
62 // its assignment. It is mainly used for search nodes and updates.
65  : item_id(item_id), is_in(is_in) {}
66 
67  int item_id;
68  bool is_in;
69 };
70 
71 // ----- KnapsackItemForCuts -----
72 // KnapsackItemForCuts is a small struct to pair an item weight with its
73 // corresponding profit.
74 // The aim of the knapsack problem is to pack as many valuable items as
75 // possible. A straight forward heuristic is to take those with the greatest
76 // profit-per-unit-weight. This ratio is called efficiency in this
77 // implementation. So items will be grouped in vectors, and sorted by
78 // decreasing efficiency.
80  KnapsackItemForCuts(int id, double weight, double profit)
81  : id(id), weight(weight), profit(profit) {}
82 
83  double GetEfficiency(double profit_max) const {
84  return (weight > 0) ? profit / weight : profit_max;
85  }
86 
87  // The 'id' field is used to retrieve the initial item in order to
88  // communicate with other propagators and state.
89  const int id;
90  const double weight;
91  const double profit;
92 };
93 using KnapsackItemForCutsPtr = std::unique_ptr<KnapsackItemForCuts>;
94 
95 // ----- KnapsackSearchNodeForCuts -----
96 // KnapsackSearchNodeForCuts is a class used to describe a decision in the
97 // decision search tree.
98 // The node is defined by a pointer to the parent search node and an
99 // assignment (see KnapsackAssignmentForCuts).
100 // As the current state is not explicitly stored in a search node, one should
101 // go through the search tree to incrementally build a partial solution from
102 // a previous search node.
104  public:
107 
110  delete;
111 
112  int depth() const { return depth_; }
113  const KnapsackSearchNodeForCuts* const parent() const { return parent_; }
114  const KnapsackAssignmentForCuts& assignment() const { return assignment_; }
115 
116  double current_profit() const { return current_profit_; }
117  void set_current_profit(double profit) { current_profit_ = profit; }
118 
119  double profit_upper_bound() const { return profit_upper_bound_; }
120  void set_profit_upper_bound(double profit) { profit_upper_bound_ = profit; }
121 
122  int next_item_id() const { return next_item_id_; }
123  void set_next_item_id(int id) { next_item_id_ = id; }
124 
125  private:
126  // 'depth_' is used to navigate efficiently through the search tree.
127  int depth_;
128  const KnapsackSearchNodeForCuts* const parent_;
129  KnapsackAssignmentForCuts assignment_;
130 
131  // 'current_profit_' and 'profit_upper_bound_' fields are used to sort search
132  // nodes using a priority queue. That allows to pop the node with the best
133  // upper bound, and more importantly to stop the search when optimality is
134  // proved.
135  double current_profit_;
136  double profit_upper_bound_;
137 
138  // 'next_item_id_' field allows to avoid an O(number_of_items) scan to find
139  // next item to select. This is done for free by the upper bound computation.
140  int next_item_id_;
141 };
142 
143 // ----- KnapsackSearchPathForCuts -----
144 // KnapsackSearchPathForCuts is a small class used to represent the path between
145 // a node to another node in the search tree.
146 // As the solution state is not stored for each search node, the state should
147 // be rebuilt at each node. One simple solution is to apply all decisions
148 // between the node 'to' and the root. This can be computed in
149 // O(number_of_items).
150 //
151 // However, it is possible to achieve better average complexity. Two
152 // consecutively explored nodes are usually close enough (i.e., much less than
153 // number_of_items) to benefit from an incremental update from the node
154 // 'from' to the node 'to'.
155 //
156 // The 'via' field is the common parent of 'from' field and 'to' field.
157 // So the state can be built by reverting all decisions from 'from' to 'via'
158 // and then applying all decisions from 'via' to 'to'.
160  public:
163 
166  delete;
167 
168  void Init();
169  const KnapsackSearchNodeForCuts& from() const { return *from_; }
170  const KnapsackSearchNodeForCuts& via() const { return *via_; }
171  const KnapsackSearchNodeForCuts& to() const { return *to_; }
172 
173  private:
174  const KnapsackSearchNodeForCuts* from_;
175  const KnapsackSearchNodeForCuts* via_; // Computed in 'Init'.
176  const KnapsackSearchNodeForCuts* to_;
177 };
178 
179 // From the given node, this method moves up the tree and returns the node at
180 // given depth.
181 const KnapsackSearchNodeForCuts* MoveUpToDepth(
182  const KnapsackSearchNodeForCuts* node, int depth);
183 
184 // ----- KnapsackStateForCuts -----
185 // KnapsackStateForCuts represents a partial solution to the knapsack problem.
187  public:
189 
192 
193  // Initializes vectors with number_of_items set to false (i.e. not bound yet).
194  void Init(int number_of_items);
195 
196  // Updates the state by applying or reverting a decision.
197  // Returns false if fails, i.e. trying to apply an inconsistent decision
198  // to an already assigned item.
199  bool UpdateState(bool revert, const KnapsackAssignmentForCuts& assignment);
200 
201  int GetNumberOfItems() const { return is_bound_.size(); }
202  bool is_bound(int id) const { return is_bound_.at(id); }
203  bool is_in(int id) const { return is_in_.at(id); }
204 
205  private:
206  // Vectors 'is_bound_' and 'is_in_' contain a boolean value for each item.
207  // 'is_bound_(item_i)' is false when there is no decision for item_i yet.
208  // When item_i is bound, 'is_in_(item_i)' represents the presence (true) or
209  // the absence (false) of item_i in the current solution.
210  std::vector<bool> is_bound_;
211  std::vector<bool> is_in_;
212 };
213 
214 // ----- KnapsackPropagatorForCuts -----
215 // KnapsackPropagatorForCuts is used to enforce a capacity constraint.
216 // It is supposed to compute profit lower and upper bounds, and get the next
217 // item to select, it can be seen as a 0-1 Knapsack solver. The most efficient
218 // way to compute the upper bound is to iterate on items in
219 // profit-per-unit-weight decreasing order. The break item is commonly defined
220 // as the first item for which there is not enough remaining capacity. Selecting
221 // this break item as the next-item-to-assign usually gives the best results
222 // (see Greenberg & Hegerich).
223 //
224 // This is exactly what is implemented in this class.
225 //
226 // It is possible to compute a better profit lower bound almost for free. During
227 // the scan to find the break element all unbound items are added just as if
228 // they were part of the current solution. This is used in both
229 // ComputeProfitBounds() and CopyCurrentSolution(). For incrementality reasons,
230 // the ith item should be accessible in O(1). That's the reason why the item
231 // vector has to be duplicated 'sorted_items_'.
233  public:
236 
239  delete;
240 
241  // Initializes the data structure and then calls InitPropagator.
242  void Init(const std::vector<double>& profits,
243  const std::vector<double>& weights, double capacity);
244 
245  // Updates data structure. Returns false on failure.
246  bool Update(bool revert, const KnapsackAssignmentForCuts& assignment);
247  // ComputeProfitBounds should set 'profit_lower_bound_' and
248  // 'profit_upper_bound_' which are constraint specific.
249  void ComputeProfitBounds();
250  // Returns the id of next item to assign.
251  // Returns kNoSelection when all items are bound.
252  int GetNextItemId() const { return break_item_id_; }
253 
254  double current_profit() const { return current_profit_; }
255  double profit_lower_bound() const { return profit_lower_bound_; }
256  double profit_upper_bound() const { return profit_upper_bound_; }
257 
258  // Copies the current state into 'solution'.
259  // All unbound items are set to false (i.e. not in the knapsack).
260  void CopyCurrentStateToSolution(std::vector<bool>* solution) const;
261 
262  // Initializes the propagator. This method is called by Init() after filling
263  // the fields defined in this class.
264  void InitPropagator();
265 
266  const KnapsackStateForCuts& state() const { return *state_; }
267  const std::vector<KnapsackItemForCutsPtr>& items() const { return items_; }
268 
269  void set_profit_lower_bound(double profit) { profit_lower_bound_ = profit; }
270  void set_profit_upper_bound(double profit) { profit_upper_bound_ = profit; }
271 
272  private:
273  // An obvious additional profit upper bound corresponds to the linear
274  // relaxation: remaining_capacity * efficiency of the break item.
275  // It is possible to do better in O(1), using Martello-Toth bound U2.
276  // The main idea is to enforce integrality constraint on the break item,
277  // i.e. either the break item is part of the solution, or it is not.
278  // So basically the linear relaxation is done on the item before the break
279  // item, or the one after the break item. This is what GetAdditionalProfit
280  // method implements.
281  double GetAdditionalProfitUpperBound(double remaining_capacity,
282  int break_item_id) const;
283 
284  double capacity_;
285  double consumed_capacity_;
286  int break_item_id_;
287  std::vector<KnapsackItemForCutsPtr> sorted_items_;
288  double profit_max_;
289  std::vector<KnapsackItemForCutsPtr> items_;
290  double current_profit_;
291  double profit_lower_bound_;
292  double profit_upper_bound_;
293  const KnapsackStateForCuts* const state_;
294 };
295 
296 // ----- KnapsackSolverForCuts -----
297 // KnapsackSolverForCuts is the one-dimensional knapsack solver class.
298 // In the current implementation, the next item to assign is given by the
299 // primary propagator. Using SetPrimaryPropagator allows changing the default
300 // (propagator of the first dimension).
302  public:
303  explicit KnapsackSolverForCuts(std::string solver_name);
304 
307 
308  // Initializes the solver and enters the problem to be solved.
309  void Init(const std::vector<double>& profits,
310  const std::vector<double>& weights, const double capacity);
311  int GetNumberOfItems() const { return state_.GetNumberOfItems(); }
312 
313  // Gets the lower and the upper bound when the item is in or out of the
314  // knapsack. To ensure objects are correctly initialized, this method should
315  // not be called before Init().
316  void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in,
317  double* lower_bound, double* upper_bound);
318 
319  // Get the best upper bound found so far.
320  double GetUpperBound() { return GetAggregatedProfitUpperBound(); }
321 
322  // The solver stops if a solution with profit better than
323  // 'solution_lower_bound_threshold' is found.
325  const double solution_lower_bound_threshold) {
326  solution_lower_bound_threshold_ = solution_lower_bound_threshold;
327  }
328 
329  // The solver stops if the upper bound on profit drops below
330  // 'solution_upper_bound_threshold'.
332  const double solution_upper_bound_threshold) {
333  solution_upper_bound_threshold_ = solution_upper_bound_threshold;
334  }
335 
336  // Stops the knapsack solver after processing 'node_limit' nodes.
337  void set_node_limit(const int64_t node_limit) { node_limit_ = node_limit; }
338 
339  // Solves the problem and returns the profit of the best solution found.
340  double Solve(TimeLimit* time_limit, bool* is_solution_optimal);
341  // Returns true if the item 'item_id' is packed in the optimal knapsack.
342  bool best_solution(int item_id) const {
343  DCHECK(item_id < best_solution_.size());
344  return best_solution_[item_id];
345  }
346 
347  const std::string& GetName() const { return solver_name_; }
348 
349  private:
350  // Updates propagator reverting/applying all decision on the path. Returns
351  // true if the propagation fails. Note that even if it fails, propagator
352  // should be updated to be in a stable state in order to stay incremental.
353  bool UpdatePropagators(const KnapsackSearchPathForCuts& path);
354  // Updates propagator reverting/applying one decision. Returns true if
355  // the propagation fails. Note that even if it fails, propagator should
356  // be updated to be in a stable state in order to stay incremental.
357  bool IncrementalUpdate(bool revert,
358  const KnapsackAssignmentForCuts& assignment);
359  // Updates the best solution if the current solution has a better profit.
360  void UpdateBestSolution();
361 
362  // Returns true if new relevant search node was added to the nodes array. That
363  // means this node should be added to the search queue too.
364  bool MakeNewNode(const KnapsackSearchNodeForCuts& node, bool is_in);
365 
366  // Gets the aggregated (min) profit upper bound among all propagators.
367  double GetAggregatedProfitUpperBound();
368  double GetCurrentProfit() const { return propagator_.current_profit(); }
369  int GetNextItemId() const { return propagator_.GetNextItemId(); }
370 
371  KnapsackPropagatorForCuts propagator_;
372  std::vector<std::unique_ptr<KnapsackSearchNodeForCuts>> search_nodes_;
373  KnapsackStateForCuts state_;
374  double best_solution_profit_;
375  std::vector<bool> best_solution_;
376  const std::string solver_name_;
377  double solution_lower_bound_threshold_ =
378  std::numeric_limits<double>::infinity();
379  double solution_upper_bound_threshold_ =
380  -std::numeric_limits<double>::infinity();
381  int64_t node_limit_ = std::numeric_limits<int64_t>::max();
382 };
383 // TODO(user) : Add reduction algorithm.
384 
385 } // namespace operations_research
386 
387 #endif // OR_TOOLS_ALGORITHMS_KNAPSACK_SOLVER_FOR_CUTS_H_
int64_t max
Definition: alldiff_cst.cc:140
void Init(const std::vector< double > &profits, const std::vector< double > &weights, double capacity)
void CopyCurrentStateToSolution(std::vector< bool > *solution) const
const std::vector< KnapsackItemForCutsPtr > & items() const
KnapsackPropagatorForCuts(const KnapsackPropagatorForCuts &)=delete
KnapsackPropagatorForCuts(const KnapsackStateForCuts *state)
bool Update(bool revert, const KnapsackAssignmentForCuts &assignment)
KnapsackPropagatorForCuts & operator=(const KnapsackPropagatorForCuts &)=delete
const KnapsackSearchNodeForCuts *const parent() const
KnapsackSearchNodeForCuts(const KnapsackSearchNodeForCuts &)=delete
KnapsackSearchNodeForCuts & operator=(const KnapsackSearchNodeForCuts &)=delete
const KnapsackAssignmentForCuts & assignment() const
KnapsackSearchNodeForCuts(const KnapsackSearchNodeForCuts *parent, const KnapsackAssignmentForCuts &assignment)
const KnapsackSearchNodeForCuts & from() const
const KnapsackSearchNodeForCuts & via() const
const KnapsackSearchNodeForCuts & to() const
KnapsackSearchPathForCuts(const KnapsackSearchNodeForCuts *from, const KnapsackSearchNodeForCuts *to)
KnapsackSearchPathForCuts & operator=(const KnapsackSearchPathForCuts &)=delete
KnapsackSearchPathForCuts(const KnapsackSearchPathForCuts &)=delete
KnapsackSolverForCuts & operator=(const KnapsackSolverForCuts &)=delete
KnapsackSolverForCuts(const KnapsackSolverForCuts &)=delete
void Init(const std::vector< double > &profits, const std::vector< double > &weights, const double capacity)
double Solve(TimeLimit *time_limit, bool *is_solution_optimal)
void GetLowerAndUpperBoundWhenItem(int item_id, bool is_item_in, double *lower_bound, double *upper_bound)
void set_solution_lower_bound_threshold(const double solution_lower_bound_threshold)
void set_solution_upper_bound_threshold(const double solution_upper_bound_threshold)
KnapsackStateForCuts(const KnapsackStateForCuts &)=delete
bool UpdateState(bool revert, const KnapsackAssignmentForCuts &assignment)
KnapsackStateForCuts & operator=(const KnapsackStateForCuts &)=delete
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
Definition: time_limit.h:106
ModelSharedTimeLimit * time_limit
const int64_t profit_max
Collection of objects used to extend the Constraint Solver library.
std::unique_ptr< KnapsackItemForCuts > KnapsackItemForCutsPtr
const KnapsackSearchNodeForCuts * MoveUpToDepth(const KnapsackSearchNodeForCuts *node, int depth)
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
int64_t capacity
KnapsackItemForCuts(int id, double weight, double profit)
double GetEfficiency(double profit_max) const