job-shop-lib 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,8 @@ import os
6
6
  import functools
7
7
  from typing import Any
8
8
 
9
+ import numpy as np
10
+
9
11
  from job_shop_lib import Operation
10
12
 
11
13
 
@@ -264,6 +266,58 @@ class JobShopInstance:
264
266
  [operation.machine_id for operation in job] for job in self.jobs
265
267
  ]
266
268
 
269
+ @functools.cached_property
270
+ def durations_matrix_array(self) -> np.ndarray:
271
+ """Returns the duration matrix of the instance as a numpy array.
272
+
273
+ The returned array has shape (num_jobs, max_num_operations_per_job).
274
+ Non-existing operations are filled with np.nan.
275
+
276
+ Example:
277
+ >>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
278
+ >>> instance = JobShopInstance(jobs)
279
+ >>> instance.durations_matrix_array
280
+ array([[ 2., 2.],
281
+ [ 4., nan]], dtype=float32)
282
+ """
283
+ duration_matrix = self.durations_matrix
284
+ return self._fill_matrix_with_nans_2d(duration_matrix)
285
+
286
+ @functools.cached_property
287
+ def machines_matrix_array(self) -> np.ndarray:
288
+ """Returns the machines matrix of the instance as a numpy array.
289
+
290
+ The returned array has shape (num_jobs, max_num_operations_per_job,
291
+ max_num_machines_per_operation). Non-existing machines are filled with
292
+ np.nan.
293
+
294
+ Example:
295
+ >>> jobs = [
296
+ ... [Operation(machines=[0, 1], 2), Operation(machines=1, 3)],
297
+ ... [Operation(machines=0, 6)],
298
+ ... ]
299
+ >>> instance = JobShopInstance(jobs)
300
+ >>> instance.machines_matrix_array
301
+ array([[[ 0., 1.],
302
+ [ 1., nan]],
303
+ [[ 0., nan],
304
+ [nan, nan]]], dtype=float32)
305
+ """
306
+
307
+ machines_matrix = self.machines_matrix
308
+ if self.is_flexible:
309
+ # False positive from mypy, the type of machines_matrix is
310
+ # list[list[list[int]]] here
311
+ return self._fill_matrix_with_nans_3d(
312
+ machines_matrix # type: ignore[arg-type]
313
+ )
314
+
315
+ # False positive from mypy, the type of machines_matrix is
316
+ # list[list[int]] here
317
+ return self._fill_matrix_with_nans_2d(
318
+ machines_matrix # type: ignore[arg-type]
319
+ )
320
+
267
321
  @functools.cached_property
268
322
  def operations_by_machine(self) -> list[list[Operation]]:
269
323
  """Returns a list of lists of operations.
@@ -353,3 +407,50 @@ class JobShopInstance:
353
407
  def total_duration(self) -> int:
354
408
  """Returns the sum of the durations of all operations in all jobs."""
355
409
  return sum(self.job_durations)
410
+
411
+ @staticmethod
412
+ def _fill_matrix_with_nans_2d(matrix: list[list[int]]) -> np.ndarray:
413
+ """Fills a matrix with np.nan values.
414
+
415
+ Args:
416
+ matrix:
417
+ A list of lists of integers.
418
+
419
+ Returns:
420
+ A numpy array with the same shape as the input matrix, filled with
421
+ np.nan values.
422
+ """
423
+ max_length = max(len(row) for row in matrix)
424
+ squared_matrix = np.full(
425
+ (len(matrix), max_length), np.nan, dtype=np.float32
426
+ )
427
+ for i, row in enumerate(matrix):
428
+ squared_matrix[i, : len(row)] = row
429
+ return squared_matrix
430
+
431
+ @staticmethod
432
+ def _fill_matrix_with_nans_3d(matrix: list[list[list[int]]]) -> np.ndarray:
433
+ """Fills a 3D matrix with np.nan values.
434
+
435
+ Args:
436
+ matrix:
437
+ A list of lists of lists of integers.
438
+
439
+ Returns:
440
+ A numpy array with the same shape as the input matrix, filled with
441
+ np.nan values.
442
+ """
443
+ max_length = max(len(row) for row in matrix)
444
+ max_inner_length = len(matrix[0][0])
445
+ for row in matrix:
446
+ for inner_row in row:
447
+ max_inner_length = max(max_inner_length, len(inner_row))
448
+ squared_matrix = np.full(
449
+ (len(matrix), max_length, max_inner_length),
450
+ np.nan,
451
+ dtype=np.float32,
452
+ )
453
+ for i, row in enumerate(matrix):
454
+ for j, inner_row in enumerate(row):
455
+ squared_matrix[i, j, : len(inner_row)] = inner_row
456
+ return squared_matrix
job_shop_lib/operation.py CHANGED
@@ -107,10 +107,10 @@ class Operation:
107
107
  def __hash__(self) -> int:
108
108
  return hash(self.operation_id)
109
109
 
110
- def __eq__(self, __value: object) -> bool:
111
- if isinstance(__value, Operation):
112
- return self.operation_id == __value.operation_id
113
- return False
110
+ def __eq__(self, value: object) -> bool:
111
+ if not isinstance(value, Operation):
112
+ return False
113
+ return self.__slots__ == value.__slots__
114
114
 
115
115
  def __repr__(self) -> str:
116
116
  machines = (
job_shop_lib/schedule.py CHANGED
@@ -1,6 +1,11 @@
1
1
  """Home of the `Schedule` class."""
2
2
 
3
- from job_shop_lib import ScheduledOperation, JobShopInstance
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from collections import deque
7
+
8
+ from job_shop_lib import ScheduledOperation, JobShopInstance, JobShopLibError
4
9
 
5
10
 
6
11
  class Schedule:
@@ -71,6 +76,96 @@ class Schedule:
71
76
  """Returns the number of operations that have been scheduled."""
72
77
  return sum(len(machine_schedule) for machine_schedule in self.schedule)
73
78
 
79
+ def to_dict(self) -> dict:
80
+ """Returns a dictionary representation of the schedule.
81
+
82
+ This representation is useful for saving the instance to a JSON file.
83
+
84
+ Returns:
85
+ A dictionary representation of the schedule with the following
86
+ keys:
87
+ - "instance": A dictionary representation of the instance.
88
+ - "job_sequences": A list of lists of job ids. Each list of job
89
+ ids represents the order of operations on the machine. The
90
+ machine that the list corresponds to is determined by the
91
+ index of the list.
92
+ - "metadata": A dictionary with additional information about
93
+ the schedule.
94
+ """
95
+ job_sequences: list[list[int]] = []
96
+ for machine_schedule in self.schedule:
97
+ job_sequences.append(
98
+ [operation.job_id for operation in machine_schedule]
99
+ )
100
+
101
+ return {
102
+ "instance": self.instance.to_dict(),
103
+ "job_sequences": job_sequences,
104
+ "metadata": self.metadata,
105
+ }
106
+
107
+ @staticmethod
108
+ def from_dict(
109
+ instance: dict[str, Any] | JobShopInstance,
110
+ job_sequences: list[list[int]],
111
+ metadata: dict[str, Any] | None = None,
112
+ ) -> Schedule:
113
+ """Creates a schedule from a dictionary representation."""
114
+ if isinstance(instance, dict):
115
+ instance = JobShopInstance.from_matrices(**instance)
116
+ schedule = Schedule.from_job_sequences(instance, job_sequences)
117
+ schedule.metadata = metadata if metadata is not None else {}
118
+ return schedule
119
+
120
+ @staticmethod
121
+ def from_job_sequences(
122
+ instance: JobShopInstance,
123
+ job_sequences: list[list[int]],
124
+ ) -> Schedule:
125
+ """Creates an active schedule from a list of job sequences.
126
+
127
+ An active schedule is the optimal schedule for the given job sequences.
128
+ In other words, it is not possible to construct another schedule,
129
+ through changes in the order of processing on the machines, with at
130
+ least one operation finishing earlier and no operation finishing later.
131
+
132
+ Args:
133
+ instance:
134
+ The `JobShopInstance` object that the schedule is for.
135
+ job_sequences:
136
+ A list of lists of job ids. Each list of job ids represents the
137
+ order of operations on the machine. The machine that the list
138
+ corresponds to is determined by the index of the list.
139
+
140
+ Returns:
141
+ A `Schedule` object with the given job sequences.
142
+ """
143
+ from job_shop_lib.dispatching import Dispatcher
144
+
145
+ dispatcher = Dispatcher(instance)
146
+ dispatcher.reset()
147
+ raw_solution_deques = [deque(job_ids) for job_ids in job_sequences]
148
+
149
+ while not dispatcher.schedule.is_complete():
150
+ at_least_one_operation_scheduled = False
151
+ for machine_id, job_ids in enumerate(raw_solution_deques):
152
+ if not job_ids:
153
+ continue
154
+ job_id = job_ids[0]
155
+ operation_index = dispatcher.job_next_operation_index[job_id]
156
+ operation = instance.jobs[job_id][operation_index]
157
+ is_ready = dispatcher.is_operation_ready(operation)
158
+ if is_ready and machine_id in operation.machines:
159
+ dispatcher.dispatch(operation, machine_id)
160
+ job_ids.popleft()
161
+ at_least_one_operation_scheduled = True
162
+
163
+ if not at_least_one_operation_scheduled:
164
+ raise JobShopLibError(
165
+ "Invalid job sequences. No valid operation to schedule."
166
+ )
167
+ return dispatcher.schedule
168
+
74
169
  def reset(self):
75
170
  """Resets the schedule to an empty state."""
76
171
  self.schedule = [[] for _ in range(self.instance.num_machines)]
@@ -1,6 +1,8 @@
1
1
  """Home of the `ScheduledOperation` class."""
2
2
 
3
- from job_shop_lib import Operation
3
+ from warnings import warn
4
+
5
+ from job_shop_lib import Operation, JobShopLibError
4
6
 
5
7
 
6
8
  class ScheduledOperation:
@@ -47,7 +49,7 @@ class ScheduledOperation:
47
49
  @machine_id.setter
48
50
  def machine_id(self, value: int):
49
51
  if value not in self.operation.machines:
50
- raise ValueError(
52
+ raise JobShopLibError(
51
53
  f"Operation cannot be scheduled on machine {value}. "
52
54
  f"Valid machines are {self.operation.machines}."
53
55
  )
@@ -62,18 +64,28 @@ class ScheduledOperation:
62
64
  """
63
65
 
64
66
  if self.operation.job_id is None:
65
- raise ValueError("Operation has no job_id.")
67
+ raise JobShopLibError("Operation has no job_id.")
66
68
  return self.operation.job_id
67
69
 
68
70
  @property
69
71
  def position(self) -> int:
72
+ """Deprecated. Use `position_in_job` instead."""
73
+ warn(
74
+ "The `position` attribute is deprecated. Use `position_in_job` "
75
+ "instead. It will be removed in version 1.0.0.",
76
+ DeprecationWarning,
77
+ )
78
+ return self.position_in_job
79
+
80
+ @property
81
+ def position_in_job(self) -> int:
70
82
  """Returns the position (starting at zero) of the operation in the job.
71
83
 
72
84
  Raises:
73
85
  ValueError: If the operation has no position_in_job.
74
86
  """
75
87
  if self.operation.position_in_job is None:
76
- raise ValueError("Operation has no position.")
88
+ raise JobShopLibError("Operation has no position.")
77
89
  return self.operation.position_in_job
78
90
 
79
91
  @property
@@ -91,7 +103,7 @@ class ScheduledOperation:
91
103
  if not isinstance(value, ScheduledOperation):
92
104
  return False
93
105
  return (
94
- self.operation is value.operation
106
+ self.operation == value.operation
95
107
  and self.start_time == value.start_time
96
108
  and self.machine_id == value.machine_id
97
109
  )
@@ -27,7 +27,8 @@ def create_gif(
27
27
  instance: JobShopInstance,
28
28
  solver: DispatchingRuleSolver,
29
29
  plot_function: (
30
- Callable[[Schedule, int, list[Operation] | None], Figure] | None
30
+ Callable[[Schedule, int, list[Operation] | None, int | None], Figure]
31
+ | None
31
32
  ) = None,
32
33
  fps: int = 1,
33
34
  remove_frames: bool = True,
@@ -80,50 +81,59 @@ def plot_gantt_chart_wrapper(
80
81
  title: str | None = None,
81
82
  cmap: str = "viridis",
82
83
  show_available_operations: bool = False,
83
- ) -> Callable[[Schedule, int, list[Operation] | None], Figure]:
84
+ ) -> Callable[[Schedule, int, list[Operation] | None, int | None], Figure]:
84
85
  """Returns a function that plots a Gantt chart for an unfinished schedule.
85
86
 
86
87
  Args:
87
88
  title: The title of the Gantt chart.
88
89
  cmap: The name of the colormap to use.
90
+ show_available_operations:
91
+ Whether to show the available operations in the Gantt chart.
89
92
 
90
93
  Returns:
91
94
  A function that plots a Gantt chart for a schedule. The function takes
92
- a `Schedule` object and the makespan of the schedule as input and
93
- returns a `Figure` object.
95
+ the following arguments:
96
+ - schedule: The schedule to plot.
97
+ - makespan: The makespan of the schedule.
98
+ - available_operations: A list of available operations. If None,
99
+ the available operations are not shown.
100
+ - current_time: The current time in the schedule. If provided, a
101
+ red vertical line is plotted at this time.
94
102
  """
95
103
 
96
104
  def plot_function(
97
105
  schedule: Schedule,
98
106
  makespan: int,
99
107
  available_operations: list | None = None,
108
+ current_time: int | None = None,
100
109
  ) -> Figure:
101
110
  fig, ax = plot_gantt_chart(
102
111
  schedule, title=title, cmap_name=cmap, xlim=makespan
103
112
  )
104
113
 
105
- if not show_available_operations or available_operations is None:
106
- return fig
107
-
108
- operations_text = "\n".join(
109
- str(operation) for operation in available_operations
110
- )
111
- text = f"Available operations:\n{operations_text}"
112
- # Print the available operations at the bottom right corner
113
- # of the Gantt chart
114
- fig.text(
115
- 1.25,
116
- 0.05,
117
- text,
118
- ha="right",
119
- va="bottom",
120
- transform=ax.transAxes,
121
- bbox={
122
- "facecolor": "white",
123
- "alpha": 0.5,
124
- "boxstyle": "round,pad=0.5",
125
- },
126
- )
114
+ if show_available_operations and available_operations is not None:
115
+
116
+ operations_text = "\n".join(
117
+ str(operation) for operation in available_operations
118
+ )
119
+ text = f"Available operations:\n{operations_text}"
120
+ # Print the available operations at the bottom right corner
121
+ # of the Gantt chart
122
+ fig.text(
123
+ 1.25,
124
+ 0.05,
125
+ text,
126
+ ha="right",
127
+ va="bottom",
128
+ transform=ax.transAxes,
129
+ bbox={
130
+ "facecolor": "white",
131
+ "alpha": 0.5,
132
+ "boxstyle": "round,pad=0.5",
133
+ },
134
+ )
135
+ if current_time is not None:
136
+ ax.axvline(current_time, color="red", linestyle="--")
127
137
  return fig
128
138
 
129
139
  return plot_function
@@ -133,7 +143,9 @@ def create_gantt_chart_frames(
133
143
  frames_dir: str,
134
144
  instance: JobShopInstance,
135
145
  solver: DispatchingRuleSolver,
136
- plot_function: Callable[[Schedule, int, list[Operation] | None], Figure],
146
+ plot_function: Callable[
147
+ [Schedule, int, list[Operation] | None, int | None], Figure
148
+ ],
137
149
  plot_current_time: bool = True,
138
150
  ) -> None:
139
151
  """Creates frames of the Gantt chart for the schedule being built.
@@ -150,7 +162,8 @@ def create_gantt_chart_frames(
150
162
  should take a `Schedule` object and the makespan of the schedule as
151
163
  input and return a `Figure` object.
152
164
  plot_current_time:
153
- Whether to plot a vertical line at the current time."""
165
+ Whether to plot a vertical line at the current time.
166
+ """
154
167
  dispatcher = Dispatcher(instance, pruning_function=solver.pruning_function)
155
168
  history_tracker = HistoryTracker(dispatcher)
156
169
  makespan = solver.solve(instance, dispatcher).makespan()
@@ -160,23 +173,19 @@ def create_gantt_chart_frames(
160
173
  dispatcher.dispatch(
161
174
  scheduled_operation.operation, scheduled_operation.machine_id
162
175
  )
176
+ current_time = (
177
+ None if not plot_current_time else dispatcher.current_time()
178
+ )
163
179
  fig = plot_function(
164
180
  dispatcher.schedule,
165
181
  makespan,
166
182
  dispatcher.available_operations(),
183
+ current_time,
167
184
  )
168
- current_time = (
169
- None if not plot_current_time else dispatcher.current_time()
170
- )
171
- _save_frame(fig, frames_dir, i, current_time)
172
-
185
+ _save_frame(fig, frames_dir, i)
173
186
 
174
- def _save_frame(
175
- figure: Figure, frames_dir: str, number: int, current_time: int | None
176
- ) -> None:
177
- if current_time is not None:
178
- figure.gca().axvline(current_time, color="red", linestyle="--")
179
187
 
188
+ def _save_frame(figure: Figure, frames_dir: str, number: int) -> None:
180
189
  figure.savefig(f"{frames_dir}/frame_{number:02d}.png", bbox_inches="tight")
181
190
  plt.close(figure)
182
191
 
@@ -65,7 +65,7 @@ def _plot_machine_schedules(
65
65
  ) -> dict[int, Patch]:
66
66
  """Plots the schedules for each machine."""
67
67
  max_job_id = schedule.instance.num_jobs - 1
68
- cmap = plt.cm.get_cmap(cmap_name, max_job_id + 1)
68
+ cmap = plt.get_cmap(cmap_name, max_job_id + 1)
69
69
  norm = Normalize(vmin=0, vmax=max_job_id)
70
70
  legend_handles = {}
71
71
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)
5
5
  License: MIT
6
6
  Author: Pabloo22
@@ -15,7 +15,8 @@ Provides-Extra: pygraphviz
15
15
  Requires-Dist: imageio (>=2,<3)
16
16
  Requires-Dist: matplotlib (>=3,<4)
17
17
  Requires-Dist: networkx (>=3,<4)
18
- Requires-Dist: ortools (>=9,<10)
18
+ Requires-Dist: numpy (>=1.26.4,<2.0.0)
19
+ Requires-Dist: ortools (>=9.9,<9.10)
19
20
  Requires-Dist: pyarrow (>=15.0.0,<16.0.0)
20
21
  Requires-Dist: pygraphviz (>=1.12,<2.0) ; extra == "pygraphviz"
21
22
  Description-Content-Type: text/markdown
@@ -83,7 +84,7 @@ ft06 = load_benchmark_instance("ft06")
83
84
  ```
84
85
 
85
86
  The module `benchmarks` contains functions to load the instances from the file and return them as `JobShopInstance` objects without having to download them
86
- manually. The instances are stored in [benchmark_instances.json](job_shop_lib/benchmarks/benchmark_instances.json).
87
+ manually.
87
88
 
88
89
  The contributions to this benchmark dataset are as follows:
89
90
 
@@ -171,13 +172,15 @@ class DispatchingRule(str, Enum):
171
172
  We can visualize the solution with a `DispatchingRuleSolver` as a gif:
172
173
 
173
174
  ```python
174
- from job_shop_lib.visualization import create_gif, get_plot_function
175
+ from job_shop_lib.visualization import create_gif, plot_gantt_chart_wrapper
175
176
  from job_shop_lib.dispatching import DispatchingRuleSolver, DispatchingRule
176
177
 
177
178
  plt.style.use("ggplot")
178
179
 
179
180
  mwkr_solver = DispatchingRuleSolver("most_work_remaining")
180
- plot_function = get_plot_function(title="Solution with Most Work Remaining Rule")
181
+ plot_function = plot_gantt_chart_wrapper(
182
+ title="Solution with Most Work Remaining Rule"
183
+ )
181
184
  create_gif(
182
185
  gif_path="ft06_optimized.gif",
183
186
  instance=ft06,
@@ -350,3 +353,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
350
353
  Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
351
354
 
352
355
  - Park, Junyoung, Sanjar Bakhtiyar, and Jinkyoo Park. "ScheduleNet: Learn to solve multi-agent scheduling problems with reinforcement learning." arXiv preprint arXiv:2106.03051, 2021.
356
+
@@ -6,10 +6,21 @@ job_shop_lib/benchmarking/load_benchmark.py,sha256=CjiSALutgWcfD-SDU6w9WO3udvPVp
6
6
  job_shop_lib/cp_sat/__init__.py,sha256=DqrF9IewFMkVB5BhFOHhlJvG6w6BW4ecxBXySunGLoU,97
7
7
  job_shop_lib/cp_sat/ortools_solver.py,sha256=zsISUQy0dQvn7bmUsAQBCe-V92CFskJHkSfngSP4KSg,8130
8
8
  job_shop_lib/dispatching/__init__.py,sha256=xk6NjndZ4-EH5G_fGSEX4LQEXL53TRYn5dKEb5uFggI,1568
9
- job_shop_lib/dispatching/dispatcher.py,sha256=O9iqytBveJT3WMTELkpNy8IeNiI2Yy8uxbP2ruLLT1A,12582
9
+ job_shop_lib/dispatching/dispatcher.py,sha256=3WdShJtVMP4ZBeoOIegelTXziJLpEjtonAku21yqr20,19299
10
10
  job_shop_lib/dispatching/dispatching_rule_solver.py,sha256=fbNfSclH6Jw1F-QGY1oxAj9wm2hHhJHGnsF2HateXX8,4669
11
11
  job_shop_lib/dispatching/dispatching_rules.py,sha256=SIDkPx_1uTkM0loEqGMqotLBBSaGi1gH0WS85GXrT_I,5557
12
12
  job_shop_lib/dispatching/factories.py,sha256=ldyIbz3QuLuDkrqbgJXV6YoM6AV6CKyHu8z4hXLG2Vo,7267
13
+ job_shop_lib/dispatching/feature_observers/__init__.py,sha256=j7GXFCKK0mLjGc3M6bw2vePZaLyou_9BUjlHtAMriHc,1023
14
+ job_shop_lib/dispatching/feature_observers/composite_feature_observer.py,sha256=wLd5yirDekg_pFrBPWIyxKou8htimvKhtDbz0cJ8SKE,3302
15
+ job_shop_lib/dispatching/feature_observers/duration_observer.py,sha256=EMSfRsWkx94VeOF6v1f2s73hrEQ696vkFoWVVw5tWZ8,3804
16
+ job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py,sha256=pKrxaLOPHMnf-eniDhkPWVzzRpKLnpbXAeORqEmsrp0,6296
17
+ job_shop_lib/dispatching/feature_observers/factory.py,sha256=XF5spNp8T-dnPPnpWQ5dxuG7vrHLf-Wb8MP1tn4_BXA,1941
18
+ job_shop_lib/dispatching/feature_observers/feature_observer.py,sha256=q2i50LOhgjaQtu_0-3VlSH64uHcf73JddjcGijeKCVk,3646
19
+ job_shop_lib/dispatching/feature_observers/is_completed_observer.py,sha256=6UCxp-QLXxbw_1nppQJuG_qZZvjtGugYTxQdPTcIcgo,4086
20
+ job_shop_lib/dispatching/feature_observers/is_ready_observer.py,sha256=km75Pa8vd55grjvr7X-8e0K9vfxVWfms1ea6kAesKLo,1405
21
+ job_shop_lib/dispatching/feature_observers/is_scheduled_observer.py,sha256=PeLxPVLJX_TP4TG8ViEQFR8WS43wIp6CqyuapM8lIt8,1477
22
+ job_shop_lib/dispatching/feature_observers/position_in_job_observer.py,sha256=PEC-WwcyeHL8WOACElImDtAAoaf7MroaD9x5QlQddVE,1344
23
+ job_shop_lib/dispatching/feature_observers/remaining_operations_observer.py,sha256=yVde9gC0cheFNCoPa1lFgGSmzyew-zOGA98nZpBBw1I,1891
13
24
  job_shop_lib/dispatching/history_tracker.py,sha256=3jSh7pKEGiOcEK6bXK8AQJK4NtASxTknRjmHRKenxt8,649
14
25
  job_shop_lib/dispatching/pruning_functions.py,sha256=d94_uBHuESp4NSf_jBk1q8eBCfTPuU9meiL3StiqJiA,4378
15
26
  job_shop_lib/exceptions.py,sha256=0Wla1lK6E2u1o3t2hJj9hUwyoJ-1ebkXd42GdXFAhV0,899
@@ -18,20 +29,20 @@ job_shop_lib/generators/basic_generator.py,sha256=pbRDQWC2mnHU0dbc-T8wkdwVeJPlRn
18
29
  job_shop_lib/generators/transformations.py,sha256=FI2qHrETATJUrQP3-RYhZAQ5boyEZ0CF2StDbacBej8,5290
19
30
  job_shop_lib/graphs/__init__.py,sha256=mWyF0MypyYfvFhy2F93BJkFIVsxS_0ZqvPuc29B7TJg,1454
20
31
  job_shop_lib/graphs/build_agent_task_graph.py,sha256=ktj-oNLUPmWHfL81EVyaoF4hXClWYfnN7oG2Nn4pOsg,7128
21
- job_shop_lib/graphs/build_disjunctive_graph.py,sha256=IRMBtHw8aru5rYGz796-dc6QyaLJFh4LlPlN_BPSq5c,2877
32
+ job_shop_lib/graphs/build_disjunctive_graph.py,sha256=z1jiuTTaWPJZj-vSZdo064quGx4LEDKjtZIb1FieZW4,3705
22
33
  job_shop_lib/graphs/constants.py,sha256=dqPF--okue5sF70Iv-YR14QKFx4pxPwT2dL1Rh5jylM,374
23
34
  job_shop_lib/graphs/job_shop_graph.py,sha256=B0buqcg7US6UvIRWsoY8_FwqzPa_nVjnBu7hPIrygUo,7404
24
35
  job_shop_lib/graphs/node.py,sha256=FrSndtvqgRbN69jIcU6q1TkBh-LOGg8sxxYjDZqCcf4,5613
25
- job_shop_lib/job_shop_instance.py,sha256=ZB0NOcTvGSq0zmmxiDceaC0DH9ljpJXD0hfKOmP0jcE,12801
26
- job_shop_lib/operation.py,sha256=bZclBeyge71Avm9ArwvGuBKZp5Idw5EUm6m35jav0C4,3924
27
- job_shop_lib/schedule.py,sha256=1xzue2ro927VZw9IWg_tlBLZ7kDbN091aOW6ZMEjOYQ,6509
28
- job_shop_lib/scheduled_operation.py,sha256=EfG5AcrspjO3erhM2ejlSOtYKNnaNTsLEu2gu2N3FxA,3127
36
+ job_shop_lib/job_shop_instance.py,sha256=awEZ-xKM4yPlD4gE8SdfQdt68CWX_R3IebeVY8ST4bs,16376
37
+ job_shop_lib/operation.py,sha256=S61x0xgu09JLwrRp7syd1P2psbl0ByGuK_hHoHp4ng8,3916
38
+ job_shop_lib/schedule.py,sha256=aODGwMv9slFIqOTCz2hF_EIpXhddz8-iAH5gSzGO5G8,10393
39
+ job_shop_lib/scheduled_operation.py,sha256=qzXzat1dQBbQ-sLyoG1iXbF9eWbdFeZDFjhAFVavHPk,3526
29
40
  job_shop_lib/visualization/__init__.py,sha256=Kxjk3ERYXPAHR72nkD92gFdJltSLA2kxLZrlZzZJS8o,693
30
41
  job_shop_lib/visualization/agent_task_graph.py,sha256=G-c9eiawz6m9sdnDM1r-ZHz6K-gYDIAreHpb6pkYE7w,8284
31
- job_shop_lib/visualization/create_gif.py,sha256=KrwMpSYvSCsL5Ld3taiNHSl_QDrODLpqM-MKQG_C2oU,6674
42
+ job_shop_lib/visualization/create_gif.py,sha256=aUB_2ChyFNo4KuKiQl2ANYmVB5NFcGb7pxKeqr0CVJQ,7186
32
43
  job_shop_lib/visualization/disjunctive_graph.py,sha256=pg4KG9BfQbnBPnXYgbyPGe0AuHSmhYqPeqWYAf_spWQ,5905
33
- job_shop_lib/visualization/gantt_chart.py,sha256=OyBMBnjSsRC769qXimJ3IIQWlssgPfx-nlVeSeU5sWY,4415
34
- job_shop_lib-0.3.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
35
- job_shop_lib-0.3.0.dist-info/METADATA,sha256=DPjUe3anmq-s0y-_09o8uBjvFTmHqVt5N_AI3BPu1AM,12624
36
- job_shop_lib-0.3.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
37
- job_shop_lib-0.3.0.dist-info/RECORD,,
44
+ job_shop_lib/visualization/gantt_chart.py,sha256=B9sn4XrEUqgQhRKju-1VUG5R67AZXRu7jbrtA8VcndU,4412
45
+ job_shop_lib-0.5.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
46
+ job_shop_lib-0.5.0.dist-info/METADATA,sha256=a_AtBz1LwmfrEoIVWXepewPFx2Pa1QiFJt857kCGncw,12582
47
+ job_shop_lib-0.5.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
48
+ job_shop_lib-0.5.0.dist-info/RECORD,,