2
2
import torch
3
3
4
4
from dataclasses import dataclass , field
5
+ from enum import Enum
5
6
6
7
7
8
@dataclass
8
9
class Job :
10
+
11
+ class ReductionStrategy (Enum ):
12
+ """
13
+ Job doesn't know in advance on which machine it will be processed inside work-center. ReductionStrategy
14
+ defines the way, how the expected processing time on the work-center is calculated
15
+ """
16
+ mean = 0
17
+ min = 1
18
+ max = 2
19
+
9
20
# Id of the job
10
21
id : int
11
-
12
- # The sequence of indices, which job must visit in order to be complete
22
+ # The sequence of work-centers, which job must visit in order to be complete
13
23
step_idx : torch .LongTensor
14
- # The processing time of the job on each machine
24
+ # The processing time of the job in workcenter depending on the machine it was scheduled
15
25
processing_times : torch .LongTensor
16
-
17
- # The index of the current operation to complete
18
- current_operation_idx : int = 0
19
-
26
+ # The index of the workcenter that the job is currently visiting
27
+ current_step_idx : int = 0
28
+ # The index of the machine in the work-center where the job is being processed
29
+ current_machine_idx : int = 0
20
30
# The creation time of the job
21
31
created_at : torch .LongTensor = 0
22
32
# The time of the job completion
@@ -25,85 +35,100 @@ class Job:
25
35
due_at : torch .LongTensor = 0
26
36
# The time, when each operation arrives to the specified machine
27
37
arrived_at : torch .LongTensor = field (default_factory = torch .LongTensor )
28
- # The list of the times, when operation was selected for processing on the machine
38
+ # The list of the times, when operation was selected for processing on the workcenter
29
39
started_at : torch .LongTensor = field (default_factory = torch .LongTensor )
30
40
# Slack, i.e. the amount of time, that the job can be postponed
31
41
# It is calculated as the due_at - current_time - remaining_processing time, and it is recorded at the arrival
32
42
# of the job on the machine
33
43
slack : torch .LongTensor = field (default_factory = torch .LongTensor )
34
44
35
45
def __post_init__ (self ):
36
- self .arrived_at = torch .zeros_like (self .processing_times )
37
- self .started_at = torch .zeros_like (self .processing_times )
38
- self .stack = torch .zeros_like (self .processing_times )
46
+ self .arrived_at = torch .zeros_like (self .step_idx )
47
+ self .started_at = torch .zeros_like (self .step_idx )
48
+ self .slack = torch .zeros_like (self .step_idx )
39
49
40
50
@property
41
- def processing_time_moments (self ):
51
+ def processing_time_moments (self , reduction_strategy : ReductionStrategy = ReductionStrategy . mean ):
42
52
"""
43
53
Returns: The expectation and variance of processing times
44
54
"""
45
- return torch .mean (self .processing_times ), torch .std (self .processing_times )
55
+ processing_times = self .expected_processing_times (self .processing_times .float (), reduction_strategy )
56
+
57
+ return torch .mean (processing_times ), torch .std (processing_times )
46
58
47
59
@property
48
- def current_operation_processing_time (self ):
60
+ def current_operation_processing_time_on_machine (self ):
49
61
"""
50
- Returns: Returns the processing time of the current operation
62
+ Returns: Returns the processing time of the current operation in machine
51
63
"""
52
- return self .processing_times [self .current_operation_idx ]
64
+ return self .processing_times [self .current_step_idx ][ self . current_machine_idx ]
53
65
54
66
@property
55
- def remaining_processing_time (self ):
67
+ def current_operation_processing_time_in_workcenter (self ):
68
+ """
69
+ Returns: Returns the processing time of the current operation in workcenter
70
+ """
71
+ return self .processing_times [self .current_step_idx ]
72
+
73
+ @property
74
+ def remaining_processing_time (self , strategy : ReductionStrategy = ReductionStrategy .mean ):
56
75
"""
57
76
Returns: The total processing time of the remaining operations
58
77
"""
59
- return self .processing_times [self .current_operation_idx :].sum ()
78
+
79
+ # Since we don't know, to which machine inside work-center the job will be dispatched next, we
80
+ # approximate it with the average
81
+ expected_processing_time = self .processing_times [self .current_step_idx :]
82
+ expected_processing_time = self .expected_processing_times (expected_processing_time .float (), strategy )
83
+
84
+ return expected_processing_time .sum ()
60
85
61
86
@property
62
87
def remaining_operations_count (self ):
63
88
"""
64
89
Returns: The number of remaining operations
65
90
"""
66
- return len ( self .processing_times ) - self .current_operation_idx
91
+ return self .processing_times . shape [ 0 ] - self .current_step_idx
67
92
68
93
@property
69
- def next_machine_idx (self ):
94
+ def next_work_center_idx (self ):
70
95
"""
71
- Returns: The index of the next machine to visit or None if there is no next machine
96
+ Returns: The index of the work-center to visit or None if the job is completed
72
97
"""
73
- next_idx = self .current_operation_idx + 1
98
+ next_idx = self .current_step_idx + 1
74
99
75
100
if next_idx >= len (self .step_idx ):
76
101
return None
77
102
78
103
return self .step_idx [next_idx ]
79
104
80
105
@property
81
- def next_operation_processing_time (self ):
106
+ def next_operation_processing_time (self , strategy : ReductionStrategy = ReductionStrategy . mean ):
82
107
"""
83
108
Returns: The processing time of the next operation
84
109
"""
85
- next_idx = self .current_operation_idx + 1
110
+ next_idx = self .current_step_idx + 1
86
111
87
112
if next_idx >= len (self .step_idx ):
88
113
return 0
89
114
90
- return self .processing_times [next_idx ]
115
+ pt = self .processing_times [next_idx ]
116
+
117
+ return self .expected_processing_times (pt .float (), strategy )
91
118
92
119
@property
93
120
def slack_upon_arrival (self ):
94
121
"""
95
122
Returns: The slack upon arrival of the job on the machine
96
123
"""
97
- return self .slack [self .current_operation_idx ]
124
+ return self .slack [self .current_step_idx ]
98
125
99
126
def slack_upon_now (self , now : int ):
100
127
"""
101
-
102
128
Args:
103
129
now: Current time
104
130
105
- Returns: The slack upon arrival of the job on the machine at now
106
-
131
+ Returns: The slack upon now of the job on the machine
107
132
"""
108
133
return self .due_at - now - self .remaining_processing_time
109
134
@@ -118,26 +143,40 @@ def time_until_due(self, now: int):
118
143
119
144
def current_operation_waiting_time (self , now : int ):
120
145
"""
121
-
122
146
Args:
123
147
now: Current time
124
148
125
149
Returns: The time that the current operation has been waiting for processing on current machine
126
-
127
150
"""
128
- return now - self .arrived_at [self .current_operation_idx ]
151
+ return now - self .arrived_at [self .current_step_idx ]
129
152
130
153
def operation_completion_rate (self ):
131
154
"""
132
- Returns: The completion rate of the job based on the number of completed operations
155
+ The completion rate of the job based on the number of completed operations
133
156
"""
134
157
return self .remaining_operations_count / len (self .step_idx )
135
158
136
159
def time_completion_rate (self , now : int ):
137
160
"""
138
- Returns: The completion rate of the job based on the remaining processing time
161
+ The completion rate of the job based on the remaining processing time
139
162
"""
140
163
return self .remaining_processing_time / self .processing_times .sum ()
164
+ def with_next_step (self ):
165
+ """
166
+ Advances the job to the next work-center
167
+ """
168
+ self .current_step_idx += 1
169
+ self .current_machine_idx = - 1
170
+
171
+ return self
172
+
173
+ def with_assigned_machine (self , machine_idx : int ):
174
+ """
175
+ Advances the job to the next machine
176
+ """
177
+ self .current_machine_idx = machine_idx
178
+
179
+ return self
141
180
142
181
def with_arrival (self , now : int ):
143
182
"""
@@ -148,7 +187,6 @@ def with_arrival(self, now: int):
148
187
149
188
Returns: Reference to self
150
189
"""
151
- # TODO: Where the move to the next machine is updated?
152
190
return self .with_current_operation_arrival_time (now ).with_current_operation_slack_upon_arrival ()
153
191
154
192
def with_current_operation_arrival_time (self , now : int ):
@@ -160,7 +198,7 @@ def with_current_operation_arrival_time(self, now: int):
160
198
161
199
Returns: Reference to self
162
200
"""
163
- self .arrived_at [self .current_operation_idx ] = now
201
+ self .arrived_at [self .current_step_idx ] = now
164
202
165
203
return self
166
204
@@ -173,7 +211,7 @@ def with_current_operation_start_time(self, now: int):
173
211
174
212
Returns: Reference to self
175
213
"""
176
- self .started_at [self .current_operation_idx ] = now
214
+ self .started_at [self .current_step_idx ] = now
177
215
178
216
return self
179
217
@@ -186,7 +224,7 @@ def with_current_operation_slack_upon_arrival(self):
186
224
187
225
Returns: Reference to self
188
226
"""
189
- self .slack [self .current_operation_idx ] = self .slack_upon_now (self .arrived_at [self .current_operation_idx ])
227
+ self .slack [self .current_step_idx ] = self .slack_upon_now (self .arrived_at [self .current_step_idx ])
190
228
191
229
return self
192
230
@@ -220,3 +258,17 @@ def with_completion_time(self, time: int):
220
258
self .completed_at = time
221
259
222
260
return self
261
+
262
+ @staticmethod
263
+ def expected_processing_times (processing_times , strategy : ReductionStrategy ):
264
+ processing_times = torch .atleast_2d (processing_times )
265
+
266
+ match strategy :
267
+ case Job .ReductionStrategy .mean :
268
+ return processing_times .mean (axis = 1 )
269
+ case Job .ReductionStrategy .min :
270
+ return processing_times .min (axis = 1 )
271
+ case Job .ReductionStrategy .max :
272
+ return processing_times .max (axis = 1 )
273
+ case _:
274
+ raise ValueError (f"Unknown reduction strategy { strategy } " )
0 commit comments