job-shop-lib 0.4.0__py3-none-any.whl → 0.5.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. job_shop_lib/dispatching/dispatcher.py +219 -51
  2. job_shop_lib/dispatching/feature_observers/__init__.py +28 -0
  3. job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +87 -0
  4. job_shop_lib/dispatching/feature_observers/duration_observer.py +95 -0
  5. job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +156 -0
  6. job_shop_lib/dispatching/feature_observers/factory.py +58 -0
  7. job_shop_lib/dispatching/feature_observers/feature_observer.py +113 -0
  8. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +98 -0
  9. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +40 -0
  10. job_shop_lib/dispatching/feature_observers/is_scheduled_observer.py +34 -0
  11. job_shop_lib/dispatching/feature_observers/position_in_job_observer.py +39 -0
  12. job_shop_lib/dispatching/feature_observers/remaining_operations_observer.py +54 -0
  13. job_shop_lib/generation/__init__.py +11 -0
  14. job_shop_lib/generation/general_instance_generator.py +169 -0
  15. job_shop_lib/generation/instance_generator.py +122 -0
  16. job_shop_lib/generation/transformations.py +164 -0
  17. job_shop_lib/generators/__init__.py +2 -1
  18. job_shop_lib/generators/basic_generator.py +3 -0
  19. job_shop_lib/graphs/build_disjunctive_graph.py +20 -0
  20. job_shop_lib/job_shop_instance.py +101 -0
  21. job_shop_lib/visualization/create_gif.py +47 -38
  22. job_shop_lib/visualization/gantt_chart.py +1 -1
  23. {job_shop_lib-0.4.0.dist-info → job_shop_lib-0.5.1.dist-info}/METADATA +9 -5
  24. job_shop_lib-0.5.1.dist-info/RECORD +52 -0
  25. job_shop_lib-0.4.0.dist-info/RECORD +0 -37
  26. {job_shop_lib-0.4.0.dist-info → job_shop_lib-0.5.1.dist-info}/LICENSE +0 -0
  27. {job_shop_lib-0.4.0.dist-info → job_shop_lib-0.5.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,122 @@
1
+ import abc
2
+
3
+ import random
4
+ from typing import Iterator
5
+
6
+ from job_shop_lib import JobShopInstance
7
+
8
+
9
+ class InstanceGenerator(abc.ABC):
10
+ """Common interface for all generators.
11
+
12
+ The class supports both single instance generation and iteration over
13
+ multiple instances, controlled by the `iteration_limit` parameter. It
14
+ implements the iterator protocol, allowing it to be used in a `for` loop.
15
+
16
+ Note:
17
+ When used as an iterator, the generator will produce instances until it
18
+ reaches the specified `iteration_limit`. If `iteration_limit` is None,
19
+ it will continue indefinitely.
20
+
21
+ Attributes:
22
+ num_jobs_range:
23
+ The range of the number of jobs to generate. If a single
24
+ int is provided, it is used as both the minimum and maximum.
25
+ duration_range:
26
+ The range of durations for each operation.
27
+ num_machines_range:
28
+ The range of the number of machines available. If a
29
+ single int is provided, it is used as both the minimum and maximum.
30
+ name_suffix:
31
+ A suffix to append to each instance's name for identification.
32
+ seed:
33
+ Seed for the random number generator to ensure reproducibility.
34
+ """
35
+
36
+ def __init__( # pylint: disable=too-many-arguments
37
+ self,
38
+ num_jobs: int | tuple[int, int] = (10, 20),
39
+ num_machines: int | tuple[int, int] = (5, 10),
40
+ duration_range: tuple[int, int] = (1, 99),
41
+ name_suffix: str = "generated_instance",
42
+ seed: int | None = None,
43
+ iteration_limit: int | None = None,
44
+ ):
45
+ """Initializes the instance generator with the given parameters.
46
+
47
+ Args:
48
+ num_jobs:
49
+ The range of the number of jobs to generate.
50
+ num_machines:
51
+ The range of the number of machines available.
52
+ duration_range:
53
+ The range of durations for each operation.
54
+ name_suffix:
55
+ Suffix for instance names.
56
+ seed:
57
+ Seed for the random number generator.
58
+ iteration_limit:
59
+ Maximum number of instances to generate in iteration mode.
60
+ """
61
+
62
+ if isinstance(num_jobs, int):
63
+ num_jobs = (num_jobs, num_jobs)
64
+ if isinstance(num_machines, int):
65
+ num_machines = (num_machines, num_machines)
66
+ if seed is not None:
67
+ random.seed(seed)
68
+
69
+ self.num_jobs_range = num_jobs
70
+ self.num_machines_range = num_machines
71
+ self.duration_range = duration_range
72
+ self.name_suffix = name_suffix
73
+
74
+ self._counter = 0
75
+ self._current_iteration = 0
76
+ self._iteration_limit = iteration_limit
77
+
78
+ @abc.abstractmethod
79
+ def generate(self) -> JobShopInstance:
80
+ """Generates a single job shop instance"""
81
+
82
+ def _next_name(self) -> str:
83
+ self._counter += 1
84
+ return f"{self.name_suffix}_{self._counter}"
85
+
86
+ def __iter__(self) -> Iterator[JobShopInstance]:
87
+ self._current_iteration = 0
88
+ return self
89
+
90
+ def __next__(self) -> JobShopInstance:
91
+ if (
92
+ self._iteration_limit is not None
93
+ and self._current_iteration >= self._iteration_limit
94
+ ):
95
+ raise StopIteration
96
+ self._current_iteration += 1
97
+ return self.generate()
98
+
99
+ def __len__(self) -> int:
100
+ if self._iteration_limit is None:
101
+ raise ValueError("Iteration limit is not set.")
102
+ return self._iteration_limit
103
+
104
+ @property
105
+ def max_num_jobs(self) -> int:
106
+ """Returns the maximum number of jobs that can be generated."""
107
+ return self.num_jobs_range[1]
108
+
109
+ @property
110
+ def min_num_jobs(self) -> int:
111
+ """Returns the minimum number of jobs that can be generated."""
112
+ return self.num_jobs_range[0]
113
+
114
+ @property
115
+ def max_num_machines(self) -> int:
116
+ """Returns the maximum number of machines that can be generated."""
117
+ return self.num_machines_range[1]
118
+
119
+ @property
120
+ def min_num_machines(self) -> int:
121
+ """Returns the minimum number of machines that can be generated."""
122
+ return self.num_machines_range[0]
@@ -0,0 +1,164 @@
1
+ """Classes for generating transformed JobShopInstance objects."""
2
+
3
+ import abc
4
+ import copy
5
+ import random
6
+
7
+ from job_shop_lib import JobShopInstance, Operation
8
+
9
+
10
+ class Transformation(abc.ABC):
11
+ """Base class for transformations applied to JobShopInstance objects."""
12
+
13
+ def __init__(self, suffix: str = ""):
14
+ self.suffix = suffix
15
+ self.counter = 0
16
+
17
+ @abc.abstractmethod
18
+ def apply(self, instance: JobShopInstance) -> JobShopInstance:
19
+ """Applies the transformation to a given JobShopInstance.
20
+
21
+ Args:
22
+ instance: The JobShopInstance to transform.
23
+
24
+ Returns:
25
+ A new JobShopInstance with the transformation applied.
26
+ """
27
+
28
+ def __call__(self, instance: JobShopInstance) -> JobShopInstance:
29
+ instance = self.apply(instance)
30
+ suffix = f"{self.suffix}_id={self.counter}"
31
+ instance.name += suffix
32
+ self.counter += 1
33
+ return instance
34
+
35
+
36
+ # pylint: disable=too-few-public-methods
37
+ class RemoveMachines(Transformation):
38
+ """Removes operations associated with randomly selected machines until
39
+ there are exactly num_machines machines left."""
40
+
41
+ def __init__(self, num_machines: int, suffix: str | None = None):
42
+ if suffix is None:
43
+ suffix = f"_machines={num_machines}"
44
+ super().__init__(suffix=suffix)
45
+ self.num_machines = num_machines
46
+
47
+ def apply(self, instance: JobShopInstance) -> JobShopInstance:
48
+ if instance.num_machines <= self.num_machines:
49
+ return instance # No need to remove machines
50
+
51
+ # Select machine indices to keep
52
+ machines_to_keep = set(
53
+ random.sample(range(instance.num_machines), self.num_machines)
54
+ )
55
+
56
+ # Re-index machines
57
+ machine_reindex_map = {
58
+ old_id: new_id
59
+ for new_id, old_id in enumerate(sorted(machines_to_keep))
60
+ }
61
+
62
+ new_jobs = []
63
+ for job in instance.jobs:
64
+ # Keep operations whose machine_id is in machines_to_keep and
65
+ # re-index them
66
+ new_jobs.append(
67
+ [
68
+ Operation(machine_reindex_map[op.machine_id], op.duration)
69
+ for op in job
70
+ if op.machine_id in machines_to_keep
71
+ ]
72
+ )
73
+
74
+ return JobShopInstance(new_jobs, instance.name)
75
+
76
+
77
+ # pylint: disable=too-few-public-methods
78
+ class AddDurationNoise(Transformation):
79
+ """Adds uniform integer noise to operation durations."""
80
+
81
+ def __init__(
82
+ self,
83
+ min_duration: int = 1,
84
+ max_duration: int = 100,
85
+ noise_level: int = 10,
86
+ suffix: str | None = None,
87
+ ):
88
+ if suffix is None:
89
+ suffix = f"_noise={noise_level}"
90
+ super().__init__(suffix=suffix)
91
+ self.min_duration = min_duration
92
+ self.max_duration = max_duration
93
+ self.noise_level = noise_level
94
+
95
+ def apply(self, instance: JobShopInstance) -> JobShopInstance:
96
+ new_jobs = []
97
+ for job in instance.jobs:
98
+ new_job = []
99
+ for op in job:
100
+ noise = random.randint(-self.noise_level, self.noise_level)
101
+ new_duration = max(
102
+ self.min_duration,
103
+ min(self.max_duration, op.duration + noise),
104
+ )
105
+
106
+ new_job.append(Operation(op.machine_id, new_duration))
107
+ new_jobs.append(new_job)
108
+
109
+ return JobShopInstance(new_jobs, instance.name)
110
+
111
+
112
+ class RemoveJobs(Transformation):
113
+ """Removes jobs randomly until the number of jobs is within a specified
114
+ range."""
115
+
116
+ def __init__(
117
+ self,
118
+ min_jobs: int,
119
+ max_jobs: int,
120
+ target_jobs: int | None = None,
121
+ suffix: str | None = None,
122
+ ):
123
+ """
124
+ Args:
125
+ min_jobs: The minimum number of jobs to remain in the instance.
126
+ max_jobs: The maximum number of jobs to remain in the instance.
127
+ target_jobs: If specified, the number of jobs to remain in the
128
+ instance. Overrides min_jobs and max_jobs.
129
+ """
130
+ if suffix is None:
131
+ suffix = f"_jobs={min_jobs}-{max_jobs}"
132
+ super().__init__(suffix=suffix)
133
+ self.min_jobs = min_jobs
134
+ self.max_jobs = max_jobs
135
+ self.target_jobs = target_jobs
136
+
137
+ def apply(self, instance: JobShopInstance) -> JobShopInstance:
138
+ if self.target_jobs is None:
139
+ target_jobs = random.randint(self.min_jobs, self.max_jobs)
140
+ else:
141
+ target_jobs = self.target_jobs
142
+ new_jobs = copy.deepcopy(instance.jobs)
143
+
144
+ while len(new_jobs) > target_jobs:
145
+ new_jobs.pop(random.randint(0, len(new_jobs) - 1))
146
+
147
+ return JobShopInstance(new_jobs, instance.name)
148
+
149
+ @staticmethod
150
+ def remove_job(
151
+ instance: JobShopInstance, job_index: int
152
+ ) -> JobShopInstance:
153
+ """Removes a specific job from the instance.
154
+
155
+ Args:
156
+ instance: The JobShopInstance from which to remove the job.
157
+ job_index: The index of the job to remove.
158
+
159
+ Returns:
160
+ A new JobShopInstance with the specified job removed.
161
+ """
162
+ new_jobs = copy.deepcopy(instance.jobs)
163
+ new_jobs.pop(job_index)
164
+ return JobShopInstance(new_jobs, instance.name)
@@ -1,4 +1,5 @@
1
- """Package for generating job shop instances."""
1
+ """DEPRECATED: Use `job_shop_lib.generation` instead. It will be removed in
2
+ version 1.0.0."""
2
3
 
3
4
  from job_shop_lib.generators.basic_generator import BasicGenerator
4
5
 
@@ -9,6 +9,9 @@ from job_shop_lib import JobShopInstance, Operation
9
9
  class BasicGenerator: # pylint: disable=too-many-instance-attributes
10
10
  """Generates instances for job shop problems.
11
11
 
12
+ DEPRECATED: Class moved to `job_shop_lib.generation` and renamed to
13
+ `GeneralInstanceGenerator`. This class will be removed in version 1.0.0.
14
+
12
15
  This class is designed to be versatile, enabling the creation of various
13
16
  job shop instances without the need for multiple dedicated classes.
14
17
 
@@ -23,6 +23,26 @@ from job_shop_lib.graphs import JobShopGraph, EdgeType, NodeType, Node
23
23
 
24
24
 
25
25
  def build_disjunctive_graph(instance: JobShopInstance) -> JobShopGraph:
26
+ """Builds and returns a disjunctive graph for the given job shop instance.
27
+
28
+ This function creates a complete disjunctive graph from a JobShopInstance.
29
+ It starts by initializing a JobShopGraph object and proceeds by adding
30
+ disjunctive edges between operations using the same machine, conjunctive
31
+ edges between successive operations in the same job, and finally, special
32
+ source and sink nodes with their respective edges to and from all other
33
+ operations.
34
+
35
+ Edges have a "type" attribute indicating whether they are disjunctive or
36
+ conjunctive.
37
+
38
+ Args:
39
+ instance (JobShopInstance): The job shop instance for which to build
40
+ the graph.
41
+
42
+ Returns:
43
+ JobShopGraph: A JobShopGraph object representing the disjunctive graph
44
+ of the job shop scheduling problem.
45
+ """
26
46
  graph = JobShopGraph(instance)
27
47
  add_disjunctive_edges(graph)
28
48
  add_conjunctive_edges(graph)
@@ -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
@@ -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.4.0
3
+ Version: 0.5.1
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
+