most-queue 2.6__py3-none-any.whl → 2.7__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.
- most_queue/sim/base.py +177 -45
- most_queue/sim/base_core.py +89 -0
- most_queue/sim/batch.py +3 -7
- most_queue/sim/finite_source.py +2 -1
- most_queue/sim/fork_join.py +6 -12
- most_queue/sim/fork_join_delta.py +7 -0
- most_queue/sim/impatient.py +39 -42
- most_queue/sim/negative.py +59 -41
- most_queue/sim/networks/base_network_sim.py +201 -1
- most_queue/sim/networks/negative_network.py +623 -0
- most_queue/sim/networks/network.py +28 -27
- most_queue/sim/networks/priority_network.py +33 -35
- most_queue/sim/priority.py +70 -52
- most_queue/sim/utils/events.py +123 -0
- most_queue/sim/vacations.py +40 -63
- most_queue/theory/__init__.py +11 -7
- most_queue/theory/fifo/__init__.py +4 -0
- most_queue/theory/fifo/gmc_takahasi.py +332 -0
- most_queue/theory/fifo/hkhk_takahasi.py +486 -0
- most_queue/theory/fifo/mgn_takahasi.py +249 -60
- most_queue/theory/fifo/takahasi_base.py +211 -0
- most_queue/theory/negative/mgn_disaster.py +10 -75
- most_queue/theory/negative/mgn_rcs.py +10 -77
- most_queue/theory/networks/__init__.py +8 -0
- most_queue/theory/networks/negative_network.py +247 -0
- {most_queue-2.6.dist-info → most_queue-2.7.dist-info}/METADATA +30 -3
- {most_queue-2.6.dist-info → most_queue-2.7.dist-info}/RECORD +29 -22
- {most_queue-2.6.dist-info → most_queue-2.7.dist-info}/WHEEL +0 -0
- {most_queue-2.6.dist-info → most_queue-2.7.dist-info}/licenses/LICENSE +0 -0
most_queue/sim/base.py
CHANGED
|
@@ -4,13 +4,14 @@ Simulation model of QS GI/G/n/r and GI/G/n
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
|
|
7
|
-
import numpy as np
|
|
8
7
|
from colorama import Fore, Style, init
|
|
9
8
|
from tqdm import tqdm
|
|
10
9
|
|
|
11
10
|
from most_queue.constants import DEFAULT_NUM_STATES
|
|
12
11
|
from most_queue.random.utils.create import create_distribution
|
|
13
12
|
from most_queue.random.utils.load import calc_qs_load
|
|
13
|
+
from most_queue.sim.base_core import BaseSimulationCore
|
|
14
|
+
from most_queue.sim.utils.events import EventScheduler
|
|
14
15
|
from most_queue.sim.utils.qs_queue import QsQueueDeque, QsQueueList
|
|
15
16
|
from most_queue.sim.utils.servers import Server
|
|
16
17
|
from most_queue.sim.utils.stats_update import refresh_moments_stat
|
|
@@ -20,7 +21,7 @@ from most_queue.structs import QueueResults
|
|
|
20
21
|
init()
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class QsSim:
|
|
24
|
+
class QsSim(BaseSimulationCore):
|
|
24
25
|
"""
|
|
25
26
|
Base class for Queueing System Simulator
|
|
26
27
|
"""
|
|
@@ -33,22 +34,19 @@ class QsSim:
|
|
|
33
34
|
:param verbose: bool : whether to print detailed information during simulation
|
|
34
35
|
:param buffer_type: str : type of the buffer, "list" or "deque"
|
|
35
36
|
"""
|
|
37
|
+
super().__init__()
|
|
38
|
+
|
|
36
39
|
self.n = num_of_channels
|
|
37
40
|
self.buffer = buffer
|
|
38
41
|
self.verbose = verbose
|
|
39
42
|
|
|
40
|
-
self.generator = np.random.default_rng()
|
|
41
|
-
|
|
42
43
|
self.free_channels = self.n
|
|
43
44
|
self.num_of_states = DEFAULT_NUM_STATES
|
|
44
45
|
self.load = 0 # utilization factor
|
|
45
46
|
|
|
46
47
|
# to track the length of the continuous channel occupancy period:
|
|
47
48
|
self.start_busy = 0
|
|
48
|
-
self.busy = [0, 0, 0]
|
|
49
|
-
self.busy_moments = 0
|
|
50
49
|
|
|
51
|
-
self.ttek = 0 # current simulation time
|
|
52
50
|
self.total = 0
|
|
53
51
|
|
|
54
52
|
self.w = [0, 0, 0, 0] # raw moments of waiting time in the QS
|
|
@@ -85,18 +83,14 @@ class QsSim:
|
|
|
85
83
|
self.is_set_source_params = False
|
|
86
84
|
self.is_set_server_params = False
|
|
87
85
|
|
|
88
|
-
self.time_spent = 0
|
|
89
|
-
|
|
90
86
|
self.zero_wait_arrivals_num = 0
|
|
91
87
|
|
|
92
|
-
# Cache for server with minimum time
|
|
93
|
-
self._min_server_time = 1e16
|
|
94
|
-
self._min_server_idx = -1
|
|
95
|
-
self._servers_time_changed = True
|
|
96
|
-
|
|
97
88
|
# Set of free server indices for O(1) access
|
|
98
89
|
self._free_servers = set(range(self.n))
|
|
99
90
|
|
|
91
|
+
# Event scheduler for managing simulation events
|
|
92
|
+
self.event_scheduler = EventScheduler()
|
|
93
|
+
|
|
100
94
|
def set_sources(self, params, kendall_notation: str = "M"):
|
|
101
95
|
"""
|
|
102
96
|
Specifies the type and parameters of source time distribution.
|
|
@@ -139,7 +133,7 @@ class QsSim:
|
|
|
139
133
|
)
|
|
140
134
|
for _i in range(self.n)
|
|
141
135
|
]
|
|
142
|
-
self.
|
|
136
|
+
self._mark_servers_time_changed()
|
|
143
137
|
self._free_servers = set(range(self.n))
|
|
144
138
|
|
|
145
139
|
def calc_load(self) -> float:
|
|
@@ -180,7 +174,7 @@ class QsSim:
|
|
|
180
174
|
self.servers[server_idx].start_service(tsk, self.ttek, is_warm_start)
|
|
181
175
|
self._free_servers.remove(server_idx)
|
|
182
176
|
self.free_channels -= 1
|
|
183
|
-
self.
|
|
177
|
+
self._mark_servers_time_changed()
|
|
184
178
|
|
|
185
179
|
# Проверям, не наступил ли ПНЗ:
|
|
186
180
|
if self.free_channels == 0:
|
|
@@ -199,6 +193,10 @@ class QsSim:
|
|
|
199
193
|
if new_tsk is None:
|
|
200
194
|
new_tsk = Task(self.ttek)
|
|
201
195
|
new_tsk.start_waiting_time = self.ttek
|
|
196
|
+
else:
|
|
197
|
+
# If task already exists but start_waiting_time is not set, set it now
|
|
198
|
+
if new_tsk.start_waiting_time < 0:
|
|
199
|
+
new_tsk.start_waiting_time = self.ttek
|
|
202
200
|
|
|
203
201
|
if self.buffer is None: # queue length is not specified, i.e. infinite queue
|
|
204
202
|
|
|
@@ -220,11 +218,8 @@ class QsSim:
|
|
|
220
218
|
"""
|
|
221
219
|
|
|
222
220
|
self.arrived += 1
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
# Extend p array if needed
|
|
226
|
-
self.p.extend([0.0] * (self.in_sys - len(self.p) + 1))
|
|
227
|
-
self.p[self.in_sys] += self.arrival_time - self.ttek
|
|
221
|
+
# Update state probabilities
|
|
222
|
+
self._update_state_probs(self.ttek, self.arrival_time, self.in_sys)
|
|
228
223
|
|
|
229
224
|
if moment:
|
|
230
225
|
self.ttek = moment
|
|
@@ -258,14 +253,11 @@ class QsSim:
|
|
|
258
253
|
"""
|
|
259
254
|
time_to_end = self.servers[c].time_to_end_service
|
|
260
255
|
end_ts = self.servers[c].end_service()
|
|
261
|
-
self.
|
|
256
|
+
self._mark_servers_time_changed()
|
|
262
257
|
self._free_servers.add(c) # Server is now free
|
|
263
258
|
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
# Extend p array if needed
|
|
267
|
-
self.p.extend([0.0] * (self.in_sys - len(self.p) + 1))
|
|
268
|
-
self.p[self.in_sys] += time_to_end - self.ttek
|
|
259
|
+
# Update state probabilities
|
|
260
|
+
self._update_state_probs(self.ttek, time_to_end, self.in_sys)
|
|
269
261
|
|
|
270
262
|
self.ttek = time_to_end
|
|
271
263
|
self.served += 1
|
|
@@ -326,23 +318,154 @@ class QsSim:
|
|
|
326
318
|
self._servers_time_changed = False
|
|
327
319
|
return self._min_server_idx, self._min_server_time
|
|
328
320
|
|
|
329
|
-
def
|
|
321
|
+
def _update_state_probs(self, old_time: float, new_time: float, state: int):
|
|
330
322
|
"""
|
|
331
|
-
|
|
323
|
+
Update state probabilities array.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
old_time: Previous time
|
|
327
|
+
new_time: New time
|
|
328
|
+
state: Current system state
|
|
332
329
|
"""
|
|
330
|
+
# Ensure p array is large enough
|
|
331
|
+
while state >= len(self.p):
|
|
332
|
+
# Extend p array in chunks to avoid frequent reallocations
|
|
333
|
+
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
334
|
+
self.p[state] += new_time - old_time
|
|
333
335
|
|
|
334
|
-
|
|
336
|
+
# Hook methods for customization
|
|
337
|
+
def _before_arrival(self, moment: float = None, ts=None):
|
|
338
|
+
"""
|
|
339
|
+
Hook called before arrival event is processed.
|
|
340
|
+
Override this to add custom logic before arrival.
|
|
335
341
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
Args:
|
|
343
|
+
moment: Optional specific arrival moment
|
|
344
|
+
ts: Optional task object
|
|
345
|
+
"""
|
|
346
|
+
# Override in subclasses if needed
|
|
347
|
+
|
|
348
|
+
def _after_arrival(self, moment: float = None, ts=None):
|
|
349
|
+
"""
|
|
350
|
+
Hook called after arrival event is processed.
|
|
351
|
+
Override this to add custom logic after arrival.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
moment: Optional specific arrival moment
|
|
355
|
+
ts: Optional task object
|
|
356
|
+
"""
|
|
357
|
+
# Override in subclasses if needed
|
|
358
|
+
|
|
359
|
+
def _before_serving(self, channel: int, is_network: bool = False):
|
|
360
|
+
"""
|
|
361
|
+
Hook called before serving event is processed.
|
|
362
|
+
Override this to add custom logic before serving.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
channel: Channel number that completed service
|
|
366
|
+
is_network: Whether this is part of a network simulation
|
|
367
|
+
"""
|
|
368
|
+
# Override in subclasses if needed
|
|
369
|
+
|
|
370
|
+
def _after_serving(self, channel: int, task=None, is_network: bool = False):
|
|
371
|
+
"""
|
|
372
|
+
Hook called after serving event is processed.
|
|
373
|
+
Override this to add custom logic after serving.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
channel: Channel number that completed service
|
|
377
|
+
task: Task that completed service
|
|
378
|
+
is_network: Whether this is part of a network simulation
|
|
379
|
+
"""
|
|
380
|
+
# Override in subclasses if needed
|
|
381
|
+
|
|
382
|
+
def _get_custom_events(self):
|
|
383
|
+
"""
|
|
384
|
+
Get custom events for this simulator.
|
|
385
|
+
Override this method to register custom event types.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
dict: Dictionary mapping event_type -> event_time
|
|
389
|
+
"""
|
|
390
|
+
return {}
|
|
391
|
+
|
|
392
|
+
def _handle_custom_event(self, event_type: str):
|
|
393
|
+
"""
|
|
394
|
+
Handle a custom event.
|
|
395
|
+
Override this method to process custom events.
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
event_type: Type of the custom event
|
|
399
|
+
"""
|
|
400
|
+
raise NotImplementedError(f"Custom event '{event_type}' not handled")
|
|
401
|
+
|
|
402
|
+
def _get_available_events(self):
|
|
403
|
+
"""
|
|
404
|
+
Collect all available events (arrival, serving, custom).
|
|
405
|
+
Returns dictionary of event_type -> event_time.
|
|
406
|
+
"""
|
|
407
|
+
events = {}
|
|
408
|
+
|
|
409
|
+
# Standard arrival event
|
|
410
|
+
if hasattr(self, "arrival_time") and self.arrival_time < float("inf"):
|
|
411
|
+
events["arrival"] = self.arrival_time
|
|
412
|
+
|
|
413
|
+
# Standard serving event (next service completion)
|
|
414
|
+
server_idx, serv_time = self._get_min_server_time()
|
|
415
|
+
if server_idx >= 0 and serv_time < float("inf"):
|
|
416
|
+
events["serving"] = serv_time
|
|
417
|
+
|
|
418
|
+
# Custom events from subclass
|
|
419
|
+
custom_events = self._get_custom_events()
|
|
420
|
+
events.update(custom_events)
|
|
421
|
+
|
|
422
|
+
return events
|
|
423
|
+
|
|
424
|
+
def _select_next_event(self):
|
|
425
|
+
"""
|
|
426
|
+
Select the next event to process based on minimum time.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
tuple: (event_type, event_time) or (None, None) if no events
|
|
430
|
+
"""
|
|
431
|
+
events = self._get_available_events()
|
|
432
|
+
if not events:
|
|
433
|
+
return None, None
|
|
434
|
+
|
|
435
|
+
event_type = min(events.keys(), key=lambda k: events[k])
|
|
436
|
+
return event_type, events[event_type]
|
|
437
|
+
|
|
438
|
+
def _execute_event(self, event_type: str):
|
|
439
|
+
"""
|
|
440
|
+
Execute the specified event.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
event_type: Type of event to execute
|
|
444
|
+
"""
|
|
445
|
+
if event_type == "arrival":
|
|
446
|
+
self._before_arrival()
|
|
345
447
|
self.arrival()
|
|
448
|
+
self._after_arrival()
|
|
449
|
+
elif event_type == "serving":
|
|
450
|
+
server_idx, _ = self._get_min_server_time()
|
|
451
|
+
self._before_serving(server_idx)
|
|
452
|
+
task = self.serving(server_idx)
|
|
453
|
+
self._after_serving(server_idx, task)
|
|
454
|
+
else:
|
|
455
|
+
# Custom event
|
|
456
|
+
self._handle_custom_event(event_type)
|
|
457
|
+
|
|
458
|
+
def run_one_step(self) -> None:
|
|
459
|
+
"""
|
|
460
|
+
Execute one step of the simulation using event-based approach.
|
|
461
|
+
Automatically handles arrival, serving, and custom events.
|
|
462
|
+
"""
|
|
463
|
+
event_type, _event_time = self._select_next_event()
|
|
464
|
+
|
|
465
|
+
if event_type is None:
|
|
466
|
+
raise RuntimeError("No events available to process")
|
|
467
|
+
|
|
468
|
+
self._execute_event(event_type)
|
|
346
469
|
|
|
347
470
|
def run(self, total_served: int) -> QueueResults:
|
|
348
471
|
"""
|
|
@@ -386,32 +509,41 @@ class QsSim:
|
|
|
386
509
|
|
|
387
510
|
return QueueResults(v=v, w=w, p=p, duration=self.time_spent, utilization=self.calc_load())
|
|
388
511
|
|
|
389
|
-
def refresh_busy_stat(self, new_a: float) -> None:
|
|
512
|
+
def refresh_busy_stat(self, new_a: float, count: int = None) -> None:
|
|
390
513
|
"""
|
|
391
514
|
Update statistics of the busy period.
|
|
392
515
|
|
|
393
516
|
Args:
|
|
394
517
|
new_a: New busy period duration to include in statistics.
|
|
518
|
+
count: Number of busy periods (if None, uses self.busy_moments)
|
|
395
519
|
"""
|
|
396
|
-
|
|
520
|
+
if count is None:
|
|
521
|
+
count = self.busy_moments
|
|
522
|
+
super().refresh_busy_stat(new_a, count)
|
|
397
523
|
|
|
398
|
-
def refresh_v_stat(self, new_a: float) -> None:
|
|
524
|
+
def refresh_v_stat(self, new_a: float, count: int = None) -> None:
|
|
399
525
|
"""
|
|
400
526
|
Update statistics of sojourn times.
|
|
401
527
|
|
|
402
528
|
Args:
|
|
403
529
|
new_a: New sojourn time value to include in statistics.
|
|
530
|
+
count: Number of served tasks (if None, uses self.served)
|
|
404
531
|
"""
|
|
405
|
-
|
|
532
|
+
if count is None:
|
|
533
|
+
count = self.served
|
|
534
|
+
self.v = refresh_moments_stat(self.v, new_a, count)
|
|
406
535
|
|
|
407
|
-
def refresh_w_stat(self, new_a: float) -> None:
|
|
536
|
+
def refresh_w_stat(self, new_a: float, count: int = None) -> None:
|
|
408
537
|
"""
|
|
409
538
|
Update statistics of wait times.
|
|
410
539
|
|
|
411
540
|
Args:
|
|
412
541
|
new_a: New waiting time value to include in statistics.
|
|
542
|
+
count: Number of taken tasks (if None, uses self.taked)
|
|
413
543
|
"""
|
|
414
|
-
|
|
544
|
+
if count is None:
|
|
545
|
+
count = self.taked
|
|
546
|
+
self.w = refresh_moments_stat(self.w, new_a, count)
|
|
415
547
|
|
|
416
548
|
def get_p(self) -> list[float]:
|
|
417
549
|
"""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base simulation core with common functionality for all simulators
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from most_queue.sim.utils.stats_update import refresh_moments_stat
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseSimulationCore:
|
|
11
|
+
"""
|
|
12
|
+
Base class for queueing system simulators.
|
|
13
|
+
Contains common attributes and methods shared across different simulator types.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""Initialize the base simulation core."""
|
|
18
|
+
self.ttek = 0 # current simulation time
|
|
19
|
+
self.generator = np.random.default_rng()
|
|
20
|
+
self.time_spent = 0
|
|
21
|
+
|
|
22
|
+
# Statistics for busy periods
|
|
23
|
+
self.busy = [0, 0, 0]
|
|
24
|
+
self.busy_moments = 0
|
|
25
|
+
|
|
26
|
+
# Cache for server with minimum time
|
|
27
|
+
self._min_server_time = 1e16
|
|
28
|
+
self._min_server_idx = -1
|
|
29
|
+
self._servers_time_changed = True
|
|
30
|
+
|
|
31
|
+
def refresh_busy_stat(self, new_a: float, count: int = None) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Update statistics of the busy period.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
new_a: New busy period duration to include in statistics.
|
|
37
|
+
count: Number of busy periods (if None, uses self.busy_moments)
|
|
38
|
+
"""
|
|
39
|
+
if count is None:
|
|
40
|
+
count = self.busy_moments
|
|
41
|
+
self.busy = refresh_moments_stat(self.busy, new_a, count)
|
|
42
|
+
|
|
43
|
+
def refresh_v_stat(self, new_a: float, count: int = None) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Update statistics of sojourn times.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
new_a: New sojourn time value to include in statistics.
|
|
49
|
+
count: Number of served tasks (if None, should be overridden by subclass)
|
|
50
|
+
"""
|
|
51
|
+
# This is a placeholder - subclasses should override with proper count
|
|
52
|
+
# Initialize v if not exists
|
|
53
|
+
if not hasattr(self, "v"):
|
|
54
|
+
self.v = [0, 0, 0, 0]
|
|
55
|
+
if count is not None:
|
|
56
|
+
self.v = refresh_moments_stat(self.v, new_a, count)
|
|
57
|
+
|
|
58
|
+
def refresh_w_stat(self, new_a: float, count: int = None) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Update statistics of wait times.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
new_a: New waiting time value to include in statistics.
|
|
64
|
+
count: Number of taken tasks (if None, should be overridden by subclass)
|
|
65
|
+
"""
|
|
66
|
+
# This is a placeholder - subclasses should override with proper count
|
|
67
|
+
# Initialize w if not exists
|
|
68
|
+
if not hasattr(self, "w"):
|
|
69
|
+
self.w = [0, 0, 0, 0]
|
|
70
|
+
if count is not None:
|
|
71
|
+
self.w = refresh_moments_stat(self.w, new_a, count)
|
|
72
|
+
|
|
73
|
+
def _get_min_server_time(self):
|
|
74
|
+
"""
|
|
75
|
+
Get server with minimum time to end service.
|
|
76
|
+
Uses caching to avoid O(n) search on every call.
|
|
77
|
+
|
|
78
|
+
This is a base implementation that should be overridden
|
|
79
|
+
if the simulator uses a different server structure.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
tuple: (server_index, min_time) or (-1, float('inf')) if no servers
|
|
83
|
+
"""
|
|
84
|
+
# Base implementation - subclasses should override
|
|
85
|
+
return -1, float("inf")
|
|
86
|
+
|
|
87
|
+
def _mark_servers_time_changed(self):
|
|
88
|
+
"""Mark that server times have changed and cache needs refresh."""
|
|
89
|
+
self._servers_time_changed = True
|
most_queue/sim/batch.py
CHANGED
|
@@ -61,7 +61,7 @@ class QueueingSystemBatchSim(QsSim):
|
|
|
61
61
|
self.servers[server_idx].start_service(Task(ttek), ttek, False)
|
|
62
62
|
self._free_servers.remove(server_idx)
|
|
63
63
|
self.free_channels -= 1
|
|
64
|
-
self.
|
|
64
|
+
self._mark_servers_time_changed()
|
|
65
65
|
|
|
66
66
|
# Check if busy period has started:
|
|
67
67
|
if self.free_channels == 0:
|
|
@@ -85,12 +85,8 @@ class QueueingSystemBatchSim(QsSim):
|
|
|
85
85
|
|
|
86
86
|
for _tsk in range(batch_size):
|
|
87
87
|
self.arrived += 1
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
while self.in_sys >= len(self.p):
|
|
91
|
-
# Extend p array in chunks to avoid frequent reallocations
|
|
92
|
-
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
93
|
-
self.p[self.in_sys] += self.arrival_time - self.ttek
|
|
88
|
+
# Update state probabilities
|
|
89
|
+
self._update_state_probs(self.ttek, self.arrival_time, self.in_sys)
|
|
94
90
|
self.in_sys += 1
|
|
95
91
|
|
|
96
92
|
if self.free_channels == 0:
|
most_queue/sim/finite_source.py
CHANGED
|
@@ -73,7 +73,8 @@ class QueueingFiniteSourceSim(QsSim):
|
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
75
|
self.arrived += 1
|
|
76
|
-
|
|
76
|
+
# Update state probabilities
|
|
77
|
+
self._update_state_probs(self.ttek, self.arrival_times[self.arrived_num], self.in_sys)
|
|
77
78
|
self.sources_left -= 1
|
|
78
79
|
self.in_sys += 1
|
|
79
80
|
self.ttek = self.arrival_times[self.arrived_num]
|
most_queue/sim/fork_join.py
CHANGED
|
@@ -85,11 +85,8 @@ class ForkJoinSim(QsSim):
|
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
87
|
self.arrived += 1
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
# Extend p array in chunks to avoid frequent reallocations
|
|
91
|
-
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
92
|
-
self.p[self.in_sys] += self.arrival_time - self.ttek
|
|
88
|
+
# Update state probabilities
|
|
89
|
+
self._update_state_probs(self.ttek, self.arrival_time, self.in_sys)
|
|
93
90
|
self.ttek = self.arrival_time
|
|
94
91
|
self.arrival_time = self.ttek + self.source.generate()
|
|
95
92
|
|
|
@@ -122,7 +119,7 @@ class ForkJoinSim(QsSim):
|
|
|
122
119
|
self.servers[i].start_service(t.subtasks[i], self.ttek)
|
|
123
120
|
self._free_servers.discard(i)
|
|
124
121
|
self.free_channels -= 1
|
|
125
|
-
self.
|
|
122
|
+
self._mark_servers_time_changed()
|
|
126
123
|
else:
|
|
127
124
|
self.queues[i].append(t.subtasks[i])
|
|
128
125
|
|
|
@@ -189,14 +186,11 @@ class ForkJoinSim(QsSim):
|
|
|
189
186
|
:param c: int : number of channel where service is completed.
|
|
190
187
|
"""
|
|
191
188
|
time_to_end = self.servers[c].time_to_end_service
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
# Extend p array in chunks to avoid frequent reallocations
|
|
195
|
-
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
196
|
-
self.p[self.in_sys] += time_to_end - self.ttek
|
|
189
|
+
# Update state probabilities
|
|
190
|
+
self._update_state_probs(self.ttek, time_to_end, self.in_sys)
|
|
197
191
|
end_ts = self.servers[c].end_service()
|
|
198
192
|
self._free_servers.add(c) # Server is now free
|
|
199
|
-
self.
|
|
193
|
+
self._mark_servers_time_changed()
|
|
200
194
|
self.ttek = time_to_end
|
|
201
195
|
self.served_subtask_in_task[end_ts.task_id] += 1
|
|
202
196
|
self.total += 1
|
|
@@ -17,6 +17,13 @@ class ForkJoinSimDelta(ForkJoinSim):
|
|
|
17
17
|
Simulation of ForkJoin queue with delta.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
def _handle_custom_event(self, event_type):
|
|
21
|
+
"""
|
|
22
|
+
Handle custom events (none needed for this simulator).
|
|
23
|
+
"""
|
|
24
|
+
# This simulator doesn't use custom events
|
|
25
|
+
raise NotImplementedError(f"Custom event '{event_type}' not supported by ForkJoinSimDelta")
|
|
26
|
+
|
|
20
27
|
def __init__(
|
|
21
28
|
self,
|
|
22
29
|
num_of_channels,
|
most_queue/sim/impatient.py
CHANGED
|
@@ -52,17 +52,46 @@ class ImpatientQueueSim(QsSim):
|
|
|
52
52
|
|
|
53
53
|
self.impatience = create_distribution(params, kendall_notation, self.generator)
|
|
54
54
|
|
|
55
|
+
def _get_custom_events(self):
|
|
56
|
+
"""
|
|
57
|
+
Get custom events (impatient task drops).
|
|
58
|
+
"""
|
|
59
|
+
events = {}
|
|
60
|
+
if self.queue.size() > 0:
|
|
61
|
+
min_leave_time = float("inf")
|
|
62
|
+
for tsk in self.queue.queue:
|
|
63
|
+
if tsk.moment_to_leave < min_leave_time:
|
|
64
|
+
min_leave_time = tsk.moment_to_leave
|
|
65
|
+
if min_leave_time < float("inf"):
|
|
66
|
+
events["task_drop"] = min_leave_time
|
|
67
|
+
return events
|
|
68
|
+
|
|
69
|
+
def _handle_custom_event(self, event_type: str):
|
|
70
|
+
"""
|
|
71
|
+
Handle custom events (impatient task drops).
|
|
72
|
+
"""
|
|
73
|
+
if event_type == "task_drop":
|
|
74
|
+
# Find the task with minimum leave time
|
|
75
|
+
num_of_task_earlier = -1
|
|
76
|
+
moment_to_leave_earlier = float("inf")
|
|
77
|
+
for i, tsk in enumerate(self.queue.queue):
|
|
78
|
+
if tsk.moment_to_leave < moment_to_leave_earlier:
|
|
79
|
+
moment_to_leave_earlier = tsk.moment_to_leave
|
|
80
|
+
num_of_task_earlier = i
|
|
81
|
+
|
|
82
|
+
if num_of_task_earlier >= 0:
|
|
83
|
+
self.drop_task(num_of_task_earlier, moment_to_leave_earlier)
|
|
84
|
+
else:
|
|
85
|
+
super()._handle_custom_event(event_type)
|
|
86
|
+
|
|
55
87
|
def arrival(self, moment=None, ts=None):
|
|
56
88
|
"""
|
|
57
89
|
Actions on arrival of a task.
|
|
58
90
|
"""
|
|
59
91
|
|
|
60
92
|
self.arrived += 1
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
# Extend p array in chunks to avoid frequent reallocations
|
|
64
|
-
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
65
|
-
self.p[self.in_sys] += self.arrival_time - self.ttek
|
|
93
|
+
# Update state probabilities
|
|
94
|
+
self._update_state_probs(self.ttek, self.arrival_time, self.in_sys)
|
|
66
95
|
|
|
67
96
|
self.in_sys += 1
|
|
68
97
|
self.ttek = self.arrival_time
|
|
@@ -92,7 +121,7 @@ class ImpatientQueueSim(QsSim):
|
|
|
92
121
|
self.servers[server_idx].start_service(ImpatientTask(self.ttek, moment_to_leave), self.ttek, False)
|
|
93
122
|
self._free_servers.remove(server_idx)
|
|
94
123
|
self.free_channels -= 1
|
|
95
|
-
self.
|
|
124
|
+
self._mark_servers_time_changed()
|
|
96
125
|
|
|
97
126
|
# Проверям, не наступил ли ПНЗ:
|
|
98
127
|
if self.free_channels == 0:
|
|
@@ -103,11 +132,8 @@ class ImpatientQueueSim(QsSim):
|
|
|
103
132
|
"""
|
|
104
133
|
Drop a task from the queue and update statistics.
|
|
105
134
|
"""
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
# Extend p array in chunks to avoid frequent reallocations
|
|
109
|
-
self.p.extend([0.0] * min(1000, max(1, len(self.p) // 10)))
|
|
110
|
-
self.p[self.in_sys] += moment_to_leave_earlier - self.ttek
|
|
135
|
+
# Update state probabilities
|
|
136
|
+
self._update_state_probs(self.ttek, moment_to_leave_earlier, self.in_sys)
|
|
111
137
|
self.ttek = moment_to_leave_earlier
|
|
112
138
|
|
|
113
139
|
# Remove task from queue - need to handle deque properly
|
|
@@ -132,37 +158,8 @@ class ImpatientQueueSim(QsSim):
|
|
|
132
158
|
end_ts.wait_time = self.ttek - end_ts.arr_time
|
|
133
159
|
self.refresh_w_stat(end_ts.wait_time)
|
|
134
160
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Run one step of the simulation.
|
|
138
|
-
"""
|
|
139
|
-
# Use optimized method from base class
|
|
140
|
-
num_of_server_earlier, serv_earl = self._get_min_server_time()
|
|
141
|
-
|
|
142
|
-
num_of_task_earlier = -1
|
|
143
|
-
moment_to_leave_earlier = 1e10
|
|
144
|
-
if self.queue.size() > 0:
|
|
145
|
-
for i, tsk in enumerate(self.queue.queue):
|
|
146
|
-
if tsk.moment_to_leave < moment_to_leave_earlier:
|
|
147
|
-
moment_to_leave_earlier = tsk.moment_to_leave
|
|
148
|
-
num_of_task_earlier = i
|
|
149
|
-
|
|
150
|
-
# Key moment:
|
|
151
|
-
|
|
152
|
-
if self.arrival_time < serv_earl:
|
|
153
|
-
if self.arrival_time < moment_to_leave_earlier:
|
|
154
|
-
self.arrival()
|
|
155
|
-
elif num_of_task_earlier >= 0:
|
|
156
|
-
self.drop_task(num_of_task_earlier, moment_to_leave_earlier)
|
|
157
|
-
else:
|
|
158
|
-
self.arrival()
|
|
159
|
-
else:
|
|
160
|
-
if serv_earl < moment_to_leave_earlier and num_of_task_earlier >= 0:
|
|
161
|
-
self.serving(num_of_server_earlier)
|
|
162
|
-
elif num_of_task_earlier >= 0:
|
|
163
|
-
self.drop_task(num_of_task_earlier, moment_to_leave_earlier)
|
|
164
|
-
else:
|
|
165
|
-
self.serving(num_of_server_earlier)
|
|
161
|
+
# run_one_step is now inherited from base class and uses event-based approach
|
|
162
|
+
# Custom events (task drops) are handled via _get_custom_events() and _handle_custom_event()
|
|
166
163
|
|
|
167
164
|
def __str__(self, is_short=False):
|
|
168
165
|
"""
|