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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vlcSim
3
- Version: 0.6.0
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
  ![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)
28
28
  ![Poetry](https://img.shields.io/badge/poetry-2.3+-blue.svg)
29
29
  ![Coverage](https://img.shields.io/badge/coverage-72%25-brightgreen.svg)
30
- ![Tests](https://img.shields.io/badge/tests-166%20passing-brightgreen.svg)
30
+ ![Tests](https://img.shields.io/badge/tests-173%20passing-brightgreen.svg)
31
31
  ![License](https://img.shields.io/badge/license-MIT-blue.svg)
32
32
  ![GitLab](https://img.shields.io/badge/gitlab-simvlc-orange.svg)
33
33
  [![PyPI](https://img.shields.io/badge/PyPI-vlcSim-blue.svg)](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 capacity calculations
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 or have poor SNR
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
  ![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)
4
4
  ![Poetry](https://img.shields.io/badge/poetry-2.3+-blue.svg)
5
5
  ![Coverage](https://img.shields.io/badge/coverage-72%25-brightgreen.svg)
6
- ![Tests](https://img.shields.io/badge/tests-166%20passing-brightgreen.svg)
6
+ ![Tests](https://img.shields.io/badge/tests-173%20passing-brightgreen.svg)
7
7
  ![License](https://img.shields.io/badge/license-MIT-blue.svg)
8
8
  ![GitLab](https://img.shields.io/badge/gitlab-simvlc-orange.svg)
9
9
  [![PyPI](https://img.shields.io/badge/PyPI-vlcSim-blue.svg)](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 capacity calculations
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 or have poor SNR
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.0" # Placeholder, will be replaced by dynamic versioning
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.0" # placeholder for poetry-dynamic-versioning
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 VLed with best SNR (if < 5 active connections)
617
- 2. If all VLeds busy or poor SNR, fall back to best RF
618
- 3. Select VLed requiring fewer than 5 slices
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 cannot be allocated (no valid AP)
731
+ ValueError: If the connection lacks a capacity requirement
632
732
 
633
733
  Note:
634
- - Prefers VLC over RF when SNR is better
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
- numberOfSlices = 0
654
- number_better_rf = 0
655
- for vled_pos in range(len(vleds)):
656
- number_better_rf = 0
657
- for rf_pos in range(len(rfs)):
658
- if vled_snr[vled_pos] > rf_snr[rf_pos]:
659
- if controller.numberOfActiveConnections(vleds[vled_pos]) < 5:
660
- connection.AP = vleds[vled_pos]
661
- connection.receiver.capacityFromAP = scenario.capacityVled(
662
- receiver, connection.AP
663
- )
664
- numberOfSlices = connection.numberOfSlicesNeeded(
665
- connection.capacityRequired,
666
- connection.receiver.capacityFromAP,
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
- actualSlice = connection.nextSliceInAPWhenArriving(connection.AP)
720
- aux = 0
721
- auxFrame = 0
773
+ if candidate is None:
774
+ return Controller.status.WAIT, connection
722
775
 
723
- # Actual frame
724
- for slice in range(actualSlice, connection.AP.slicesInFrame):
725
- if (
726
- len(controller.framesState(connection.AP)) == 0
727
- or controller.framesState(connection.AP)[0][slice] == False
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
- self.__allocatedConnections += 1
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
- self.__allocatedConnections += 1
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 NOT successfully
1116
- allocated. It represents the percentage of connection requests that were denied
1117
- due to insufficient resources.
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 = 1 - (allocated_connections / total_connections)
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
- blocking = round(
1133
- 1 - self.__allocatedConnections / self.__numberOfConnections, 2
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