This repository has been archived by the owner on Oct 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
generation_parameters.py
1494 lines (1161 loc) · 65.1 KB
/
generation_parameters.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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
"""
Created on Sat Jul 4 13:33:50 2020
@author: janeiroja
Parameters concerning the generation of radio channel traces
"""
import numpy as np
import scipy.io
import pathlib
import copy
import utils as ut
class Generation_parameters:
def __init__(self, seed, speed):
# 1- Init General Variables (General parameters and Vari)
self.set_general_param_generation(seed)
# 3- Init parameters regarding how to do the radio channel generation
# parallelisation, number of parallel instances, etc...
self.set_parallelisation_param()
# 4- Compute the variables that depend on several parameters,
# or that have value restrictions based on the defined variables
self.compute_vars_generation()
# 2- Init IO parameters (which folders to write to, and so on)
self.set_io_param(seed, speed)
# 5- Set and Save Matlab variables
self.set_and_save_matlab_parameters(speed)
def set_general_param_generation(self, seed):
# Seed for coefficient generation in Matlab
self.matlab_seed = seed
# If the channel builders were generated by another
# initialisation, then the setup phase will be skipped
# ONLY used if the next variable is 'False'
self.use_existing_builders = False
# Automatically choose to use the existing builders or not
# If the first instance is instance 1, it is probably the start,
# hence create builders. Otherwise, use_existing_builders is set to 1
self.auto_decide_use_existing_builders = False
# Set this to True in case nothing is to be computed, but
# the instances that would run are still going to be printed.
# Note: it only works with hard-batching because it has never been
# needed with soft-batching.
self.dry_run = False
# Set to True if only the builders are to be generated. When the
# setup is done, any generation can use such builders to compute the
# channel, it is only required to have the correct path. For that,
# see how the variable 'use_existing_builders' works.
self.only_setup = False
# In case of generating channels in different machines simultaneously,
# this is used to setup which instances run in each one.
# If this option is off, all instances are computed.
self.specify_instances_to_compute = True
# If we are to run in a distributed setting, it will be much easier to
# tell in the command line what each machine should compute.
# This is the option for that. Further, note that also the number of
# instances running in parallel will be read from the command line.
# If this is set to false, the parameters are the end of the
# parallelisation section will be used.
self.read_inputs_from_command_line = True
# When the disk is shared in multi-machine computations, we need to
# separate the log files of each instance. We do this by changing the
# parent directory used for each one. And this is only needed if
# we compute different traces simultaneously! i.e. if we compute
# in each machine a couple of instances from the same seed, the log
# files do not interfere. However, when computing in each machine
# instances with the same index, the log files would interfere.
# We always separate log files, but no_trace_computation_sharing is
# still useful to know to advise regarding proper parallelisation
# settings. If no seeds are shared betwen machines, this is not needed.
self.common_disk = True
self.no_trace_computation_sharing = True
# Delete the builders after the last instance is generated.
self.delete_builders_at_end = True
# Choose between 'soft_batching' and 'hard_batching', but the first
# is the default if you miss the mark.
# 'soft_batching' runs instances one after the other until a maximum
# of the number of instances running in parallel. When one finishes,
# other one starts right away. 'hard_batching' runs
self.batching_strat = 'soft_batching'
# Parallelisation helper is a small tool that helps to optimise the
# parameters to a efficient parallelisation. It will perform a couple
# of additional checks on the simulation to make sure it can be done
# efficiently. Somewhat of a legacy variable that is not used now.
# It checks when the number of instances doesn't match the number of
# batches perfectly, and the number of batches can't be uniformly
# divided across the machines.
# Note: it only helps if multiple machines are computing the same
# trace.
self.parallelisation_helper = False
# Estimate the trace generation memory and time requirements from the
# variables set and print it at the start of the program.
self.estimate_generation_load = False
# All random numbers come from here
self.numpy_random_seed = 13492
# To print every step of the generation
self.verbose = 1
# ################# Mode 0: General Parameters #######################
# Duration of the Simulation [s]
self.sim_duration = 16
# Frequencies to Simulate [Hz]
self.freqs = [3.5, 26] # [GHz]
self.freqs = [f * 1e9 for f in self.freqs]
# the line above is exceptionally necessary to be here,
# all other computations are in compute_vars()
self.time_compression_ratio = 40
self.freq_compression_ratio = 5
# ###### ###### ###### ###### ###### ###### ###### ###### ######
# ###### Channel Generation: NUMEROLOGY, BANDWIDTHS and N_PRB #######
# Note: There must be a match between numerologies, bandwidths and
# number of prbs. Only include the numerologies that should be
# simulated, having a non-zero bandwidth.
# OFDM numerology - numerologies to be used in the simulation
# in 5G is between 0 and 3 (for data)
self.numerology = np.array([2], dtype=np.double)
# Bandwidths to be used in each frequency at each numerology
self.bandwidth = np.array([[40],
[40]], dtype=np.double) * 1e6
# Number of Physical Resource Blocks (Input always 2D array)
self.n_prb = np.array([[10],
[10]], dtype=np.double)
# See Tools->5G PHY layer -> PRBs per bandwidth
# ###### ###### ###### ###### ###### ###### ###### ###### ######
# ###### ###### ###### ###### ###### ###### ###### ###### ######
# Room Characteristics #
# Physical Users - users in this room
self.n_phy = 4
# Virtual Users - users in other rooms
self.n_vir = self.n_phy
# Cameras per User - number of cameras each users needs
self.n_cameras_per_user = 0
# The camera streams are aggregated in a single UL stream
self.ul_aggregation = True
# Number of BSs
self.n_bs = 1
# Load specific tracks. Note: if tracks aren't created, this needs to
# be set to False to create the vars.mat
self.load_tracks_from_file = False
# Indices of users seats: these indices depend on the number of seats!
# Phy user disposition
self.phy_user_disposition = [i*2 for i in range(self.n_phy)] # 'uniform'
# Vir user disposition
self.vir_user_disposition = [i*2+1 for i in range(self.n_vir)]
# The speaker list is: [time of speach start, user speaking]; [...,...]
# Note the user list is 1-indexed, so the first user is 1.
# Also, the first users are the physical users and only after the
# virtual users (3x % 8 + 1 = 1, 4, 7, 2, 5, 8, 3, 6)
# Important. Should these be interpreter as user indices [phy vir],
# or as seats?
self.custom_speaker_list_uses_seats = True
self.custom_speaker_list = np.array([[0, 1],
[2.1, 4],
[4.2, 7],
[6.3, 2],
[8.4, 5],
[10.5, 8],
[12.6, 3],
[14.7, 6]], dtype=np.double)
# Either customize, or create one automatically
self.use_custom_speaker_list = False
# When creating one speaker list automatically, these two parameters
# are needed to set what speakers and for how long each speaks.
self.only_vir_speak = True
self.speaking_time = 4
# Antenna types, one input per frequency
# [a single 'omni' or 'patch' or 'dipole' or 'array']
self.user_ant_type = np.array(["array",
"array"], dtype=np.object)
self.cam_ant_type = np.array(["array",
"array"], dtype=np.object)
self.bs_ant_type = np.array(["array",
"array"], dtype=np.object)
# If the antenan type in an array, this information is used:
# Antenna structure and element spacing
self.bs_ant_config = np.array([[4, 4], [8, 8]], dtype=np.double)
self.bs_ant_element_spacing = 0.5
# User's Headset
self.user_ant_config = np.array([[2, 2], [4, 4]], dtype=np.double)
self.user_ant_element_spacing = 0.5
# Cameras
self.cam_ant_config = np.array([[2, 2], [4, 4]], dtype=np.double)
self.cam_ant_element_spacing = 0.5
# Use of Hybrid Beamforming/Sub arrays:
self.bs_ant_hybrid_bf = 0
self.user_ant_hybrid_bf = 0
self.cam_ant_hybrid_bf = 0
# Aggregate the subarray responses in a single coefficient (1), or
# return a coeff per AE (0)? The first assumes that we'll only precode
# the digital part. The second is for when also the analog part should
# be messed with.
self.bs_one_response_per_subarray = 1
self.user_one_response_per_subarray = 1
self.cam_one_response_per_subarray = 1
# Subarray structures (Only vertical subarrays are possible! (for now))
self.bs_ant_subarray = np.array([[1, 1], [4, 1]], dtype=np.double)
self.user_ant_subarray = np.array([[1, 1], [3, 1]], dtype=np.double)
self.cam_ant_subarray = np.array([[1, 1], [2, 1]], dtype=np.double)
# NOTE: when subarrays are used, the antenna configuration becomes the
# subarray structure!
# Distinguish between orthogonal polarisations? Basically,
# if the elemenets of each dual-polarised antenna should have separated
# channel responses. This is important for layers.
self.diff_orthogonal_polarisation = 1
# When the elements are not cross polarized, this variable should be 0.
# To apply the Human Blockage models to the computed channel.
# It will generate twice as much data, as the non-blocked channel needs
# to be generated and saved in the first place
self.apply_blocking = 0
# To aggregate channels creating a file with all information.
# Warning: the file can quickly reach to hundreds of GB, or even
# several TBs long
self.aggregate_channels = 0
# Decide if the computed channel should be saved in time domain, or not
self.save_channel_time_domain = False
def set_io_param(self, seed, speed):
# #################### IO Files & Names ##############################
self.curr_path = str(pathlib.Path().absolute())
self.matlab_folder = self.curr_path + '\\Matlab\\'
self.tracks_filename = self.matlab_folder + \
r'Tracks\Circ_Track_SEED1_SPEED1_UE4_point_centre.mat'
#f'Tracks\Track_SEED{seed}_SPEED{speed}_UE{self.n_phy}.mat'
self.conf_folder = self.matlab_folder + 'QuaDRiGa\\quadriga_src\\config\\'
if ' ' in self.conf_folder:
print('Problem! Path has spaces!!!')
ut.stop_execution()
# Folder where the (partial in mode 3 or full in mode 0) channel
# calculations will be stored
# Directory where all channel parts will be placed
self.channel_folder_name = 'Channel_parts\\'
# Directory name where all builders will be placed
self.builders_folder_name = 'Builders\\'
# path to the matlab executable
self.executable_path = (self.matlab_folder +
'Instances-Home\\Meeting12.exe')
# self.executable_path = matlab_folder + 'Instances-Home\\test4.exe'
# Directory where all instances logs are stored
if not self.common_disk:
machine_id = '_0'
else:
machine_id = '_' + ut.get_computer_name()
self.log_dir = (self.curr_path + '\\Instance_logs\\' + \
'Machine' + machine_id + '\\')
# Decide if the log files should be have a backup at the end
self.backup_logfiles = False
date_str = ut.get_time()
# Backup log file - created for each run of this script
# Directory where all log files will be backed up to
self.log_backup_dir = self.log_dir + 'Backup_' + date_str + '\\'
# it can't have, e.g. colons (:)
# Create instance directories if they are not set already or are not
# enough for the instances we intend to compute
if not ut.isdir(self.log_dir) or \
not ut.isdir(self.log_dir + f'Instance{self.last_instance}'):
for i in range(1, self.last_instance + 1):
if not ut.isdir(self.log_dir + f'Instance{i}'):
ut.makedirs(self.log_dir + f'Instance{i}')
# Automatic decision of using the past builders or not.
if self.auto_decide_use_existing_builders:
if self.first_instance == 1:
self.use_existing_builders = False
else:
self.use_existing_builders = True
self.trace_folder = self.matlab_folder + '\\TraceGeneration\\'
if not ut.isdir(self.trace_folder):
ut.makedirs(self.trace_folder)
if self.use_existing_builders:
# Read from little left-over file the last generation
with open('last_gen_folder.txt') as fp:
aux = fp.read()
self.gen_folder = (self.trace_folder + aux.split('\\')[-2] + '\\')
else:
# Create a folder specific for the generation
self.gen_folder = (self.trace_folder + 'Sim_' + date_str +
f'_SEED{self.matlab_seed}\\')
with open('last_gen_folder.txt', 'w') as fp:
fp.write(self.gen_folder)
# File of input parameters to matlab
self.input_param_file_name = 'MatlabInput.mat'
self.input_param_file = self.matlab_folder + self.input_param_file_name
# Builders File path
self.builders_folder = self.gen_folder + self.builders_folder_name
# Each instance should output in the format: {prefix}_{instance ID}
# {prefix}_part_{partID}_instance_{instanceID}_num_{numerology}
self.output_preffix = 'ch'
# Log file name, every log file has the same name
self.log_file_name = 'log.txt'
def set_parallelisation_param(self):
# ################### Parallelisation Parameters ######################
self.monitor_method = 'console' # 'console' or 'log_files'
# It seems they run faster if they are in console mode, it's
# like they have more priority...
self.window_mode = 'windowless' # 'windowless', 'first', 'all'
# #####################################################################
# ############ THE 3 VARIABLES BELOW ARE VERY IMPORTANT: ############
# Used to set how many builders should be used in the same instance.
# In order to parallelise as much as possible, 'UE' is the best option.
self.parallelisation_level = 'UE' # 'None', 'FR', 'BS', 'UE'
# Maximum number of time divisions
self.time_divisions = 2
# Note: The total number of instances is derived from the 2 vars above
# Number of instances to execute per batch (Python needs a core also)
self.n_inst_running_parallel = 4 # ut.get_cpu_count() - 1
# Note: the number of batches = n_inst / inst_in_parallel
# #####################################################################
# #####################################################################
# NOTE:
# 1) instances_per_time_div depends on the parallelisation level
# 2) n_total_instances is = time_divisions * instances_per_time_div
# If one machine is computing everything, then
# n_computing_instances = n_total_instances.
# Otherwise the # of instances to be executed need to be inputed.
# 3) n_batches = n_computing_instances / n_inst_running_parallel
# If read_inputs_from_command_line is set to false,
# and specify_instances_to_compute is set to True, below is used:
self.first_instance = 1
self.last_instance = 16
# The inputs to be read from the command line are the 3 variables above
# Variables for the parallelisation helper function:
# In case the same number of instances are to run in different machines
# there can be an automatic advice for good parallelisation parameters
self.n_machines = 1
self.max_inst_parallel = 12
self.lim_instances_per_machine = 2000
def check_vars_generation(self):
""" Check if the values for coeff generation are correct.
"""
if (self.n_inst_running_parallel % self.time_divisions):
print('Less than optimum configuration for '
'complete parallelisation.')
if self.read_inputs_from_command_line and \
self.specify_instances_to_compute:
usage_print = \
"""Usage: sxr_gen.py <instances in parallel> [first instance]
[last instance] <first seed> <last seed> <speed>\n"""
try:
ut.parse_input_type(int(ut.get_input_arg(1)), ['int'])
ut.parse_input_type(int(ut.get_input_arg(2)), ['int'])
ut.parse_input_type(int(ut.get_input_arg(3)), ['int'])
except ValueError:
print('PROBLEM: THE INPUTS CANNOT BE CONVERTED TO INTS!\n')
print(usage_print)
ut.stop_execution()
except IndexError:
print('Not enough inputs in the command line')
print(usage_print)
ut.stop_execution()
else:
if self.specify_instances_to_compute:
print('WARNING: Reading all input parameters from files, '
'NOT from command line.\n'
'If you want to read parameters from the '
'command line, set to True the variable '
'read_inputs_from_command_line to False.')
# Check if there's enough information for multi-frequency simulations
if len(self.freqs) != self.bandwidth.shape[0] or \
len(self.freqs) != self.n_prb.shape[0]:
print('In multi-frequency simulations the parameters bandwidth '
'and n_prb need to have the same number of rows.')
ut.stop_execution()
# Check numerology, bandwidth and n_prb consistency
if self.numerology.shape[0] != self.bandwidth.shape[1] or \
self.numerology.shape[0] != self.n_prb.shape[1]:
print('Which numerologies should be generated? The ones to be '
'omitted should have zeros across the correspondent places '
'in the bandwidth and n_prb variables.'
'At the moment that is not happening:\n'
f"numerology: {self.numerology}\n"
f"bandwidth: {self.bandwidth}\n"
f"n_prb: {self.n_prb}")
ut.stop_execution()
def compute_vars_generation(self):
self.check_vars_generation()
# there are many many checks on the inputs here...
# There can be a method made only to check variables... check_vars()
# and either raises exceptions or follows through.
# Set Random Seed
np.random.seed(self.numpy_random_seed)
# Update or create timestamp objects
# Simulation duration [s]
self.sim_duration = ut.timestamp(self.sim_duration)
# # TTI [ms]
self.TTI_duration = (
ut.timestamp(0, float(1 / (2**np.max(self.numerology)))))
# # Number of TTIs of the simulation (system level simulator)
self.sim_TTIs = int(self.sim_duration / self.TTI_duration)
# # Number of frequencies of our simulation
self.n_freq = len(self.freqs)
# # In QuaDRiGa the nomenclature makes us call TX and RX,
# # respectively to BSs and UEs, even though they transmit and
# # receive. To break that unfortunate naming, here is solved:
# # Number of UEs
self.n_ue = self.n_phy * (1 + self.n_cameras_per_user)
self.n_users = self.n_phy + self.n_vir
if self.ul_aggregation:
# If there's ul aggregation, there will a single uplink stream
self.n_ul_streams_per_user = 1
else:
# Else, each camera uplinks it's information
self.n_ul_streams_per_user = self.n_cameras_per_user
# The number of PRBs is set manually in the general parameters.
# ONE COULD do it automatically like:
# Number of subcarriers in each frequency, at numerology
# This can be derived from the subcarrier spacing and bandwidth.
# self.n_prb = np.array(
# [np.floor(self.bandwidth[i] / (12 * 15000 *
# 2 ** self.numerology))
# for i in range(len(self.freqs))])
# ################ Parallelisation Vars #####################
if self.parallelisation_level == 'None':
self.instances_per_time_div = 1
if self.parallelisation_level == 'FR':
self.instances_per_time_div = self.n_freq
if self.parallelisation_level == 'BS':
self.instances_per_time_div = self.n_freq * self.n_bs
if self.parallelisation_level == 'UE':
self.instances_per_time_div = (self.n_freq * self.n_bs *
self.n_ue)
# Number of instances (number of separations of the simulation)
self.n_total_instances = (self.time_divisions *
self.instances_per_time_div)
if not self.specify_instances_to_compute:
# First instance of the computation
self.first_instance = 1
# Last instance of the computation
self.last_instance = self.n_total_instances
self.n_instances_to_compute = self.n_total_instances
if self.read_inputs_from_command_line:
self.n_inst_running_parallel = int(ut.get_input_arg(1))
else:
if self.read_inputs_from_command_line:
self.n_inst_running_parallel = int(ut.get_input_arg(1))
self.first_instance = int(ut.get_input_arg(2))
self.last_instance = int(ut.get_input_arg(3))
self.n_instances_to_compute = (self.last_instance -
self.first_instance + 1)
# Number of batches
self.n_batches = int(np.ceil(self.n_instances_to_compute /
self.n_inst_running_parallel))
print(f'Preparing to simulate {self.n_ue} UEs, {self.n_bs} BSs, '
f' and {self.n_freq} Freqs = '
f'{self.instances_per_time_div} instances per time division. '
f'Using {self.time_divisions} time divisions, it results in '
f'a total of {self.n_total_instances} instances to generate.')
print('PARAMETER CHECK 1: ', end='')
# IMPORTANT CHECK
# since the frequency response for different numerologies will have
# a smaller number of ttis, we must be sure of 2 things:
# a) that the total amount of ttis is divisible by 2,4 or 8,
# (depending on the numerologies considered), in order to
# make this reduction;
# b) When we want to do time divisions, each division must also
# be divisible by this number, or else there will be a
# numerology that gets cut off because there won't be enough
# samples to average from.
# This check is done in matlab, but it should be done here too
# because we want to know before calling instances!
# Check a) and b)
# The maximum compression is out much to shrink the number of ttis
# from the highest numerology to the lowest.
max_compression = ut.get_max_compression(self.numerology)
if (self.sim_TTIs % max_compression or
self.sim_TTIs/self.time_divisions % max_compression):
print('BAD!')
print(f"The number of TTIs to simulate ({self.sim_TTIs}) will "
f"be divided by {self.time_divisions}, resulting in "
f"{round(self.sim_TTIs / self.time_divisions, 1)}, "
f"which must be divisible by {max_compression} "
f"when we are computing the lowest numerology. If the "
f"result is not divisible, then an error will pop up in "
f"in Matlab instances.\n\n")
print('There will be problems when segmenting the complete '
'simulation. Pick one of the time divisions from below:')
self.print_best_parallelisation_parameters()
ut.stop_execution()
else:
print('GOOD!'
'The simulation time and number of time divisions is good!')
if self.parallelisation_helper and \
not self.no_trace_computation_sharing:
print('PARAMETER CHECK 2: ', end='')
# if it has decimal part
if self.n_instances_to_compute / self.n_inst_running_parallel % 1:
print('BAD!!')
print('Providing some further advice on the simulation '
'duration...')
print('There will be some instances left at the last '
'batch. For a more effective simulation, use '
'a number of instances that is divisible by '
'the number of instances running in parallel.')
self.print_best_parallelisation_parameters()
else:
print('GOOD! '
'The number of instances to compute in this machine and '
'the number of instances in parallel is good!'
' (There will be no left-overs in the last batch)')
print('PARAMETER CHECK 3: ', end='')
# A final check, to make sure the instances at the limits
# correspond to what each machine should be computing
if self.specify_instances_to_compute and \
not self.no_trace_computation_sharing:
if (self.n_instances_to_compute * self.n_machines) \
!= self.n_total_instances:
actual_number_inst = (self.n_total_instances /
self.n_machines)
print(f'BAD!! Wrong math? There are '
f'{self.n_total_instances} instances in total, '
f'therefore {actual_number_inst} instances need to '
f'be computed in each of the {self.n_machines} '
f'machines in order to complete the simulation!!')
else:
print(f'GOOD! This machine will generate '
f'1/{self.n_machines} of the total amount of '
f'instances to generate.')
if self.estimate_generation_load:
print('-------------------------------------------------')
self.load_estimation()
print('-------------------------------------------------')
def set_and_save_matlab_parameters(self, speed):
# Set matlab parameters and Write them to .mat file
"""
Note: there are a few parameters that are not matlab specific.
These are already assigned previously and here will be just copied
from the Simulation_parameters object.
The rule is: if it's not needed anywhere else in the simulator, it dies
here, i.e. there's no attribute in the
simulation_parameters object.
The exact places where this happens, in the order they appear here, are
(naming from the self variable names):
- conf_folder
- channel_dir
- builders_dir
- sim_duration
- freqs
- n_cams_per_user
"""
# Seed used for random number generation in Matlab: possibilitates
# repeatable results
SEED = self.matlab_seed
# Debug variable - enabling printing in different places (in MATLAB)
debug_mode = [0, # Head Model working mode
0, # Positions, Tracks & Movement
0, # Scenarios in Stats
0, # Speaker list
0, # Print speaker list azimuth
1] # Print steps of the way.
# ########### I/O names ############
# Configurations folder, where the QuaDRiGa configuration files lie.
conf_folder = self.conf_folder
# Folder where the channel calculations before aggregation are placed
channel_parts_folder = self.channel_folder_name
builders_folder = self.builders_folder_name
default_builder_name = 'builder'
default_channel_name = 'channel'
output_folder_name = self.gen_folder
# If the file already exists, decide whether it should be overriden
# (With 4 nines of certainty: False only slows down testing.)
override_existing_folder = True;
# ############## General Parameters ##############
# Display Progress Bars in MATLAB
progress_bars = 1
# To backup the positions and orientations of the tracks
backup_pos_and_ori = 1
# backup to separate file if 1, otherwise, backup into vars.mat.
save_pos_and_ori_backup_separately = 0
# Turn off the change in position
turn_off_positions = 0
# Turn off the orientation (no heads rotation)
turn_off_orientations = 0
# Compute the complete channel response
# Consumes more than double the memory, not necessary for prototyping..
get_full_freq_response = 1
# Save Channel Coefficients (if computed) to File
# These are the one we'll use, not the ones in the time domain
save_freq_response = 1
# Applies the procedure to attenuate the blocked coefficients
apply_blocking = self.apply_blocking
# Save channel in the time domain for posterior usage (e.g. Blocking)?
save_partial_channel_time_domain = self.save_channel_time_domain
# In mode 4 (blocking mode / aggregation mode), it's possible to load
# all previously computed channels and save an aggregated response
# either in time:
aggregate_full_channel_time_domain = [0, 0]
# or in frequency (for each numerology):
aggregate_coeffs_in_freq = [0, 0]
# The first index is for the non-blocked channel, the second is
# for the channel with the influence of blocking
# Compute the maximum channel variability in the simulation, in
# intervals ranging from 1 to 10 TTIs
variability_check = 0
# Visualize where the reflections occur
# (related with how QuaDRiGA works internally. A curisoity really.)
visualize_clusters = 0
# Calculate some statistics about the power on each user
calc_stats = [0, # Power calculations
0] # Path calculations
# ################### Visualization parameters ########################
# Which plots should be shown
visualize_plots = [0, # Room Arrangement (UE and BS disposition)
0, # Track users movement only
0, # Track users movement only (on layout)
0, # Track users movement + virtual users
0, # Track users movement + virtual users + BS
0, # Track all (on layout)
0] # Power plots per receiver (cameras too)
# which plots should be saved (they need to be shown to be saved)
save_plots = np.array(["", # Room Arrangement
"", # Track users movement only
"", # Track users movement only (on layout)
"", # Track users movement + virtual users
"", # Track All
"", # Track All (on Layout)
""], # Power plots per receiver (cameras too)
dtype=np.object)
# which plots should have a gif out of them (need to be shown)
make_gif = [0, # Track users movement only
0, # Track users movement only (on layout)
0, # Track users movement + virtual users
0, # Track users movement + virtual users + BS (all)
0] # Track all on layout
# Plots customised limits
custom_limits = [0, 4, 0, 4, 0, 4]
# if these are enabled, the limits are computed automatically
enable_automatic_limits = 1
# Defines the frame interval of the dynamic plots.
snapshot_interval = 50
# Interval between pauses to analayse plot
pause_interval = 3
# Duration of said pause
pause_duration = 3
# If the points and lines persists in the graph
shade = 1
# If the line showing the orientation should be visible or not
display_orientation = 0
# View perspective of the 3D plots 'normal2', 'xy' and others
view_mode = "normal" # 'up', 'side'
# ################### General parameters ##########################
# Time interval to which channel coefficients are generated
simulation_duration = ut.get_seconds(self.sim_duration) # [s]
# Throw out samples before begin_slack, as means of rejecting the start
begin_slack = 1 # trim the first x-1 samples
# Frequency values
f_values = self.freqs
# For mmWave frequencies, there should appear an warning regarding
# sample density. Fulfilling the sampling theorem is only important if
# we want to interpolate at the maximum speed after creating the trace,
# which does not happen.
# Base rate for numerology 0 - TTI duration at numerology 0 [seconds]
base_update_rate = 1e-3
# The compression ratios for time and frequency compression.
# Each sample in time/frequency corresponds to so many TTI/PRBs
time_compression_ratio = self.time_compression_ratio
freq_compression_ratio = self.freq_compression_ratio
# OFDM numerology, for mmWaves is 2 and 3.
# (and has the same name as global variable.)
numerology = self.numerology
bandwidth = self.bandwidth
n_prb = self.n_prb
# length of phy_user_pos, vir_user_pos
n_room = [self.n_phy, self.n_vir]
n_users = self.n_users # = sum(room)
n_camera_streams = self.n_cameras_per_user # cameras per user
if self.ul_aggregation and n_camera_streams > 1:
n_camera_streams = 1
n_tx = self.n_bs # Number of BSs
tx_height = 3 # [m]
user_height = 1.4 # [m]
cam_height = 1 # [m]
# Either seat the users automatically, or use a customized arrangement
use_standard_arrangement = 1
# Table format: 'quadrangular', 'rectangular' or 'round'/'circular'
table_type = 'round'
# The seat arrangement is only needed for rectangular tables
# For square tables only input one dimension (seats at one side)
# For circular it's not used
seat_arrangement = [1, 1]
# This variable is used for defining the number of seats around a table
# the keyword is 'around' - for 'round' tables
# total_seats = 2 * sum(seat_arrangement)
total_seats = n_users
# Distances that define the placement of 2 cameras per user in the room
# (This conf. has an angle of 30º from the perpendicular
# to the user to each camera)
d_u = 0.6 # distance along perpendicular to the user
d_s = 0.3 # distance from the perpendicular to each side (2 cameras)
phy_user_disposition = self.phy_user_disposition
vir_user_disposition = self.vir_user_disposition
# Custom Arragement
# if the use_standar_arragement is 0, these will be used
phy_usr_pos = np.array([[1, 1, 1],
[1, 2, 3],
[1, 1, 1]], dtype=np.double) # UE positions
vir_usr_pos = np.array([[2, 4],
[4, 3],
[1, 1]], dtype=np.double)
cam_pos = np.array([[1.5, 1.5],
[1.75, 2.25],
[1, 1]], dtype=np.double)
tx_pos = np.array([[3, 6, 6],
[3, 0, 6],
[tx_height] * 3], dtype=np.double)
# Based on the combination of number of TXs and this variable, they
# are placed in different places.
# 0 - no special places, just put them at the 'tx_pos' position
# 1 - 'centre first': places BS at the centre first, and if there's
# more than one, they are placed in the corners
# of the room. Requires 'room_size' variable!
# 2 - 'corners first': places BS at the corners first. If there's more
# than 4, puts at the centre.
# Note: max number of TXs (BSs) to be placed automatically is 5!
# For n_tx > 5, the positions need to be set in tx_pos.
tx_special_pos = 1
# To be used with tx_special_pos to defined the prefered corners
# of the room
select_corners = [1, 2, 3, 4]
# E.g. [1 2 3 4] is the normal order, [3 4 1 2] would favour corner 3,
# then 4, and so on. Corner 1 is the top left corner (0, 0), and the
# rest of the corners go counter-clock wise.
# Room dimensions
room_size = [8, 8]
# The offset from the centre of mass of the head of a user.
# The first value is distance in front, the second value is height.
rx_pos_offset = [0.15, 0, 0.05]
# The angular offset of the array. If a linear array is considered,
# using no offset will place the antenna vertically. 90 will place the
# first element to the right of the head (imagine the rotation axis
# coming out of your nose, and right hand rule for the rotations)
rx_rot_offset = 90
# To decide whether tracks are to be loaded, or created.
load_tracks_from_file = self.load_tracks_from_file
tracks_filename = self.tracks_filename
# Radius of the table, for display purposes and for user placement
# r_table for 4 PHY + 4 VIR = 1
# r_table for 8 PHY + 8 VIR = 1.5
if self.n_phy == self.n_vir == 4:
r_table = 1
if self.n_phy == self.n_vir == 8:
r_table = 1.5
# Comment to stop overriding:
r_table = 1.4
# Additional radius for the users to seat around the table
r_users_dist = 0.2
# ################## MOVEMENT ####################
# Radius of sphere for random head position of the participants
r_head_sphere = 0.2
# Standard deviation of the gaussians of position sampling
# such that 99.7% of points are inside this sphere
sigma_head = r_head_sphere / 3
# Position
# 0- Constant speed across the simulation
# 1- Vary mvnt profiles across the simulation
# speed_profile = 0 # not worth including if it's not implemented
# If all users have the same movement profile
# (position and orientation change speeds)
same_mvnt_for_all_usrs = 1
# Mvnt val for all users (only used if the above variable is set to 1)
const_mvnt_value = speed
# If a custom movement profile is to be set, use the below variable
# Define the movement profiles of the receivers (one per user and cam)
# How quick each receiver moves (Position & Orientation)
rx_mvnt = []
# Moving speed Rotation Interval
# 0 - (static) for cameras
# 1 0.1 1.3
# 2 0.2 1.1
# 3 0.3 0.9 (empirically the most likely)
# 4 0.4 0.7