job-shop-lib 1.0.0a2__py3-none-any.whl → 1.0.0a3__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,15 +20,27 @@ class JobShopInstance:
20
20
  computations.
21
21
 
22
22
  Attributes:
23
- jobs:
23
+ jobs (list[list[Operation]]):
24
24
  A list of lists of operations. Each list of operations represents
25
25
  a job, and the operations are ordered by their position in the job.
26
26
  The `job_id`, `position_in_job`, and `operation_id` attributes of
27
27
  the operations are set when the instance is created.
28
- name:
28
+ name (str):
29
29
  A string with the name of the instance.
30
- metadata:
30
+ metadata (dict[str, Any]):
31
31
  A dictionary with additional information about the instance.
32
+
33
+ Args:
34
+ jobs:
35
+ A list of lists of operations. Each list of operations
36
+ represents a job, and the operations are ordered by their
37
+ position in the job. The `job_id`, `position_in_job`, and
38
+ `operation_id` attributes of the operations are set when the
39
+ instance is created.
40
+ name:
41
+ A string with the name of the instance.
42
+ **metadata:
43
+ Additional information about the instance.
32
44
  """
33
45
 
34
46
  def __init__(
@@ -37,24 +49,10 @@ class JobShopInstance:
37
49
  name: str = "JobShopInstance",
38
50
  **metadata: Any,
39
51
  ):
40
- """Initializes the instance based on a list of lists of operations.
41
-
42
- Args:
43
- jobs:
44
- A list of lists of operations. Each list of operations
45
- represents a job, and the operations are ordered by their
46
- position in the job. The `job_id`, `position_in_job`, and
47
- `operation_id` attributes of the operations are set when the
48
- instance is created.
49
- name:
50
- A string with the name of the instance.
51
- **metadata:
52
- Additional information about the instance.
53
- """
54
- self.jobs = jobs
52
+ self.jobs: list[list[Operation]] = jobs
55
53
  self.set_operation_attributes()
56
- self.name = name
57
- self.metadata = metadata
54
+ self.name: str = name
55
+ self.metadata: dict[str, Any] = metadata
58
56
 
59
57
  def set_operation_attributes(self):
60
58
  """Sets the job_id and position of each operation."""
@@ -51,8 +51,10 @@ class Operation:
51
51
  }
52
52
 
53
53
  def __init__(self, machines: int | list[int], duration: int):
54
- self.machines = [machines] if isinstance(machines, int) else machines
55
- self.duration = duration
54
+ self.machines: list[int] = (
55
+ [machines] if isinstance(machines, int) else machines
56
+ )
57
+ self.duration: int = duration
56
58
 
57
59
  # Defined outside the class by the JobShopInstance class:
58
60
  self.job_id: int = -1
@@ -64,8 +66,8 @@ class Operation:
64
66
  """Returns the id of the machine associated with the operation.
65
67
 
66
68
  Raises:
67
- UninitializedAttributeError: If the operation has multiple machines
68
- in its list.
69
+ UninitializedAttributeError:
70
+ If the operation has multiple machines in its list.
69
71
  """
70
72
  if len(self.machines) > 1:
71
73
  raise UninitializedAttributeError(
job_shop_lib/_schedule.py CHANGED
@@ -46,7 +46,7 @@ class Schedule:
46
46
  self,
47
47
  instance: JobShopInstance,
48
48
  schedule: list[list[ScheduledOperation]] | None = None,
49
- **metadata,
49
+ **metadata: Any,
50
50
  ):
51
51
  """Initializes the object with the given instance and schedule.
52
52
 
@@ -65,9 +65,9 @@ class Schedule:
65
65
 
66
66
  Schedule.check_schedule(schedule)
67
67
 
68
- self.instance = instance
68
+ self.instance: JobShopInstance = instance
69
69
  self._schedule = schedule
70
- self.metadata = metadata
70
+ self.metadata: dict[str, Any] = metadata
71
71
 
72
72
  def __repr__(self) -> str:
73
73
  return str(self.schedule)
@@ -31,8 +31,8 @@ class ScheduledOperation:
31
31
  If the given machine_id is not in the list of valid machines
32
32
  for the operation.
33
33
  """
34
- self.operation = operation
35
- self.start_time = start_time
34
+ self.operation: Operation = operation
35
+ self.start_time: int = start_time
36
36
  self._machine_id = machine_id
37
37
  self.machine_id = machine_id # Validate machine_id
38
38
 
@@ -32,6 +32,8 @@ from ._ready_operation_filters import (
32
32
  filter_dominated_operations,
33
33
  filter_non_immediate_machines,
34
34
  ReadyOperationsFilter,
35
+ filter_non_idle_machines,
36
+ filter_non_immediate_operations,
35
37
  )
36
38
  from ._dispatcher_observer_config import DispatcherObserverConfig
37
39
  from ._factories import (
@@ -53,4 +55,6 @@ __all__ = [
53
55
  "DispatcherObserverConfig",
54
56
  "UnscheduledOperationsObserver",
55
57
  "ReadyOperationsFilter",
58
+ "filter_non_idle_machines",
59
+ "filter_non_immediate_operations",
56
60
  ]
@@ -156,26 +156,30 @@ class Dispatcher:
156
156
  responsible for scheduling the operations on the machines and keeping
157
157
  track of the next available time for each machine and job.
158
158
 
159
- Attributes:
159
+ Args:
160
160
  instance:
161
- The instance of the job shop problem to be scheduled.
162
- schedule:
163
- The schedule of operations on machines.
161
+ The instance of the job shop problem to be solved.
164
162
  ready_operations_filter:
165
- A function that filters out operations that are not ready to be
166
- scheduled.
163
+ A function that filters out operations that are not ready to
164
+ be scheduled. The function should take the dispatcher and a
165
+ list of operations as input and return a list of operations
166
+ that are ready to be scheduled. If ``None``, no filtering is
167
+ done.
167
168
  """
168
169
 
169
- __slots__ = (
170
- "instance",
171
- "schedule",
172
- "_machine_next_available_time",
173
- "_job_next_operation_index",
174
- "_job_next_available_time",
175
- "ready_operations_filter",
176
- "subscribers",
177
- "_cache",
178
- )
170
+ __slots__ = {
171
+ "instance": "The instance of the job shop problem to be scheduled.",
172
+ "schedule": "The schedule of operations on machines.",
173
+ "_machine_next_available_time": "",
174
+ "_job_next_operation_index": "",
175
+ "_job_next_available_time": "",
176
+ "ready_operations_filter": (
177
+ "A function that filters out operations that are not ready to be "
178
+ "scheduled."
179
+ ),
180
+ "subscribers": "A list of observers subscribed to the dispatcher.",
181
+ "_cache": "A dictionary to cache the results of the cached methods.",
182
+ }
179
183
 
180
184
  def __init__(
181
185
  self,
@@ -184,18 +188,6 @@ class Dispatcher:
184
188
  Callable[[Dispatcher, list[Operation]], list[Operation]] | None
185
189
  ) = None,
186
190
  ) -> None:
187
- """Initializes the object with the given instance.
188
-
189
- Args:
190
- instance:
191
- The instance of the job shop problem to be solved.
192
- ready_operations_filter:
193
- A function that filters out operations that are not ready to
194
- be scheduled. The function should take the dispatcher and a
195
- list of operations as input and return a list of operations
196
- that are ready to be scheduled. If ``None``, no filtering is
197
- done.
198
- """
199
191
 
200
192
  self.instance = instance
201
193
  self.schedule = Schedule(self.instance)
@@ -371,7 +363,7 @@ class Dispatcher:
371
363
  The current time is the minimum start time of the available
372
364
  operations.
373
365
  """
374
- available_operations = self.ready_operations()
366
+ available_operations = self.available_operations()
375
367
  current_time = self.min_start_time(available_operations)
376
368
  return current_time
377
369
 
@@ -387,7 +379,7 @@ class Dispatcher:
387
379
  return int(min_start_time)
388
380
 
389
381
  @_dispatcher_cache
390
- def ready_operations(self) -> list[Operation]:
382
+ def available_operations(self) -> list[Operation]:
391
383
  """Returns a list of available operations for processing, optionally
392
384
  filtering out operations using the filter function.
393
385
 
@@ -443,7 +435,7 @@ class Dispatcher:
443
435
  @_dispatcher_cache
444
436
  def available_machines(self) -> list[int]:
445
437
  """Returns the list of ready machines."""
446
- available_operations = self.ready_operations()
438
+ available_operations = self.available_operations()
447
439
  available_machines = set()
448
440
  for operation in available_operations:
449
441
  available_machines.update(operation.machines)
@@ -452,7 +444,7 @@ class Dispatcher:
452
444
  @_dispatcher_cache
453
445
  def available_jobs(self) -> list[int]:
454
446
  """Returns the list of ready jobs."""
455
- available_operations = self.ready_operations()
447
+ available_operations = self.available_operations()
456
448
  available_jobs = set(
457
449
  operation.job_id for operation in available_operations
458
450
  )
@@ -13,6 +13,8 @@ from job_shop_lib.dispatching import (
13
13
  Dispatcher,
14
14
  filter_dominated_operations,
15
15
  filter_non_immediate_machines,
16
+ filter_non_idle_machines,
17
+ filter_non_immediate_operations,
16
18
  ReadyOperationsFilter,
17
19
  )
18
20
 
@@ -27,6 +29,8 @@ class ReadyOperationsFilterType(str, Enum):
27
29
 
28
30
  DOMINATED_OPERATIONS = "dominated_operations"
29
31
  NON_IMMEDIATE_MACHINES = "non_immediate_machines"
32
+ NON_IDLE_MACHINES = "non_idle_machines"
33
+ NON_IMMEDIATE_OPERATIONS = "non_immediate_operations"
30
34
 
31
35
 
32
36
  def create_composite_operation_filter(
@@ -114,6 +118,10 @@ def ready_operations_filter_factory(
114
118
  ReadyOperationsFilterType.NON_IMMEDIATE_MACHINES: (
115
119
  filter_non_immediate_machines
116
120
  ),
121
+ ReadyOperationsFilterType.NON_IDLE_MACHINES: filter_non_idle_machines,
122
+ ReadyOperationsFilterType.NON_IMMEDIATE_OPERATIONS: (
123
+ filter_non_immediate_operations
124
+ ),
117
125
  }
118
126
 
119
127
  if filter_name not in filtering_strategies:
@@ -15,6 +15,86 @@ ReadyOperationsFilter = Callable[
15
15
  ]
16
16
 
17
17
 
18
+ def filter_non_idle_machines(
19
+ dispatcher: Dispatcher, operations: list[Operation]
20
+ ) -> list[Operation]:
21
+ """Filters out all the operations associated with non-idle machines.
22
+
23
+ A machine is considered idle if there are no ongoing operations
24
+ currently scheduled on it. This filter removes operations that are
25
+ associated with machines that are busy (i.e., have at least one
26
+ uncompleted operation).
27
+
28
+ Utilizes :meth:``Dispatcher.ongoing_operations()`` to determine machine
29
+ statuses.
30
+
31
+ Args:
32
+ dispatcher: The dispatcher object.
33
+ operations: The list of operations to filter.
34
+
35
+ Returns:
36
+ The list of operations that are associated with idle machines.
37
+ """
38
+ current_time = dispatcher.min_start_time(operations)
39
+ non_idle_machines = _get_non_idle_machines(dispatcher, current_time)
40
+
41
+ # Filter operations to keep those that are associated with at least one
42
+ # idle machine
43
+ filtered_operations: list[Operation] = []
44
+ for operation in operations:
45
+ if all(
46
+ machine_id in non_idle_machines
47
+ for machine_id in operation.machines
48
+ ):
49
+ continue
50
+ filtered_operations.append(operation)
51
+
52
+ return filtered_operations
53
+
54
+
55
+ def _get_non_idle_machines(
56
+ dispatcher: Dispatcher, current_time: int
57
+ ) -> set[int]:
58
+ """Returns the set of machine ids that are currently busy (i.e., have at
59
+ least one uncompleted operation)."""
60
+
61
+ non_idle_machines = set()
62
+ for machine_schedule in dispatcher.schedule.schedule:
63
+ for scheduled_operation in reversed(machine_schedule):
64
+ is_completed = scheduled_operation.end_time <= current_time
65
+ if is_completed:
66
+ break
67
+ non_idle_machines.add(scheduled_operation.machine_id)
68
+
69
+ return non_idle_machines
70
+
71
+
72
+ def filter_non_immediate_operations(
73
+ dispatcher: Dispatcher, operations: list[Operation]
74
+ ) -> list[Operation]:
75
+ """Filters out all the operations that can't start immediately.
76
+
77
+ An operation can start immediately if its earliest start time is the
78
+ current time.
79
+
80
+ The current time is determined by the minimum start time of the
81
+ operations.
82
+
83
+ Args:
84
+ dispatcher: The dispatcher object.
85
+ operations: The list of operations to filter.
86
+ """
87
+
88
+ min_start_time = dispatcher.min_start_time(operations)
89
+ immediate_operations: list[Operation] = []
90
+ for operation in operations:
91
+ start_time = dispatcher.earliest_start_time(operation)
92
+ if start_time == min_start_time:
93
+ immediate_operations.append(operation)
94
+
95
+ return immediate_operations
96
+
97
+
18
98
  def filter_dominated_operations(
19
99
  dispatcher: Dispatcher, operations: list[Operation]
20
100
  ) -> list[Operation]:
@@ -29,5 +29,5 @@ class IsReadyObserver(FeatureObserver):
29
29
  self.initialize_features()
30
30
 
31
31
  def _get_ready_operations(self) -> list[int]:
32
- available_operations = self.dispatcher.ready_operations()
32
+ available_operations = self.dispatcher.available_operations()
33
33
  return [operation.operation_id for operation in available_operations]
@@ -1,12 +1,14 @@
1
1
  """Home of the `DispatchingRuleSolver` class."""
2
2
 
3
- from collections.abc import Callable
3
+ from collections.abc import Callable, Iterable
4
4
 
5
5
  from job_shop_lib import JobShopInstance, Schedule, Operation, BaseSolver
6
6
  from job_shop_lib.dispatching import (
7
7
  ready_operations_filter_factory,
8
8
  Dispatcher,
9
9
  ReadyOperationsFilterType,
10
+ ReadyOperationsFilter,
11
+ create_composite_operation_filter,
10
12
  )
11
13
  from job_shop_lib.dispatching.rules import (
12
14
  dispatching_rule_factory,
@@ -30,6 +32,35 @@ class DispatchingRuleSolver(BaseSolver):
30
32
  pruning_function:
31
33
  The pruning function to use. It is used to initialize the
32
34
  dispatcher object internally when calling the solve method.
35
+
36
+ Args:
37
+ dispatching_rule:
38
+ The dispatching rule to use. It can be a string with the name
39
+ of the dispatching rule, a class`DispatchingRuleType` enum member,
40
+ or a callable that takes a dispatcher and returns the operation to
41
+ be dispatched next.
42
+ machine_chooser:
43
+ The machine chooser to use. It can be a string with the name
44
+ of the machine chooser, a :class:`MachineChooserType` member, or a
45
+ callable that takes a dispatcher and an operation and returns
46
+ the machine id where the operation will be dispatched.
47
+ ready_operations_filter:
48
+ The ready operations filter to use. It can be either:
49
+
50
+ - a string with the name of the pruning function
51
+ - a :class`ReadyOperationsFilterType` enum member.
52
+ - a callable that takes a dispatcher and a list of operations
53
+ and returns a list of operations that should be considered
54
+ for dispatching,
55
+ - a list with names or actual ready operations filters to be used.
56
+ If a list is provided, a composite filter will be created
57
+ using the specified filters.
58
+
59
+ .. seealso::
60
+ - :func:`job_shop_lib.dispatching.rules.dispatching_rule_factory`
61
+ - :func:`job_shop_lib.dispatching.rules.machine_chooser_factory`
62
+ - :func:`~job_shop_lib.dispatching.ready_operations_filter_factory`
63
+ - :func:`~job_shop_lib.dispatching.create_composite_operation_filter`
33
64
  """
34
65
 
35
66
  def __init__(
@@ -41,32 +72,16 @@ class DispatchingRuleSolver(BaseSolver):
41
72
  str | Callable[[Dispatcher, Operation], int]
42
73
  ) = MachineChooserType.FIRST,
43
74
  ready_operations_filter: (
44
- str
45
- | Callable[[Dispatcher, list[Operation]], list[Operation]]
75
+ Iterable[ReadyOperationsFilter | str | ReadyOperationsFilterType]
76
+ | str
77
+ | ReadyOperationsFilterType
78
+ | ReadyOperationsFilter
46
79
  | None
47
- ) = ReadyOperationsFilterType.DOMINATED_OPERATIONS,
80
+ ) = (
81
+ ReadyOperationsFilterType.DOMINATED_OPERATIONS,
82
+ ReadyOperationsFilterType.NON_IDLE_MACHINES,
83
+ ),
48
84
  ):
49
- """Initializes the solver with the given dispatching rule, machine
50
- chooser and pruning function.
51
-
52
- Args:
53
- dispatching_rule:
54
- The dispatching rule to use. It can be a string with the name
55
- of the dispatching rule, a DispatchingRule enum member, or a
56
- callable that takes a dispatcher and returns the operation to
57
- be dispatched next.
58
- machine_chooser:
59
- The machine chooser to use. It can be a string with the name
60
- of the machine chooser, a MachineChooser enum member, or a
61
- callable that takes a dispatcher and an operation and returns
62
- the machine id where the operation will be dispatched.
63
- ready_operations_filter:
64
- The ready operations filter to use. It can be a string with
65
- the name of the pruning function, a PruningFunction enum
66
- member, or a callable that takes a dispatcher and a list of
67
- operations and returns a list of operations that should be
68
- considered for dispatching.
69
- """
70
85
  if isinstance(dispatching_rule, str):
71
86
  dispatching_rule = dispatching_rule_factory(dispatching_rule)
72
87
  if isinstance(machine_chooser, str):
@@ -75,6 +90,10 @@ class DispatchingRuleSolver(BaseSolver):
75
90
  ready_operations_filter = ready_operations_filter_factory(
76
91
  ready_operations_filter
77
92
  )
93
+ if isinstance(ready_operations_filter, Iterable):
94
+ ready_operations_filter = create_composite_operation_filter(
95
+ ready_operations_filter
96
+ )
78
97
 
79
98
  self.dispatching_rule = dispatching_rule
80
99
  self.machine_chooser = machine_chooser
@@ -21,7 +21,7 @@ from job_shop_lib.dispatching.feature_observers import (
21
21
  def shortest_processing_time_rule(dispatcher: Dispatcher) -> Operation:
22
22
  """Dispatches the operation with the shortest duration."""
23
23
  return min(
24
- dispatcher.ready_operations(),
24
+ dispatcher.available_operations(),
25
25
  key=lambda operation: operation.duration,
26
26
  )
27
27
 
@@ -29,7 +29,7 @@ def shortest_processing_time_rule(dispatcher: Dispatcher) -> Operation:
29
29
  def first_come_first_served_rule(dispatcher: Dispatcher) -> Operation:
30
30
  """Dispatches the operation with the lowest position in job."""
31
31
  return min(
32
- dispatcher.ready_operations(),
32
+ dispatcher.available_operations(),
33
33
  key=lambda operation: operation.position_in_job,
34
34
  )
35
35
 
@@ -41,7 +41,7 @@ def most_work_remaining_rule(dispatcher: Dispatcher) -> Operation:
41
41
  job_remaining_work[operation.job_id] += operation.duration
42
42
 
43
43
  return max(
44
- dispatcher.ready_operations(),
44
+ dispatcher.available_operations(),
45
45
  key=lambda operation: job_remaining_work[operation.job_id],
46
46
  )
47
47
 
@@ -53,14 +53,14 @@ def most_operations_remaining_rule(dispatcher: Dispatcher) -> Operation:
53
53
  job_remaining_operations[operation.job_id] += 1
54
54
 
55
55
  return max(
56
- dispatcher.ready_operations(),
56
+ dispatcher.available_operations(),
57
57
  key=lambda operation: job_remaining_operations[operation.job_id],
58
58
  )
59
59
 
60
60
 
61
61
  def random_operation_rule(dispatcher: Dispatcher) -> Operation:
62
62
  """Dispatches a random operation."""
63
- return random.choice(dispatcher.ready_operations())
63
+ return random.choice(dispatcher.available_operations())
64
64
 
65
65
 
66
66
  def score_based_rule(
@@ -80,7 +80,7 @@ def score_based_rule(
80
80
  def rule(dispatcher: Dispatcher) -> Operation:
81
81
  scores = score_function(dispatcher)
82
82
  return max(
83
- dispatcher.ready_operations(),
83
+ dispatcher.available_operations(),
84
84
  key=lambda operation: scores[operation.job_id],
85
85
  )
86
86
 
@@ -102,7 +102,7 @@ def score_based_rule_with_tie_breaker(
102
102
  """
103
103
 
104
104
  def rule(dispatcher: Dispatcher) -> Operation:
105
- candidates = dispatcher.ready_operations()
105
+ candidates = dispatcher.available_operations()
106
106
  for scoring_function in score_functions:
107
107
  scores = scoring_function(dispatcher)
108
108
  best_score = max(scores)
@@ -126,7 +126,7 @@ def shortest_processing_time_score(dispatcher: Dispatcher) -> list[int]:
126
126
  """Scores each job based on the duration of the next operation."""
127
127
  num_jobs = dispatcher.instance.num_jobs
128
128
  scores = [0] * num_jobs
129
- for operation in dispatcher.ready_operations():
129
+ for operation in dispatcher.available_operations():
130
130
  scores[operation.job_id] = -operation.duration
131
131
  return scores
132
132
 
@@ -135,7 +135,7 @@ def first_come_first_served_score(dispatcher: Dispatcher) -> list[int]:
135
135
  """Scores each job based on the position of the next operation."""
136
136
  num_jobs = dispatcher.instance.num_jobs
137
137
  scores = [0] * num_jobs
138
- for operation in dispatcher.ready_operations():
138
+ for operation in dispatcher.available_operations():
139
139
  scores[operation.job_id] = operation.operation_id
140
140
  return scores
141
141
 
@@ -260,7 +260,7 @@ class SingleJobShopGraphEnv(gym.Env):
260
260
  truncated = False
261
261
  info: dict[str, Any] = {
262
262
  "feature_names": self.composite_observer.column_names,
263
- "available_operations": self.dispatcher.ready_operations(),
263
+ "available_operations": self.dispatcher.available_operations(),
264
264
  }
265
265
  return obs, reward, done, truncated, info
266
266
 
@@ -1,11 +1,11 @@
1
1
  """Package for visualization."""
2
2
 
3
- from job_shop_lib.visualization._gantt_chart import plot_gantt_chart
3
+ from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
4
4
  from job_shop_lib.visualization._gantt_chart_video_and_gif_creation import (
5
- create_gif,
5
+ create_gantt_chart_gif,
6
6
  create_gantt_chart_video,
7
7
  create_gantt_chart_frames,
8
- plot_gantt_chart_wrapper,
8
+ get_partial_gantt_chart_plotter,
9
9
  create_video_from_frames,
10
10
  create_gif_from_frames,
11
11
  )
@@ -26,9 +26,9 @@ from job_shop_lib.visualization._gantt_chart_creator import (
26
26
  __all__ = [
27
27
  "plot_gantt_chart",
28
28
  "create_gantt_chart_video",
29
- "create_gif",
29
+ "create_gantt_chart_gif",
30
30
  "create_gantt_chart_frames",
31
- "plot_gantt_chart_wrapper",
31
+ "get_partial_gantt_chart_plotter",
32
32
  "create_gif_from_frames",
33
33
  "create_video_from_frames",
34
34
  "plot_disjunctive_graph",
@@ -10,8 +10,8 @@ from job_shop_lib.dispatching import (
10
10
  )
11
11
  from job_shop_lib.visualization import (
12
12
  create_gantt_chart_video,
13
- plot_gantt_chart_wrapper,
14
- create_gif,
13
+ get_partial_gantt_chart_plotter,
14
+ create_gantt_chart_gif,
15
15
  )
16
16
 
17
17
 
@@ -146,7 +146,7 @@ class GanttChartCreator:
146
146
  self.history_observer: HistoryObserver = (
147
147
  dispatcher.create_or_get_observer(HistoryObserver)
148
148
  )
149
- self.plot_function = plot_gantt_chart_wrapper(
149
+ self.plot_function = get_partial_gantt_chart_plotter(
150
150
  **self.gannt_chart_wrapper_config
151
151
  )
152
152
 
@@ -175,7 +175,7 @@ class GanttChartCreator:
175
175
  return self.plot_function(
176
176
  self.schedule,
177
177
  None,
178
- self.dispatcher.ready_operations(),
178
+ self.dispatcher.available_operations(),
179
179
  self.dispatcher.current_time(),
180
180
  )
181
181
 
@@ -196,7 +196,7 @@ class GanttChartCreator:
196
196
  The configuration for the GIF creation can be customized through the
197
197
  `gif_config` attribute.
198
198
  """
199
- create_gif(
199
+ create_gantt_chart_gif(
200
200
  instance=self.history_observer.dispatcher.instance,
201
201
  schedule_history=self.history_observer.history,
202
202
  plot_function=self.plot_function,
@@ -3,8 +3,7 @@
3
3
  import os
4
4
  import pathlib
5
5
  import shutil
6
- from collections.abc import Callable
7
- from typing import Sequence
6
+ from typing import Sequence, Protocol
8
7
 
9
8
  import imageio
10
9
  import matplotlib.pyplot as plt
@@ -23,19 +22,49 @@ from job_shop_lib.dispatching import (
23
22
  HistoryObserver,
24
23
  )
25
24
  from job_shop_lib.dispatching.rules import DispatchingRuleSolver
26
- from job_shop_lib.visualization._gantt_chart import plot_gantt_chart
27
-
25
+ from job_shop_lib.visualization._plot_gantt_chart import plot_gantt_chart
26
+
27
+
28
+ # This class serves as a more meaningful type hint than simply:
29
+ # PlotFunction = Callable[
30
+ # [Schedule, int | None, list[Operation] | None, int | None], Figure
31
+ # ]
32
+ # That's why it doesn't have more methods or attributes. It is a protocol
33
+ # for functions, not for classes.
34
+ # pylint: disable=too-few-public-methods
35
+ class PartialGanttChartPlotter(Protocol):
36
+ """A protocol for a function that plots an uncompleted Gantt chart
37
+ for a schedule.
38
+
39
+ This kind of functions are created using the
40
+ :func:`plot_gantt_chart_wrapper` function.
41
+
42
+ The function should take the following arguments:
43
+
44
+ - schedule: The schedule to plot.
45
+ - makespan: The makespan of the schedule if known. Can be used to fix the
46
+ x-axis limits.
47
+ - available_operations: A list of available operations. If ``None``,
48
+ the available operations are not shown.
49
+ - current_time: The current time in the schedule. If provided, a red
50
+ vertical line is plotted at this time.
51
+ """
28
52
 
29
- PlotFunction = Callable[
30
- [Schedule, int | None, list[Operation] | None, int | None], Figure
31
- ]
53
+ def __call__(
54
+ self,
55
+ schedule: Schedule,
56
+ makespan: int | None = None,
57
+ available_operations: list[Operation] | None = None,
58
+ current_time: int | None = None,
59
+ ) -> Figure:
60
+ pass
32
61
 
33
62
 
34
- def plot_gantt_chart_wrapper(
63
+ def get_partial_gantt_chart_plotter(
35
64
  title: str | None = None,
36
65
  cmap: str = "viridis",
37
66
  show_available_operations: bool = False,
38
- ) -> PlotFunction:
67
+ ) -> PartialGanttChartPlotter:
39
68
  """Returns a function that plots a Gantt chart for an unfinished schedule.
40
69
 
41
70
  Args:
@@ -47,12 +76,13 @@ def plot_gantt_chart_wrapper(
47
76
  Returns:
48
77
  A function that plots a Gantt chart for a schedule. The function takes
49
78
  the following arguments:
79
+
50
80
  - schedule: The schedule to plot.
51
81
  - makespan: The makespan of the schedule.
52
82
  - available_operations: A list of available operations. If None,
53
- the available operations are not shown.
83
+ the available operations are not shown.
54
84
  - current_time: The current time in the schedule. If provided, a
55
- red vertical line is plotted at this time.
85
+ red vertical line is plotted at this time.
56
86
 
57
87
  """
58
88
 
@@ -97,33 +127,30 @@ def plot_gantt_chart_wrapper(
97
127
  # Most of the arguments are optional with default values. There is no way to
98
128
  # reduce the number of arguments without losing functionality.
99
129
  # pylint: disable=too-many-arguments
100
- def create_gif(
101
- gif_path: str | None,
130
+ def create_gantt_chart_gif(
102
131
  instance: JobShopInstance,
132
+ gif_path: str | None = None,
103
133
  solver: DispatchingRuleSolver | None = None,
104
- plot_function: PlotFunction | None = None,
134
+ plot_function: PartialGanttChartPlotter | None = None,
105
135
  fps: int = 1,
106
136
  remove_frames: bool = True,
107
137
  frames_dir: str | None = None,
108
138
  plot_current_time: bool = True,
109
139
  schedule_history: Sequence[ScheduledOperation] | None = None,
110
140
  ) -> None:
111
- """Creates a GIF of the schedule being built by the given solver.
112
-
113
- Deprecated since version 0.6.0: Use `create_gif_or_video` instead.
141
+ """Creates a GIF of the schedule being built.
114
142
 
115
143
  Args:
116
- gif_path:
117
- The path to save the GIF file. It should end with ".gif". If not
118
- provided, the name of the instance is used. It will be made an
119
- optional argument in version 1.0.0.
120
144
  instance:
121
145
  The instance of the job shop problem to be scheduled.
146
+ gif_path:
147
+ The path to save the GIF file. It should end with ".gif". If not
148
+ provided, the name of the instance is used.
122
149
  solver:
123
150
  The dispatching rule solver to use. If not provided, the history
124
151
  of scheduled operations should be provided.
125
152
  plot_function:
126
- A function that plots a Gantt chart for a schedule. It
153
+ A :class:`PlotFunction` that plots a Gantt chart for a schedule. It
127
154
  should take a `Schedule` object and the makespan of the schedule as
128
155
  input and return a `Figure` object. If not provided, a default
129
156
  function is used.
@@ -133,7 +160,7 @@ def create_gif(
133
160
  Whether to remove the frames after creating the GIF.
134
161
  frames_dir:
135
162
  The directory to save the frames in. If not provided,
136
- `gif_path.replace(".gif", "") + "_frames"` is used.
163
+ ``gif_path.replace(".gif", "") + "_frames"`` is used.
137
164
  plot_current_time:
138
165
  Whether to plot a vertical line at the current time.
139
166
  schedule_history:
@@ -144,7 +171,7 @@ def create_gif(
144
171
  gif_path = f"{instance.name}_gantt_chart.gif"
145
172
 
146
173
  if plot_function is None:
147
- plot_function = plot_gantt_chart_wrapper()
174
+ plot_function = get_partial_gantt_chart_plotter()
148
175
 
149
176
  if frames_dir is None:
150
177
  # Use the name of the GIF file as the directory name
@@ -173,14 +200,14 @@ def create_gantt_chart_video(
173
200
  instance: JobShopInstance,
174
201
  video_path: str | None = None,
175
202
  solver: DispatchingRuleSolver | None = None,
176
- plot_function: PlotFunction | None = None,
203
+ plot_function: PartialGanttChartPlotter | None = None,
177
204
  fps: int = 1,
178
205
  remove_frames: bool = True,
179
206
  frames_dir: str | None = None,
180
207
  plot_current_time: bool = True,
181
208
  schedule_history: Sequence[ScheduledOperation] | None = None,
182
209
  ) -> None:
183
- """Creates a GIF of the schedule being built by the given solver.
210
+ """Creates a video of the schedule being built.
184
211
 
185
212
  Args:
186
213
  instance:
@@ -192,16 +219,16 @@ def create_gantt_chart_video(
192
219
  of scheduled operations should be provided.
193
220
  plot_function:
194
221
  A function that plots a Gantt chart for a schedule. It
195
- should take a `Schedule` object and the makespan of the schedule as
196
- input and return a `Figure` object. If not provided, a default
197
- function is used.
222
+ should take a :class:`Schedule` object and the makespan of the
223
+ schedule as input and return a ``Figure`` object. If not provided,
224
+ a default function is used.
198
225
  fps:
199
- The number of frames per second in the GIF.
226
+ The number of frames per second in the video.
200
227
  remove_frames:
201
- Whether to remove the frames after creating the GIF.
228
+ Whether to remove the frames after creating the video.
202
229
  frames_dir:
203
230
  The directory to save the frames in. If not provided,
204
- `name_without_the_extension` + "_frames"` is used.
231
+ ``name_without_the_extension + "_frames"`` is used.
205
232
  plot_current_time:
206
233
  Whether to plot a vertical line at the current time.
207
234
  schedule_history:
@@ -212,7 +239,7 @@ def create_gantt_chart_video(
212
239
  video_path = f"{instance.name}_gantt_chart.mp4"
213
240
 
214
241
  if plot_function is None:
215
- plot_function = plot_gantt_chart_wrapper()
242
+ plot_function = get_partial_gantt_chart_plotter()
216
243
 
217
244
  if frames_dir is None:
218
245
  extension = video_path.split(".")[-1]
@@ -238,7 +265,7 @@ def create_gantt_chart_frames(
238
265
  frames_dir: str,
239
266
  instance: JobShopInstance,
240
267
  solver: DispatchingRuleSolver | None,
241
- plot_function: PlotFunction,
268
+ plot_function: PartialGanttChartPlotter,
242
269
  plot_current_time: bool = True,
243
270
  schedule_history: Sequence[ScheduledOperation] | None = None,
244
271
  ) -> None:
@@ -295,7 +322,7 @@ def create_gantt_chart_frames(
295
322
  fig = plot_function(
296
323
  dispatcher.schedule,
297
324
  makespan,
298
- dispatcher.ready_operations(),
325
+ dispatcher.available_operations(),
299
326
  current_time,
300
327
  )
301
328
  _save_frame(fig, frames_dir, i)
@@ -20,16 +20,39 @@ def plot_gantt_chart(
20
20
  cmap_name: str = "viridis",
21
21
  xlim: int | None = None,
22
22
  number_of_x_ticks: int = 15,
23
+ job_labels: None | list[str] = None,
24
+ machine_labels: None | list[str] = None,
25
+ legend_title: str = "",
26
+ x_label: str = "Time units",
27
+ y_label: str = "Machines",
23
28
  ) -> tuple[Figure, plt.Axes]:
24
29
  """Plots a Gantt chart for the schedule.
25
30
 
31
+ This function generates a Gantt chart that visualizes the schedule of jobs
32
+ across multiple machines. Each job is represented with a unique color,
33
+ and operations are plotted as bars on the corresponding machines over time.
34
+
35
+ The Gantt chart helps to understand the flow of jobs on machines and
36
+ visualize the makespan of the schedule, i.e., the time it takes to
37
+ complete all jobs.
38
+
39
+ The Gantt chart includes:
40
+
41
+ - X-axis: Time units, representing the progression of the schedule.
42
+ - Y-axis: Machines, which are assigned jobs at various time slots.
43
+ - Legend: A list of jobs, labeled and color-coded for clarity.
44
+
45
+ .. note::
46
+ The last tick on the x-axis always represents the makespan for easy
47
+ identification of the completion time.
48
+
26
49
  Args:
27
50
  schedule:
28
51
  The schedule to plot.
29
52
  title:
30
53
  The title of the plot. If not provided, the title:
31
- `f"Gantt Chart for {schedule.instance.name} instance"`
32
- is used.
54
+ ``f"Gantt Chart for {schedule.instance.name} instance"``
55
+ is used. To remove the title, provide an empty string.
33
56
  cmap_name:
34
57
  The name of the colormap to use. Default is "viridis".
35
58
  xlim:
@@ -37,21 +60,45 @@ def plot_gantt_chart(
37
60
  the schedule is used.
38
61
  number_of_x_ticks:
39
62
  The number of ticks to use in the x-axis.
63
+ job_labels:
64
+ A list of labels for each job. If ``None``, the labels are
65
+ automatically generated as "Job 0", "Job 1", etc.
66
+ machine_labels:
67
+ A list of labels for each machine. If ``None``, the labels are
68
+ automatically generated as "0", "1", etc.
69
+ legend_title:
70
+ The title of the legend. If not provided, the legend will not have
71
+ a title.
72
+ x_label:
73
+ The label for the x-axis. Default is "Time units". To remove the
74
+ label, provide an empty string.
75
+ y_label:
76
+ The label for the y-axis. Default is "Machines". To remove the
77
+ label, provide an empty string.
78
+
79
+ Returns:
80
+ - A ``matplotlib.figure.Figure`` object.
81
+ - A ``matplotlib.axes.Axes`` object where the Gantt chart is plotted.
40
82
  """
41
- fig, ax = _initialize_plot(schedule, title)
42
- legend_handles = _plot_machine_schedules(schedule, ax, cmap_name)
43
- _configure_legend(ax, legend_handles)
44
- _configure_axes(schedule, ax, xlim, number_of_x_ticks)
83
+ fig, ax = _initialize_plot(schedule, title, x_label, y_label)
84
+ legend_handles = _plot_machine_schedules(
85
+ schedule, ax, cmap_name, job_labels
86
+ )
87
+ _configure_legend(ax, legend_handles, legend_title)
88
+ _configure_axes(schedule, ax, xlim, number_of_x_ticks, machine_labels)
45
89
  return fig, ax
46
90
 
47
91
 
48
92
  def _initialize_plot(
49
- schedule: Schedule, title: str | None
93
+ schedule: Schedule,
94
+ title: str | None,
95
+ x_label: str = "Time units",
96
+ y_label: str = "Machines",
50
97
  ) -> tuple[Figure, plt.Axes]:
51
98
  """Initializes the plot."""
52
99
  fig, ax = plt.subplots()
53
- ax.set_xlabel("Time units")
54
- ax.set_ylabel("Machines")
100
+ ax.set_xlabel(x_label)
101
+ ax.set_ylabel(y_label)
55
102
  ax.grid(True, which="both", axis="x", linestyle="--", linewidth=0.5)
56
103
  ax.yaxis.grid(False)
57
104
  if title is None:
@@ -61,7 +108,10 @@ def _initialize_plot(
61
108
 
62
109
 
63
110
  def _plot_machine_schedules(
64
- schedule: Schedule, ax: plt.Axes, cmap_name: str
111
+ schedule: Schedule,
112
+ ax: plt.Axes,
113
+ cmap_name: str,
114
+ job_labels: list[str] | None,
65
115
  ) -> dict[int, Patch]:
66
116
  """Plots the schedules for each machine."""
67
117
  max_job_id = schedule.instance.num_jobs - 1
@@ -81,12 +131,20 @@ def _plot_machine_schedules(
81
131
  )
82
132
  if scheduled_op.job_id not in legend_handles:
83
133
  legend_handles[scheduled_op.job_id] = Patch(
84
- facecolor=color, label=f"Job {scheduled_op.job_id}"
134
+ facecolor=color,
135
+ label=_get_job_label(job_labels, scheduled_op.job_id),
85
136
  )
86
137
 
87
138
  return legend_handles
88
139
 
89
140
 
141
+ def _get_job_label(job_labels: list[str] | None, job_id: int) -> str:
142
+ """Returns the label for the job."""
143
+ if job_labels is None:
144
+ return f"Job {job_id}"
145
+ return job_labels[job_id]
146
+
147
+
90
148
  def _plot_scheduled_operation(
91
149
  ax: plt.Axes,
92
150
  scheduled_op: ScheduledOperation,
@@ -103,7 +161,9 @@ def _plot_scheduled_operation(
103
161
  )
104
162
 
105
163
 
106
- def _configure_legend(ax: plt.Axes, legend_handles: dict[int, Patch]):
164
+ def _configure_legend(
165
+ ax: plt.Axes, legend_handles: dict[int, Patch], legend_title: str
166
+ ):
107
167
  """Configures the legend for the plot."""
108
168
  sorted_legend_handles = [
109
169
  legend_handles[job_id] for job_id in sorted(legend_handles)
@@ -111,7 +171,8 @@ def _configure_legend(ax: plt.Axes, legend_handles: dict[int, Patch]):
111
171
  ax.legend(
112
172
  handles=sorted_legend_handles,
113
173
  loc="upper left",
114
- bbox_to_anchor=(1.01, 1),
174
+ bbox_to_anchor=(1, 1),
175
+ title=legend_title,
115
176
  )
116
177
 
117
178
 
@@ -120,6 +181,7 @@ def _configure_axes(
120
181
  ax: plt.Axes,
121
182
  xlim: Optional[int],
122
183
  number_of_x_ticks: int,
184
+ machine_labels: list[str] | None,
123
185
  ):
124
186
  """Sets the limits and labels for the axes."""
125
187
  num_machines = len(schedule.schedule)
@@ -132,7 +194,9 @@ def _configure_axes(
132
194
  for i in range(num_machines)
133
195
  ]
134
196
  )
135
- ax.set_yticklabels([str(i) for i in range(num_machines)])
197
+ if machine_labels is None:
198
+ machine_labels = [str(i) for i in range(num_machines)]
199
+ ax.set_yticklabels(machine_labels)
136
200
  makespan = schedule.makespan()
137
201
  xlim = xlim if xlim is not None else makespan
138
202
  ax.set_xlim(0, xlim)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.0.0a2
3
+ Version: 1.0.0a3
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
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
29
29
  <h1>JobShopLib</h1>
30
30
 
31
31
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
32
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
32
33
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
33
34
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
34
35
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -39,7 +40,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S
39
40
 
40
41
  It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
41
42
 
42
- See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
43
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
43
44
 
44
45
  ## Installation :package:
45
46
 
@@ -47,12 +48,23 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
47
48
 
48
49
  JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
49
50
 
50
- You can install the latest version using `pip`:
51
+ You can install the latest stable version (version 0.5.1) using `pip`:
51
52
 
52
53
  ```bash
53
54
  pip install job-shop-lib
54
55
  ```
55
56
 
57
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
58
+
59
+
60
+ Version 1.0.0 is currently in beta stage and can be installed with:
61
+
62
+ ```bash
63
+ pip install job-shop-lib==1.0.0a3
64
+ ```
65
+
66
+ Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiare yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
67
+
56
68
  <!-- end installation -->
57
69
 
58
70
  <!-- key features -->
@@ -1,20 +1,20 @@
1
1
  job_shop_lib/__init__.py,sha256=Ci5ipn-zciO88C5aX5Wx-UN8iBTbpde3dSSg02ZcfwM,597
2
2
  job_shop_lib/_base_solver.py,sha256=p17XmtufNc9Y481cqZUT45pEkUmmW1HWG53dfhIBJH8,1363
3
- job_shop_lib/_job_shop_instance.py,sha256=Q0ml3C36tmcBskBo8MyaZWILJPbdvHjJcvXzD_YfLsU,16475
4
- job_shop_lib/_operation.py,sha256=lJ4srQs82cYhUYuBxqkfKr9MuZoG0X2x0z3M1_QXkAs,3360
5
- job_shop_lib/_schedule.py,sha256=H6PREUE9KCC0DtrQV8y9MB8S4sh3qnDwby5Y4WVDfzg,11333
6
- job_shop_lib/_scheduled_operation.py,sha256=ptqYRW39EvG5tnk4DLQGFCOkbGTTYIQJhUMcuLSbdSA,2812
3
+ job_shop_lib/_job_shop_instance.py,sha256=Ubmm10DWO9SmhCOFsvEqeww7XFn-60JWTPDA40FOWZE,16434
4
+ job_shop_lib/_operation.py,sha256=wAYijQtKPPPuXQo9648tYV1GFUU9XGiXIRQgPsCbYtM,3404
5
+ job_shop_lib/_schedule.py,sha256=McDpisMyygxhw349Hxe4N3mCnugr8B5dVNCGN6sSYKg,11371
6
+ job_shop_lib/_scheduled_operation.py,sha256=a1LW2LQvpH1WTxpE2x792_glp5M_gInCf70fuBiheqc,2828
7
7
  job_shop_lib/benchmarking/__init__.py,sha256=BYCrJUNr_uk2c0xIbDt07OnUMhQx8Dudkukx3TFWxgw,3271
8
8
  job_shop_lib/benchmarking/_load_benchmark.py,sha256=-cgyx0Kn6uAc3KdGFSQb6eUVQjQggmpVKOH9qusNkXI,2930
9
9
  job_shop_lib/benchmarking/benchmark_instances.json,sha256=F9EvyzFwVxiKAN6rQTsrMhsKstmyUmroyWduM7a00KQ,464841
10
10
  job_shop_lib/constraint_programming/__init__.py,sha256=kKQRUxxS_nVFUdXGnf4bQOD9mqrXxZZWElS753A4YiA,454
11
11
  job_shop_lib/constraint_programming/_ortools_solver.py,sha256=gRoImEBUa8_io-TzbSS-3f0CJ7UwacU5Lrz0bsDqibo,10462
12
- job_shop_lib/dispatching/__init__.py,sha256=QV7qy-y0sSoKp_FslTm7sdqczYzpq0YctzKQ36l0ykg,1510
13
- job_shop_lib/dispatching/_dispatcher.py,sha256=PCSBpYAF6QPXWrjwkBQXTxOdGdq6Y1Uqw8esQTW05TQ,21357
12
+ job_shop_lib/dispatching/__init__.py,sha256=2VQYWNSyuDx3zzrDPCEzs5VJd2AIJvF7vA7LwK5z2V4,1648
13
+ job_shop_lib/dispatching/_dispatcher.py,sha256=o0BqZBub9flPAHX59qBHV2RETQg7n31xHe6owg9Ki7U,21305
14
14
  job_shop_lib/dispatching/_dispatcher_observer_config.py,sha256=l_lbaw9JJ5icVOmDAzAL6G5t6wG25bQLpRedN1bys8c,1932
15
- job_shop_lib/dispatching/_factories.py,sha256=UAZLq7d_-puzMYteiAbbhkcW5ucKO-lo3bj8pCCEnOA,4229
15
+ job_shop_lib/dispatching/_factories.py,sha256=MYnjuK9NOVU9n98mrNu5Z0svBIYBCTDbVGzUX9rDWsQ,4594
16
16
  job_shop_lib/dispatching/_history_observer.py,sha256=Vl8rQaxekUeEB-AyNxyC3c76zQakeh-rdri2iDnZvXw,610
17
- job_shop_lib/dispatching/_ready_operation_filters.py,sha256=q8Xv4kp_2GsvEMC5mlTuJXivAz_b8bbrqo5sXaS3PJU,3110
17
+ job_shop_lib/dispatching/_ready_operation_filters.py,sha256=Mywt57h8Nlj6XrptWakVt9n1Tq4jsneZFQEgjLMxJgw,5731
18
18
  job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=LNEzqOWqEf6fvtkQrDmDWFEhCfA75OgEtzdomzbxYII,2683
19
19
  job_shop_lib/dispatching/feature_observers/__init__.py,sha256=EuJLvSpJpoXUK8A4UuC2k6Mpa293ZR3oCnnvYivIBtU,2240
20
20
  job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=DOS961MtWaDk2gxjOLA_75SyT6Nmn3IKuNtYO8odk8s,7938
@@ -23,14 +23,14 @@ job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha2
23
23
  job_shop_lib/dispatching/feature_observers/_factory.py,sha256=b5YyzdnorijtWUNrYWs4sf-G17eDxw8oYrol1rzMN1Q,2919
24
24
  job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=crbqG1KrmUOfG4z7shHNzhUg7-uSP4_RWxyOi-RRWmE,8635
25
25
  job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=wKmlASLjodztAB2ypTsi0XFLZ3h1ltzvsa9BpPrbksU,4581
26
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=KYDUO3Zz1wgkClZ64i5-az6W-SFpi8rckAlv4Zjcii4,1260
26
+ job_shop_lib/dispatching/feature_observers/_is_ready_observer.py,sha256=wy_pA-1wmnzVjhq92mdsT2JJHYbfsm79mcMgSgYUCOs,1264
27
27
  job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py,sha256=OcuMUB9_By6ZMtX-1_3z-xaxGbP85a5Zv0ywAv7XxWQ,1491
28
28
  job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py,sha256=WRknpQBKXs6h6cXLFJW7ZCvjtU8CPL-iXXNPw3g-mLE,1303
29
29
  job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py,sha256=5V87lCrJUabEe8AkTGXPu5yS8OGxeN8L3-xNyHmdmLs,1441
30
30
  job_shop_lib/dispatching/rules/__init__.py,sha256=p1rkqf66L62uvAOM1ZxNV8xHoh7SuYjHi_8ZNBvPIjg,1450
31
31
  job_shop_lib/dispatching/rules/_dispatching_rule_factory.py,sha256=5fNpv90fAoR6rcE6NeJOWiB7ir-FVnoONIhHtKJ9H0E,2904
32
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=Nb3EPSIdnbeqKaIf5ufE2zxQsNpNkZxmYa0Eh1jnCnw,5537
33
- job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=Wb9fQIfePvCJi4RqJ59UrRSnYufgQw5nQ_Am8M6-JOI,7569
32
+ job_shop_lib/dispatching/rules/_dispatching_rule_solver.py,sha256=YgcxDUx9RqwfTxWtoKesngYVFDsHiWtsm1BAUrAyerY,6353
33
+ job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=yRJXYH6QSxDCIK8vqNUfMCMpzWmr3j_camAR72Z6D9Q,7605
34
34
  job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=xsJ8nJwPDBi-sfLJRQF_BBQDbyXDfopD1U-efXffQAE,2331
35
35
  job_shop_lib/dispatching/rules/_utils.py,sha256=X8vET2p1D3RyoB9mFfsfRgmilcTmxPssKYyJQ2zEt0Q,4605
36
36
  job_shop_lib/exceptions.py,sha256=ARzpoZJCvRIvOesCiqqFSRxkv6w9WwEXx0aBP-l2IKA,1597
@@ -51,16 +51,16 @@ job_shop_lib/graphs/graph_updaters/_utils.py,sha256=X5YfwJA1CCgpm1r9C036Gal2CkDh
51
51
  job_shop_lib/reinforcement_learning/__init__.py,sha256=QVFo9e1X-tpanZkGdcCPV_WobQ2EZ_y5uoYSJ36XrQI,957
52
52
  job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py,sha256=jKMcWYBvz1kwlw00Xe1x9HqhFkMMqlh-Y95NmeBL3-0,15129
53
53
  job_shop_lib/reinforcement_learning/_reward_observers.py,sha256=4Kdyn9Jlp_1sBtVr6raF-ZFtcnKxwyCLykfX53TmuhU,2959
54
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=Kbd3N1rKcXm6IHTo99En-oX85vqVobv2bFBCyAht2mE,12949
54
+ job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=ByQQ1fCCpji0VAjKTcbBOGST-3MF6oq2naAtdElJUh8,12953
55
55
  job_shop_lib/reinforcement_learning/_types_and_constants.py,sha256=CY849lbv6UXy40KRcMJT3WxvGWrLqcfysu65LPkTfg8,1715
56
56
  job_shop_lib/reinforcement_learning/_utils.py,sha256=ilI089Bs8CRlfRV_yH6XH8oypTDtRa7hS-H4iRCC5lU,2497
57
- job_shop_lib/visualization/__init__.py,sha256=jXC188u5AnSWcO1lRZEzZAPZTXbqlYSPhYc7LMc0itU,1094
57
+ job_shop_lib/visualization/__init__.py,sha256=SVxg7Dt3azETfI0IvUtLtCBsirXQWirdjEjOQ2d5eWU,1137
58
58
  job_shop_lib/visualization/_agent_task_graph.py,sha256=AaBTD_S34WjrsZnL_iMAplR_f67RahZi7x58SOvp-q0,8834
59
59
  job_shop_lib/visualization/_disjunctive_graph.py,sha256=pg4KG9BfQbnBPnXYgbyPGe0AuHSmhYqPeqWYAf_spWQ,5905
60
- job_shop_lib/visualization/_gantt_chart.py,sha256=B9sn4XrEUqgQhRKju-1VUG5R67AZXRu7jbrtA8VcndU,4412
61
- job_shop_lib/visualization/_gantt_chart_creator.py,sha256=qFhCfk3oC3uF7Mau3lrNhH-34sfHXvkqEXbsDzrIbBk,7721
62
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py,sha256=GQhQF2YzSRQT_DdUjJYHFKMZMjk_hPrYjQU5PpedNvs,13213
63
- job_shop_lib-1.0.0a2.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
64
- job_shop_lib-1.0.0a2.dist-info/METADATA,sha256=IVDNFeNtFCpjUzIGXkEn0uQ3aBgJLSLXHuJKEgpBajI,14810
65
- job_shop_lib-1.0.0a2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
66
- job_shop_lib-1.0.0a2.dist-info/RECORD,,
60
+ job_shop_lib/visualization/_gantt_chart_creator.py,sha256=xw7lfd1HIm7r5j2kDxgBdrgSickCslpUcm7QFrNXmtg,7763
61
+ job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py,sha256=IpKe2oZbrW4fvsdnRtuFWfXN-FdfwkYVdTAq3av9mKE,14249
62
+ job_shop_lib/visualization/_plot_gantt_chart.py,sha256=1WCJ5Gjl3dwA-w4Jn9suIg-ZGR28yYUAy8Jp-IiyvfI,6842
63
+ job_shop_lib-1.0.0a3.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
64
+ job_shop_lib-1.0.0a3.dist-info/METADATA,sha256=fJ7RYm8yUkmZvG5moRO5QUVDw0_ACIcqb7gxiuqLrQo,15663
65
+ job_shop_lib-1.0.0a3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
66
+ job_shop_lib-1.0.0a3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any