IGJSP 1.1.1__tar.gz → 1.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IGJSP
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Instance generator for JSP
5
5
  Project-URL: Homepage, https://gps.blogs.upv.es/
6
6
  Author-email: GPS-UPV <gps@dsic.upv.es>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "IGJSP"
7
- version = "1.1.1"
7
+ version = "1.1.2"
8
8
  authors = [
9
9
  { name="GPS-UPV", email="gps@dsic.upv.es" },
10
10
  ]
@@ -28,27 +28,51 @@ def t(c):
28
28
 
29
29
  # ------- Helpers internos para el DZN -------
30
30
 
31
- def _parse_int_var(text, name, default=None):
31
+
32
+ def _parse_set_size(text, name):
33
+ """
34
+ Obtiene el tamaño de un conjunto tipo:
35
+ JOBS = 1..50;
36
+ Devuelve 50.
37
+ """
38
+ m = re.search(rf'\b{name}\b\s*=\s*1\.\.(\d+)\s*;', text)
39
+ if not m:
40
+ raise ValueError(f"No se pudo encontrar el conjunto {name} = 1..N; en el fichero DZN.")
41
+ return int(m.group(1))
42
+
43
+
44
+ def _parse_speed(text):
32
45
  """
33
- Busca una variable escalar tipo: name = 10;
46
+ SPEED puede ir como:
47
+ SPEED = 1;
48
+ o (por si acaso) como 1..SPEED; (aunque en tu ejemplo es un escalar).
34
49
  """
35
- m = re.search(rf'\b{name}\b\s*=\s*([0-9]+)', text)
50
+ m = re.search(r'\bSPEED\b\s*=\s*(\d+)\s*;', text)
36
51
  if m:
37
52
  return int(m.group(1))
38
- if default is not None:
39
- return default
40
- raise ValueError(f"No se encontró la variable entera '{name}' en el fichero DZN.")
53
+
54
+ m = re.search(r'\bSPEED\b\s*=\s*1\.\.(\d+)\s*;', text)
55
+ if m:
56
+ return int(m.group(1))
57
+
58
+ # Si no aparece, asumimos 1 (como en muchos templates tuyos)
59
+ return 1
41
60
 
42
61
 
43
- def _parse_array_var(text, name):
62
+ def _parse_array_from_arrayXd(text, name):
44
63
  """
45
- Busca una variable array tipo:
46
- name = [1, 2, 3];
47
- o
48
- name = [1 2 3];
49
- Devuelve un np.array de ints o None si no está.
64
+ Extrae la parte entre corchetes de cosas tipo:
65
+ name = array3d(...,[1,2,3,...]);
66
+ name = array2d(...,[1,2,3,...]);
67
+ name = array1d(...,[1,2,3,...]);
68
+
69
+ Devuelve np.array de ints o None si no se encuentra.
50
70
  """
51
- m = re.search(rf'\b{name}\b\s*=.*?\[(.*?)\];', text, re.DOTALL)
71
+ m = re.search(
72
+ rf'\b{name}\b\s*=\s*array[123]d\([^[]*\[\s*(.*?)\s*\]\s*\)\s*;',
73
+ text,
74
+ re.DOTALL
75
+ )
52
76
  if not m:
53
77
  return None
54
78
 
@@ -62,6 +86,41 @@ def _parse_array_var(text, name):
62
86
 
63
87
  return np.array([int(t) for t in tokens], dtype=int)
64
88
 
89
+
90
+ def _parse_array_fallback_plain(text, name):
91
+ """
92
+ Fallback para cosas tipo:
93
+ name = [1,2,3,...];
94
+ por si en algún template no se usa arrayXd.
95
+ """
96
+ m = re.search(
97
+ rf'\b{name}\b\s*=\s*\[\s*(.*?)\s*\]\s*;',
98
+ text,
99
+ re.DOTALL
100
+ )
101
+ if not m:
102
+ return None
103
+
104
+ inner = m.group(1).strip()
105
+ if not inner:
106
+ return np.array([], dtype=int)
107
+
108
+ tokens = re.split(r'[\s,]+', inner)
109
+ tokens = [t for t in tokens if t != '']
110
+
111
+ return np.array([int(t) for t in tokens], dtype=int)
112
+
113
+
114
+ def _parse_array_generic(text, name):
115
+ """
116
+ Intenta primero arrayXd(...,[...]); y si no lo encuentra,
117
+ prueba el formato plano name = [ ... ];
118
+ """
119
+ arr = _parse_array_from_arrayXd(text, name)
120
+ if arr is not None:
121
+ return arr
122
+ return _parse_array_fallback_plain(text, name)
123
+
65
124
  #################################################################################
66
125
  # #
67
126
  # JSP #
@@ -183,24 +242,46 @@ class JSP:
183
242
 
184
243
  def loadDznFile(path):
185
244
  """
186
- Carga un .dzn generado por saveDznFile y devuelve un JSP.
187
- Soporta rddd = 0, 1, 2 (sin fechas, fechas por job, fechas por operación).
245
+ Carga un .dzn generado a partir de tus templates, del estilo:
246
+
247
+ JOBS = 1..J;
248
+ MACHINES = 1..M;
249
+ SPEED = S;
250
+
251
+ time = array3d(JOBS,MACHINES,1..SPEED,[...]);
252
+ energy = array3d(JOBS,MACHINES,1..SPEED,[...]);
253
+ precedence = array2d(JOBS,MACHINES,[...]);
254
+
255
+ Opcionalmente puede contener:
256
+ releaseDate = array1d(JOBS,[...]) (rddd = 1)
257
+ dueDate = array1d(JOBS,[...])
258
+
259
+ o
260
+
261
+ releaseDate = array2d(JOBS,MACHINES,[...]) (rddd = 2)
262
+ dueDate = array2d(JOBS,MACHINES,[...])
263
+
264
+ Devuelve un objeto JSP(**sol) consistente con loadJsonFile.
188
265
  """
189
266
  with open(path, 'r', encoding='utf-8') as f:
190
267
  text = f.read()
191
268
 
192
- numJobs = _parse_int_var(text, 'jobs')
193
- numMchs = _parse_int_var(text, 'machines')
194
- speed = _parse_int_var(text, 'Speed', default=1)
269
+ # Eliminar comentarios de Minizinc (% hasta final de línea)
270
+ text = re.sub(r'%.*', '', text)
195
271
 
196
- time_flat = _parse_array_var(text, 'time')
197
- energy_flat = _parse_array_var(text, 'energy')
198
- prec_flat = _parse_array_var(text, 'precedence')
272
+ # Leer tamaños de conjuntos
273
+ numJobs = _parse_set_size(text, "JOBS")
274
+ numMchs = _parse_set_size(text, "MACHINES")
275
+ speed = _parse_speed(text)
276
+
277
+ # Leer arrays principales
278
+ time_flat = _parse_array_generic(text, "time")
279
+ energy_flat = _parse_array_generic(text, "energy")
280
+ prec_flat = _parse_array_generic(text, "precedence")
199
281
 
200
282
  if time_flat is None or energy_flat is None or prec_flat is None:
201
- raise ValueError("Faltan variables obligatorias (time, energy o precedence) en el fichero DZN.")
283
+ raise ValueError("No se pudieron leer 'time', 'energy' o 'precedence' del fichero DZN.")
202
284
 
203
- # Comprobar tamaños
204
285
  expected_te = numJobs * numMchs * speed
205
286
  if time_flat.size != expected_te or energy_flat.size != expected_te:
206
287
  raise ValueError(
@@ -218,29 +299,27 @@ class JSP:
218
299
  EnergyConsumption = energy_flat.reshape((numJobs, numMchs, speed))
219
300
  precedence = prec_flat.reshape((numJobs, numMchs))
220
301
 
221
- # Reconstruir Orden a partir de la precedencia (cada fila es posiciones 0..numMchs-1 por máquina)
302
+ # Reconstruir Orden a partir de precedence (igual que en saveDznFile)
303
+ # precedence[j, m] = posición de la máquina m en la secuencia 0..M-1
222
304
  Orden = np.zeros((numJobs, numMchs), dtype=int)
223
305
  for j in range(numJobs):
224
- # precedence[j, m] = posición de la máquina m en la secuencia
225
- # argsort da el índice de la máquina por orden de prioridad
226
306
  Orden[j, :] = np.argsort(precedence[j, :])
227
307
 
228
- # Release / Due dates (pueden no existir)
229
- release_flat = _parse_array_var(text, 'releaseDate')
230
- due_flat = _parse_array_var(text, 'dueDate')
308
+ # --- Release / Due dates (si existen) ---
309
+ release_flat = _parse_array_generic(text, "releaseDate")
310
+ due_flat = _parse_array_generic(text, "dueDate")
231
311
 
232
312
  if release_flat is None or due_flat is None:
233
- # rddd = 0
313
+ # rddd = 0 (no fechas)
234
314
  ReleaseDueDate = np.array([])
235
315
  else:
236
- # O bien vector por job, o matriz jobs x machines
237
316
  if release_flat.size == numJobs and due_flat.size == numJobs:
238
- # rddd = 1 → (numJobs, 2)
317
+ # rddd = 1: job-level
239
318
  ReleaseDueDate = np.zeros((numJobs, 2), dtype=int)
240
319
  ReleaseDueDate[:, 0] = release_flat
241
320
  ReleaseDueDate[:, 1] = due_flat
242
321
  elif release_flat.size == numJobs * numMchs and due_flat.size == numJobs * numMchs:
243
- # rddd = 2 → (numJobs, numMchs, 2)
322
+ # rddd = 2: operation-level
244
323
  ReleaseDueDate = np.zeros((numJobs, numMchs, 2), dtype=int)
245
324
  ReleaseDueDate[:, :, 0] = release_flat.reshape((numJobs, numMchs))
246
325
  ReleaseDueDate[:, :, 1] = due_flat.reshape((numJobs, numMchs))
@@ -257,6 +336,7 @@ class JSP:
257
336
  'ReleaseDateDueDate': ReleaseDueDate,
258
337
  'Orden': Orden
259
338
  }
339
+
260
340
  return JSP(**sol)
261
341
 
262
342
  def loadTaillardFile(path):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes