vlcSim 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vlcsim/__init__.py +25 -0
- vlcsim/controller.py +743 -0
- vlcsim/scene.py +1793 -0
- vlcsim/simulator.py +797 -0
- vlcsim-0.4.0.dist-info/METADATA +164 -0
- vlcsim-0.4.0.dist-info/RECORD +8 -0
- vlcsim-0.4.0.dist-info/WHEEL +4 -0
- vlcsim-0.4.0.dist-info/licenses/LICENSE.md +17 -0
vlcsim/controller.py
ADDED
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The :mod:`vlcsim.controller` module is in charge of managing the connections and the physical sustrate.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from typing import Optional, Union, Callable, Any
|
|
7
|
+
from .scene import *
|
|
8
|
+
from enum import Enum
|
|
9
|
+
import math
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Connection:
|
|
14
|
+
"""
|
|
15
|
+
The :class:`vlcsim.controller.Connection` contains the connection information. Each :class:`vlcsim.controller.Connection` object contains an ID connection, the Access Point (AP) that uses the :class:`vlcsim.controller.Connection` object, the receiver connected to the AP, and finally if the connection was successfully allocated.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, id: int, receiver: "Receiver", time: float) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Connection constructor.
|
|
21
|
+
|
|
22
|
+
:param id: Connection ID (non-negative integer).
|
|
23
|
+
:param receiver: Associated receiver (Receiver instance).
|
|
24
|
+
:param time: Creation time (float >= 0).
|
|
25
|
+
:raises ValueError: If any argument is invalid.
|
|
26
|
+
"""
|
|
27
|
+
if id < 0:
|
|
28
|
+
raise ValueError("id must be a non-negative integer.")
|
|
29
|
+
if time < 0:
|
|
30
|
+
raise ValueError("time must be a non-negative float.")
|
|
31
|
+
self.__id: int = id
|
|
32
|
+
self.__ap: Optional[AccessPoint] = None
|
|
33
|
+
self.__receiver: Receiver = receiver
|
|
34
|
+
self.__allocated: bool = True
|
|
35
|
+
self.__frameSlice: list[list[int]] = []
|
|
36
|
+
self.__timesAssigned: list[float] = []
|
|
37
|
+
self.__goalTime: Optional[float] = None
|
|
38
|
+
self.__time: float = float(time)
|
|
39
|
+
self.__capacityRequired: Optional[float] = None
|
|
40
|
+
self.__snr: float = sys.float_info.min
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def snr(self) -> float:
|
|
44
|
+
"""Signal to Noise Ratio
|
|
45
|
+
|
|
46
|
+
:return: Signal to Noise Ratio
|
|
47
|
+
:rtype: float
|
|
48
|
+
"""
|
|
49
|
+
return self.__snr
|
|
50
|
+
|
|
51
|
+
@snr.setter
|
|
52
|
+
def snr(self, value: float):
|
|
53
|
+
self.__snr = float(value)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def capacityRequired(self) -> Optional[float]:
|
|
57
|
+
"""
|
|
58
|
+
Capacity required in bps (can be None).
|
|
59
|
+
|
|
60
|
+
:return: Capacity required in bps or None.
|
|
61
|
+
:rtype: Optional[float]
|
|
62
|
+
"""
|
|
63
|
+
return self.__capacityRequired
|
|
64
|
+
|
|
65
|
+
@capacityRequired.setter
|
|
66
|
+
def capacityRequired(self, value: Optional[float]):
|
|
67
|
+
if value is not None and value < 0:
|
|
68
|
+
raise ValueError("capacityRequired must be a non-negative float or None.")
|
|
69
|
+
self.__capacityRequired = float(value) if value is not None else None
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def time(self) -> float:
|
|
73
|
+
"""The time when this connection arrived to simulation
|
|
74
|
+
|
|
75
|
+
:return: time when this connection arrived to simulation
|
|
76
|
+
:rtype: float
|
|
77
|
+
"""
|
|
78
|
+
return self.__time
|
|
79
|
+
|
|
80
|
+
@time.setter
|
|
81
|
+
def _time(self, value: float):
|
|
82
|
+
if value < 0:
|
|
83
|
+
raise ValueError("time must be a non-negative float.")
|
|
84
|
+
self.__time = float(value)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def goalTime(self) -> Optional[float]:
|
|
88
|
+
"""
|
|
89
|
+
Total effective time that the connection will use the channel.
|
|
90
|
+
Depends on the required and available capacity.
|
|
91
|
+
|
|
92
|
+
:return: Effective time or None.
|
|
93
|
+
:rtype: Optional[float]
|
|
94
|
+
"""
|
|
95
|
+
return self.__goalTime
|
|
96
|
+
|
|
97
|
+
@goalTime.setter
|
|
98
|
+
def goalTime(self, value: Optional[float]):
|
|
99
|
+
if value is not None and value < 0:
|
|
100
|
+
raise ValueError("goalTime must be a non-negative float or None.")
|
|
101
|
+
self.__goalTime = float(value) if value is not None else None
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def frameSlice(self) -> list[list[int]]:
|
|
105
|
+
"""
|
|
106
|
+
List of lists containing the tuples [frame, slice] that this connection will use
|
|
107
|
+
|
|
108
|
+
:return: Tuples frame,slice that the connection will use
|
|
109
|
+
:rtype: list[list[int]]
|
|
110
|
+
"""
|
|
111
|
+
return self.__frameSlice
|
|
112
|
+
|
|
113
|
+
@frameSlice.setter
|
|
114
|
+
def frameSlice(self, value: list[list[int]]):
|
|
115
|
+
self.__frameSlice = value
|
|
116
|
+
|
|
117
|
+
def getNextTime(self) -> float:
|
|
118
|
+
"""
|
|
119
|
+
Gets the next time when this connection will start to transmit again.
|
|
120
|
+
|
|
121
|
+
:return: the next time
|
|
122
|
+
:rtype: float
|
|
123
|
+
:raises IndexError: If there are no assigned times left.
|
|
124
|
+
"""
|
|
125
|
+
if not self.__timesAssigned:
|
|
126
|
+
raise IndexError("No assigned times left for this connection.")
|
|
127
|
+
return self.__timesAssigned.pop(0)
|
|
128
|
+
|
|
129
|
+
def insertTime(self, time: float) -> None:
|
|
130
|
+
"""function in charge of defininfg the times when this connection will be used. It works with the simulation FEL
|
|
131
|
+
|
|
132
|
+
:param time: List of times that this connection will start the transmission
|
|
133
|
+
:type time: float
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
for i in range(len(self.__timesAssigned)):
|
|
137
|
+
if self.__timesAssigned[i] > time:
|
|
138
|
+
self.__timesAssigned.insert(i, time)
|
|
139
|
+
break
|
|
140
|
+
self.__timesAssigned.append(time)
|
|
141
|
+
|
|
142
|
+
def assignFrameSlice(self, frame: int, slice: int):
|
|
143
|
+
"""Assigns a frame and an slice to this connection.
|
|
144
|
+
|
|
145
|
+
:param frame: frame number
|
|
146
|
+
:type frame: int
|
|
147
|
+
:param slice: slice number
|
|
148
|
+
:type slice: int
|
|
149
|
+
"""
|
|
150
|
+
self.__frameSlice.append([frame, slice])
|
|
151
|
+
|
|
152
|
+
def numberOfSlicesNeeded(
|
|
153
|
+
self, capacityRequired: float, capacityFromAP: float
|
|
154
|
+
) -> int:
|
|
155
|
+
"""
|
|
156
|
+
Determines the number of slices needed by this connection depending on the required and available capacity.
|
|
157
|
+
|
|
158
|
+
:param capacityRequired: Capacity required
|
|
159
|
+
:type capacityRequired: float
|
|
160
|
+
:param capacityFromAP: capacity given
|
|
161
|
+
:type capacityFromAP: float
|
|
162
|
+
:return: The number of slices needed by this connection
|
|
163
|
+
:rtype: int
|
|
164
|
+
:raises ValueError: If capacityFromAP is zero or negative.
|
|
165
|
+
"""
|
|
166
|
+
if capacityFromAP <= 0:
|
|
167
|
+
raise ValueError("capacityFromAP must be a positive number.")
|
|
168
|
+
return math.ceil(capacityRequired / capacityFromAP)
|
|
169
|
+
|
|
170
|
+
def nextSliceInAPWhenArriving(self, ap: AccessPoint) -> int:
|
|
171
|
+
"""
|
|
172
|
+
When a new connection arrives, this function gives the position of the next slot depending on the arriving time for an AP.
|
|
173
|
+
|
|
174
|
+
:param ap: Access Point
|
|
175
|
+
:type ap: :class:`vlcsim.scene.AccessPoint`
|
|
176
|
+
:return: the slot position
|
|
177
|
+
:rtype: int
|
|
178
|
+
:raises ValueError: If ap.sliceTime or ap.slicesInFrame is None or invalid.
|
|
179
|
+
"""
|
|
180
|
+
if ap.sliceTime is None or ap.slicesInFrame is None:
|
|
181
|
+
raise ValueError("AccessPoint must have valid sliceTime and slicesInFrame.")
|
|
182
|
+
if ap.sliceTime <= 0 or ap.slicesInFrame <= 0:
|
|
183
|
+
raise ValueError("sliceTime and slicesInFrame must be positive numbers.")
|
|
184
|
+
return math.ceil(self.__time / ap.sliceTime) % ap.slicesInFrame
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def receiver(self) -> Receiver:
|
|
188
|
+
"""
|
|
189
|
+
Receiver of this connection
|
|
190
|
+
|
|
191
|
+
:return: The receiver of this connection.
|
|
192
|
+
:rtype: :class:`vlcsim.scene.Receiver`
|
|
193
|
+
"""
|
|
194
|
+
return self.__receiver
|
|
195
|
+
|
|
196
|
+
@receiver.setter
|
|
197
|
+
def receiver(self, value: Receiver):
|
|
198
|
+
self.__receiver = value
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def id(self) -> int:
|
|
202
|
+
"""
|
|
203
|
+
ID of this connection
|
|
204
|
+
|
|
205
|
+
:return: The ID of this connection.
|
|
206
|
+
:rtype: int
|
|
207
|
+
"""
|
|
208
|
+
return self.__id
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def AP(self) -> Optional[AccessPoint]:
|
|
212
|
+
"""
|
|
213
|
+
Access Point of this connection
|
|
214
|
+
|
|
215
|
+
:return: The Access Point used by this connection.
|
|
216
|
+
:rtype: Optional[:class:`vlcsim.scene.AccessPoint`]
|
|
217
|
+
"""
|
|
218
|
+
return self.__ap
|
|
219
|
+
|
|
220
|
+
@AP.setter
|
|
221
|
+
def AP(self, ap: Optional[AccessPoint]):
|
|
222
|
+
self.__ap = ap
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def allocated(self) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Is this connection allocated?
|
|
228
|
+
|
|
229
|
+
:return: True if the connection was allocated. False otherwise.
|
|
230
|
+
:rtype: bool
|
|
231
|
+
"""
|
|
232
|
+
return self.__allocated
|
|
233
|
+
|
|
234
|
+
@allocated.setter
|
|
235
|
+
def allocated(self, value: bool):
|
|
236
|
+
self.__allocated = value
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class Controller:
|
|
240
|
+
"""
|
|
241
|
+
Class in charge of controlling which connections are assigned and which are not. Contains the routines necessary to assign, unassign, pause and resume a connection.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
status = Enum("status", "ALLOCATED NOT_ALLOCATED WAIT")
|
|
245
|
+
"""
|
|
246
|
+
Corresponds to the status that the allocation algorithm could return. The status **ALLOCATED** means that the connection was succesfully allocated. **NOT_ALLOCATED** means that the allocation algorithm could not allocate the connection, and this was refused. And finally, the **WAIT** means that the connection will wait to be allocated in the future, using the same allocation algorithm.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
nextStatus = Enum("nextStatus", "PAUSE FINISH RESUME IDLE RND_WAIT")
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
Corresponds to the status used inside every allocation routine in the controller module. The status **PAUSE** means that the connection was PAUSED, and it will be resumed in the future. **FINISH** means that the connection completes its work, and finished. The **RESUME** means the connection will start again the transmission after a **PAUSE**. **IDLE** means that the AP is not connected to any receiver. Finally, **RND_WAIT** means that the connection could not be connected, and the controller will wait a random time to try again.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def __init__(self, x: float, y: float, z: float, nGrids: int, rho: float) -> None:
|
|
256
|
+
"""
|
|
257
|
+
Controller constructor.
|
|
258
|
+
|
|
259
|
+
:param x: Length of the scenario.
|
|
260
|
+
:type x: float
|
|
261
|
+
:param y: Width of the scenario.
|
|
262
|
+
:type y: float
|
|
263
|
+
:param z: Height of the scenario.
|
|
264
|
+
:type z: float
|
|
265
|
+
:param nGrids: Number of segments on each dimension. Useful for precision.
|
|
266
|
+
:type nGrids: int
|
|
267
|
+
:param rho: Reflection coefficient. A floating number between 0 and 1.
|
|
268
|
+
:type rho: float
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
self.__scenario: Scenario = Scenario(x, y, z, nGrids, rho)
|
|
272
|
+
self.__allocator: Optional[
|
|
273
|
+
Callable[
|
|
274
|
+
[Receiver, Connection, Scenario, "Controller"], tuple[int, Connection]
|
|
275
|
+
]
|
|
276
|
+
] = None
|
|
277
|
+
self.__allocationStatus: Optional[int] = None
|
|
278
|
+
self.__activeConnections: list[list[list[Union[bool, Connection]]]] = []
|
|
279
|
+
self.__numberActiveConnections: list[int] = []
|
|
280
|
+
# self.__activeConnections = [[]] * len(self.__scenario.vleds)
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def scenario(self) -> Scenario:
|
|
284
|
+
"""
|
|
285
|
+
Scenario controlled by this controller.
|
|
286
|
+
|
|
287
|
+
:return: The scenario of this controller.
|
|
288
|
+
:rtype: :class:`vlcsim.scene.Scenario`
|
|
289
|
+
"""
|
|
290
|
+
return self.__scenario
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def allocationStatus(self):
|
|
294
|
+
"""
|
|
295
|
+
The last status of the allocation process
|
|
296
|
+
|
|
297
|
+
:return: The last status of the allocation process
|
|
298
|
+
:rtype: :class:`vlcsim.controller.status`
|
|
299
|
+
"""
|
|
300
|
+
return self.__allocationStatus
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def allocator(self):
|
|
304
|
+
"""
|
|
305
|
+
The method used to allocate connections. This method could be replaced with any other method using always this signature:
|
|
306
|
+
|
|
307
|
+
.. code-block:: python
|
|
308
|
+
:linenos:
|
|
309
|
+
|
|
310
|
+
def alloc_function(receiver: Receiver, connection: Connection, scenario: Scenario, controller: Controller):
|
|
311
|
+
# Some allocation algorithm using the parameters.
|
|
312
|
+
|
|
313
|
+
# It have to return an status and the connection in a tuple
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
An example of an allocation method:
|
|
317
|
+
|
|
318
|
+
.. code-block:: python
|
|
319
|
+
:linenos:
|
|
320
|
+
:emphasize-lines: 1,11
|
|
321
|
+
|
|
322
|
+
def alloc(receiver, connection, scenario: Scenario, controller: Controller):
|
|
323
|
+
vleds = scenario.vleds
|
|
324
|
+
snrFromVleds = []
|
|
325
|
+
for vled in vleds:
|
|
326
|
+
snrFromVleds.append(scenario.snrVled(receiver, vled))
|
|
327
|
+
posBestSNR = snrFromVleds.index(max(snrFromVleds))
|
|
328
|
+
if controller.numberOfActiveConnections(posBestSNR) > 2:
|
|
329
|
+
return Controller.status.WAIT, connection
|
|
330
|
+
else:
|
|
331
|
+
connection.AP = vleds[posBestSNR]
|
|
332
|
+
return Controller.status.ALLOCATED, connection
|
|
333
|
+
"""
|
|
334
|
+
return self.__allocator
|
|
335
|
+
|
|
336
|
+
@allocator.setter
|
|
337
|
+
def allocator(
|
|
338
|
+
self,
|
|
339
|
+
allocator: Callable[
|
|
340
|
+
[Receiver, Connection, Scenario, "Controller"], tuple[int, Connection]
|
|
341
|
+
],
|
|
342
|
+
):
|
|
343
|
+
"""Set the allocation algorithm.
|
|
344
|
+
|
|
345
|
+
:param allocator: The allocator function to use
|
|
346
|
+
:type allocator: Callable
|
|
347
|
+
"""
|
|
348
|
+
self.__allocator = allocator
|
|
349
|
+
|
|
350
|
+
def assignConnection(self, connection: Connection, time: float):
|
|
351
|
+
"""
|
|
352
|
+
Routine that assigns a connection to a specific AP. This routine uses the allocator method to make the assignment.
|
|
353
|
+
|
|
354
|
+
:param connection: Connection object that will try to connect.
|
|
355
|
+
:type connection: :class:`vlcsim.controller.Connection`
|
|
356
|
+
:param time: Instant in which this connection tried to connect.
|
|
357
|
+
:type time: float
|
|
358
|
+
"""
|
|
359
|
+
if self.__allocator is None:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
"Allocator function must be set before assigning connections."
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
self.__allocationStatus, connection = self.__allocator(
|
|
365
|
+
connection.receiver, connection, self.__scenario, self
|
|
366
|
+
)
|
|
367
|
+
connection.receiver.timeFirstConnected = time
|
|
368
|
+
|
|
369
|
+
if self.__allocationStatus == Controller.status.ALLOCATED:
|
|
370
|
+
if connection.AP is None:
|
|
371
|
+
raise ValueError(
|
|
372
|
+
"Connection must have a valid AccessPoint (AP) after allocation."
|
|
373
|
+
)
|
|
374
|
+
index = self.APPosition(connection.AP)
|
|
375
|
+
self.__numberActiveConnections[index] += 1
|
|
376
|
+
actualSlice = connection.nextSliceInAPWhenArriving(connection.AP) - 1
|
|
377
|
+
actualTime = (
|
|
378
|
+
(time // (connection.AP.slicesInFrame * connection.AP.sliceTime))
|
|
379
|
+
* connection.AP.slicesInFrame
|
|
380
|
+
* connection.AP.sliceTime
|
|
381
|
+
)
|
|
382
|
+
auxPreviousTime = 0
|
|
383
|
+
if actualSlice == -1:
|
|
384
|
+
auxPreviousTime = 1
|
|
385
|
+
if (
|
|
386
|
+
connection.receiver.capacityFromAP is None
|
|
387
|
+
or connection.AP.sliceTime is None
|
|
388
|
+
):
|
|
389
|
+
raise ValueError(
|
|
390
|
+
"Receiver must have capacityFromAP and AP must have sliceTime set."
|
|
391
|
+
)
|
|
392
|
+
if connection.capacityRequired is None:
|
|
393
|
+
raise ValueError("Connection must have capacityRequired set.")
|
|
394
|
+
connection.receiver.goalTime = (
|
|
395
|
+
connection.capacityRequired
|
|
396
|
+
/ connection.receiver.capacityFromAP
|
|
397
|
+
* connection.AP.sliceTime
|
|
398
|
+
)
|
|
399
|
+
connection.goalTime = connection.receiver.goalTime
|
|
400
|
+
for fs in connection.frameSlice:
|
|
401
|
+
if fs[0] <= 0 and fs[1] <= actualSlice:
|
|
402
|
+
raise Exception("You are trying to assign a slice in the past...")
|
|
403
|
+
self.assignSlice(index, fs[0], fs[1], connection)
|
|
404
|
+
connection.insertTime(
|
|
405
|
+
actualTime
|
|
406
|
+
+ connection.AP.sliceTime
|
|
407
|
+
* connection.AP.slicesInFrame
|
|
408
|
+
* (fs[0] + auxPreviousTime)
|
|
409
|
+
+ fs[1] * connection.AP.sliceTime
|
|
410
|
+
)
|
|
411
|
+
time = connection.getNextTime()
|
|
412
|
+
return Controller.nextStatus.RESUME, time, connection
|
|
413
|
+
elif self.__allocationStatus == Controller.status.NOT_ALLOCATED:
|
|
414
|
+
return Controller.nextStatus.IDLE, time, connection
|
|
415
|
+
elif self.__allocationStatus == Controller.status.WAIT:
|
|
416
|
+
return Controller.nextStatus.RND_WAIT, time, connection
|
|
417
|
+
else:
|
|
418
|
+
raise Exception("Return status of allocation algorithm not supported")
|
|
419
|
+
|
|
420
|
+
def pauseConnection(self, connection: Connection, time: float):
|
|
421
|
+
"""
|
|
422
|
+
Routine that pauses a connection to a specific AP.
|
|
423
|
+
|
|
424
|
+
:param connection: Connection object that will be paused.
|
|
425
|
+
:type connection: :class:`vlcsim.controller.Connection`
|
|
426
|
+
:param time: Instant in which this connection was paused.
|
|
427
|
+
:type time: float
|
|
428
|
+
"""
|
|
429
|
+
if connection.AP is None:
|
|
430
|
+
raise ValueError("Connection must have a valid AccessPoint (AP).")
|
|
431
|
+
|
|
432
|
+
receiver = connection.receiver
|
|
433
|
+
index = self.APPosition(connection.AP)
|
|
434
|
+
receiver.timeActive += connection.AP.sliceTime
|
|
435
|
+
timeNext = connection.getNextTime()
|
|
436
|
+
nextSlice = connection.nextSliceInAPWhenArriving(connection.AP)
|
|
437
|
+
flag = False
|
|
438
|
+
for i in range(nextSlice, connection.AP.slicesInFrame):
|
|
439
|
+
if len(self.__activeConnections[index]) == 0:
|
|
440
|
+
flag = True
|
|
441
|
+
break
|
|
442
|
+
if self.__activeConnections[index][0][i] != False:
|
|
443
|
+
flag = True
|
|
444
|
+
break
|
|
445
|
+
if not flag:
|
|
446
|
+
self.__activeConnections[index].pop(0)
|
|
447
|
+
return Controller.nextStatus.RESUME, timeNext, connection
|
|
448
|
+
|
|
449
|
+
def resumeConnection(self, connection: Connection, time: float):
|
|
450
|
+
"""
|
|
451
|
+
Routine that resumes a connection to a specific AP.
|
|
452
|
+
|
|
453
|
+
:param connection: Connection object that will be resumed.
|
|
454
|
+
:type connection: :class:`vlcsim.controller.Connection`
|
|
455
|
+
:param time: Instant in which this connection was resumed.
|
|
456
|
+
:type time: float
|
|
457
|
+
"""
|
|
458
|
+
if connection.AP is None:
|
|
459
|
+
raise ValueError("Connection must have a valid AccessPoint (AP).")
|
|
460
|
+
|
|
461
|
+
receiver = connection.receiver
|
|
462
|
+
if receiver.goalTime is None:
|
|
463
|
+
raise ValueError(
|
|
464
|
+
"Receiver must have goalTime set before resuming connection."
|
|
465
|
+
)
|
|
466
|
+
if receiver.goalTime < receiver.timeActive + connection.AP.sliceTime:
|
|
467
|
+
return (
|
|
468
|
+
Controller.nextStatus.FINISH,
|
|
469
|
+
time + receiver.goalTime - receiver.timeActive,
|
|
470
|
+
connection,
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
return (
|
|
474
|
+
Controller.nextStatus.PAUSE,
|
|
475
|
+
time + connection.AP.sliceTime,
|
|
476
|
+
connection,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
def unassignConnection(self, connection: Connection, time: float):
|
|
480
|
+
"""
|
|
481
|
+
Routine that unassigns a connection from a specific AP.
|
|
482
|
+
|
|
483
|
+
:param connection: Connection object that will be unassigned.
|
|
484
|
+
:type connection: :class:`vlcsim.controller.Connection`
|
|
485
|
+
:param time: Instant in which this connection was unassigned.
|
|
486
|
+
:type time: float
|
|
487
|
+
"""
|
|
488
|
+
if connection.AP is None:
|
|
489
|
+
raise ValueError("Connection must have a valid AccessPoint (AP).")
|
|
490
|
+
|
|
491
|
+
index = self.APPosition(connection.AP)
|
|
492
|
+
receiver = connection.receiver
|
|
493
|
+
if receiver.goalTime is not None:
|
|
494
|
+
receiver.timeActive = receiver.goalTime
|
|
495
|
+
receiver.timeFinished = time
|
|
496
|
+
|
|
497
|
+
nextSlice = connection.nextSliceInAPWhenArriving(connection.AP)
|
|
498
|
+
flag = False
|
|
499
|
+
for i in range(nextSlice, connection.AP.slicesInFrame):
|
|
500
|
+
if len(self.__activeConnections[index]) == 0:
|
|
501
|
+
break
|
|
502
|
+
if self.__activeConnections[index][0][i] != False:
|
|
503
|
+
flag = True
|
|
504
|
+
if flag:
|
|
505
|
+
self.__activeConnections[index].pop(self.__activeConnection[index])
|
|
506
|
+
self.__numberActiveConnections[index] -= 1
|
|
507
|
+
return Controller.nextStatus.IDLE, time, None
|
|
508
|
+
|
|
509
|
+
def init(self):
|
|
510
|
+
"""Initialize the controller's internal structures for simulation.
|
|
511
|
+
|
|
512
|
+
Must be invoked before starting the simulation.
|
|
513
|
+
Initializes the active connections lists and the count of connections per AP.
|
|
514
|
+
Performs robustness checks on the scenario and APs configuration.
|
|
515
|
+
|
|
516
|
+
:raises RuntimeError: If the scenario or APs are not correctly configured.
|
|
517
|
+
"""
|
|
518
|
+
self.__activeConnections = []
|
|
519
|
+
self.__numberActiveConnections = []
|
|
520
|
+
|
|
521
|
+
if not hasattr(self, "_Controller__scenario") or self.__scenario is None:
|
|
522
|
+
raise RuntimeError("The scenario is not configured in the controller.")
|
|
523
|
+
vleds = getattr(self.__scenario, "vleds", None)
|
|
524
|
+
rfs = getattr(self.__scenario, "rfs", None)
|
|
525
|
+
if vleds is None or rfs is None:
|
|
526
|
+
raise RuntimeError("The scenario must have 'vleds' and 'rfs' lists.")
|
|
527
|
+
|
|
528
|
+
nvleds = 0
|
|
529
|
+
nrfs = 0
|
|
530
|
+
actual = None
|
|
531
|
+
total_aps = len(vleds) + len(rfs)
|
|
532
|
+
for i in range(total_aps):
|
|
533
|
+
self.__numberActiveConnections.append(0)
|
|
534
|
+
self.__activeConnections.append([])
|
|
535
|
+
if nvleds < len(vleds) and self.APPosition(vleds[nvleds]) == i:
|
|
536
|
+
actual = vleds[nvleds]
|
|
537
|
+
nvleds += 1
|
|
538
|
+
elif nrfs < len(rfs):
|
|
539
|
+
actual = rfs[nrfs]
|
|
540
|
+
nrfs += 1
|
|
541
|
+
else:
|
|
542
|
+
raise RuntimeError(f"Could not determine the AP for index {i}.")
|
|
543
|
+
|
|
544
|
+
if not hasattr(actual, "slicesInFrame") or actual.slicesInFrame is None:
|
|
545
|
+
raise RuntimeError(
|
|
546
|
+
f"The AP at position {i} does not have 'slicesInFrame' defined."
|
|
547
|
+
)
|
|
548
|
+
if not isinstance(actual.slicesInFrame, int) or actual.slicesInFrame <= 0:
|
|
549
|
+
raise RuntimeError(
|
|
550
|
+
f"'slicesInFrame' must be a positive integer for the AP at position {i}."
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
self.__activeConnections[-1].append([])
|
|
554
|
+
for _ in range(actual.slicesInFrame):
|
|
555
|
+
self.__activeConnections[-1][-1].append(False)
|
|
556
|
+
self.__activeConnection = [0] * len(self.__activeConnections)
|
|
557
|
+
|
|
558
|
+
def APPosition(self, ap: Union[VLed, RF, AccessPoint]) -> int:
|
|
559
|
+
"""
|
|
560
|
+
Returns the AP position. The AP could be a VLed or a Femtocell, so this method gets the position of this AP in the controller.
|
|
561
|
+
|
|
562
|
+
:param ap: The AP.
|
|
563
|
+
:type ap: :class:`vlcsim.scene.VLed` or :class:`vlcsim.scene.RF`
|
|
564
|
+
:return: The position of the AP.
|
|
565
|
+
:rtype: int
|
|
566
|
+
"""
|
|
567
|
+
if isinstance(ap, VLed):
|
|
568
|
+
return self.__scenario.vledsPositions[ap.ID]
|
|
569
|
+
elif isinstance(ap, RF):
|
|
570
|
+
return self.__scenario.rfsPositions[ap.ID]
|
|
571
|
+
return -1 # Should not reach here
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def activeConnections(self):
|
|
575
|
+
"""
|
|
576
|
+
This attribute is a list of a list of Connections. Every element on the first list corresponds to a list of active connections in every Access Point. In that way, the cardinality of the outer list is equal to the number of Access Points in the scenario, while the number of connections in every access point is the cardinality of each item of the outer list.
|
|
577
|
+
|
|
578
|
+
:return: A list that contains *n* lists, where *n* is the number of APs in the scenario. Each item on the *n* lists corresponds to the connections associated to each AP.
|
|
579
|
+
:rtype: list of lists
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
return self.__activeConnections
|
|
583
|
+
|
|
584
|
+
def numberOfActiveConnections(self, ap: Union[VLed, RF, AccessPoint]) -> int:
|
|
585
|
+
"""
|
|
586
|
+
The number of active connections in this AP.
|
|
587
|
+
|
|
588
|
+
:param ap: The access point.
|
|
589
|
+
:type ap: :class:`vlcsim.scene.VLed` or :class:`vlcsim.scene.RF`
|
|
590
|
+
:return: The number of active connections in this AP.
|
|
591
|
+
:rtype: int
|
|
592
|
+
"""
|
|
593
|
+
return self.__numberActiveConnections[self.APPosition(ap)]
|
|
594
|
+
|
|
595
|
+
def assignSlice(self, apIndex: int, frame: int, slice: int, connection: Connection):
|
|
596
|
+
"""Assigns the connection to the AP, in the frame and slice given.
|
|
597
|
+
|
|
598
|
+
:param apIndex: Index of the AP.
|
|
599
|
+
:type apIndex: int
|
|
600
|
+
:param frame: Frame to be used.
|
|
601
|
+
:type frame: int
|
|
602
|
+
:param slice: Slice to be used.
|
|
603
|
+
:type slice: int
|
|
604
|
+
:param connection: Connection to be assigned.
|
|
605
|
+
:type connection: Connection
|
|
606
|
+
"""
|
|
607
|
+
numberOfFrames = len(self.__activeConnections[apIndex])
|
|
608
|
+
if numberOfFrames < frame + 1:
|
|
609
|
+
for _ in range(numberOfFrames, frame + 1):
|
|
610
|
+
self.__activeConnections[apIndex].append([])
|
|
611
|
+
for __ in range(connection.AP.slicesInFrame):
|
|
612
|
+
self.__activeConnections[apIndex][-1].append(False)
|
|
613
|
+
|
|
614
|
+
if self.__activeConnections[apIndex][frame][slice] != False:
|
|
615
|
+
raise ValueError(
|
|
616
|
+
f"The connection in frame: {frame}, slice: {slice} is already used."
|
|
617
|
+
)
|
|
618
|
+
else:
|
|
619
|
+
self.__activeConnections[apIndex][frame][slice] = connection
|
|
620
|
+
|
|
621
|
+
def framesState(self, ap: AccessPoint):
|
|
622
|
+
"""Return the set of frame/slices of the ap given.
|
|
623
|
+
|
|
624
|
+
:param ap: Access Point
|
|
625
|
+
:type ap: AccessPoint
|
|
626
|
+
:return: A list of lists containing the frames and slices of this AP
|
|
627
|
+
"""
|
|
628
|
+
return self.__activeConnections[self.APPosition(ap)]
|
|
629
|
+
|
|
630
|
+
@staticmethod
|
|
631
|
+
def default_alloc(
|
|
632
|
+
receiver: "Receiver",
|
|
633
|
+
connection: Connection,
|
|
634
|
+
scenario: Scenario,
|
|
635
|
+
controller: "Controller",
|
|
636
|
+
) -> tuple[Any, Connection]:
|
|
637
|
+
vleds = scenario.vleds
|
|
638
|
+
rfs = scenario.rfs
|
|
639
|
+
vled_snr: list[float] = []
|
|
640
|
+
rf_snr: list[float] = []
|
|
641
|
+
for vled in vleds:
|
|
642
|
+
vled_snr.append(scenario.snrVled(receiver, vled))
|
|
643
|
+
for rf in rfs:
|
|
644
|
+
rf_snr.append(scenario.snrRf(receiver, rf))
|
|
645
|
+
|
|
646
|
+
numberOfSlices = 0
|
|
647
|
+
number_better_rf = 0
|
|
648
|
+
for vled_pos in range(len(vleds)):
|
|
649
|
+
number_better_rf = 0
|
|
650
|
+
for rf_pos in range(len(rfs)):
|
|
651
|
+
if vled_snr[vled_pos] > rf_snr[rf_pos]:
|
|
652
|
+
if controller.numberOfActiveConnections(vleds[vled_pos]) < 5:
|
|
653
|
+
connection.AP = vleds[vled_pos]
|
|
654
|
+
connection.receiver.capacityFromAP = scenario.capacityVled(
|
|
655
|
+
receiver, connection.AP
|
|
656
|
+
)
|
|
657
|
+
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
658
|
+
connection.capacityRequired,
|
|
659
|
+
connection.receiver.capacityFromAP,
|
|
660
|
+
)
|
|
661
|
+
connection.snr = vled_snr[vled_pos]
|
|
662
|
+
if numberOfSlices < 5:
|
|
663
|
+
number_better_rf += 1
|
|
664
|
+
break
|
|
665
|
+
else:
|
|
666
|
+
number_better_rf += 1
|
|
667
|
+
break
|
|
668
|
+
else:
|
|
669
|
+
number_better_rf += 1
|
|
670
|
+
break
|
|
671
|
+
if number_better_rf == 0:
|
|
672
|
+
break
|
|
673
|
+
if number_better_rf != 0:
|
|
674
|
+
best_rf_SNR = sys.float_info.min
|
|
675
|
+
best_rf = None
|
|
676
|
+
for rf_pos in range(len(rfs)):
|
|
677
|
+
if rf_snr[rf_pos] > best_rf_SNR:
|
|
678
|
+
best_rf_SNR = rf_snr[rf_pos]
|
|
679
|
+
best_rf = rf_pos
|
|
680
|
+
if best_rf is not None:
|
|
681
|
+
connection.AP = rfs[best_rf]
|
|
682
|
+
connection.receiver.capacityFromAP = scenario.capacityRf(
|
|
683
|
+
receiver, connection.AP
|
|
684
|
+
)
|
|
685
|
+
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
686
|
+
connection.capacityRequired, connection.receiver.capacityFromAP
|
|
687
|
+
)
|
|
688
|
+
connection.snr = rf_snr[best_rf]
|
|
689
|
+
else:
|
|
690
|
+
for vled_pos in range(len(vleds)):
|
|
691
|
+
if vled_snr[vled_pos] > connection.snr:
|
|
692
|
+
connection.AP = vleds[vled_pos]
|
|
693
|
+
connection.receiver.capacityFromAP = scenario.capacityVled(
|
|
694
|
+
receiver, connection.AP
|
|
695
|
+
)
|
|
696
|
+
numberOfSlices = connection.numberOfSlicesNeeded(
|
|
697
|
+
connection.capacityRequired, connection.receiver.capacityFromAP
|
|
698
|
+
)
|
|
699
|
+
connection.snr = vled_snr[vled_pos]
|
|
700
|
+
|
|
701
|
+
if connection.AP is None:
|
|
702
|
+
raise ValueError("Failed to assign an AccessPoint to the connection.")
|
|
703
|
+
|
|
704
|
+
if (
|
|
705
|
+
connection.capacityRequired is None
|
|
706
|
+
or connection.receiver.capacityFromAP is None
|
|
707
|
+
):
|
|
708
|
+
raise ValueError(
|
|
709
|
+
"Connection must have valid capacityRequired and capacityFromAP."
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
actualSlice = connection.nextSliceInAPWhenArriving(connection.AP)
|
|
713
|
+
aux = 0
|
|
714
|
+
auxFrame = 0
|
|
715
|
+
|
|
716
|
+
# Actual frame
|
|
717
|
+
for slice in range(actualSlice, connection.AP.slicesInFrame):
|
|
718
|
+
if (
|
|
719
|
+
len(controller.framesState(connection.AP)) == 0
|
|
720
|
+
or controller.framesState(connection.AP)[0][slice] == False
|
|
721
|
+
):
|
|
722
|
+
connection.assignFrameSlice(0, slice)
|
|
723
|
+
aux += 1
|
|
724
|
+
break
|
|
725
|
+
|
|
726
|
+
# next frames
|
|
727
|
+
for frameIndex in range(1, len(controller.framesState(connection.AP))):
|
|
728
|
+
for slice in range(connection.AP.slicesInFrame):
|
|
729
|
+
if controller.framesState(connection.AP)[frameIndex][slice] == False:
|
|
730
|
+
connection.assignFrameSlice(frameIndex, slice)
|
|
731
|
+
aux += 1
|
|
732
|
+
auxFrame = frameIndex
|
|
733
|
+
break
|
|
734
|
+
|
|
735
|
+
if aux == numberOfSlices:
|
|
736
|
+
break
|
|
737
|
+
|
|
738
|
+
frameIndex = auxFrame + 1
|
|
739
|
+
while aux < numberOfSlices:
|
|
740
|
+
connection.assignFrameSlice(frameIndex, 0)
|
|
741
|
+
frameIndex += 1
|
|
742
|
+
aux += 1
|
|
743
|
+
return Controller.status.ALLOCATED, connection
|