A Discrete-Event Network Simulator
API
three-gpp-channel-model.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2019 SIGNET Lab, Department of Information Engineering,
3  * University of Padova
4  * Copyright (c) 2015, NYU WIRELESS, Tandon School of Engineering,
5  * New York University
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation;
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  *
20  */
21 
23 
24 #include "ns3/double.h"
25 #include "ns3/integer.h"
26 #include "ns3/log.h"
27 #include "ns3/mobility-model.h"
28 #include "ns3/node.h"
29 #include "ns3/phased-array-model.h"
30 #include "ns3/pointer.h"
31 #include "ns3/string.h"
32 #include <ns3/simulator.h>
33 
34 #include <algorithm>
35 #include <map>
36 #include <random>
37 
38 namespace ns3
39 {
40 
41 NS_LOG_COMPONENT_DEFINE("ThreeGppChannelModel");
42 
43 NS_OBJECT_ENSURE_REGISTERED(ThreeGppChannelModel);
44 
47 static const double offSetAlpha[20] = {
48  0.0447, -0.0447, 0.1413, -0.1413, 0.2492, -0.2492, 0.3715, -0.3715, 0.5129, -0.5129,
49  0.6797, -0.6797, 0.8844, -0.8844, 1.1481, -1.1481, 1.5195, -1.5195, 2.1551, -2.1551,
50 };
51 
60 static const double sqrtC_RMa_LOS[7][7] = {
61  {1, 0, 0, 0, 0, 0, 0},
62  {0, 1, 0, 0, 0, 0, 0},
63  {-0.5, 0, 0.866025, 0, 0, 0, 0},
64  {0, 0, 0, 1, 0, 0, 0},
65  {0, 0, 0, 0, 1, 0, 0},
66  {0.01, 0, -0.0519615, 0.73, -0.2, 0.651383, 0},
67  {-0.17, -0.02, 0.21362, -0.14, 0.24, 0.142773, 0.909661},
68 };
69 
79 static const double sqrtC_RMa_NLOS[6][6] = {
80  {1, 0, 0, 0, 0, 0},
81  {-0.5, 0.866025, 0, 0, 0, 0},
82  {0.6, -0.11547, 0.791623, 0, 0, 0},
83  {0, 0, 0, 1, 0, 0},
84  {-0.04, -0.138564, 0.540662, -0.18, 0.809003, 0},
85  {-0.25, -0.606218, -0.240013, 0.26, -0.231685, 0.625392},
86 };
87 
96 static const double sqrtC_RMa_O2I[6][6] = {
97  {1, 0, 0, 0, 0, 0},
98  {0, 1, 0, 0, 0, 0},
99  {0, 0, 1, 0, 0, 0},
100  {0, 0, -0.7, 0.714143, 0, 0},
101  {0, 0, 0.66, -0.123225, 0.741091, 0},
102  {0, 0, 0.47, 0.152631, -0.393194, 0.775373},
103 };
104 
113 static const double sqrtC_UMa_LOS[7][7] = {
114  {1, 0, 0, 0, 0, 0, 0},
115  {0, 1, 0, 0, 0, 0, 0},
116  {-0.4, -0.4, 0.824621, 0, 0, 0, 0},
117  {-0.5, 0, 0.242536, 0.83137, 0, 0, 0},
118  {-0.5, -0.2, 0.630593, -0.484671, 0.278293, 0, 0},
119  {0, 0, -0.242536, 0.672172, 0.642214, 0.27735, 0},
120  {-0.8, 0, -0.388057, -0.367926, 0.238537, -3.58949e-15, 0.130931},
121 };
122 
132 static const double sqrtC_UMa_NLOS[6][6] = {
133  {1, 0, 0, 0, 0, 0},
134  {-0.4, 0.916515, 0, 0, 0, 0},
135  {-0.6, 0.174574, 0.78072, 0, 0, 0},
136  {0, 0.654654, 0.365963, 0.661438, 0, 0},
137  {0, -0.545545, 0.762422, 0.118114, 0.327327, 0},
138  {-0.4, -0.174574, -0.396459, 0.392138, 0.49099, 0.507445},
139 };
140 
149 static const double sqrtC_UMa_O2I[6][6] = {
150  {1, 0, 0, 0, 0, 0},
151  {-0.5, 0.866025, 0, 0, 0, 0},
152  {0.2, 0.57735, 0.791623, 0, 0, 0},
153  {0, 0.46188, -0.336861, 0.820482, 0, 0},
154  {0, -0.69282, 0.252646, 0.493742, 0.460857, 0},
155  {0, -0.23094, 0.16843, 0.808554, -0.220827, 0.464515},
156 
157 };
158 
167 static const double sqrtC_UMi_LOS[7][7] = {
168  {1, 0, 0, 0, 0, 0, 0},
169  {0.5, 0.866025, 0, 0, 0, 0, 0},
170  {-0.4, -0.57735, 0.711805, 0, 0, 0, 0},
171  {-0.5, 0.057735, 0.468293, 0.726201, 0, 0, 0},
172  {-0.4, -0.11547, 0.805464, -0.23482, 0.350363, 0, 0},
173  {0, 0, 0, 0.688514, 0.461454, 0.559471, 0},
174  {0, 0, 0.280976, 0.231921, -0.490509, 0.11916, 0.782603},
175 };
176 
186 static const double sqrtC_UMi_NLOS[6][6] = {
187  {1, 0, 0, 0, 0, 0},
188  {-0.7, 0.714143, 0, 0, 0, 0},
189  {0, 0, 1, 0, 0, 0},
190  {-0.4, 0.168034, 0, 0.90098, 0, 0},
191  {0, -0.70014, 0.5, 0.130577, 0.4927, 0},
192  {0, 0, 0.5, 0.221981, -0.566238, 0.616522},
193 };
194 
203 static const double sqrtC_UMi_O2I[6][6] = {
204  {1, 0, 0, 0, 0, 0},
205  {-0.5, 0.866025, 0, 0, 0, 0},
206  {0.2, 0.57735, 0.791623, 0, 0, 0},
207  {0, 0.46188, -0.336861, 0.820482, 0, 0},
208  {0, -0.69282, 0.252646, 0.493742, 0.460857, 0},
209  {0, -0.23094, 0.16843, 0.808554, -0.220827, 0.464515},
210 };
211 
220 static const double sqrtC_office_LOS[7][7] = {
221  {1, 0, 0, 0, 0, 0, 0},
222  {0.5, 0.866025, 0, 0, 0, 0, 0},
223  {-0.8, -0.11547, 0.588784, 0, 0, 0, 0},
224  {-0.4, 0.23094, 0.520847, 0.717903, 0, 0, 0},
225  {-0.5, 0.288675, 0.73598, -0.348236, 0.0610847, 0, 0},
226  {0.2, -0.11547, 0.418943, 0.541106, 0.219905, 0.655744, 0},
227  {0.3, -0.057735, 0.73598, -0.348236, 0.0610847, -0.304997, 0.383375},
228 };
229 
239 static const double sqrtC_office_NLOS[6][6] = {
240  {1, 0, 0, 0, 0, 0},
241  {-0.5, 0.866025, 0, 0, 0, 0},
242  {0, 0.46188, 0.886942, 0, 0, 0},
243  {-0.4, -0.23094, 0.120263, 0.878751, 0, 0},
244  {0, -0.311769, 0.55697, -0.249198, 0.728344, 0},
245  {0, -0.069282, 0.295397, 0.430696, 0.468462, 0.709214},
246 };
247 
249 {
250  NS_LOG_FUNCTION(this);
251  m_uniformRv = CreateObject<UniformRandomVariable>();
252  m_uniformRvShuffle = CreateObject<UniformRandomVariable>();
253  m_uniformRvDoppler = CreateObject<UniformRandomVariable>();
254 
255  m_normalRv = CreateObject<NormalRandomVariable>();
256  m_normalRv->SetAttribute("Mean", DoubleValue(0.0));
257  m_normalRv->SetAttribute("Variance", DoubleValue(1.0));
258 }
259 
261 {
262  NS_LOG_FUNCTION(this);
263 }
264 
265 void
267 {
268  NS_LOG_FUNCTION(this);
270  {
271  m_channelConditionModel->Dispose();
272  }
273  m_channelMatrixMap.clear();
274  m_channelParamsMap.clear();
275  m_channelConditionModel = nullptr;
276 }
277 
278 TypeId
280 {
281  static TypeId tid =
282  TypeId("ns3::ThreeGppChannelModel")
283  .SetGroupName("Spectrum")
285  .AddConstructor<ThreeGppChannelModel>()
286  .AddAttribute("Frequency",
287  "The operating Frequency in Hz",
288  DoubleValue(500.0e6),
291  MakeDoubleChecker<double>())
292  .AddAttribute(
293  "Scenario",
294  "The 3GPP scenario (RMa, UMa, UMi-StreetCanyon, InH-OfficeOpen, InH-OfficeMixed)",
295  StringValue("UMa"),
299  .AddAttribute("ChannelConditionModel",
300  "Pointer to the channel condition model",
301  PointerValue(),
304  MakePointerChecker<ChannelConditionModel>())
305  .AddAttribute("UpdatePeriod",
306  "Specify the channel coherence time",
309  MakeTimeChecker())
310  // attributes for the blockage model
311  .AddAttribute("Blockage",
312  "Enable blockage model A (sec 7.6.4.1)",
313  BooleanValue(false),
316  .AddAttribute("NumNonselfBlocking",
317  "number of non-self-blocking regions",
318  IntegerValue(4),
320  MakeIntegerChecker<uint16_t>())
321  .AddAttribute("PortraitMode",
322  "true for portrait mode, false for landscape mode",
323  BooleanValue(true),
326  .AddAttribute("BlockerSpeed",
327  "The speed of moving blockers, the unit is m/s",
328  DoubleValue(1),
330  MakeDoubleChecker<double>())
331  .AddAttribute("vScatt",
332  "Maximum speed of the vehicle in the layout (see 3GPP TR 37.885 v15.3.0, "
333  "Sec. 6.2.3)."
334  "Used to compute the additional contribution for the Doppler of"
335  "delayed (reflected) paths",
336  DoubleValue(0.0),
338  MakeDoubleChecker<double>(0.0))
339 
340  ;
341  return tid;
342 }
343 
344 void
346 {
347  NS_LOG_FUNCTION(this);
348  m_channelConditionModel = model;
349 }
350 
353 {
354  NS_LOG_FUNCTION(this);
356 }
357 
358 void
360 {
361  NS_LOG_FUNCTION(this);
362  NS_ASSERT_MSG(f >= 500.0e6 && f <= 100.0e9,
363  "Frequency should be between 0.5 and 100 GHz but is " << f);
364  m_frequency = f;
365 }
366 
367 double
369 {
370  NS_LOG_FUNCTION(this);
371  return m_frequency;
372 }
373 
374 void
375 ThreeGppChannelModel::SetScenario(const std::string& scenario)
376 {
377  NS_LOG_FUNCTION(this);
378  NS_ASSERT_MSG(scenario == "RMa" || scenario == "UMa" || scenario == "UMi-StreetCanyon" ||
379  scenario == "InH-OfficeOpen" || scenario == "InH-OfficeMixed" ||
380  scenario == "V2V-Urban" || scenario == "V2V-Highway",
381  "Unknown scenario, choose between: RMa, UMa, UMi-StreetCanyon, "
382  "InH-OfficeOpen, InH-OfficeMixed, V2V-Urban or V2V-Highway");
383  m_scenario = scenario;
384 }
385 
386 std::string
388 {
389  NS_LOG_FUNCTION(this);
390  return m_scenario;
391 }
392 
395  double hBS,
396  double hUT,
397  double distance2D) const
398 {
399  NS_LOG_FUNCTION(this);
400 
401  double fcGHz = m_frequency / 1.0e9;
402  Ptr<ParamsTable> table3gpp = Create<ParamsTable>();
403  // table3gpp includes the following parameters:
404  // numOfCluster, raysPerCluster, uLgDS, sigLgDS, uLgASD, sigLgASD,
405  // uLgASA, sigLgASA, uLgZSA, sigLgZSA, uLgZSD, sigLgZSD, offsetZOD,
406  // cDS, cASD, cASA, cZSA, uK, sigK, rTau, uXpr, sigXpr, shadowingStd
407 
408  bool los = channelCondition->IsLos();
409  bool o2i = channelCondition->IsO2i();
410 
411  // In NLOS case, parameter uK and sigK are not used and they are set to 0
412  if (m_scenario == "RMa")
413  {
414  if (los && !o2i)
415  {
416  // 3GPP mentioned that 3.91 ns should be used when the Cluster DS (cDS)
417  // entry is N/A.
418  table3gpp->m_numOfCluster = 11;
419  table3gpp->m_raysPerCluster = 20;
420  table3gpp->m_uLgDS = -7.49;
421  table3gpp->m_sigLgDS = 0.55;
422  table3gpp->m_uLgASD = 0.90;
423  table3gpp->m_sigLgASD = 0.38;
424  table3gpp->m_uLgASA = 1.52;
425  table3gpp->m_sigLgASA = 0.24;
426  table3gpp->m_uLgZSA = 0.47;
427  table3gpp->m_sigLgZSA = 0.40;
428  table3gpp->m_uLgZSD = 0.34;
429  table3gpp->m_sigLgZSD =
430  std::max(-1.0, -0.17 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.22);
431  table3gpp->m_offsetZOD = 0;
432  table3gpp->m_cDS = 3.91e-9;
433  table3gpp->m_cASD = 2;
434  table3gpp->m_cASA = 3;
435  table3gpp->m_cZSA = 3;
436  table3gpp->m_uK = 7;
437  table3gpp->m_sigK = 4;
438  table3gpp->m_rTau = 3.8;
439  table3gpp->m_uXpr = 12;
440  table3gpp->m_sigXpr = 4;
441  table3gpp->m_perClusterShadowingStd = 3;
442 
443  for (uint8_t row = 0; row < 7; row++)
444  {
445  for (uint8_t column = 0; column < 7; column++)
446  {
447  table3gpp->m_sqrtC[row][column] = sqrtC_RMa_LOS[row][column];
448  }
449  }
450  }
451  else if (!los && !o2i)
452  {
453  table3gpp->m_numOfCluster = 10;
454  table3gpp->m_raysPerCluster = 20;
455  table3gpp->m_uLgDS = -7.43;
456  table3gpp->m_sigLgDS = 0.48;
457  table3gpp->m_uLgASD = 0.95;
458  table3gpp->m_sigLgASD = 0.45;
459  table3gpp->m_uLgASA = 1.52;
460  table3gpp->m_sigLgASA = 0.13;
461  table3gpp->m_uLgZSA = 0.58;
462  table3gpp->m_sigLgZSA = 0.37;
463  table3gpp->m_uLgZSD =
464  std::max(-1.0, -0.19 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.28);
465  table3gpp->m_sigLgZSD = 0.30;
466  table3gpp->m_offsetZOD = atan((35 - 3.5) / distance2D) - atan((35 - 1.5) / distance2D);
467  table3gpp->m_cDS = 3.91e-9;
468  table3gpp->m_cASD = 2;
469  table3gpp->m_cASA = 3;
470  table3gpp->m_cZSA = 3;
471  table3gpp->m_uK = 0;
472  table3gpp->m_sigK = 0;
473  table3gpp->m_rTau = 1.7;
474  table3gpp->m_uXpr = 7;
475  table3gpp->m_sigXpr = 3;
476  table3gpp->m_perClusterShadowingStd = 3;
477 
478  for (uint8_t row = 0; row < 6; row++)
479  {
480  for (uint8_t column = 0; column < 6; column++)
481  {
482  table3gpp->m_sqrtC[row][column] = sqrtC_RMa_NLOS[row][column];
483  }
484  }
485  }
486  else // o2i
487  {
488  table3gpp->m_numOfCluster = 10;
489  table3gpp->m_raysPerCluster = 20;
490  table3gpp->m_uLgDS = -7.47;
491  table3gpp->m_sigLgDS = 0.24;
492  table3gpp->m_uLgASD = 0.67;
493  table3gpp->m_sigLgASD = 0.18;
494  table3gpp->m_uLgASA = 1.66;
495  table3gpp->m_sigLgASA = 0.21;
496  table3gpp->m_uLgZSA = 0.93;
497  table3gpp->m_sigLgZSA = 0.22;
498  table3gpp->m_uLgZSD =
499  std::max(-1.0, -0.19 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.28);
500  table3gpp->m_sigLgZSD = 0.30;
501  table3gpp->m_offsetZOD = atan((35 - 3.5) / distance2D) - atan((35 - 1.5) / distance2D);
502  table3gpp->m_cDS = 3.91e-9;
503  table3gpp->m_cASD = 2;
504  table3gpp->m_cASA = 3;
505  table3gpp->m_cZSA = 3;
506  table3gpp->m_uK = 0;
507  table3gpp->m_sigK = 0;
508  table3gpp->m_rTau = 1.7;
509  table3gpp->m_uXpr = 7;
510  table3gpp->m_sigXpr = 3;
511  table3gpp->m_perClusterShadowingStd = 3;
512 
513  for (uint8_t row = 0; row < 6; row++)
514  {
515  for (uint8_t column = 0; column < 6; column++)
516  {
517  table3gpp->m_sqrtC[row][column] = sqrtC_RMa_O2I[row][column];
518  }
519  }
520  }
521  }
522  else if (m_scenario == "UMa")
523  {
524  if (los && !o2i)
525  {
526  table3gpp->m_numOfCluster = 12;
527  table3gpp->m_raysPerCluster = 20;
528  table3gpp->m_uLgDS = -6.955 - 0.0963 * log10(fcGHz);
529  table3gpp->m_sigLgDS = 0.66;
530  table3gpp->m_uLgASD = 1.06 + 0.1114 * log10(fcGHz);
531  table3gpp->m_sigLgASD = 0.28;
532  table3gpp->m_uLgASA = 1.81;
533  table3gpp->m_sigLgASA = 0.20;
534  table3gpp->m_uLgZSA = 0.95;
535  table3gpp->m_sigLgZSA = 0.16;
536  table3gpp->m_uLgZSD =
537  std::max(-0.5, -2.1 * distance2D / 1000.0 - 0.01 * (hUT - 1.5) + 0.75);
538  table3gpp->m_sigLgZSD = 0.40;
539  table3gpp->m_offsetZOD = 0;
540  table3gpp->m_cDS = std::max(0.25, -3.4084 * log10(fcGHz) + 6.5622) * 1e-9;
541  table3gpp->m_cASD = 5;
542  table3gpp->m_cASA = 11;
543  table3gpp->m_cZSA = 7;
544  table3gpp->m_uK = 9;
545  table3gpp->m_sigK = 3.5;
546  table3gpp->m_rTau = 2.5;
547  table3gpp->m_uXpr = 8;
548  table3gpp->m_sigXpr = 4;
549  table3gpp->m_perClusterShadowingStd = 3;
550 
551  for (uint8_t row = 0; row < 7; row++)
552  {
553  for (uint8_t column = 0; column < 7; column++)
554  {
555  table3gpp->m_sqrtC[row][column] = sqrtC_UMa_LOS[row][column];
556  }
557  }
558  }
559  else
560  {
561  double uLgZSD = std::max(-0.5, -2.1 * distance2D / 1000.0 - 0.01 * (hUT - 1.5) + 0.9);
562 
563  double afc = 0.208 * log10(fcGHz) - 0.782;
564  double bfc = 25;
565  double cfc = -0.13 * log10(fcGHz) + 2.03;
566  double efc = 7.66 * log10(fcGHz) - 5.96;
567 
568  double offsetZOD = efc - std::pow(10, afc * log10(std::max(bfc, distance2D)) + cfc);
569 
570  if (!los && !o2i)
571  {
572  table3gpp->m_numOfCluster = 20;
573  table3gpp->m_raysPerCluster = 20;
574  table3gpp->m_uLgDS = -6.28 - 0.204 * log10(fcGHz);
575  table3gpp->m_sigLgDS = 0.39;
576  table3gpp->m_uLgASD = 1.5 - 0.1144 * log10(fcGHz);
577  table3gpp->m_sigLgASD = 0.28;
578  table3gpp->m_uLgASA = 2.08 - 0.27 * log10(fcGHz);
579  table3gpp->m_sigLgASA = 0.11;
580  table3gpp->m_uLgZSA = -0.3236 * log10(fcGHz) + 1.512;
581  table3gpp->m_sigLgZSA = 0.16;
582  table3gpp->m_uLgZSD = uLgZSD;
583  table3gpp->m_sigLgZSD = 0.49;
584  table3gpp->m_offsetZOD = offsetZOD;
585  table3gpp->m_cDS = std::max(0.25, -3.4084 * log10(fcGHz) + 6.5622) * 1e-9;
586  table3gpp->m_cASD = 2;
587  table3gpp->m_cASA = 15;
588  table3gpp->m_cZSA = 7;
589  table3gpp->m_uK = 0;
590  table3gpp->m_sigK = 0;
591  table3gpp->m_rTau = 2.3;
592  table3gpp->m_uXpr = 7;
593  table3gpp->m_sigXpr = 3;
594  table3gpp->m_perClusterShadowingStd = 3;
595 
596  for (uint8_t row = 0; row < 6; row++)
597  {
598  for (uint8_t column = 0; column < 6; column++)
599  {
600  table3gpp->m_sqrtC[row][column] = sqrtC_UMa_NLOS[row][column];
601  }
602  }
603  }
604  else //(o2i)
605  {
606  table3gpp->m_numOfCluster = 12;
607  table3gpp->m_raysPerCluster = 20;
608  table3gpp->m_uLgDS = -6.62;
609  table3gpp->m_sigLgDS = 0.32;
610  table3gpp->m_uLgASD = 1.25;
611  table3gpp->m_sigLgASD = 0.42;
612  table3gpp->m_uLgASA = 1.76;
613  table3gpp->m_sigLgASA = 0.16;
614  table3gpp->m_uLgZSA = 1.01;
615  table3gpp->m_sigLgZSA = 0.43;
616  table3gpp->m_uLgZSD = uLgZSD;
617  table3gpp->m_sigLgZSD = 0.49;
618  table3gpp->m_offsetZOD = offsetZOD;
619  table3gpp->m_cDS = 11e-9;
620  table3gpp->m_cASD = 5;
621  table3gpp->m_cASA = 8;
622  table3gpp->m_cZSA = 3;
623  table3gpp->m_uK = 0;
624  table3gpp->m_sigK = 0;
625  table3gpp->m_rTau = 2.2;
626  table3gpp->m_uXpr = 9;
627  table3gpp->m_sigXpr = 5;
628  table3gpp->m_perClusterShadowingStd = 4;
629 
630  for (uint8_t row = 0; row < 6; row++)
631  {
632  for (uint8_t column = 0; column < 6; column++)
633  {
634  table3gpp->m_sqrtC[row][column] = sqrtC_UMa_O2I[row][column];
635  }
636  }
637  }
638  }
639  }
640  else if (m_scenario == "UMi-StreetCanyon")
641  {
642  if (los && !o2i)
643  {
644  table3gpp->m_numOfCluster = 12;
645  table3gpp->m_raysPerCluster = 20;
646  table3gpp->m_uLgDS = -0.24 * log10(1 + fcGHz) - 7.14;
647  table3gpp->m_sigLgDS = 0.38;
648  table3gpp->m_uLgASD = -0.05 * log10(1 + fcGHz) + 1.21;
649  table3gpp->m_sigLgASD = 0.41;
650  table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.73;
651  table3gpp->m_sigLgASA = 0.014 * log10(1 + fcGHz) + 0.28;
652  table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
653  table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
654  table3gpp->m_uLgZSD =
655  std::max(-0.21, -14.8 * distance2D / 1000.0 + 0.01 * std::abs(hUT - hBS) + 0.83);
656  table3gpp->m_sigLgZSD = 0.35;
657  table3gpp->m_offsetZOD = 0;
658  table3gpp->m_cDS = 5e-9;
659  table3gpp->m_cASD = 3;
660  table3gpp->m_cASA = 17;
661  table3gpp->m_cZSA = 7;
662  table3gpp->m_uK = 9;
663  table3gpp->m_sigK = 5;
664  table3gpp->m_rTau = 3;
665  table3gpp->m_uXpr = 9;
666  table3gpp->m_sigXpr = 3;
667  table3gpp->m_perClusterShadowingStd = 3;
668 
669  for (uint8_t row = 0; row < 7; row++)
670  {
671  for (uint8_t column = 0; column < 7; column++)
672  {
673  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
674  }
675  }
676  }
677  else
678  {
679  double uLgZSD =
680  std::max(-0.5, -3.1 * distance2D / 1000.0 + 0.01 * std::max(hUT - hBS, 0.0) + 0.2);
681  double offsetZOD = -1 * std::pow(10, -1.5 * log10(std::max(10.0, distance2D)) + 3.3);
682  if (!los && !o2i)
683  {
684  table3gpp->m_numOfCluster = 19;
685  table3gpp->m_raysPerCluster = 20;
686  table3gpp->m_uLgDS = -0.24 * log10(1 + fcGHz) - 6.83;
687  table3gpp->m_sigLgDS = 0.16 * log10(1 + fcGHz) + 0.28;
688  table3gpp->m_uLgASD = -0.23 * log10(1 + fcGHz) + 1.53;
689  table3gpp->m_sigLgASD = 0.11 * log10(1 + fcGHz) + 0.33;
690  table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
691  table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
692  table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
693  table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
694  table3gpp->m_uLgZSD = uLgZSD;
695  table3gpp->m_sigLgZSD = 0.35;
696  table3gpp->m_offsetZOD = offsetZOD;
697  table3gpp->m_cDS = 11e-9;
698  table3gpp->m_cASD = 10;
699  table3gpp->m_cASA = 22;
700  table3gpp->m_cZSA = 7;
701  table3gpp->m_uK = 0;
702  table3gpp->m_sigK = 0;
703  table3gpp->m_rTau = 2.1;
704  table3gpp->m_uXpr = 8;
705  table3gpp->m_sigXpr = 3;
706  table3gpp->m_perClusterShadowingStd = 3;
707 
708  for (uint8_t row = 0; row < 6; row++)
709  {
710  for (uint8_t column = 0; column < 6; column++)
711  {
712  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
713  }
714  }
715  }
716  else //(o2i)
717  {
718  table3gpp->m_numOfCluster = 12;
719  table3gpp->m_raysPerCluster = 20;
720  table3gpp->m_uLgDS = -6.62;
721  table3gpp->m_sigLgDS = 0.32;
722  table3gpp->m_uLgASD = 1.25;
723  table3gpp->m_sigLgASD = 0.42;
724  table3gpp->m_uLgASA = 1.76;
725  table3gpp->m_sigLgASA = 0.16;
726  table3gpp->m_uLgZSA = 1.01;
727  table3gpp->m_sigLgZSA = 0.43;
728  table3gpp->m_uLgZSD = uLgZSD;
729  table3gpp->m_sigLgZSD = 0.35;
730  table3gpp->m_offsetZOD = offsetZOD;
731  table3gpp->m_cDS = 11e-9;
732  table3gpp->m_cASD = 5;
733  table3gpp->m_cASA = 8;
734  table3gpp->m_cZSA = 3;
735  table3gpp->m_uK = 0;
736  table3gpp->m_sigK = 0;
737  table3gpp->m_rTau = 2.2;
738  table3gpp->m_uXpr = 9;
739  table3gpp->m_sigXpr = 5;
740  table3gpp->m_perClusterShadowingStd = 4;
741 
742  for (uint8_t row = 0; row < 6; row++)
743  {
744  for (uint8_t column = 0; column < 6; column++)
745  {
746  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_O2I[row][column];
747  }
748  }
749  }
750  }
751  }
752  else if (m_scenario == "InH-OfficeMixed" || m_scenario == "InH-OfficeOpen")
753  {
754  NS_ASSERT_MSG(!o2i, "The indoor scenario does out support outdoor to indoor");
755  if (los)
756  {
757  table3gpp->m_numOfCluster = 15;
758  table3gpp->m_raysPerCluster = 20;
759  table3gpp->m_uLgDS = -0.01 * log10(1 + fcGHz) - 7.692;
760  table3gpp->m_sigLgDS = 0.18;
761  table3gpp->m_uLgASD = 1.60;
762  table3gpp->m_sigLgASD = 0.18;
763  table3gpp->m_uLgASA = -0.19 * log10(1 + fcGHz) + 1.781;
764  table3gpp->m_sigLgASA = 0.12 * log10(1 + fcGHz) + 0.119;
765  table3gpp->m_uLgZSA = -0.26 * log10(1 + fcGHz) + 1.44;
766  table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.264;
767  table3gpp->m_uLgZSD = -1.43 * log10(1 + fcGHz) + 2.228;
768  table3gpp->m_sigLgZSD = 0.13 * log10(1 + fcGHz) + 0.30;
769  table3gpp->m_offsetZOD = 0;
770  table3gpp->m_cDS = 3.91e-9;
771  table3gpp->m_cASD = 5;
772  table3gpp->m_cASA = 8;
773  table3gpp->m_cZSA = 9;
774  table3gpp->m_uK = 7;
775  table3gpp->m_sigK = 4;
776  table3gpp->m_rTau = 3.6;
777  table3gpp->m_uXpr = 11;
778  table3gpp->m_sigXpr = 4;
779  table3gpp->m_perClusterShadowingStd = 6;
780 
781  for (uint8_t row = 0; row < 7; row++)
782  {
783  for (uint8_t column = 0; column < 7; column++)
784  {
785  table3gpp->m_sqrtC[row][column] = sqrtC_office_LOS[row][column];
786  }
787  }
788  }
789  else
790  {
791  table3gpp->m_numOfCluster = 19;
792  table3gpp->m_raysPerCluster = 20;
793  table3gpp->m_uLgDS = -0.28 * log10(1 + fcGHz) - 7.173;
794  table3gpp->m_sigLgDS = 0.1 * log10(1 + fcGHz) + 0.055;
795  table3gpp->m_uLgASD = 1.62;
796  table3gpp->m_sigLgASD = 0.25;
797  table3gpp->m_uLgASA = -0.11 * log10(1 + fcGHz) + 1.863;
798  table3gpp->m_sigLgASA = 0.12 * log10(1 + fcGHz) + 0.059;
799  table3gpp->m_uLgZSA = -0.15 * log10(1 + fcGHz) + 1.387;
800  table3gpp->m_sigLgZSA = -0.09 * log10(1 + fcGHz) + 0.746;
801  table3gpp->m_uLgZSD = 1.08;
802  table3gpp->m_sigLgZSD = 0.36;
803  table3gpp->m_offsetZOD = 0;
804  table3gpp->m_cDS = 3.91e-9;
805  table3gpp->m_cASD = 5;
806  table3gpp->m_cASA = 11;
807  table3gpp->m_cZSA = 9;
808  table3gpp->m_uK = 0;
809  table3gpp->m_sigK = 0;
810  table3gpp->m_rTau = 3;
811  table3gpp->m_uXpr = 10;
812  table3gpp->m_sigXpr = 4;
813  table3gpp->m_perClusterShadowingStd = 3;
814 
815  for (uint8_t row = 0; row < 6; row++)
816  {
817  for (uint8_t column = 0; column < 6; column++)
818  {
819  table3gpp->m_sqrtC[row][column] = sqrtC_office_NLOS[row][column];
820  }
821  }
822  }
823  }
824  else if (m_scenario == "V2V-Urban")
825  {
826  if (channelCondition->IsLos())
827  {
828  // 3GPP mentioned that 3.91 ns should be used when the Cluster DS (cDS)
829  // entry is N/A.
830  table3gpp->m_numOfCluster = 12;
831  table3gpp->m_raysPerCluster = 20;
832  table3gpp->m_uLgDS = -0.2 * log10(1 + fcGHz) - 7.5;
833  table3gpp->m_sigLgDS = 0.1;
834  table3gpp->m_uLgASD = -0.1 * log10(1 + fcGHz) + 1.6;
835  table3gpp->m_sigLgASD = 0.1;
836  table3gpp->m_uLgASA = -0.1 * log10(1 + fcGHz) + 1.6;
837  table3gpp->m_sigLgASA = 0.1;
838  table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
839  table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
840  table3gpp->m_uLgZSD = -0.1 * log10(1 + fcGHz) + 0.73;
841  table3gpp->m_sigLgZSD = -0.04 * log10(1 + fcGHz) + 0.34;
842  table3gpp->m_offsetZOD = 0;
843  table3gpp->m_cDS = 5;
844  table3gpp->m_cASD = 17;
845  table3gpp->m_cASA = 17;
846  table3gpp->m_cZSA = 7;
847  table3gpp->m_uK = 3.48;
848  table3gpp->m_sigK = 2;
849  table3gpp->m_rTau = 3;
850  table3gpp->m_uXpr = 9;
851  table3gpp->m_sigXpr = 3;
852  table3gpp->m_perClusterShadowingStd = 4;
853 
854  for (uint8_t row = 0; row < 7; row++)
855  {
856  for (uint8_t column = 0; column < 7; column++)
857  {
858  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
859  }
860  }
861  }
862  else if (channelCondition->IsNlos())
863  {
864  table3gpp->m_numOfCluster = 19;
865  table3gpp->m_raysPerCluster = 20;
866  table3gpp->m_uLgDS = -0.3 * log10(1 + fcGHz) - 7;
867  table3gpp->m_sigLgDS = 0.28;
868  table3gpp->m_uLgASD = -0.08 * log10(1 + fcGHz) + 1.81;
869  table3gpp->m_sigLgASD = 0.05 * log10(1 + fcGHz) + 0.3;
870  table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
871  table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
872  table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
873  table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
874  table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
875  table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
876  table3gpp->m_offsetZOD = 0;
877  table3gpp->m_cDS = 11;
878  table3gpp->m_cASD = 22;
879  table3gpp->m_cASA = 22;
880  table3gpp->m_cZSA = 7;
881  table3gpp->m_uK = 0; // N/A
882  table3gpp->m_sigK = 0; // N/A
883  table3gpp->m_rTau = 2.1;
884  table3gpp->m_uXpr = 8;
885  table3gpp->m_sigXpr = 3;
886  table3gpp->m_perClusterShadowingStd = 4;
887 
888  for (uint8_t row = 0; row < 6; row++)
889  {
890  for (uint8_t column = 0; column < 6; column++)
891  {
892  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
893  }
894  }
895  }
896  else if (channelCondition->IsNlosv())
897  {
898  table3gpp->m_numOfCluster = 19;
899  table3gpp->m_raysPerCluster = 20;
900  table3gpp->m_uLgDS = -0.4 * log10(1 + fcGHz) - 7;
901  table3gpp->m_sigLgDS = 0.1;
902  table3gpp->m_uLgASD = -0.1 * log10(1 + fcGHz) + 1.7;
903  table3gpp->m_sigLgASD = 0.1;
904  table3gpp->m_uLgASA = -0.1 * log10(1 + fcGHz) + 1.7;
905  table3gpp->m_sigLgASA = 0.1;
906  table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
907  table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
908  table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
909  table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
910  table3gpp->m_offsetZOD = 0;
911  table3gpp->m_cDS = 11;
912  table3gpp->m_cASD = 22;
913  table3gpp->m_cASA = 22;
914  table3gpp->m_cZSA = 7;
915  table3gpp->m_uK = 0;
916  table3gpp->m_sigK = 4.5;
917  table3gpp->m_rTau = 2.1;
918  table3gpp->m_uXpr = 8;
919  table3gpp->m_sigXpr = 3;
920  table3gpp->m_perClusterShadowingStd = 4;
921 
922  for (uint8_t row = 0; row < 6; row++)
923  {
924  for (uint8_t column = 0; column < 6; column++)
925  {
926  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
927  }
928  }
929  }
930  else
931  {
932  NS_FATAL_ERROR("Unknown channel condition");
933  }
934  }
935  else if (m_scenario == "V2V-Highway")
936  {
937  if (channelCondition->IsLos())
938  {
939  table3gpp->m_numOfCluster = 12;
940  table3gpp->m_raysPerCluster = 20;
941  table3gpp->m_uLgDS = -8.3;
942  table3gpp->m_sigLgDS = 0.2;
943  table3gpp->m_uLgASD = 1.4;
944  table3gpp->m_sigLgASD = 0.1;
945  table3gpp->m_uLgASA = 1.4;
946  table3gpp->m_sigLgASA = 0.1;
947  table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
948  table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
949  table3gpp->m_uLgZSD = -0.1 * log10(1 + fcGHz) + 0.73;
950  table3gpp->m_sigLgZSD = -0.04 * log10(1 + fcGHz) + 0.34;
951  table3gpp->m_offsetZOD = 0;
952  table3gpp->m_cDS = 5;
953  table3gpp->m_cASD = 17;
954  table3gpp->m_cASA = 17;
955  table3gpp->m_cZSA = 7;
956  table3gpp->m_uK = 9;
957  table3gpp->m_sigK = 3.5;
958  table3gpp->m_rTau = 3;
959  table3gpp->m_uXpr = 9;
960  table3gpp->m_sigXpr = 3;
961  table3gpp->m_perClusterShadowingStd = 4;
962 
963  for (uint8_t row = 0; row < 7; row++)
964  {
965  for (uint8_t column = 0; column < 7; column++)
966  {
967  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
968  }
969  }
970  }
971  else if (channelCondition->IsNlosv())
972  {
973  table3gpp->m_numOfCluster = 19;
974  table3gpp->m_raysPerCluster = 20;
975  table3gpp->m_uLgDS = -8.3;
976  table3gpp->m_sigLgDS = 0.3;
977  table3gpp->m_uLgASD = 1.5;
978  table3gpp->m_sigLgASD = 0.1;
979  table3gpp->m_uLgASA = 1.5;
980  table3gpp->m_sigLgASA = 0.1;
981  table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
982  table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
983  table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
984  table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
985  table3gpp->m_offsetZOD = 0;
986  table3gpp->m_cDS = 11;
987  table3gpp->m_cASD = 22;
988  table3gpp->m_cASA = 22;
989  table3gpp->m_cZSA = 7;
990  table3gpp->m_uK = 0;
991  table3gpp->m_sigK = 4.5;
992  table3gpp->m_rTau = 2.1;
993  table3gpp->m_uXpr = 8.0;
994  table3gpp->m_sigXpr = 3;
995  table3gpp->m_perClusterShadowingStd = 4;
996 
997  for (uint8_t row = 0; row < 6; row++)
998  {
999  for (uint8_t column = 0; column < 6; column++)
1000  {
1001  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1002  }
1003  }
1004  }
1005  else if (channelCondition->IsNlos())
1006  {
1007  NS_LOG_WARN(
1008  "The fast fading parameters for the NLOS condition in the Highway scenario are not "
1009  "defined in TR 37.885, use the ones defined in TDoc R1-1803671 instead");
1010 
1011  table3gpp->m_numOfCluster = 19;
1012  table3gpp->m_raysPerCluster = 20;
1013  table3gpp->m_uLgDS = -0.3 * log10(1 + fcGHz) - 7;
1014  table3gpp->m_sigLgDS = 0.28;
1015  table3gpp->m_uLgASD = -0.08 * log10(1 + fcGHz) + 1.81;
1016  table3gpp->m_sigLgASD = 0.05 * log10(1 + fcGHz) + 0.3;
1017  table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
1018  table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
1019  table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1020  table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1021  table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
1022  table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
1023  table3gpp->m_offsetZOD = 0;
1024  table3gpp->m_cDS = 11;
1025  table3gpp->m_cASD = 22;
1026  table3gpp->m_cASA = 22;
1027  table3gpp->m_cZSA = 7;
1028  table3gpp->m_uK = 0; // N/A
1029  table3gpp->m_sigK = 0; // N/A
1030  table3gpp->m_rTau = 2.1;
1031  table3gpp->m_uXpr = 8;
1032  table3gpp->m_sigXpr = 3;
1033  table3gpp->m_perClusterShadowingStd = 4;
1034 
1035  for (uint8_t row = 0; row < 6; row++)
1036  {
1037  for (uint8_t column = 0; column < 6; column++)
1038  {
1039  table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
1040  }
1041  }
1042  }
1043  else
1044  {
1045  NS_FATAL_ERROR("Unknown channel condition");
1046  }
1047  }
1048  else
1049  {
1050  NS_FATAL_ERROR("unknown scenarios");
1051  }
1052 
1053  return table3gpp;
1054 }
1055 
1056 bool
1058  Ptr<const ChannelCondition> channelCondition) const
1059 {
1060  NS_LOG_FUNCTION(this);
1061 
1062  bool update = false;
1063 
1064  // if the channel condition is different the channel has to be updated
1065  if (!channelCondition->IsEqual(channelParams->m_losCondition, channelParams->m_o2iCondition))
1066  {
1067  NS_LOG_DEBUG("Update the channel condition");
1068  update = true;
1069  }
1070 
1071  // if the coherence time is over the channel has to be updated
1072  if (!m_updatePeriod.IsZero() &&
1073  Simulator::Now() - channelParams->m_generatedTime > m_updatePeriod)
1074  {
1075  NS_LOG_DEBUG("Generation time " << channelParams->m_generatedTime.As(Time::NS) << " now "
1076  << Now().As(Time::NS));
1077  update = true;
1078  }
1079 
1080  return update;
1081 }
1082 
1083 bool
1085  Ptr<const ChannelMatrix> channelMatrix)
1086 {
1087  return channelParams->m_generatedTime > channelMatrix->m_generatedTime;
1088 }
1089 
1093  Ptr<const PhasedArrayModel> aAntenna,
1094  Ptr<const PhasedArrayModel> bAntenna)
1095 {
1096  NS_LOG_FUNCTION(this);
1097 
1098  // Compute the channel params key. The key is reciprocal, i.e., key (a, b) = key (b, a)
1099  uint64_t channelParamsKey =
1100  GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
1101  // Compute the channel matrix key. The key is reciprocal, i.e., key (a, b) = key (b, a)
1102  uint64_t channelMatrixKey = GetKey(aAntenna->GetId(), bAntenna->GetId());
1103 
1104  // retrieve the channel condition
1105  Ptr<const ChannelCondition> condition =
1106  m_channelConditionModel->GetChannelCondition(aMob, bMob);
1107 
1108  // Check if the channel is present in the map and return it, otherwise
1109  // generate a new channel
1110  bool updateParams = false;
1111  bool updateMatrix = false;
1112  bool notFoundParams = false;
1113  bool notFoundMatrix = false;
1114  Ptr<ChannelMatrix> channelMatrix;
1115  Ptr<ThreeGppChannelParams> channelParams;
1116 
1117  if (m_channelParamsMap.find(channelParamsKey) != m_channelParamsMap.end())
1118  {
1119  channelParams = m_channelParamsMap[channelParamsKey];
1120  // check if it has to be updated
1121  updateParams = ChannelParamsNeedsUpdate(channelParams, condition);
1122  }
1123  else
1124  {
1125  NS_LOG_DEBUG("channel params not found");
1126  notFoundParams = true;
1127  }
1128 
1129  double x = aMob->GetPosition().x - bMob->GetPosition().x;
1130  double y = aMob->GetPosition().y - bMob->GetPosition().y;
1131  double distance2D = sqrt(x * x + y * y);
1132 
1133  // NOTE we assume hUT = min (height(a), height(b)) and
1134  // hBS = max (height (a), height (b))
1135  double hUt = std::min(aMob->GetPosition().z, bMob->GetPosition().z);
1136  double hBs = std::max(aMob->GetPosition().z, bMob->GetPosition().z);
1137 
1138  // get the 3GPP parameters
1139  Ptr<const ParamsTable> table3gpp = GetThreeGppTable(condition, hBs, hUt, distance2D);
1140 
1141  if (notFoundParams || updateParams)
1142  {
1143  // Step 4: Generate large scale parameters. All LSPS are uncorrelated.
1144  // Step 5: Generate Delays.
1145  // Step 6: Generate cluster powers.
1146  // Step 7: Generate arrival and departure angles for both azimuth and elevation.
1147  // Step 8: Coupling of rays within a cluster for both azimuth and elevation
1148  // shuffle all the arrays to perform random coupling
1149  // Step 9: Generate the cross polarization power ratios
1150  // Step 10: Draw initial phases
1151  channelParams = GenerateChannelParameters(condition, table3gpp, aMob, bMob);
1152  // store or replace the channel parameters
1153  m_channelParamsMap[channelParamsKey] = channelParams;
1154  }
1155 
1156  if (m_channelMatrixMap.find(channelMatrixKey) != m_channelMatrixMap.end())
1157  {
1158  // channel matrix present in the map
1159  NS_LOG_DEBUG("channel matrix present in the map");
1160  channelMatrix = m_channelMatrixMap[channelMatrixKey];
1161  updateMatrix = ChannelMatrixNeedsUpdate(channelParams, channelMatrix);
1162  }
1163  else
1164  {
1165  NS_LOG_DEBUG("channel matrix not found");
1166  notFoundMatrix = true;
1167  }
1168 
1169  // If the channel is not present in the map or if it has to be updated
1170  // generate a new realization
1171  if (notFoundMatrix || updateMatrix)
1172  {
1173  // channel matrix not found or has to be updated, generate a new one
1174  channelMatrix = GetNewChannel(channelParams, table3gpp, aMob, bMob, aAntenna, bAntenna);
1175  channelMatrix->m_antennaPair =
1176  std::make_pair(aAntenna->GetId(),
1177  bAntenna->GetId()); // save antenna pair, with the exact order of s and u
1178  // antennas at the moment of the channel generation
1179 
1180  // store or replace the channel matrix in the channel map
1181  m_channelMatrixMap[channelMatrixKey] = channelMatrix;
1182  }
1183 
1184  return channelMatrix;
1185 }
1186 
1189 {
1190  NS_LOG_FUNCTION(this);
1191 
1192  // Compute the channel key. The key is reciprocal, i.e., key (a, b) = key (b, a)
1193  uint64_t channelParamsKey =
1194  GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
1195 
1196  if (m_channelParamsMap.find(channelParamsKey) != m_channelParamsMap.end())
1197  {
1198  return m_channelParamsMap.find(channelParamsKey)->second;
1199  }
1200  else
1201  {
1202  NS_LOG_WARN("Channel params map not found. Returning a nullptr.");
1203  return nullptr;
1204  }
1205 }
1206 
1209  const Ptr<const ParamsTable> table3gpp,
1210  const Ptr<const MobilityModel> aMob,
1211  const Ptr<const MobilityModel> bMob) const
1212 {
1213  NS_LOG_FUNCTION(this);
1214  // create a channel matrix instance
1215  Ptr<ThreeGppChannelParams> channelParams = Create<ThreeGppChannelParams>();
1216  channelParams->m_generatedTime = Simulator::Now();
1217  channelParams->m_nodeIds =
1218  std::make_pair(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
1219  channelParams->m_losCondition = channelCondition->GetLosCondition();
1220  channelParams->m_o2iCondition = channelCondition->GetO2iCondition();
1221 
1222  // Step 4: Generate large scale parameters. All LSPS are uncorrelated.
1223  DoubleVector LSPsIndep;
1224  DoubleVector LSPs;
1225  uint8_t paramNum = 6;
1226  if (channelParams->m_losCondition == ChannelCondition::LOS)
1227  {
1228  paramNum = 7;
1229  }
1230 
1231  // Generate paramNum independent LSPs.
1232  for (uint8_t iter = 0; iter < paramNum; iter++)
1233  {
1234  LSPsIndep.push_back(m_normalRv->GetValue());
1235  }
1236  for (uint8_t row = 0; row < paramNum; row++)
1237  {
1238  double temp = 0;
1239  for (uint8_t column = 0; column < paramNum; column++)
1240  {
1241  temp += table3gpp->m_sqrtC[row][column] * LSPsIndep[column];
1242  }
1243  LSPs.push_back(temp);
1244  }
1245 
1246  // NOTE the shadowing is generated in the propagation loss model
1247  double DS;
1248  double ASD;
1249  double ASA;
1250  double ZSA;
1251  double ZSD;
1252  double kFactor = 0;
1253  if (channelParams->m_losCondition == ChannelCondition::LOS)
1254  {
1255  kFactor = LSPs[1] * table3gpp->m_sigK + table3gpp->m_uK;
1256  DS = pow(10, LSPs[2] * table3gpp->m_sigLgDS + table3gpp->m_uLgDS);
1257  ASD = pow(10, LSPs[3] * table3gpp->m_sigLgASD + table3gpp->m_uLgASD);
1258  ASA = pow(10, LSPs[4] * table3gpp->m_sigLgASA + table3gpp->m_uLgASA);
1259  ZSD = pow(10, LSPs[5] * table3gpp->m_sigLgZSD + table3gpp->m_uLgZSD);
1260  ZSA = pow(10, LSPs[6] * table3gpp->m_sigLgZSA + table3gpp->m_uLgZSA);
1261  }
1262  else
1263  {
1264  DS = pow(10, LSPs[1] * table3gpp->m_sigLgDS + table3gpp->m_uLgDS);
1265  ASD = pow(10, LSPs[2] * table3gpp->m_sigLgASD + table3gpp->m_uLgASD);
1266  ASA = pow(10, LSPs[3] * table3gpp->m_sigLgASA + table3gpp->m_uLgASA);
1267  ZSD = pow(10, LSPs[4] * table3gpp->m_sigLgZSD + table3gpp->m_uLgZSD);
1268  ZSA = pow(10, LSPs[5] * table3gpp->m_sigLgZSA + table3gpp->m_uLgZSA);
1269  }
1270  ASD = std::min(ASD, 104.0);
1271  ASA = std::min(ASA, 104.0);
1272  ZSD = std::min(ZSD, 52.0);
1273  ZSA = std::min(ZSA, 52.0);
1274 
1275  // save DS and K_factor parameters in the structure
1276  channelParams->m_DS = DS;
1277  channelParams->m_K_factor = kFactor;
1278 
1279  NS_LOG_INFO("K-factor=" << kFactor << ", DS=" << DS << ", ASD=" << ASD << ", ASA=" << ASA
1280  << ", ZSD=" << ZSD << ", ZSA=" << ZSA);
1281 
1282  // Step 5: Generate Delays.
1283  DoubleVector clusterDelay;
1284  double minTau = 100.0;
1285  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1286  {
1287  double tau = -1 * table3gpp->m_rTau * DS * log(m_uniformRv->GetValue(0, 1)); //(7.5-1)
1288  if (minTau > tau)
1289  {
1290  minTau = tau;
1291  }
1292  clusterDelay.push_back(tau);
1293  }
1294 
1295  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1296  {
1297  clusterDelay[cIndex] -= minTau;
1298  }
1299  std::sort(clusterDelay.begin(), clusterDelay.end()); //(7.5-2)
1300 
1301  /* since the scaled Los delays are not to be used in cluster power generation,
1302  * we will generate cluster power first and resume to compute Los cluster delay later.*/
1303 
1304  // Step 6: Generate cluster powers.
1305  DoubleVector clusterPower;
1306  double powerSum = 0;
1307  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1308  {
1309  double power =
1310  exp(-1 * clusterDelay[cIndex] * (table3gpp->m_rTau - 1) / table3gpp->m_rTau / DS) *
1311  pow(10,
1312  -1 * m_normalRv->GetValue() * table3gpp->m_perClusterShadowingStd / 10.0); //(7.5-5)
1313  powerSum += power;
1314  clusterPower.push_back(power);
1315  }
1316  channelParams->m_clusterPower = clusterPower;
1317 
1318  double powerMax = 0;
1319 
1320  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1321  {
1322  channelParams->m_clusterPower[cIndex] =
1323  channelParams->m_clusterPower[cIndex] / powerSum; //(7.5-6)
1324  }
1325 
1326  DoubleVector clusterPowerForAngles; // this power is only for equation (7.5-9) and (7.5-14), not
1327  // for (7.5-22)
1328  if (channelParams->m_losCondition == ChannelCondition::LOS)
1329  {
1330  double kLinear = pow(10, kFactor / 10.0);
1331 
1332  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1333  {
1334  if (cIndex == 0)
1335  {
1336  clusterPowerForAngles.push_back(channelParams->m_clusterPower[cIndex] /
1337  (1 + kLinear) +
1338  kLinear / (1 + kLinear)); //(7.5-8)
1339  }
1340  else
1341  {
1342  clusterPowerForAngles.push_back(channelParams->m_clusterPower[cIndex] /
1343  (1 + kLinear)); //(7.5-8)
1344  }
1345  if (powerMax < clusterPowerForAngles[cIndex])
1346  {
1347  powerMax = clusterPowerForAngles[cIndex];
1348  }
1349  }
1350  }
1351  else
1352  {
1353  for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
1354  {
1355  clusterPowerForAngles.push_back(channelParams->m_clusterPower[cIndex]); //(7.5-6)
1356  if (powerMax < clusterPowerForAngles[cIndex])
1357  {
1358  powerMax = clusterPowerForAngles[cIndex];
1359  }
1360  }
1361  }
1362 
1363  // remove clusters with less than -25 dB power compared to the maxim cluster power;
1364  // double thresh = pow(10, -2.5);
1365  double thresh = 0.0032;
1366  for (uint8_t cIndex = table3gpp->m_numOfCluster; cIndex > 0; cIndex--)
1367  {
1368  if (clusterPowerForAngles[cIndex - 1] < thresh * powerMax)
1369  {
1370  clusterPowerForAngles.erase(clusterPowerForAngles.begin() + cIndex - 1);
1371  channelParams->m_clusterPower.erase(channelParams->m_clusterPower.begin() + cIndex - 1);
1372  clusterDelay.erase(clusterDelay.begin() + cIndex - 1);
1373  }
1374  }
1375 
1376  NS_ASSERT(channelParams->m_clusterPower.size() < UINT8_MAX);
1377  channelParams->m_reducedClusterNumber = channelParams->m_clusterPower.size();
1378  // Resume step 5 to compute the delay for LoS condition.
1379  if (channelParams->m_losCondition == ChannelCondition::LOS)
1380  {
1381  double cTau =
1382  0.7705 - 0.0433 * kFactor + 2e-4 * pow(kFactor, 2) + 17e-6 * pow(kFactor, 3); //(7.5-3)
1383  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1384  {
1385  clusterDelay[cIndex] = clusterDelay[cIndex] / cTau; //(7.5-4)
1386  }
1387  }
1388 
1389  // Step 7: Generate arrival and departure angles for both azimuth and elevation.
1390 
1391  double cNlos;
1392  // According to table 7.5-6, only cluster number equals to 8, 10, 11, 12, 19 and 20 is valid.
1393  // Not sure why the other cases are in Table 7.5-2.
1394  switch (table3gpp->m_numOfCluster) // Table 7.5-2
1395  {
1396  case 4:
1397  cNlos = 0.779;
1398  break;
1399  case 5:
1400  cNlos = 0.860;
1401  break;
1402  case 8:
1403  cNlos = 1.018;
1404  break;
1405  case 10:
1406  cNlos = 1.090;
1407  break;
1408  case 11:
1409  cNlos = 1.123;
1410  break;
1411  case 12:
1412  cNlos = 1.146;
1413  break;
1414  case 14:
1415  cNlos = 1.190;
1416  break;
1417  case 15:
1418  cNlos = 1.221;
1419  break;
1420  case 16:
1421  cNlos = 1.226;
1422  break;
1423  case 19:
1424  cNlos = 1.273;
1425  break;
1426  case 20:
1427  cNlos = 1.289;
1428  break;
1429  default:
1430  NS_FATAL_ERROR("Invalid cluster number");
1431  }
1432 
1433  double cPhi = cNlos;
1434 
1435  if (channelParams->m_losCondition == ChannelCondition::LOS)
1436  {
1437  cPhi *= (1.1035 - 0.028 * kFactor - 2e-3 * pow(kFactor, 2) +
1438  1e-4 * pow(kFactor, 3)); //(7.5-10))
1439  }
1440 
1441  switch (table3gpp->m_numOfCluster) // Table 7.5-4
1442  {
1443  case 8:
1444  cNlos = 0.889;
1445  break;
1446  case 10:
1447  cNlos = 0.957;
1448  break;
1449  case 11:
1450  cNlos = 1.031;
1451  break;
1452  case 12:
1453  cNlos = 1.104;
1454  break;
1455  case 15:
1456  cNlos = 1.1088;
1457  break;
1458  case 19:
1459  cNlos = 1.184;
1460  break;
1461  case 20:
1462  cNlos = 1.178;
1463  break;
1464  default:
1465  NS_FATAL_ERROR("Invalid cluster number");
1466  }
1467 
1468  double cTheta = cNlos;
1469  if (channelCondition->IsLos())
1470  {
1471  cTheta *= (1.3086 + 0.0339 * kFactor - 0.0077 * pow(kFactor, 2) +
1472  2e-4 * pow(kFactor, 3)); //(7.5-15)
1473  }
1474 
1475  DoubleVector clusterAoa;
1476  DoubleVector clusterAod;
1477  DoubleVector clusterZoa;
1478  DoubleVector clusterZod;
1479  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1480  {
1481  double logCalc = -1 * log(clusterPowerForAngles[cIndex] / powerMax);
1482  double angle = 2 * sqrt(logCalc) / 1.4 / cPhi; //(7.5-9)
1483  clusterAoa.push_back(ASA * angle);
1484  clusterAod.push_back(ASD * angle);
1485  angle = logCalc / cTheta; //(7.5-14)
1486  clusterZoa.push_back(ZSA * angle);
1487  clusterZod.push_back(ZSD * angle);
1488  }
1489 
1490  Angles sAngle(bMob->GetPosition(), aMob->GetPosition());
1491  Angles uAngle(aMob->GetPosition(), bMob->GetPosition());
1492 
1493  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1494  {
1495  int Xn = 1;
1496  if (m_uniformRv->GetValue(0, 1) < 0.5)
1497  {
1498  Xn = -1;
1499  }
1500  clusterAoa[cIndex] = clusterAoa[cIndex] * Xn + (m_normalRv->GetValue() * ASA / 7.0) +
1501  RadiansToDegrees(uAngle.GetAzimuth()); //(7.5-11)
1502  clusterAod[cIndex] = clusterAod[cIndex] * Xn + (m_normalRv->GetValue() * ASD / 7.0) +
1503  RadiansToDegrees(sAngle.GetAzimuth());
1504  if (channelCondition->IsO2i())
1505  {
1506  clusterZoa[cIndex] =
1507  clusterZoa[cIndex] * Xn + (m_normalRv->GetValue() * ZSA / 7.0) + 90; //(7.5-16)
1508  }
1509  else
1510  {
1511  clusterZoa[cIndex] = clusterZoa[cIndex] * Xn + (m_normalRv->GetValue() * ZSA / 7.0) +
1512  RadiansToDegrees(uAngle.GetInclination()); //(7.5-16)
1513  }
1514  clusterZod[cIndex] = clusterZod[cIndex] * Xn + (m_normalRv->GetValue() * ZSD / 7.0) +
1515  RadiansToDegrees(sAngle.GetInclination()) +
1516  table3gpp->m_offsetZOD; //(7.5-19)
1517  }
1518 
1519  if (channelParams->m_losCondition == ChannelCondition::LOS)
1520  {
1521  // The 7.5-12 can be rewrite as Theta_n,ZOA = Theta_n,ZOA - (Theta_1,ZOA - Theta_LOS,ZOA) =
1522  // Theta_n,ZOA - diffZOA, Similar as AOD, ZSA and ZSD.
1523  double diffAoa = clusterAoa[0] - RadiansToDegrees(uAngle.GetAzimuth());
1524  double diffAod = clusterAod[0] - RadiansToDegrees(sAngle.GetAzimuth());
1525  double diffZsa = clusterZoa[0] - RadiansToDegrees(uAngle.GetInclination());
1526  double diffZsd = clusterZod[0] - RadiansToDegrees(sAngle.GetInclination());
1527 
1528  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1529  {
1530  clusterAoa[cIndex] -= diffAoa; //(7.5-12)
1531  clusterAod[cIndex] -= diffAod;
1532  clusterZoa[cIndex] -= diffZsa; //(7.5-17)
1533  clusterZod[cIndex] -= diffZsd;
1534  }
1535  }
1536 
1537  double sizeTemp = clusterZoa.size();
1538  for (uint8_t ind = 0; ind < 4; ind++)
1539  {
1540  DoubleVector angleDegree;
1541  switch (ind)
1542  {
1543  case 0:
1544  angleDegree = clusterAoa;
1545  break;
1546  case 1:
1547  angleDegree = clusterZoa;
1548  break;
1549  case 2:
1550  angleDegree = clusterAod;
1551  break;
1552  case 3:
1553  angleDegree = clusterZod;
1554  break;
1555  default:
1556  NS_FATAL_ERROR("Programming Error");
1557  }
1558  for (uint8_t nIndex = 0; nIndex < sizeTemp; nIndex++)
1559  {
1560  while (angleDegree[nIndex] > 360)
1561  {
1562  angleDegree[nIndex] -= 360;
1563  }
1564 
1565  while (angleDegree[nIndex] < 0)
1566  {
1567  angleDegree[nIndex] += 360;
1568  }
1569 
1570  if (ind == 1 || ind == 3)
1571  {
1572  if (angleDegree[nIndex] > 180)
1573  {
1574  angleDegree[nIndex] = 360 - angleDegree[nIndex];
1575  }
1576  }
1577  }
1578  switch (ind)
1579  {
1580  case 0:
1581  clusterAoa = angleDegree;
1582  break;
1583  case 1:
1584  clusterZoa = angleDegree;
1585  break;
1586  case 2:
1587  clusterAod = angleDegree;
1588  break;
1589  case 3:
1590  clusterZod = angleDegree;
1591  break;
1592  default:
1593  NS_FATAL_ERROR("Programming Error");
1594  }
1595  }
1596 
1597  DoubleVector attenuationDb;
1598  if (m_blockage)
1599  {
1600  attenuationDb = CalcAttenuationOfBlockage(channelParams, clusterAoa, clusterZoa);
1601  for (uint8_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
1602  {
1603  channelParams->m_clusterPower[cInd] =
1604  channelParams->m_clusterPower[cInd] / pow(10, attenuationDb[cInd] / 10.0);
1605  }
1606  }
1607  else
1608  {
1609  attenuationDb.push_back(0);
1610  }
1611 
1612  // store attenuation
1613  channelParams->m_attenuation_dB = attenuationDb;
1614 
1615  // Step 8: Coupling of rays within a cluster for both azimuth and elevation
1616  // shuffle all the arrays to perform random coupling
1618  channelParams->m_reducedClusterNumber,
1619  DoubleVector(table3gpp->m_raysPerCluster,
1620  0)); // rayAoaRadian[n][m], where n is cluster index, m is ray index
1622  channelParams->m_reducedClusterNumber,
1623  DoubleVector(table3gpp->m_raysPerCluster,
1624  0)); // rayAodRadian[n][m], where n is cluster index, m is ray index
1626  channelParams->m_reducedClusterNumber,
1627  DoubleVector(table3gpp->m_raysPerCluster,
1628  0)); // rayZoaRadian[n][m], where n is cluster index, m is ray index
1630  channelParams->m_reducedClusterNumber,
1631  DoubleVector(table3gpp->m_raysPerCluster,
1632  0)); // rayZodRadian[n][m], where n is cluster index, m is ray index
1633 
1634  for (uint8_t nInd = 0; nInd < channelParams->m_reducedClusterNumber; nInd++)
1635  {
1636  for (uint8_t mInd = 0; mInd < table3gpp->m_raysPerCluster; mInd++)
1637  {
1638  double tempAoa = clusterAoa[nInd] + table3gpp->m_cASA * offSetAlpha[mInd]; //(7.5-13)
1639  double tempZoa = clusterZoa[nInd] + table3gpp->m_cZSA * offSetAlpha[mInd]; //(7.5-18)
1640  std::tie(rayAoaRadian[nInd][mInd], rayZoaRadian[nInd][mInd]) =
1641  WrapAngles(DegreesToRadians(tempAoa), DegreesToRadians(tempZoa));
1642 
1643  double tempAod = clusterAod[nInd] + table3gpp->m_cASD * offSetAlpha[mInd]; //(7.5-13)
1644  double tempZod = clusterZod[nInd] +
1645  0.375 * pow(10, table3gpp->m_uLgZSD) * offSetAlpha[mInd]; //(7.5-20)
1646  std::tie(rayAodRadian[nInd][mInd], rayZodRadian[nInd][mInd]) =
1647  WrapAngles(DegreesToRadians(tempAod), DegreesToRadians(tempZod));
1648  }
1649  }
1650 
1651  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1652  {
1653  Shuffle(&rayAodRadian[cIndex][0], &rayAodRadian[cIndex][table3gpp->m_raysPerCluster]);
1654  Shuffle(&rayAoaRadian[cIndex][0], &rayAoaRadian[cIndex][table3gpp->m_raysPerCluster]);
1655  Shuffle(&rayZodRadian[cIndex][0], &rayZodRadian[cIndex][table3gpp->m_raysPerCluster]);
1656  Shuffle(&rayZoaRadian[cIndex][0], &rayZoaRadian[cIndex][table3gpp->m_raysPerCluster]);
1657  }
1658 
1659  // store values
1660  channelParams->m_rayAodRadian = rayAodRadian;
1661  channelParams->m_rayAoaRadian = rayAoaRadian;
1662  channelParams->m_rayZodRadian = rayZodRadian;
1663  channelParams->m_rayZoaRadian = rayZoaRadian;
1664 
1665  // Step 9: Generate the cross polarization power ratios
1666  // Step 10: Draw initial phases
1667  Double2DVector crossPolarizationPowerRatios; // vector containing the cross polarization power
1668  // ratios, as defined by 7.5-21
1669  Double3DVector clusterPhase; // rayAoaRadian[n][m], where n is cluster index, m is ray index
1670  for (uint8_t nInd = 0; nInd < channelParams->m_reducedClusterNumber; nInd++)
1671  {
1672  DoubleVector temp; // used to store the XPR values
1674  temp2; // used to store the PHI values for all the possible combination of polarization
1675  for (uint8_t mInd = 0; mInd < table3gpp->m_raysPerCluster; mInd++)
1676  {
1677  double uXprLinear = pow(10, table3gpp->m_uXpr / 10.0); // convert to linear
1678  double sigXprLinear = pow(10, table3gpp->m_sigXpr / 10.0); // convert to linear
1679 
1680  temp.push_back(
1681  std::pow(10, (m_normalRv->GetValue() * sigXprLinear + uXprLinear) / 10.0));
1682  DoubleVector temp3; // used to store the PHI values
1683  for (uint8_t pInd = 0; pInd < 4; pInd++)
1684  {
1685  temp3.push_back(m_uniformRv->GetValue(-1 * M_PI, M_PI));
1686  }
1687  temp2.push_back(temp3);
1688  }
1689  crossPolarizationPowerRatios.push_back(temp);
1690  clusterPhase.push_back(temp2);
1691  }
1692  // store the cluster phase
1693  channelParams->m_clusterPhase = clusterPhase;
1694  channelParams->m_crossPolarizationPowerRatios = crossPolarizationPowerRatios;
1695 
1696  uint8_t cluster1st = 0;
1697  uint8_t cluster2nd = 0; // first and second strongest cluster;
1698  double maxPower = 0;
1699  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1700  {
1701  if (maxPower < channelParams->m_clusterPower[cIndex])
1702  {
1703  maxPower = channelParams->m_clusterPower[cIndex];
1704  cluster1st = cIndex;
1705  }
1706  }
1707  channelParams->m_cluster1st = cluster1st;
1708  maxPower = 0;
1709  for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
1710  {
1711  if (maxPower < channelParams->m_clusterPower[cIndex] && cluster1st != cIndex)
1712  {
1713  maxPower = channelParams->m_clusterPower[cIndex];
1714  cluster2nd = cIndex;
1715  }
1716  }
1717  channelParams->m_cluster2nd = cluster2nd;
1718 
1719  NS_LOG_INFO("1st strongest cluster:" << +cluster1st
1720  << ", 2nd strongest cluster:" << +cluster2nd);
1721 
1722  // store the delays and the angles for the subclusters
1723  if (cluster1st == cluster2nd)
1724  {
1725  clusterDelay.push_back(clusterDelay[cluster1st] + 1.28 * table3gpp->m_cDS);
1726  clusterDelay.push_back(clusterDelay[cluster1st] + 2.56 * table3gpp->m_cDS);
1727 
1728  clusterAoa.push_back(clusterAoa[cluster1st]);
1729  clusterAoa.push_back(clusterAoa[cluster1st]);
1730 
1731  clusterZoa.push_back(clusterZoa[cluster1st]);
1732  clusterZoa.push_back(clusterZoa[cluster1st]);
1733 
1734  clusterAod.push_back(clusterAod[cluster1st]);
1735  clusterAod.push_back(clusterAod[cluster1st]);
1736 
1737  clusterZod.push_back(clusterZod[cluster1st]);
1738  clusterZod.push_back(clusterZod[cluster1st]);
1739  }
1740  else
1741  {
1742  double min;
1743  double max;
1744  if (cluster1st < cluster2nd)
1745  {
1746  min = cluster1st;
1747  max = cluster2nd;
1748  }
1749  else
1750  {
1751  min = cluster2nd;
1752  max = cluster1st;
1753  }
1754  clusterDelay.push_back(clusterDelay[min] + 1.28 * table3gpp->m_cDS);
1755  clusterDelay.push_back(clusterDelay[min] + 2.56 * table3gpp->m_cDS);
1756  clusterDelay.push_back(clusterDelay[max] + 1.28 * table3gpp->m_cDS);
1757  clusterDelay.push_back(clusterDelay[max] + 2.56 * table3gpp->m_cDS);
1758 
1759  clusterAoa.push_back(clusterAoa[min]);
1760  clusterAoa.push_back(clusterAoa[min]);
1761  clusterAoa.push_back(clusterAoa[max]);
1762  clusterAoa.push_back(clusterAoa[max]);
1763 
1764  clusterZoa.push_back(clusterZoa[min]);
1765  clusterZoa.push_back(clusterZoa[min]);
1766  clusterZoa.push_back(clusterZoa[max]);
1767  clusterZoa.push_back(clusterZoa[max]);
1768 
1769  clusterAod.push_back(clusterAod[min]);
1770  clusterAod.push_back(clusterAod[min]);
1771  clusterAod.push_back(clusterAod[max]);
1772  clusterAod.push_back(clusterAod[max]);
1773 
1774  clusterZod.push_back(clusterZod[min]);
1775  clusterZod.push_back(clusterZod[min]);
1776  clusterZod.push_back(clusterZod[max]);
1777  clusterZod.push_back(clusterZod[max]);
1778  }
1779 
1780  channelParams->m_delay = clusterDelay;
1781  channelParams->m_angle.clear();
1782  channelParams->m_angle.push_back(clusterAoa);
1783  channelParams->m_angle.push_back(clusterZoa);
1784  channelParams->m_angle.push_back(clusterAod);
1785  channelParams->m_angle.push_back(clusterZod);
1786 
1787  // Compute alpha and D as described in 3GPP TR 37.885 v15.3.0, Sec. 6.2.3
1788  // These terms account for an additional Doppler contribution due to the
1789  // presence of moving objects in the surrounding environment, such as in
1790  // vehicular scenarios.
1791  // This contribution is applied only to the delayed (reflected) paths and
1792  // must be properly configured by setting the value of
1793  // m_vScatt, which is defined as "maximum speed of the vehicle in the
1794  // layout".
1795  // By default, m_vScatt is set to 0, so there is no additional Doppler
1796  // contribution.
1797 
1798  DoubleVector dopplerTermAlpha;
1799  DoubleVector dopplerTermD;
1800 
1801  // 2 or 4 is added to account for additional subrays for the 1st and 2nd clusters, if there is
1802  // only one cluster then would be added 2 more subrays (see creation of Husn channel matrix)
1803  uint8_t updatedClusterNumber = (channelParams->m_reducedClusterNumber == 1)
1804  ? channelParams->m_reducedClusterNumber + 2
1805  : channelParams->m_reducedClusterNumber + 4;
1806 
1807  for (uint8_t cIndex = 0; cIndex < updatedClusterNumber; cIndex++)
1808  {
1809  double alpha = 0;
1810  double D = 0;
1811  if (cIndex != 0)
1812  {
1813  alpha = m_uniformRvDoppler->GetValue(-1, 1);
1815  }
1816  dopplerTermAlpha.push_back(alpha);
1817  dopplerTermD.push_back(D);
1818  }
1819  channelParams->m_alpha = dopplerTermAlpha;
1820  channelParams->m_D = dopplerTermD;
1821 
1822  return channelParams;
1823 }
1824 
1827  Ptr<const ParamsTable> table3gpp,
1828  const Ptr<const MobilityModel> sMob,
1829  const Ptr<const MobilityModel> uMob,
1830  Ptr<const PhasedArrayModel> sAntenna,
1831  Ptr<const PhasedArrayModel> uAntenna) const
1832 {
1833  NS_LOG_FUNCTION(this);
1834 
1835  NS_ASSERT_MSG(m_frequency > 0.0, "Set the operating frequency first!");
1836 
1837  // create a channel matrix instance
1838  Ptr<ChannelMatrix> channelMatrix = Create<ChannelMatrix>();
1839  channelMatrix->m_generatedTime = Simulator::Now();
1840  // save in which order is generated this matrix
1841  channelMatrix->m_nodeIds =
1842  std::make_pair(sMob->GetObject<Node>()->GetId(), uMob->GetObject<Node>()->GetId());
1843  // check if channelParams structure is generated in direction s-to-u or u-to-s
1844  bool isSameDirection = (channelParams->m_nodeIds == channelMatrix->m_nodeIds);
1845 
1850 
1851  // if channel params is generated in the same direction in which we
1852  // generate the channel matrix, angles and zenith od departure and arrival are ok,
1853  // just set them to corresponding variable that will be used for the generation
1854  // of channel matrix, otherwise we need to flip angles and zeniths of departure and arrival
1855  if (isSameDirection)
1856  {
1857  rayAodRadian = channelParams->m_rayAodRadian;
1858  rayAoaRadian = channelParams->m_rayAoaRadian;
1859  rayZodRadian = channelParams->m_rayZodRadian;
1860  rayZoaRadian = channelParams->m_rayZoaRadian;
1861  }
1862  else
1863  {
1864  rayAodRadian = channelParams->m_rayAoaRadian;
1865  rayAoaRadian = channelParams->m_rayAodRadian;
1866  rayZodRadian = channelParams->m_rayZoaRadian;
1867  rayZoaRadian = channelParams->m_rayZodRadian;
1868  }
1869 
1870  // Step 11: Generate channel coefficients for each cluster n and each receiver
1871  // and transmitter element pair u,s.
1872  // where n is cluster index, u and s are receive and transmit antenna element.
1873  size_t uSize = uAntenna->GetNumElems();
1874  size_t sSize = sAntenna->GetNumElems();
1875 
1876  // NOTE: Since each of the strongest 2 clusters are divided into 3 sub-clusters,
1877  // the total cluster will generally be numReducedCLuster + 4.
1878  // However, it might be that m_cluster1st = m_cluster2nd. In this case the
1879  // total number of clusters will be numReducedCLuster + 2.
1880  uint16_t numOverallCluster = (channelParams->m_cluster1st != channelParams->m_cluster2nd)
1881  ? channelParams->m_reducedClusterNumber + 4
1882  : channelParams->m_reducedClusterNumber + 2;
1883  Complex3DVector hUsn(uSize, sSize, numOverallCluster); // channel coefficient hUsn (u, s, n);
1884  NS_ASSERT(channelParams->m_reducedClusterNumber <= channelParams->m_clusterPhase.size());
1885  NS_ASSERT(channelParams->m_reducedClusterNumber <= channelParams->m_clusterPower.size());
1886  NS_ASSERT(channelParams->m_reducedClusterNumber <=
1887  channelParams->m_crossPolarizationPowerRatios.size());
1888  NS_ASSERT(channelParams->m_reducedClusterNumber <= rayZoaRadian.size());
1889  NS_ASSERT(channelParams->m_reducedClusterNumber <= rayZodRadian.size());
1890  NS_ASSERT(channelParams->m_reducedClusterNumber <= rayAoaRadian.size());
1891  NS_ASSERT(channelParams->m_reducedClusterNumber <= rayAodRadian.size());
1892  NS_ASSERT(table3gpp->m_raysPerCluster <= channelParams->m_clusterPhase[0].size());
1893  NS_ASSERT(table3gpp->m_raysPerCluster <=
1894  channelParams->m_crossPolarizationPowerRatios[0].size());
1895  NS_ASSERT(table3gpp->m_raysPerCluster <= rayZoaRadian[0].size());
1896  NS_ASSERT(table3gpp->m_raysPerCluster <= rayZodRadian[0].size());
1897  NS_ASSERT(table3gpp->m_raysPerCluster <= rayAoaRadian[0].size());
1898  NS_ASSERT(table3gpp->m_raysPerCluster <= rayAodRadian[0].size());
1899 
1900  double x = sMob->GetPosition().x - uMob->GetPosition().x;
1901  double y = sMob->GetPosition().y - uMob->GetPosition().y;
1902  double distance2D = sqrt(x * x + y * y);
1903  // NOTE we assume hUT = min (height(a), height(b)) and
1904  // hBS = max (height (a), height (b))
1905  double hUt = std::min(sMob->GetPosition().z, uMob->GetPosition().z);
1906  double hBs = std::max(sMob->GetPosition().z, uMob->GetPosition().z);
1907  // compute the 3D distance using eq. 7.4-1
1908  double distance3D = std::sqrt(distance2D * distance2D + (hBs - hUt) * (hBs - hUt));
1909 
1910  Angles sAngle(uMob->GetPosition(), sMob->GetPosition());
1911  Angles uAngle(sMob->GetPosition(), uMob->GetPosition());
1912 
1913  Double2DVector sinCosA; // cached multiplications of sin and cos of the ZoA and AoA angles
1914  Double2DVector sinSinA; // cached multiplications of sines of the ZoA and AoA angles
1915  Double2DVector cosZoA; // cached cos of the ZoA angle
1916  Double2DVector sinCosD; // cached multiplications of sin and cos of the ZoD and AoD angles
1917  Double2DVector sinSinD; // cached multiplications of the cosines of the ZoA and AoA angles
1918  Double2DVector cosZoD; // cached cos of the ZoD angle
1919 
1920  // contains part of the ray expression, cached as independent from the u- and s-indexes,
1921  // but calculate it for different polarization angles of s and u
1922  std::map<std::pair<uint8_t, uint8_t>, Complex2DVector> raysPreComp;
1923  for (size_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa)
1924  {
1925  for (size_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa)
1926  {
1927  raysPreComp[std::make_pair(polSa, polUa)] =
1928  Complex2DVector(channelParams->m_reducedClusterNumber, table3gpp->m_raysPerCluster);
1929  }
1930  }
1931 
1932  // resize to appropriate dimensions
1933  sinCosA.resize(channelParams->m_reducedClusterNumber);
1934  sinSinA.resize(channelParams->m_reducedClusterNumber);
1935  cosZoA.resize(channelParams->m_reducedClusterNumber);
1936  sinCosD.resize(channelParams->m_reducedClusterNumber);
1937  sinSinD.resize(channelParams->m_reducedClusterNumber);
1938  cosZoD.resize(channelParams->m_reducedClusterNumber);
1939  for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
1940  {
1941  sinCosA[nIndex].resize(table3gpp->m_raysPerCluster);
1942  sinSinA[nIndex].resize(table3gpp->m_raysPerCluster);
1943  cosZoA[nIndex].resize(table3gpp->m_raysPerCluster);
1944  sinCosD[nIndex].resize(table3gpp->m_raysPerCluster);
1945  sinSinD[nIndex].resize(table3gpp->m_raysPerCluster);
1946  cosZoD[nIndex].resize(table3gpp->m_raysPerCluster);
1947  }
1948  // pre-compute the terms which are independent from uIndex and sIndex
1949  for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
1950  {
1951  for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
1952  {
1953  DoubleVector initialPhase = channelParams->m_clusterPhase[nIndex][mIndex];
1954  NS_ASSERT(4 <= initialPhase.size());
1955  double k = channelParams->m_crossPolarizationPowerRatios[nIndex][mIndex];
1956 
1957  // cache the component of the "rays" terms which depend on the random angle of arrivals
1958  // and departures and initial phases only
1959  for (uint8_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa)
1960  {
1961  auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern(
1962  Angles(channelParams->m_rayAoaRadian[nIndex][mIndex],
1963  channelParams->m_rayZoaRadian[nIndex][mIndex]),
1964  polUa);
1965  for (uint8_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa)
1966  {
1967  auto [txFieldPatternPhi, txFieldPatternTheta] =
1968  sAntenna->GetElementFieldPattern(
1969  Angles(channelParams->m_rayAodRadian[nIndex][mIndex],
1970  channelParams->m_rayZodRadian[nIndex][mIndex]),
1971  polSa);
1972  raysPreComp[std::make_pair(polSa, polUa)](nIndex, mIndex) =
1973  std::complex<double>(cos(initialPhase[0]), sin(initialPhase[0])) *
1974  rxFieldPatternTheta * txFieldPatternTheta +
1975  std::complex<double>(cos(initialPhase[1]), sin(initialPhase[1])) *
1976  std::sqrt(1.0 / k) * rxFieldPatternTheta * txFieldPatternPhi +
1977  std::complex<double>(cos(initialPhase[2]), sin(initialPhase[2])) *
1978  std::sqrt(1.0 / k) * rxFieldPatternPhi * txFieldPatternTheta +
1979  std::complex<double>(cos(initialPhase[3]), sin(initialPhase[3])) *
1980  rxFieldPatternPhi * txFieldPatternPhi;
1981  }
1982  }
1983 
1984  // cache the component of the "rxPhaseDiff" terms which depend on the random angle of
1985  // arrivals only
1986  double sinRayZoa = sin(rayZoaRadian[nIndex][mIndex]);
1987  double sinRayAoa = sin(rayAoaRadian[nIndex][mIndex]);
1988  double cosRayAoa = cos(rayAoaRadian[nIndex][mIndex]);
1989  sinCosA[nIndex][mIndex] = sinRayZoa * cosRayAoa;
1990  sinSinA[nIndex][mIndex] = sinRayZoa * sinRayAoa;
1991  cosZoA[nIndex][mIndex] = cos(rayZoaRadian[nIndex][mIndex]);
1992 
1993  // cache the component of the "txPhaseDiff" terms which depend on the random angle of
1994  // departure only
1995  double sinRayZod = sin(rayZodRadian[nIndex][mIndex]);
1996  double sinRayAod = sin(rayAodRadian[nIndex][mIndex]);
1997  double cosRayAod = cos(rayAodRadian[nIndex][mIndex]);
1998  sinCosD[nIndex][mIndex] = sinRayZod * cosRayAod;
1999  sinSinD[nIndex][mIndex] = sinRayZod * sinRayAod;
2000  cosZoD[nIndex][mIndex] = cos(rayZodRadian[nIndex][mIndex]);
2001  }
2002  }
2003 
2004  // The following for loops computes the channel coefficients
2005  // Keeps track of how many sub-clusters have been added up to now
2006  uint8_t numSubClustersAdded = 0;
2007  for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
2008  {
2009  for (size_t uIndex = 0; uIndex < uSize; uIndex++)
2010  {
2011  Vector uLoc = uAntenna->GetElementLocation(uIndex);
2012 
2013  for (size_t sIndex = 0; sIndex < sSize; sIndex++)
2014  {
2015  Vector sLoc = sAntenna->GetElementLocation(sIndex);
2016  // Compute the N-2 weakest cluster, assuming 0 slant angle and a
2017  // polarization slant angle configured in the array (7.5-22)
2018  if (nIndex != channelParams->m_cluster1st && nIndex != channelParams->m_cluster2nd)
2019  {
2020  std::complex<double> rays(0, 0);
2021  for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
2022  {
2023  // lambda_0 is accounted in the antenna spacing uLoc and sLoc.
2024  double rxPhaseDiff =
2025  2 * M_PI *
2026  (sinCosA[nIndex][mIndex] * uLoc.x + sinSinA[nIndex][mIndex] * uLoc.y +
2027  cosZoA[nIndex][mIndex] * uLoc.z);
2028 
2029  double txPhaseDiff =
2030  2 * M_PI *
2031  (sinCosD[nIndex][mIndex] * sLoc.x + sinSinD[nIndex][mIndex] * sLoc.y +
2032  cosZoD[nIndex][mIndex] * sLoc.z);
2033  // NOTE Doppler is computed in the CalcBeamformingGain function and is
2034  // simplified to only account for the center angle of each cluster.
2035  rays += raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex),
2036  uAntenna->GetElemPol(uIndex))](nIndex,
2037  mIndex) *
2038  std::complex<double>(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
2039  std::complex<double>(cos(txPhaseDiff), sin(txPhaseDiff));
2040  }
2041  rays *=
2042  sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
2043  hUsn(uIndex, sIndex, nIndex) = rays;
2044  }
2045  else //(7.5-28)
2046  {
2047  std::complex<double> raysSub1(0, 0);
2048  std::complex<double> raysSub2(0, 0);
2049  std::complex<double> raysSub3(0, 0);
2050 
2051  for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
2052  {
2053  // ZML:Just remind me that the angle offsets for the 3 subclusters were not
2054  // generated correctly.
2055  double rxPhaseDiff =
2056  2 * M_PI *
2057  (sinCosA[nIndex][mIndex] * uLoc.x + sinSinA[nIndex][mIndex] * uLoc.y +
2058  cosZoA[nIndex][mIndex] * uLoc.z);
2059 
2060  double txPhaseDiff =
2061  2 * M_PI *
2062  (sinCosD[nIndex][mIndex] * sLoc.x + sinSinD[nIndex][mIndex] * sLoc.y +
2063  cosZoD[nIndex][mIndex] * sLoc.z);
2064 
2065  std::complex<double> raySub =
2066  raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex),
2067  uAntenna->GetElemPol(uIndex))](nIndex,
2068  mIndex) *
2069  std::complex<double>(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
2070  std::complex<double>(cos(txPhaseDiff), sin(txPhaseDiff));
2071 
2072  switch (mIndex)
2073  {
2074  case 9:
2075  case 10:
2076  case 11:
2077  case 12:
2078  case 17:
2079  case 18:
2080  raysSub2 += raySub;
2081  break;
2082  case 13:
2083  case 14:
2084  case 15:
2085  case 16:
2086  raysSub3 += raySub;
2087  break;
2088  default: // case 1,2,3,4,5,6,7,8,19,20
2089  raysSub1 += raySub;
2090  break;
2091  }
2092  }
2093  raysSub1 *=
2094  sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
2095  raysSub2 *=
2096  sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
2097  raysSub3 *=
2098  sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
2099  hUsn(uIndex, sIndex, nIndex) = raysSub1;
2100  hUsn(uIndex,
2101  sIndex,
2102  channelParams->m_reducedClusterNumber + numSubClustersAdded) = raysSub2;
2103  hUsn(uIndex,
2104  sIndex,
2105  channelParams->m_reducedClusterNumber + numSubClustersAdded + 1) =
2106  raysSub3;
2107  }
2108  }
2109  }
2110  if (nIndex == channelParams->m_cluster1st || nIndex == channelParams->m_cluster2nd)
2111  {
2112  numSubClustersAdded += 2;
2113  }
2114  }
2115 
2116  if (channelParams->m_losCondition == ChannelCondition::LOS) //(7.5-29) && (7.5-30)
2117  {
2118  double lambda = 3.0e8 / m_frequency; // the wavelength of the carrier frequency
2119  std::complex<double> phaseDiffDueToDistance(cos(-2 * M_PI * distance3D / lambda),
2120  sin(-2 * M_PI * distance3D / lambda));
2121 
2122  const double sinUAngleIncl = sin(uAngle.GetInclination());
2123  const double cosUAngleIncl = cos(uAngle.GetInclination());
2124  const double sinUAngleAz = sin(uAngle.GetAzimuth());
2125  const double cosUAngleAz = cos(uAngle.GetAzimuth());
2126  const double sinSAngleIncl = sin(sAngle.GetInclination());
2127  const double cosSAngleIncl = cos(sAngle.GetInclination());
2128  const double sinSAngleAz = sin(sAngle.GetAzimuth());
2129  const double cosSAngleAz = cos(sAngle.GetAzimuth());
2130 
2131  for (size_t uIndex = 0; uIndex < uSize; uIndex++)
2132  {
2133  Vector uLoc = uAntenna->GetElementLocation(uIndex);
2134  double rxPhaseDiff = 2 * M_PI *
2135  (sinUAngleIncl * cosUAngleAz * uLoc.x +
2136  sinUAngleIncl * sinUAngleAz * uLoc.y + cosUAngleIncl * uLoc.z);
2137 
2138  for (size_t sIndex = 0; sIndex < sSize; sIndex++)
2139  {
2140  Vector sLoc = sAntenna->GetElementLocation(sIndex);
2141  std::complex<double> ray(0, 0);
2142  double txPhaseDiff =
2143  2 * M_PI *
2144  (sinSAngleIncl * cosSAngleAz * sLoc.x + sinSAngleIncl * sinSAngleAz * sLoc.y +
2145  cosSAngleIncl * sLoc.z);
2146 
2147  auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern(
2148  Angles(uAngle.GetAzimuth(), uAngle.GetInclination()),
2149  uAntenna->GetElemPol(uIndex));
2150  auto [txFieldPatternPhi, txFieldPatternTheta] = sAntenna->GetElementFieldPattern(
2151  Angles(sAngle.GetAzimuth(), sAngle.GetInclination()),
2152  sAntenna->GetElemPol(sIndex));
2153 
2154  ray = (rxFieldPatternTheta * txFieldPatternTheta -
2155  rxFieldPatternPhi * txFieldPatternPhi) *
2156  phaseDiffDueToDistance *
2157  std::complex<double>(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
2158  std::complex<double>(cos(txPhaseDiff), sin(txPhaseDiff));
2159 
2160  double kLinear = pow(10, channelParams->m_K_factor / 10.0);
2161  // the LOS path should be attenuated if blockage is enabled.
2162  hUsn(uIndex, sIndex, 0) =
2163  sqrt(1.0 / (kLinear + 1)) * hUsn(uIndex, sIndex, 0) +
2164  sqrt(kLinear / (1 + kLinear)) * ray /
2165  pow(10,
2166  channelParams->m_attenuation_dB[0] / 10.0); //(7.5-30) for tau = tau1
2167  for (size_t nIndex = 1; nIndex < hUsn.GetNumPages(); nIndex++)
2168  {
2169  hUsn(uIndex, sIndex, nIndex) *=
2170  sqrt(1.0 / (kLinear + 1)); //(7.5-30) for tau = tau2...tauN
2171  }
2172  }
2173  }
2174  }
2175 
2176  NS_LOG_DEBUG("Husn (sAntenna, uAntenna):" << sAntenna->GetId() << ", " << uAntenna->GetId());
2177  for (size_t cIndex = 0; cIndex < hUsn.GetNumPages(); cIndex++)
2178  {
2179  for (size_t rowIdx = 0; rowIdx < hUsn.GetNumRows(); rowIdx++)
2180  {
2181  for (size_t colIdx = 0; colIdx < hUsn.GetNumCols(); colIdx++)
2182  {
2183  NS_LOG_DEBUG(" " << hUsn(rowIdx, colIdx, cIndex) << ",");
2184  }
2185  }
2186  }
2187 
2188  NS_LOG_INFO("size of coefficient matrix (rows, columns, clusters) = ("
2189  << hUsn.GetNumRows() << ", " << hUsn.GetNumCols() << ", " << hUsn.GetNumPages()
2190  << ")");
2191  channelMatrix->m_channel = hUsn;
2192  return channelMatrix;
2193 }
2194 
2195 std::pair<double, double>
2196 ThreeGppChannelModel::WrapAngles(double azimuthRad, double inclinationRad)
2197 {
2198  inclinationRad = WrapTo2Pi(inclinationRad);
2199  if (inclinationRad > M_PI)
2200  {
2201  // inclination must be in [0, M_PI]
2202  inclinationRad -= M_PI;
2203  azimuthRad += M_PI;
2204  }
2205 
2206  azimuthRad = WrapTo2Pi(azimuthRad);
2207 
2208  NS_ASSERT_MSG(0 <= inclinationRad && inclinationRad <= M_PI,
2209  "inclinationRad=" << inclinationRad << " not valid, should be in [0, pi]");
2210  NS_ASSERT_MSG(0 <= azimuthRad && azimuthRad <= 2 * M_PI,
2211  "azimuthRad=" << azimuthRad << " not valid, should be in [0, 2*pi]");
2212 
2213  return std::make_pair(azimuthRad, inclinationRad);
2214 }
2215 
2219  const DoubleVector& clusterAOA,
2220  const DoubleVector& clusterZOA) const
2221 {
2222  NS_LOG_FUNCTION(this);
2223 
2224  auto clusterNum = clusterAOA.size();
2225 
2226  // Initial power attenuation for all clusters to be 0 dB
2227  DoubleVector powerAttenuation(clusterNum, 0);
2228 
2229  // step a: the number of non-self blocking blockers is stored in m_numNonSelfBlocking.
2230 
2231  // step b:Generate the size and location of each blocker
2232  // generate self blocking (i.e., for blockage from the human body)
2233  // table 7.6.4.1-1 Self-blocking region parameters.
2234  // Defaults: landscape mode
2235  double phiSb = 40;
2236  double xSb = 160;
2237  double thetaSb = 110;
2238  double ySb = 75;
2239  if (m_portraitMode)
2240  {
2241  phiSb = 260;
2242  xSb = 120;
2243  thetaSb = 100;
2244  ySb = 80;
2245  }
2246 
2247  // generate or update non-self blocking
2248  if (channelParams->m_nonSelfBlocking.empty()) // generate new blocking regions
2249  {
2250  for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
2251  {
2252  // draw value from table 7.6.4.1-2 Blocking region parameters
2253  DoubleVector table;
2254  table.push_back(m_normalRv->GetValue()); // phi_k: store the normal RV that will be
2255  // mapped to uniform (0,360) later.
2256  if (m_scenario == "InH-OfficeMixed" || m_scenario == "InH-OfficeOpen")
2257  {
2258  table.push_back(m_uniformRv->GetValue(15, 45)); // x_k
2259  table.push_back(90); // Theta_k
2260  table.push_back(m_uniformRv->GetValue(5, 15)); // y_k
2261  table.push_back(2); // r
2262  }
2263  else
2264  {
2265  table.push_back(m_uniformRv->GetValue(5, 15)); // x_k
2266  table.push_back(90); // Theta_k
2267  table.push_back(5); // y_k
2268  table.push_back(10); // r
2269  }
2270  channelParams->m_nonSelfBlocking.push_back(table);
2271  }
2272  }
2273  else
2274  {
2275  double deltaX = sqrt(pow(channelParams->m_preLocUT.x - channelParams->m_locUT.x, 2) +
2276  pow(channelParams->m_preLocUT.y - channelParams->m_locUT.y, 2));
2277  // if deltaX and speed are both 0, the autocorrelation is 1, skip updating
2278  if (deltaX > 1e-6 || m_blockerSpeed > 1e-6)
2279  {
2280  double corrDis;
2281  // draw value from table 7.6.4.1-4: Spatial correlation distance for different
2282  // m_scenarios.
2283  if (m_scenario == "InH-OfficeMixed" || m_scenario == "InH-OfficeOpen")
2284  {
2285  // InH, correlation distance = 5;
2286  corrDis = 5;
2287  }
2288  else
2289  {
2290  if (channelParams->m_o2iCondition == ChannelCondition::O2I) // outdoor to indoor
2291  {
2292  corrDis = 5;
2293  }
2294  else // LOS or NLOS
2295  {
2296  corrDis = 10;
2297  }
2298  }
2299  double R;
2300  if (m_blockerSpeed > 1e-6) // speed not equal to 0
2301  {
2302  double corrT = corrDis / m_blockerSpeed;
2303  R = exp(-1 * (deltaX / corrDis +
2304  (Now().GetSeconds() - channelParams->m_generatedTime.GetSeconds()) /
2305  corrT));
2306  }
2307  else
2308  {
2309  R = exp(-1 * (deltaX / corrDis));
2310  }
2311 
2312  NS_LOG_INFO("Distance change:"
2313  << deltaX << " Speed:" << m_blockerSpeed << " Time difference:"
2314  << Now().GetSeconds() - channelParams->m_generatedTime.GetSeconds()
2315  << " correlation:" << R);
2316 
2317  // In order to generate correlated uniform random variables, we first generate
2318  // correlated normal random variables and map the normal RV to uniform RV. Notice the
2319  // correlation will change if the RV is transformed from normal to uniform. To
2320  // compensate the distortion, the correlation of the normal RV is computed such that the
2321  // uniform RV would have the desired correlation when transformed from normal RV.
2322 
2323  // The following formula was obtained from MATLAB numerical simulation.
2324 
2325  if (R * R * (-0.069) + R * 1.074 - 0.002 <
2326  1) // transform only when the correlation of normal RV is smaller than 1
2327  {
2328  R = R * R * (-0.069) + R * 1.074 - 0.002;
2329  }
2330  for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
2331  {
2332  // Generate a new correlated normal RV with the following formula
2333  channelParams->m_nonSelfBlocking[blockInd][PHI_INDEX] =
2334  R * channelParams->m_nonSelfBlocking[blockInd][PHI_INDEX] +
2335  sqrt(1 - R * R) * m_normalRv->GetValue();
2336  }
2337  }
2338  }
2339 
2340  // step c: Determine the attenuation of each blocker due to blockers
2341  for (std::size_t cInd = 0; cInd < clusterNum; cInd++)
2342  {
2343  NS_ASSERT_MSG(clusterAOA[cInd] >= 0 && clusterAOA[cInd] <= 360,
2344  "the AOA should be the range of [0,360]");
2345  NS_ASSERT_MSG(clusterZOA[cInd] >= 0 && clusterZOA[cInd] <= 180,
2346  "the ZOA should be the range of [0,180]");
2347 
2348  // check self blocking
2349  NS_LOG_INFO("AOA=" << clusterAOA[cInd] << " Block Region[" << phiSb - xSb / 2.0 << ","
2350  << phiSb + xSb / 2.0 << "]");
2351  NS_LOG_INFO("ZOA=" << clusterZOA[cInd] << " Block Region[" << thetaSb - ySb / 2.0 << ","
2352  << thetaSb + ySb / 2.0 << "]");
2353  if (std::abs(clusterAOA[cInd] - phiSb) < (xSb / 2.0) &&
2354  std::abs(clusterZOA[cInd] - thetaSb) < (ySb / 2.0))
2355  {
2356  powerAttenuation[cInd] += 30; // attenuate by 30 dB.
2357  NS_LOG_INFO("Cluster[" << +cInd
2358  << "] is blocked by self blocking region and reduce 30 dB power,"
2359  "the attenuation is ["
2360  << powerAttenuation[cInd] << " dB]");
2361  }
2362 
2363  // check non-self blocking
2364  for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
2365  {
2366  // The normal RV is transformed to uniform RV with the desired correlation.
2367  double phiK =
2368  (0.5 * erfc(-1 * channelParams->m_nonSelfBlocking[blockInd][PHI_INDEX] / sqrt(2))) *
2369  360;
2370  while (phiK > 360)
2371  {
2372  phiK -= 360;
2373  }
2374 
2375  while (phiK < 0)
2376  {
2377  phiK += 360;
2378  }
2379 
2380  double xK = channelParams->m_nonSelfBlocking[blockInd][X_INDEX];
2381  double thetaK = channelParams->m_nonSelfBlocking[blockInd][THETA_INDEX];
2382  double yK = channelParams->m_nonSelfBlocking[blockInd][Y_INDEX];
2383 
2384  NS_LOG_INFO("AOA=" << clusterAOA[cInd] << " Block Region[" << phiK - xK << ","
2385  << phiK + xK << "]");
2386  NS_LOG_INFO("ZOA=" << clusterZOA[cInd] << " Block Region[" << thetaK - yK << ","
2387  << thetaK + yK << "]");
2388 
2389  if (std::abs(clusterAOA[cInd] - phiK) < (xK) &&
2390  std::abs(clusterZOA[cInd] - thetaK) < (yK))
2391  {
2392  double A1 = clusterAOA[cInd] - (phiK + xK / 2.0); //(7.6-24)
2393  double A2 = clusterAOA[cInd] - (phiK - xK / 2.0); //(7.6-25)
2394  double Z1 = clusterZOA[cInd] - (thetaK + yK / 2.0); //(7.6-26)
2395  double Z2 = clusterZOA[cInd] - (thetaK - yK / 2.0); //(7.6-27)
2396  int signA1;
2397  int signA2;
2398  int signZ1;
2399  int signZ2;
2400  // draw sign for the above parameters according to table 7.6.4.1-3 Description of
2401  // signs
2402  if (xK / 2.0 < clusterAOA[cInd] - phiK && clusterAOA[cInd] - phiK <= xK)
2403  {
2404  signA1 = -1;
2405  }
2406  else
2407  {
2408  signA1 = 1;
2409  }
2410  if (-1 * xK < clusterAOA[cInd] - phiK && clusterAOA[cInd] - phiK <= -1 * xK / 2.0)
2411  {
2412  signA2 = -1;
2413  }
2414  else
2415  {
2416  signA2 = 1;
2417  }
2418 
2419  if (yK / 2.0 < clusterZOA[cInd] - thetaK && clusterZOA[cInd] - thetaK <= yK)
2420  {
2421  signZ1 = -1;
2422  }
2423  else
2424  {
2425  signZ1 = 1;
2426  }
2427  if (-1 * yK < clusterZOA[cInd] - thetaK &&
2428  clusterZOA[cInd] - thetaK <= -1 * yK / 2.0)
2429  {
2430  signZ2 = -1;
2431  }
2432  else
2433  {
2434  signZ2 = 1;
2435  }
2436  double lambda = 3e8 / m_frequency;
2437  double fA1 =
2438  atan(signA1 * M_PI / 2.0 *
2439  sqrt(M_PI / lambda * channelParams->m_nonSelfBlocking[blockInd][R_INDEX] *
2440  (1.0 / cos(DegreesToRadians(A1)) - 1))) /
2441  M_PI; //(7.6-23)
2442  double fA2 =
2443  atan(signA2 * M_PI / 2.0 *
2444  sqrt(M_PI / lambda * channelParams->m_nonSelfBlocking[blockInd][R_INDEX] *
2445  (1.0 / cos(DegreesToRadians(A2)) - 1))) /
2446  M_PI;
2447  double fZ1 =
2448  atan(signZ1 * M_PI / 2.0 *
2449  sqrt(M_PI / lambda * channelParams->m_nonSelfBlocking[blockInd][R_INDEX] *
2450  (1.0 / cos(DegreesToRadians(Z1)) - 1))) /
2451  M_PI;
2452  double fZ2 =
2453  atan(signZ2 * M_PI / 2.0 *
2454  sqrt(M_PI / lambda * channelParams->m_nonSelfBlocking[blockInd][R_INDEX] *
2455  (1.0 / cos(DegreesToRadians(Z2)) - 1))) /
2456  M_PI;
2457  double lDb = -20 * log10(1 - (fA1 + fA2) * (fZ1 + fZ2)); //(7.6-22)
2458  powerAttenuation[cInd] += lDb;
2459  NS_LOG_INFO("Cluster[" << +cInd << "] is blocked by no-self blocking, the loss is ["
2460  << lDb << "] dB");
2461  }
2462  }
2463  }
2464  return powerAttenuation;
2465 }
2466 
2467 void
2468 ThreeGppChannelModel::Shuffle(double* first, double* last) const
2469 {
2470  for (auto i = (last - first) - 1; i > 0; --i)
2471  {
2473  }
2474 }
2475 
2476 int64_t
2478 {
2479  NS_LOG_FUNCTION(this << stream);
2480  m_normalRv->SetStream(stream);
2481  m_uniformRv->SetStream(stream + 1);
2482  m_uniformRvShuffle->SetStream(stream + 2);
2483  m_uniformRvDoppler->SetStream(stream + 3);
2484  return 4;
2485 }
2486 
2487 } // namespace ns3
#define min(a, b)
Definition: 80211b.c:41
double f(double x, void *params)
Definition: 80211b.c:70
#define max(a, b)
Definition: 80211b.c:42
Class holding the azimuth and inclination angles of spherical coordinates.
Definition: angles.h:118
double GetInclination() const
Getter for inclination angle.
Definition: angles.cc:246
double GetAzimuth() const
Getter for azimuth angle.
Definition: angles.cc:240
This class can be used to hold variables of floating point type such as 'double' or 'float'.
Definition: double.h:42
Hold a signed integer type.
Definition: integer.h:45
MatrixArray class inherits ValArray class and provides additional interfaces to ValArray which enable...
Definition: matrix-array.h:83
This is an interface for a channel model that can be described by a channel matrix,...
std::vector< double > DoubleVector
Type definition for vectors of doubles.
ComplexMatrixArray Complex2DVector
Create an alias for 2D complex vectors.
std::vector< Double2DVector > Double3DVector
Type definition for 3D matrices of doubles.
std::vector< DoubleVector > Double2DVector
Type definition for matrices of doubles.
static uint64_t GetKey(uint32_t a, uint32_t b)
Generate a unique value for the pair of unsigned integer of 32 bits, where the order does not matter,...
A network Node.
Definition: node.h:57
uint32_t GetId() const
Definition: node.cc:117
Hold objects of type Ptr<T>.
Definition: pointer.h:37
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
void SetStream(int64_t stream)
Specifies the stream number for the RngStream.
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
Hold variables of type string.
Definition: string.h:56
DoubleVector CalcAttenuationOfBlockage(const Ptr< ThreeGppChannelModel::ThreeGppChannelParams > channelParams, const DoubleVector &clusterAOA, const DoubleVector &clusterZOA) const
Applies the blockage model A described in 3GPP TR 38.901.
int64_t AssignStreams(int64_t stream)
Assign a fixed random variable stream number to the random variables used by this model.
bool m_portraitMode
true if portrait mode, false if landscape
bool ChannelParamsNeedsUpdate(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ChannelCondition > channelCondition) const
Check if the channel params has to be updated.
virtual Ptr< const ParamsTable > GetThreeGppTable(Ptr< const ChannelCondition > channelCondition, double hBS, double hUT, double distance2D) const
Get the parameters needed to apply the channel generation procedure.
Ptr< NormalRandomVariable > m_normalRv
normal random variable
static const uint8_t Y_INDEX
index of the Y value in the m_nonSelfBlocking array
bool m_blockage
enables the blockage model A
Ptr< const ChannelParams > GetParams(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const override
Looks for the channel params associated to the aMob and bMob pair in m_channelParamsMap.
~ThreeGppChannelModel() override
Destructor.
bool ChannelMatrixNeedsUpdate(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ChannelMatrix > channelMatrix)
Check if the channel matrix has to be updated (it needs update when the channel params generation tim...
static const uint8_t THETA_INDEX
index of the THETA value in the m_nonSelfBlocking array
std::unordered_map< uint64_t, Ptr< ThreeGppChannelParams > > m_channelParamsMap
map containing the common channel parameters per pair of nodes, the key of this map is reciprocal and...
static std::pair< double, double > WrapAngles(double azimuthRad, double inclinationRad)
Wrap an (azimuth, inclination) angle pair in a valid range.
double m_blockerSpeed
the blocker speed
Ptr< const ChannelMatrix > GetChannel(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, Ptr< const PhasedArrayModel > aAntenna, Ptr< const PhasedArrayModel > bAntenna) override
Looks for the channel matrix associated to the aMob and bMob pair in m_channelMatrixMap.
void SetFrequency(double f)
Sets the center frequency of the model.
std::unordered_map< uint64_t, Ptr< ChannelMatrix > > m_channelMatrixMap
map containing the channel realizations per pair of PhasedAntennaArray instances, the key of this map...
Ptr< UniformRandomVariable > m_uniformRv
uniform random variable
void DoDispose() override
Destructor implementation.
void SetScenario(const std::string &scenario)
Sets the propagation scenario.
void SetChannelConditionModel(Ptr< ChannelConditionModel > model)
Set the channel condition model.
Ptr< UniformRandomVariable > m_uniformRvDoppler
uniform random variable, used to compute the additional Doppler contribution
uint16_t m_numNonSelfBlocking
number of non-self-blocking regions
std::string GetScenario() const
Returns the propagation scenario.
virtual Ptr< ChannelMatrix > GetNewChannel(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ParamsTable > table3gpp, const Ptr< const MobilityModel > sMob, const Ptr< const MobilityModel > uMob, Ptr< const PhasedArrayModel > sAntenna, Ptr< const PhasedArrayModel > uAntenna) const
Compute the channel matrix between two nodes a and b, and their antenna arrays aAntenna and bAntenna ...
static const uint8_t PHI_INDEX
index of the PHI value in the m_nonSelfBlocking array
double m_frequency
the operating frequency
double m_vScatt
value used to compute the additional Doppler contribution for the delayed paths
Ptr< ChannelConditionModel > GetChannelConditionModel() const
Get the associated channel condition model.
Ptr< ChannelConditionModel > m_channelConditionModel
the channel condition model
std::string m_scenario
the 3GPP scenario
static const uint8_t R_INDEX
index of the R value in the m_nonSelfBlocking array
static TypeId GetTypeId()
Get the type ID.
void Shuffle(double *first, double *last) const
Shuffle the elements of a simple sequence container of type double.
Ptr< ThreeGppChannelParams > GenerateChannelParameters(const Ptr< const ChannelCondition > channelCondition, const Ptr< const ParamsTable > table3gpp, const Ptr< const MobilityModel > aMob, const Ptr< const MobilityModel > bMob) const
Prepare 3gpp channel parameters among the nodes a and b.
double GetFrequency() const
Returns the center frequency.
Time m_updatePeriod
the channel update period
static const uint8_t X_INDEX
index of the X value in the m_nonSelfBlocking array
Ptr< UniformRandomVariable > m_uniformRvShuffle
uniform random variable used to shuffle array in GetNewChannel
@ NS
nanosecond
Definition: nstime.h:119
bool IsZero() const
Exactly equivalent to t == 0.
Definition: nstime.h:315
a unique identifier for an interface.
Definition: type-id.h:59
TypeId SetGroupName(std::string groupName)
Set the group name.
Definition: type-id.cc:939
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition: type-id.cc:931
double GetValue(double min, double max)
Get the next random value drawn from the distribution.
uint32_t GetInteger(uint32_t min, uint32_t max)
Get the next random value drawn from the distribution.
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
Definition: fatal-error.h:179
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_WARN(msg)
Use NS_LOG to output a message of level LOG_WARN.
Definition: log.h:261
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition: object-base.h:46
Time Now()
create an ns3::Time instance which contains the current simulation time.
Definition: simulator.cc:305
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1338
Definition: first.py:1
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition: boolean.cc:124
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Definition: nstime.h:1414
static const double offSetAlpha[20]
The ray offset angles within a cluster, given for rms angle spread normalized to 1.
static const double sqrtC_RMa_O2I[6][6]
The square root matrix for RMa O2I, which is generated using the Cholesky decomposition according to ...
static const double sqrtC_UMi_LOS[7][7]
The square root matrix for UMi LOS, which is generated using the Cholesky decomposition according to ...
Ptr< const AttributeAccessor > MakePointerAccessor(T1 a1)
Definition: pointer.h:227
static const double sqrtC_office_LOS[7][7]
The square root matrix for Indoor-Office LOS, which is generated using the Cholesky decomposition acc...
static const double sqrtC_UMa_O2I[6][6]
The square root matrix for UMa O2I, which is generated using the Cholesky decomposition according to ...
Ptr< const AttributeAccessor > MakeIntegerAccessor(T1 a1)
Definition: integer.h:46
static const double sqrtC_RMa_NLOS[6][6]
The square root matrix for RMa NLOS, which is generated using the Cholesky decomposition according to...
static const double sqrtC_UMa_LOS[7][7]
The square root matrix for UMa LOS, which is generated using the Cholesky decomposition according to ...
static const double sqrtC_UMi_NLOS[6][6]
The square root matrix for UMi NLOS, which is generated using the Cholesky decomposition according to...
Ptr< const AttributeChecker > MakeTimeChecker(const Time min, const Time max)
Helper to make a Time checker with bounded range.
Definition: time.cc:533
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition: boolean.h:86
static const double sqrtC_RMa_LOS[7][7]
The square root matrix for RMa LOS, which is generated using the Cholesky decomposition according to ...
Ptr< const AttributeAccessor > MakeDoubleAccessor(T1 a1)
Definition: double.h:43
double DegreesToRadians(double degrees)
converts degrees to radians
Definition: angles.cc:39
void swap(UUID &uuid1, UUID &uuid2) noexcept
Definition: uuid.cc:212
Ptr< const AttributeChecker > MakeStringChecker()
Definition: string.cc:30
Ptr< const AttributeAccessor > MakeStringAccessor(T1 a1)
Definition: string.h:57
static const double sqrtC_UMi_O2I[6][6]
The square root matrix for UMi O2I, which is generated using the Cholesky decomposition according to ...
static const double sqrtC_office_NLOS[6][6]
The square root matrix for Indoor-Office NLOS, which is generated using the Cholesky decomposition ac...
static const double sqrtC_UMa_NLOS[6][6]
The square root matrix for UMa NLOS, which is generated using the Cholesky decomposition according to...
double WrapTo2Pi(double a)
Wrap angle in [0, 2*M_PI)
Definition: angles.cc:117
double RadiansToDegrees(double radians)
converts radians to degrees
Definition: angles.cc:45
Complex3DVector m_channel
Channel matrix H[u][s][n].
std::pair< uint32_t, uint32_t > m_antennaPair
The first element is the ID of the antenna of the s-node (the antenna of the transmitter when the cha...
std::pair< uint32_t, uint32_t > m_nodeIds
The first element is the s-node ID (the transmitter when the channel was generated),...