job-shop-lib 1.0.0a1__tar.gz → 1.0.0a3__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/PKG-INFO +15 -3
  2. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/README.md +14 -2
  3. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/_job_shop_instance.py +18 -20
  4. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/_operation.py +30 -24
  5. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/_schedule.py +17 -16
  6. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/_scheduled_operation.py +10 -12
  7. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/constraint_programming/_ortools_solver.py +31 -16
  8. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/__init__.py +4 -0
  9. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_dispatcher.py +24 -32
  10. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_factories.py +8 -0
  11. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_ready_operation_filters.py +80 -0
  12. job_shop_lib-1.0.0a3/job_shop_lib/dispatching/feature_observers/__init__.py +66 -0
  13. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +54 -14
  14. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_duration_observer.py +15 -2
  15. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +62 -10
  16. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_factory.py +5 -1
  17. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_feature_observer.py +87 -16
  18. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +32 -2
  19. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +3 -3
  20. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_is_scheduled_observer.py +9 -5
  21. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_position_in_job_observer.py +7 -2
  22. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/feature_observers/_remaining_operations_observer.py +1 -1
  23. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +66 -43
  24. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rules_functions.py +9 -9
  25. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/__init__.py +2 -0
  26. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_build_agent_task_graph.py +2 -2
  27. job_shop_lib-1.0.0a3/job_shop_lib/graphs/_constants.py +38 -0
  28. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_job_shop_graph.py +36 -20
  29. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_node.py +60 -52
  30. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/__init__.py +11 -1
  31. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +1 -1
  32. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/__init__.py +5 -5
  33. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_gantt_chart_creator.py +5 -5
  34. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_gantt_chart_video_and_gif_creation.py +63 -36
  35. job_shop_lib-1.0.0a1/job_shop_lib/visualization/_gantt_chart.py → job_shop_lib-1.0.0a3/job_shop_lib/visualization/_plot_gantt_chart.py +78 -14
  36. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/pyproject.toml +11 -1
  37. job_shop_lib-1.0.0a1/job_shop_lib/dispatching/feature_observers/__init__.py +0 -34
  38. job_shop_lib-1.0.0a1/job_shop_lib/graphs/_constants.py +0 -21
  39. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/LICENSE +0 -0
  40. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/__init__.py +0 -0
  41. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/_base_solver.py +0 -0
  42. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/__init__.py +0 -0
  43. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/_load_benchmark.py +0 -0
  44. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/benchmarking/benchmark_instances.json +0 -0
  45. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/constraint_programming/__init__.py +0 -0
  46. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_dispatcher_observer_config.py +0 -0
  47. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_history_observer.py +0 -0
  48. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/_unscheduled_operations_observer.py +0 -0
  49. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/__init__.py +0 -0
  50. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +0 -0
  51. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_machine_chooser_factory.py +0 -0
  52. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/dispatching/rules/_utils.py +0 -0
  53. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/exceptions.py +0 -0
  54. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/__init__.py +0 -0
  55. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_general_instance_generator.py +0 -0
  56. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_instance_generator.py +0 -0
  57. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/generation/_transformations.py +0 -0
  58. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/_build_disjunctive_graph.py +0 -0
  59. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_graph_updater.py +0 -0
  60. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +0 -0
  61. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/graphs/graph_updaters/_utils.py +0 -0
  62. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/__init__.py +0 -0
  63. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +0 -0
  64. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_reward_observers.py +0 -0
  65. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_types_and_constants.py +0 -0
  66. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/reinforcement_learning/_utils.py +0 -0
  67. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_agent_task_graph.py +0 -0
  68. {job_shop_lib-1.0.0a1 → job_shop_lib-1.0.0a3}/job_shop_lib/visualization/_disjunctive_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.0.0a1
3
+ Version: 1.0.0a3
4
4
  Summary: An easy-to-use and modular Python library for the Job Shop Scheduling Problem (JSSP)
5
5
  License: MIT
6
6
  Author: Pabloo22
@@ -29,6 +29,7 @@ Description-Content-Type: text/markdown
29
29
  <h1>JobShopLib</h1>
30
30
 
31
31
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
32
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
32
33
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
33
34
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
34
35
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -39,7 +40,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S
39
40
 
40
41
  It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
41
42
 
42
- See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
43
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
43
44
 
44
45
  ## Installation :package:
45
46
 
@@ -47,12 +48,23 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
47
48
 
48
49
  JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
49
50
 
50
- You can install the latest version using `pip`:
51
+ You can install the latest stable version (version 0.5.1) using `pip`:
51
52
 
52
53
  ```bash
53
54
  pip install job-shop-lib
54
55
  ```
55
56
 
57
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
58
+
59
+
60
+ Version 1.0.0 is currently in beta stage and can be installed with:
61
+
62
+ ```bash
63
+ pip install job-shop-lib==1.0.0a3
64
+ ```
65
+
66
+ Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiare yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
67
+
56
68
  <!-- end installation -->
57
69
 
58
70
  <!-- key features -->
@@ -5,6 +5,7 @@
5
5
  <h1>JobShopLib</h1>
6
6
 
7
7
  [![Tests](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml/badge.svg)](https://github.com/Pabloo22/job_shop_lib/actions/workflows/tests.yaml)
8
+ [![Documentation Status](https://readthedocs.org/projects/job-shop-lib/badge/?version=latest)](https://job-shop-lib.readthedocs.io/en/latest/?badge=latest)
8
9
  ![Python versions](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)
9
10
  [![Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
10
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -15,7 +16,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S
15
16
 
16
17
  It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.
17
18
 
18
- See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
19
+ See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
19
20
 
20
21
  ## Installation :package:
21
22
 
@@ -23,12 +24,23 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
23
24
 
24
25
  JobShopLib is distributed on [PyPI](https://pypi.org/project/job-shop-lib/) and it supports Python 3.10+.
25
26
 
26
- You can install the latest version using `pip`:
27
+ You can install the latest stable version (version 0.5.1) using `pip`:
27
28
 
28
29
  ```bash
29
30
  pip install job-shop-lib
30
31
  ```
31
32
 
33
+ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcowwTZ4H?usp=sharing) Google Colab notebook for a quick start guide!
34
+
35
+
36
+ Version 1.0.0 is currently in beta stage and can be installed with:
37
+
38
+ ```bash
39
+ pip install job-shop-lib==1.0.0a3
40
+ ```
41
+
42
+ Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiare yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
43
+
32
44
  <!-- end installation -->
33
45
 
34
46
  <!-- key features -->
@@ -20,15 +20,27 @@ class JobShopInstance:
20
20
  computations.
21
21
 
22
22
  Attributes:
23
- jobs:
23
+ jobs (list[list[Operation]]):
24
24
  A list of lists of operations. Each list of operations represents
25
25
  a job, and the operations are ordered by their position in the job.
26
26
  The `job_id`, `position_in_job`, and `operation_id` attributes of
27
27
  the operations are set when the instance is created.
28
- name:
28
+ name (str):
29
29
  A string with the name of the instance.
30
- metadata:
30
+ metadata (dict[str, Any]):
31
31
  A dictionary with additional information about the instance.
32
+
33
+ Args:
34
+ jobs:
35
+ A list of lists of operations. Each list of operations
36
+ represents a job, and the operations are ordered by their
37
+ position in the job. The `job_id`, `position_in_job`, and
38
+ `operation_id` attributes of the operations are set when the
39
+ instance is created.
40
+ name:
41
+ A string with the name of the instance.
42
+ **metadata:
43
+ Additional information about the instance.
32
44
  """
33
45
 
34
46
  def __init__(
@@ -37,24 +49,10 @@ class JobShopInstance:
37
49
  name: str = "JobShopInstance",
38
50
  **metadata: Any,
39
51
  ):
40
- """Initializes the instance based on a list of lists of operations.
41
-
42
- Args:
43
- jobs:
44
- A list of lists of operations. Each list of operations
45
- represents a job, and the operations are ordered by their
46
- position in the job. The `job_id`, `position_in_job`, and
47
- `operation_id` attributes of the operations are set when the
48
- instance is created.
49
- name:
50
- A string with the name of the instance.
51
- **metadata:
52
- Additional information about the instance.
53
- """
54
- self.jobs = jobs
52
+ self.jobs: list[list[Operation]] = jobs
55
53
  self.set_operation_attributes()
56
- self.name = name
57
- self.metadata = metadata
54
+ self.name: str = name
55
+ self.metadata: dict[str, Any] = metadata
58
56
 
59
57
  def set_operation_attributes(self):
60
58
  """Sets the job_id and position of each operation."""
@@ -23,32 +23,38 @@ class Operation:
23
23
  necessary to multiply all durations by a sufficiently large integer so
24
24
  that every duration is an integer.
25
25
 
26
- Attributes:
27
- machines: A list of machine ids that can perform the operation.
28
- duration: The time it takes to perform the operation.
26
+ Args:
27
+ machines:
28
+ A list of machine ids that can perform the operation. If
29
+ only one machine can perform the operation, it can be passed as
30
+ an integer.
31
+ duration:
32
+ The time it takes to perform the operation.
29
33
  """
30
34
 
31
- __slots__ = (
32
- "machines",
33
- "duration",
34
- "job_id",
35
- "position_in_job",
36
- "operation_id",
37
- )
35
+ __slots__ = {
36
+ "machines": (
37
+ "A list of machine ids that can perform the operation. If "
38
+ "only one machine can perform the operation, it can be passed as "
39
+ "an integer."
40
+ ),
41
+ "duration": (
42
+ "The time it takes to perform the operation. Often referred"
43
+ " to as the processing time."
44
+ ),
45
+ "job_id": "The id of the job the operation belongs to.",
46
+ "position_in_job": "The index of the operation in the job.",
47
+ "operation_id": (
48
+ "The id of the operation. This is unique within a "
49
+ ":class:`JobShopInstance`."
50
+ ),
51
+ }
38
52
 
39
53
  def __init__(self, machines: int | list[int], duration: int):
40
- """Initializes the object with the given machines and duration.
41
-
42
- Args:
43
- machines:
44
- A list of machine ids that can perform the operation. If
45
- only one machine can perform the operation, it can be passed as
46
- an integer.
47
- duration:
48
- The time it takes to perform the operation.
49
- """
50
- self.machines = [machines] if isinstance(machines, int) else machines
51
- self.duration = duration
54
+ self.machines: list[int] = (
55
+ [machines] if isinstance(machines, int) else machines
56
+ )
57
+ self.duration: int = duration
52
58
 
53
59
  # Defined outside the class by the JobShopInstance class:
54
60
  self.job_id: int = -1
@@ -60,8 +66,8 @@ class Operation:
60
66
  """Returns the id of the machine associated with the operation.
61
67
 
62
68
  Raises:
63
- UninitializedAttributeError: If the operation has multiple machines
64
- in its list.
69
+ UninitializedAttributeError:
70
+ If the operation has multiple machines in its list.
65
71
  """
66
72
  if len(self.machines) > 1:
67
73
  raise UninitializedAttributeError(
@@ -25,27 +25,28 @@ class Schedule:
25
25
  is_complete
26
26
  add
27
27
  reset
28
-
29
- Attributes:
30
- instance:
31
- The :class:`JobShopInstance` object that the schedule is for.
32
- metadata:
33
- A dictionary with additional information about the schedule. It
34
- can be used to store information about the algorithm that generated
35
- the schedule, for example.
36
28
  """
37
29
 
38
- __slots__ = (
39
- "instance",
40
- "_schedule",
41
- "metadata",
42
- )
30
+ __slots__ = {
31
+ "instance": (
32
+ "The :class:`JobShopInstance` object that the schedule is for."
33
+ ),
34
+ "_schedule": (
35
+ "A list of lists of :class:`ScheduledOperation` objects. "
36
+ "Each list represents the order of operations on a machine."
37
+ ),
38
+ "metadata": (
39
+ "A dictionary with additional information about the "
40
+ "schedule. It can be used to store information about the "
41
+ "algorithm that generated the schedule, for example."
42
+ ),
43
+ }
43
44
 
44
45
  def __init__(
45
46
  self,
46
47
  instance: JobShopInstance,
47
48
  schedule: list[list[ScheduledOperation]] | None = None,
48
- **metadata,
49
+ **metadata: Any,
49
50
  ):
50
51
  """Initializes the object with the given instance and schedule.
51
52
 
@@ -64,9 +65,9 @@ class Schedule:
64
65
 
65
66
  Schedule.check_schedule(schedule)
66
67
 
67
- self.instance = instance
68
+ self.instance: JobShopInstance = instance
68
69
  self._schedule = schedule
69
- self.metadata = metadata
70
+ self.metadata: dict[str, Any] = metadata
70
71
 
71
72
  def __repr__(self) -> str:
72
73
  return str(self.schedule)
@@ -5,17 +5,15 @@ from job_shop_lib.exceptions import ValidationError
5
5
 
6
6
 
7
7
  class ScheduledOperation:
8
- """Data structure to store a scheduled operation.
8
+ """Data structure to store a scheduled operation."""
9
9
 
10
- Attributes:
11
- operation:
12
- The :class:`Operation` object that is scheduled.
13
- start_time:
14
- The time at which the operation is scheduled to start.
15
-
16
- """
17
-
18
- __slots__ = ("operation", "start_time", "_machine_id")
10
+ __slots__ = {
11
+ "operation": "The :class:`Operation` object that is scheduled.",
12
+ "start_time": "The time at which the operation is scheduled to start.",
13
+ "_machine_id": (
14
+ "The id of the machine on which the operation is scheduled."
15
+ ),
16
+ }
19
17
 
20
18
  def __init__(self, operation: Operation, start_time: int, machine_id: int):
21
19
  """Initializes a new instance of the :class:`ScheduledOperation` class.
@@ -33,8 +31,8 @@ class ScheduledOperation:
33
31
  If the given machine_id is not in the list of valid machines
34
32
  for the operation.
35
33
  """
36
- self.operation = operation
37
- self.start_time = start_time
34
+ self.operation: Operation = operation
35
+ self.start_time: int = start_time
38
36
  self._machine_id = machine_id
39
37
  self.machine_id = machine_id # Validate machine_id
40
38
 
@@ -22,22 +22,32 @@ class ORToolsSolver(BaseSolver):
22
22
  <https://developers.google.com/optimization/cp/cp_solver>`_ solver.
23
23
 
24
24
  Attributes:
25
- log_search_progress (``bool``):
25
+ log_search_progress (bool):
26
26
  Whether to log the search progress to the console.
27
- max_time_in_seconds (``float | None``):
27
+ max_time_in_seconds (float | None):
28
28
  The maximum time in seconds to allow the solver to search for
29
29
  a solution. If no solution is found within this time, a
30
30
  :class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
31
31
  raised. If ``None``, the solver will run until an optimal
32
32
  solution is found.
33
- model (``cp_model.CpModel``):
33
+ model (cp_model.CpModel):
34
34
  The `OR-Tools' CP-SAT model
35
35
  <https://developers.google.com/optimization/reference/python/sat/
36
36
  python/cp_model#cp_model.CpModel>`_.
37
- solver (``cp_model.CpSolver``):
37
+ solver (cp_model.CpSolver):
38
38
  The `OR-Tools' CP-SAT solver
39
39
  <https://developers.google.com/optimization/reference/python/sat/
40
40
  python/cp_model#cp_model.CpSolver>`_.
41
+
42
+ Args:
43
+ max_time_in_seconds:
44
+ The maximum time in seconds to allow the solver to search for
45
+ a solution. If no solution is found within this time, a
46
+ :class:`~job_shop_lib.exceptions.NoSolutionFoundError` is
47
+ raised. If ``None``, the solver will run until an optimal
48
+ solution is found.
49
+ log_search_progress:
50
+ Whether to log the search progress to the console.
41
51
  """
42
52
 
43
53
  def __init__(
@@ -45,18 +55,6 @@ class ORToolsSolver(BaseSolver):
45
55
  max_time_in_seconds: float | None = None,
46
56
  log_search_progress: bool = False,
47
57
  ):
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
- """
60
58
  self.log_search_progress = log_search_progress
61
59
  self.max_time_in_seconds = max_time_in_seconds
62
60
 
@@ -66,6 +64,23 @@ class ORToolsSolver(BaseSolver):
66
64
  self._operations_start: dict[Operation, tuple[IntVar, IntVar]] = {}
67
65
 
68
66
  def __call__(self, instance: JobShopInstance) -> Schedule:
67
+ """Equivalent to calling the :meth:`~ORToolsSolver.solve` method.
68
+
69
+ This method is necessary because, in JobShopLib, solvers are defined
70
+ as callables that receive an instance and return a schedule.
71
+
72
+ Args:
73
+ instance: The job shop instance to be solved.
74
+
75
+ Returns:
76
+ The best schedule found by the solver.
77
+ Its metadata contains the following information:
78
+
79
+ * status (``str``): ``"optimal"`` or ``"feasible"``
80
+ * elapsed_time (``float``): The time taken to solve the problem
81
+ * makespan (``int``): The total duration of the schedule
82
+ * solved_by (``str``): ``"ORToolsSolver"``
83
+ """
69
84
  # Re-defined here since we already add metadata to the schedule in
70
85
  # the solve method.
71
86
  return self.solve(instance)
@@ -32,6 +32,8 @@ from ._ready_operation_filters import (
32
32
  filter_dominated_operations,
33
33
  filter_non_immediate_machines,
34
34
  ReadyOperationsFilter,
35
+ filter_non_idle_machines,
36
+ filter_non_immediate_operations,
35
37
  )
36
38
  from ._dispatcher_observer_config import DispatcherObserverConfig
37
39
  from ._factories import (
@@ -53,4 +55,6 @@ __all__ = [
53
55
  "DispatcherObserverConfig",
54
56
  "UnscheduledOperationsObserver",
55
57
  "ReadyOperationsFilter",
58
+ "filter_non_idle_machines",
59
+ "filter_non_immediate_operations",
56
60
  ]
@@ -156,26 +156,30 @@ class Dispatcher:
156
156
  responsible for scheduling the operations on the machines and keeping
157
157
  track of the next available time for each machine and job.
158
158
 
159
- Attributes:
159
+ Args:
160
160
  instance:
161
- The instance of the job shop problem to be scheduled.
162
- schedule:
163
- The schedule of operations on machines.
161
+ The instance of the job shop problem to be solved.
164
162
  ready_operations_filter:
165
- A function that filters out operations that are not ready to be
166
- scheduled.
163
+ A function that filters out operations that are not ready to
164
+ be scheduled. The function should take the dispatcher and a
165
+ list of operations as input and return a list of operations
166
+ that are ready to be scheduled. If ``None``, no filtering is
167
+ done.
167
168
  """
168
169
 
169
- __slots__ = (
170
- "instance",
171
- "schedule",
172
- "_machine_next_available_time",
173
- "_job_next_operation_index",
174
- "_job_next_available_time",
175
- "ready_operations_filter",
176
- "subscribers",
177
- "_cache",
178
- )
170
+ __slots__ = {
171
+ "instance": "The instance of the job shop problem to be scheduled.",
172
+ "schedule": "The schedule of operations on machines.",
173
+ "_machine_next_available_time": "",
174
+ "_job_next_operation_index": "",
175
+ "_job_next_available_time": "",
176
+ "ready_operations_filter": (
177
+ "A function that filters out operations that are not ready to be "
178
+ "scheduled."
179
+ ),
180
+ "subscribers": "A list of observers subscribed to the dispatcher.",
181
+ "_cache": "A dictionary to cache the results of the cached methods.",
182
+ }
179
183
 
180
184
  def __init__(
181
185
  self,
@@ -184,18 +188,6 @@ class Dispatcher:
184
188
  Callable[[Dispatcher, list[Operation]], list[Operation]] | None
185
189
  ) = None,
186
190
  ) -> None:
187
- """Initializes the object with the given instance.
188
-
189
- Args:
190
- instance:
191
- The instance of the job shop problem to be solved.
192
- ready_operations_filter:
193
- A function that filters out operations that are not ready to
194
- be scheduled. The function should take the dispatcher and a
195
- list of operations as input and return a list of operations
196
- that are ready to be scheduled. If ``None``, no filtering is
197
- done.
198
- """
199
191
 
200
192
  self.instance = instance
201
193
  self.schedule = Schedule(self.instance)
@@ -371,7 +363,7 @@ class Dispatcher:
371
363
  The current time is the minimum start time of the available
372
364
  operations.
373
365
  """
374
- available_operations = self.ready_operations()
366
+ available_operations = self.available_operations()
375
367
  current_time = self.min_start_time(available_operations)
376
368
  return current_time
377
369
 
@@ -387,7 +379,7 @@ class Dispatcher:
387
379
  return int(min_start_time)
388
380
 
389
381
  @_dispatcher_cache
390
- def ready_operations(self) -> list[Operation]:
382
+ def available_operations(self) -> list[Operation]:
391
383
  """Returns a list of available operations for processing, optionally
392
384
  filtering out operations using the filter function.
393
385
 
@@ -443,7 +435,7 @@ class Dispatcher:
443
435
  @_dispatcher_cache
444
436
  def available_machines(self) -> list[int]:
445
437
  """Returns the list of ready machines."""
446
- available_operations = self.ready_operations()
438
+ available_operations = self.available_operations()
447
439
  available_machines = set()
448
440
  for operation in available_operations:
449
441
  available_machines.update(operation.machines)
@@ -452,7 +444,7 @@ class Dispatcher:
452
444
  @_dispatcher_cache
453
445
  def available_jobs(self) -> list[int]:
454
446
  """Returns the list of ready jobs."""
455
- available_operations = self.ready_operations()
447
+ available_operations = self.available_operations()
456
448
  available_jobs = set(
457
449
  operation.job_id for operation in available_operations
458
450
  )
@@ -13,6 +13,8 @@ from job_shop_lib.dispatching import (
13
13
  Dispatcher,
14
14
  filter_dominated_operations,
15
15
  filter_non_immediate_machines,
16
+ filter_non_idle_machines,
17
+ filter_non_immediate_operations,
16
18
  ReadyOperationsFilter,
17
19
  )
18
20
 
@@ -27,6 +29,8 @@ class ReadyOperationsFilterType(str, Enum):
27
29
 
28
30
  DOMINATED_OPERATIONS = "dominated_operations"
29
31
  NON_IMMEDIATE_MACHINES = "non_immediate_machines"
32
+ NON_IDLE_MACHINES = "non_idle_machines"
33
+ NON_IMMEDIATE_OPERATIONS = "non_immediate_operations"
30
34
 
31
35
 
32
36
  def create_composite_operation_filter(
@@ -114,6 +118,10 @@ def ready_operations_filter_factory(
114
118
  ReadyOperationsFilterType.NON_IMMEDIATE_MACHINES: (
115
119
  filter_non_immediate_machines
116
120
  ),
121
+ ReadyOperationsFilterType.NON_IDLE_MACHINES: filter_non_idle_machines,
122
+ ReadyOperationsFilterType.NON_IMMEDIATE_OPERATIONS: (
123
+ filter_non_immediate_operations
124
+ ),
117
125
  }
118
126
 
119
127
  if filter_name not in filtering_strategies:
@@ -15,6 +15,86 @@ ReadyOperationsFilter = Callable[
15
15
  ]
16
16
 
17
17
 
18
+ def filter_non_idle_machines(
19
+ dispatcher: Dispatcher, operations: list[Operation]
20
+ ) -> list[Operation]:
21
+ """Filters out all the operations associated with non-idle machines.
22
+
23
+ A machine is considered idle if there are no ongoing operations
24
+ currently scheduled on it. This filter removes operations that are
25
+ associated with machines that are busy (i.e., have at least one
26
+ uncompleted operation).
27
+
28
+ Utilizes :meth:``Dispatcher.ongoing_operations()`` to determine machine
29
+ statuses.
30
+
31
+ Args:
32
+ dispatcher: The dispatcher object.
33
+ operations: The list of operations to filter.
34
+
35
+ Returns:
36
+ The list of operations that are associated with idle machines.
37
+ """
38
+ current_time = dispatcher.min_start_time(operations)
39
+ non_idle_machines = _get_non_idle_machines(dispatcher, current_time)
40
+
41
+ # Filter operations to keep those that are associated with at least one
42
+ # idle machine
43
+ filtered_operations: list[Operation] = []
44
+ for operation in operations:
45
+ if all(
46
+ machine_id in non_idle_machines
47
+ for machine_id in operation.machines
48
+ ):
49
+ continue
50
+ filtered_operations.append(operation)
51
+
52
+ return filtered_operations
53
+
54
+
55
+ def _get_non_idle_machines(
56
+ dispatcher: Dispatcher, current_time: int
57
+ ) -> set[int]:
58
+ """Returns the set of machine ids that are currently busy (i.e., have at
59
+ least one uncompleted operation)."""
60
+
61
+ non_idle_machines = set()
62
+ for machine_schedule in dispatcher.schedule.schedule:
63
+ for scheduled_operation in reversed(machine_schedule):
64
+ is_completed = scheduled_operation.end_time <= current_time
65
+ if is_completed:
66
+ break
67
+ non_idle_machines.add(scheduled_operation.machine_id)
68
+
69
+ return non_idle_machines
70
+
71
+
72
+ def filter_non_immediate_operations(
73
+ dispatcher: Dispatcher, operations: list[Operation]
74
+ ) -> list[Operation]:
75
+ """Filters out all the operations that can't start immediately.
76
+
77
+ An operation can start immediately if its earliest start time is the
78
+ current time.
79
+
80
+ The current time is determined by the minimum start time of the
81
+ operations.
82
+
83
+ Args:
84
+ dispatcher: The dispatcher object.
85
+ operations: The list of operations to filter.
86
+ """
87
+
88
+ min_start_time = dispatcher.min_start_time(operations)
89
+ immediate_operations: list[Operation] = []
90
+ for operation in operations:
91
+ start_time = dispatcher.earliest_start_time(operation)
92
+ if start_time == min_start_time:
93
+ immediate_operations.append(operation)
94
+
95
+ return immediate_operations
96
+
97
+
18
98
  def filter_dominated_operations(
19
99
  dispatcher: Dispatcher, operations: list[Operation]
20
100
  ) -> list[Operation]: