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.
- job_shop_lib/__init__.py +19 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
- job_shop_lib/_operation.py +118 -0
- job_shop_lib/{schedule.py → _schedule.py} +102 -84
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
- job_shop_lib/benchmarking/__init__.py +66 -43
- job_shop_lib/benchmarking/_load_benchmark.py +88 -0
- job_shop_lib/constraint_programming/__init__.py +13 -0
- job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +77 -22
- job_shop_lib/dispatching/__init__.py +51 -42
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
- job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
- job_shop_lib/dispatching/_factories.py +135 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
- job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
- job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
- job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +87 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
- job_shop_lib/dispatching/rules/_utils.py +128 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +10 -2
- job_shop_lib/generation/_general_instance_generator.py +165 -0
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +37 -26
- job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +30 -12
- job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
- job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
- job_shop_lib/graphs/_constants.py +38 -0
- job_shop_lib/graphs/_job_shop_graph.py +320 -0
- job_shop_lib/graphs/_node.py +182 -0
- job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
- job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/py.typed +0 -0
- job_shop_lib/reinforcement_learning/__init__.py +68 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
- job_shop_lib/reinforcement_learning/_utils.py +199 -0
- job_shop_lib/visualization/__init__.py +0 -25
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
- job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
- job_shop_lib-1.0.0.dist-info/RECORD +73 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
- job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
- job_shop_lib/dispatching/feature_observers/factory.py +0 -58
- job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
- job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
- job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
- job_shop_lib/dispatching/pruning_functions.py +0 -116
- job_shop_lib/generation/general_instance_generator.py +0 -169
- job_shop_lib/generation/transformations.py +0 -164
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/graphs/constants.py +0 -21
- job_shop_lib/graphs/job_shop_graph.py +0 -202
- job_shop_lib/graphs/node.py +0 -166
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/agent_task_graph.py +0 -257
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib/visualization/disjunctive_graph.py +0 -210
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -1,7 +1,8 @@
|
|
1
|
-
"""Home of the ORToolsSolver class."""
|
1
|
+
"""Home of the `ORToolsSolver` class."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
+
from typing import Any, Dict, List, Tuple
|
5
6
|
import time
|
6
7
|
|
7
8
|
from ortools.sat.python import cp_model
|
@@ -13,15 +14,41 @@ from job_shop_lib import (
|
|
13
14
|
ScheduledOperation,
|
14
15
|
Operation,
|
15
16
|
)
|
16
|
-
from job_shop_lib import
|
17
|
+
from job_shop_lib import BaseSolver
|
18
|
+
from job_shop_lib.exceptions import NoSolutionFoundError
|
17
19
|
|
18
20
|
|
19
21
|
class ORToolsSolver(BaseSolver):
|
20
|
-
"""
|
21
|
-
|
22
|
+
"""Solver based on `OR-Tools' CP-SAT
|
23
|
+
<https://developers.google.com/optimization/cp/cp_solver>`_ solver.
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
Attributes:
|
26
|
+
log_search_progress (bool):
|
27
|
+
Whether to log the search progress to the console.
|
28
|
+
max_time_in_seconds (float | None):
|
29
|
+
The maximum time in seconds to allow the solver to search for
|
30
|
+
a solution. If no solution is found within this time, a
|
31
|
+
:class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
|
32
|
+
raised. If ``None``, the solver will run until an optimal
|
33
|
+
solution is found.
|
34
|
+
model (cp_model.CpModel):
|
35
|
+
The `OR-Tools' CP-SAT model
|
36
|
+
<https://developers.google.com/optimization/reference/python/sat/
|
37
|
+
python/cp_model#cp_model.CpModel>`_.
|
38
|
+
solver (cp_model.CpSolver):
|
39
|
+
The `OR-Tools' CP-SAT solver
|
40
|
+
<https://developers.google.com/optimization/reference/python/sat/
|
41
|
+
python/cp_model#cp_model.CpSolver>`_.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
max_time_in_seconds:
|
45
|
+
The maximum time in seconds to allow the solver to search for
|
46
|
+
a solution. If no solution is found within this time, a
|
47
|
+
:class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
|
48
|
+
raised. If ``None``, the solver will run until an optimal
|
49
|
+
solution is found.
|
50
|
+
log_search_progress:
|
51
|
+
Whether to log the search progress to the console.
|
25
52
|
"""
|
26
53
|
|
27
54
|
def __init__(
|
@@ -32,12 +59,29 @@ class ORToolsSolver(BaseSolver):
|
|
32
59
|
self.log_search_progress = log_search_progress
|
33
60
|
self.max_time_in_seconds = max_time_in_seconds
|
34
61
|
|
35
|
-
self.
|
62
|
+
self._makespan: cp_model.IntVar | None = None
|
36
63
|
self.model = cp_model.CpModel()
|
37
64
|
self.solver = cp_model.CpSolver()
|
38
|
-
self._operations_start:
|
65
|
+
self._operations_start: Dict[Operation, Tuple[IntVar, IntVar]] = {}
|
39
66
|
|
40
67
|
def __call__(self, instance: JobShopInstance) -> Schedule:
|
68
|
+
"""Equivalent to calling the :meth:`~ORToolsSolver.solve` method.
|
69
|
+
|
70
|
+
This method is necessary because, in JobShopLib, solvers are defined
|
71
|
+
as callables that receive an instance and return a schedule.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
instance: The job shop instance to be solved.
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
The best schedule found by the solver.
|
78
|
+
Its metadata contains the following information:
|
79
|
+
|
80
|
+
* status (``str``): ``"optimal"`` or ``"feasible"``
|
81
|
+
* elapsed_time (``float``): The time taken to solve the problem
|
82
|
+
* makespan (``int``): The total duration of the schedule
|
83
|
+
* solved_by (``str``): ``"ORToolsSolver"``
|
84
|
+
"""
|
41
85
|
# Re-defined here since we already add metadata to the schedule in
|
42
86
|
# the solve method.
|
43
87
|
return self.solve(instance)
|
@@ -46,9 +90,22 @@ class ORToolsSolver(BaseSolver):
|
|
46
90
|
"""Creates the variables, constraints and objective, and solves the
|
47
91
|
problem.
|
48
92
|
|
49
|
-
|
50
|
-
|
51
|
-
|
93
|
+
Args:
|
94
|
+
instance: The job shop instance to be solved.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
The best schedule found by the solver.
|
98
|
+
Its metadata contains the following information:
|
99
|
+
|
100
|
+
* status (``str``): ``"optimal"`` or ``"feasible"``
|
101
|
+
* elapsed_time (``float``): The time taken to solve the problem
|
102
|
+
* makespan (``int``): The total duration of the schedule
|
103
|
+
* solved_by (``str``): ``"ORToolsSolver"``
|
104
|
+
|
105
|
+
Raises:
|
106
|
+
NoSolutionFoundError:
|
107
|
+
If no solution could be found for the given problem within the
|
108
|
+
time limit.
|
52
109
|
"""
|
53
110
|
self._initialize_model(instance)
|
54
111
|
|
@@ -61,14 +118,12 @@ class ORToolsSolver(BaseSolver):
|
|
61
118
|
f"No solution could be found for the given problem. "
|
62
119
|
f"Elapsed time: {elapsed_time} seconds."
|
63
120
|
)
|
64
|
-
|
65
|
-
# Check added to satisfy mypy
|
66
|
-
raise ValueError("The makespan variable was not set.")
|
121
|
+
assert self._makespan is not None # Make mypy happy
|
67
122
|
|
68
123
|
metadata = {
|
69
124
|
"status": "optimal" if status == cp_model.OPTIMAL else "feasible",
|
70
125
|
"elapsed_time": elapsed_time,
|
71
|
-
"makespan": self.solver.Value(self.
|
126
|
+
"makespan": self.solver.Value(self._makespan),
|
72
127
|
"solved_by": "ORToolsSolver",
|
73
128
|
}
|
74
129
|
return self._create_schedule(instance, metadata)
|
@@ -97,15 +152,15 @@ class ORToolsSolver(BaseSolver):
|
|
97
152
|
self._set_objective(instance)
|
98
153
|
|
99
154
|
def _create_schedule(
|
100
|
-
self, instance: JobShopInstance, metadata:
|
155
|
+
self, instance: JobShopInstance, metadata: Dict[str, Any]
|
101
156
|
) -> Schedule:
|
102
157
|
"""Creates a Schedule object from the solution."""
|
103
|
-
operations_start:
|
158
|
+
operations_start: Dict[Operation, int] = {
|
104
159
|
operation: self.solver.Value(start_var)
|
105
160
|
for operation, (start_var, _) in self._operations_start.items()
|
106
161
|
}
|
107
162
|
|
108
|
-
unsorted_schedule:
|
163
|
+
unsorted_schedule: List[List[ScheduledOperation]] = [
|
109
164
|
[] for _ in range(instance.num_machines)
|
110
165
|
]
|
111
166
|
for operation, start_time in operations_start.items():
|
@@ -154,12 +209,12 @@ class ORToolsSolver(BaseSolver):
|
|
154
209
|
def _set_objective(self, instance: JobShopInstance):
|
155
210
|
"""The objective is to minimize the makespan, which is the total
|
156
211
|
duration of the schedule."""
|
157
|
-
self.
|
212
|
+
self._makespan = self.model.NewIntVar(
|
158
213
|
0, instance.total_duration, "makespan"
|
159
214
|
)
|
160
215
|
end_times = [end for _, end in self._operations_start.values()]
|
161
|
-
self.model.AddMaxEquality(self.
|
162
|
-
self.model.Minimize(self.
|
216
|
+
self.model.AddMaxEquality(self._makespan, end_times)
|
217
|
+
self.model.Minimize(self._makespan)
|
163
218
|
|
164
219
|
def _add_job_constraints(self, instance: JobShopInstance):
|
165
220
|
"""Adds job constraints to the model. Operations within a job must be
|
@@ -180,7 +235,7 @@ class ORToolsSolver(BaseSolver):
|
|
180
235
|
each machine."""
|
181
236
|
|
182
237
|
# Create interval variables for each operation on each machine
|
183
|
-
machines_operations:
|
238
|
+
machines_operations: List[List[Tuple[Tuple[IntVar, IntVar], int]]] = [
|
184
239
|
[] for _ in range(instance.num_machines)
|
185
240
|
]
|
186
241
|
for job in instance.jobs:
|
@@ -1,52 +1,61 @@
|
|
1
|
-
"""
|
2
|
-
Problem step-by-step.
|
1
|
+
"""Contains classes and functions to solve the Job Shop Scheduling
|
2
|
+
Problem step-by-step.
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
.. autosummary::
|
5
|
+
:nosignatures:
|
6
|
+
|
7
|
+
Dispatcher
|
8
|
+
DispatcherObserver
|
9
|
+
HistoryObserver
|
10
|
+
UnscheduledOperationsObserver
|
11
|
+
OptimalOperationsObserver
|
12
|
+
ReadyOperationsFilter
|
13
|
+
DispatcherObserverConfig
|
14
|
+
filter_dominated_operations
|
15
|
+
filter_non_immediate_machines
|
16
|
+
create_composite_operation_filter
|
17
|
+
ReadyOperationsFilterType
|
18
|
+
ready_operations_filter_factory
|
19
|
+
|
20
|
+
Dispatching refers to the decision-making process of selecting which job
|
21
|
+
should be processed next on a particular machine when that machine becomes
|
22
|
+
available.
|
23
|
+
"""
|
24
|
+
|
25
|
+
from ._dispatcher import Dispatcher, DispatcherObserver
|
26
|
+
from ._history_observer import (
|
27
|
+
HistoryObserver,
|
17
28
|
)
|
18
|
-
from
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
29
|
+
from ._unscheduled_operations_observer import UnscheduledOperationsObserver
|
30
|
+
from ._optimal_operations_observer import OptimalOperationsObserver
|
31
|
+
from ._ready_operation_filters import (
|
32
|
+
filter_dominated_operations,
|
33
|
+
filter_non_immediate_machines,
|
34
|
+
ReadyOperationsFilter,
|
35
|
+
filter_non_idle_machines,
|
36
|
+
filter_non_immediate_operations,
|
26
37
|
)
|
27
|
-
from
|
28
|
-
|
38
|
+
from ._dispatcher_observer_config import DispatcherObserverConfig
|
39
|
+
from ._factories import (
|
40
|
+
ReadyOperationsFilterType,
|
41
|
+
ready_operations_filter_factory,
|
42
|
+
create_composite_operation_filter,
|
29
43
|
)
|
30
44
|
|
31
45
|
|
32
46
|
__all__ = [
|
33
|
-
"dispatching_rule_factory",
|
34
|
-
"machine_chooser_factory",
|
35
|
-
"shortest_processing_time_rule",
|
36
|
-
"first_come_first_served_rule",
|
37
|
-
"most_work_remaining_rule",
|
38
|
-
"most_operations_remaining_rule",
|
39
|
-
"random_operation_rule",
|
40
|
-
"DispatchingRule",
|
41
|
-
"MachineChooser",
|
42
47
|
"Dispatcher",
|
43
|
-
"
|
44
|
-
"
|
45
|
-
"
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"pruning_function_factory",
|
49
|
-
"composite_pruning_function_factory",
|
48
|
+
"filter_dominated_operations",
|
49
|
+
"filter_non_immediate_machines",
|
50
|
+
"create_composite_operation_filter",
|
51
|
+
"ReadyOperationsFilterType",
|
52
|
+
"ready_operations_filter_factory",
|
50
53
|
"DispatcherObserver",
|
51
|
-
"
|
54
|
+
"HistoryObserver",
|
55
|
+
"DispatcherObserverConfig",
|
56
|
+
"UnscheduledOperationsObserver",
|
57
|
+
"ReadyOperationsFilter",
|
58
|
+
"filter_non_idle_machines",
|
59
|
+
"filter_non_immediate_operations",
|
60
|
+
"OptimalOperationsObserver",
|
52
61
|
]
|