job-shop-lib 1.6.1__py3-none-any.whl → 1.7.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 CHANGED
@@ -19,7 +19,7 @@ from job_shop_lib._schedule import Schedule
19
19
  from job_shop_lib._base_solver import BaseSolver, Solver
20
20
 
21
21
 
22
- __version__ = "1.6.1"
22
+ __version__ = "1.7.0"
23
23
 
24
24
  __all__ = [
25
25
  "Operation",
@@ -40,12 +40,12 @@ class JobShopInstance:
40
40
  num_machines
41
41
  num_operations
42
42
  is_flexible
43
- durations_matrix
43
+ duration_matrix
44
44
  machines_matrix
45
45
  release_dates_matrix
46
46
  deadlines_matrix
47
47
  due_dates_matrix
48
- durations_matrix_array
48
+ duration_matrix_array
49
49
  machines_matrix_array
50
50
  operations_by_machine
51
51
  max_duration
@@ -216,7 +216,7 @@ class JobShopInstance:
216
216
 
217
217
  {
218
218
  "name": self.name,
219
- "duration_matrix": self.durations_matrix,
219
+ "duration_matrix": self.duration_matrix,
220
220
  "machines_matrix": self.machines_matrix,
221
221
  "metadata": self.metadata,
222
222
  # Optionally (if the instance has them):
@@ -227,7 +227,7 @@ class JobShopInstance:
227
227
  """
228
228
  data = {
229
229
  "name": self.name,
230
- "duration_matrix": self.durations_matrix,
230
+ "duration_matrix": self.duration_matrix,
231
231
  "machines_matrix": self.machines_matrix,
232
232
  "metadata": self.metadata,
233
233
  }
@@ -374,6 +374,22 @@ class JobShopInstance:
374
374
 
375
375
  @functools.cached_property
376
376
  def durations_matrix(self) -> list[list[int]]:
377
+ """Another name for `duration_matrix` attribute, kept for
378
+ backward compatibility.
379
+
380
+ It may be removed in future versions.
381
+ """
382
+ warnings.warn(
383
+ "`duration_matrix` attribute is deprecated and will be "
384
+ "removed in future versions. Please use `duration_matrix` "
385
+ "property instead.",
386
+ DeprecationWarning,
387
+ stacklevel=2,
388
+ )
389
+ return self.duration_matrix
390
+
391
+ @functools.cached_property
392
+ def duration_matrix(self) -> list[list[int]]:
377
393
  """Returns the duration matrix of the instance.
378
394
 
379
395
  The duration of the operation with ``job_id`` i and ``position_in_job``
@@ -452,7 +468,23 @@ class JobShopInstance:
452
468
  If the jobs have different number of operations, the matrix is
453
469
  padded with ``np.nan`` to make it rectangular.
454
470
  """
455
- return self._fill_matrix_with_nans_2d(self.durations_matrix)
471
+ warnings.warn(
472
+ "`durations_matrix_array` attribute is deprecated and will be "
473
+ "removed in future versions. Please use `duration_matrix_array` "
474
+ "property instead.",
475
+ DeprecationWarning,
476
+ stacklevel=2,
477
+ )
478
+ return self.duration_matrix_array
479
+
480
+ @property
481
+ def duration_matrix_array(self) -> NDArray[np.float32]:
482
+ """Returns the duration matrix of the instance as a numpy array.
483
+
484
+ If the jobs have different number of operations, the matrix is
485
+ padded with ``np.nan`` to make it rectangular.
486
+ """
487
+ return self._fill_matrix_with_nans_2d(self.duration_matrix)
456
488
 
457
489
  @functools.cached_property
458
490
  def release_dates_matrix_array(self) -> NDArray[np.float32]:
@@ -6,6 +6,7 @@
6
6
  load_all_benchmark_instances
7
7
  load_benchmark_instance
8
8
  load_benchmark_json
9
+ load_benchmark_group
9
10
 
10
11
  You can load a benchmark instance from the library:
11
12
 
@@ -93,10 +94,12 @@ from job_shop_lib.benchmarking._load_benchmark import (
93
94
  load_all_benchmark_instances,
94
95
  load_benchmark_instance,
95
96
  load_benchmark_json,
97
+ load_benchmark_group,
96
98
  )
97
99
 
98
100
  __all__ = [
99
101
  "load_all_benchmark_instances",
100
102
  "load_benchmark_instance",
101
103
  "load_benchmark_json",
104
+ "load_benchmark_group",
102
105
  ]
@@ -86,3 +86,28 @@ def load_benchmark_json() -> dict[str, dict[str, Any]]:
86
86
 
87
87
  with benchmark_file.open("r", encoding="utf-8") as f:
88
88
  return json.load(f)
89
+
90
+
91
+ @functools.cache
92
+ def load_benchmark_group(group_prefix: str) -> list[JobShopInstance]:
93
+ """Loads a group of benchmark instances whose names start with the given
94
+ prefix.
95
+
96
+ Args:
97
+ group_prefix:
98
+ The prefix of the benchmark instances to load. For example,
99
+ if the prefix is "la", all instances whose names start with "la"
100
+ (e.g., "la01-40", "la02-40", etc.) will be loaded.
101
+
102
+ Returns:
103
+ A list of :class:`JobShopInstance` objects whose names start with
104
+ the given prefix.
105
+
106
+ .. versionadded:: 1.7.0
107
+ """
108
+ all_instances = load_all_benchmark_instances()
109
+ return [
110
+ instance
111
+ for name, instance in all_instances.items()
112
+ if name.startswith(group_prefix)
113
+ ]
@@ -58,7 +58,7 @@ class DurationObserver(FeatureObserver):
58
58
  mapping[feature_type](scheduled_operation)
59
59
 
60
60
  def _initialize_operation_durations(self):
61
- duration_matrix = self.dispatcher.instance.durations_matrix_array
61
+ duration_matrix = self.dispatcher.instance.duration_matrix_array
62
62
  operation_durations = np.array(duration_matrix).reshape(-1, 1)
63
63
  # Drop the NaN values
64
64
  operation_durations = operation_durations[
@@ -80,7 +80,7 @@ class EarliestStartTimeObserver(FeatureObserver):
80
80
 
81
81
  # Earliest start times initialization
82
82
  # -------------------------------
83
- squared_duration_matrix = dispatcher.instance.durations_matrix_array
83
+ squared_duration_matrix = dispatcher.instance.duration_matrix_array
84
84
  self.earliest_start_times: NDArray[np.float32] = np.hstack(
85
85
  (
86
86
  np.zeros((squared_duration_matrix.shape[0], 1), dtype=float),
@@ -5,16 +5,46 @@
5
5
 
6
6
  InstanceGenerator
7
7
  GeneralInstanceGenerator
8
- generate_duration_matrix
8
+ modular_instance_generator
9
9
  generate_machine_matrix_with_recirculation
10
10
  generate_machine_matrix_without_recirculation
11
+ generate_duration_matrix
12
+ range_size_selector
13
+ choice_size_selector
14
+ get_default_machine_matrix_creator
15
+ get_default_duration_matrix_creator
16
+ ReleaseDateStrategy
17
+ create_release_dates_matrix
18
+ get_independent_release_date_strategy
19
+ get_cumulative_release_date_strategy
20
+ get_mixed_release_date_strategy
21
+ compute_horizon_proxy
11
22
 
12
23
  """
13
24
 
14
- from job_shop_lib.generation._utils import (
15
- generate_duration_matrix,
25
+ from job_shop_lib.generation._size_selectors import (
26
+ range_size_selector,
27
+ choice_size_selector,
28
+ )
29
+ from job_shop_lib.generation._machine_matrix import (
16
30
  generate_machine_matrix_with_recirculation,
17
31
  generate_machine_matrix_without_recirculation,
32
+ get_default_machine_matrix_creator,
33
+ )
34
+ from job_shop_lib.generation._duration_matrix import (
35
+ get_default_duration_matrix_creator,
36
+ generate_duration_matrix,
37
+ )
38
+ from job_shop_lib.generation._release_date_matrix import (
39
+ ReleaseDateStrategy,
40
+ create_release_dates_matrix,
41
+ get_independent_release_date_strategy,
42
+ get_cumulative_release_date_strategy,
43
+ get_mixed_release_date_strategy,
44
+ compute_horizon_proxy,
45
+ )
46
+ from job_shop_lib.generation._modular_instance_generator import (
47
+ modular_instance_generator,
18
48
  )
19
49
  from job_shop_lib.generation._instance_generator import InstanceGenerator
20
50
  from job_shop_lib.generation._general_instance_generator import (
@@ -27,4 +57,15 @@ __all__ = [
27
57
  "generate_duration_matrix",
28
58
  "generate_machine_matrix_with_recirculation",
29
59
  "generate_machine_matrix_without_recirculation",
60
+ "modular_instance_generator",
61
+ "range_size_selector",
62
+ "choice_size_selector",
63
+ "get_default_machine_matrix_creator",
64
+ "get_default_duration_matrix_creator",
65
+ "ReleaseDateStrategy",
66
+ "create_release_dates_matrix",
67
+ "get_independent_release_date_strategy",
68
+ "get_cumulative_release_date_strategy",
69
+ "get_mixed_release_date_strategy",
70
+ "compute_horizon_proxy",
30
71
  ]
@@ -0,0 +1,83 @@
1
+ from collections.abc import Callable
2
+ import random
3
+
4
+ import numpy as np
5
+ from numpy.typing import NDArray
6
+
7
+ from job_shop_lib.exceptions import ValidationError
8
+
9
+
10
+ def get_default_duration_matrix_creator(
11
+ duration_range: tuple[int, int] = (1, 99),
12
+ ) -> Callable[
13
+ [list[list[list[int]]] | list[list[int]], random.Random],
14
+ list[list[int]],
15
+ ]:
16
+ """Creates a duration matrix generator function.
17
+
18
+ Internally, it wraps :func:`generate_duration_matrix`.
19
+
20
+ .. note::
21
+
22
+ This function assumes that the machine matrix has the shape (num_jobs,
23
+ num_machines).
24
+
25
+ Args:
26
+ duration_range:
27
+ A tuple specifying the inclusive range for operation durations.
28
+
29
+ Returns:
30
+ A callable that generates a duration matrix of shape (num_jobs,
31
+ num_machines) when called with a machine matrix and a
32
+ `random.Random` instance.
33
+ """
34
+
35
+ def duration_matrix_creator(
36
+ machine_matrix: list[list[list[int]]] | list[list[int]],
37
+ rng: random.Random,
38
+ ) -> list[list[int]]:
39
+ seed_for_np = rng.randint(0, 2**16 - 1)
40
+ numpy_rng = np.random.default_rng(seed_for_np)
41
+ num_jobs = len(machine_matrix)
42
+ num_machines = len(machine_matrix[0])
43
+ return generate_duration_matrix(
44
+ num_jobs, num_machines, duration_range, numpy_rng
45
+ ).tolist()
46
+
47
+ return duration_matrix_creator
48
+
49
+
50
+ def generate_duration_matrix(
51
+ num_jobs: int,
52
+ num_machines: int,
53
+ duration_range: tuple[int, int],
54
+ rng: np.random.Generator | None = None,
55
+ ) -> NDArray[np.int32]:
56
+ """Generates a duration matrix.
57
+
58
+ Args:
59
+ num_jobs: The number of jobs.
60
+ num_machines: The number of machines.
61
+ duration_range: The range of the duration values.
62
+ rng: A numpy random number generator.
63
+
64
+ Returns:
65
+ A duration matrix with shape (num_jobs, num_machines).
66
+ """
67
+ rng = rng or np.random.default_rng()
68
+ if duration_range[0] > duration_range[1]:
69
+ raise ValidationError(
70
+ "The lower bound of the duration range must be less than or equal "
71
+ "to the upper bound."
72
+ )
73
+ if num_jobs <= 0:
74
+ raise ValidationError("The number of jobs must be greater than 0.")
75
+ if num_machines <= 0:
76
+ raise ValidationError("The number of machines must be greater than 0.")
77
+
78
+ return rng.integers(
79
+ duration_range[0],
80
+ duration_range[1] + 1,
81
+ size=(num_jobs, num_machines),
82
+ dtype=np.int32,
83
+ )
@@ -13,13 +13,13 @@ class InstanceGenerator(ABC):
13
13
  """Common interface for all generators.
14
14
 
15
15
  The class supports both single instance generation and iteration over
16
- multiple instances, controlled by the `iteration_limit` parameter. It
17
- implements the iterator protocol, allowing it to be used in a `for` loop.
16
+ multiple instances, controlled by the ``iteration_limit`` parameter. It
17
+ implements the iterator protocol, allowing it to be used in a ``for`` loop.
18
18
 
19
19
  Note:
20
20
  When used as an iterator, the generator will produce instances until it
21
- reaches the specified `iteration_limit`. If `iteration_limit` is None,
22
- it will continue indefinitely.
21
+ reaches the specified ``iteration_limit``. If ``iteration_limit`` is
22
+ ``None``, it will continue indefinitely.
23
23
 
24
24
  Attributes:
25
25
  num_jobs_range:
@@ -84,9 +84,11 @@ class InstanceGenerator(ABC):
84
84
  """Generates a single job shop instance
85
85
 
86
86
  Args:
87
- num_jobs: The number of jobs to generate. If None, a random value
87
+ num_jobs:
88
+ The number of jobs to generate. If None, a random value
88
89
  within the specified range will be used.
89
- num_machines: The number of machines to generate. If None, a random
90
+ num_machines:
91
+ The number of machines to generate. If None, a random
90
92
  value within the specified range will be used.
91
93
  """
92
94
 
@@ -1,43 +1,62 @@
1
+ from collections.abc import Callable
2
+ import random
3
+
1
4
  import numpy as np
2
5
  from numpy.typing import NDArray
3
6
 
4
7
  from job_shop_lib.exceptions import ValidationError
5
8
 
6
9
 
7
- def generate_duration_matrix(
8
- num_jobs: int,
9
- num_machines: int,
10
- duration_range: tuple[int, int],
11
- rng: np.random.Generator | None = None,
12
- ) -> NDArray[np.int32]:
13
- """Generates a duration matrix.
10
+ def get_default_machine_matrix_creator(
11
+ size_selector: Callable[[random.Random], tuple[int, int]] = (
12
+ lambda _: (10, 10)
13
+ ),
14
+ with_recirculation: bool = True,
15
+ ) -> Callable[[random.Random], list[list[list[int]]] | list[list[int]]]:
16
+ """Creates a machine matrix generator function.
17
+
18
+ Internally, it wraps either
19
+ :func:`generate_machine_matrix_with_recirculation`
20
+ or :func:`generate_machine_matrix_without_recirculation`
21
+ based on the `with_recirculation` parameter.
22
+
23
+ .. note::
24
+
25
+ The generated machine matrix will have the shape (num_jobs,
26
+ num_machines).
14
27
 
15
28
  Args:
16
- num_jobs: The number of jobs.
17
- num_machines: The number of machines.
18
- duration_range: The range of the duration values.
19
- rng: A numpy random number generator.
29
+ rng:
30
+ A random.Random instance.
31
+ size_selector:
32
+ A callable that takes a random.Random instance and returns a
33
+ tuple (num_jobs, num_machines).
34
+ with_recirculation:
35
+ If ``True``, generates a machine matrix with recirculation;
36
+ otherwise, without recirculation. Recirculation means that a job
37
+ can visit the same machine more than once.
20
38
 
21
39
  Returns:
22
- A duration matrix with shape (num_jobs, num_machines).
40
+ A callable that generates a machine matrix when called with a
41
+ random.Random instance.
23
42
  """
24
- rng = rng or np.random.default_rng()
25
- if duration_range[0] > duration_range[1]:
26
- raise ValidationError(
27
- "The lower bound of the duration range must be less than or equal "
28
- "to the upper bound."
29
- )
30
- if num_jobs <= 0:
31
- raise ValidationError("The number of jobs must be greater than 0.")
32
- if num_machines <= 0:
33
- raise ValidationError("The number of machines must be greater than 0.")
34
43
 
35
- return rng.integers(
36
- duration_range[0],
37
- duration_range[1] + 1,
38
- size=(num_jobs, num_machines),
39
- dtype=np.int32,
40
- )
44
+ def generator(
45
+ rng: random.Random,
46
+ ) -> list[list[list[int]]]:
47
+ num_jobs, num_machines = size_selector(rng)
48
+ seed_for_np = rng.randint(0, 2**16 - 1)
49
+ numpy_rng = np.random.default_rng(seed_for_np)
50
+ if with_recirculation:
51
+ return generate_machine_matrix_with_recirculation(
52
+ num_jobs, num_machines, numpy_rng
53
+ ).tolist()
54
+
55
+ return generate_machine_matrix_without_recirculation(
56
+ num_jobs, num_machines, numpy_rng
57
+ ).tolist()
58
+
59
+ return generator
41
60
 
42
61
 
43
62
  def generate_machine_matrix_with_recirculation(
@@ -51,8 +70,8 @@ def generate_machine_matrix_with_recirculation(
51
70
  rng: A numpy random number generator.
52
71
 
53
72
  Returns:
54
- A machine matrix with recirculation with shape (num_machines,
55
- num_jobs).
73
+ A machine matrix with recirculation with shape (num_jobs,
74
+ num_machines).
56
75
  """
57
76
  rng = rng or np.random.default_rng()
58
77
  if num_jobs <= 0:
@@ -62,7 +81,7 @@ def generate_machine_matrix_with_recirculation(
62
81
  num_machines_is_correct = False
63
82
  while not num_machines_is_correct:
64
83
  machine_matrix: np.ndarray = rng.integers(
65
- 0, num_machines, size=(num_machines, num_jobs), dtype=np.int32
84
+ 0, num_machines, size=(num_jobs, num_machines), dtype=np.int32
66
85
  )
67
86
  num_machines_is_correct = (
68
87
  len(np.unique(machine_matrix)) == num_machines
@@ -100,31 +119,3 @@ def generate_machine_matrix_without_recirculation(
100
119
  # Shuffle the columns:
101
120
  machine_matrix = np.apply_along_axis(rng.permutation, 1, machine_matrix)
102
121
  return machine_matrix
103
-
104
-
105
- if __name__ == "__main__":
106
-
107
- NUM_JOBS = 3
108
- NUM_MACHINES = 3
109
- DURATION_RANGE = (1, 10)
110
-
111
- duration_matrix = generate_duration_matrix(
112
- num_jobs=NUM_JOBS,
113
- num_machines=NUM_MACHINES,
114
- duration_range=DURATION_RANGE,
115
- )
116
- print(duration_matrix)
117
-
118
- machine_matrix_with_recirculation = (
119
- generate_machine_matrix_with_recirculation(
120
- num_jobs=NUM_JOBS, num_machines=NUM_MACHINES
121
- )
122
- )
123
- print(machine_matrix_with_recirculation)
124
-
125
- machine_matrix_without_recirculation = (
126
- generate_machine_matrix_without_recirculation(
127
- num_jobs=NUM_JOBS, num_machines=NUM_MACHINES
128
- )
129
- )
130
- print(machine_matrix_without_recirculation)
@@ -0,0 +1,116 @@
1
+ from collections.abc import Callable
2
+ from typing import Generator
3
+ import random
4
+
5
+ from job_shop_lib import JobShopInstance
6
+
7
+
8
+ def modular_instance_generator(
9
+ machine_matrix_creator: Callable[
10
+ [random.Random], list[list[list[int]]] | list[list[int]]
11
+ ],
12
+ duration_matrix_creator: Callable[
13
+ [list[list[list[int]]] | list[list[int]], random.Random],
14
+ list[list[int]],
15
+ ],
16
+ *,
17
+ name_creator: Callable[[int], str] = lambda x: f"generated_instance_{x}",
18
+ release_dates_matrix_creator: (
19
+ Callable[
20
+ [list[list[int]], random.Random],
21
+ list[list[int]],
22
+ ]
23
+ | None
24
+ ) = None,
25
+ deadlines_matrix_creator: (
26
+ Callable[[list[list[int]], random.Random], list[list[int | None]]]
27
+ | None
28
+ ) = None,
29
+ due_dates_matrix_creator: (
30
+ Callable[[list[list[int]], random.Random], list[list[int | None]]]
31
+ | None
32
+ ) = None,
33
+ seed: int | None = None,
34
+ ) -> Generator[JobShopInstance, None, None]:
35
+ """Creates a generator function that produces job shop instances using
36
+ the provided components.
37
+
38
+ Args:
39
+ machine_matrix_creator:
40
+ A callable that creates a machine matrix.
41
+ duration_matrix_creator:
42
+ A callable that creates a duration matrix.
43
+ name_creator:
44
+ A callable that generates unique names for instances.
45
+ release_dates_matrix_creator:
46
+ An optional callable that generates release dates for jobs.
47
+ deadlines_matrix_creator:
48
+ An optional callable that generates deadlines for jobs.
49
+ due_dates_matrix_creator:
50
+ An optional callable that generates due dates for jobs.
51
+ seed:
52
+ An optional seed for random number generation.
53
+
54
+ Yields:
55
+ JobShopInstance:
56
+ A generated job shop instance created using the generated matrices.
57
+
58
+ Example:
59
+
60
+ >>> from job_shop_lib.generation import (
61
+ ... get_default_machine_matrix_creator,
62
+ ... get_default_duration_matrix_creator,
63
+ ... modular_instance_generator,
64
+ ... )
65
+ >>> machine_matrix_gen = get_default_machine_matrix_creator(
66
+ ... size_selector=lambda rng: (3, 3),
67
+ ... with_recirculation=False,
68
+ ... )
69
+ >>> duration_matrix_gen = get_default_duration_matrix_creator(
70
+ ... duration_range=(1, 10),
71
+ ... )
72
+ >>> instance_gen = modular_instance_generator(
73
+ ... machine_matrix_creator=machine_matrix_gen,
74
+ ... duration_matrix_creator=duration_matrix_gen,
75
+ ... seed=42,
76
+ ... )
77
+ >>> instance = next(instance_gen)
78
+ >>> print(instance)
79
+ JobShopInstance(name=generated_instance_0, num_jobs=3, num_machines=3)
80
+ >>> print(instance.duration_matrix_array)
81
+ [[ 5. 6. 4.]
82
+ [ 5. 7. 10.]
83
+ [ 9. 9. 5.]]
84
+
85
+ .. versionadded:: 1.7.0
86
+ """
87
+ rng = random.Random(seed)
88
+ i = 0
89
+ while True:
90
+ machine_matrix = machine_matrix_creator(rng)
91
+ duration_matrix = duration_matrix_creator(machine_matrix, rng)
92
+ release_dates = (
93
+ release_dates_matrix_creator(duration_matrix, rng)
94
+ if release_dates_matrix_creator
95
+ else None
96
+ )
97
+ deadlines = (
98
+ deadlines_matrix_creator(duration_matrix, rng)
99
+ if deadlines_matrix_creator
100
+ else None
101
+ )
102
+ due_dates = (
103
+ due_dates_matrix_creator(duration_matrix, rng)
104
+ if due_dates_matrix_creator
105
+ else None
106
+ )
107
+ instance = JobShopInstance.from_matrices(
108
+ duration_matrix,
109
+ machine_matrix,
110
+ name=name_creator(i),
111
+ release_dates_matrix=release_dates,
112
+ deadlines_matrix=deadlines,
113
+ due_dates_matrix=due_dates,
114
+ )
115
+ i += 1
116
+ yield instance
@@ -0,0 +1,160 @@
1
+ from typing import Sequence, Callable
2
+ import random
3
+
4
+ from job_shop_lib.exceptions import ValidationError
5
+
6
+
7
+ ReleaseDateStrategy = Callable[[random.Random, int], int]
8
+
9
+
10
+ def create_release_dates_matrix(
11
+ duration_matrix: list[list[int]],
12
+ strategy: ReleaseDateStrategy | None = None,
13
+ rng: random.Random | None = None,
14
+ ) -> list[list[int]]:
15
+ """Generate per-operation release dates for ragged job durations.
16
+
17
+ Args:
18
+ duration_matrix:
19
+ Ragged list of per-job operation durations.
20
+ strategy:
21
+ Callable implementing the release date policy. If ``None``
22
+ a default mixed strategy (alpha=0.7, beta=0.3) is built using the
23
+ computed horizon proxy.
24
+ rng:
25
+ Optional numpy random generator (one will be created if omitted).
26
+
27
+ Returns:
28
+ A ragged list mirroring ``duration_matrix`` structure with per-
29
+ operation release dates.
30
+
31
+ .. versionadded:: 1.7.0
32
+ """
33
+ rng = rng or random.Random()
34
+
35
+ num_jobs = len(duration_matrix)
36
+ if num_jobs == 0:
37
+ return []
38
+
39
+ if strategy is None:
40
+ horizon_proxy = compute_horizon_proxy(duration_matrix)
41
+ strategy = get_mixed_release_date_strategy(0.7, 0.3, horizon_proxy)
42
+
43
+ release_dates_matrix: list[list[int]] = []
44
+ for job_durations in duration_matrix:
45
+ job_release_dates: list[int] = []
46
+ cumulative_previous_duration = 0
47
+ for duration_value in job_durations:
48
+ release_date_value = strategy(rng, cumulative_previous_duration)
49
+ job_release_dates.append(release_date_value)
50
+ cumulative_previous_duration += int(duration_value)
51
+ release_dates_matrix.append(job_release_dates)
52
+
53
+ return release_dates_matrix
54
+
55
+
56
+ def compute_horizon_proxy(duration_matrix: Sequence[Sequence[int]]) -> int:
57
+ """Compute the horizon proxy used previously for the mixed strategy.
58
+
59
+ It is defined as: round(total_duration / avg_operations_per_job)
60
+ with protections against division by zero.
61
+
62
+ Args:
63
+ duration_matrix:
64
+ Ragged list of per-job operation durations.
65
+
66
+ Returns:
67
+ The computed horizon proxy.
68
+
69
+ .. seealso::
70
+ :meth:`job_shop_lib.JobShopInstance.duration_matrix`
71
+
72
+ .. versionadded:: 1.7.0
73
+ """
74
+ num_jobs = len(duration_matrix)
75
+ if num_jobs == 0:
76
+ return 0
77
+ total_duration = sum(sum(job) for job in duration_matrix)
78
+ total_operations = sum(len(job) for job in duration_matrix)
79
+ avg_ops_per_job = total_operations / max(1, num_jobs)
80
+ return round(total_duration / max(1, avg_ops_per_job))
81
+
82
+
83
+ def get_independent_release_date_strategy(
84
+ max_release_time: int,
85
+ ) -> ReleaseDateStrategy:
86
+ """Factory for an independent (pure random) release date strategy.
87
+
88
+ The release date is drawn uniformly at random in the interval
89
+ ``[0, max_release_time]``.
90
+
91
+ Args:
92
+ max_release_time:
93
+ Inclusive upper bound for the random value.
94
+
95
+ Returns:
96
+ A callable implementing the independent release date strategy.
97
+
98
+ .. versionadded:: 1.7.0
99
+ """
100
+ if max_release_time < 0:
101
+ raise ValidationError("'max_release_time' must be >= 0.")
102
+
103
+ def _strategy(rng: random.Random, unused_cumulative_prev: int) -> int:
104
+ return int(rng.randint(0, max_release_time))
105
+
106
+ return _strategy
107
+
108
+
109
+ def get_cumulative_release_date_strategy(
110
+ maximum_slack: int = 0,
111
+ ) -> ReleaseDateStrategy:
112
+ """Factory for a cumulative strategy allowing forward slack.
113
+
114
+ The release date is the cumulative previous processing time plus a
115
+ random slack in ``[0, maximum_slack]``.
116
+
117
+ Args:
118
+ maximum_slack:
119
+ Non-negative integer defining the maximum forward slack to add.
120
+
121
+ Returns:
122
+ A callable implementing the cumulative release date strategy.
123
+ """
124
+ if maximum_slack < 0:
125
+ raise ValidationError("'maximum_slack' must be >= 0.")
126
+
127
+ def _strategy(rng: random.Random, cumulative_prev: int) -> int:
128
+ return cumulative_prev + rng.randint(0, maximum_slack)
129
+
130
+ return _strategy
131
+
132
+
133
+ def get_mixed_release_date_strategy(
134
+ alpha: float,
135
+ beta: float,
136
+ horizon_proxy: int,
137
+ ) -> ReleaseDateStrategy:
138
+ """Factory for the mixed heuristic strategy.
139
+
140
+ release_date = alpha * cumulative_previous + U(0, beta * horizon_proxy)
141
+
142
+ Args:
143
+ alpha:
144
+ Weight for the proportional cumulative component.
145
+ beta:
146
+ Weight for the random component upper bound.
147
+ horizon_proxy:
148
+ Non-negative proxy for the time horizon (e.g. derived
149
+ from durations to scale the random component consistently).
150
+ """
151
+ if horizon_proxy < 0:
152
+ raise ValidationError("'horizon_proxy' must be >= 0.")
153
+
154
+ random_component_upper = round(beta * horizon_proxy)
155
+
156
+ def _strategy(rng: random.Random, cumulative_prev: int) -> int:
157
+ random_component = rng.randint(0, random_component_upper)
158
+ return round(alpha * cumulative_prev) + random_component
159
+
160
+ return _strategy
@@ -0,0 +1,58 @@
1
+ import random
2
+
3
+
4
+ def range_size_selector(
5
+ rng: random.Random,
6
+ num_jobs_range: tuple[int, int] = (10, 20),
7
+ num_machines_range: tuple[int, int] = (5, 10),
8
+ allow_less_jobs_than_machines: bool = True,
9
+ ) -> tuple[int, int]:
10
+ """Selects the number of jobs and machines based on the provided ranges
11
+ and constraints.
12
+
13
+ Args:
14
+ rng:
15
+ A ``random.Random`` instance.
16
+ num_jobs_range:
17
+ A tuple specifying the inclusive range for the number of jobs.
18
+ num_machines_range:
19
+ A tuple specifying the inclusive range for the number of machines.
20
+ allow_less_jobs_than_machines:
21
+ If ``False``, ensures that the number of jobs is not less than the
22
+ number of machines.
23
+
24
+ Returns:
25
+ A tuple containing the selected number of jobs and machines.
26
+ """
27
+ num_jobs = rng.randint(num_jobs_range[0], num_jobs_range[1])
28
+
29
+ min_num_machines, max_num_machines = num_machines_range
30
+ if not allow_less_jobs_than_machines:
31
+ # Cap the maximum machines to the sampled number of jobs.
32
+ max_num_machines = min(max_num_machines, num_jobs)
33
+ # If min > capped max, collapse interval so we return a valid value
34
+ # (e.g. jobs=3, range=(5,10)).
35
+ if min_num_machines > max_num_machines:
36
+ min_num_machines = max_num_machines
37
+ num_machines = rng.randint(min_num_machines, max_num_machines)
38
+
39
+ return num_jobs, num_machines
40
+
41
+
42
+ def choice_size_selector(
43
+ rng: random.Random,
44
+ options: list[tuple[int, int]],
45
+ ) -> tuple[int, int]:
46
+ """Selects the number of jobs and machines from a list of options.
47
+
48
+ Args:
49
+ rng:
50
+ A ``random.Random`` instance.
51
+ options:
52
+ A list of tuples, where each tuple contains a pair of integers
53
+ representing the number of jobs and machines.
54
+
55
+ Returns:
56
+ A tuple containing the selected number of jobs and machines.
57
+ """
58
+ return rng.choice(options)
@@ -31,12 +31,16 @@ contribute!
31
31
  swap_random_operations
32
32
  ObjectiveFunction
33
33
  get_makespan_with_penalties_objective
34
+ compute_penalty_for_deadlines
35
+ compute_penalty_for_due_dates
34
36
 
35
37
  """
36
38
 
37
39
  from job_shop_lib.metaheuristics._objective_functions import (
38
40
  ObjectiveFunction,
39
41
  get_makespan_with_penalties_objective,
42
+ compute_penalty_for_deadlines,
43
+ compute_penalty_for_due_dates,
40
44
  )
41
45
  from job_shop_lib.metaheuristics._neighbor_generators import (
42
46
  NeighborGenerator,
@@ -58,4 +62,6 @@ __all__ = [
58
62
  "swap_random_operations",
59
63
  "ObjectiveFunction",
60
64
  "get_makespan_with_penalties_objective",
65
+ "compute_penalty_for_deadlines",
66
+ "compute_penalty_for_due_dates",
61
67
  ]
@@ -45,29 +45,70 @@ def get_makespan_with_penalties_objective(
45
45
 
46
46
  def objective(schedule: Schedule) -> float:
47
47
  makespan = schedule.makespan()
48
- instance = schedule.instance
49
-
50
- # Fast path: no constraint attributes present in the instance
51
- if not instance.has_deadlines and not instance.has_due_dates:
52
- return makespan
53
-
54
- penalty = 0.0
55
- for machine_schedule in schedule.schedule:
56
- for scheduled_op in machine_schedule:
57
- op = scheduled_op.operation
58
- # Deadline (hard) penalty
59
- if (
60
- op.deadline is not None
61
- and scheduled_op.end_time > op.deadline
62
- ):
63
- penalty += deadline_penalty_factor
64
- # Due date (soft) penalty
65
- if (
66
- op.due_date is not None
67
- and scheduled_op.end_time > op.due_date
68
- ):
69
- penalty += due_date_penalty_factor
48
+ penalty_for_deadlines = compute_penalty_for_deadlines(
49
+ schedule, deadline_penalty_factor
50
+ )
51
+ penalty_for_due_dates = compute_penalty_for_due_dates(
52
+ schedule, due_date_penalty_factor
53
+ )
54
+ penalty = penalty_for_deadlines + penalty_for_due_dates
70
55
 
71
56
  return makespan + penalty
72
57
 
73
58
  return objective
59
+
60
+
61
+ def compute_penalty_for_deadlines(
62
+ schedule: Schedule, penalty_per_violation: float
63
+ ) -> float:
64
+ """Compute the total penalty for deadline violations in a schedule.
65
+
66
+ Args:
67
+ schedule:
68
+ The schedule to evaluate.
69
+ penalty_per_violation:
70
+ The penalty to apply for each operation that
71
+ finishes after its deadline.
72
+
73
+ Returns:
74
+ The total penalty for deadline violations.
75
+ """
76
+ if not schedule.instance.has_deadlines or penalty_per_violation == 0:
77
+ return 0.0
78
+
79
+ penalty = 0.0
80
+ for machine_schedule in schedule.schedule:
81
+ for scheduled_op in machine_schedule:
82
+ op = scheduled_op.operation
83
+ if op.deadline is not None and scheduled_op.end_time > op.deadline:
84
+ penalty += penalty_per_violation
85
+
86
+ return penalty
87
+
88
+
89
+ def compute_penalty_for_due_dates(
90
+ schedule: Schedule, penalty_per_violation: float
91
+ ) -> float:
92
+ """Compute the total penalty for due date violations in a schedule.
93
+
94
+ Args:
95
+ schedule:
96
+ The schedule to evaluate.
97
+ penalty_per_violation:
98
+ The penalty to apply for each operation that
99
+ finishes after its due date.
100
+
101
+ Returns:
102
+ The total penalty for due date violations.
103
+ """
104
+ if not schedule.instance.has_due_dates or penalty_per_violation == 0:
105
+ return 0.0
106
+
107
+ penalty = 0.0
108
+ for machine_schedule in schedule.schedule:
109
+ for scheduled_op in machine_schedule:
110
+ op = scheduled_op.operation
111
+ if op.due_date is not None and scheduled_op.end_time > op.due_date:
112
+ penalty += penalty_per_violation
113
+
114
+ return penalty
@@ -14,11 +14,14 @@
14
14
  RewardObserver
15
15
  MakespanReward
16
16
  IdleTimeReward
17
+ RewardWithPenalties
17
18
  RenderConfig
18
19
  add_padding
19
20
  create_edge_type_dict
20
21
  map_values
21
22
  get_optimal_actions
23
+ get_deadline_violation_penalty
24
+ get_due_date_violation_penalty
22
25
 
23
26
  """
24
27
 
@@ -32,6 +35,7 @@ from job_shop_lib.reinforcement_learning._reward_observers import (
32
35
  RewardObserver,
33
36
  MakespanReward,
34
37
  IdleTimeReward,
38
+ RewardWithPenalties,
35
39
  )
36
40
 
37
41
  from job_shop_lib.reinforcement_learning._utils import (
@@ -39,6 +43,8 @@ from job_shop_lib.reinforcement_learning._utils import (
39
43
  create_edge_type_dict,
40
44
  map_values,
41
45
  get_optimal_actions,
46
+ get_deadline_violation_penalty,
47
+ get_due_date_violation_penalty,
42
48
  )
43
49
 
44
50
  from job_shop_lib.reinforcement_learning._single_job_shop_graph_env import (
@@ -63,9 +69,12 @@ __all__ = [
63
69
  "RewardObserver",
64
70
  "MakespanReward",
65
71
  "IdleTimeReward",
72
+ "RewardWithPenalties",
66
73
  "RenderConfig",
67
74
  "add_padding",
68
75
  "create_edge_type_dict",
69
76
  "map_values",
70
77
  "get_optimal_actions",
78
+ "get_deadline_violation_penalty",
79
+ "get_due_date_violation_penalty",
71
80
  ]
@@ -1,6 +1,9 @@
1
1
  """Rewards functions are defined as `DispatcherObervers` and are used to
2
2
  calculate the reward for a given state."""
3
3
 
4
+ from collections.abc import Callable
5
+
6
+ from job_shop_lib.exceptions import ValidationError
4
7
  from job_shop_lib.dispatching import DispatcherObserver, Dispatcher
5
8
  from job_shop_lib import ScheduledOperation
6
9
 
@@ -83,3 +86,76 @@ class IdleTimeReward(RewardObserver):
83
86
 
84
87
  reward = -idle_time
85
88
  self.rewards.append(reward)
89
+
90
+
91
+ class RewardWithPenalties(RewardObserver):
92
+ """Reward function that adds penalties to another reward function.
93
+
94
+ The reward is calculated as the sum of the reward from another reward
95
+ function and a penalty for each constraint violation (due dates and
96
+ deadlines).
97
+
98
+ Attributes:
99
+ base_reward_observer:
100
+ The base reward observer to use for calculating the reward.
101
+ penalty_function:
102
+ A function that takes a scheduled operation and the dispatcher as
103
+ input and returns the penalty for that operation.
104
+
105
+ Args:
106
+ dispatcher:
107
+ The dispatcher to observe.
108
+ base_reward_observer:
109
+ The base reward observer to use for calculating the reward. It
110
+ must use the same dispatcher as this reward observer. If it is
111
+ subscribed to the dispatcher, it will be unsubscribed.
112
+ penalty_function:
113
+ A function that takes a scheduled operation and the
114
+ dispatcher as input and returns the penalty for that operation.
115
+ subscribe:
116
+ Whether to subscribe to the dispatcher upon initialization.
117
+
118
+ Raises:
119
+ ValidationError:
120
+ If the base reward observer does not use the same dispatcher as
121
+ this reward observer.
122
+
123
+ .. versionadded:: 1.7.0
124
+
125
+ .. seealso::
126
+ The following functions (along with ``functools.partial``) can be
127
+ used to create penalty functions:
128
+
129
+ - :func:`~job_shop_lib.reinforcement_learning.get_deadline_violation_penalty`
130
+ - :func:`~job_shop_lib.reinforcement_learning.get_due_date_violation_penalty`
131
+
132
+ """ # noqa: E501
133
+
134
+ def __init__(
135
+ self,
136
+ dispatcher: Dispatcher,
137
+ *,
138
+ base_reward_observer: RewardObserver,
139
+ penalty_function: Callable[[ScheduledOperation, Dispatcher], float],
140
+ subscribe: bool = True,
141
+ ) -> None:
142
+ super().__init__(dispatcher, subscribe=subscribe)
143
+ self.base_reward_observer = base_reward_observer
144
+ self.penalty_function = penalty_function
145
+ if base_reward_observer.dispatcher is not dispatcher:
146
+ raise ValidationError(
147
+ "The base reward observer must use the same "
148
+ "dispatcher as this reward observer."
149
+ )
150
+ if base_reward_observer in dispatcher.subscribers:
151
+ dispatcher.unsubscribe(base_reward_observer)
152
+
153
+ def reset(self) -> None:
154
+ super().reset()
155
+ self.base_reward_observer.reset()
156
+
157
+ def update(self, scheduled_operation: ScheduledOperation):
158
+ self.base_reward_observer.update(scheduled_operation)
159
+ base_reward = self.base_reward_observer.last_reward
160
+ penalty = self.penalty_function(scheduled_operation, self.dispatcher)
161
+ self.rewards.append(base_reward - penalty)
@@ -5,8 +5,9 @@ from typing import TypeVar, Any
5
5
  import numpy as np
6
6
  from numpy.typing import NDArray
7
7
 
8
+ from job_shop_lib import ScheduledOperation
8
9
  from job_shop_lib.exceptions import ValidationError
9
- from job_shop_lib.dispatching import OptimalOperationsObserver
10
+ from job_shop_lib.dispatching import OptimalOperationsObserver, Dispatcher
10
11
 
11
12
  T = TypeVar("T", bound=np.number)
12
13
 
@@ -193,7 +194,65 @@ def get_optimal_actions(
193
194
  return optimal_actions
194
195
 
195
196
 
196
- if __name__ == "__main__":
197
- import doctest
197
+ def get_deadline_violation_penalty(
198
+ scheduled_operation: ScheduledOperation,
199
+ unused_dispatcher: Dispatcher,
200
+ deadline_penalty_factor: float = 10_000,
201
+ ) -> float:
202
+ """Compute the penalty for a scheduled operation that violates its
203
+ deadline.
198
204
 
199
- doctest.testmod()
205
+ Args:
206
+ scheduled_operation:
207
+ The scheduled operation to evaluate.
208
+ unused_dispatcher:
209
+ This argument is unused but included for compatibility with the
210
+ penalty function signature.
211
+ deadline_penalty_factor:
212
+ Cost added for each operation that
213
+ finishes after its deadline. Defaults to 10_000.
214
+ Returns:
215
+ The penalty for the scheduled operation if it violates its deadline,
216
+ otherwise 0.
217
+
218
+ .. versionadded:: 1.7.0
219
+ """
220
+ if (
221
+ scheduled_operation.operation.deadline is not None
222
+ and scheduled_operation.end_time
223
+ > scheduled_operation.operation.deadline
224
+ ):
225
+ return deadline_penalty_factor
226
+ return 0.0
227
+
228
+
229
+ def get_due_date_violation_penalty(
230
+ scheduled_operation: ScheduledOperation,
231
+ unused_dispatcher: Dispatcher,
232
+ due_date_penalty_factor: float = 100,
233
+ ) -> float:
234
+ """Compute the penalty for a scheduled operation that violates its
235
+ due date.
236
+
237
+ Args:
238
+ scheduled_operation:
239
+ The scheduled operation to evaluate.
240
+ unused_dispatcher:
241
+ This argument is unused but included for compatibility with the
242
+ penalty function signature.
243
+ due_date_penalty_factor:
244
+ Cost added for each operation that
245
+ finishes after its due date. Defaults to 100.
246
+ Returns:
247
+ The penalty for the scheduled operation if it violates its due date,
248
+ otherwise 0.
249
+
250
+ .. versionadded:: 1.7.0
251
+ """
252
+ if (
253
+ scheduled_operation.operation.due_date is not None
254
+ and scheduled_operation.end_time
255
+ > scheduled_operation.operation.due_date
256
+ ):
257
+ return due_date_penalty_factor
258
+ return 0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: job-shop-lib
3
- Version: 1.6.1
3
+ Version: 1.7.0
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
@@ -45,13 +45,13 @@ It follows a modular design, allowing users to easily extend the library with ne
45
45
  We support multiple solvers, including:
46
46
  - **Constraint Programming**: Based on OR-Tools' CP-SAT solver. It supports **release dates, deadlines, and due dates.** See the ["Solving the Problem" tutorial](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/02-Solving-the-Problem.ipynb) for an example.
47
47
  - **Dispatching Rules**: A set of predefined rules and the ability to create custom ones. They support arbitrary **setup times, machine breakdowns, release dates, deadlines, and due dates**. See the [following example](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/03-Dispatching-Rules.ipynb). You can also create videos or GIFs of the scheduling process. For creating GIFs or videos, see the [Save Gif example](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/04-Save-Gif.ipynb).
48
- - **Metaheuristics**: Currently, we have a **simulated annealing** implementation that supports **release dates, deadlines, and due dates**. We also support arbitrary neighborhood search strategies, including swapping operations in the critical path as described in the paper "Job Shop Scheduling by Simulated Annealing" by van Laarhoven et al. (1992); and energy functions. See our [simulated annealing tutorial](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/03-Simulated-Annealing.ipynb).
48
+ - **Metaheuristics**: Currently, we have a **simulated annealing** implementation that supports **release dates, deadlines, and due dates**. We also support arbitrary neighborhood search strategies, including swapping operations in the critical path as described in the paper "Job Shop Scheduling by Simulated Annealing" by van Laarhoven et al. (1992); and energy functions. See our [simulated annealing tutorial](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/04-Simulated-Annealing.ipynb).
49
49
  - **Reinforcement Learning**: Two Gymnasium environments for solving the problem with **graph neural networks** (GNNs) or any other method. The environments support **setup times, release dates, deadlines, and due dates.** We're currently building a tutorial on how to use them.
50
50
 
51
51
  We also provide useful utilities, data structures, and visualization functions:
52
52
  - **Intuitive Data Structures**: Easily create, manage, and manipulate job shop instances and solutions with user-friendly data structures. See [Getting Started](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/00-Getting-Started.ipynb) and [How Solutions are Represented](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/01-How-Solutions-are-Represented.ipynb).
53
53
  - **Benchmark Instances**: Load well-known benchmark instances directly from the library without manual downloading. See [Load Benchmark Instances](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/examples/05-Load-Benchmark-Instances.ipynb).
54
- - **Random Instance Generation**: Create random instances with customizable sizes and properties. See [`generation`](https://job-shop-lib.readthedocs.io/en/stable/api/job_shop_lib.generation.html#module-job_shop_lib.generation) module.
54
+ - **Random Instance Generation**: Create random instances with customizable sizes and properties. See [`this tutorial`](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/03-Generating-New-Instances.ipynb).
55
55
  - **Gantt Charts**: Visualize final schedules and how they are created iteratively by dispatching rule solvers or sequences of scheduling decisions with GIFs or videos.
56
56
  - **Graph Representations**: Represent and visualize instances as disjunctive graphs or agent-task graphs (introduced in the ScheduleNet paper). Build your own custom graphs with the `JobShopGraph` class. See the [Disjunctive Graphs](https://github.com/Pabloo22/job_shop_lib/blob/main/docs/source/tutorial/04-Disjunctive-Graphs.ipynb) and [Resource Task Graphs](https://job-shop-lib.readthedocs.io/en/stable/examples/07-Resource-Task-Graph.html) examples.
57
57
 
@@ -1,11 +1,11 @@
1
- job_shop_lib/__init__.py,sha256=_kA4WyA9KQUW4ZXB4anAXrhQfvhrLj38YLLHovGvFfA,639
1
+ job_shop_lib/__init__.py,sha256=2tXYQ5nb_6v3J7mpsXC1vRMI2khj2BTRijff-8j6ZLY,639
2
2
  job_shop_lib/_base_solver.py,sha256=8CCSiA2-DegCKRXhMw7yYyI8iPauTSuLku2LQ8dU-9U,1382
3
- job_shop_lib/_job_shop_instance.py,sha256=_92orxdi70645J7cQlRE1I0PebvpHRCP6958q9j2h18,24261
3
+ job_shop_lib/_job_shop_instance.py,sha256=tQDJwKCgimzAcEK6vdAAePKiF_5AvOmzHZcOptfVkd0,25403
4
4
  job_shop_lib/_operation.py,sha256=JI5WjvRXNBeSpPOv3ZwSrUJ4jsVDJYKfMaDHYOaFYts,5945
5
5
  job_shop_lib/_schedule.py,sha256=jvidw6iIh05QGe2OeA6JkiQuCzLoOtgqny8zj95_sGA,18173
6
6
  job_shop_lib/_scheduled_operation.py,sha256=czrGr87EOTlO2NPolIN5CDigeiCzvQEyra5IZPwSFZc,2801
7
- job_shop_lib/benchmarking/__init__.py,sha256=JPnCw5mK7sADAW0HctVKHEDRw22afp9caNh2eUS36Ys,3290
8
- job_shop_lib/benchmarking/_load_benchmark.py,sha256=-cgyx0Kn6uAc3KdGFSQb6eUVQjQggmpVKOH9qusNkXI,2930
7
+ job_shop_lib/benchmarking/__init__.py,sha256=M4hGy3PbNoNtcYUAJkGTYfPnk3hFVkhg5BYr6g3iB6Q,3369
8
+ job_shop_lib/benchmarking/_load_benchmark.py,sha256=5NvDXKvwtff9Vw6y5R2f2I2r2YNekMbQsSt0GXzZeao,3684
9
9
  job_shop_lib/benchmarking/benchmark_instances.json,sha256=F9EvyzFwVxiKAN6rQTsrMhsKstmyUmroyWduM7a00KQ,464841
10
10
  job_shop_lib/constraint_programming/__init__.py,sha256=kKQRUxxS_nVFUdXGnf4bQOD9mqrXxZZWElS753A4YiA,454
11
11
  job_shop_lib/constraint_programming/_ortools_solver.py,sha256=trTQtqSL2F2PXxd9RPnFhxaY8blNcfFUhTdab5QP9VU,12585
@@ -21,8 +21,8 @@ job_shop_lib/dispatching/_unscheduled_operations_observer.py,sha256=0he-j4OlvqtX
21
21
  job_shop_lib/dispatching/feature_observers/__init__.py,sha256=Pzud4tuO_t72d9KY_nEH-stGOvKUTNjo_6GeWDuJPvc,2322
22
22
  job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py,sha256=tpvqTLIcNmbYROSFT62LiUZ_tI4fHWL_qCULKK43BU4,6429
23
23
  job_shop_lib/dispatching/feature_observers/_dates_observer.py,sha256=oCk1XAo_2mrgD0ckHQLw3dD7DSQVVg7xBKn7D_u1Dvc,6083
24
- job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=pBsJjT-1pbSi32hoLppoqXCftBvJPSh7r7tl3m7etAQ,4225
25
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=AQIjVp7VRDnb5GuYZlLUwk-xiXSqbsxJW-Ji7NjLoAw,11452
24
+ job_shop_lib/dispatching/feature_observers/_duration_observer.py,sha256=3Z8CA9k4OKKjVcclCnyUrtr7USM7M4rSvp2hhwUir3A,4224
25
+ job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py,sha256=Jm6g3LUwVCiXVghSsJ1XyaxfAu5sYT1t8OunfVjHDqc,11451
26
26
  job_shop_lib/dispatching/feature_observers/_factory.py,sha256=NyXYK5A1hXsYEeEqngwVRNAFkevY95DglheeqyfFv8s,3217
27
27
  job_shop_lib/dispatching/feature_observers/_feature_observer.py,sha256=qbgtMUicQ5FWS-Ql4Izjsj4QrevfOGlWzoJ0JlVSLH0,8668
28
28
  job_shop_lib/dispatching/feature_observers/_is_completed_observer.py,sha256=EYJOyWL8ApUElLucoHnFlt0g2Ior_1yO7Q8V3FU_Qog,3576
@@ -37,10 +37,14 @@ job_shop_lib/dispatching/rules/_dispatching_rules_functions.py,sha256=c-T6jUXZ2w
37
37
  job_shop_lib/dispatching/rules/_machine_chooser_factory.py,sha256=CJ74ujgWXgG8cuULWY6VJkD_b3arTcOjTNLZJTAf8xE,2346
38
38
  job_shop_lib/dispatching/rules/_utils.py,sha256=m5qw4qyfaIvVrkmv51nuhreizr98-cg8AJKt2VTd48w,4603
39
39
  job_shop_lib/exceptions.py,sha256=ARzpoZJCvRIvOesCiqqFSRxkv6w9WwEXx0aBP-l2IKA,1597
40
- job_shop_lib/generation/__init__.py,sha256=QaWwuBfBNnOiG0OPiP_CV_flBu9dX7r2o_HwL47tREM,822
40
+ job_shop_lib/generation/__init__.py,sha256=6H94ZsuvaRWIFzvDFPvCoaqNTLp_R2RL0DgLl47IC2s,2197
41
+ job_shop_lib/generation/_duration_matrix.py,sha256=l6T06SGUYQC0BFgQhw2j7RLi7yrK_CLunGuYmAyWglQ,2475
41
42
  job_shop_lib/generation/_general_instance_generator.py,sha256=b_tnyP4H_buoN7b6lKQRLvDkeZDdys0mpqS3thB5-SQ,6544
42
- job_shop_lib/generation/_instance_generator.py,sha256=doN6WySyI0k7wz3aKy_e6hj6t7WV3dNzve3YmTFShas,4584
43
- job_shop_lib/generation/_utils.py,sha256=b3SVU5DY3-VHXX2yrOwM7ABDSexiSFSRbo1d5QjRfoI,3972
43
+ job_shop_lib/generation/_instance_generator.py,sha256=wnXk3dS7TQu5fNPsbkqbjvcuUdaZ2XE0w9ITsSVLiyY,4628
44
+ job_shop_lib/generation/_machine_matrix.py,sha256=l56h0b1I39_zTm5h0Hnc217V5vmnEB-k8_xoxGL4taw,3928
45
+ job_shop_lib/generation/_modular_instance_generator.py,sha256=pDwhbCKc85kTUCuQZEKirF-ILnv67Ln618jhTn_ltis,3900
46
+ job_shop_lib/generation/_release_date_matrix.py,sha256=rv-lcqg-z3uYgsoEKPf1r7iGZsKXf9tnSkvMxEmx-Oc,4981
47
+ job_shop_lib/generation/_size_selectors.py,sha256=y9jUOjR3hCk0dUU3H_Xce4Pok7X-YVybTuP_GK66CuQ,1969
44
48
  job_shop_lib/graphs/__init__.py,sha256=wlYIiXTuZRE6Kx3K0RpPUoZikzoegBuN2hcdqMODtGk,2433
45
49
  job_shop_lib/graphs/_build_disjunctive_graph.py,sha256=UbUYdeQaaeEqLchcKJGHEFGl4wElfGLb1o_R-u8wqnA,5120
46
50
  job_shop_lib/graphs/_build_resource_task_graphs.py,sha256=vIy_EkQjgQAd5YyJxKAuGf7CLTjgCfhz-fYrObF4DTU,6962
@@ -52,19 +56,19 @@ job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py,sha256=-t0T8W-J
52
56
  job_shop_lib/graphs/graph_updaters/_graph_updater.py,sha256=j1f7iWsa62GVszK2BPaMxnKBCEGWa9owm8g4VWUje8w,1967
53
57
  job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py,sha256=9NG3pu7Z5h-ZTfX8rRiZbI_NfNQi80h-XUHainshjZY,6064
54
58
  job_shop_lib/graphs/graph_updaters/_utils.py,sha256=sdw2Vo75P9c6Fy-YBlfgpXb9gPwHUluTB1E-9WINm_g,730
55
- job_shop_lib/metaheuristics/__init__.py,sha256=z7bHN5vP-ctOSL0eUYK-aRyhRkU2lrDyA4kBs15EME0,1884
59
+ job_shop_lib/metaheuristics/__init__.py,sha256=CfuajbzO1J21_FsA1J_6UpODvPSwzi_VNAh7xEEoe_Q,2096
56
60
  job_shop_lib/metaheuristics/_job_shop_annealer.py,sha256=Ty4SLPZh1NrL-XRqU76EeN8fwUdKfqbphqfYEDje1lQ,9195
57
61
  job_shop_lib/metaheuristics/_neighbor_generators.py,sha256=3RePlnYvJdpdhObmf0m_3NhyUM7avfNr4vOZT0PWTRQ,6563
58
- job_shop_lib/metaheuristics/_objective_functions.py,sha256=GG5M3LoLnNzo1zxzfpNMvo4bdYlqWuhVA8mIkXFsxxM,2607
62
+ job_shop_lib/metaheuristics/_objective_functions.py,sha256=XovebJbvUyuwgcnS6C8P-hGlMXZ7bFxKOM2He5NUH0Q,3727
59
63
  job_shop_lib/metaheuristics/_simulated_annealing_solver.py,sha256=EMCrFl2zzJubrvCMi5upm8lnUgtBizhZbi4EvbnIsM4,6200
60
64
  job_shop_lib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- job_shop_lib/reinforcement_learning/__init__.py,sha256=sAVgxylKfBnn2rrz0BFcab1kjvQQ1h-hgldfbkPF--E,1537
65
+ job_shop_lib/reinforcement_learning/__init__.py,sha256=aPB3XqOzRxtpjL5S3kjNXZa7L8Z2OFnO48J5wTc9DbQ,1831
62
66
  job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py,sha256=6nXw67Tfmim3LqlSuQ9Cfg3mMY-VmbMHuXfyOL90jng,15740
63
67
  job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py,sha256=ZqN6kzuXbO0BdA1UKrTEHeiHAKzRNIeuH-dBt90ttEc,12914
64
- job_shop_lib/reinforcement_learning/_reward_observers.py,sha256=4Kdyn9Jlp_1sBtVr6raF-ZFtcnKxwyCLykfX53TmuhU,2959
68
+ job_shop_lib/reinforcement_learning/_reward_observers.py,sha256=CBQ-QrTs4ymYEqqIsAXnsiEXX5BgakOrT1FweM87l_4,5858
65
69
  job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py,sha256=MnQYCVpwX4WHiGhYguHziDUrPIrKmXsjOUDoTmuoCBc,16403
66
70
  job_shop_lib/reinforcement_learning/_types_and_constants.py,sha256=6FpuQkZLV2H8_dXmax49OTgAw7dWQcUEWVWWdMLR7bs,1752
67
- job_shop_lib/reinforcement_learning/_utils.py,sha256=aHgNdW7YvUH8QM3l7NGIfrgzfpzGoklyYm1jM2Isi6Q,6043
71
+ job_shop_lib/reinforcement_learning/_utils.py,sha256=sxKFulDrt1Aei3etLEgsdFVFRmXKNIhFLoOKOWqIz00,8022
68
72
  job_shop_lib/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
73
  job_shop_lib/visualization/gantt/__init__.py,sha256=xMvuNph6bfwulHYqqklCj_6SUQgRzvC92Yul75F3Zlg,1250
70
74
  job_shop_lib/visualization/gantt/_gantt_chart_creator.py,sha256=FgE4SmKLYKnS7dfTFgnBklWhwGyIo0DKWVkmxusDmp8,8606
@@ -73,7 +77,7 @@ job_shop_lib/visualization/gantt/_plot_gantt_chart.py,sha256=_4UGUTRuIw0tLzsJD9G
73
77
  job_shop_lib/visualization/graphs/__init__.py,sha256=HUWzfgQLeklNROtjnxeJX_FIySo_baTXO6klx0zUVpQ,630
74
78
  job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py,sha256=L9_ZGgvCFpGc2rTOdZESdtydFQqShjqedimIOhqZx6Y,16209
75
79
  job_shop_lib/visualization/graphs/_plot_resource_task_graph.py,sha256=nkkdZ-9_OBevw72Frecwzv1y3WyhGZ9r9lz0y9MXvZ8,13192
76
- job_shop_lib-1.6.1.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
77
- job_shop_lib-1.6.1.dist-info/METADATA,sha256=4cuNTeLFoaVOMBqGvymRzwXKgTHQSt3ZgQ-iKr0Xs1s,19159
78
- job_shop_lib-1.6.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
79
- job_shop_lib-1.6.1.dist-info/RECORD,,
80
+ job_shop_lib-1.7.0.dist-info/LICENSE,sha256=9mggivMGd5taAu3xbmBway-VQZMBzurBGHofFopvUsQ,1069
81
+ job_shop_lib-1.7.0.dist-info/METADATA,sha256=goMjnzDFnjTYw4BEwApgq5gSs9SZtFr98WR8qRakRs0,19151
82
+ job_shop_lib-1.7.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
83
+ job_shop_lib-1.7.0.dist-info/RECORD,,