job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- job_shop_lib/__init__.py +16 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +9 -4
- job_shop_lib/_operation.py +95 -0
- job_shop_lib/{schedule.py → _schedule.py} +73 -54
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +13 -37
- 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} +57 -18
- job_shop_lib/dispatching/__init__.py +45 -41
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +153 -80
- job_shop_lib/dispatching/_dispatcher_observer_config.py +54 -0
- job_shop_lib/dispatching/_factories.py +125 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +4 -6
- job_shop_lib/dispatching/{pruning_functions.py → _ready_operation_filters.py} +6 -35
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +69 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +16 -10
- job_shop_lib/dispatching/feature_observers/{composite_feature_observer.py → _composite_feature_observer.py} +84 -2
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +6 -17
- job_shop_lib/dispatching/feature_observers/{earliest_start_time_observer.py → _earliest_start_time_observer.py} +114 -35
- job_shop_lib/dispatching/feature_observers/{factory.py → _factory.py} +31 -5
- job_shop_lib/dispatching/feature_observers/{feature_observer.py → _feature_observer.py} +59 -16
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +33 -0
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +1 -8
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +51 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +82 -0
- job_shop_lib/dispatching/{dispatching_rule_solver.py → rules/_dispatching_rule_solver.py} +44 -15
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +74 -21
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +69 -0
- job_shop_lib/dispatching/rules/_utils.py +127 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +2 -2
- job_shop_lib/generation/{general_instance_generator.py → _general_instance_generator.py} +26 -7
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +13 -3
- job_shop_lib/graphs/__init__.py +17 -6
- job_shop_lib/graphs/{job_shop_graph.py → _job_shop_graph.py} +81 -2
- job_shop_lib/graphs/{node.py → _node.py} +18 -12
- job_shop_lib/graphs/graph_updaters/__init__.py +13 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +59 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +154 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/reinforcement_learning/__init__.py +41 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +366 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +85 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +337 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +61 -0
- job_shop_lib/reinforcement_learning/_utils.py +96 -0
- job_shop_lib/visualization/__init__.py +20 -4
- job_shop_lib/visualization/{agent_task_graph.py → _agent_task_graph.py} +28 -9
- job_shop_lib/visualization/_gantt_chart_creator.py +219 -0
- job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +388 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/METADATA +68 -44
- job_shop_lib-1.0.0a1.dist-info/RECORD +66 -0
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/factories.py +0 -206
- 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/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/generators/transformations.py +0 -164
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- /job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +0 -0
- /job_shop_lib/generation/{transformations.py → _transformations.py} +0 -0
- /job_shop_lib/graphs/{build_agent_task_graph.py → _build_agent_task_graph.py} +0 -0
- /job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +0 -0
- /job_shop_lib/graphs/{constants.py → _constants.py} +0 -0
- /job_shop_lib/visualization/{disjunctive_graph.py → _disjunctive_graph.py} +0 -0
- /job_shop_lib/visualization/{gantt_chart.py → _gantt_chart.py} +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/LICENSE +0 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
"""Contains functions to load benchmark instances from a JSON file."""
|
2
|
+
|
3
|
+
from typing import Any
|
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
|
+
]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Home of the ORToolsSolver class."""
|
1
|
+
"""Home of the `ORToolsSolver` class."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
@@ -13,15 +13,31 @@ from job_shop_lib import (
|
|
13
13
|
ScheduledOperation,
|
14
14
|
Operation,
|
15
15
|
)
|
16
|
-
from job_shop_lib import
|
16
|
+
from job_shop_lib import BaseSolver
|
17
|
+
from job_shop_lib.exceptions import NoSolutionFoundError
|
17
18
|
|
18
19
|
|
19
20
|
class ORToolsSolver(BaseSolver):
|
20
|
-
"""
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
"""Solver based on `OR-Tools' CP-SAT
|
22
|
+
<https://developers.google.com/optimization/cp/cp_solver>`_ solver.
|
23
|
+
|
24
|
+
Attributes:
|
25
|
+
log_search_progress (``bool``):
|
26
|
+
Whether to log the search progress to the console.
|
27
|
+
max_time_in_seconds (``float | None``):
|
28
|
+
The maximum time in seconds to allow the solver to search for
|
29
|
+
a solution. If no solution is found within this time, a
|
30
|
+
:class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
|
31
|
+
raised. If ``None``, the solver will run until an optimal
|
32
|
+
solution is found.
|
33
|
+
model (``cp_model.CpModel``):
|
34
|
+
The `OR-Tools' CP-SAT model
|
35
|
+
<https://developers.google.com/optimization/reference/python/sat/
|
36
|
+
python/cp_model#cp_model.CpModel>`_.
|
37
|
+
solver (``cp_model.CpSolver``):
|
38
|
+
The `OR-Tools' CP-SAT solver
|
39
|
+
<https://developers.google.com/optimization/reference/python/sat/
|
40
|
+
python/cp_model#cp_model.CpSolver>`_.
|
25
41
|
"""
|
26
42
|
|
27
43
|
def __init__(
|
@@ -29,10 +45,22 @@ class ORToolsSolver(BaseSolver):
|
|
29
45
|
max_time_in_seconds: float | None = None,
|
30
46
|
log_search_progress: bool = False,
|
31
47
|
):
|
48
|
+
"""Initializes the solver.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
max_time_in_seconds:
|
52
|
+
The maximum time in seconds to allow the solver to search for
|
53
|
+
a solution. If no solution is found within this time, a
|
54
|
+
:class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
|
55
|
+
raised. If ``None``, the solver will run until an optimal
|
56
|
+
solution is found.
|
57
|
+
log_search_progress:
|
58
|
+
Whether to log the search progress to the console.
|
59
|
+
"""
|
32
60
|
self.log_search_progress = log_search_progress
|
33
61
|
self.max_time_in_seconds = max_time_in_seconds
|
34
62
|
|
35
|
-
self.
|
63
|
+
self._makespan: cp_model.IntVar | None = None
|
36
64
|
self.model = cp_model.CpModel()
|
37
65
|
self.solver = cp_model.CpSolver()
|
38
66
|
self._operations_start: dict[Operation, tuple[IntVar, IntVar]] = {}
|
@@ -46,9 +74,22 @@ class ORToolsSolver(BaseSolver):
|
|
46
74
|
"""Creates the variables, constraints and objective, and solves the
|
47
75
|
problem.
|
48
76
|
|
49
|
-
|
50
|
-
|
51
|
-
|
77
|
+
Args:
|
78
|
+
instance: The job shop instance to be solved.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
The best schedule found by the solver.
|
82
|
+
Its metadata contains the following information:
|
83
|
+
|
84
|
+
* status (``str``): ``"optimal"`` or ``"feasible"``
|
85
|
+
* elapsed_time (``float``): The time taken to solve the problem
|
86
|
+
* makespan (``int``): The total duration of the schedule
|
87
|
+
* solved_by (``str``): ``"ORToolsSolver"``
|
88
|
+
|
89
|
+
Raises:
|
90
|
+
NoSolutionFoundError:
|
91
|
+
If no solution could be found for the given problem within the
|
92
|
+
time limit.
|
52
93
|
"""
|
53
94
|
self._initialize_model(instance)
|
54
95
|
|
@@ -61,14 +102,12 @@ class ORToolsSolver(BaseSolver):
|
|
61
102
|
f"No solution could be found for the given problem. "
|
62
103
|
f"Elapsed time: {elapsed_time} seconds."
|
63
104
|
)
|
64
|
-
|
65
|
-
# Check added to satisfy mypy
|
66
|
-
raise ValueError("The makespan variable was not set.")
|
105
|
+
assert self._makespan is not None # Make mypy happy
|
67
106
|
|
68
107
|
metadata = {
|
69
108
|
"status": "optimal" if status == cp_model.OPTIMAL else "feasible",
|
70
109
|
"elapsed_time": elapsed_time,
|
71
|
-
"makespan": self.solver.Value(self.
|
110
|
+
"makespan": self.solver.Value(self._makespan),
|
72
111
|
"solved_by": "ORToolsSolver",
|
73
112
|
}
|
74
113
|
return self._create_schedule(instance, metadata)
|
@@ -154,12 +193,12 @@ class ORToolsSolver(BaseSolver):
|
|
154
193
|
def _set_objective(self, instance: JobShopInstance):
|
155
194
|
"""The objective is to minimize the makespan, which is the total
|
156
195
|
duration of the schedule."""
|
157
|
-
self.
|
196
|
+
self._makespan = self.model.NewIntVar(
|
158
197
|
0, instance.total_duration, "makespan"
|
159
198
|
)
|
160
199
|
end_times = [end for _, end in self._operations_start.values()]
|
161
|
-
self.model.AddMaxEquality(self.
|
162
|
-
self.model.Minimize(self.
|
200
|
+
self.model.AddMaxEquality(self._makespan, end_times)
|
201
|
+
self.model.Minimize(self._makespan)
|
163
202
|
|
164
203
|
def _add_job_constraints(self, instance: JobShopInstance):
|
165
204
|
"""Adds job constraints to the model. Operations within a job must be
|
@@ -1,52 +1,56 @@
|
|
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
|
-
|
4
|
+
.. autosummary::
|
5
|
+
:nosignatures:
|
6
|
+
|
7
|
+
Dispatcher
|
8
|
+
DispatcherObserver
|
9
|
+
HistoryObserver
|
10
|
+
UnscheduledOperationsObserver
|
11
|
+
ReadyOperationsFilter
|
12
|
+
DispatcherObserverConfig
|
13
|
+
filter_dominated_operations
|
14
|
+
filter_non_immediate_machines
|
15
|
+
create_composite_operation_filter
|
16
|
+
ReadyOperationsFilterType
|
17
|
+
ready_operations_filter_factory
|
18
|
+
|
19
|
+
Dispatching refers to the decision-making process of selecting which job
|
20
|
+
should be processed next on a particular machine when that machine becomes
|
21
|
+
available.
|
22
|
+
"""
|
23
|
+
|
24
|
+
from ._dispatcher import Dispatcher, DispatcherObserver
|
25
|
+
from ._history_observer import (
|
26
|
+
HistoryObserver,
|
12
27
|
)
|
13
|
-
from
|
14
|
-
|
15
|
-
prune_non_immediate_machines,
|
16
|
-
create_composite_pruning_function,
|
28
|
+
from ._unscheduled_operations_observer import (
|
29
|
+
UnscheduledOperationsObserver,
|
17
30
|
)
|
18
|
-
from
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
dispatching_rule_factory,
|
23
|
-
machine_chooser_factory,
|
24
|
-
pruning_function_factory,
|
25
|
-
composite_pruning_function_factory,
|
31
|
+
from ._ready_operation_filters import (
|
32
|
+
filter_dominated_operations,
|
33
|
+
filter_non_immediate_machines,
|
34
|
+
ReadyOperationsFilter,
|
26
35
|
)
|
27
|
-
from
|
28
|
-
|
36
|
+
from ._dispatcher_observer_config import DispatcherObserverConfig
|
37
|
+
from ._factories import (
|
38
|
+
ReadyOperationsFilterType,
|
39
|
+
ready_operations_filter_factory,
|
40
|
+
create_composite_operation_filter,
|
29
41
|
)
|
30
42
|
|
31
43
|
|
32
44
|
__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
45
|
"Dispatcher",
|
43
|
-
"
|
44
|
-
"
|
45
|
-
"
|
46
|
-
"
|
47
|
-
"
|
48
|
-
"pruning_function_factory",
|
49
|
-
"composite_pruning_function_factory",
|
46
|
+
"filter_dominated_operations",
|
47
|
+
"filter_non_immediate_machines",
|
48
|
+
"create_composite_operation_filter",
|
49
|
+
"ReadyOperationsFilterType",
|
50
|
+
"ready_operations_filter_factory",
|
50
51
|
"DispatcherObserver",
|
51
|
-
"
|
52
|
+
"HistoryObserver",
|
53
|
+
"DispatcherObserverConfig",
|
54
|
+
"UnscheduledOperationsObserver",
|
55
|
+
"ReadyOperationsFilter",
|
52
56
|
]
|