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

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.
@@ -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,,