job-shop-lib 0.5.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- job_shop_lib/__init__.py +19 -8
- job_shop_lib/{base_solver.py → _base_solver.py} +1 -1
- job_shop_lib/{job_shop_instance.py → _job_shop_instance.py} +155 -81
- job_shop_lib/_operation.py +118 -0
- job_shop_lib/{schedule.py → _schedule.py} +102 -84
- job_shop_lib/{scheduled_operation.py → _scheduled_operation.py} +25 -49
- job_shop_lib/benchmarking/__init__.py +66 -43
- job_shop_lib/benchmarking/_load_benchmark.py +88 -0
- job_shop_lib/constraint_programming/__init__.py +13 -0
- job_shop_lib/{cp_sat/ortools_solver.py → constraint_programming/_ortools_solver.py} +77 -22
- job_shop_lib/dispatching/__init__.py +51 -42
- job_shop_lib/dispatching/{dispatcher.py → _dispatcher.py} +223 -130
- job_shop_lib/dispatching/_dispatcher_observer_config.py +67 -0
- job_shop_lib/dispatching/_factories.py +135 -0
- job_shop_lib/dispatching/{history_tracker.py → _history_observer.py} +6 -7
- job_shop_lib/dispatching/_optimal_operations_observer.py +113 -0
- job_shop_lib/dispatching/_ready_operation_filters.py +168 -0
- job_shop_lib/dispatching/_unscheduled_operations_observer.py +70 -0
- job_shop_lib/dispatching/feature_observers/__init__.py +51 -13
- job_shop_lib/dispatching/feature_observers/_composite_feature_observer.py +212 -0
- job_shop_lib/dispatching/feature_observers/{duration_observer.py → _duration_observer.py} +20 -18
- job_shop_lib/dispatching/feature_observers/_earliest_start_time_observer.py +289 -0
- job_shop_lib/dispatching/feature_observers/_factory.py +95 -0
- job_shop_lib/dispatching/feature_observers/_feature_observer.py +228 -0
- job_shop_lib/dispatching/feature_observers/_is_completed_observer.py +97 -0
- job_shop_lib/dispatching/feature_observers/_is_ready_observer.py +35 -0
- job_shop_lib/dispatching/feature_observers/{is_scheduled_observer.py → _is_scheduled_observer.py} +9 -5
- job_shop_lib/dispatching/feature_observers/{position_in_job_observer.py → _position_in_job_observer.py} +8 -10
- job_shop_lib/dispatching/feature_observers/{remaining_operations_observer.py → _remaining_operations_observer.py} +8 -26
- job_shop_lib/dispatching/rules/__init__.py +87 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_factory.py +84 -0
- job_shop_lib/dispatching/rules/_dispatching_rule_solver.py +201 -0
- job_shop_lib/dispatching/{dispatching_rules.py → rules/_dispatching_rules_functions.py} +70 -16
- job_shop_lib/dispatching/rules/_machine_chooser_factory.py +71 -0
- job_shop_lib/dispatching/rules/_utils.py +128 -0
- job_shop_lib/exceptions.py +18 -0
- job_shop_lib/generation/__init__.py +10 -2
- job_shop_lib/generation/_general_instance_generator.py +165 -0
- job_shop_lib/generation/{instance_generator.py → _instance_generator.py} +37 -26
- job_shop_lib/{generators/transformations.py → generation/_transformations.py} +16 -12
- job_shop_lib/generation/_utils.py +124 -0
- job_shop_lib/graphs/__init__.py +30 -12
- job_shop_lib/graphs/{build_disjunctive_graph.py → _build_disjunctive_graph.py} +41 -3
- job_shop_lib/graphs/{build_agent_task_graph.py → _build_resource_task_graphs.py} +28 -26
- job_shop_lib/graphs/_constants.py +38 -0
- job_shop_lib/graphs/_job_shop_graph.py +320 -0
- job_shop_lib/graphs/_node.py +182 -0
- job_shop_lib/graphs/graph_updaters/__init__.py +26 -0
- job_shop_lib/graphs/graph_updaters/_disjunctive_graph_updater.py +108 -0
- job_shop_lib/graphs/graph_updaters/_graph_updater.py +57 -0
- job_shop_lib/graphs/graph_updaters/_residual_graph_updater.py +155 -0
- job_shop_lib/graphs/graph_updaters/_utils.py +25 -0
- job_shop_lib/py.typed +0 -0
- job_shop_lib/reinforcement_learning/__init__.py +68 -0
- job_shop_lib/reinforcement_learning/_multi_job_shop_graph_env.py +398 -0
- job_shop_lib/reinforcement_learning/_resource_task_graph_observation.py +329 -0
- job_shop_lib/reinforcement_learning/_reward_observers.py +87 -0
- job_shop_lib/reinforcement_learning/_single_job_shop_graph_env.py +443 -0
- job_shop_lib/reinforcement_learning/_types_and_constants.py +62 -0
- job_shop_lib/reinforcement_learning/_utils.py +199 -0
- job_shop_lib/visualization/__init__.py +0 -25
- job_shop_lib/visualization/gantt/__init__.py +48 -0
- job_shop_lib/visualization/gantt/_gantt_chart_creator.py +257 -0
- job_shop_lib/visualization/gantt/_gantt_chart_video_and_gif_creation.py +422 -0
- job_shop_lib/visualization/{gantt_chart.py → gantt/_plot_gantt_chart.py} +84 -21
- job_shop_lib/visualization/graphs/__init__.py +29 -0
- job_shop_lib/visualization/graphs/_plot_disjunctive_graph.py +418 -0
- job_shop_lib/visualization/graphs/_plot_resource_task_graph.py +389 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/METADATA +87 -55
- job_shop_lib-1.0.0.dist-info/RECORD +73 -0
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/WHEEL +1 -1
- job_shop_lib/benchmarking/load_benchmark.py +0 -142
- job_shop_lib/cp_sat/__init__.py +0 -5
- job_shop_lib/dispatching/dispatching_rule_solver.py +0 -119
- job_shop_lib/dispatching/factories.py +0 -206
- job_shop_lib/dispatching/feature_observers/composite_feature_observer.py +0 -87
- job_shop_lib/dispatching/feature_observers/earliest_start_time_observer.py +0 -156
- job_shop_lib/dispatching/feature_observers/factory.py +0 -58
- job_shop_lib/dispatching/feature_observers/feature_observer.py +0 -113
- job_shop_lib/dispatching/feature_observers/is_completed_observer.py +0 -98
- job_shop_lib/dispatching/feature_observers/is_ready_observer.py +0 -40
- job_shop_lib/dispatching/pruning_functions.py +0 -116
- job_shop_lib/generation/general_instance_generator.py +0 -169
- job_shop_lib/generation/transformations.py +0 -164
- job_shop_lib/generators/__init__.py +0 -8
- job_shop_lib/generators/basic_generator.py +0 -200
- job_shop_lib/graphs/constants.py +0 -21
- job_shop_lib/graphs/job_shop_graph.py +0 -202
- job_shop_lib/graphs/node.py +0 -166
- job_shop_lib/operation.py +0 -122
- job_shop_lib/visualization/agent_task_graph.py +0 -257
- job_shop_lib/visualization/create_gif.py +0 -209
- job_shop_lib/visualization/disjunctive_graph.py +0 -210
- job_shop_lib-0.5.1.dist-info/RECORD +0 -52
- {job_shop_lib-0.5.1.dist-info → job_shop_lib-1.0.0.dist-info}/LICENSE +0 -0
@@ -1,169 +0,0 @@
|
|
1
|
-
"""Home of the `BasicGenerator` class."""
|
2
|
-
|
3
|
-
import random
|
4
|
-
|
5
|
-
from job_shop_lib import JobShopInstance, Operation
|
6
|
-
from job_shop_lib.generation import InstanceGenerator
|
7
|
-
|
8
|
-
|
9
|
-
class GeneralInstanceGenerator(InstanceGenerator):
|
10
|
-
"""Generates instances for job shop problems.
|
11
|
-
|
12
|
-
This class is designed to be versatile, enabling the creation of various
|
13
|
-
job shop instances without the need for multiple dedicated classes.
|
14
|
-
|
15
|
-
It supports customization of the number of jobs, machines, operation
|
16
|
-
durations, and more.
|
17
|
-
|
18
|
-
The class supports both single instance generation and iteration over
|
19
|
-
multiple instances, controlled by the `iteration_limit` parameter. It
|
20
|
-
implements the iterator protocol, allowing it to be used in a `for` loop.
|
21
|
-
|
22
|
-
Note:
|
23
|
-
When used as an iterator, the generator will produce instances until it
|
24
|
-
reaches the specified `iteration_limit`. If `iteration_limit` is None,
|
25
|
-
it will continue indefinitely.
|
26
|
-
|
27
|
-
Attributes:
|
28
|
-
num_jobs_range:
|
29
|
-
The range of the number of jobs to generate. If a single
|
30
|
-
int is provided, it is used as both the minimum and maximum.
|
31
|
-
duration_range:
|
32
|
-
The range of durations for each operation.
|
33
|
-
num_machines_range:
|
34
|
-
The range of the number of machines available. If a
|
35
|
-
single int is provided, it is used as both the minimum and maximum.
|
36
|
-
machines_per_operation:
|
37
|
-
Specifies how many machines each operation
|
38
|
-
can be assigned to. If a single int is provided, it is used for
|
39
|
-
all operations.
|
40
|
-
allow_less_jobs_than_machines:
|
41
|
-
If True, allows generating instances where the number of jobs is
|
42
|
-
less than the number of machines.
|
43
|
-
allow_recirculation:
|
44
|
-
If True, a job can visit the same machine more than once.
|
45
|
-
name_suffix:
|
46
|
-
A suffix to append to each instance's name for identification.
|
47
|
-
seed:
|
48
|
-
Seed for the random number generator to ensure reproducibility.
|
49
|
-
"""
|
50
|
-
|
51
|
-
def __init__( # pylint: disable=too-many-arguments
|
52
|
-
self,
|
53
|
-
num_jobs: int | tuple[int, int] = (10, 20),
|
54
|
-
num_machines: int | tuple[int, int] = (5, 10),
|
55
|
-
duration_range: tuple[int, int] = (1, 99),
|
56
|
-
allow_less_jobs_than_machines: bool = True,
|
57
|
-
allow_recirculation: bool = False,
|
58
|
-
machines_per_operation: int | tuple[int, int] = 1,
|
59
|
-
name_suffix: str = "classic_generated_instance",
|
60
|
-
seed: int | None = None,
|
61
|
-
iteration_limit: int | None = None,
|
62
|
-
):
|
63
|
-
"""Initializes the instance generator with the given parameters.
|
64
|
-
|
65
|
-
Args:
|
66
|
-
num_jobs:
|
67
|
-
The range of the number of jobs to generate.
|
68
|
-
num_machines:
|
69
|
-
The range of the number of machines available.
|
70
|
-
duration_range:
|
71
|
-
The range of durations for each operation.
|
72
|
-
allow_less_jobs_than_machines:
|
73
|
-
Allows instances with fewer jobs than machines.
|
74
|
-
allow_recirculation:
|
75
|
-
Allows jobs to visit the same machine multiple times.
|
76
|
-
machines_per_operation:
|
77
|
-
Specifies how many machines each operation can be assigned to.
|
78
|
-
If a single int is provided, it is used for all operations.
|
79
|
-
name_suffix:
|
80
|
-
Suffix for instance names.
|
81
|
-
seed:
|
82
|
-
Seed for the random number generator.
|
83
|
-
iteration_limit:
|
84
|
-
Maximum number of instances to generate in iteration mode.
|
85
|
-
"""
|
86
|
-
super().__init__(
|
87
|
-
num_jobs=num_jobs,
|
88
|
-
num_machines=num_machines,
|
89
|
-
duration_range=duration_range,
|
90
|
-
name_suffix=name_suffix,
|
91
|
-
seed=seed,
|
92
|
-
iteration_limit=iteration_limit,
|
93
|
-
)
|
94
|
-
if isinstance(machines_per_operation, int):
|
95
|
-
machines_per_operation = (
|
96
|
-
machines_per_operation,
|
97
|
-
machines_per_operation,
|
98
|
-
)
|
99
|
-
self.machines_per_operation = machines_per_operation
|
100
|
-
|
101
|
-
self.allow_less_jobs_than_machines = allow_less_jobs_than_machines
|
102
|
-
self.allow_recirculation = allow_recirculation
|
103
|
-
self.name_suffix = name_suffix
|
104
|
-
|
105
|
-
if seed is not None:
|
106
|
-
random.seed(seed)
|
107
|
-
|
108
|
-
def generate(self) -> JobShopInstance:
|
109
|
-
"""Generates a single job shop instance"""
|
110
|
-
num_jobs = random.randint(*self.num_jobs_range)
|
111
|
-
|
112
|
-
min_num_machines, max_num_machines = self.num_machines_range
|
113
|
-
if not self.allow_less_jobs_than_machines:
|
114
|
-
min_num_machines = min(num_jobs, max_num_machines)
|
115
|
-
num_machines = random.randint(min_num_machines, max_num_machines)
|
116
|
-
|
117
|
-
jobs = []
|
118
|
-
available_machines = list(range(num_machines))
|
119
|
-
for _ in range(num_jobs):
|
120
|
-
job = []
|
121
|
-
for _ in range(num_machines):
|
122
|
-
operation = self.create_random_operation(available_machines)
|
123
|
-
job.append(operation)
|
124
|
-
jobs.append(job)
|
125
|
-
available_machines = list(range(num_machines))
|
126
|
-
|
127
|
-
return JobShopInstance(jobs=jobs, name=self._next_name())
|
128
|
-
|
129
|
-
def create_random_operation(
|
130
|
-
self, available_machines: list[int] | None = None
|
131
|
-
) -> Operation:
|
132
|
-
"""Creates a random operation with the given available machines.
|
133
|
-
|
134
|
-
Args:
|
135
|
-
available_machines:
|
136
|
-
A list of available machine_ids to choose from.
|
137
|
-
If None, all machines are available.
|
138
|
-
"""
|
139
|
-
duration = random.randint(*self.duration_range)
|
140
|
-
|
141
|
-
if self.machines_per_operation[1] > 1:
|
142
|
-
machines = self._choose_multiple_machines()
|
143
|
-
return Operation(machines=machines, duration=duration)
|
144
|
-
|
145
|
-
machine_id = self._choose_one_machine(available_machines)
|
146
|
-
return Operation(machines=machine_id, duration=duration)
|
147
|
-
|
148
|
-
def _choose_multiple_machines(self) -> list[int]:
|
149
|
-
num_machines = random.randint(*self.machines_per_operation)
|
150
|
-
available_machines = list(range(num_machines))
|
151
|
-
machines = []
|
152
|
-
for _ in range(num_machines):
|
153
|
-
machine = random.choice(available_machines)
|
154
|
-
machines.append(machine)
|
155
|
-
available_machines.remove(machine)
|
156
|
-
return machines
|
157
|
-
|
158
|
-
def _choose_one_machine(
|
159
|
-
self, available_machines: list[int] | None = None
|
160
|
-
) -> int:
|
161
|
-
if available_machines is None:
|
162
|
-
_, max_num_machines = self.num_machines_range
|
163
|
-
available_machines = list(range(max_num_machines))
|
164
|
-
|
165
|
-
machine_id = random.choice(available_machines)
|
166
|
-
if not self.allow_recirculation:
|
167
|
-
available_machines.remove(machine_id)
|
168
|
-
|
169
|
-
return machine_id
|
@@ -1,164 +0,0 @@
|
|
1
|
-
"""Classes for generating transformed JobShopInstance objects."""
|
2
|
-
|
3
|
-
import abc
|
4
|
-
import copy
|
5
|
-
import random
|
6
|
-
|
7
|
-
from job_shop_lib import JobShopInstance, Operation
|
8
|
-
|
9
|
-
|
10
|
-
class Transformation(abc.ABC):
|
11
|
-
"""Base class for transformations applied to JobShopInstance objects."""
|
12
|
-
|
13
|
-
def __init__(self, suffix: str = ""):
|
14
|
-
self.suffix = suffix
|
15
|
-
self.counter = 0
|
16
|
-
|
17
|
-
@abc.abstractmethod
|
18
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
19
|
-
"""Applies the transformation to a given JobShopInstance.
|
20
|
-
|
21
|
-
Args:
|
22
|
-
instance: The JobShopInstance to transform.
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
A new JobShopInstance with the transformation applied.
|
26
|
-
"""
|
27
|
-
|
28
|
-
def __call__(self, instance: JobShopInstance) -> JobShopInstance:
|
29
|
-
instance = self.apply(instance)
|
30
|
-
suffix = f"{self.suffix}_id={self.counter}"
|
31
|
-
instance.name += suffix
|
32
|
-
self.counter += 1
|
33
|
-
return instance
|
34
|
-
|
35
|
-
|
36
|
-
# pylint: disable=too-few-public-methods
|
37
|
-
class RemoveMachines(Transformation):
|
38
|
-
"""Removes operations associated with randomly selected machines until
|
39
|
-
there are exactly num_machines machines left."""
|
40
|
-
|
41
|
-
def __init__(self, num_machines: int, suffix: str | None = None):
|
42
|
-
if suffix is None:
|
43
|
-
suffix = f"_machines={num_machines}"
|
44
|
-
super().__init__(suffix=suffix)
|
45
|
-
self.num_machines = num_machines
|
46
|
-
|
47
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
48
|
-
if instance.num_machines <= self.num_machines:
|
49
|
-
return instance # No need to remove machines
|
50
|
-
|
51
|
-
# Select machine indices to keep
|
52
|
-
machines_to_keep = set(
|
53
|
-
random.sample(range(instance.num_machines), self.num_machines)
|
54
|
-
)
|
55
|
-
|
56
|
-
# Re-index machines
|
57
|
-
machine_reindex_map = {
|
58
|
-
old_id: new_id
|
59
|
-
for new_id, old_id in enumerate(sorted(machines_to_keep))
|
60
|
-
}
|
61
|
-
|
62
|
-
new_jobs = []
|
63
|
-
for job in instance.jobs:
|
64
|
-
# Keep operations whose machine_id is in machines_to_keep and
|
65
|
-
# re-index them
|
66
|
-
new_jobs.append(
|
67
|
-
[
|
68
|
-
Operation(machine_reindex_map[op.machine_id], op.duration)
|
69
|
-
for op in job
|
70
|
-
if op.machine_id in machines_to_keep
|
71
|
-
]
|
72
|
-
)
|
73
|
-
|
74
|
-
return JobShopInstance(new_jobs, instance.name)
|
75
|
-
|
76
|
-
|
77
|
-
# pylint: disable=too-few-public-methods
|
78
|
-
class AddDurationNoise(Transformation):
|
79
|
-
"""Adds uniform integer noise to operation durations."""
|
80
|
-
|
81
|
-
def __init__(
|
82
|
-
self,
|
83
|
-
min_duration: int = 1,
|
84
|
-
max_duration: int = 100,
|
85
|
-
noise_level: int = 10,
|
86
|
-
suffix: str | None = None,
|
87
|
-
):
|
88
|
-
if suffix is None:
|
89
|
-
suffix = f"_noise={noise_level}"
|
90
|
-
super().__init__(suffix=suffix)
|
91
|
-
self.min_duration = min_duration
|
92
|
-
self.max_duration = max_duration
|
93
|
-
self.noise_level = noise_level
|
94
|
-
|
95
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
96
|
-
new_jobs = []
|
97
|
-
for job in instance.jobs:
|
98
|
-
new_job = []
|
99
|
-
for op in job:
|
100
|
-
noise = random.randint(-self.noise_level, self.noise_level)
|
101
|
-
new_duration = max(
|
102
|
-
self.min_duration,
|
103
|
-
min(self.max_duration, op.duration + noise),
|
104
|
-
)
|
105
|
-
|
106
|
-
new_job.append(Operation(op.machine_id, new_duration))
|
107
|
-
new_jobs.append(new_job)
|
108
|
-
|
109
|
-
return JobShopInstance(new_jobs, instance.name)
|
110
|
-
|
111
|
-
|
112
|
-
class RemoveJobs(Transformation):
|
113
|
-
"""Removes jobs randomly until the number of jobs is within a specified
|
114
|
-
range."""
|
115
|
-
|
116
|
-
def __init__(
|
117
|
-
self,
|
118
|
-
min_jobs: int,
|
119
|
-
max_jobs: int,
|
120
|
-
target_jobs: int | None = None,
|
121
|
-
suffix: str | None = None,
|
122
|
-
):
|
123
|
-
"""
|
124
|
-
Args:
|
125
|
-
min_jobs: The minimum number of jobs to remain in the instance.
|
126
|
-
max_jobs: The maximum number of jobs to remain in the instance.
|
127
|
-
target_jobs: If specified, the number of jobs to remain in the
|
128
|
-
instance. Overrides min_jobs and max_jobs.
|
129
|
-
"""
|
130
|
-
if suffix is None:
|
131
|
-
suffix = f"_jobs={min_jobs}-{max_jobs}"
|
132
|
-
super().__init__(suffix=suffix)
|
133
|
-
self.min_jobs = min_jobs
|
134
|
-
self.max_jobs = max_jobs
|
135
|
-
self.target_jobs = target_jobs
|
136
|
-
|
137
|
-
def apply(self, instance: JobShopInstance) -> JobShopInstance:
|
138
|
-
if self.target_jobs is None:
|
139
|
-
target_jobs = random.randint(self.min_jobs, self.max_jobs)
|
140
|
-
else:
|
141
|
-
target_jobs = self.target_jobs
|
142
|
-
new_jobs = copy.deepcopy(instance.jobs)
|
143
|
-
|
144
|
-
while len(new_jobs) > target_jobs:
|
145
|
-
new_jobs.pop(random.randint(0, len(new_jobs) - 1))
|
146
|
-
|
147
|
-
return JobShopInstance(new_jobs, instance.name)
|
148
|
-
|
149
|
-
@staticmethod
|
150
|
-
def remove_job(
|
151
|
-
instance: JobShopInstance, job_index: int
|
152
|
-
) -> JobShopInstance:
|
153
|
-
"""Removes a specific job from the instance.
|
154
|
-
|
155
|
-
Args:
|
156
|
-
instance: The JobShopInstance from which to remove the job.
|
157
|
-
job_index: The index of the job to remove.
|
158
|
-
|
159
|
-
Returns:
|
160
|
-
A new JobShopInstance with the specified job removed.
|
161
|
-
"""
|
162
|
-
new_jobs = copy.deepcopy(instance.jobs)
|
163
|
-
new_jobs.pop(job_index)
|
164
|
-
return JobShopInstance(new_jobs, instance.name)
|
@@ -1,200 +0,0 @@
|
|
1
|
-
"""Home of the `BasicGenerator` class."""
|
2
|
-
|
3
|
-
import random
|
4
|
-
from typing import Iterator
|
5
|
-
|
6
|
-
from job_shop_lib import JobShopInstance, Operation
|
7
|
-
|
8
|
-
|
9
|
-
class BasicGenerator: # pylint: disable=too-many-instance-attributes
|
10
|
-
"""Generates instances for job shop problems.
|
11
|
-
|
12
|
-
DEPRECATED: Class moved to `job_shop_lib.generation` and renamed to
|
13
|
-
`GeneralInstanceGenerator`. This class will be removed in version 1.0.0.
|
14
|
-
|
15
|
-
This class is designed to be versatile, enabling the creation of various
|
16
|
-
job shop instances without the need for multiple dedicated classes.
|
17
|
-
|
18
|
-
It supports customization of the number of jobs, machines, operation
|
19
|
-
durations, and more.
|
20
|
-
|
21
|
-
The class supports both single instance generation and iteration over
|
22
|
-
multiple instances, controlled by the `iteration_limit` parameter. It
|
23
|
-
implements the iterator protocol, allowing it to be used in a `for` loop.
|
24
|
-
|
25
|
-
Note:
|
26
|
-
When used as an iterator, the generator will produce instances until it
|
27
|
-
reaches the specified `iteration_limit`. If `iteration_limit` is None,
|
28
|
-
it will continue indefinitely.
|
29
|
-
|
30
|
-
Attributes:
|
31
|
-
num_jobs_range:
|
32
|
-
The range of the number of jobs to generate. If a single
|
33
|
-
int is provided, it is used as both the minimum and maximum.
|
34
|
-
duration_range:
|
35
|
-
The range of durations for each operation.
|
36
|
-
num_machines_range:
|
37
|
-
The range of the number of machines available. If a
|
38
|
-
single int is provided, it is used as both the minimum and maximum.
|
39
|
-
machines_per_operation:
|
40
|
-
Specifies how many machines each operation
|
41
|
-
can be assigned to. If a single int is provided, it is used for
|
42
|
-
all operations.
|
43
|
-
allow_less_jobs_than_machines:
|
44
|
-
If True, allows generating instances where the number of jobs is
|
45
|
-
less than the number of machines.
|
46
|
-
allow_recirculation:
|
47
|
-
If True, a job can visit the same machine more than once.
|
48
|
-
name_suffix:
|
49
|
-
A suffix to append to each instance's name for identification.
|
50
|
-
seed:
|
51
|
-
Seed for the random number generator to ensure reproducibility.
|
52
|
-
"""
|
53
|
-
|
54
|
-
def __init__( # pylint: disable=too-many-arguments
|
55
|
-
self,
|
56
|
-
num_jobs: int | tuple[int, int] = (10, 20),
|
57
|
-
num_machines: int | tuple[int, int] = (5, 10),
|
58
|
-
duration_range: tuple[int, int] = (1, 99),
|
59
|
-
allow_less_jobs_than_machines: bool = True,
|
60
|
-
allow_recirculation: bool = False,
|
61
|
-
machines_per_operation: int | tuple[int, int] = 1,
|
62
|
-
name_suffix: str = "classic_generated_instance",
|
63
|
-
seed: int | None = None,
|
64
|
-
iteration_limit: int | None = None,
|
65
|
-
):
|
66
|
-
"""Initializes the instance generator with the given parameters.
|
67
|
-
|
68
|
-
Args:
|
69
|
-
num_jobs:
|
70
|
-
The range of the number of jobs to generate.
|
71
|
-
num_machines:
|
72
|
-
The range of the number of machines available.
|
73
|
-
duration_range:
|
74
|
-
The range of durations for each operation.
|
75
|
-
allow_less_jobs_than_machines:
|
76
|
-
Allows instances with fewer jobs than machines.
|
77
|
-
allow_recirculation:
|
78
|
-
Allows jobs to visit the same machine multiple times.
|
79
|
-
machines_per_operation:
|
80
|
-
Specifies how many machines each operation can be assigned to.
|
81
|
-
If a single int is provided, it is used for all operations.
|
82
|
-
name_suffix:
|
83
|
-
Suffix for instance names.
|
84
|
-
seed:
|
85
|
-
Seed for the random number generator.
|
86
|
-
iteration_limit:
|
87
|
-
Maximum number of instances to generate in iteration mode.
|
88
|
-
"""
|
89
|
-
if isinstance(num_jobs, int):
|
90
|
-
num_jobs = (num_jobs, num_jobs)
|
91
|
-
|
92
|
-
if isinstance(num_machines, int):
|
93
|
-
num_machines = (num_machines, num_machines)
|
94
|
-
|
95
|
-
if isinstance(machines_per_operation, int):
|
96
|
-
machines_per_operation = (
|
97
|
-
machines_per_operation,
|
98
|
-
machines_per_operation,
|
99
|
-
)
|
100
|
-
|
101
|
-
self.num_jobs_range = num_jobs
|
102
|
-
self.duration_range = duration_range
|
103
|
-
self.num_machines_range = num_machines
|
104
|
-
self.machines_per_operation = machines_per_operation
|
105
|
-
|
106
|
-
self.allow_less_jobs_than_machines = allow_less_jobs_than_machines
|
107
|
-
self.allow_recirculation = allow_recirculation
|
108
|
-
self.name_suffix = name_suffix
|
109
|
-
|
110
|
-
self._counter = 0
|
111
|
-
self._current_iteration = 0
|
112
|
-
self._iteration_limit = iteration_limit
|
113
|
-
|
114
|
-
if seed is not None:
|
115
|
-
random.seed(seed)
|
116
|
-
|
117
|
-
def generate(self) -> JobShopInstance:
|
118
|
-
"""Generates a single job shop instance"""
|
119
|
-
num_jobs = random.randint(*self.num_jobs_range)
|
120
|
-
|
121
|
-
min_num_machines, max_num_machines = self.num_machines_range
|
122
|
-
if not self.allow_less_jobs_than_machines:
|
123
|
-
min_num_machines = min(num_jobs, max_num_machines)
|
124
|
-
num_machines = random.randint(min_num_machines, max_num_machines)
|
125
|
-
|
126
|
-
jobs = []
|
127
|
-
available_machines = list(range(num_machines))
|
128
|
-
for _ in range(num_jobs):
|
129
|
-
job = []
|
130
|
-
for _ in range(num_machines):
|
131
|
-
operation = self.create_random_operation(available_machines)
|
132
|
-
job.append(operation)
|
133
|
-
jobs.append(job)
|
134
|
-
available_machines = list(range(num_machines))
|
135
|
-
|
136
|
-
return JobShopInstance(jobs=jobs, name=self._get_name())
|
137
|
-
|
138
|
-
def __iter__(self) -> Iterator[JobShopInstance]:
|
139
|
-
self._current_iteration = 0
|
140
|
-
return self
|
141
|
-
|
142
|
-
def __next__(self) -> JobShopInstance:
|
143
|
-
if (
|
144
|
-
self._iteration_limit is not None
|
145
|
-
and self._current_iteration >= self._iteration_limit
|
146
|
-
):
|
147
|
-
raise StopIteration
|
148
|
-
self._current_iteration += 1
|
149
|
-
return self.generate()
|
150
|
-
|
151
|
-
def __len__(self) -> int:
|
152
|
-
if self._iteration_limit is None:
|
153
|
-
raise ValueError("Iteration limit is not set.")
|
154
|
-
return self._iteration_limit
|
155
|
-
|
156
|
-
def create_random_operation(
|
157
|
-
self, available_machines: list[int] | None = None
|
158
|
-
) -> Operation:
|
159
|
-
"""Creates a random operation with the given available machines.
|
160
|
-
|
161
|
-
Args:
|
162
|
-
available_machines:
|
163
|
-
A list of available machine_ids to choose from.
|
164
|
-
If None, all machines are available.
|
165
|
-
"""
|
166
|
-
duration = random.randint(*self.duration_range)
|
167
|
-
|
168
|
-
if self.machines_per_operation[1] > 1:
|
169
|
-
machines = self._choose_multiple_machines()
|
170
|
-
return Operation(machines=machines, duration=duration)
|
171
|
-
|
172
|
-
machine_id = self._choose_one_machine(available_machines)
|
173
|
-
return Operation(machines=machine_id, duration=duration)
|
174
|
-
|
175
|
-
def _choose_multiple_machines(self) -> list[int]:
|
176
|
-
num_machines = random.randint(*self.machines_per_operation)
|
177
|
-
available_machines = list(range(num_machines))
|
178
|
-
machines = []
|
179
|
-
for _ in range(num_machines):
|
180
|
-
machine = random.choice(available_machines)
|
181
|
-
machines.append(machine)
|
182
|
-
available_machines.remove(machine)
|
183
|
-
return machines
|
184
|
-
|
185
|
-
def _choose_one_machine(
|
186
|
-
self, available_machines: list[int] | None = None
|
187
|
-
) -> int:
|
188
|
-
if available_machines is None:
|
189
|
-
_, max_num_machines = self.num_machines_range
|
190
|
-
available_machines = list(range(max_num_machines))
|
191
|
-
|
192
|
-
machine_id = random.choice(available_machines)
|
193
|
-
if not self.allow_recirculation:
|
194
|
-
available_machines.remove(machine_id)
|
195
|
-
|
196
|
-
return machine_id
|
197
|
-
|
198
|
-
def _get_name(self) -> str:
|
199
|
-
self._counter += 1
|
200
|
-
return f"{self.name_suffix}_{self._counter}"
|
job_shop_lib/graphs/constants.py
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
"""Constants for the graph module."""
|
2
|
-
|
3
|
-
import enum
|
4
|
-
|
5
|
-
|
6
|
-
class EdgeType(enum.Enum):
|
7
|
-
"""Enumeration of edge types."""
|
8
|
-
|
9
|
-
CONJUNCTIVE = 0
|
10
|
-
DISJUNCTIVE = 1
|
11
|
-
|
12
|
-
|
13
|
-
class NodeType(enum.Enum):
|
14
|
-
"""Enumeration of node types."""
|
15
|
-
|
16
|
-
OPERATION = enum.auto()
|
17
|
-
MACHINE = enum.auto()
|
18
|
-
JOB = enum.auto()
|
19
|
-
GLOBAL = enum.auto()
|
20
|
-
SOURCE = enum.auto()
|
21
|
-
SINK = enum.auto()
|