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/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