vlcSim 0.6.0__tar.gz → 0.6.2__tar.gz
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.
- {vlcsim-0.6.0 → vlcsim-0.6.2}/PKG-INFO +17 -4
- {vlcsim-0.6.0 → vlcsim-0.6.2}/README.md +16 -3
- {vlcsim-0.6.0 → vlcsim-0.6.2}/pyproject.toml +1 -1
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/__init__.py +2 -1
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/controller/controller.py +132 -101
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/simulator.py +103 -11
- {vlcsim-0.6.0 → vlcsim-0.6.2}/LICENSE.md +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/controller/__init__.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/controller/connection.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/__init__.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/access_point.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/receiver.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/rf.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/scenario.py +0 -0
- {vlcsim-0.6.0 → vlcsim-0.6.2}/vlcsim/scene/vled.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vlcSim
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Python Package of Event-Oriented Simulation for visible light communication
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|

|
|
28
28
|

|
|
29
29
|

|
|
30
|
-

|
|
31
31
|

|
|
32
32
|

|
|
33
33
|
[](https://pypi.org/project/vlcSim/)
|
|
@@ -42,7 +42,7 @@ VLCSim is an **Event-Oriented simulator** package for **Visible Light Communicat
|
|
|
42
42
|
- **TDM Frame Management**: Time-Division Multiplexing with configurable slices
|
|
43
43
|
- **Configurable Parameters**: Customize VLC/room parameters for different scenarios
|
|
44
44
|
- **Event-Driven Simulation**: Efficient discrete event simulation engine
|
|
45
|
-
- **Performance Metrics**: Built-in SNR and
|
|
45
|
+
- **Performance Metrics**: Built-in SNR, capacity, blocking, waiting, and allocation metrics
|
|
46
46
|
|
|
47
47
|
## Installation
|
|
48
48
|
|
|
@@ -116,11 +116,16 @@ sim.run()
|
|
|
116
116
|
|
|
117
117
|
# Get results
|
|
118
118
|
print(f"Blocking Probability: {sim.get_Blocking_Probability()}")
|
|
119
|
+
print(f"Waiting Probability: {sim.get_Waiting_Probability()}")
|
|
120
|
+
print(f"Attempted Connections: {sim.get_Attempted_Connections()}")
|
|
121
|
+
print(f"Allocated Connections: {sim.get_Allocated_Connections()}")
|
|
122
|
+
print(f"Waiting Connections: {sim.get_Waiting_Connections()}")
|
|
123
|
+
print(f"Blocked Connections: {sim.get_Blocked_Connections()}")
|
|
119
124
|
```
|
|
120
125
|
|
|
121
126
|
**The built-in default algorithm** (`Controller.default_alloc`):
|
|
122
127
|
- ✅ Selects VLeds with best SNR (maximum 5 connections per VLed)
|
|
123
|
-
- ✅ Falls back to RF if all VLeds are busy
|
|
128
|
+
- ✅ Falls back to RF if all VLeds are busy (maximum 12 connections per RF)
|
|
124
129
|
- ✅ Automatically allocates TDM frame/slice positions
|
|
125
130
|
- ✅ Works well for most VLC research scenarios
|
|
126
131
|
|
|
@@ -306,6 +311,14 @@ sim.print_initial_info() # Display scenario configuration
|
|
|
306
311
|
- `upper_random_wait, lower_random_wait`: Retry delay bounds (seconds)
|
|
307
312
|
- `upper_capacity_required, lower_capacity_required`: Capacity bounds (bps)
|
|
308
313
|
|
|
314
|
+
**Key Metrics:**
|
|
315
|
+
- `get_Blocking_Probability()`: Ratio of definitively blocked (`NOT_ALLOCATED`) connections to attempted connections
|
|
316
|
+
- `get_Waiting_Probability()`: Ratio of currently waiting (`WAIT`) connections to attempted connections
|
|
317
|
+
- `get_Attempted_Connections()`: Connection arrivals processed by the simulator
|
|
318
|
+
- `get_Allocated_Connections()`: Connections assigned to an AP
|
|
319
|
+
- `get_Waiting_Connections()`: Connections currently waiting for retry
|
|
320
|
+
- `get_Blocked_Connections()`: Connections rejected with `NOT_ALLOCATED`
|
|
321
|
+
|
|
309
322
|
### Scenario Class
|
|
310
323
|
|
|
311
324
|
Room configuration with access points.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

|
|
6
|
-

|
|
7
7
|

|
|
8
8
|

|
|
9
9
|
[](https://pypi.org/project/vlcSim/)
|
|
@@ -18,7 +18,7 @@ VLCSim is an **Event-Oriented simulator** package for **Visible Light Communicat
|
|
|
18
18
|
- **TDM Frame Management**: Time-Division Multiplexing with configurable slices
|
|
19
19
|
- **Configurable Parameters**: Customize VLC/room parameters for different scenarios
|
|
20
20
|
- **Event-Driven Simulation**: Efficient discrete event simulation engine
|
|
21
|
-
- **Performance Metrics**: Built-in SNR and
|
|
21
|
+
- **Performance Metrics**: Built-in SNR, capacity, blocking, waiting, and allocation metrics
|
|
22
22
|
|
|
23
23
|
## Installation
|
|
24
24
|
|
|
@@ -92,11 +92,16 @@ sim.run()
|
|
|
92
92
|
|
|
93
93
|
# Get results
|
|
94
94
|
print(f"Blocking Probability: {sim.get_Blocking_Probability()}")
|
|
95
|
+
print(f"Waiting Probability: {sim.get_Waiting_Probability()}")
|
|
96
|
+
print(f"Attempted Connections: {sim.get_Attempted_Connections()}")
|
|
97
|
+
print(f"Allocated Connections: {sim.get_Allocated_Connections()}")
|
|
98
|
+
print(f"Waiting Connections: {sim.get_Waiting_Connections()}")
|
|
99
|
+
print(f"Blocked Connections: {sim.get_Blocked_Connections()}")
|
|
95
100
|
```
|
|
96
101
|
|
|
97
102
|
**The built-in default algorithm** (`Controller.default_alloc`):
|
|
98
103
|
- ✅ Selects VLeds with best SNR (maximum 5 connections per VLed)
|
|
99
|
-
- ✅ Falls back to RF if all VLeds are busy
|
|
104
|
+
- ✅ Falls back to RF if all VLeds are busy (maximum 12 connections per RF)
|
|
100
105
|
- ✅ Automatically allocates TDM frame/slice positions
|
|
101
106
|
- ✅ Works well for most VLC research scenarios
|
|
102
107
|
|
|
@@ -282,6 +287,14 @@ sim.print_initial_info() # Display scenario configuration
|
|
|
282
287
|
- `upper_random_wait, lower_random_wait`: Retry delay bounds (seconds)
|
|
283
288
|
- `upper_capacity_required, lower_capacity_required`: Capacity bounds (bps)
|
|
284
289
|
|
|
290
|
+
**Key Metrics:**
|
|
291
|
+
- `get_Blocking_Probability()`: Ratio of definitively blocked (`NOT_ALLOCATED`) connections to attempted connections
|
|
292
|
+
- `get_Waiting_Probability()`: Ratio of currently waiting (`WAIT`) connections to attempted connections
|
|
293
|
+
- `get_Attempted_Connections()`: Connection arrivals processed by the simulator
|
|
294
|
+
- `get_Allocated_Connections()`: Connections assigned to an AP
|
|
295
|
+
- `get_Waiting_Connections()`: Connections currently waiting for retry
|
|
296
|
+
- `get_Blocked_Connections()`: Connections rejected with `NOT_ALLOCATED`
|
|
297
|
+
|
|
285
298
|
### Scenario Class
|
|
286
299
|
|
|
287
300
|
Room configuration with access points.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "vlcSim"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2" # Placeholder, will be replaced by dynamic versioning
|
|
4
4
|
description = "Python Package of Event-Oriented Simulation for visible light communication"
|
|
5
5
|
authors = ["Danilo Bórquez-Paredes <danilo.borquez.p@uai.cl>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -24,12 +24,13 @@ Example:
|
|
|
24
24
|
sim = Simulator(scenario)
|
|
25
25
|
sim.run()
|
|
26
26
|
print(f"Blocking Probability: {sim.get_Blocking_Probability()}")
|
|
27
|
+
print(f"Waiting Probability: {sim.get_Waiting_Probability()}")
|
|
27
28
|
|
|
28
29
|
See Also:
|
|
29
30
|
For detailed documentation, visit the project repository or build the Sphinx docs.
|
|
30
31
|
"""
|
|
31
32
|
|
|
32
|
-
__version__ = "0.6.
|
|
33
|
+
__version__ = "0.6.2" # placeholder for poetry-dynamic-versioning
|
|
33
34
|
|
|
34
35
|
from .scene import *
|
|
35
36
|
from .controller import *
|
|
@@ -8,7 +8,6 @@ algorithms and TDM frame/slice management.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
from typing import Optional, Union, Callable, Any
|
|
10
10
|
from enum import Enum
|
|
11
|
-
import sys
|
|
12
11
|
|
|
13
12
|
# Import all necessary classes from scene and connection modules
|
|
14
13
|
from ..scene import AccessPoint, VLed, RF, Receiver, Scenario
|
|
@@ -68,6 +67,12 @@ class Controller:
|
|
|
68
67
|
- Allocator function must be set before calling `assignConnection()`
|
|
69
68
|
"""
|
|
70
69
|
|
|
70
|
+
MAX_ACTIVE_CONNECTIONS_PER_VLED = 5
|
|
71
|
+
"""Maximum number of active connections allowed per VLed."""
|
|
72
|
+
|
|
73
|
+
MAX_ACTIVE_CONNECTIONS_PER_RF = 12
|
|
74
|
+
"""Maximum number of active connections allowed per RF access point."""
|
|
75
|
+
|
|
71
76
|
status = Enum("status", "ALLOCATED NOT_ALLOCATED WAIT")
|
|
72
77
|
"""Allocation algorithm return status.
|
|
73
78
|
|
|
@@ -603,6 +608,100 @@ class Controller:
|
|
|
603
608
|
"""
|
|
604
609
|
return self.__activeConnections[self.APPosition(ap)]
|
|
605
610
|
|
|
611
|
+
@staticmethod
|
|
612
|
+
def _assign_frame_slices(
|
|
613
|
+
connection: Connection,
|
|
614
|
+
controller: "Controller",
|
|
615
|
+
numberOfSlices: int,
|
|
616
|
+
) -> None:
|
|
617
|
+
actualSlice = connection.nextSliceInAPWhenArriving(connection.AP)
|
|
618
|
+
aux = 0
|
|
619
|
+
auxFrame = 0
|
|
620
|
+
|
|
621
|
+
# Actual frame
|
|
622
|
+
for slice in range(actualSlice, connection.AP.slicesInFrame):
|
|
623
|
+
if (
|
|
624
|
+
len(controller.framesState(connection.AP)) == 0
|
|
625
|
+
or controller.framesState(connection.AP)[0][slice] == False
|
|
626
|
+
):
|
|
627
|
+
connection.assignFrameSlice(0, slice)
|
|
628
|
+
aux += 1
|
|
629
|
+
break
|
|
630
|
+
|
|
631
|
+
# Next frames
|
|
632
|
+
for frameIndex in range(1, len(controller.framesState(connection.AP))):
|
|
633
|
+
for slice in range(connection.AP.slicesInFrame):
|
|
634
|
+
if controller.framesState(connection.AP)[frameIndex][slice] == False:
|
|
635
|
+
connection.assignFrameSlice(frameIndex, slice)
|
|
636
|
+
aux += 1
|
|
637
|
+
auxFrame = frameIndex
|
|
638
|
+
break
|
|
639
|
+
|
|
640
|
+
if aux == numberOfSlices:
|
|
641
|
+
break
|
|
642
|
+
|
|
643
|
+
frameIndex = auxFrame + 1
|
|
644
|
+
while aux < numberOfSlices:
|
|
645
|
+
connection.assignFrameSlice(frameIndex, 0)
|
|
646
|
+
frameIndex += 1
|
|
647
|
+
aux += 1
|
|
648
|
+
|
|
649
|
+
@staticmethod
|
|
650
|
+
def _select_vled_candidate(
|
|
651
|
+
receiver: "Receiver",
|
|
652
|
+
connection: Connection,
|
|
653
|
+
scenario: Scenario,
|
|
654
|
+
controller: "Controller",
|
|
655
|
+
vled_snr: list[float],
|
|
656
|
+
) -> Optional[tuple[VLed, float, float, int]]:
|
|
657
|
+
best_candidate: Optional[tuple[VLed, float, float, int]] = None
|
|
658
|
+
|
|
659
|
+
for vled_pos, vled in enumerate(scenario.vleds):
|
|
660
|
+
if (
|
|
661
|
+
controller.numberOfActiveConnections(vled)
|
|
662
|
+
>= Controller.MAX_ACTIVE_CONNECTIONS_PER_VLED
|
|
663
|
+
):
|
|
664
|
+
continue
|
|
665
|
+
|
|
666
|
+
capacity = scenario.capacityVled(receiver, vled)
|
|
667
|
+
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
668
|
+
connection.capacityRequired,
|
|
669
|
+
capacity,
|
|
670
|
+
)
|
|
671
|
+
candidate = (vled, vled_snr[vled_pos], capacity, numberOfSlices)
|
|
672
|
+
if best_candidate is None or candidate[1] > best_candidate[1]:
|
|
673
|
+
best_candidate = candidate
|
|
674
|
+
|
|
675
|
+
return best_candidate
|
|
676
|
+
|
|
677
|
+
@staticmethod
|
|
678
|
+
def _select_rf_candidate(
|
|
679
|
+
receiver: "Receiver",
|
|
680
|
+
connection: Connection,
|
|
681
|
+
scenario: Scenario,
|
|
682
|
+
controller: "Controller",
|
|
683
|
+
rf_snr: list[float],
|
|
684
|
+
) -> Optional[tuple[RF, float, float, int]]:
|
|
685
|
+
best_candidate: Optional[tuple[RF, float, float, int]] = None
|
|
686
|
+
|
|
687
|
+
for rf_pos, rf in enumerate(scenario.rfs):
|
|
688
|
+
if (
|
|
689
|
+
controller.numberOfActiveConnections(rf)
|
|
690
|
+
>= Controller.MAX_ACTIVE_CONNECTIONS_PER_RF
|
|
691
|
+
):
|
|
692
|
+
continue
|
|
693
|
+
|
|
694
|
+
capacity = scenario.capacityRf(receiver, rf)
|
|
695
|
+
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
696
|
+
connection.capacityRequired,
|
|
697
|
+
capacity,
|
|
698
|
+
)
|
|
699
|
+
candidate = (rf, rf_snr[rf_pos], capacity, numberOfSlices)
|
|
700
|
+
if best_candidate is None or candidate[1] > best_candidate[1]:
|
|
701
|
+
best_candidate = candidate
|
|
702
|
+
|
|
703
|
+
return best_candidate
|
|
704
|
+
|
|
606
705
|
@staticmethod
|
|
607
706
|
def default_alloc(
|
|
608
707
|
receiver: "Receiver",
|
|
@@ -613,9 +712,9 @@ class Controller:
|
|
|
613
712
|
"""Default allocation algorithm for connections.
|
|
614
713
|
|
|
615
714
|
Implements a hybrid VLC/RF allocation strategy:
|
|
616
|
-
1. Try to allocate to
|
|
617
|
-
2. If
|
|
618
|
-
3.
|
|
715
|
+
1. Try to allocate to the best available VLed
|
|
716
|
+
2. If no VLed is available, fall back to the best available RF
|
|
717
|
+
3. Wait only when both VLed and RF access points are unavailable
|
|
619
718
|
4. Assign available frame/slice positions
|
|
620
719
|
|
|
621
720
|
Args:
|
|
@@ -625,14 +724,16 @@ class Controller:
|
|
|
625
724
|
controller: Controller managing allocation
|
|
626
725
|
|
|
627
726
|
Returns:
|
|
628
|
-
tuple: (Controller.status.ALLOCATED, connection)
|
|
727
|
+
tuple: (Controller.status.ALLOCATED, connection) when an AP is assigned,
|
|
728
|
+
otherwise (Controller.status.WAIT, connection)
|
|
629
729
|
|
|
630
730
|
Raises:
|
|
631
|
-
ValueError: If connection
|
|
731
|
+
ValueError: If the connection lacks a capacity requirement
|
|
632
732
|
|
|
633
733
|
Note:
|
|
634
|
-
- Prefers
|
|
734
|
+
- Prefers available VLed APs over RF fallback
|
|
635
735
|
- Limits VLed connections to 5 per AP
|
|
736
|
+
- Limits RF connections to 12 per AP
|
|
636
737
|
- Automatically finds free frame/slice positions
|
|
637
738
|
|
|
638
739
|
Example:
|
|
@@ -641,6 +742,9 @@ class Controller:
|
|
|
641
742
|
controller.allocator = Controller.default_alloc
|
|
642
743
|
# Or create custom allocator with similar signature
|
|
643
744
|
"""
|
|
745
|
+
if connection.capacityRequired is None:
|
|
746
|
+
raise ValueError("Connection must have capacityRequired set.")
|
|
747
|
+
|
|
644
748
|
vleds = scenario.vleds
|
|
645
749
|
rfs = scenario.rfs
|
|
646
750
|
vled_snr: list[float] = []
|
|
@@ -650,101 +754,28 @@ class Controller:
|
|
|
650
754
|
for rf in rfs:
|
|
651
755
|
rf_snr.append(scenario.snrRf(receiver, rf))
|
|
652
756
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
)
|
|
668
|
-
connection.snr = vled_snr[vled_pos]
|
|
669
|
-
if numberOfSlices < 5:
|
|
670
|
-
number_better_rf += 1
|
|
671
|
-
break
|
|
672
|
-
else:
|
|
673
|
-
number_better_rf += 1
|
|
674
|
-
break
|
|
675
|
-
else:
|
|
676
|
-
number_better_rf += 1
|
|
677
|
-
break
|
|
678
|
-
if number_better_rf == 0:
|
|
679
|
-
break
|
|
680
|
-
if number_better_rf != 0:
|
|
681
|
-
best_rf_SNR = sys.float_info.min
|
|
682
|
-
best_rf = None
|
|
683
|
-
for rf_pos in range(len(rfs)):
|
|
684
|
-
if rf_snr[rf_pos] > best_rf_SNR:
|
|
685
|
-
best_rf_SNR = rf_snr[rf_pos]
|
|
686
|
-
best_rf = rf_pos
|
|
687
|
-
if best_rf is not None:
|
|
688
|
-
connection.AP = rfs[best_rf]
|
|
689
|
-
connection.receiver.capacityFromAP = scenario.capacityRf(
|
|
690
|
-
receiver, connection.AP
|
|
691
|
-
)
|
|
692
|
-
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
693
|
-
connection.capacityRequired, connection.receiver.capacityFromAP
|
|
694
|
-
)
|
|
695
|
-
connection.snr = rf_snr[best_rf]
|
|
696
|
-
else:
|
|
697
|
-
for vled_pos in range(len(vleds)):
|
|
698
|
-
if vled_snr[vled_pos] > connection.snr:
|
|
699
|
-
connection.AP = vleds[vled_pos]
|
|
700
|
-
connection.receiver.capacityFromAP = scenario.capacityVled(
|
|
701
|
-
receiver, connection.AP
|
|
702
|
-
)
|
|
703
|
-
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
704
|
-
connection.capacityRequired, connection.receiver.capacityFromAP
|
|
705
|
-
)
|
|
706
|
-
connection.snr = vled_snr[vled_pos]
|
|
707
|
-
|
|
708
|
-
if connection.AP is None:
|
|
709
|
-
raise ValueError("Failed to assign an AccessPoint to the connection.")
|
|
710
|
-
|
|
711
|
-
if (
|
|
712
|
-
connection.capacityRequired is None
|
|
713
|
-
or connection.receiver.capacityFromAP is None
|
|
714
|
-
):
|
|
715
|
-
raise ValueError(
|
|
716
|
-
"Connection must have valid capacityRequired and capacityFromAP."
|
|
757
|
+
candidate = Controller._select_vled_candidate(
|
|
758
|
+
receiver,
|
|
759
|
+
connection,
|
|
760
|
+
scenario,
|
|
761
|
+
controller,
|
|
762
|
+
vled_snr,
|
|
763
|
+
)
|
|
764
|
+
if candidate is None:
|
|
765
|
+
candidate = Controller._select_rf_candidate(
|
|
766
|
+
receiver,
|
|
767
|
+
connection,
|
|
768
|
+
scenario,
|
|
769
|
+
controller,
|
|
770
|
+
rf_snr,
|
|
717
771
|
)
|
|
718
772
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
auxFrame = 0
|
|
773
|
+
if candidate is None:
|
|
774
|
+
return Controller.status.WAIT, connection
|
|
722
775
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
):
|
|
729
|
-
connection.assignFrameSlice(0, slice)
|
|
730
|
-
aux += 1
|
|
731
|
-
break
|
|
732
|
-
|
|
733
|
-
# next frames
|
|
734
|
-
for frameIndex in range(1, len(controller.framesState(connection.AP))):
|
|
735
|
-
for slice in range(connection.AP.slicesInFrame):
|
|
736
|
-
if controller.framesState(connection.AP)[frameIndex][slice] == False:
|
|
737
|
-
connection.assignFrameSlice(frameIndex, slice)
|
|
738
|
-
aux += 1
|
|
739
|
-
auxFrame = frameIndex
|
|
740
|
-
break
|
|
741
|
-
|
|
742
|
-
if aux == numberOfSlices:
|
|
743
|
-
break
|
|
744
|
-
|
|
745
|
-
frameIndex = auxFrame + 1
|
|
746
|
-
while aux < numberOfSlices:
|
|
747
|
-
connection.assignFrameSlice(frameIndex, 0)
|
|
748
|
-
frameIndex += 1
|
|
749
|
-
aux += 1
|
|
776
|
+
ap, snr, capacity, numberOfSlices = candidate
|
|
777
|
+
connection.AP = ap
|
|
778
|
+
connection.receiver.capacityFromAP = capacity
|
|
779
|
+
connection.snr = snr
|
|
780
|
+
Controller._assign_frame_slices(connection, controller, numberOfSlices)
|
|
750
781
|
return Controller.status.ALLOCATED, connection
|
|
@@ -11,7 +11,7 @@ The Simulator module handles:
|
|
|
11
11
|
- Event scheduling and management using Future Event List (FEL)
|
|
12
12
|
- Random variable generation for arrivals, departures, and positions
|
|
13
13
|
- Connection lifecycle tracking (arrive → allocate → transmit → depart)
|
|
14
|
-
- Statistical data collection (blocking probability, AP utilization)
|
|
14
|
+
- Statistical data collection (blocking/waiting probability, AP utilization)
|
|
15
15
|
- Scenario initialization and execution control
|
|
16
16
|
|
|
17
17
|
Example:
|
|
@@ -37,6 +37,7 @@ Example:
|
|
|
37
37
|
|
|
38
38
|
# Get results
|
|
39
39
|
print(f"Blocking probability: {sim.get_Blocking_Probability()}")
|
|
40
|
+
print(f"Waiting probability: {sim.get_Waiting_Probability()}")
|
|
40
41
|
|
|
41
42
|
Note:
|
|
42
43
|
- Must call `init()` before `run()`
|
|
@@ -159,7 +160,7 @@ class Simulator:
|
|
|
159
160
|
1. **Initialization**: Configure scenario, APs, and simulation parameters
|
|
160
161
|
2. **Event Scheduling**: Maintain chronologically ordered Future Event List (FEL)
|
|
161
162
|
3. **Event Processing**: Execute event routines (arrive, pause, resume, depart)
|
|
162
|
-
4. **Statistics Collection**: Track blocking probability, AP utilization, delays
|
|
163
|
+
4. **Statistics Collection**: Track blocking/waiting probability, AP utilization, delays
|
|
163
164
|
|
|
164
165
|
Key Features:
|
|
165
166
|
- Poisson arrival process (exponential inter-arrival times)
|
|
@@ -193,7 +194,9 @@ class Simulator:
|
|
|
193
194
|
|
|
194
195
|
# Get results
|
|
195
196
|
blocking = sim.get_Blocking_Probability()
|
|
197
|
+
waiting = sim.get_Waiting_Probability()
|
|
196
198
|
print(f"Blocking Probability: {blocking}")
|
|
199
|
+
print(f"Waiting Probability: {waiting}")
|
|
197
200
|
|
|
198
201
|
Note:
|
|
199
202
|
- Must call `init()` before `run()`
|
|
@@ -246,6 +249,10 @@ class Simulator:
|
|
|
246
249
|
self.__rtn_allocation: Optional[Any] = None
|
|
247
250
|
|
|
248
251
|
self.__allocatedConnections: int = 0
|
|
252
|
+
self.__attemptedConnections: int = 0
|
|
253
|
+
self.__allocatedConnectionIds: set[int] = set()
|
|
254
|
+
self.__blockedConnectionIds: set[int] = set()
|
|
255
|
+
self.__waitingConnectionIds: set[int] = set()
|
|
249
256
|
# time
|
|
250
257
|
self.__clock: float = 0.0
|
|
251
258
|
self.__time_duration: Optional[float] = None
|
|
@@ -305,6 +312,10 @@ class Simulator:
|
|
|
305
312
|
self.__lower_capacity_required = 1e5
|
|
306
313
|
self.__upper_capacity_required = 5e5
|
|
307
314
|
self.__allocatedConnections = 0
|
|
315
|
+
self.__attemptedConnections = 0
|
|
316
|
+
self.__allocatedConnectionIds = set()
|
|
317
|
+
self.__blockedConnectionIds = set()
|
|
318
|
+
self.__waitingConnectionIds = set()
|
|
308
319
|
self.__controller.allocator = Controller.default_alloc
|
|
309
320
|
|
|
310
321
|
@property
|
|
@@ -790,6 +801,7 @@ class Simulator:
|
|
|
790
801
|
# print(self.__controller.activeConnections[3])
|
|
791
802
|
# print(self.__controller.activeConnections[4])
|
|
792
803
|
if self.__current_event.type == Event.event.ARRIVE:
|
|
804
|
+
self.__attemptedConnections += 1
|
|
793
805
|
next_event_time = self.__clock + self.__arrival_variable.exponential(
|
|
794
806
|
self.__lambdaS
|
|
795
807
|
)
|
|
@@ -857,9 +869,10 @@ class Simulator:
|
|
|
857
869
|
e.connection = connection
|
|
858
870
|
self.__events.insert(pos + 1, e)
|
|
859
871
|
break
|
|
860
|
-
|
|
872
|
+
self.__record_allocated_connection(connection)
|
|
861
873
|
elif next_status == Controller.nextStatus.RND_WAIT:
|
|
862
874
|
self.__current_event.connection = connection
|
|
875
|
+
self.__record_waiting_connection(connection)
|
|
863
876
|
next_event_time = self.__clock + self.__random_wait_variable.uniform(
|
|
864
877
|
low=self.__lower_random_wait,
|
|
865
878
|
high=self.upper_random_wait,
|
|
@@ -875,6 +888,8 @@ class Simulator:
|
|
|
875
888
|
if self.__events[pos].time < next_event_time:
|
|
876
889
|
self.__events.insert(pos + 1, next_event)
|
|
877
890
|
break
|
|
891
|
+
elif next_status == Controller.nextStatus.IDLE:
|
|
892
|
+
self.__record_blocked_connection(connection)
|
|
878
893
|
elif self.__current_event.type == Event.event.NEXT_CONNECTION_TRY:
|
|
879
894
|
if not self.__current_event.connection:
|
|
880
895
|
return
|
|
@@ -900,8 +915,9 @@ class Simulator:
|
|
|
900
915
|
e.connection = connection
|
|
901
916
|
self.__events.insert(pos + 1, e)
|
|
902
917
|
break
|
|
903
|
-
|
|
918
|
+
self.__record_allocated_connection(connection)
|
|
904
919
|
elif next_status == Controller.nextStatus.RND_WAIT:
|
|
920
|
+
self.__record_waiting_connection(connection)
|
|
905
921
|
next_event_time = self.__clock + self.__random_wait_variable.uniform(
|
|
906
922
|
low=self.__lower_random_wait,
|
|
907
923
|
high=self.upper_random_wait,
|
|
@@ -916,6 +932,8 @@ class Simulator:
|
|
|
916
932
|
if self.__events[pos].time < next_event_time:
|
|
917
933
|
self.__events.insert(pos + 1, next_event)
|
|
918
934
|
break
|
|
935
|
+
elif next_status == Controller.nextStatus.IDLE:
|
|
936
|
+
self.__record_blocked_connection(connection)
|
|
919
937
|
elif self.__current_event.type == Event.event.PAUSE:
|
|
920
938
|
if not self.__current_event.connection:
|
|
921
939
|
return
|
|
@@ -977,6 +995,28 @@ class Simulator:
|
|
|
977
995
|
|
|
978
996
|
return self.__rtn_allocation
|
|
979
997
|
|
|
998
|
+
def __record_allocated_connection(self, connection: Connection) -> None:
|
|
999
|
+
connection_id = connection.id
|
|
1000
|
+
if connection_id in self.__allocatedConnectionIds:
|
|
1001
|
+
return
|
|
1002
|
+
self.__waitingConnectionIds.discard(connection_id)
|
|
1003
|
+
self.__allocatedConnectionIds.add(connection_id)
|
|
1004
|
+
self.__allocatedConnections += 1
|
|
1005
|
+
|
|
1006
|
+
def __record_waiting_connection(self, connection: Connection) -> None:
|
|
1007
|
+
connection_id = connection.id
|
|
1008
|
+
if connection_id in self.__allocatedConnectionIds:
|
|
1009
|
+
return
|
|
1010
|
+
if connection_id in self.__blockedConnectionIds:
|
|
1011
|
+
return
|
|
1012
|
+
self.__waitingConnectionIds.add(connection_id)
|
|
1013
|
+
|
|
1014
|
+
def __record_blocked_connection(self, connection: Connection) -> None:
|
|
1015
|
+
connection_id = connection.id
|
|
1016
|
+
self.__waitingConnectionIds.discard(connection_id)
|
|
1017
|
+
if connection_id not in self.__allocatedConnectionIds:
|
|
1018
|
+
self.__blockedConnectionIds.add(connection_id)
|
|
1019
|
+
|
|
980
1020
|
def init(self):
|
|
981
1021
|
"""Initialize the simulation engine and prepare for execution.
|
|
982
1022
|
|
|
@@ -1011,6 +1051,10 @@ class Simulator:
|
|
|
1011
1051
|
self.__events = []
|
|
1012
1052
|
self.__numberOfConnections = 0
|
|
1013
1053
|
self.__allocatedConnections = 0
|
|
1054
|
+
self.__attemptedConnections = 0
|
|
1055
|
+
self.__allocatedConnectionIds = set()
|
|
1056
|
+
self.__blockedConnectionIds = set()
|
|
1057
|
+
self.__waitingConnectionIds = set()
|
|
1014
1058
|
self.__users_by_vlc = []
|
|
1015
1059
|
self.__users_by_rf = []
|
|
1016
1060
|
self.__current_event = None
|
|
@@ -1109,19 +1153,65 @@ class Simulator:
|
|
|
1109
1153
|
"""
|
|
1110
1154
|
return self.__time_duration if self.__time_duration is not None else 0.0
|
|
1111
1155
|
|
|
1156
|
+
def get_Attempted_Connections(self) -> int:
|
|
1157
|
+
"""Get the number of arrival events processed by the simulator.
|
|
1158
|
+
|
|
1159
|
+
Returns:
|
|
1160
|
+
int: Number of distinct connection requests that reached allocation logic
|
|
1161
|
+
"""
|
|
1162
|
+
return self.__attemptedConnections
|
|
1163
|
+
|
|
1164
|
+
def get_Allocated_Connections(self) -> int:
|
|
1165
|
+
"""Get the number of connections successfully allocated.
|
|
1166
|
+
|
|
1167
|
+
Returns:
|
|
1168
|
+
int: Number of distinct connections assigned to an access point
|
|
1169
|
+
"""
|
|
1170
|
+
return self.__allocatedConnections
|
|
1171
|
+
|
|
1172
|
+
def get_Waiting_Connections(self) -> int:
|
|
1173
|
+
"""Get the number of connections currently waiting for a retry.
|
|
1174
|
+
|
|
1175
|
+
Returns:
|
|
1176
|
+
int: Number of distinct connections in WAIT state
|
|
1177
|
+
"""
|
|
1178
|
+
return len(self.__waitingConnectionIds)
|
|
1179
|
+
|
|
1180
|
+
def get_Blocked_Connections(self) -> int:
|
|
1181
|
+
"""Get the number of connections definitively rejected.
|
|
1182
|
+
|
|
1183
|
+
Returns:
|
|
1184
|
+
int: Number of distinct connections that returned NOT_ALLOCATED
|
|
1185
|
+
"""
|
|
1186
|
+
return len(self.__blockedConnectionIds)
|
|
1187
|
+
|
|
1188
|
+
def get_Waiting_Probability(self) -> float:
|
|
1189
|
+
"""Calculate the fraction of attempted connections currently waiting.
|
|
1190
|
+
|
|
1191
|
+
Returns:
|
|
1192
|
+
float: Waiting probability (0.0 to 1.0, rounded to 2 decimals)
|
|
1193
|
+
"""
|
|
1194
|
+
if self.__attemptedConnections == 0:
|
|
1195
|
+
return 0.0
|
|
1196
|
+
return round(
|
|
1197
|
+
self.get_Waiting_Connections() / self.__attemptedConnections,
|
|
1198
|
+
2,
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1112
1201
|
def get_Blocking_Probability(self) -> float:
|
|
1113
1202
|
"""Calculate the connection blocking probability.
|
|
1114
1203
|
|
|
1115
|
-
Blocking probability is the fraction of connections that were
|
|
1116
|
-
|
|
1117
|
-
|
|
1204
|
+
Blocking probability is the fraction of attempted connections that were
|
|
1205
|
+
definitively rejected with NOT_ALLOCATED. Connections waiting for a retry are
|
|
1206
|
+
reported separately by `get_Waiting_Connections()` and
|
|
1207
|
+
`get_Waiting_Probability()`.
|
|
1118
1208
|
|
|
1119
1209
|
Returns:
|
|
1120
1210
|
float: Blocking probability (0.0 to 1.0, rounded to 2 decimals)
|
|
1121
1211
|
|
|
1122
1212
|
Formula::
|
|
1123
1213
|
|
|
1124
|
-
Blocking Probability =
|
|
1214
|
+
Blocking Probability = blocked_connections / attempted_connections
|
|
1125
1215
|
|
|
1126
1216
|
Example::
|
|
1127
1217
|
|
|
@@ -1129,10 +1219,12 @@ class Simulator:
|
|
|
1129
1219
|
blocking = sim.get_Blocking_Probability()
|
|
1130
1220
|
print(f"Blocking rate: {blocking * 100:.1f}%") # e.g., "Blocking rate: 5.2%"
|
|
1131
1221
|
"""
|
|
1132
|
-
|
|
1133
|
-
|
|
1222
|
+
if self.__attemptedConnections == 0:
|
|
1223
|
+
return 0.0
|
|
1224
|
+
return round(
|
|
1225
|
+
self.get_Blocked_Connections() / self.__attemptedConnections,
|
|
1226
|
+
2,
|
|
1134
1227
|
)
|
|
1135
|
-
return blocking
|
|
1136
1228
|
|
|
1137
1229
|
def set_allocation_algorithm(
|
|
1138
1230
|
self,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|