|
1 | 1 | import numpy as np |
2 | 2 | from ortools.sat.python.cp_model import CpModel, LinearExpr |
| 3 | +from itertools import product |
3 | 4 |
|
4 | 5 | import pyjobshop.solvers.utils as utils |
5 | 6 | from pyjobshop.ProblemData import ( |
@@ -203,48 +204,54 @@ def _circuit_constraints(self): |
203 | 204 | """ |
204 | 205 | Creates the circuit constraints for each machine, if activated by |
205 | 206 | sequencing constraints (consecutive and setup times). |
| 207 | +
|
| 208 | + IMPORTANT: This is specifically implemented for the experiments in the |
| 209 | + paper and it is not meant to be used outside the scope of those |
| 210 | + experiments because it may not be compatible with all other features. |
206 | 211 | """ |
207 | 212 | model, data = self._model, self._data |
208 | 213 | setup_times = utils.setup_times_matrix(data) |
209 | 214 |
|
210 | | - for idx, resource in enumerate(data.resources): |
211 | | - if not isinstance(resource, Machine): |
212 | | - continue |
| 215 | + if not data.permutation: |
| 216 | + return # not a permutation problem, skip |
213 | 217 |
|
214 | | - seq_var = self._sequence_vars[idx] |
215 | | - if not seq_var.is_active: |
216 | | - # No sequencing constraints active. Skip the creation of |
217 | | - # expensive circuit constraints. |
218 | | - continue |
| 218 | + # Create arcs for circuit constraints. |
| 219 | + arcs = [] |
| 220 | + for idx1 in range(data.num_jobs): |
| 221 | + arcs.append((0, idx1 + 1, model.new_bool_var("start"))) |
| 222 | + arcs.append((idx1 + 1, 0, model.new_bool_var("end"))) |
219 | 223 |
|
220 | | - mode_vars = seq_var.mode_vars |
221 | | - arcs = seq_var.arcs |
| 224 | + lits = {} |
| 225 | + for idx1, idx2 in product(range(data.num_jobs), repeat=2): |
| 226 | + if idx1 != idx2: |
| 227 | + lit = model.new_bool_var(f"{idx1} -> {idx2}") |
| 228 | + lits[idx1, idx2] = lit |
| 229 | + arcs.append((idx1 + 1, idx2 + 1, lit)) |
222 | 230 |
|
223 | | - graph = [(u, v, var) for (u, v), var in arcs.items()] |
224 | | - model.add_circuit(graph) |
| 231 | + model.add_circuit(arcs) |
225 | 232 |
|
226 | | - for idx1, var1 in enumerate(mode_vars): |
227 | | - # If the (dummy) self arc is selected, then the var must not |
228 | | - # be present. |
229 | | - model.add(arcs[idx1, idx1] <= ~var1.present) |
230 | | - model.add(arcs[seq_var.DUMMY, seq_var.DUMMY] <= ~var1.present) |
| 233 | + for res_idx, resource in enumerate(data.resources): |
| 234 | + if not isinstance(resource, Machine): |
| 235 | + raise ValueError("Machines only in permutation problems.") |
231 | 236 |
|
232 | | - for idx1, var1 in enumerate(mode_vars): |
233 | | - for idx2, var2 in enumerate(mode_vars): |
234 | | - if idx1 == idx2: |
235 | | - continue |
| 237 | + seq_var = self._sequence_vars[res_idx] |
| 238 | + assert seq_var is not None |
| 239 | + |
| 240 | + for idx1, idx2 in product(range(data.num_jobs), repeat=2): |
| 241 | + if idx1 == idx2: |
| 242 | + continue |
236 | 243 |
|
237 | | - # If the arc is selected, then both tasks must be present. |
238 | | - model.add(arcs[idx1, idx2] <= var1.present) |
239 | | - model.add(arcs[idx1, idx2] <= var2.present) |
240 | | - |
241 | | - setup = ( |
242 | | - setup_times[idx, var1.task_idx, var2.task_idx] |
243 | | - if setup_times is not None |
244 | | - else 0 |
245 | | - ) |
246 | | - expr = var1.end + setup <= var2.start |
247 | | - model.add(expr).only_enforce_if(arcs[idx1, idx2]) |
| 244 | + var1 = seq_var.mode_vars[idx1] |
| 245 | + var2 = seq_var.mode_vars[idx2] |
| 246 | + |
| 247 | + lit = lits[idx1, idx2] |
| 248 | + setup = ( |
| 249 | + setup_times[res_idx, var1.task_idx, var2.task_idx] |
| 250 | + if setup_times is not None |
| 251 | + else 0 |
| 252 | + ) |
| 253 | + expr = var1.end + setup <= var2.start |
| 254 | + model.add(expr).only_enforce_if(lit) |
248 | 255 |
|
249 | 256 | def add_constraints(self): |
250 | 257 | """ |
|
0 commit comments