1 # Copyright (c) 2020 Moritz Dederichs
2 # Copyright (c) 2020 Janos Piddubnij
4 from gurobipy import Model, GRB, quicksum
5 from typing import Dict, Tuple, Any
8 def __termination_criterion(model, where):
9 """ This termination criterion ensures that the optimizer will not get stuck trying all optimal solutions if there
10 are multiple possible solutions.
20 if where == GRB.Callback.MIP:
21 if model._last_sol_time is not None:
22 now = model.cbGet(GRB.Callback.RUNTIME)
23 if now - model._last_sol_time > 5:
26 if where == GRB.Callback.MIPSOL:
27 if not model._time_limit_disabled:
28 model.params.TimeLimit = GRB.INFINITY
29 model._time_limit_disabled = True
30 now = model.cbGet(GRB.Callback.RUNTIME)
31 model._last_sol_time = now
34 def __formalise_solution(model: Model, items: list, width_items: dict, height_items: dict, length_items: dict) -> Dict[Tuple, Tuple]:
35 """ This function transform the optimal solution of a model into a processable and readable dictionary.
40 The model whose solution should be formalised.
43 The list of items that have been packed in the given box.
46 A dictionary specifying the width of each item type.
49 A dictionary specifying the height of each item type.
52 A dictionary specifying the length of each item type.
57 The dictionary specifying the placement coordinates and orientation of each item within the box.
59 formalised_solution = dict()
60 if model.status in (GRB.status.OPTIMAL, GRB.status.INTERRUPTED):
67 x_begin = model.getVarByName(f'x_{i}')
68 y_begin = model.getVarByName(f'y_{i}')
69 z_begin = model.getVarByName(f'z_{i}')
70 w_x = model.getVarByName(f'w_x_{i}')
71 w_y = model.getVarByName(f'w_y_{i}')
72 w_z = model.getVarByName(f'w_z_{i}')
73 h_x = model.getVarByName(f'h_x_{i}')
74 h_y = model.getVarByName(f'h_y_{i}')
75 h_z = model.getVarByName(f'h_z_{i}')
76 l_x = model.getVarByName(f'l_x_{i}')
77 l_y = model.getVarByName(f'l_y_{i}')
78 l_z = model.getVarByName(f'l_z_{i}')
79 x_end = x_begin.x + w_x.x * width_items[i[0]] + h_x.x * height_items[i[0]] + l_x.x * length_items[i[0]]
80 y_end = y_begin.x + w_y.x * width_items[i[0]] + h_y.x * height_items[i[0]] + l_y.x * length_items[i[0]]
81 z_end = z_begin.x + w_z.x * width_items[i[0]] + h_z.x * height_items[i[0]] + l_z.x * length_items[i[0]]
82 x_align = 1 * w_x.x + 2 * h_x.x + 3 * l_x.x
83 y_align = 1 * w_y.x + 2 * h_y.x + 3 * l_y.x
84 z_align = 1 * w_z.x + 2 * h_z.x + 3 * l_z.x
85 formalised_solution[i] = (x_begin.x, y_begin.x, z_begin.x, x_end, y_end, z_end, align.get(x_align), align.get(y_align), align.get(z_align))
86 return formalised_solution
89 def place_items_in_box(items: list, width_items: Dict[str, int], height_items: Dict[str, int],
90 length_items: Dict[str, int], width_box: int, height_box: int,
91 length_box: int) -> Dict[Any, Tuple[float, float, float, str, str, str]]:
92 """ This function tries to place a given set of items into a given box.
93 The minimisation criterion for this model is to reduce the overall sum of z-coordinates (height)
99 The list of items that should be packed in the given box.
101 width_items: Dict[str, int]
102 A dictionary defining the width of each item type.
104 height_items: Dict[str, int]
105 A dictionary defining the height of each item type.
107 length_items: Dict[str, int]
108 A dictionary defining the length of each item type.
111 The width of the box to pack.
114 The height of the box to pack.
117 The length of the box to pack.
121 Dict[Any, Tuple[float, float, float, str, str, str]]
122 A dictionary specifying for each item the exact coordinates and the item orientation.
123 1. x-coordinate of the item.
124 2. y-coordinate of the item.
125 3. z-coordinate of the item.
126 4. Whether height (h), length (l) or width (w) of the item is aligned to the length of the box.
127 5. Whether height (h), length (l) or width (w) of the item is aligned to the width of the box.
128 6. Whether height (h), length (l) or width (w) of the item is aligned to the height of the box.
130 model = Model('Single_Box_INFORM')
131 model.params.TimeLimit = 15
133 # Calculating the upper bound for the item coordinates within a box
135 if height_box > big_m:
137 if length_box > big_m:
139 if width_box > big_m:
141 # big_m = max([width_box, height_box, length_box])
145 # Variables representing the x-, y- and z-coordinates of item i within the box
149 # Variables indicating whether the width of the item is aligned to the x-, y- or z-side of the box
153 # Variables indicating whether the height of the item is aligned to the x-, y- or z-side of the box
157 # Variables indicating whether the length of the item is aligned to the x-, y- or z-side of the box
162 x[i] = model.addVar(name=f'x_{i}', vtype=GRB.INTEGER, lb=0, ub=big_m)
163 y[i] = model.addVar(name=f'y_{i}', vtype=GRB.INTEGER, lb=0, ub=big_m)
164 z[i] = model.addVar(name=f'z_{i}', vtype=GRB.INTEGER, lb=0, ub=big_m)
165 w_x[i] = model.addVar(name=f'w_x_{i}', vtype=GRB.BINARY)
166 w_y[i] = model.addVar(name=f'w_y_{i}', vtype=GRB.BINARY)
167 w_z[i] = model.addVar(name=f'w_z_{i}', vtype=GRB.BINARY)
168 h_x[i] = model.addVar(name=f'h_x_{i}', vtype=GRB.BINARY)
169 h_y[i] = model.addVar(name=f'h_y_{i}', vtype=GRB.BINARY)
170 h_z[i] = model.addVar(name=f'h_z_{i}', vtype=GRB.BINARY)
171 l_x[i] = model.addVar(name=f'l_x_{i}', vtype=GRB.BINARY)
172 l_y[i] = model.addVar(name=f'l_y_{i}', vtype=GRB.BINARY)
173 l_z[i] = model.addVar(name=f'l_z_{i}', vtype=GRB.BINARY)
175 # Variables indicating the relative position of two items i and j to each other
185 a[i, j] = model.addVar(name=f'a_{i}_{j}', vtype=GRB.INTEGER)
186 b[i, j] = model.addVar(name=f'b_{i}_{j}', vtype=GRB.INTEGER)
187 c[i, j] = model.addVar(name=f'c_{i}_{j}', vtype=GRB.INTEGER)
188 d[i, j] = model.addVar(name=f'd_{i}_{j}', vtype=GRB.INTEGER)
189 e[i, j] = model.addVar(name=f'e_{i}_{j}', vtype=GRB.INTEGER)
190 f[i, j] = model.addVar(name=f'f_{i}_{j}', vtype=GRB.INTEGER)
194 # Items are not allowed to overlap
195 # Additionally items must have at least one positional relation to any other item
199 model.addConstr(x[i] + width_items[i[0]] * w_x[i] + height_items[i[0]] * h_x[i] + length_items[i[0]] * l_x[i] <= x[j] + (1 - a[i, j]) * big_m)
200 model.addConstr(x[j] + width_items[j[0]] * w_x[j] + height_items[j[0]] * h_x[j] + length_items[j[0]] * l_x[j] <= x[i] + (1 - b[i, j]) * big_m)
201 model.addConstr(y[i] + width_items[i[0]] * w_y[i] + height_items[i[0]] * h_y[i] + length_items[i[0]] * l_y[i] <= y[j] + (1 - c[i, j]) * big_m)
202 model.addConstr(y[j] + width_items[j[0]] * w_y[j] + height_items[j[0]] * h_y[j] + length_items[j[0]] * l_y[j] <= y[i] + (1 - d[i, j]) * big_m)
203 model.addConstr(z[i] + width_items[i[0]] * w_z[i] + height_items[i[0]] * h_z[i] + length_items[i[0]] * l_z[i] <= z[j] + (1 - e[i, j]) * big_m)
204 model.addConstr(z[j] + width_items[j[0]] * w_z[j] + height_items[j[0]] * h_z[j] + length_items[j[0]] * l_z[j] <= z[i] + (1 - f[i, j]) * big_m)
205 model.addConstr(a[i, j] + b[i, j] + c[i, j] + d[i, j] + e[i, j] + f[i, j] >= 1)
207 # No item i may be packed over the boundaries of the box
209 model.addConstr(x[i] + width_items[i[0]] * w_x[i] + height_items[i[0]] * h_x[i] + length_items[i[0]] * l_x[i] <= length_box)
210 model.addConstr(y[i] + width_items[i[0]] * w_y[i] + height_items[i[0]] * h_y[i] + length_items[i[0]] * l_y[i] <= width_box)
211 model.addConstr(z[i] + width_items[i[0]] * w_z[i] + height_items[i[0]] * h_z[i] + length_items[i[0]] * l_z[i] <= height_box)
213 # Each side of an item i may only be aligned to one side of the box
215 model.addConstr(w_x[i] + w_y[i] + w_z[i] == 1)
216 model.addConstr(h_x[i] + h_y[i] + h_z[i] == 1)
217 model.addConstr(l_x[i] + l_y[i] + l_z[i] == 1)
218 model.addConstr(w_x[i] + h_x[i] + l_x[i] == 1)
219 model.addConstr(w_y[i] + h_y[i] + l_y[i] == 1)
220 model.addConstr(w_z[i] + h_z[i] + l_z[i] == 1)
222 model._last_sol_time = None
223 model._time_limit_disabled = False
225 model.setObjective(quicksum(z[i] for i in items), GRB.MINIMIZE)
227 model.optimize(__termination_criterion)
229 return __formalise_solution(model, items, width_items, height_items, length_items)