5G-LENA nr-v3.0-33-g7aea1e4
The 5G/NR module for the ns-3 simulator
Loading...
Searching...
No Matches
hexagonal-grid-scenario-helper.cc
1/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
2
3// Copyright (c) 2020 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
4//
5// SPDX-License-Identifier: GPL-2.0-only
6
7#include "hexagonal-grid-scenario-helper.h"
8
9#include "ns3/constant-velocity-mobility-model.h"
10#include <ns3/double.h>
11#include <ns3/mobility-helper.h>
12
13#include <cmath>
14
15namespace ns3
16{
17
18void
19HexagonalGridScenarioHelper::SetResultsDir(std::string resultsDir)
20{
21 m_resultsDir = resultsDir;
22}
23
24void
25HexagonalGridScenarioHelper::SetSimTag(std::string simTag)
26{
27 m_simTag = simTag;
28}
29
31{
32 m_r = CreateObject<UniformRandomVariable>();
33 m_theta = CreateObject<UniformRandomVariable>();
34}
35
39
40const double distTo2ndRing = std::sqrt(3);
41const double distTo4thRing = std::sqrt(7);
42// Site positions in terms of distance and angle w.r.t. the central site
43std::vector<double> HexagonalGridScenarioHelper::siteDistances{0,
44 1,
45 1,
46 1,
47 1,
48 1,
49 1,
50 distTo2ndRing,
51 distTo2ndRing,
52 distTo2ndRing,
53 distTo2ndRing,
54 distTo2ndRing,
55 distTo2ndRing,
56 2,
57 2,
58 2,
59 2,
60 2,
61 2,
62 distTo4thRing,
63 distTo4thRing,
64 distTo4thRing,
65 distTo4thRing,
66 distTo4thRing,
67 distTo4thRing,
68 distTo4thRing,
69 distTo4thRing,
70 distTo4thRing,
71 distTo4thRing,
72 distTo4thRing,
73 distTo4thRing,
74 3,
75 3,
76 3,
77 3,
78 3,
79 3};
80
81/*
82 * Site angles w.r.t. the central site center.
83 *
84 * Note that the angles in the following vector are when looking the deployment in which hexagons
85 * are oriented in the following way:
86 *
87 * ^ ______
88 * | / \
89 * | ______/ \
90 * | / \ /
91 * y | / \______/
92 * | \ / \
93 * | \______/ \
94 * | \ /
95 * | \______/
96 * ------------------------>
97 * x
98 *
99 * This is important to note because the gnuplot function of this the HexagonalGridScenarioHelper
100 * plots hexagon in different orientation pointing towards top-bottom, e.g.:
101 *
102 * /\
103 * / \
104 * | |
105 * | |
106 * \ /
107 * \/
108 *
109 */
110
111// the angle of the first hexagon of the fourth ring in the first quadrant
112const double ang4thRingAlpha1 = atan2(1, (3 * sqrt(3))) * (180 / M_PI);
113// the angle of the second hexagon of the fourth ring in the first quadrant
114const double ang4thRingAlpha2 = 90 - atan2(sqrt(3), 2) * (180 / M_PI);
115// the angle of the third hexagon of the fourth ring in the first quadrant
116const double ang4thRingAlpha3 = 90 - atan2(3, (5 * sqrt(3))) * (180 / M_PI);
117
118std::vector<double> HexagonalGridScenarioHelper::siteAngles{
119 0, // 0 ring
120 30,
121 90,
122 150,
123 210,
124 270,
125 330, // 1. ring
126 0,
127 60,
128 120,
129 180,
130 240,
131 300, // 2. ring
132 30,
133 90,
134 150,
135 210,
136 270,
137 330, // 3. ring
138 ang4thRingAlpha1,
139 ang4thRingAlpha2,
140 ang4thRingAlpha3, // 4. ring 1. quadrant
141 180 - ang4thRingAlpha3,
142 180 - ang4thRingAlpha2,
143 180 - ang4thRingAlpha1, // 4. ring 2. quadrant
144 180 + ang4thRingAlpha1,
145 180 + ang4thRingAlpha2,
146 180 + ang4thRingAlpha3, // 4. ring 3. quadrant
147 -ang4thRingAlpha3,
148 -ang4thRingAlpha2,
149 -ang4thRingAlpha1, // 4. ring 4. quadrant
150 30,
151 90,
152 150,
153 210,
154 270,
155 330 // 5. ring
156};
157
168static void
169PlotHexagonalDeployment(const Ptr<const ListPositionAllocator>& sitePosVector,
170 const Ptr<const ListPositionAllocator>& cellCenterVector,
171 const Ptr<const ListPositionAllocator>& utPosVector,
172 double cellRadius,
173 std::string resultsDir,
174 std::string simTag)
175{
176 uint16_t numCells = cellCenterVector->GetSize();
177 uint16_t numSites = sitePosVector->GetSize();
178 uint16_t numSectors = numCells / numSites;
179 uint16_t numUts = utPosVector->GetSize();
180 NS_ASSERT_MSG(numCells > 0, "no cells");
181 NS_ASSERT_MSG(numSites > 0, "no sites");
182 NS_ASSERT_MSG(numUts > 0, "no uts");
183
184 // Try to open a new GNUPLOT file
185 std::ofstream topologyOutfile;
186 std::string topologyFileRoot = resultsDir + "./hexagonal-topology";
187 std::string topologyFileName = topologyFileRoot + simTag + ".gnuplot";
188 topologyOutfile.open(topologyFileName.c_str(), std::ios_base::out | std::ios_base::trunc);
189 if (!topologyOutfile.is_open())
190 {
191 NS_ABORT_MSG("Can't open " << topologyFileName);
192 }
193
194 topologyOutfile << "set term pdf" << std::endl;
195 topologyOutfile << "set output \"" << topologyFileName << ".pdf\"" << std::endl;
196 topologyOutfile << "set style arrow 1 lc \"black\" lt 1 head filled" << std::endl;
197 // topologyOutfile << "set autoscale" << std::endl;
198
199 uint16_t margin =
200 (12 * cellRadius) + 1;
201 topologyOutfile << "set xrange [-" << margin << ":" << margin << "]" << std::endl;
202 topologyOutfile << "set yrange [-" << margin << ":" << margin << "]" << std::endl;
203 // FIXME: Need to recalculate ranges if the scenario origin is different to (0,0)
204
205 double arrowLength =
206 cellRadius /
207 4.0; //<! Control the arrow length that indicates the orientation of the sectorized antenna
208 std::vector<double> hx{0.0, -0.5, -0.5, 0.0, 0.5, 0.5, 0.0}; //<! Hexagon vertices in x-axis
209 std::vector<double> hy{-1.0, -0.5, 0.5, 1.0, 0.5, -0.5, -1.0}; //<! Hexagon vertices in y-axis
210 Vector sitePos;
211
212 for (uint16_t cellId = 0; cellId < numCells; ++cellId)
213 {
214 Vector cellPos = cellCenterVector->GetNext();
215 double angleDeg = 30 + 120 * (cellId % 3);
216 double angleRad = angleDeg * M_PI / 180;
217 double x;
218 double y;
219
220 if (cellId % numSectors == 0)
221 {
222 sitePos = sitePosVector->GetNext();
223 }
224 topologyOutfile << "set arrow " << cellId + 1 << " from " << sitePos.x << "," << sitePos.y
225 << " rto " << arrowLength * std::cos(angleRad) << ","
226 << arrowLength * std::sin(angleRad) << " arrowstyle 1 \n";
227
228 // Draw the hexagon arond the cell center
229 topologyOutfile << "set object " << cellId + 1 << " polygon from \\\n";
230
231 for (uint16_t vertexId = 0; vertexId <= 6; ++vertexId)
232 {
233 // angle of the vertex w.r.t. y-axis
234 x = cellRadius * std::sqrt(3.0) * hx.at(vertexId) + cellPos.x;
235 y = cellRadius * hy.at(vertexId) + cellPos.y;
236 topologyOutfile << x << ", " << y;
237 if (vertexId == 6)
238 {
239 topologyOutfile << " front fs empty \n";
240 }
241 else
242 {
243 topologyOutfile << " to \\\n";
244 }
245 }
246
247 topologyOutfile << "set label " << cellId + 1 << " \"" << (cellId + 1) << "\" at "
248 << cellPos.x << " , " << cellPos.y << " center" << std::endl;
249 }
250
251 for (uint16_t utId = 0; utId < numUts; ++utId)
252 {
253 Vector utPos = utPosVector->GetNext();
254 // set label at xPos, yPos, zPos "" point pointtype 7 pointsize 2
255 topologyOutfile << "set label at " << utPos.x << " , " << utPos.y
256 << " point pointtype 7 pointsize 0.2 center" << std::endl;
257 }
258
259 topologyOutfile << "unset key" << std::endl;
260 topologyOutfile << "plot 1/0" << std::endl;
261}
262
263static Vector
264GetClosestSitePosition(Vector cellCenterPos, const Ptr<ListPositionAllocator>& sitePosVector)
265{
266 double minDistance = UINT32_MAX;
267 Vector closestSitePosition;
268
269 for (uint32_t i = 0; i < sitePosVector->GetSize(); i++)
270 {
271 Vector sitePos = sitePosVector->GetNext();
272 double d_x = sitePos.x - cellCenterPos.x;
273 double d_y = sitePos.y - cellCenterPos.y;
274 double distance2D = sqrt(d_x * d_x + d_y * d_y);
275 if (distance2D < minDistance)
276 {
277 minDistance = distance2D;
278 closestSitePosition = sitePos;
279 }
280 }
281
282 NS_ABORT_MSG_IF(minDistance == UINT32_MAX,
283 "Get closest site position function not executed properly.");
284 return closestSitePosition;
285}
286
287void
289{
290 NS_ABORT_MSG_IF(numRings > 5, "Unsupported number of outer rings (Maximum is 5");
291
292 m_numRings = numRings;
293
294 /*
295 * 0 rings = 1 + 6 * 0 = 1 site
296 * 1 rings = 1 + 6 * 1 = 7 sites
297 * 2 rings = 1 + 6 * 2 = 13 sites
298 * 3 rings = 1 + 6 * 3 = 19 site5
299 * 4 rings = 1 + 6 * 5 = 31 sites
300 * 5 rings = 1 + 6 * 6 = 37 sites
301 */
302 switch (numRings)
303 {
304 case 0:
305 m_numSites = 1;
306 break;
307 case 1:
308 m_numSites = 7;
309 break;
310 case 2:
311 m_numSites = 13;
312 break;
313 case 3:
314 m_numSites = 19;
315 break;
316 case 4:
317 m_numSites = 31;
318 break;
319 case 5:
320 m_numSites = 37;
321 break;
322 }
324}
325
326double
328{
329 return m_hexagonalRadius;
330}
331
332Vector
333HexagonalGridScenarioHelper::GetHexagonalCellCenter(const Vector& sitePos, uint16_t cellId) const
334{
335 Vector center(sitePos);
336
337 auto sectors = GetNumSectorsPerSite();
338 switch (sectors)
339 {
340 case 0:
341 NS_ABORT_MSG("Number of sectors has not been defined");
342 break;
343
344 case 1:
345 break;
346
347 case 3:
348 switch (GetSectorIndex(cellId))
349 {
350 case 0:
351 center.x += m_hexagonalRadius * std::sqrt(0.75);
352 center.y += m_hexagonalRadius / 2;
353 break;
354
355 case 1:
356 center.x -= m_hexagonalRadius * std::sqrt(0.75);
357 center.y += m_hexagonalRadius / 2;
358 break;
359
360 case 2:
361 center.y -= m_hexagonalRadius;
362 break;
363
364 default:
365 NS_ABORT_MSG("Unknown sector number: " << GetSectorIndex(cellId));
366 }
367 break;
368
369 default:
370 NS_ABORT_MSG("Unsupported number of sectors");
371 break;
372 }
373
374 return center;
375}
376
377void
379{
380 m_hexagonalRadius = m_isd / 3;
381
382 m_bs.Create(m_numBs);
383 m_ut.Create(m_numUt);
384
385 NS_ASSERT(m_isd > 0);
386 NS_ASSERT(m_numRings < 6);
387 NS_ASSERT(m_hexagonalRadius > 0);
388 NS_ASSERT(m_bsHeight >= 0.0);
389 NS_ASSERT(m_utHeight >= 0.0);
390 NS_ASSERT(m_bs.GetN() > 0);
391 NS_ASSERT(m_ut.GetN() > 0);
392
393 MobilityHelper mobility;
394 Ptr<ListPositionAllocator> bsPosVector = CreateObject<ListPositionAllocator>();
395 Ptr<ListPositionAllocator> bsCenterVector = CreateObject<ListPositionAllocator>();
396 Ptr<ListPositionAllocator> sitePosVector = CreateObject<ListPositionAllocator>();
397 Ptr<ListPositionAllocator> utPosVector = CreateObject<ListPositionAllocator>();
398
399 // BS position
400 for (std::size_t cellId = 0; cellId < m_numBs; cellId++)
401 {
402 uint16_t siteIndex = GetSiteIndex(cellId);
403 Vector sitePos(m_centralPos);
404 const double dist = siteDistances.at(siteIndex);
405 const double angleRad = siteAngles.at(siteIndex) * M_PI / 180;
406 sitePos.x += m_isd * dist * cos(angleRad);
407 sitePos.y += m_isd * dist * sin(angleRad);
408 sitePos.z = m_bsHeight;
409
410 if (GetSectorIndex(cellId) == 0)
411 {
412 sitePosVector->Add(sitePos);
413 }
414
415 // FIXME: Until sites can have more than one antenna array, it is necessary to apply some
416 // distance offset from the site center (gNBs cannot have the same location)
417 Vector bsPos = GetAntennaPosition(sitePos, cellId);
418
419 bsPosVector->Add(bsPos);
420
421 // Store cell center position for plotting the deployment
422 Vector cellCenterPos = GetHexagonalCellCenter(bsPos, cellId);
423 bsCenterVector->Add(cellCenterPos);
424
425 // What about the antenna orientation? It should be dealt with when installing the gNB
426 }
427
428 // To allocate UEs, I need the center of the hexagonal cell.
429 // Allocate UE around the disk of radius isd/3, the diameter of a the
430 // hexagon representing the footprint of a single sector.
431 // Reduce this radius by the min BS-UT distance, to respect that standoff
432 // at the one corner of the sector hexagon where the sector antenna lies.
433 // This results in UTs uniformly distributed in a disc centered on
434 // the sector hexagon; there are no UTs near the vertices of the hexagon.
435 // Spread UEs inside the inner hexagonal radius
436 // Need to weight r to get uniform in the sector hexagon
437 // See https://stackoverflow.com/questions/5837572
438 // Set max = radius^2 here, then take sqrt below
439 const double outerR = (std::sqrt(3) / 2) * m_hexagonalRadius - m_minBsUtDistance;
440 m_r->SetAttribute("Min", DoubleValue(0));
441 m_r->SetAttribute("Max", DoubleValue(outerR * outerR));
442 m_theta->SetAttribute("Min", DoubleValue(-1.0 * M_PI));
443 m_theta->SetAttribute("Max", DoubleValue(M_PI));
444
445 // UT position
446
447 for (uint32_t utId = 0; utId < m_ut.GetN(); ++utId)
448 {
449 double d = std::sqrt(m_r->GetValue());
450 double t = m_theta->GetValue();
451
452 // Vector utPos (cellCenterPos);
453 Vector utPos(bsCenterVector->GetNext());
454 utPos.x += d * cos(t);
455 utPos.y += d * sin(t);
456 utPos.z = m_utHeight;
457
458 utPosVector->Add(utPos);
459 }
460
461 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
462 mobility.SetPositionAllocator(bsPosVector);
463 mobility.Install(m_bs);
464
465 mobility.SetPositionAllocator(utPosVector);
466 mobility.Install(m_ut);
467
468 PlotHexagonalDeployment(sitePosVector,
469 bsCenterVector,
470 utPosVector,
471 m_hexagonalRadius,
472 m_resultsDir,
473 m_simTag);
474}
475
476void
477HexagonalGridScenarioHelper::CreateScenarioWithMobility(const Vector& speed, double percentage)
478{
479 m_hexagonalRadius = m_isd / 3;
480
481 m_bs.Create(m_numBs);
482 m_ut.Create(m_numUt);
483
484 NS_ASSERT(m_isd > 0);
485 NS_ASSERT(m_numRings < 6);
486 NS_ASSERT(m_hexagonalRadius > 0);
487 NS_ASSERT(m_bsHeight >= 0.0);
488 NS_ASSERT(m_utHeight >= 0.0);
489 NS_ASSERT(m_bs.GetN() > 0);
490 NS_ASSERT(m_ut.GetN() > 0);
491 NS_ASSERT_MSG(percentage >= 0 || percentage <= 1,
492 "Percentage must between 0"
493 " and 1");
494
495 MobilityHelper mobility;
496 MobilityHelper ueMobility;
497 Ptr<ListPositionAllocator> bsPosVector = CreateObject<ListPositionAllocator>();
498 Ptr<ListPositionAllocator> bsCenterVector = CreateObject<ListPositionAllocator>();
499 Ptr<ListPositionAllocator> sitePosVector = CreateObject<ListPositionAllocator>();
500 Ptr<ListPositionAllocator> utPosVector = CreateObject<ListPositionAllocator>();
501
502 // BS position
503 for (std::size_t cellId = 0; cellId < m_numBs; cellId++)
504 {
505 uint16_t siteIndex = GetSiteIndex(cellId);
506 Vector sitePos(m_centralPos);
507 const double dist = siteDistances.at(siteIndex);
508 const double angleRad = siteAngles.at(siteIndex) * M_PI / 180;
509 sitePos.x += m_isd * dist * cos(angleRad);
510 sitePos.y += m_isd * dist * sin(angleRad);
511 sitePos.z = m_bsHeight;
512
513 if (GetSectorIndex(cellId) == 0)
514 {
515 sitePosVector->Add(sitePos);
516 }
517
518 // FIXME: Until sites can have more than one antenna array, it is necessary to apply some
519 // distance offset from the site center (gNBs cannot have the same location)
520 Vector bsPos = GetAntennaPosition(sitePos, cellId);
521
522 bsPosVector->Add(bsPos);
523
524 // Store cell center position for plotting the deployment
525 Vector cellCenterPos = GetHexagonalCellCenter(bsPos, cellId);
526 bsCenterVector->Add(cellCenterPos);
527
528 // What about the antenna orientation? It should be dealt with when installing the gNB
529 }
530
531 // To allocate UEs, I need the center of the hexagonal cell.
532 // Allocate UE around the disk of radius isd/3, the diameter of a the
533 // hexagon representing the footprint of a single sector.
534 // Reduce this radius by the min BS-UT distance, to respect that standoff
535 // at the one corner of the sector hexagon where the sector antenna lies.
536 // This results in UTs uniformly distributed in a disc centered on
537 // the sector hexagon; there are no UTs near the vertices of the hexagon.
538 // Spread UEs inside the inner hexagonal radius
539 // Need to weight r to get uniform in the sector hexagon
540 // See https://stackoverflow.com/questions/5837572
541 // Set max = radius^2 here, then take sqrt below
542 const double outerR = m_hexagonalRadius * std::sqrt(3) / 2 - m_minBsUtDistance;
543 m_r->SetAttribute("Min", DoubleValue(0));
544 m_r->SetAttribute("Max", DoubleValue(outerR * outerR));
545 m_theta->SetAttribute("Min", DoubleValue(-1.0 * M_PI));
546 m_theta->SetAttribute("Max", DoubleValue(M_PI));
547
548 // UT position
549
550 uint32_t numUesWithRandomUtHeight = 0;
551 if (percentage != 0)
552 {
553 numUesWithRandomUtHeight = percentage * m_ut.GetN();
554 }
555
556 for (uint32_t utId = 0; utId < m_ut.GetN(); ++utId)
557 {
558 Vector cellCenterPos = bsCenterVector->GetNext();
559 Vector utPos;
560
561 Vector closestSitePosition = GetClosestSitePosition(cellCenterPos, sitePosVector);
562
563 double distance2DToClosestSite = 0;
564
565 // We do not want to take into account the positions that are in the part of the
566 // disk that is far away from the closest site.
567 // To determine whether the position is far away we use
568 // parameter max distance to closest site.
569 uint16_t sanityCounter = 0;
570 do
571 {
572 NS_ABORT_MSG_IF(sanityCounter++ > 1000,
573 "Algorithm needs too many trials to find correct UE position. Please "
574 "check parameters.");
575 double d = std::sqrt(m_r->GetValue());
576 double t = m_theta->GetValue();
577 utPos = cellCenterPos;
578 utPos.x += d * cos(t);
579 utPos.y += d * sin(t);
580 double d_x = utPos.x - closestSitePosition.x;
581 double d_y = utPos.y - closestSitePosition.y;
582 distance2DToClosestSite = sqrt(d_x * d_x + d_y * d_y);
583 } while (distance2DToClosestSite > m_maxUeDistanceToClosestSite);
584
585 if (numUesWithRandomUtHeight > 0)
586 {
587 Ptr<UniformRandomVariable> uniformRandomVariable =
588 CreateObject<UniformRandomVariable>();
589 double Nfl = uniformRandomVariable->GetValue(4, 8);
590 double nfl = uniformRandomVariable->GetValue(1, Nfl);
591
592 if (m_bsHeight == 10)
593 {
594 utPos.z = std::min(3 * (nfl - 1) + 1.5, 9.99);
595 }
596 else
597 {
598 utPos.z = 3 * (nfl - 1) + 1.5;
599 }
600
601 numUesWithRandomUtHeight--;
602 }
603 else
604 {
605 utPos.z = m_utHeight;
606 }
607
608 utPosVector->Add(utPos);
609 }
610
611 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
612 mobility.SetPositionAllocator(bsPosVector);
613 mobility.Install(m_bs);
614
615 if (speed.GetLength())
616 {
617 ueMobility.SetMobilityModel("ns3::ConstantVelocityMobilityModel");
618 ueMobility.SetPositionAllocator(utPosVector);
619 ueMobility.Install(m_ut);
620
621 for (uint32_t i = 0; i < m_ut.GetN(); i++)
622 {
623 m_ut.Get(i)->GetObject<ConstantVelocityMobilityModel>()->SetVelocity(speed);
624 }
625 }
626 else
627 {
628 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
629 mobility.SetPositionAllocator(utPosVector);
630 mobility.Install(m_ut);
631 }
632
633 PlotHexagonalDeployment(sitePosVector,
634 bsCenterVector,
635 utPosVector,
636 m_hexagonalRadius,
637 m_resultsDir,
638 m_simTag);
639}
640
641int64_t
643{
644 m_r->SetStream(stream);
645 m_theta->SetStream(stream + 1);
646 return 2;
647}
648
649void
650HexagonalGridScenarioHelper::SetMaxUeDistanceToClosestSite(double maxUeDistanceToClosestSite)
651{
652 NS_ASSERT(maxUeDistanceToClosestSite > 0 + (m_minBsUtDistance > 0) ? m_minBsUtDistance : 0);
653 m_maxUeDistanceToClosestSite = maxUeDistanceToClosestSite;
654}
655
656} // namespace ns3
Vector GetHexagonalCellCenter(const Vector &sitePos, uint16_t cellId) const
Returns the cell center coordinates.
void CreateScenario() override
Create the scenario, with the configured parameter.
~HexagonalGridScenarioHelper() override
~HexagonalGridScenarioHelper
double GetHexagonalCellRadius() const
Gets the radius of the hexagonal cell.
void SetNumRings(uint8_t numRings)
Sets the number of outer rings of sites around the central site.
void CreateScenarioWithMobility(const Vector &speed, double percentage)
This function can be used to create a scenario with UEs with mobility and define a percentage of UEs,...
HexagonalGridScenarioHelper()
HexagonalGridScenarioHelper.
std::size_t m_numBs
Number of base stations to create.
uint16_t GetSiteIndex(std::size_t cellId) const
Gets the site index the queried cell id belongs to.
Vector GetAntennaPosition(const Vector &sitePos, uint16_t cellId) const
Returns the position of the cell antenna.
std::size_t m_numUt
Number of user terminals to create.
std::size_t m_numSites
Number of sites with base stations.
void SetSitesNumber(std::size_t n)
Set number of sites/towers.
uint16_t GetSectorIndex(std::size_t cellId) const
Get the sector index the queried cell id belongs to.
double m_isd
Inter-site distance (ISD) in meters.
uint32_t GetNumSectorsPerSite() const
Gets the number of sectors per site.
double m_bsHeight
Height of gNB nodes.
double m_minBsUtDistance
Minimum distance between BS and UT in meters.
double m_utHeight
Height of UE nodes.