job-shop-lib 0.5.1__py3-none-any.whl → 1.0.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.
Files changed (95) hide show
  1. job_shop_lib/__init__.py +19 -8
  2. job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
  3. job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
  4. job_shop_lib/_operation.py +118 -0
  5. job_shop_lib/{schedule.py → _schedule.py} +102 -84
  6. job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
  7. job_shop_lib/benchmarking/__init__.py +66 -43
  8. job_shop_lib/benchmarking/_load_benchmark.py +88 -0
  9. job_shop_lib/constraint_programming/__init__.py +13 -0
  10. job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +77 -22
  11. job_shop_lib/dispatching/__init__.py +51 -42
  12. job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
  13. job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
  14. job_shop_lib/dispatching/_factories.py +135 -0
  15. job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
  16. job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
  17. job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
  18. job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
  19. job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
  20. job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
  21. job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
  22. job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
  23. job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
  24. job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
  25. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
  26. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
  27. job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
  28. job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
  29. job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
  30. job_shop_lib/dispatching/rules/__init__.py +87 -0
  31. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
  32. job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
  33. job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
  34. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
  35. job_shop_lib/dispatching/rules/_utils.py +128 -0
  36. job_shop_lib/exceptions.py +18 -0
  37. job_shop_lib/generation/__init__.py +10 -2
  38. job_shop_lib/generation/_general_instance_generator.py +165 -0
  39. job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +37 -26
  40. job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
  41. job_shop_lib/generation/_utils.py +124 -0
  42. job_shop_lib/graphs/__init__.py +30 -12
  43. job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
  44. job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
  45. job_shop_lib/graphs/_constants.py +38 -0
  46. job_shop_lib/graphs/_job_shop_graph.py +320 -0
  47. job_shop_lib/graphs/_node.py +182 -0
  48. job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
  49. job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
  50. job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
  51. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
  52. job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
  53. job_shop_lib/py.typed +0 -0
  54. job_shop_lib/reinforcement_learning/__init__.py +68 -0
  55. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
  56. job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
  57. job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
  58. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
  59. job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
  60. job_shop_lib/reinforcement_learning/_utils.py +199 -0
  61. job_shop_lib/visualization/__init__.py +0 -25
  62. job_shop_lib/visualization/gantt/__init__.py +48 -0
  63. job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
  64. job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
  65. job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
  66. job_shop_lib/visualization/graphs/__init__.py +29 -0
  67. job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
  68. job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
  69. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
  70. job_shop_lib-1.0.0.dist-info/RECORD +73 -0
  71. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
  72. job_shop_lib/benchmarking/load_benchmark.py +0 -142
  73. job_shop_lib/cp_sat/__init__.py +0 -5
  74. job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
  75. job_shop_lib/dispatching/factories.py +0 -206
  76. job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
  77. job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
  78. job_shop_lib/dispatching/feature_observers/factory.py +0 -58
  79. job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
  80. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
  81. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
  82. job_shop_lib/dispatching/pruning_functions.py +0 -116
  83. job_shop_lib/generation/general_instance_generator.py +0 -169
  84. job_shop_lib/generation/transformations.py +0 -164
  85. job_shop_lib/generators/__init__.py +0 -8
  86. job_shop_lib/generators/basic_generator.py +0 -200
  87. job_shop_lib/graphs/constants.py +0 -21
  88. job_shop_lib/graphs/job_shop_graph.py +0 -202
  89. job_shop_lib/graphs/node.py +0 -166
  90. job_shop_lib/operation.py +0 -122
  91. job_shop_lib/visualization/agent_task_graph.py +0 -257
  92. job_shop_lib/visualization/create_gif.py +0 -209
  93. job_shop_lib/visualization/disjunctive_graph.py +0 -210
  94. job_shop_lib-0.5.1.dist-info/RECORD +0 -52
  95. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -2,78 +2,88 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any
5
+ from typing import Any, List, Union, Dict, Optional
6
6
  from collections import deque
7
7
 
8
- from job_shop_lib import ScheduledOperation, JobShopInstance, JobShopLibError
8
+ from job_shop_lib import ScheduledOperation, JobShopInstance
9
+ from job_shop_lib.exceptions import ValidationError
9
10
 
10
11
 
11
12
  class Schedule:
12
- """Data structure to store a schedule for a `JobShopInstance` object.
13
+ """Data structure to store a complete or partial solution for a particular
14
+ :class:`JobShopInstance`.
13
15
 
14
- Attributes:
16
+ A schedule is a list of lists of :class:`ScheduledOperation` objects. Each
17
+ list represents the order of operations on a machine.
18
+
19
+ The main methods of this class are:
20
+
21
+ .. autosummary::
22
+ :nosignatures:
23
+
24
+ makespan
25
+ is_complete
26
+ add
27
+ reset
28
+
29
+ Args:
15
30
  instance:
16
- The `JobShopInstance` object that the schedule is for.
31
+ The :class:`JobShopInstance` object that the schedule is for.
17
32
  schedule:
18
- A list of lists of `ScheduledOperation` objects. Each list of
19
- `ScheduledOperation` objects represents the order of operations
20
- on a machine.
21
- metadata:
22
- A dictionary with additional information about the schedule. It
23
- can be used to store information about the algorithm that generated
24
- the schedule, for example.
33
+ A list of lists of :class:`ScheduledOperation` objects. Each
34
+ list represents the order of operations on a machine. If
35
+ not provided, the schedule is initialized as an empty schedule.
36
+ **metadata:
37
+ Additional information about the schedule.
25
38
  """
26
39
 
27
- __slots__ = (
28
- "instance",
29
- "_schedule",
30
- "metadata",
31
- )
40
+ __slots__ = {
41
+ "instance": (
42
+ "The :class:`JobShopInstance` object that the schedule is for."
43
+ ),
44
+ "_schedule": (
45
+ "A list of lists of :class:`ScheduledOperation` objects. "
46
+ "Each list represents the order of operations on a machine."
47
+ ),
48
+ "metadata": (
49
+ "A dictionary with additional information about the "
50
+ "schedule. It can be used to store information about the "
51
+ "algorithm that generated the schedule, for example."
52
+ ),
53
+ }
32
54
 
33
55
  def __init__(
34
56
  self,
35
57
  instance: JobShopInstance,
36
- schedule: list[list[ScheduledOperation]] | None = None,
37
- **metadata,
58
+ schedule: Optional[List[List[ScheduledOperation]]] = None,
59
+ **metadata: Any,
38
60
  ):
39
- """Initializes the object with the given instance and schedule.
40
-
41
- Args:
42
- instance:
43
- The `JobShopInstance` object that the schedule is for.
44
- schedule:
45
- A list of lists of `ScheduledOperation` objects. Each list of
46
- `ScheduledOperation` objects represents the order of operations
47
- on a machine. If not provided, the schedule is initialized as
48
- an empty schedule.
49
- **metadata:
50
- Additional information about the schedule.
51
- """
52
61
  if schedule is None:
53
62
  schedule = [[] for _ in range(instance.num_machines)]
54
63
 
55
64
  Schedule.check_schedule(schedule)
56
65
 
57
- self.instance = instance
66
+ self.instance: JobShopInstance = instance
58
67
  self._schedule = schedule
59
- self.metadata = metadata
68
+ self.metadata: Dict[str, Any] = metadata
60
69
 
61
70
  def __repr__(self) -> str:
62
71
  return str(self.schedule)
63
72
 
64
73
  @property
65
- def schedule(self) -> list[list[ScheduledOperation]]:
66
- """Returns the schedule attribute."""
74
+ def schedule(self) -> List[List[ScheduledOperation]]:
75
+ """A list of lists of :class:`ScheduledOperation` objects. Each list
76
+ represents the order of operations on a machine."""
67
77
  return self._schedule
68
78
 
69
79
  @schedule.setter
70
- def schedule(self, new_schedule: list[list[ScheduledOperation]]):
80
+ def schedule(self, new_schedule: List[List[ScheduledOperation]]):
71
81
  Schedule.check_schedule(new_schedule)
72
82
  self._schedule = new_schedule
73
83
 
74
84
  @property
75
85
  def num_scheduled_operations(self) -> int:
76
- """Returns the number of operations that have been scheduled."""
86
+ """The number of operations that have been scheduled so far."""
77
87
  return sum(len(machine_schedule) for machine_schedule in self.schedule)
78
88
 
79
89
  def to_dict(self) -> dict:
@@ -84,15 +94,16 @@ class Schedule:
84
94
  Returns:
85
95
  A dictionary representation of the schedule with the following
86
96
  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.
97
+
98
+ - **"instance"**: A dictionary representation of the instance.
99
+ - **"job_sequences"**: A list of lists of job ids. Each list
100
+ of job ids represents the order of operations on the machine.
101
+ The machine that the list corresponds to is determined by the
102
+ index of the list.
103
+ - **"metadata"**: A dictionary with additional information
104
+ about the schedule.
94
105
  """
95
- job_sequences: list[list[int]] = []
106
+ job_sequences: List[List[int]] = []
96
107
  for machine_schedule in self.schedule:
97
108
  job_sequences.append(
98
109
  [operation.job_id for operation in machine_schedule]
@@ -106,9 +117,9 @@ class Schedule:
106
117
 
107
118
  @staticmethod
108
119
  def from_dict(
109
- instance: dict[str, Any] | JobShopInstance,
110
- job_sequences: list[list[int]],
111
- metadata: dict[str, Any] | None = None,
120
+ instance: Union[Dict[str, Any], JobShopInstance],
121
+ job_sequences: List[List[int]],
122
+ metadata: Optional[Dict[str, Any]] = None,
112
123
  ) -> Schedule:
113
124
  """Creates a schedule from a dictionary representation."""
114
125
  if isinstance(instance, dict):
@@ -120,7 +131,7 @@ class Schedule:
120
131
  @staticmethod
121
132
  def from_job_sequences(
122
133
  instance: JobShopInstance,
123
- job_sequences: list[list[int]],
134
+ job_sequences: List[List[int]],
124
135
  ) -> Schedule:
125
136
  """Creates an active schedule from a list of job sequences.
126
137
 
@@ -131,14 +142,14 @@ class Schedule:
131
142
 
132
143
  Args:
133
144
  instance:
134
- The `JobShopInstance` object that the schedule is for.
145
+ The :class:`JobShopInstance` object that the schedule is for.
135
146
  job_sequences:
136
147
  A list of lists of job ids. Each list of job ids represents the
137
148
  order of operations on the machine. The machine that the list
138
149
  corresponds to is determined by the index of the list.
139
150
 
140
151
  Returns:
141
- A `Schedule` object with the given job sequences.
152
+ A :class:`Schedule` object with the given job sequences.
142
153
  """
143
154
  from job_shop_lib.dispatching import Dispatcher
144
155
 
@@ -161,7 +172,7 @@ class Schedule:
161
172
  at_least_one_operation_scheduled = True
162
173
 
163
174
  if not at_least_one_operation_scheduled:
164
- raise JobShopLibError(
175
+ raise ValidationError(
165
176
  "Invalid job sequences. No valid operation to schedule."
166
177
  )
167
178
  return dispatcher.schedule
@@ -182,18 +193,19 @@ class Schedule:
182
193
  return max_end_time
183
194
 
184
195
  def is_complete(self) -> bool:
185
- """Returns True if all operations have been scheduled."""
196
+ """Returns ``True`` if all operations have been scheduled."""
186
197
  return self.num_scheduled_operations == self.instance.num_operations
187
198
 
188
199
  def add(self, scheduled_operation: ScheduledOperation):
189
- """Adds a new `ScheduledOperation` to the schedule.
200
+ """Adds a new :class:`ScheduledOperation` to the schedule.
190
201
 
191
202
  Args:
192
203
  scheduled_operation:
193
- The `ScheduledOperation` to add to the schedule.
204
+ The :class:`ScheduledOperation` to add to the schedule.
194
205
 
195
206
  Raises:
196
- ValueError: If the start time of the new operation is before the
207
+ ValidationError:
208
+ If the start time of the new operation is before the
197
209
  end time of the last operation on the same machine. In favor of
198
210
  performance, this method does not checks precedence
199
211
  constraints.
@@ -212,49 +224,45 @@ class Schedule:
212
224
  return
213
225
 
214
226
  last_operation = self.schedule[new_operation.machine_id][-1]
215
- self._check_start_time(new_operation, last_operation)
227
+ if not self._is_valid_start_time(new_operation, last_operation):
228
+ raise ValidationError(
229
+ "Operation cannot be scheduled before the last operation on "
230
+ "the same machine: end time of last operation "
231
+ f"({last_operation.end_time}) > start time of new operation "
232
+ f"({new_operation.start_time})."
233
+ )
216
234
 
217
235
  @staticmethod
218
- def _check_start_time(
236
+ def _is_valid_start_time(
219
237
  scheduled_operation: ScheduledOperation,
220
238
  previous_operation: ScheduledOperation,
221
239
  ):
222
- """Raises a ValueError if the start time of the new operation is before
223
- the end time of the last operation on the same machine."""
224
-
225
- if previous_operation.end_time <= scheduled_operation.start_time:
226
- return
227
-
228
- raise ValueError(
229
- "Operation cannot be scheduled before the last operation on "
230
- "the same machine: end time of last operation "
231
- f"({previous_operation.end_time}) > start time of new operation "
232
- f"({scheduled_operation.start_time})."
233
- )
240
+ return previous_operation.end_time <= scheduled_operation.start_time
234
241
 
235
242
  @staticmethod
236
- def check_schedule(schedule: list[list[ScheduledOperation]]):
237
- """Checks if a schedule is valid and raises a ValueError if it is not.
243
+ def check_schedule(schedule: List[List[ScheduledOperation]]):
244
+ """Checks if a schedule is valid and raises a
245
+ :class:`~exceptions.ValidationError` if it is not.
238
246
 
239
247
  A schedule is considered invalid if:
240
- - A `ScheduledOperation` has a machine id that does not match the
241
- machine id of the machine schedule (the list of
242
- `ScheduledOperation` objects) that it belongs to.
243
- - The start time of a `ScheduledOperation` is before the end time
244
- of the last operation on the same machine.
248
+ - A :class:`ScheduledOperation` has a machine id that does not
249
+ match the machine id of the machine schedule (the list of
250
+ :class:`ScheduledOperation` objects) that it belongs to.
251
+ - The start time of a :class:`ScheduledOperation` is before the
252
+ end time of the last operation on the same machine.
245
253
 
246
254
  Args:
247
255
  schedule:
248
- The schedule (a list of lists of `ScheduledOperation` objects)
249
- to check.
256
+ The schedule (a list of lists of :class:`ScheduledOperation`
257
+ objects) to check.
250
258
 
251
259
  Raises:
252
- ValueError: If the schedule is invalid.
260
+ ValidationError: If the schedule is invalid.
253
261
  """
254
262
  for machine_id, scheduled_operations in enumerate(schedule):
255
263
  for i, scheduled_operation in enumerate(scheduled_operations):
256
264
  if scheduled_operation.machine_id != machine_id:
257
- raise ValueError(
265
+ raise ValidationError(
258
266
  "The machine id of the scheduled operation "
259
267
  f"({ScheduledOperation.machine_id}) does not match "
260
268
  f"the machine id of the machine schedule ({machine_id}"
@@ -264,9 +272,19 @@ class Schedule:
264
272
  if i == 0:
265
273
  continue
266
274
 
267
- Schedule._check_start_time(
275
+ if not Schedule._is_valid_start_time(
268
276
  scheduled_operation, scheduled_operations[i - 1]
269
- )
277
+ ):
278
+ raise ValidationError(
279
+ "Invalid schedule. The start time of the new "
280
+ "operation is before the end time of the last "
281
+ "operation on the same machine."
282
+ "End time of last operation: "
283
+ f"{scheduled_operations[i - 1].end_time}. "
284
+ f"Start time of new operation: "
285
+ f"{scheduled_operation.start_time}. At index "
286
+ f"[{machine_id}][{i}]."
287
+ )
270
288
 
271
289
  def __eq__(self, value: object) -> bool:
272
290
  if not isinstance(value, Schedule):
@@ -1,42 +1,37 @@
1
1
  """Home of the `ScheduledOperation` class."""
2
2
 
3
- from warnings import warn
4
-
5
- from job_shop_lib import Operation, JobShopLibError
3
+ from job_shop_lib import Operation
4
+ from job_shop_lib.exceptions import ValidationError
6
5
 
7
6
 
8
7
  class ScheduledOperation:
9
8
  """Data structure to store a scheduled operation.
10
9
 
11
- Attributes:
10
+ Args:
12
11
  operation:
13
- The `Operation` object that is scheduled.
12
+ The :class:`Operation` object that is scheduled.
14
13
  start_time:
15
14
  The time at which the operation is scheduled to start.
16
15
  machine_id:
17
16
  The id of the machine on which the operation is scheduled.
17
+
18
+ Raises:
19
+ ValidationError:
20
+ If the given machine_id is not in the list of valid machines
21
+ for the operation.
18
22
  """
19
23
 
20
- __slots__ = ("operation", "start_time", "_machine_id")
24
+ __slots__ = {
25
+ "operation": "The :class:`Operation` object that is scheduled.",
26
+ "start_time": "The time at which the operation is scheduled to start.",
27
+ "_machine_id": (
28
+ "The id of the machine on which the operation is scheduled."
29
+ ),
30
+ }
21
31
 
22
32
  def __init__(self, operation: Operation, start_time: int, machine_id: int):
23
- """Initializes the object with the given operation, start time, and
24
- machine id.
25
-
26
- Args:
27
- operation:
28
- The `Operation` object that is scheduled.
29
- start_time:
30
- The time at which the operation is scheduled to start.
31
- machine_id:
32
- The id of the machine on which the operation is scheduled.
33
-
34
- Raises:
35
- ValueError:
36
- If the machine_id is not valid for the operation.
37
- """
38
- self.operation = operation
39
- self.start_time = start_time
33
+ self.operation: Operation = operation
34
+ self.start_time: int = start_time
40
35
  self._machine_id = machine_id
41
36
  self.machine_id = machine_id # Validate machine_id
42
37
 
@@ -49,7 +44,7 @@ class ScheduledOperation:
49
44
  @machine_id.setter
50
45
  def machine_id(self, value: int):
51
46
  if value not in self.operation.machines:
52
- raise JobShopLibError(
47
+ raise ValidationError(
53
48
  f"Operation cannot be scheduled on machine {value}. "
54
49
  f"Valid machines are {self.operation.machines}."
55
50
  )
@@ -57,35 +52,13 @@ class ScheduledOperation:
57
52
 
58
53
  @property
59
54
  def job_id(self) -> int:
60
- """Returns the id of the job that the operation belongs to.
61
-
62
- Raises:
63
- ValueError: If the operation has no job_id.
64
- """
65
-
66
- if self.operation.job_id is None:
67
- raise JobShopLibError("Operation has no job_id.")
55
+ """Returns the id of the job that the operation belongs to."""
68
56
  return self.operation.job_id
69
57
 
70
- @property
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
58
  @property
81
59
  def position_in_job(self) -> int:
82
- """Returns the position (starting at zero) of the operation in the job.
83
-
84
- Raises:
85
- ValueError: If the operation has no position_in_job.
86
- """
87
- if self.operation.position_in_job is None:
88
- raise JobShopLibError("Operation has no position.")
60
+ """Returns the position (starting at zero) of the operation in the
61
+ job."""
89
62
  return self.operation.position_in_job
90
63
 
91
64
  @property
@@ -107,3 +80,6 @@ class ScheduledOperation:
107
80
  and self.start_time == value.start_time
108
81
  and self.machine_id == value.machine_id
109
82
  )
83
+
84
+ def __hash__(self) -> int:
85
+ return hash((self.operation, self.start_time, self.machine_id))
@@ -1,71 +1,94 @@
1
- """Package for loading benchmark instances.
1
+ """Contains functions to load benchmark instances.
2
2
 
3
- All benchmark instances are stored in a single JSON file. This module provides
4
- functions to load the instances from the file and return them as
5
- JobShopInstance objects.
3
+ .. autosummary::
4
+
5
+ load_all_benchmark_instances
6
+ load_benchmark_instance
7
+ load_benchmark_json
8
+
9
+ You can load a benchmark instance from the library:
10
+
11
+ .. code-block:: python
12
+
13
+ from job_shop_lib.benchmarking import load_benchmark_instance
14
+
15
+ ft06 = load_benchmark_instance("ft06")
16
+
17
+
18
+ This package contains functions to load the instances from the file
19
+ and return them as :class:`JobShopInstance` objects without having to download
20
+ them manually.
6
21
 
7
22
  The contributions to this benchmark dataset are as follows:
8
23
 
9
- abz5-9: This subset, comprising five instances, was introduced by Adams et
10
- al. (1988).
11
- ft06, ft10, ft20: These three instances are attributed to the work of
12
- Fisher and Thompson, as detailed in their 1963 work.
13
- la01-40: A collection of forty instances, this group was contributed by
14
- Lawrence, as referenced in his 1984 report.
15
- orb01-10: Ten instances in this category were provided by Applegate and
16
- Cook, as seen in their 1991 study.
17
- swb01-20: This segment, encompassing twenty instances, was contributed by
18
- Storer et al., as per their 1992 article.
19
- yn1-4: Yamada and Nakano are credited with the addition of four instances
20
- in this group, as found in their 1992 paper.
21
- ta01-80: The largest contribution, consisting of eighty instances, was
22
- made by Taillard, as documented in his 1993 paper.
24
+ - ``abz5-9``: by Adams et al. (1988).
23
25
 
24
- The metadata from these instances has been updated using data from:
26
+ - ``ft06``, ``ft10``, ``ft20``: by Fisher and Thompson (1963).
27
+
28
+ - ``la01-40``: by Lawrence (1984)
25
29
 
26
- Thomas Weise. jsspInstancesAndResults. Accessed in January 2024.
27
- Available at: https://github.com/thomasWeise/jsspInstancesAndResults
30
+ - ``orb01-10``: by Applegate and Cook (1991).
31
+
32
+ - ``swb01-20``: by Storer et al. (1992).
33
+
34
+ - ``yn1-4``: by Yamada and Nakano (1992).
35
+
36
+ - ``ta01-80``: by Taillard (1993).
37
+
38
+ The metadata from these instances has been updated using data from:
39
+ https://github.com/thomasWeise/jsspInstancesAndResults
28
40
 
29
41
  It includes the following information:
30
- - "optimum" (int | None): The optimal makespan for the instance.
31
- - "lower_bound" (int): The lower bound for the makespan. If
32
- optimality is known, it is equal to the optimum.
33
- - "upper_bound" (int): The upper bound for the makespan. If
34
- optimality is known, it is equal to the optimum.
35
- - "reference" (str): The paper or source where the instance was first
36
- introduced.
42
+ - "optimum" (``int | None``): The optimal makespan for the instance.
43
+ - "lower_bound" (``int``): The lower bound for the makespan. If
44
+ optimality is known, it is equal to the optimum.
45
+ - "upper_bound" (``int``): The upper bound for the makespan. If
46
+ optimality is known, it is equal to the optimum.
47
+ - "reference" (``str``): The paper or source where the instance was first
48
+ introduced.
49
+
50
+ .. code-block:: python
51
+
52
+ >>> ft06.metadata
53
+ {'optimum': 55,
54
+ 'upper_bound': 55,
55
+ 'lower_bound': 55,
56
+ 'reference': "J.F. Muth, G.L. Thompson. 'Industrial scheduling.',
57
+ Englewood Cliffs, NJ, Prentice-Hall, 1963."}
37
58
 
38
59
  References:
60
+
39
61
  - J. Adams, E. Balas, and D. Zawack, "The shifting bottleneck procedure
40
- for job shop scheduling," Management Science, vol. 34, no. 3,
41
- pp. 391–401, 1988.
62
+ for job shop scheduling," Management Science, vol. 34, no. 3,
63
+ pp. 391–401, 1988.
42
64
 
43
65
  - J.F. Muth and G.L. Thompson, Industrial scheduling. Englewood Cliffs,
44
- NJ: Prentice-Hall, 1963.
66
+ NJ: Prentice-Hall, 1963.
45
67
 
46
68
  - S. Lawrence, "Resource constrained project scheduling: An experimental
47
- investigation of heuristic scheduling techniques (Supplement),"
48
- Carnegie-Mellon University, Graduate School of Industrial
49
- Administration, Pittsburgh, Pennsylvania, 1984.
69
+ investigation of heuristic scheduling techniques (Supplement),"
70
+ Carnegie-Mellon University, Graduate School of Industrial
71
+ Administration, Pittsburgh, Pennsylvania, 1984.
50
72
 
51
73
  - D. Applegate and W. Cook, "A computational study of job-shop
52
- scheduling," ORSA Journal on Computer, vol. 3, no. 2, pp. 149–156,
53
- 1991.
74
+ scheduling," ORSA Journal on Computer, vol. 3, no. 2, pp. 149–156,
75
+ 1991.
54
76
 
55
77
  - R.H. Storer, S.D. Wu, and R. Vaccari, "New search spaces for
56
- sequencing problems with applications to job-shop scheduling,"
57
- Management Science, vol. 38, no. 10, pp. 1495–1509, 1992.
78
+ sequencing problems with applications to job-shop scheduling,"
79
+ Management Science, vol. 38, no. 10, pp. 1495–1509, 1992.
58
80
 
59
81
  - T. Yamada and R. Nakano, "A genetic algorithm applicable to
60
- large-scale job-shop problems," in Proceedings of the Second
61
- International Workshop on Parallel Problem Solving from Nature
62
- (PPSN'2), Brussels, Belgium, pp. 281–290, 1992.
82
+ large-scale job-shop problems," in Proceedings of the Second
83
+ International Workshop on Parallel Problem Solving from Nature
84
+ (PPSN'2), Brussels, Belgium, pp. 281–290, 1992.
63
85
 
64
86
  - E. Taillard, "Benchmarks for basic scheduling problems," European
65
- Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
87
+ Journal of Operational Research, vol. 64, no. 2, pp. 278–285, 1993.
88
+
66
89
  """
67
90
 
68
- from job_shop_lib.benchmarking.load_benchmark import (
91
+ from job_shop_lib.benchmarking._load_benchmark import (
69
92
  load_all_benchmark_instances,
70
93
  load_benchmark_instance,
71
94
  load_benchmark_json,
@@ -0,0 +1,88 @@
1
+ """Contains functions to load benchmark instances from a JSON file."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ import functools
6
+ import json
7
+ from importlib import resources
8
+
9
+ from job_shop_lib import JobShopInstance
10
+
11
+
12
+ @functools.cache
13
+ def load_all_benchmark_instances() -> Dict[str, JobShopInstance]:
14
+ """Loads all benchmark instances available.
15
+
16
+ Returns:
17
+ A dictionary containing the names of the benchmark instances as keys
18
+ and the corresponding :class:`JobShopInstance` objects as values.
19
+
20
+ """
21
+ benchmark_instances_dict = load_benchmark_json()
22
+ return {
23
+ name: load_benchmark_instance(name)
24
+ for name in benchmark_instances_dict
25
+ }
26
+
27
+
28
+ def load_benchmark_instance(name: str) -> JobShopInstance:
29
+ """Loads a specific benchmark instance.
30
+
31
+ Calls to :func:`load_benchmark_json` to load the benchmark instances from
32
+ the JSON file. The instance is then loaded from the dictionary using the
33
+ provided name. Since `load_benchmark_json` is cached, the file is only
34
+ read once.
35
+
36
+ Args:
37
+ name: The name of the benchmark instance to load. Can be one of the
38
+ following: "abz5-9", "ft06", "ft10", "ft20", "la01-40", "orb01-10",
39
+ "swb01-20", "yn1-4", or "ta01-80".
40
+ """
41
+ benchmark_dict = load_benchmark_json()[name]
42
+ return JobShopInstance.from_matrices(
43
+ duration_matrix=benchmark_dict["duration_matrix"],
44
+ machines_matrix=benchmark_dict["machines_matrix"],
45
+ name=name,
46
+ metadata=benchmark_dict["metadata"],
47
+ )
48
+
49
+
50
+ @functools.cache
51
+ def load_benchmark_json() -> Dict[str, Dict[str, Any]]:
52
+ """Loads the raw JSON file containing the benchmark instances.
53
+
54
+ Results are cached to avoid reading the file multiple times.
55
+
56
+ Each instance is represented as a dictionary with the following keys
57
+ and values:
58
+
59
+ * ``name`` (``str``): The name of the instance.
60
+ * ``duration_matrix`` (``list[list[int]]``): The matrix containing the
61
+ durations for each operation.
62
+ * ``machines_matrix`` (``list[list[int]]``): The matrix containing the
63
+ machines for each operation.
64
+ * ``metadata`` (``dict[str, Any]``): A dictionary containing metadata
65
+ about the instance. The keys are:
66
+
67
+ * ``optimum`` (``int | None``)
68
+ * ``lower_bound`` (``int``)
69
+ * ``upper_bound`` (``int``)
70
+ * ``reference`` (``str``)
71
+
72
+ Note:
73
+ This dictionary is the standard way to represent instances in
74
+ JobShopLib. You can obtain the dictionary of each instance with
75
+ :class:`~job_shop_lib.JobShopInstance`'s
76
+ :meth:`~job_shop_lib.JobShopInstance.to_dict` method.
77
+
78
+ Returns:
79
+ The dictionary containing the benchmark instances represented as
80
+ dictionaries.
81
+ """
82
+ benchmark_file = (
83
+ resources.files("job_shop_lib.benchmarking")
84
+ / "benchmark_instances.json"
85
+ )
86
+
87
+ with benchmark_file.open("r", encoding="utf-8") as f:
88
+ return json.load(f)
@@ -0,0 +1,13 @@
1
+ """Contains solvers based on Constraint Programming (CP).
2
+
3
+ CP defines the Job Shop Scheduling Problem through constraints
4
+ and focuses on finding a feasible solution. It usually aims to minimize an
5
+ objective function, typically the makespan. One of the advantages of these
6
+ methods is that they are not restricted to linear constraints.
7
+ """
8
+
9
+ from job_shop_lib.constraint_programming._ortools_solver import ORToolsSolver
10
+
11
+ __all__ = [
12
+ "ORToolsSolver",
13
+ ]