IGJSP 1.1.0__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.0
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.0"
7
+ version = "1.1.2"
8
8
  authors = [
9
9
  { name="GPS-UPV", email="gps@dsic.upv.es" },
10
10
  ]
@@ -26,6 +26,101 @@ def t(c):
26
26
  return 4.0704 * np.log(2) / np.log(1 + (c* 2.5093)**3)
27
27
 
28
28
 
29
+ # ------- Helpers internos para el DZN -------
30
+
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):
45
+ """
46
+ SPEED puede ir como:
47
+ SPEED = 1;
48
+ o (por si acaso) como 1..SPEED; (aunque en tu ejemplo es un escalar).
49
+ """
50
+ m = re.search(r'\bSPEED\b\s*=\s*(\d+)\s*;', text)
51
+ if m:
52
+ return int(m.group(1))
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
60
+
61
+
62
+ def _parse_array_from_arrayXd(text, name):
63
+ """
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.
70
+ """
71
+ m = re.search(
72
+ rf'\b{name}\b\s*=\s*array[123]d\([^[]*\[\s*(.*?)\s*\]\s*\)\s*;',
73
+ text,
74
+ re.DOTALL
75
+ )
76
+ if not m:
77
+ return None
78
+
79
+ inner = m.group(1).strip()
80
+ if not inner:
81
+ return np.array([], dtype=int)
82
+
83
+ # Separar por comas o espacios
84
+ tokens = re.split(r'[\s,]+', inner)
85
+ tokens = [t for t in tokens if t != '']
86
+
87
+ return np.array([int(t) for t in tokens], dtype=int)
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
+
29
124
  #################################################################################
30
125
  # #
31
126
  # JSP #
@@ -147,24 +242,46 @@ class JSP:
147
242
 
148
243
  def loadDznFile(path):
149
244
  """
150
- Carga un .dzn generado por saveDznFile y devuelve un JSP.
151
- 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.
152
265
  """
153
266
  with open(path, 'r', encoding='utf-8') as f:
154
267
  text = f.read()
155
268
 
156
- numJobs = _parse_int_var(text, 'jobs')
157
- numMchs = _parse_int_var(text, 'machines')
158
- speed = _parse_int_var(text, 'Speed', default=1)
269
+ # Eliminar comentarios de Minizinc (% hasta final de línea)
270
+ text = re.sub(r'%.*', '', text)
159
271
 
160
- time_flat = _parse_array_var(text, 'time')
161
- energy_flat = _parse_array_var(text, 'energy')
162
- 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")
163
281
 
164
282
  if time_flat is None or energy_flat is None or prec_flat is None:
165
- 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.")
166
284
 
167
- # Comprobar tamaños
168
285
  expected_te = numJobs * numMchs * speed
169
286
  if time_flat.size != expected_te or energy_flat.size != expected_te:
170
287
  raise ValueError(
@@ -182,29 +299,27 @@ class JSP:
182
299
  EnergyConsumption = energy_flat.reshape((numJobs, numMchs, speed))
183
300
  precedence = prec_flat.reshape((numJobs, numMchs))
184
301
 
185
- # 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
186
304
  Orden = np.zeros((numJobs, numMchs), dtype=int)
187
305
  for j in range(numJobs):
188
- # precedence[j, m] = posición de la máquina m en la secuencia
189
- # argsort da el índice de la máquina por orden de prioridad
190
306
  Orden[j, :] = np.argsort(precedence[j, :])
191
307
 
192
- # Release / Due dates (pueden no existir)
193
- release_flat = _parse_array_var(text, 'releaseDate')
194
- 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")
195
311
 
196
312
  if release_flat is None or due_flat is None:
197
- # rddd = 0
313
+ # rddd = 0 (no fechas)
198
314
  ReleaseDueDate = np.array([])
199
315
  else:
200
- # O bien vector por job, o matriz jobs x machines
201
316
  if release_flat.size == numJobs and due_flat.size == numJobs:
202
- # rddd = 1 → (numJobs, 2)
317
+ # rddd = 1: job-level
203
318
  ReleaseDueDate = np.zeros((numJobs, 2), dtype=int)
204
319
  ReleaseDueDate[:, 0] = release_flat
205
320
  ReleaseDueDate[:, 1] = due_flat
206
321
  elif release_flat.size == numJobs * numMchs and due_flat.size == numJobs * numMchs:
207
- # rddd = 2 → (numJobs, numMchs, 2)
322
+ # rddd = 2: operation-level
208
323
  ReleaseDueDate = np.zeros((numJobs, numMchs, 2), dtype=int)
209
324
  ReleaseDueDate[:, :, 0] = release_flat.reshape((numJobs, numMchs))
210
325
  ReleaseDueDate[:, :, 1] = due_flat.reshape((numJobs, numMchs))
@@ -221,6 +336,7 @@ class JSP:
221
336
  'ReleaseDateDueDate': ReleaseDueDate,
222
337
  'Orden': Orden
223
338
  }
339
+
224
340
  return JSP(**sol)
225
341
 
226
342
  def loadTaillardFile(path):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes