-
Notifications
You must be signed in to change notification settings - Fork 7
/
ue.py
361 lines (335 loc) · 11.8 KB
/
ue.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import os
from typing import Tuple
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import BitGenerator
from buffer import Buffer
from channel import Channel
class UE:
"""
Class containing the UE functions. Each UE have a buffer and Channel values
for specific trials. Each UE will be assigned to a slice.
"""
def __init__(
self,
bs_name: str,
id: int,
trial_number: int,
traffic_type: str,
traffic_throughput: float,
max_packets_buffer: int = 1024,
buffer_max_lat: int = 100,
bandwidth: float = 100000000,
packet_size: int = 8192 * 8,
frequency: int = 2,
total_number_rbs: int = 17,
plots: bool = False,
rng: BitGenerator = np.random.default_rng(),
windows_size_obs: int = 1,
windows_size: int = 10,
normalize_obs: bool = False,
root_path: str = ".",
) -> None:
self.bs_name = bs_name
self.id = id
self.trial_number = trial_number
self.max_packets_buffer = max_packets_buffer
self.bandwidth = bandwidth
self.packet_size = packet_size
self.traffic_type = traffic_type
self.frequency = frequency
self.total_number_rbs = total_number_rbs
self.root_path = root_path
self.se = Channel.read_se_file(
"{}/se/trial{}_f{}_ue{}.npy", trial_number, frequency, id, self.root_path
)
self.buffer_max_lat = buffer_max_lat
self.buffer = Buffer(max_packets_buffer, buffer_max_lat)
self.traffic_throughput = traffic_throughput
self.windows_size_obs = windows_size_obs
self.windows_size = windows_size
self.plots = plots
self.normalize_obs = normalize_obs
self.get_arrived_packets = self.define_traffic_function()
self.hist_labels = [
"pkt_rcv",
"pkt_snt",
"pkt_thr",
"buffer_occ",
"avg_lat",
"pkt_loss",
"se",
"long_term_pkt_thr",
"fifth_perc_pkt_thr",
]
self.hist = {hist_label: np.array([]) for hist_label in self.hist_labels}
self.no_windows_hist = {
hist_label: np.array([]) for hist_label in self.hist_labels
}
self.number_pkt_loss = np.array([])
self.rng = rng
def define_traffic_function(self):
"""
Return a function to calculate the number of packets received to queue
in the buffer structure. It varies in according to the slice traffic behavior.
"""
def traffic_embb():
return np.floor(
np.abs(
self.rng.poisson((self.traffic_throughput * 1e6) / self.packet_size)
)
)
def traffic_urllc():
return np.floor(
np.abs(
self.rng.poisson((self.traffic_throughput * 1e6) / self.packet_size)
)
)
def traffic_be():
if self.traffic_throughput != -1:
return np.floor(
np.abs(
self.rng.poisson(
(self.traffic_throughput * 1e6) / self.packet_size
)
)
)
else:
return 0
if self.traffic_type == "embb":
return traffic_embb
elif self.traffic_type == "urllc":
return traffic_urllc
elif self.traffic_type == "be":
return traffic_be
else:
raise Exception(
"UE {} traffic type {} specified is not valid".format(
self.id, self.traffic_type
)
)
def get_pkt_throughput(
self, step_number: int, number_rbs_allocated: int
) -> np.array:
"""
Calculate the throughput available to be sent by the UE given the number
of RBs allocated, bandwidth and the spectral efficiency. It is not the
real throughput since the UE may have less packets in the buffer than
the number of packets available to send.
"""
return np.floor(
(
(number_rbs_allocated / self.total_number_rbs)
* self.bandwidth
* self.se[step_number]
)
/ self.packet_size
)
def update_hist(
self,
packets_received: int,
packets_sent: int,
packets_throughput: int,
buffer_occupancy: float,
avg_latency: float,
pkt_loss: int,
step_number: int,
) -> None:
"""
Update the variables history to enable the record to external files.
It creates a tmp_hist that records the history of UE variables without
using a windows calculation and it is used as basis to calculate the
hist variable using windows average.
"""
hist_vars = [
self.packets_to_mbps(self.packet_size, packets_received),
self.packets_to_mbps(self.packet_size, packets_sent),
self.packets_to_mbps(self.packet_size, packets_throughput),
buffer_occupancy,
avg_latency,
pkt_loss,
self.se[step_number],
]
normalize_factors = (
[100, 100, 100, 1, self.buffer_max_lat, 1, 100, 100, 100]
if self.normalize_obs
else np.ones(len(self.hist.keys()))
)
self.number_pkt_loss = np.append(self.number_pkt_loss, pkt_loss)
# Hist with no windows for log (not used in the observation space)
idx = (
slice(-(self.windows_size - 1), None)
if self.windows_size != 1
else slice(0, 0)
)
for i, var in enumerate(self.no_windows_hist.items()):
if var[0] == "pkt_loss":
buffer_pkts = (
np.sum(self.buffer.buffer)
+ np.sum(self.no_windows_hist["pkt_snt"][idx])
+ np.sum(self.number_pkt_loss[idx])
- np.sum(self.no_windows_hist["pkt_rcv"][idx])
)
den = (
np.sum(self.no_windows_hist["pkt_rcv"][idx])
+ hist_vars[0]
+ buffer_pkts
)
self.no_windows_hist[var[0]] = (
np.append(
self.no_windows_hist[var[0]],
(np.sum(self.number_pkt_loss[idx]) + hist_vars[i]) / den,
)
if den != 0
else np.append(self.no_windows_hist[var[0]], 0)
)
elif var[0] == "long_term_pkt_thr":
self.no_windows_hist[var[0]] = np.append(
self.no_windows_hist[var[0]],
np.sum(self.no_windows_hist["pkt_thr"][-self.windows_size :])
/ self.no_windows_hist["pkt_thr"][-self.windows_size :].shape[0],
)
elif var[0] == "fifth_perc_pkt_thr":
self.no_windows_hist[var[0]] = np.append(
self.no_windows_hist[var[0]],
np.percentile(
self.no_windows_hist["pkt_thr"][-self.windows_size :], 5
),
)
else:
self.no_windows_hist[var[0]] = np.append(
self.no_windows_hist[var[0]], hist_vars[i]
)
# Hist calculation to be used as observation space (using windows and normalization if applied)
idx_obs = slice(-(self.windows_size_obs), None)
for i, var in enumerate(self.hist.items()):
value = (
np.mean(self.no_windows_hist[var[0]][idx_obs])
if self.no_windows_hist[var[0]].shape[0] != 0
else 0
)
self.hist[var[0]] = np.append(
self.hist[var[0]], value / normalize_factors[i]
)
def save_hist(self) -> None:
"""
Save variables history to external file.
"""
path = "{}/hist/{}/trial{}/ues/".format(
self.root_path, self.bs_name, self.trial_number
)
try:
os.makedirs(path)
except OSError:
pass
np.savez_compressed((path + "ue{}").format(self.id), **self.no_windows_hist)
if self.plots:
UE.plot_metrics(self.bs_name, self.trial_number, self.id, self.root_path)
@staticmethod
def read_hist(
bs_name: str, trial_number: int, ue_id: int, root_path: str = "."
) -> np.array:
"""
Read variables history from external file.
"""
path = "{}/hist/{}/trial{}/ues/ue{}.npz".format(
root_path, bs_name, trial_number, ue_id
)
data = np.load(path)
return np.array(
[
data.f.pkt_rcv,
data.f.pkt_snt,
data.f.pkt_thr,
data.f.buffer_occ,
data.f.avg_lat,
data.f.pkt_loss,
data.f.se,
data.f.long_term_pkt_thr,
data.f.fifth_perc_pkt_thr,
]
)
@staticmethod
def plot_metrics(
bs_name: str, trial_number: int, ue_id: int, root_path: str = "."
) -> None:
"""
Plot UE performance obtained over a specific trial. Read the
information from external file.
"""
hist = UE.read_hist(bs_name, trial_number, ue_id, root_path)
title_labels = [
"Received Throughput",
"Sent Throughput",
"Throughput Capacity",
"Buffer Occupancy Rate",
"Average Buffer Latency",
"Packet Loss Rate",
]
x_label = "Iteration [n]"
y_labels = [
"Throughput (Mbps)",
"Throughput (Mbps)",
"Throughput (Mbps)",
"Occupancy rate",
"Latency (ms)",
"Packet loss rate",
]
w, h = plt.figaspect(0.6)
fig = plt.figure(figsize=(w, h))
fig.suptitle("Trial {}, UE {}".format(trial_number, ue_id))
for i in np.arange(len(title_labels)):
ax = fig.add_subplot(3, 2, i + 1)
ax.set_title(title_labels[i])
ax.set_xlabel(x_label)
ax.set_ylabel(y_labels[i])
ax.scatter(np.arange(hist[i].shape[0]), hist[i])
ax.grid()
fig.tight_layout()
fig.savefig(
"{}/hist/{}/trial{}/ues/ue{}.png".format(
root_path, bs_name, trial_number, ue_id
),
bbox_inches="tight",
pad_inches=0,
format="png",
dpi=100,
)
plt.close()
@staticmethod
def packets_to_mbps(packet_size, number_packets):
return packet_size * number_packets / 1e6
def step(self, step_number: int, number_rbs_allocated: int) -> None:
"""
Executes the UE packets processing. Adding the received packets to the
buffer and sending them in according to the throughput available and
buffer.
"""
pkt_throughput = self.get_pkt_throughput(step_number, number_rbs_allocated)
pkt_received = self.get_arrived_packets()
self.buffer.receive_packets(pkt_received)
self.buffer.send_packets(pkt_throughput)
self.update_hist(
pkt_received,
self.buffer.sent_packets,
pkt_throughput,
self.buffer.get_buffer_occupancy(),
self.buffer.get_avg_delay(),
self.buffer.dropped_packets,
step_number,
)
def main():
# Testing UE functions
ue = UE(
bs_name="test",
id=1,
trial_number=1,
traffic_type="embb",
traffic_throughput=50,
plots=False,
)
for i in range(2000):
ue.step(i, 2)
ue.save_hist()
if __name__ == "__main__":
main()