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
@@ -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 NoSolutionFoundError, BaseSolver
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
- """A solver for the job shop scheduling problem using constraint
21
- programming.
22
+ """Solver based on `OR-Tools' CP-SAT
23
+ <https://developers.google.com/optimization/cp/cp_solver>`_ solver.
22
24
 
23
- This solver uses the ortools library to solve the job shop scheduling
24
- problem using constraint programming.
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.makespan: cp_model.IntVar | None = None
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: dict[Operation, tuple[IntVar, IntVar]] = {}
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
- If a solution is found, it extracts and returns the start times of
50
- each operation and the makespan. If no solution is found, it raises
51
- a NoSolutionFound exception.
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
- if self.makespan is None:
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.makespan),
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: dict[str, object]
155
+ self, instance: JobShopInstance, metadata: Dict[str, Any]
101
156
  ) -> Schedule:
102
157
  """Creates a Schedule object from the solution."""
103
- operations_start: dict[Operation, int] = {
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: list[list[ScheduledOperation]] = [
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.makespan = self.model.NewIntVar(
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.makespan, end_times)
162
- self.model.Minimize(self.makespan)
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: list[list[tuple[tuple[IntVar, IntVar], int]]] = [
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
- """Package containing all the functionality to solve the Job Shop Scheduling
2
- Problem step-by-step."""
1
+ """Contains classes and functions to solve the Job Shop Scheduling
2
+ Problem step-by-step.
3
3
 
4
- from job_shop_lib.dispatching.dispatcher import Dispatcher, DispatcherObserver
5
- from job_shop_lib.dispatching.history_tracker import HistoryTracker
6
- from job_shop_lib.dispatching.dispatching_rules import (
7
- shortest_processing_time_rule,
8
- first_come_first_served_rule,
9
- most_work_remaining_rule,
10
- most_operations_remaining_rule,
11
- random_operation_rule,
12
- )
13
- from job_shop_lib.dispatching.pruning_functions import (
14
- prune_dominated_operations,
15
- prune_non_immediate_machines,
16
- create_composite_pruning_function,
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 job_shop_lib.dispatching.factories import (
19
- PruningFunction,
20
- DispatchingRule,
21
- MachineChooser,
22
- dispatching_rule_factory,
23
- machine_chooser_factory,
24
- pruning_function_factory,
25
- composite_pruning_function_factory,
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 job_shop_lib.dispatching.dispatching_rule_solver import (
28
- DispatchingRuleSolver,
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
- "DispatchingRuleSolver",
44
- "prune_dominated_operations",
45
- "prune_non_immediate_machines",
46
- "create_composite_pruning_function",
47
- "PruningFunction",
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
- "HistoryTracker",
54
+ "HistoryObserver",
55
+ "DispatcherObserverConfig",
56
+ "UnscheduledOperationsObserver",
57
+ "ReadyOperationsFilter",
58
+ "filter_non_idle_machines",
59
+ "filter_non_immediate_operations",
60
+ "OptimalOperationsObserver",
52
61
  ]