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.
Files changed (76) hide show
  1. job_shop_lib/__init__.py +16 -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} +9 -4
  4. job_shop_lib/_operation.py +95 -0
  5. job_shop_lib/{schedule.py → _schedule.py} +73 -54
  6. job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +13 -37
  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} +57 -18
  11. job_shop_lib/dispatching/__init__.py +45 -41
  12. job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +153 -80
  13. job_shop_lib/dispatching/_dispatcher_observer_config.py +54 -0
  14. job_shop_lib/dispatching/_factories.py +125 -0
  15. job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +4 -6
  16. job_shop_lib/dispatching/{pruning_functions.py → _ready_operation_filters.py} +6 -35
  17. job_shop_lib/dispatching/_unscheduled_operations_observer.py +69 -0
  18. job_shop_lib/dispatching/feature_observers/__init__.py +16 -10
  19. job_shop_lib/dispatching/feature_observers/{composite_feature_observer.py → _composite_feature_observer.py} +84 -2
  20. job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +6 -17
  21. job_shop_lib/dispatching/feature_observers/{earliest_start_time_observer.py → _earliest_start_time_observer.py} +114 -35
  22. job_shop_lib/dispatching/feature_observers/{factory.py → _factory.py} +31 -5
  23. job_shop_lib/dispatching/feature_observers/{feature_observer.py → _feature_observer.py} +59 -16
  24. job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
  25. job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +33 -0
  26. job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +1 -8
  27. job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
  28. job_shop_lib/dispatching/rules/__init__.py +51 -0
  29. job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +82 -0
  30. job_shop_lib/dispatching/{dispatching_rule_solver.py → rules/_dispatching_rule_solver.py} +44 -15
  31. job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +74 -21
  32. job_shop_lib/dispatching/rules/_machine_chooser_factory.py +69 -0
  33. job_shop_lib/dispatching/rules/_utils.py +127 -0
  34. job_shop_lib/exceptions.py +18 -0
  35. job_shop_lib/generation/__init__.py +2 -2
  36. job_shop_lib/generation/{general_instance_generator.py → _general_instance_generator.py} +26 -7
  37. job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +13 -3
  38. job_shop_lib/graphs/__init__.py +17 -6
  39. job_shop_lib/graphs/{job_shop_graph.py → _job_shop_graph.py} +81 -2
  40. job_shop_lib/graphs/{node.py → _node.py} +18 -12
  41. job_shop_lib/graphs/graph_updaters/__init__.py +13 -0
  42. job_shop_lib/graphs/graph_updaters/_graph_updater.py +59 -0
  43. job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +154 -0
  44. job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
  45. job_shop_lib/reinforcement_learning/__init__.py +41 -0
  46. job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +366 -0
  47. job_shop_lib/reinforcement_learning/_reward_observers.py +85 -0
  48. job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +337 -0
  49. job_shop_lib/reinforcement_learning/_types_and_constants.py +61 -0
  50. job_shop_lib/reinforcement_learning/_utils.py +96 -0
  51. job_shop_lib/visualization/__init__.py +20 -4
  52. job_shop_lib/visualization/{agent_task_graph.py → _agent_task_graph.py} +28 -9
  53. job_shop_lib/visualization/_gantt_chart_creator.py +219 -0
  54. job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +388 -0
  55. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/METADATA +68 -44
  56. job_shop_lib-1.0.0a1.dist-info/RECORD +66 -0
  57. job_shop_lib/benchmarking/load_benchmark.py +0 -142
  58. job_shop_lib/cp_sat/__init__.py +0 -5
  59. job_shop_lib/dispatching/factories.py +0 -206
  60. job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
  61. job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
  62. job_shop_lib/generators/__init__.py +0 -8
  63. job_shop_lib/generators/basic_generator.py +0 -200
  64. job_shop_lib/generators/transformations.py +0 -164
  65. job_shop_lib/operation.py +0 -122
  66. job_shop_lib/visualization/create_gif.py +0 -209
  67. job_shop_lib-0.5.1.dist-info/RECORD +0 -52
  68. /job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +0 -0
  69. /job_shop_lib/generation/{transformations.py → _transformations.py} +0 -0
  70. /job_shop_lib/graphs/{build_agent_task_graph.py → _build_agent_task_graph.py} +0 -0
  71. /job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +0 -0
  72. /job_shop_lib/graphs/{constants.py → _constants.py} +0 -0
  73. /job_shop_lib/visualization/{disjunctive_graph.py → _disjunctive_graph.py} +0 -0
  74. /job_shop_lib/visualization/{gantt_chart.py → _gantt_chart.py} +0 -0
  75. {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0a1.dist-info}/LICENSE +0 -0
  76. {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 NoSolutionFoundError, BaseSolver
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
- """A solver for the job shop scheduling problem using constraint
21
- programming.
22
-
23
- This solver uses the ortools library to solve the job shop scheduling
24
- problem using constraint programming.
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.makespan: cp_model.IntVar | None = None
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
- 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.
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
- if self.makespan is None:
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.makespan),
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.makespan = self.model.NewIntVar(
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.makespan, end_times)
162
- self.model.Minimize(self.makespan)
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
- """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,
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 job_shop_lib.dispatching.pruning_functions import (
14
- prune_dominated_operations,
15
- prune_non_immediate_machines,
16
- create_composite_pruning_function,
28
+ from ._unscheduled_operations_observer import (
29
+ UnscheduledOperationsObserver,
17
30
  )
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,
31
+ from ._ready_operation_filters import (
32
+ filter_dominated_operations,
33
+ filter_non_immediate_machines,
34
+ ReadyOperationsFilter,
26
35
  )
27
- from job_shop_lib.dispatching.dispatching_rule_solver import (
28
- DispatchingRuleSolver,
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
- "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",
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
- "HistoryTracker",
52
+ "HistoryObserver",
53
+ "DispatcherObserverConfig",
54
+ "UnscheduledOperationsObserver",
55
+ "ReadyOperationsFilter",
52
56
  ]