A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
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 * Copyright (c) 2026, CTTC, Centre Tecnologic de Telecomunicacions de Catalunya
7 *
8 * SPDX-License-Identifier: GPL-2.0-only
9 *
10 */
12
13#include "ns3/boolean.h"
14#include "ns3/double.h"
15#include "ns3/geocentric-constant-position-mobility-model.h"
16#include "ns3/integer.h"
17#include "ns3/log.h"
18#include "ns3/mobility-model.h"
19#include "ns3/node.h"
20#include "ns3/phased-array-model.h"
21#include "ns3/pointer.h"
22#include "ns3/shuffle.h"
23#include "ns3/simulator.h"
24#include "ns3/string.h"
25
26#include <algorithm>
27#include <array>
28#include <map>
29#include <random>
30
31namespace ns3
32{
33NS_LOG_COMPONENT_DEFINE("ThreeGppChannelModel");
34
36
37/// Conversion factor: degrees to radians
38constexpr double DEG2RAD = M_PI / 180.0;
39/**
40 * Maximum 2D displacement (in meters) allowed for a single channel-consistency update step.
41 *
42 * This value reflects the TR 38.901 guidance to limit the update displacement to within 1 m.
43 * If the effective endpoint displacement exceeds this threshold, ns-3 regenerates the channel
44 * parameters (fallback) rather than attempting a consistency update over a larger step.
45 */
46constexpr double kMaxConsistencyStepMeters = 1.0;
47
48/**
49 * Small threshold (in meters) used to treat very small displacements as zero.
50 *
51 * This avoids triggering channel-consistency updates due to numerical noise when the effective
52 * endpoint displacement is negligible.
53 */
54static constexpr double kNoDisplacementEpsMeters = 1e-6;
55
56/// The ray offset angles within a cluster, given for rms angle spread normalized to 1.
57/// (Table 7.5-3)
58static constexpr std::array offSetAlpha = {
59 0.0447, -0.0447, 0.1413, -0.1413, 0.2492, -0.2492, 0.3715, -0.3715, 0.5129, -0.5129,
60 0.6797, -0.6797, 0.8844, -0.8844, 1.1481, -1.1481, 1.5195, -1.5195, 2.1551, -2.1551,
61};
62
63/**
64 * The square root matrix for <em>RMa LOS</em>, which is generated using the
65 * Cholesky decomposition according to table 7.5-6 Part 2 and follows the order
66 * of [SF, K, DS, ASD, ASA, ZSD, ZSA].
67 *
68 * The Matlab file to generate the matrices can be found in
69 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
70 */
71static constexpr std::array<std::array<double, 7>, 7> sqrtC_RMa_LOS = {{
72 {1, 0, 0, 0, 0, 0, 0},
73 {0, 1, 0, 0, 0, 0, 0},
74 {-0.5, 0, 0.866025, 0, 0, 0, 0},
75 {0, 0, 0, 1, 0, 0, 0},
76 {0, 0, 0, 0, 1, 0, 0},
77 {0.01, 0, -0.0519615, 0.73, -0.2, 0.651383, 0},
78 {-0.17, -0.02, 0.21362, -0.14, 0.24, 0.142773, 0.909661},
79}};
80
81/**
82 * The square root matrix for <em>RMa NLOS</em>, which is generated using the
83 * Cholesky decomposition according to table 7.5-6 Part 2 and follows the order
84 * of [SF, DS, ASD, ASA, ZSD, ZSA].
85 * The Matlab file to generate the matrices can be found in
86 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
87 */
88static constexpr std::array<std::array<double, 6>, 6> sqrtC_RMa_NLOS = {{
89 {1, 0, 0, 0, 0, 0},
90 {-0.5, 0.866025, 0, 0, 0, 0},
91 {0.6, -0.11547, 0.791623, 0, 0, 0},
92 {0, 0, 0, 1, 0, 0},
93 {-0.04, -0.138564, 0.540662, -0.18, 0.809003, 0},
94 {-0.25, -0.606218, -0.240013, 0.26, -0.231685, 0.625392},
95}};
96
97/**
98 * The square root matrix for <em>RMa O2I</em>, which is generated using the
99 * Cholesky decomposition according to table 7.5-6 Part 2 and follows the order
100 * of [SF, K, DS, ASD, ASA, ZSD, ZSA].
101 *
102 * The Matlab file to generate the matrices can be found in
103 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
104 */
105static constexpr std::array<std::array<double, 6>, 6> sqrtC_RMa_O2I = {{
106 {1, 0, 0, 0, 0, 0},
107 {0, 1, 0, 0, 0, 0},
108 {0, 0, 1, 0, 0, 0},
109 {0, 0, -0.7, 0.714143, 0, 0},
110 {0, 0, 0.66, -0.123225, 0.741091, 0},
111 {0, 0, 0.47, 0.152631, -0.393194, 0.775373},
112}};
113
114/**
115 * The square root matrix for <em>UMa LOS</em>, which is generated using the
116 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
117 * of [SF, K, DS, ASD, ASA, ZSD, ZSA].
118 *
119 * The Matlab file to generate the matrices can be found in
120 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
121 */
122static constexpr std::array<std::array<double, 7>, 7> sqrtC_UMa_LOS = {{
123 {1, 0, 0, 0, 0, 0, 0},
124 {0, 1, 0, 0, 0, 0, 0},
125 {-0.4, -0.4, 0.824621, 0, 0, 0, 0},
126 {-0.5, 0, 0.242536, 0.83137, 0, 0, 0},
127 {-0.5, -0.2, 0.630593, -0.484671, 0.278293, 0, 0},
128 {0, 0, -0.242536, 0.672172, 0.642214, 0.27735, 0},
129 {-0.8, 0, -0.388057, -0.367926, 0.238537, -3.58949e-15, 0.130931},
130}};
131
132/**
133 * The square root matrix for <em>UMa NLOS</em>, which is generated using the
134 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
135 * of [SF, DS, ASD, ASA, ZSD, ZSA].
136 *
137 * The Matlab file to generate the matrices can be found in
138 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
139 */
140static constexpr std::array<std::array<double, 6>, 6> sqrtC_UMa_NLOS = {{
141 {1, 0, 0, 0, 0, 0},
142 {-0.4, 0.916515, 0, 0, 0, 0},
143 {-0.6, 0.174574, 0.78072, 0, 0, 0},
144 {0, 0.654654, 0.365963, 0.661438, 0, 0},
145 {0, -0.545545, 0.762422, 0.118114, 0.327327, 0},
146 {-0.4, -0.174574, -0.396459, 0.392138, 0.49099, 0.507445},
147}};
148
149/**
150 * The square root matrix for <em>UMa O2I</em>, which is generated using the
151 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
152 * of [SF, DS, ASD, ASA, ZSD, ZSA].
153 *
154 * The Matlab file to generate the matrices can be found in
155 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
156 */
157static constexpr std::array<std::array<double, 6>, 6> sqrtC_UMa_O2I = {{
158 {1, 0, 0, 0, 0, 0},
159 {-0.5, 0.866025, 0, 0, 0, 0},
160 {0.2, 0.57735, 0.791623, 0, 0, 0},
161 {0, 0.46188, -0.336861, 0.820482, 0, 0},
162 {0, -0.69282, 0.252646, 0.493742, 0.460857, 0},
163 {0, -0.23094, 0.16843, 0.808554, -0.220827, 0.464515},
164}};
165
166/**
167 * The square root matrix for <em>UMi LOS</em>, which is generated using the
168 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
169 * of [SF, K, DS, ASD, ASA, ZSD, ZSA].
170 *
171 * The Matlab file to generate the matrices can be found in
172 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
173 */
174static constexpr std::array<std::array<double, 7>, 7> sqrtC_UMi_LOS = {{
175 {1, 0, 0, 0, 0, 0, 0},
176 {0.5, 0.866025, 0, 0, 0, 0, 0},
177 {-0.4, -0.57735, 0.711805, 0, 0, 0, 0},
178 {-0.5, 0.057735, 0.468293, 0.726201, 0, 0, 0},
179 {-0.4, -0.11547, 0.805464, -0.23482, 0.350363, 0, 0},
180 {0, 0, 0, 0.688514, 0.461454, 0.559471, 0},
181 {0, 0, 0.280976, 0.231921, -0.490509, 0.11916, 0.782603},
182}};
183
184/**
185 * The square root matrix for <em>UMi NLOS</em>, which is generated using the
186 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
187 * of [SF, DS, ASD, ASA, ZSD, ZSA].
188 *
189 * The Matlab file to generate the matrices can be found in
190 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
191 */
192static constexpr std::array<std::array<double, 6>, 6> sqrtC_UMi_NLOS = {{
193 {1, 0, 0, 0, 0, 0},
194 {-0.7, 0.714143, 0, 0, 0, 0},
195 {0, 0, 1, 0, 0, 0},
196 {-0.4, 0.168034, 0, 0.90098, 0, 0},
197 {0, -0.70014, 0.5, 0.130577, 0.4927, 0},
198 {0, 0, 0.5, 0.221981, -0.566238, 0.616522},
199}};
200
201/**
202 * The square root matrix for <em>UMi O2I</em>, which is generated using the
203 * Cholesky decomposition according to table 7.5-6 Part 1 and follows the order
204 * of [SF, DS, ASD, ASA, ZSD, ZSA].
205 *
206 * The Matlab file to generate the matrices can be found in
207 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
208 */
209static constexpr std::array<std::array<double, 6>, 6> sqrtC_UMi_O2I = {{
210 {1, 0, 0, 0, 0, 0},
211 {-0.5, 0.866025, 0, 0, 0, 0},
212 {0.2, 0.57735, 0.791623, 0, 0, 0},
213 {0, 0.46188, -0.336861, 0.820482, 0, 0},
214 {0, -0.69282, 0.252646, 0.493742, 0.460857, 0},
215 {0, -0.23094, 0.16843, 0.808554, -0.220827, 0.464515},
216}};
217
218/**
219 * The square root matrix for <em>Indoor-Office LOS</em>, which is generated
220 * using the Cholesky decomposition according to table 7.5-6 Part 2 and follows
221 * the order of [SF, K, DS, ASD, ASA, ZSD, ZSA].
222 *
223 * The Matlab file to generate the matrices can be found in
224 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
225 */
226static constexpr std::array<std::array<double, 7>, 7> sqrtC_office_LOS = {{
227 {1, 0, 0, 0, 0, 0, 0},
228 {0.5, 0.866025, 0, 0, 0, 0, 0},
229 {-0.8, -0.11547, 0.588784, 0, 0, 0, 0},
230 {-0.4, 0.23094, 0.520847, 0.717903, 0, 0, 0},
231 {-0.5, 0.288675, 0.73598, -0.348236, 0.0610847, 0, 0},
232 {0.2, -0.11547, 0.418943, 0.541106, 0.219905, 0.655744, 0},
233 {0.3, -0.057735, 0.73598, -0.348236, 0.0610847, -0.304997, 0.383375},
234}};
235
236/**
237 * The square root matrix for <em>Indoor-Office NLOS</em>, which is generated
238 * using the Cholesky decomposition according to table 7.5-6 Part 2 and follows
239 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
240 *
241 * The Matlab file to generate the matrices can be found in
242 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
243 */
244static constexpr std::array<std::array<double, 6>, 6> sqrtC_office_NLOS = {{
245 {1, 0, 0, 0, 0, 0},
246 {-0.5, 0.866025, 0, 0, 0, 0},
247 {0, 0.46188, 0.886942, 0, 0, 0},
248 {-0.4, -0.23094, 0.120263, 0.878751, 0, 0},
249 {0, -0.311769, 0.55697, -0.249198, 0.728344, 0},
250 {0, -0.069282, 0.295397, 0.430696, 0.468462, 0.709214},
251}};
252
253/**
254 * The square root matrix for <em>NTN Dense Urban LOS</em>, which is generated
255 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-1 and follows
256 * the order of [SF, K, DS, ASD, ASA, ZSD, ZSA].
257 *
258 * The Matlab file to generate the matrices can be found in
259 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
260 */
261static constexpr std::array<std::array<double, 7>, 7> sqrtC_NTN_DenseUrban_LOS = {{
262 {1, 0, 0, 0, 0, 0, 0},
263 {0, 1, 0, 0, 0, 0, 0},
264 {-0.4, -0.4, 0.824621, 0, 0, 0, 0},
265 {-0.5, 0, 0.242536, 0.83137, 0, 0, 0},
266 {-0.5, -0.2, 0.630593, -0.484671, 0.278293, 0, 0},
267 {0, 0, -0.242536, 0.672172, 0.642214, 0.27735, 0},
268 {-0.8, 0, -0.388057, -0.367926, 0.238537, -4.09997e-15, 0.130931},
269}};
270
271/**
272 * The square root matrix for <em>NTN Dense Urban NLOS</em>, which is generated
273 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-2 and follows
274 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
275 *
276 * The Matlab file to generate the matrices can be found in
277 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
278 */
279static constexpr std::array<std::array<double, 6>, 6> sqrtC_NTN_DenseUrban_NLOS = {{
280 {1, 0, 0, 0, 0, 0},
281 {-0.4, 0.916515, 0, 0, 0, 0},
282 {-0.6, 0.174574, 0.78072, 0, 0, 0},
283 {0, 0.654654, 0.365963, 0.661438, 0, 0},
284 {0, -0.545545, 0.762422, 0.118114, 0.327327, 0},
285 {-0.4, -0.174574, -0.396459, 0.392138, 0.49099, 0.507445},
286}};
287
288/**
289 * The square root matrix for <em>NTN Urban LOS</em>, which is generated
290 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-3 and follows
291 * the order of [SF, K, DS, ASD, ASA, ZSD, ZSA].
292 *
293 * The Matlab file to generate the matrices can be found in
294 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
295 */
296static constexpr std::array<std::array<double, 7>, 7> sqrtC_NTN_Urban_LOS = {{
297 {1, 0, 0, 0, 0, 0, 0},
298 {0, 1, 0, 0, 0, 0, 0},
299 {-0.4, -0.4, 0.824621, 0, 0, 0, 0},
300 {-0.5, 0, 0.242536, 0.83137, 0, 0, 0},
301 {-0.5, -0.2, 0.630593, -0.484671, 0.278293, 0, 0},
302 {0, 0, -0.242536, 0.672172, 0.642214, 0.27735, 0},
303 {-0.8, 0, -0.388057, -0.367926, 0.238537, -4.09997e-15, 0.130931},
304}};
305
306/**
307 * The square root matrix for <em>NTN Urban NLOS</em>, which is generated
308 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-4 and follows
309 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
310 *
311 * The square root matrix is dependent on the elevation angle, thus requiring a map.
312 *
313 * The Matlab file to generate the matrices can be found in
314 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
315 */
316static const std::map<int, std::array<std::array<double, 6>, 6>> sqrtC_NTN_Urban_NLOS{
317 {10,
318 {{
319 {1, 0, 0, 0, 0, 0},
320 {-0.21, 0.977701, 0, 0, 0, 0},
321 {-0.48, 0.459445, 0.747335, 0, 0, 0},
322 {-0.05, 0.377927, 0.28416, 0.879729, 0, 0},
323 {-0.02, 0.691213, 0.258017, 0.073265, 0.670734, 0},
324 {-0.31, -0.00521632, -0.115615, 0.0788023, 0.00218104, 0.940368},
325 }}},
326 {20,
327 {{
328 {1, 0, 0, 0, 0, 0},
329 {-0.25, 0.968246, 0, 0, 0, 0},
330 {-0.52, 0.35115, 0.778648, 0, 0, 0},
331 {-0.04, 0.371806, 0.345008, 0.860889, 0, 0},
332 {0, 0.743613, 0.281102, 0.0424415, 0.605161, 0},
333 {-0.32, 0.0206559, -0.0689057, 0.154832, 0.061865, 0.929852},
334 }}},
335 {30,
336 {{
337 {1, 0, 0, 0, 0, 0},
338 {-0.21, 0.977701, 0, 0, 0, 0},
339 {-0.52, 0.450853, 0.725487, 0, 0, 0},
340 {-0.04, 0.288023, 0.260989, 0.920504, 0, 0},
341 {0.01, 0.697657, 0.386856, 0.0418183, 0.601472, 0},
342 {-0.33, 0.0416283, -0.0694268, 0.166137, 0.139937, 0.915075},
343 }}},
344 {40,
345 {{
346 {1, 0, 0, 0, 0, 0},
347 {-0.26, 0.965609, 0, 0, 0, 0},
348 {-0.53, 0.395813, 0.749955, 0, 0, 0},
349 {-0.04, 0.299914, 0.320139, 0.897754, 0, 0},
350 {0.01, 0.696556, 0.372815, 0.0580784, 0.610202, 0},
351 {-0.33, 0.0457742, -0.0173584, 0.154417, 0.129332, 0.920941},
352 }}},
353 {50,
354 {{
355 {1, 0, 0, 0, 0, 0},
356 {-0.25, 0.968246, 0, 0, 0, 0},
357 {-0.57, 0.420864, 0.705672, 0, 0, 0},
358 {-0.03, 0.229797, 0.235501, 0.943839, 0, 0},
359 {0.03, 0.679063, 0.384466, 0.0681379, 0.6209, 0},
360 {-0.41, -0.147173, -0.229228, 0.270707, 0.293002, 0.773668},
361 }}},
362 {60,
363 {{
364 {1, 0, 0, 0, 0, 0},
365 {-0.2, 0.979796, 0, 0, 0, 0},
366 {-0.53, 0.473568, 0.703444, 0, 0, 0},
367 {-0.05, 0.204124, 0.109225, 0.971547, 0, 0},
368 {0.03, 0.68994, 0.411073, 0.0676935, 0.591202, 0},
369 {-0.4, -0.224537, -0.292371, 0.275609, 0.301835, 0.732828},
370 }}},
371 {70,
372 {{
373 {1, 0, 0, 0, 0, 0},
374 {-0.19, 0.981784, 0, 0, 0, 0},
375 {-0.5, 0.524555, 0.689088, 0, 0, 0},
376 {-0.03, 0.228462, 0.18163, 0.955989, 0, 0},
377 {-0.02, 0.637818, 0.428725, 0.00608114, 0.639489, 0},
378 {-0.36, -0.18171, -0.282523, 0.106726, 0.123808, 0.854894},
379 }}},
380 {80,
381 {{
382 {1, 0, 0, 0, 0, 0},
383 {-0.2, 0.979796, 0, 0, 0, 0},
384 {-0.49, 0.502145, 0.712566, 0, 0, 0},
385 {-0.01, 0.232702, 0.151916, 0.960558, 0, 0},
386 {-0.05, 0.612372, 0.376106, 0.0206792, 0.693265, 0},
387 {-0.37, -0.320475, -0.365405, -0.00376264, 0.0364343, 0.790907},
388 }}},
389 {90,
390 {{
391 {1, 0, 0, 0, 0, 0},
392 {-0.19, 0.981784, 0, 0, 0, 0},
393 {-0.38, 0.58852, 0.713613, 0, 0, 0},
394 {-0.03, 0.360874, 0.12082, 0.924269, 0, 0},
395 {-0.12, 0.526796, 0.34244, 0.0594196, 0.766348, 0},
396 {-0.33, -0.257389, -0.24372, -0.257035, -0.176521, 0.817451},
397 }}},
398};
399
400/**
401 * The square root matrix for <em>NTN Suburban LOS</em>, which is generated
402 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-5 and follows
403 * the order of [SF, K, DS, ASD, ASA, ZSD, ZSA].
404 *
405 * The Matlab file to generate the matrices can be found in
406 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
407 */
408static constexpr std::array<std::array<double, 7>, 7> sqrtC_NTN_Suburban_LOS = {{
409 {1, 0, 0, 0, 0, 0, 0},
410 {0, 1, 0, 0, 0, 0, 0},
411 {-0.4, -0.4, 0.824621, 0, 0, 0, 0},
412 {-0.5, 0, 0.242536, 0.83137, 0, 0, 0},
413 {-0.5, -0.2, 0.630593, -0.484671, 0.278293, 0, 0},
414 {0, 0, -0.242536, 0.672172, 0.642214, 0.27735, 0},
415 {-0.8, 0, -0.388057, -0.367926, 0.238537, -4.09997e-15, 0.130931},
416}};
417
418/**
419 * The square root matrix for <em>NTN Suburban NLOS</em>, which is generated
420 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-6 and follows
421 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
422 *
423 * The Matlab file to generate the matrices can be found in
424 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
425 */
426static constexpr std::array<std::array<double, 6>, 6> sqrtC_NTN_Suburban_NLOS = {{
427 {1, 0, 0, 0, 0, 0},
428 {-0.4, 0.916515, 0, 0, 0, 0},
429 {-0.6, 0.174574, 0.78072, 0, 0, 0},
430 {0, 0.654654, 0.365963, 0.661438, 0, 0},
431 {0, -0.545545, 0.762422, 0.118114, 0.327327, 0},
432 {-0.4, -0.174574, -0.396459, 0.392138, 0.49099, 0.507445},
433}};
434
435/**
436 * The square root matrix for <em>NTN Rural LOS</em>, which is generated
437 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-7 and follows
438 * the order of [SF, K, DS, ASD, ASA, ZSD, ZSA].
439 *
440 * The Matlab file to generate the matrices can be found in
441 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
442 */
443static constexpr std::array<std::array<double, 7>, 7> sqrtC_NTN_Rural_LOS = {{
444 {1, 0, 0, 0, 0, 0, 0},
445 {0, 1, 0, 0, 0, 0, 0},
446 {-0.5, 0, 0.866025, 0, 0, 0, 0},
447 {0, 0, 0, 1, 0, 0, 0},
448 {0, 0, 0, 0, 1, 0, 0},
449 {0.01, 0, -0.0519615, 0.73, -0.2, 0.651383, 0},
450 {-0.17, -0.02, 0.21362, -0.14, 0.24, 0.142773, 0.909661},
451}};
452
453/**
454 * The square root matrix for <em>NTN Rural NLOS S Band</em>, which is generated
455 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-8a and follows
456 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
457 *
458 * The square root matrix is dependent on the elevation angle, thus requiring a map.
459 *
460 * The Matlab file to generate the matrices can be found in
461 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
462 */
463static const std::map<int, std::array<std::array<double, 6>, 6>> sqrtC_NTN_Rural_NLOS_S{
464 {10,
465 {{
466 {1, 0, 0, 0, 0, 0},
467 {-0.36, 0.932952, 0, 0, 0, 0},
468 {0.45, 0.516639, 0.728412, 0, 0, 0},
469 {0.02, 0.329277, 0.371881, 0.867687, 0, 0},
470 {-0.06, 0.59853, 0.436258, -0.0324062, 0.668424, 0},
471 {-0.07, 0.0373009, 0.305087, -0.0280496, -0.225204, 0.921481},
472 }}},
473 {20,
474 {{
475 {1, 0, 0, 0, 0, 0},
476 {-0.39, 0.920815, 0, 0, 0, 0},
477 {0.52, 0.426579, 0.740021, 0, 0, 0},
478 {0, 0.347518, -0.0381664, 0.936896, 0, 0},
479 {-0.04, 0.710675, 0.172483, 0.116993, 0.670748, 0},
480 {-0.17, -0.0394216, 0.115154, 0.243458, -0.0702635, 0.944498},
481 }}},
482 {30,
483 {{
484 {1, 0, 0, 0, 0, 0},
485 {-0.41, 0.912086, 0, 0, 0, 0},
486 {0.54, 0.49491, 0.680782, 0, 0, 0},
487 {0, 0.350844, -0.152231, 0.923977, 0, 0},
488 {-0.04, 0.694672, 0.0702137, 0.0832998, 0.709903, 0},
489 {-0.19, -0.0854087, 0.0805978, 0.283811, -0.137441, 0.922318},
490 }}},
491 {40,
492 {{
493 {1, 0, 0, 0, 0, 0},
494 {-0.37, 0.929032, 0, 0, 0, 0},
495 {0.53, 0.480177, 0.698949, 0, 0, 0},
496 {0.01, 0.434538, 0.00864797, 0.900556, 0, 0},
497 {-0.05, 0.765851, -0.0303947, 0.0421641, 0.63896, 0},
498 {-0.17, -0.16458, 0.0989022, 0.158081, -0.150425, 0.941602},
499 }}},
500 {50,
501 {{
502 {1, 0, 0, 0, 0, 0},
503 {-0.4, 0.916515, 0, 0, 0, 0},
504 {0.55, 0.403703, 0.731111, 0, 0, 0},
505 {0.02, 0.499719, -0.0721341, 0.862947, 0, 0},
506 {-0.06, 0.835775, -0.156481, 0.0373835, 0.521534, 0},
507 {-0.19, -0.301141, 0.145082, 0.144564, -0.0238067, 0.911427},
508 }}},
509 {60,
510 {{
511 {1, 0, 0, 0, 0, 0},
512 {-0.41, 0.912086, 0, 0, 0, 0},
513 {0.56, 0.339442, 0.755764, 0, 0, 0},
514 {0.02, 0.436582, -0.0256617, 0.899076, 0, 0},
515 {-0.07, 0.856608, -0.12116, 0.0715303, 0.491453, 0},
516 {-0.2, -0.331109, 0.15136, 0.036082, 0.031313, 0.908391},
517 }}},
518 {70,
519 {{
520 {1, 0, 0, 0, 0, 0},
521 {-0.4, 0.916515, 0, 0, 0, 0},
522 {0.56, 0.386246, 0.732949, 0, 0, 0},
523 {0.04, 0.573913, -0.0601289, 0.815726, 0, 0},
524 {-0.11, 0.813953, -0.0720183, 0.0281118, 0.565158, 0},
525 {-0.19, -0.432071, 0.236423, -0.0247788, -0.0557206, 0.847113},
526 }}},
527 {80,
528 {{
529 {1, 0, 0, 0, 0, 0},
530 {-0.46, 0.887919, 0, 0, 0, 0},
531 {0.58, 0.469412, 0.665772, 0, 0, 0},
532 {0.01, 0.309262, -0.286842, 0.90663, 0, 0},
533 {-0.05, 0.762457, -0.268721, -0.0467443, 0.584605, 0},
534 {-0.23, -0.580909, 0.399665, 0.0403629, 0.326208, 0.584698},
535 }}},
536 {90,
537 {{
538 {1, 0, 0, 0, 0, 0},
539 {-0.3, 0.953939, 0, 0, 0, 0},
540 {0.47, 0.81871, 0.329868, 0, 0, 0},
541 {0.06, 0.0712834, -0.595875, 0.797654, 0, 0},
542 {-0.1, 0.408831, -0.0233859, 0.0412736, 0.905873, 0},
543 {-0.13, -0.407783, 0.439436, -0.0768289, -0.212875, 0.756631},
544 }}},
545};
546
547/**
548 * The square root matrix for <em>NTN Rural NLOS Ka Band</em>, which is generated
549 * using the Cholesky decomposition according to 3GPP TR 38.811 v15.4.0 table 6.7.2-8b and follows
550 * the order of [SF, DS, ASD, ASA, ZSD, ZSA].
551 *
552 * The square root matrix is dependent on the elevation angle, which acts as the corresponding map's
553 * key.
554 *
555 * The Matlab file to generate the matrices can be found in
556 * https://github.com/nyuwireless-unipd/ns3-mmwave/blob/master/src/mmwave/model/BeamFormingMatrix/SqrtMatrix.m
557 */
558static const std::map<int, std::array<std::array<double, 6>, 6>> sqrtC_NTN_Rural_NLOS_Ka{
559 {10,
560 {{
561 {1, 0, 0, 0, 0, 0},
562 {-0.36, 0.932952, 0, 0, 0, 0},
563 {0.45, 0.527358, 0.72069, 0, 0, 0},
564 {0.02, 0.350715, 0.355282, 0.866241, 0, 0},
565 {-0.07, 0.562515, 0.478504, 0.0162932, 0.670406, 0},
566 {-0.06, 0.0411597, 0.270982, 0.0121094, -0.159927, 0.946336},
567 }}},
568 {20,
569 {{
570 {1, 0, 0, 0, 0, 0},
571 {-0.38, 0.924986, 0, 0, 0, 0},
572 {0.52, 0.473088, 0.711188, 0, 0, 0},
573 {0, 0.367573, -0.0617198, 0.927944, 0, 0},
574 {-0.04, 0.68628, 0.149228, 0.115257, 0.701332, 0},
575 {-0.16, -0.0441088, 0.118207, 0.251641, -0.0752458, 0.943131},
576 }}},
577 {30,
578 {{
579 {1, 0, 0, 0, 0, 0},
580 {-0.42, 0.907524, 0, 0, 0, 0},
581 {0.54, 0.48131, 0.690464, 0, 0, 0},
582 {0, 0.363627, -0.137613, 0.921324, 0, 0},
583 {-0.04, 0.686704, 0.117433, 0.104693, 0.708581, 0},
584 {-0.19, -0.0438556, 0.0922685, 0.269877, -0.136292, 0.928469},
585 }}},
586 {40,
587 {{
588 {1, 0, 0, 0, 0, 0},
589 {-0.36, 0.932952, 0, 0, 0, 0},
590 {0.53, 0.483197, 0.696865, 0, 0, 0},
591 {0.01, 0.464761, -0.0285153, 0.88492, 0, 0},
592 {-0.05, 0.763169, 0.140255, 0.0562856, 0.626286, 0},
593 {-0.16, -0.126051, 0.0942905, 0.195354, -0.217188, 0.92967},
594 }}},
595 {50,
596 {{
597 {1, 0, 0, 0, 0, 0},
598 {-0.39, 0.920815, 0, 0, 0, 0},
599 {0.55, 0.406705, 0.729446, 0, 0, 0},
600 {0.01, 0.503793, -0.123923, 0.854831, 0, 0},
601 {-0.06, 0.821664, -0.207246, 0.0245302, 0.526988, 0},
602 {-0.19, -0.254231, 0.10679, 0.190931, -0.0665276, 0.920316},
603 }}},
604 {60,
605 {{
606 {1, 0, 0, 0, 0, 0},
607 {-0.42, 0.907524, 0, 0, 0, 0},
608 {0.56, 0.391395, 0.730213, 0, 0, 0},
609 {0.02, 0.427978, -0.0393147, 0.902712, 0, 0},
610 {-0.06, 0.820694, -0.119986, 0.105509, 0.545281, 0},
611 {-0.2, -0.279882, 0.180145, 0.0563477, -0.0121631, 0.919723},
612 }}},
613 {70,
614 {{
615 {1, 0, 0, 0, 0, 0},
616 {-0.36, 0.932952, 0, 0, 0, 0},
617 {0.54, 0.519212, 0.662434, 0, 0, 0},
618 {0.04, 0.412025, -0.0234416, 0.909992, 0, 0},
619 {-0.09, 0.758452, -0.0682296, 0.0214276, 0.64151, 0},
620 {-0.17, -0.387158, 0.306169, -0.0291255, -0.109344, 0.845378},
621 }}},
622 {80,
623 {{
624 {1, 0, 0, 0, 0, 0},
625 {-0.44, 0.897998, 0, 0, 0, 0},
626 {0.57, 0.43519, 0.696928, 0, 0, 0},
627 {0.01, 0.316705, -0.248988, 0.915207, 0, 0},
628 {-0.06, 0.805793, -0.296262, -0.0419182, 0.507514, 0},
629 {-0.22, -0.497551, 0.289742, 0.0785823, 0.328773, 0.711214},
630 }}},
631 {90,
632 {{
633 {1, 0, 0, 0, 0, 0},
634 {-0.27, 0.96286, 0, 0, 0, 0},
635 {0.46, 0.741748, 0.488067, 0, 0, 0},
636 {0.04, 0.0735309, -0.374828, 0.923308, 0, 0},
637 {-0.08, 0.517624, 0.128779, 0.0795063, 0.838308, 0},
638 {-0.11, -0.321646, 0.0802763, -0.131981, -0.193429, 0.907285},
639 }}},
640};
641
642/**
643 * The enumerator used for code clarity when performing parameter assignment in GetThreeGppTable
644 */
670
671/**
672 * The nested map containing the 3GPP value tables for the NTN Dense Urban LOS scenario.
673 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
674 * the quantized elevation angle.
675 * The inner vector collects the table3gpp values.
676 */
677static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNDenseUrbanLOS{
678 {"S",
679 {
680 {10, {-7.12, 0.8, -3.06, 0.48, 0.94, 0.7, 0.82, 0.03, -2.52, 0.5, 4.4,
681 3.3, 2.5, 24.4, 3.8, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
682 {20, {-7.28, 0.67, -2.68, 0.36, 0.87, 0.66, 0.5, 0.09, -2.29, 0.53, 9.0,
683 6.6, 2.5, 23.6, 4.7, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
684 {30, {-7.45, 0.68, -2.51, 0.38, 0.92, 0.68, 0.82, 0.05, -2.19, 0.58, 9.3,
685 6.1, 2.5, 23.2, 4.6, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
686 {40, {-7.73, 0.66, -2.4, 0.32, 0.79, 0.64, 1.23, 0.03, -2.24, 0.51, 7.9,
687 4.0, 2.5, 22.6, 4.9, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
688 {50, {-7.91, 0.62, -2.31, 0.33, 0.72, 0.63, 1.43, 0.06, -2.3, 0.46, 7.4,
689 3.0, 2.5, 21.8, 5.7, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
690 {60, {-8.14, 0.51, -2.2, 0.39, 0.6, 0.54, 1.56, 0.05, -2.48, 0.35, 7.0,
691 2.6, 2.5, 20.5, 6.9, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
692 {70, {-8.23, 0.45, -2.0, 0.4, 0.55, 0.52, 1.66, 0.05, -2.64, 0.31, 6.9,
693 2.2, 2.5, 19.3, 8.1, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
694 {80, {-8.28, 0.31, -1.64, 0.32, 0.71, 0.53, 1.73, 0.02, -2.68, 0.39, 6.5,
695 2.1, 2.5, 17.4, 10.3, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
696 {90, {-8.36, 0.08, -0.63, 0.53, 0.81, 0.62, 1.79, 0.01, -2.61, 0.28, 6.8,
697 1.9, 2.5, 12.3, 15.2, 3.0, 20.0, 3.9, 0.0, 11.0, 7.0, 3.0}},
698 }},
699 {"Ka",
700 {
701 {10, {-7.43, 0.9, -3.43, 0.54, 0.65, 0.82, 0.82, 0.05, -2.75, 0.55, 6.1,
702 2.6, 2.5, 24.7, 2.1, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
703 {20, {-7.62, 0.78, -3.06, 0.41, 0.53, 0.78, 0.47, 0.11, -2.64, 0.64, 13.7,
704 6.8, 2.5, 24.4, 2.8, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
705 {30, {-7.76, 0.8, -2.91, 0.42, 0.6, 0.83, 0.8, 0.05, -2.49, 0.69, 12.9,
706 6.0, 2.5, 24.4, 2.7, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
707 {40, {-8.02, 0.72, -2.81, 0.34, 0.43, 0.78, 1.23, 0.04, -2.51, 0.57, 10.3,
708 3.3, 2.5, 24.2, 2.7, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
709 {50, {-8.13, 0.61, -2.74, 0.34, 0.36, 0.77, 1.42, 0.1, -2.54, 0.5, 9.2,
710 2.2, 2.5, 23.9, 3.1, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
711 {60, {-8.3, 0.47, -2.72, 0.7, 0.16, 0.84, 1.56, 0.06, -2.71, 0.37, 8.4,
712 1.9, 2.5, 23.3, 3.9, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
713 {70, {-8.34, 0.39, -2.46, 0.4, 0.18, 0.64, 1.65, 0.07, -2.85, 0.31, 8.0,
714 1.5, 2.5, 22.6, 4.8, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
715 {80, {-8.39, 0.26, -2.3, 0.78, 0.24, 0.81, 1.73, 0.02, -3.01, 0.45, 7.4,
716 1.6, 2.5, 21.2, 6.8, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
717 {90, {-8.45, 0.01, -1.11, 0.51, 0.36, 0.65, 1.79, 0.01, -3.08, 0.27, 7.6,
718 1.3, 2.5, 17.6, 12.7, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
719 }},
720};
721
722/**
723 * The nested map containing the 3GPP value tables for the NTN Dense Urban NLOS scenario.
724 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
725 * the quantized elevation angle.
726 * The inner vector collects the table3gpp values.
727 */
728static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNDenseUrbanNLOS{
729 {"S",
730 {
731 {10, {-6.84, 0.82, -2.08, 0.87, 1.0, 1.6, 1.0, 0.63, -2.08, 0.58, 0.0,
732 0.0, 2.3, 23.8, 4.4, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
733 {20, {-6.81, 0.61, -1.68, 0.73, 1.44, 0.87, 0.94, 0.65, -1.66, 0.5, 0.0,
734 0.0, 2.3, 21.9, 6.3, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
735 {30, {-6.94, 0.49, -1.46, 0.53, 1.54, 0.64, 1.15, 0.42, -1.48, 0.4, 0.0,
736 0.0, 2.3, 19.7, 8.1, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
737 {40, {-7.14, 0.49, -1.43, 0.5, 1.53, 0.56, 1.35, 0.28, -1.46, 0.37, 0.0,
738 0.0, 2.3, 18.1, 9.3, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
739 {50, {-7.34, 0.51, -1.44, 0.58, 1.48, 0.54, 1.44, 0.25, -1.53, 0.47, 0.0,
740 0.0, 2.3, 16.3, 11.5, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
741 {60, {-7.53, 0.47, -1.33, 0.49, 1.39, 0.68, 1.56, 0.16, -1.61, 0.43, 0.0,
742 0.0, 2.3, 14.0, 13.3, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
743 {70, {-7.67, 0.44, -1.31, 0.65, 1.42, 0.55, 1.64, 0.18, -1.77, 0.5, 0.0,
744 0.0, 2.3, 12.1, 14.9, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
745 {80, {-7.82, 0.42, -1.11, 0.69, 1.38, 0.6, 1.7, 0.09, -1.9, 0.42, 0.0,
746 0.0, 2.3, 8.7, 17.0, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
747 {90, {-7.84, 0.55, -0.11, 0.53, 1.23, 0.6, 1.7, 0.17, -1.99, 0.5, 0.0,
748 0.0, 2.3, 6.4, 12.3, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
749 }},
750 {"Ka",
751 {
752 {10, {-6.86, 0.81, -2.12, 0.94, 1.02, 1.44, 1.01, 0.56, -2.11, 0.59, 0.0,
753 0.0, 2.3, 23.7, 4.5, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
754 {20, {-6.84, 0.61, -1.74, 0.79, 1.44, 0.77, 0.96, 0.55, -1.69, 0.51, 0.0,
755 0.0, 2.3, 21.8, 6.3, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
756 {30, {-7.0, 0.56, -1.56, 0.66, 1.48, 0.7, 1.13, 0.43, -1.52, 0.46, 0.0,
757 0.0, 2.3, 19.6, 8.2, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
758 {40, {-7.21, 0.56, -1.54, 0.63, 1.46, 0.6, 1.3, 0.37, -1.51, 0.43, 0.0,
759 0.0, 2.3, 18.0, 9.4, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
760 {50, {-7.42, 0.57, -1.45, 0.56, 1.4, 0.59, 1.4, 0.32, -1.54, 0.45, 0.0,
761 0.0, 2.3, 16.3, 11.5, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
762 {60, {-7.86, 0.55, -1.64, 0.78, 0.97, 1.27, 1.41, 0.45, -1.84, 0.63, 0.0,
763 0.0, 2.3, 15.9, 12.4, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
764 {70, {-7.76, 0.47, -1.37, 0.56, 1.33, 0.56, 1.63, 0.17, -1.86, 0.51, 0.0,
765 0.0, 2.3, 12.3, 15.0, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
766 {80, {-8.07, 0.42, -1.29, 0.76, 1.12, 1.04, 1.68, 0.14, -2.16, 0.74, 0.0,
767 0.0, 2.3, 10.5, 15.7, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
768 {90, {-7.95, 0.59, -0.41, 0.59, 1.04, 0.63, 1.7, 0.17, -2.21, 0.61, 0.0,
769 0.0, 2.3, 10.5, 15.7, 4.0, 20.0, 3.9, 0.0, 15.0, 7.0, 3.0}},
770 }},
771};
772
773/**
774 * The nested map containing the 3GPP value tables for the NTN Urban LOS scenario.
775 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
776 * the quantized elevation angle.
777 * The inner vector collects the table3gpp values.
778 */
779static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNUrbanLOS{
780 {"S",
781 {
782 {10, {-7.97, 1.0, -2.6, 0.79, 0.18, 0.74, -0.63, 2.6, -2.54, 2.62, 31.83,
783 13.84, 2.5, 8.0, 4.0, 4.0, 20.0, 3.9, 0.09, 12.55, 1.25, 3.0}},
784 {20, {-8.12, 0.83, -2.48, 0.8, 0.42, 0.9, -0.15, 3.31, -2.67, 2.96, 18.78,
785 13.78, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.09, 12.76, 3.23, 3.0}},
786 {30, {-8.21, 0.68, -2.44, 0.91, 0.41, 1.3, 0.54, 1.1, -2.03, 0.86, 10.49,
787 10.42, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.12, 14.36, 4.39, 3.0}},
788 {40, {-8.31, 0.48, -2.6, 1.02, 0.18, 1.69, 0.35, 1.59, -2.28, 1.19, 7.46,
789 8.01, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.16, 16.42, 5.72, 3.0}},
790 {50, {-8.37, 0.38, -2.71, 1.17, -0.07, 2.04, 0.27, 1.62, -2.48, 1.4, 6.52,
791 8.27, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.2, 17.13, 6.17, 3.0}},
792 {60, {-8.39, 0.24, -2.76, 1.17, -0.43, 2.54, 0.26, 0.97, -2.56, 0.85, 5.47,
793 7.26, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.28, 19.01, 7.36, 3.0}},
794 {70, {-8.38, 0.18, -2.78, 1.2, -0.64, 2.47, -0.12, 1.99, -2.96, 1.61, 4.54,
795 5.53, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.44, 19.31, 7.3, 3.0}},
796 {80, {-8.35, 0.13, -2.65, 1.45, -0.91, 2.69, -0.21, 1.82, -3.08, 1.49, 4.03,
797 4.49, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 0.9, 22.39, 7.7, 3.0}},
798 {90, {-8.34, 0.09, -2.27, 1.85, -0.54, 1.66, -0.07, 1.43, -3.0, 1.09, 3.68,
799 3.14, 2.5, 8.0, 4.0, 3.0, 20.0, 3.9, 2.87, 27.8, 9.25, 3.0}},
800 }},
801 {"Ka",
802 {
803 {10, {-8.52, 0.92, -3.18, 0.79, -0.4, 0.77, -0.67, 2.22, -2.61, 2.41, 40.18,
804 16.99, 2.5, 8.0, 4.0, 4.0, 20.0, 1.6, 0.09, 11.8, 1.14, 3.0}},
805 {20, {-8.59, 0.79, -3.05, 0.87, -0.15, 0.97, -0.34, 3.04, -2.82, 2.59, 23.62,
806 18.96, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.09, 11.6, 2.78, 3.0}},
807 {30, {-8.51, 0.65, -2.98, 1.04, -0.18, 1.58, 0.07, 1.33, -2.48, 1.02, 12.48,
808 14.23, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.11, 13.05, 3.87, 3.0}},
809 {40, {-8.49, 0.48, -3.11, 1.06, -0.31, 1.69, -0.08, 1.45, -2.76, 1.27, 8.56,
810 11.06, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.15, 14.56, 4.94, 3.0}},
811 {50, {-8.48, 0.46, -3.19, 1.12, -0.58, 2.13, -0.21, 1.62, -2.93, 1.38, 7.42,
812 11.21, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.18, 15.35, 5.41, 3.0}},
813 {60, {-8.44, 0.34, -3.25, 1.14, -0.9, 2.51, -0.25, 1.06, -3.05, 0.96, 5.97,
814 9.47, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.27, 16.97, 6.31, 3.0}},
815 {70, {-8.4, 0.27, -3.33, 1.25, -1.16, 2.47, -0.61, 1.88, -3.45, 1.51, 4.88,
816 7.24, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.42, 17.96, 6.66, 3.0}},
817 {80, {-8.37, 0.19, -3.22, 1.35, -1.48, 2.61, -0.79, 1.87, -3.66, 1.49, 4.22,
818 5.79, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 0.86, 20.68, 7.31, 3.0}},
819 {90, {-8.35, 0.14, -2.83, 1.62, -1.14, 1.7, -0.58, 1.19, -3.56, 0.89, 3.81,
820 4.25, 2.5, 8.0, 4.0, 3.0, 20.0, 1.6, 2.55, 25.08, 9.23, 3.0}},
821 }},
822};
823
824/**
825 * The nested map containing the 3GPP value tables for the NTN Urban NLOS scenario.
826 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
827 * the quantized elevation angle.
828 * The inner vector collects the table3gpp values.
829 */
830static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNUrbanNLOS{
831 {"S",
832 {
833 {10, {-7.24, 1.26, -1.58, 0.89, 0.13, 2.99, -1.13, 2.66, -2.87, 2.76, 0.0,
834 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.08, 14.72, 1.57, 3.0}},
835 {20, {-7.7, 0.99, -1.67, 0.89, 0.19, 3.12, 0.49, 2.03, -2.68, 2.76, 0.0,
836 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.1, 14.62, 4.3, 3.0}},
837 {30, {-7.82, 0.86, -1.84, 1.3, 0.44, 2.69, 0.95, 1.54, -2.12, 1.54, 0.0,
838 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.14, 16.4, 6.64, 3.0}},
839 {40, {-8.04, 0.75, -2.02, 1.15, 0.48, 2.45, 1.15, 1.02, -2.27, 1.77, 0.0,
840 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.22, 17.86, 9.21, 3.0}},
841 {50, {-8.08, 0.77, -2.06, 1.23, 0.56, 2.17, 1.14, 1.61, -2.5, 2.36, 0.0,
842 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.31, 19.74, 10.32, 3.0}},
843 {60, {-8.1, 0.76, -1.99, 1.02, 0.55, 1.93, 1.13, 1.84, -2.47, 2.33, 0.0,
844 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.49, 19.73, 10.3, 3.0}},
845 {70, {-8.16, 0.73, -2.19, 1.78, 0.48, 1.72, 1.16, 1.81, -2.83, 2.84, 0.0,
846 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 0.97, 20.5, 10.2, 3.0}},
847 {80, {-8.03, 0.79, -1.88, 1.55, 0.53, 1.51, 1.28, 1.35, -2.82, 2.87, 0.0,
848 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 1.52, 26.16, 12.27, 3.0}},
849 {90, {-8.33, 0.7, -2.0, 1.4, 0.32, 1.2, 1.42, 0.6, -4.55, 4.27, 0.0,
850 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 5.36, 25.83, 12.75, 3.0}},
851 }},
852 {"Ka",
853 {
854 {10, {-7.24, 1.26, -1.58, 0.89, 0.13, 2.99, -1.13, 2.66, -2.87, 2.76, 0.0,
855 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.08, 14.72, 1.57, 3.0}},
856 {20, {-7.7, 0.99, -1.67, 0.89, 0.19, 3.12, 0.49, 2.03, -2.68, 2.76, 0.0,
857 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.1, 14.62, 4.3, 3.0}},
858 {30, {-7.82, 0.86, -1.84, 1.3, 0.44, 2.69, 0.95, 1.54, -2.12, 1.54, 0.0,
859 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.14, 16.4, 6.64, 3.0}},
860 {40, {-8.04, 0.75, -2.02, 1.15, 0.48, 2.45, 1.15, 1.02, -2.27, 1.77, 0.0,
861 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.22, 17.86, 9.21, 3.0}},
862 {50, {-8.08, 0.77, -2.06, 1.23, 0.56, 2.17, 1.14, 1.61, -2.5, 2.36, 0.0,
863 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.31, 19.74, 10.32, 3.0}},
864 {60, {-8.1, 0.76, -1.99, 1.02, 0.55, 1.93, 1.13, 1.84, -2.47, 2.33, 0.0,
865 0.0, 2.3, 7.0, 3.0, 3.0, 20.0, 1.6, 0.49, 19.73, 10.3, 3.0}},
866 {70, {-8.16, 0.73, -2.19, 1.78, 0.48, 1.72, 1.16, 1.81, -2.83, 2.84, 0.0,
867 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 0.97, 20.5, 10.2, 3.0}},
868 {80, {-8.03, 0.79, -1.88, 1.55, 0.53, 1.51, 1.28, 1.35, -2.82, 2.87, 0.0,
869 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 1.52, 26.16, 12.27, 3.0}},
870 {90, {-8.33, 0.7, -2.0, 1.4, 0.32, 1.2, 1.42, 0.6, -4.55, 4.27, 0.0,
871 0.0, 2.3, 7.0, 3.0, 2.0, 20.0, 1.6, 5.36, 25.83, 12.75, 3.0}},
872 }},
873};
874
875/**
876 * The nested map containing the 3GPP value tables for the NTN Suburban LOS scenario.
877 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
878 * the quantized elevation angle.
879 * The inner vector collects the table3gpp values.
880 */
881static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNSuburbanLOS{
882 {"S",
883 {
884 {10, {-8.16, 0.99, -3.57, 1.62, 0.05, 1.84, -1.78, 0.62, -1.06, 0.96, 11.4,
885 6.26, 2.2, 21.3, 7.6, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
886 {20, {-8.56, 0.96, -3.8, 1.74, -0.38, 1.94, -1.84, 0.81, -1.21, 0.95, 19.45,
887 10.32, 3.36, 21.0, 8.9, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
888 {30, {-8.72, 0.79, -3.77, 1.72, -0.56, 1.75, -1.67, 0.57, -1.28, 0.49, 20.8,
889 16.34, 3.5, 21.2, 8.5, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
890 {40, {-8.71, 0.81, -3.57, 1.6, -0.59, 1.82, -1.59, 0.86, -1.32, 0.79, 21.2,
891 15.63, 2.81, 21.1, 8.4, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
892 {50, {-8.72, 1.12, -3.42, 1.49, -0.58, 1.87, -1.55, 1.05, -1.39, 0.97, 21.6,
893 14.22, 2.39, 20.7, 9.2, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
894 {60, {-8.66, 1.23, -3.27, 1.43, -0.55, 1.92, -1.51, 1.23, -1.36, 1.17, 19.75,
895 14.19, 2.73, 20.6, 9.8, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
896 {70, {-8.38, 0.55, -3.08, 1.36, -0.28, 1.16, -1.27, 0.54, -1.08, 0.62, 12.0,
897 5.7, 2.07, 20.3, 10.8, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
898 {80, {-8.34, 0.63, -2.75, 1.26, -0.17, 1.09, -1.28, 0.67, -1.31, 0.76, 12.85,
899 9.91, 2.04, 19.8, 12.2, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
900 {90, {-8.34, 0.63, -2.75, 1.26, -0.17, 1.09, -1.28, 0.67, -1.31, 0.76, 12.85,
901 9.91, 2.04, 19.1, 13.0, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
902 }},
903 {"Ka",
904 {
905 {10, {-8.07, 0.46, -3.55, 0.48, 0.89, 0.67, 0.63, 0.35, -3.37, 0.28, 8.9,
906 4.4, 2.5, 23.2, 5.0, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
907 {20, {-8.61, 0.45, -3.69, 0.41, 0.31, 0.78, 0.76, 0.3, -3.28, 0.27, 14.0,
908 4.6, 2.5, 23.6, 4.5, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
909 {30, {-8.72, 0.28, -3.59, 0.41, 0.02, 0.75, 1.11, 0.28, -3.04, 0.26, 11.3,
910 3.7, 2.5, 23.5, 4.7, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
911 {40, {-8.63, 0.17, -3.38, 0.35, -0.1, 0.65, 1.37, 0.23, -2.88, 0.21, 9.0,
912 3.5, 2.5, 23.4, 5.2, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
913 {50, {-8.54, 0.14, -3.23, 0.35, -0.19, 0.55, 1.53, 0.23, -2.83, 0.18, 7.5,
914 3.0, 2.5, 23.2, 5.7, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
915 {60, {-8.48, 0.15, -3.19, 0.43, -0.54, 0.96, 1.65, 0.17, -2.86, 0.17, 6.6,
916 2.6, 2.5, 23.3, 5.9, 3.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
917 {70, {-8.42, 0.09, -2.83, 0.33, -0.24, 0.43, 1.74, 0.11, -2.95, 0.1, 5.9,
918 1.7, 2.5, 23.4, 6.2, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
919 {80, {-8.39, 0.05, -2.66, 0.44, -0.52, 0.93, 1.82, 0.05, -3.21, 0.07, 5.5,
920 0.7, 2.5, 23.2, 7.0, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
921 {90, {-8.37, 0.02, -1.22, 0.31, -0.15, 0.44, 1.87, 0.02, -3.49, 0.24, 5.4,
922 0.3, 2.5, 23.1, 7.6, 2.0, 20.0, 1.6, 0.0, 11.0, 7.0, 3.0}},
923 }},
924};
925
926/**
927 * The nested map containing the 3GPP value tables for the NTN Suburban NLOS scenario.
928 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
929 * the quantized elevation angle.
930 * The inner vector collects the table3gpp values.
931 */
932static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNSuburbanNLOS{
933 {"S",
934 {
935 {10, {-7.43, 0.5, -2.89, 0.41, 1.49, 0.4, 0.81, 0.36, -3.09, 0.32, 0.0,
936 0.0, 2.3, 22.5, 5.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
937 {20, {-7.63, 0.61, -2.76, 0.41, 1.24, 0.82, 1.06, 0.41, -2.93, 0.47, 0.0,
938 0.0, 2.3, 19.4, 8.5, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
939 {30, {-7.86, 0.56, -2.64, 0.41, 1.06, 0.71, 1.12, 0.4, -2.91, 0.46, 0.0,
940 0.0, 2.3, 15.5, 10.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
941 {40, {-7.96, 0.58, -2.41, 0.52, 0.91, 0.55, 1.14, 0.39, -2.78, 0.54, 0.0,
942 0.0, 2.3, 13.9, 10.6, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
943 {50, {-7.98, 0.59, -2.42, 0.7, 0.98, 0.58, 1.29, 0.35, -2.7, 0.45, 0.0,
944 0.0, 2.3, 11.7, 10.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
945 {60, {-8.45, 0.47, -2.53, 0.5, 0.49, 1.37, 1.38, 0.36, -3.03, 0.36, 0.0,
946 0.0, 2.3, 9.8, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
947 {70, {-8.21, 0.36, -2.35, 0.58, 0.73, 0.49, 1.36, 0.29, -2.9, 0.42, 0.0,
948 0.0, 2.3, 10.3, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
949 {80, {-8.69, 0.29, -2.31, 0.73, -0.04, 1.48, 1.38, 0.2, -3.2, 0.3, 0.0,
950 0.0, 2.3, 15.6, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
951 {90, {-8.69, 0.29, -2.31, 0.73, -0.04, 1.48, 1.38, 0.2, -3.2, 0.3, 0.0,
952 0.0, 2.3, 15.6, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
953 }},
954 {"Ka",
955 {
956 {10, {-7.43, 0.5, -2.89, 0.41, 1.49, 0.4, 0.81, 0.36, -3.09, 0.32, 0.0,
957 0.0, 2.3, 22.5, 5.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
958 {20, {-7.63, 0.61, -2.76, 0.41, 1.24, 0.82, 1.06, 0.41, -2.93, 0.47, 0.0,
959 0.0, 2.3, 19.4, 8.5, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
960 {30, {-7.86, 0.56, -2.64, 0.41, 1.06, 0.71, 1.12, 0.4, -2.91, 0.46, 0.0,
961 0.0, 2.3, 15.5, 10.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
962 {40, {-7.96, 0.58, -2.41, 0.52, 0.91, 0.55, 1.14, 0.39, -2.78, 0.54, 0.0,
963 0.0, 2.3, 13.9, 10.6, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
964 {50, {-7.98, 0.59, -2.42, 0.7, 0.98, 0.58, 1.29, 0.35, -2.7, 0.45, 0.0,
965 0.0, 2.3, 11.7, 10.0, 4.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
966 {60, {-8.45, 0.47, -2.53, 0.5, 0.49, 1.37, 1.38, 0.36, -3.03, 0.36, 0.0,
967 0.0, 2.3, 9.8, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
968 {70, {-8.21, 0.36, -2.35, 0.58, 0.73, 0.49, 1.36, 0.29, -2.9, 0.42, 0.0,
969 0.0, 2.3, 10.3, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
970 {80, {-8.69, 0.29, -2.31, 0.73, -0.04, 1.48, 1.38, 0.2, -3.2, 0.3, 0.0,
971 0.0, 2.3, 15.6, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
972 {90, {-8.69, 0.29, -2.31, 0.73, -0.04, 1.48, 1.38, 0.2, -3.2, 0.3, 0.0,
973 0.0, 2.3, 15.6, 9.1, 3.0, 20.0, 1.6, 0.0, 15.0, 7.0, 3.0}},
974 }},
975};
976
977/**
978 * The nested map containing the 3GPP value tables for the NTN Rural LOS scenario.
979 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
980 * the quantized elevation angle.
981 * The inner vector collects the table3gpp values.
982 */
983static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNRuralLOS{
984 {"S",
985 {
986 {10, {-9.55, 0.66, -3.42, 0.89, -9.45, 7.83, -4.2, 6.3, -6.03, 5.19, 24.72,
987 5.07, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.39, 10.81, 1.94, 3.0}},
988 {20, {-8.68, 0.44, -3.0, 0.63, -4.45, 6.86, -2.31, 5.04, -4.31, 4.18, 12.31,
989 5.75, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.31, 8.09, 1.83, 3.0}},
990 {30, {-8.46, 0.28, -2.86, 0.52, -2.39, 5.14, -0.28, 0.81, -2.57, 0.61, 8.05,
991 5.46, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.29, 13.7, 2.28, 3.0}},
992 {40, {-8.36, 0.19, -2.78, 0.45, -1.28, 3.44, -0.38, 1.16, -2.59, 0.79, 6.21,
993 5.23, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.37, 20.05, 2.93, 3.0}},
994 {50, {-8.29, 0.14, -2.7, 0.42, -0.99, 2.59, -0.38, 0.82, -2.59, 0.65, 5.04,
995 3.95, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.61, 24.51, 2.84, 3.0}},
996 {60, {-8.26, 0.1, -2.66, 0.41, -1.05, 2.42, -0.46, 0.67, -2.65, 0.52, 4.42,
997 3.75, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.9, 26.35, 3.17, 3.0}},
998 {70, {-8.22, 0.1, -2.53, 0.42, -0.9, 1.78, -0.49, 1.0, -2.69, 0.78, 3.92,
999 2.56, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 1.43, 31.84, 3.88, 3.0}},
1000 {80, {-8.2, 0.05, -2.21, 0.5, -0.89, 1.65, -0.53, 1.18, -2.65, 1.01, 3.65,
1001 1.77, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 2.87, 36.62, 4.17, 3.0}},
1002 {90, {-8.19, 0.06, -1.78, 0.91, -0.81, 1.26, -0.46, 0.91, -2.65, 0.71, 3.59,
1003 1.77, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 5.48, 36.77, 4.29, 3.0}},
1004 }},
1005 {"Ka",
1006 {
1007 {10, {-9.68, 0.46, -4.03, 0.91, -9.74, 7.52, -5.85, 6.51, -7.45, 5.3, 25.43,
1008 7.04, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.36, 4.63, 0.75, 3.0}},
1009 {20, {-8.86, 0.29, -3.55, 0.7, -4.88, 6.67, -3.27, 5.36, -5.25, 4.42, 12.72,
1010 7.47, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.3, 6.83, 1.25, 3.0}},
1011 {30, {-8.59, 0.18, -3.45, 0.55, -2.6, 4.63, -0.88, 0.93, -3.16, 0.68, 8.4,
1012 7.18, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.25, 12.91, 1.93, 3.0}},
1013 {40, {-8.46, 0.19, -3.38, 0.52, -1.92, 3.45, -0.93, 0.96, -3.15, 0.73, 6.52,
1014 6.88, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.35, 18.9, 2.37, 3.0}},
1015 {50, {-8.36, 0.14, -3.33, 0.46, -1.56, 2.44, -0.99, 0.97, -3.2, 0.77, 5.24,
1016 5.28, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.53, 22.44, 2.66, 3.0}},
1017 {60, {-8.3, 0.15, -3.29, 0.43, -1.66, 2.38, -1.04, 0.83, -3.27, 0.61, 4.57,
1018 4.92, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 0.88, 25.69, 3.23, 3.0}},
1019 {70, {-8.26, 0.13, -3.24, 0.46, -1.59, 1.67, -1.17, 1.01, -3.42, 0.74, 4.02,
1020 3.4, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 1.39, 27.95, 3.71, 3.0}},
1021 {80, {-8.22, 0.03, -2.9, 0.44, -1.58, 1.44, -1.19, 1.01, -3.36, 0.79, 3.7,
1022 2.22, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 2.7, 31.45, 4.17, 3.0}},
1023 {90, {-8.21, 0.07, -2.5, 0.82, -1.51, 1.13, -1.13, 0.85, -3.35, 0.65, 3.62,
1024 2.28, 3.8, 12.0, 4.0, 2.0, 20.0, 0.0, 4.97, 28.01, 4.14, 3.0}},
1025 }},
1026};
1027
1028/**
1029 * The nested map containing the 3GPP value tables for the NTN Rural NLOS scenario
1030 * The outer key specifies the band (either "S" or "Ka"), while the inner key represents
1031 * the quantized elevation angle.
1032 * The inner vector collects the table3gpp values.
1033 */
1034static const std::map<std::string, std::map<int, std::array<float, 22>>> NTNRuralNLOS{
1035 {"S",
1036 {
1037 {10, {-9.01, 1.59, -2.9, 1.34, -3.33, 6.22, -0.88, 3.26, -4.92, 3.96, 0.0,
1038 0.0, 1.7, 7.0, 3.0, 3.0, 20.0, 0.0, 0.03, 18.16, 2.32, 3.0}},
1039 {20, {-8.37, 0.95, -2.5, 1.18, -0.74, 4.22, -0.07, 3.29, -4.06, 4.07, 0.0,
1040 0.0, 1.7, 7.0, 3.0, 3.0, 20.0, 0.0, 0.05, 26.82, 7.34, 3.0}},
1041 {30, {-8.05, 0.92, -2.12, 1.08, 0.08, 3.02, 0.75, 1.92, -2.33, 1.7, 0.0,
1042 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.07, 21.99, 8.28, 3.0}},
1043 {40, {-7.92, 0.92, -1.99, 1.06, 0.32, 2.45, 0.72, 1.92, -2.24, 2.01, 0.0,
1044 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.1, 22.86, 8.76, 3.0}},
1045 {50, {-7.92, 0.87, -1.9, 1.05, 0.53, 1.63, 0.95, 1.45, -2.24, 2.0, 0.0,
1046 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.15, 25.93, 9.68, 3.0}},
1047 {60, {-7.96, 0.87, -1.85, 1.06, 0.33, 2.08, 0.97, 1.62, -2.22, 1.82, 0.0,
1048 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.22, 27.79, 9.94, 3.0}},
1049 {70, {-7.91, 0.82, -1.69, 1.14, 0.55, 1.58, 1.1, 1.43, -2.19, 1.66, 0.0,
1050 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.5, 28.5, 8.9, 3.0}},
1051 {80, {-7.79, 0.86, -1.46, 1.16, 0.45, 2.01, 0.97, 1.88, -2.41, 2.58, 0.0,
1052 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 1.04, 37.53, 13.74, 3.0}},
1053 {90, {-7.74, 0.81, -1.32, 1.3, 0.4, 2.19, 1.35, 0.62, -2.45, 2.52, 0.0,
1054 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 2.11, 29.23, 12.16, 3.0}},
1055 }},
1056 {"Ka",
1057 {
1058 {10, {-9.13, 1.91, -2.9, 1.32, -3.4, 6.28, -1.19, 3.81, -5.47, 4.39, 0.0,
1059 0.0, 1.7, 7.0, 3.0, 3.0, 20.0, 0.0, 0.03, 18.21, 2.13, 3.0}},
1060 {20, {-8.39, 0.94, -2.53, 1.18, -0.51, 3.75, -0.11, 3.33, -4.06, 4.04, 0.0,
1061 0.0, 1.7, 7.0, 3.0, 3.0, 20.0, 0.0, 0.05, 24.08, 6.52, 3.0}},
1062 {30, {-8.1, 0.92, -2.16, 1.08, 0.06, 2.95, 0.72, 1.93, -2.32, 1.54, 0.0,
1063 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.07, 22.06, 7.72, 3.0}},
1064 {40, {-7.96, 0.94, -2.04, 1.09, 0.2, 2.65, 0.69, 1.91, -2.19, 1.73, 0.0,
1065 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.09, 21.4, 8.45, 3.0}},
1066 {50, {-7.99, 0.89, -1.99, 1.08, 0.4, 1.85, 0.84, 1.7, -2.16, 1.5, 0.0,
1067 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.16, 24.26, 8.92, 3.0}},
1068 {60, {-8.05, 0.87, -1.95, 1.06, 0.32, 1.83, 0.99, 1.27, -2.24, 1.64, 0.0,
1069 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.22, 24.15, 8.76, 3.0}},
1070 {70, {-8.01, 0.82, -1.81, 1.17, 0.46, 1.57, 0.95, 1.86, -2.29, 1.66, 0.0,
1071 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.51, 25.99, 9.0, 3.0}},
1072 {80, {-8.05, 1.65, -1.56, 1.2, 0.33, 1.99, 0.92, 1.84, -2.65, 2.86, 0.0,
1073 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 0.89, 36.07, 13.6, 3.0}},
1074 {90, {-7.91, 0.76, -1.53, 1.27, 0.24, 2.18, 1.29, 0.59, -2.23, 1.12, 0.0,
1075 0.0, 1.7, 7.0, 3.0, 2.0, 20.0, 0.0, 1.68, 24.51, 10.56, 3.0}},
1076 }},
1077};
1078
1079/**
1080 * According to table 7.5-6, only cluster number equal to 8, 10, 11, 12, 19 and 20 is valid.
1081 * (38.901) Not sure why the other cases are in Table 7.5-2. Added case 2 and 3 for the NTN
1082 * according to table 6.7.2-1aa (28.811)
1083 */
1084static const std::unordered_map<int, double> cNlosTablePhi = {
1085 {2, 0.501},
1086 {3, 0.680},
1087 {4, 0.779},
1088 {5, 0.860},
1089 {8, 1.018},
1090 {10, 1.090},
1091 {11, 1.123},
1092 {12, 1.146},
1093 {14, 1.190},
1094 {15, 1.221},
1095 {16, 1.226},
1096 {19, 1.273},
1097 {20, 1.289},
1098};
1099/**
1100 * Table 7.5-4 (38.901)
1101 * Added case 2, 3 and 4 for the NTN according to table 6.7.2-1ab (28.811)
1102 */
1103static const std::unordered_map<int, double> cNlosTableTheta = {
1104 {2, 0.430},
1105 {3, 0.594},
1106 {4, 0.697},
1107 {8, 0.889},
1108 {10, 0.957},
1109 {11, 1.031},
1110 {12, 1.104},
1111 {15, 1.1088},
1112 {19, 1.184},
1113 {20, 1.178},
1114};
1115
1127
1132
1133void
1135{
1136 NS_LOG_FUNCTION(this);
1138 {
1139 m_channelConditionModel->Dispose();
1140 }
1141 m_channelMatrixMap.clear();
1142 m_channelParamsMap.clear();
1143 m_channelConditionModel = nullptr;
1144}
1145
1146TypeId
1148{
1149 static TypeId tid =
1150 TypeId("ns3::ThreeGppChannelModel")
1151 .SetGroupName("Spectrum")
1153 .AddConstructor<ThreeGppChannelModel>()
1154 .AddAttribute("Frequency",
1155 "The operating Frequency in Hz",
1156 DoubleValue(500.0e6),
1160 .AddAttribute(
1161 "Scenario",
1162 "The 3GPP scenario (RMa, UMa, UMi-StreetCanyon, InH-OfficeOpen, InH-OfficeMixed, "
1163 "NTN-DenseUrban, NTN-Urban, NTN-Suburban, NTN-Rural)",
1164 StringValue("UMa"),
1168 .AddAttribute("ChannelConditionModel",
1169 "Pointer to the channel condition model",
1170 PointerValue(),
1174 .AddAttribute(
1175 "UpdatePeriod",
1176 "Spatial-consistency update period. When non-zero, ns-3 attempts to evolve cached "
1177 "channel parameters using update equations aligned with 3GPP TR 38.901 "
1178 "Procedure A (Sec. 7.6.3.2) whenever the channel is evaluated and the period has "
1179 "elapsed. If the 1 m step-size constraint is violated at an update attempt, the "
1180 "model re-generates channel parameters (new realization).",
1184 // attributes for the blockage model
1185 .AddAttribute("Blockage",
1186 "Enable blockage model A (sec 7.6.4.1)",
1187 BooleanValue(false),
1190 .AddAttribute("NumNonselfBlocking",
1191 "number of non-self-blocking regions",
1192 IntegerValue(4),
1195 .AddAttribute("PortraitMode",
1196 "true for portrait mode, false for landscape mode",
1197 BooleanValue(true),
1200 .AddAttribute("BlockerSpeed",
1201 "The speed of moving blockers, the unit is m/s",
1202 DoubleValue(1),
1205 .AddAttribute("vScatt",
1206 "Maximum speed of the vehicle in the layout (see 3GPP TR 37.885 v15.3.0, "
1207 "Sec. 6.2.3)."
1208 "Used to compute the additional contribution for the Doppler of"
1209 "delayed (reflected) paths",
1210 DoubleValue(0.0),
1213 return tid;
1214}
1215
1216void
1222
1229
1230void
1232{
1233 NS_LOG_FUNCTION(this);
1234 NS_ASSERT_MSG(f >= 500.0e6 && f <= 100.0e9,
1235 "Frequency should be between 0.5 and 100 GHz but is " << f);
1236 m_frequency = f;
1237}
1238
1239double
1241{
1242 NS_LOG_FUNCTION(this);
1243 return m_frequency;
1244}
1245
1246void
1247ThreeGppChannelModel::SetScenario(const std::string& scenario)
1248{
1249 NS_LOG_FUNCTION(this);
1250 NS_ASSERT_MSG(scenario == "RMa" || scenario == "UMa" || scenario == "UMi-StreetCanyon" ||
1251 scenario == "InH-OfficeOpen" || scenario == "InH-OfficeMixed" ||
1252 scenario == "V2V-Urban" || scenario == "V2V-Highway" ||
1253 scenario == "NTN-DenseUrban" || scenario == "NTN-Urban" ||
1254 scenario == "NTN-Suburban" || scenario == "NTN-Rural",
1255 "Unknown scenario, choose between: RMa, UMa, UMi-StreetCanyon, "
1256 "InH-OfficeOpen, InH-OfficeMixed, V2V-Urban, V2V-Highway, "
1257 "NTN-DenseUrban, NTN-Urban, NTN-Suburban or NTN-Rural");
1258 m_scenario = scenario;
1259}
1260
1261std::string
1263{
1264 NS_LOG_FUNCTION(this);
1265 return m_scenario;
1266}
1267
1271 Ptr<const ChannelCondition> channelCondition) const
1272{
1273 NS_LOG_FUNCTION(this);
1274
1275 // NOTE we assume hUT = min (height(a), height(b)) and
1276 // hBS = max (height (a), height (b))
1277 double hUT = std::min(aMob->GetPosition().z, bMob->GetPosition().z);
1278 double hBS = std::max(aMob->GetPosition().z, bMob->GetPosition().z);
1279
1280 double distance2D = sqrt(pow(aMob->GetPosition().x - bMob->GetPosition().x, 2) +
1281 pow(aMob->GetPosition().y - bMob->GetPosition().y, 2));
1282
1283 double fcGHz = m_frequency / 1.0e9;
1285 // table3gpp includes the following parameters:
1286 // numOfCluster, raysPerCluster, uLgDS, sigLgDS, uLgASD, sigLgASD,
1287 // uLgASA, sigLgASA, uLgZSA, sigLgZSA, uLgZSD, sigLgZSD, offsetZOD,
1288 // cDS, cASD, cASA, cZSA, uK, sigK, rTau, uXpr, sigXpr, shadowingStd
1289
1290 bool los = channelCondition->IsLos();
1291 bool o2i = channelCondition->IsO2i();
1292
1293 // In NLOS case, parameter uK and sigK are not used and they are set to 0
1294 if (m_scenario == "RMa")
1295 {
1296 if (los && !o2i)
1297 {
1298 // 3GPP mentioned that 3.91 ns should be used when the Cluster DS (cDS)
1299 // entry is N/A.
1300 table3gpp->m_numOfCluster = 11;
1301 table3gpp->m_raysPerCluster = 20;
1302 table3gpp->m_uLgDS = -7.49;
1303 table3gpp->m_sigLgDS = 0.55;
1304 table3gpp->m_uLgASD = 0.90;
1305 table3gpp->m_sigLgASD = 0.38;
1306 table3gpp->m_uLgASA = 1.52;
1307 table3gpp->m_sigLgASA = 0.24;
1308 table3gpp->m_uLgZSA = 0.47;
1309 table3gpp->m_sigLgZSA = 0.40;
1310 table3gpp->m_uLgZSD = 0.34;
1311 table3gpp->m_sigLgZSD =
1312 std::max(-1.0, -0.17 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.22);
1313 table3gpp->m_offsetZOD = 0;
1314 table3gpp->m_cDS = 3.91e-9;
1315 table3gpp->m_cASD = 2;
1316 table3gpp->m_cASA = 3;
1317 table3gpp->m_cZSA = 3;
1318 table3gpp->m_uK = 7;
1319 table3gpp->m_sigK = 4;
1320 table3gpp->m_rTau = 3.8;
1321 table3gpp->m_uXpr = 12;
1322 table3gpp->m_sigXpr = 4;
1323 table3gpp->m_perClusterShadowingStd = 3;
1324 table3gpp->m_perClusterRayDcorDistance = 50;
1325 table3gpp->m_blockerDcorDistance = 10;
1326
1327 for (uint8_t row = 0; row < 7; row++)
1328 {
1329 for (uint8_t column = 0; column < 7; column++)
1330 {
1331 table3gpp->m_sqrtC[row][column] = sqrtC_RMa_LOS[row][column];
1332 }
1333 }
1334 }
1335 else if (!los && !o2i)
1336 {
1337 table3gpp->m_numOfCluster = 10;
1338 table3gpp->m_raysPerCluster = 20;
1339 table3gpp->m_uLgDS = -7.43;
1340 table3gpp->m_sigLgDS = 0.48;
1341 table3gpp->m_uLgASD = 0.95;
1342 table3gpp->m_sigLgASD = 0.45;
1343 table3gpp->m_uLgASA = 1.52;
1344 table3gpp->m_sigLgASA = 0.13;
1345 table3gpp->m_uLgZSA = 0.58;
1346 table3gpp->m_sigLgZSA = 0.37;
1347 table3gpp->m_uLgZSD =
1348 std::max(-1.0, -0.19 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.28);
1349 table3gpp->m_sigLgZSD = 0.30;
1350 table3gpp->m_offsetZOD = atan((35 - 3.5) / distance2D) - atan((35 - 1.5) / distance2D);
1351 table3gpp->m_cDS = 3.91e-9;
1352 table3gpp->m_cASD = 2;
1353 table3gpp->m_cASA = 3;
1354 table3gpp->m_cZSA = 3;
1355 table3gpp->m_uK = 0;
1356 table3gpp->m_sigK = 0;
1357 table3gpp->m_rTau = 1.7;
1358 table3gpp->m_uXpr = 7;
1359 table3gpp->m_sigXpr = 3;
1360 table3gpp->m_perClusterShadowingStd = 3;
1361 table3gpp->m_perClusterRayDcorDistance = 60;
1362 table3gpp->m_blockerDcorDistance = 10;
1363
1364 for (uint8_t row = 0; row < 6; row++)
1365 {
1366 for (uint8_t column = 0; column < 6; column++)
1367 {
1368 table3gpp->m_sqrtC[row][column] = sqrtC_RMa_NLOS[row][column];
1369 }
1370 }
1371 }
1372 else // o2i
1373 {
1374 table3gpp->m_numOfCluster = 10;
1375 table3gpp->m_raysPerCluster = 20;
1376 table3gpp->m_uLgDS = -7.47;
1377 table3gpp->m_sigLgDS = 0.24;
1378 table3gpp->m_uLgASD = 0.67;
1379 table3gpp->m_sigLgASD = 0.18;
1380 table3gpp->m_uLgASA = 1.66;
1381 table3gpp->m_sigLgASA = 0.21;
1382 table3gpp->m_uLgZSA = 0.93;
1383 table3gpp->m_sigLgZSA = 0.22;
1384 table3gpp->m_uLgZSD =
1385 std::max(-1.0, -0.19 * (distance2D / 1000.0) - 0.01 * (hUT - 1.5) + 0.28);
1386 table3gpp->m_sigLgZSD = 0.30;
1387 table3gpp->m_offsetZOD = atan((35 - 3.5) / distance2D) - atan((35 - 1.5) / distance2D);
1388 table3gpp->m_cDS = 3.91e-9;
1389 table3gpp->m_cASD = 2;
1390 table3gpp->m_cASA = 3;
1391 table3gpp->m_cZSA = 3;
1392 table3gpp->m_uK = 0;
1393 table3gpp->m_sigK = 0;
1394 table3gpp->m_rTau = 1.7;
1395 table3gpp->m_uXpr = 7;
1396 table3gpp->m_sigXpr = 3;
1397 table3gpp->m_perClusterShadowingStd = 3;
1398 table3gpp->m_perClusterRayDcorDistance = 15;
1399 table3gpp->m_blockerDcorDistance = 5;
1400
1401 for (uint8_t row = 0; row < 6; row++)
1402 {
1403 for (uint8_t column = 0; column < 6; column++)
1404 {
1405 table3gpp->m_sqrtC[row][column] = sqrtC_RMa_O2I[row][column];
1406 }
1407 }
1408 }
1409 }
1410 else if (m_scenario == "UMa")
1411 {
1412 if (los && !o2i)
1413 {
1414 table3gpp->m_numOfCluster = 12;
1415 table3gpp->m_raysPerCluster = 20;
1416 table3gpp->m_uLgDS = -6.955 - 0.0963 * log10(fcGHz);
1417 table3gpp->m_sigLgDS = 0.66;
1418 table3gpp->m_uLgASD = 1.06 + 0.1114 * log10(fcGHz);
1419 table3gpp->m_sigLgASD = 0.28;
1420 table3gpp->m_uLgASA = 1.81;
1421 table3gpp->m_sigLgASA = 0.20;
1422 table3gpp->m_uLgZSA = 0.95;
1423 table3gpp->m_sigLgZSA = 0.16;
1424 table3gpp->m_uLgZSD =
1425 std::max(-0.5, -2.1 * distance2D / 1000.0 - 0.01 * (hUT - 1.5) + 0.75);
1426 table3gpp->m_sigLgZSD = 0.40;
1427 table3gpp->m_offsetZOD = 0;
1428 table3gpp->m_cDS = std::max(0.25, -3.4084 * log10(fcGHz) + 6.5622) * 1e-9;
1429 table3gpp->m_cASD = 5;
1430 table3gpp->m_cASA = 11;
1431 table3gpp->m_cZSA = 7;
1432 table3gpp->m_uK = 9;
1433 table3gpp->m_sigK = 3.5;
1434 table3gpp->m_rTau = 2.5;
1435 table3gpp->m_uXpr = 8;
1436 table3gpp->m_sigXpr = 4;
1437 table3gpp->m_perClusterShadowingStd = 3;
1438 table3gpp->m_perClusterRayDcorDistance = 40;
1439 table3gpp->m_blockerDcorDistance = 10;
1440
1441 for (uint8_t row = 0; row < 7; row++)
1442 {
1443 for (uint8_t column = 0; column < 7; column++)
1444 {
1445 table3gpp->m_sqrtC[row][column] = sqrtC_UMa_LOS[row][column];
1446 }
1447 }
1448 }
1449 else
1450 {
1451 double uLgZSD = std::max(-0.5, -2.1 * distance2D / 1000.0 - 0.01 * (hUT - 1.5) + 0.9);
1452
1453 double afc = 0.208 * log10(fcGHz) - 0.782;
1454 double bfc = 25;
1455 double cfc = -0.13 * log10(fcGHz) + 2.03;
1456 double efc = 7.66 * log10(fcGHz) - 5.96;
1457
1458 double offsetZOD = efc - std::pow(10, afc * log10(std::max(bfc, distance2D)) + cfc);
1459
1460 if (!los && !o2i)
1461 {
1462 table3gpp->m_numOfCluster = 20;
1463 table3gpp->m_raysPerCluster = 20;
1464 table3gpp->m_uLgDS = -6.28 - 0.204 * log10(fcGHz);
1465 table3gpp->m_sigLgDS = 0.39;
1466 table3gpp->m_uLgASD = 1.5 - 0.1144 * log10(fcGHz);
1467 table3gpp->m_sigLgASD = 0.28;
1468 table3gpp->m_uLgASA = 2.08 - 0.27 * log10(fcGHz);
1469 table3gpp->m_sigLgASA = 0.11;
1470 table3gpp->m_uLgZSA = -0.3236 * log10(fcGHz) + 1.512;
1471 table3gpp->m_sigLgZSA = 0.16;
1472 table3gpp->m_uLgZSD = uLgZSD;
1473 table3gpp->m_sigLgZSD = 0.49;
1474 table3gpp->m_offsetZOD = offsetZOD;
1475 table3gpp->m_cDS = std::max(0.25, -3.4084 * log10(fcGHz) + 6.5622) * 1e-9;
1476 table3gpp->m_cASD = 2;
1477 table3gpp->m_cASA = 15;
1478 table3gpp->m_cZSA = 7;
1479 table3gpp->m_uK = 0;
1480 table3gpp->m_sigK = 0;
1481 table3gpp->m_rTau = 2.3;
1482 table3gpp->m_uXpr = 7;
1483 table3gpp->m_sigXpr = 3;
1484 table3gpp->m_perClusterShadowingStd = 3;
1485 table3gpp->m_perClusterRayDcorDistance = 50;
1486 table3gpp->m_blockerDcorDistance = 10;
1487
1488 for (uint8_t row = 0; row < 6; row++)
1489 {
1490 for (uint8_t column = 0; column < 6; column++)
1491 {
1492 table3gpp->m_sqrtC[row][column] = sqrtC_UMa_NLOS[row][column];
1493 }
1494 }
1495 }
1496 else //(o2i)
1497 {
1498 table3gpp->m_numOfCluster = 12;
1499 table3gpp->m_raysPerCluster = 20;
1500 table3gpp->m_uLgDS = -6.62;
1501 table3gpp->m_sigLgDS = 0.32;
1502 table3gpp->m_uLgASD = 1.25;
1503 table3gpp->m_sigLgASD = 0.42;
1504 table3gpp->m_uLgASA = 1.76;
1505 table3gpp->m_sigLgASA = 0.16;
1506 table3gpp->m_uLgZSA = 1.01;
1507 table3gpp->m_sigLgZSA = 0.43;
1508 table3gpp->m_uLgZSD = uLgZSD;
1509 table3gpp->m_sigLgZSD = 0.49;
1510 table3gpp->m_offsetZOD = offsetZOD;
1511 table3gpp->m_cDS = 11e-9;
1512 table3gpp->m_cASD = 5;
1513 table3gpp->m_cASA = 8;
1514 table3gpp->m_cZSA = 3;
1515 table3gpp->m_uK = 0;
1516 table3gpp->m_sigK = 0;
1517 table3gpp->m_rTau = 2.2;
1518 table3gpp->m_uXpr = 9;
1519 table3gpp->m_sigXpr = 5;
1520 table3gpp->m_perClusterShadowingStd = 4;
1521 table3gpp->m_perClusterRayDcorDistance = 15;
1522 table3gpp->m_blockerDcorDistance = 5;
1523
1524 for (uint8_t row = 0; row < 6; row++)
1525 {
1526 for (uint8_t column = 0; column < 6; column++)
1527 {
1528 table3gpp->m_sqrtC[row][column] = sqrtC_UMa_O2I[row][column];
1529 }
1530 }
1531 }
1532 }
1533 }
1534 else if (m_scenario == "UMi-StreetCanyon")
1535 {
1536 if (los && !o2i)
1537 {
1538 table3gpp->m_numOfCluster = 12;
1539 table3gpp->m_raysPerCluster = 20;
1540 table3gpp->m_uLgDS = -0.24 * log10(1 + fcGHz) - 7.14;
1541 table3gpp->m_sigLgDS = 0.38;
1542 table3gpp->m_uLgASD = -0.05 * log10(1 + fcGHz) + 1.21;
1543 table3gpp->m_sigLgASD = 0.41;
1544 table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.73;
1545 table3gpp->m_sigLgASA = 0.014 * log10(1 + fcGHz) + 0.28;
1546 table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
1547 table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
1548 table3gpp->m_uLgZSD =
1549 std::max(-0.21, -14.8 * distance2D / 1000.0 + 0.01 * std::abs(hUT - hBS) + 0.83);
1550 table3gpp->m_sigLgZSD = 0.35;
1551 table3gpp->m_offsetZOD = 0;
1552 table3gpp->m_cDS = 5e-9;
1553 table3gpp->m_cASD = 3;
1554 table3gpp->m_cASA = 17;
1555 table3gpp->m_cZSA = 7;
1556 table3gpp->m_uK = 9;
1557 table3gpp->m_sigK = 5;
1558 table3gpp->m_rTau = 3;
1559 table3gpp->m_uXpr = 9;
1560 table3gpp->m_sigXpr = 3;
1561 table3gpp->m_perClusterShadowingStd = 3;
1562 table3gpp->m_perClusterRayDcorDistance = 12;
1563 table3gpp->m_blockerDcorDistance = 10;
1564
1565 for (uint8_t row = 0; row < 7; row++)
1566 {
1567 for (uint8_t column = 0; column < 7; column++)
1568 {
1569 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1570 }
1571 }
1572 }
1573 else
1574 {
1575 double uLgZSD =
1576 std::max(-0.5, -3.1 * distance2D / 1000.0 + 0.01 * std::max(hUT - hBS, 0.0) + 0.2);
1577 double offsetZOD = -1 * std::pow(10, -1.5 * log10(std::max(10.0, distance2D)) + 3.3);
1578 if (!los && !o2i)
1579 {
1580 table3gpp->m_numOfCluster = 19;
1581 table3gpp->m_raysPerCluster = 20;
1582 table3gpp->m_uLgDS = -0.24 * log10(1 + fcGHz) - 6.83;
1583 table3gpp->m_sigLgDS = 0.16 * log10(1 + fcGHz) + 0.28;
1584 table3gpp->m_uLgASD = -0.23 * log10(1 + fcGHz) + 1.53;
1585 table3gpp->m_sigLgASD = 0.11 * log10(1 + fcGHz) + 0.33;
1586 table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
1587 table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
1588 table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1589 table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1590 table3gpp->m_uLgZSD = uLgZSD;
1591 table3gpp->m_sigLgZSD = 0.35;
1592 table3gpp->m_offsetZOD = offsetZOD;
1593 table3gpp->m_cDS = 11e-9;
1594 table3gpp->m_cASD = 10;
1595 table3gpp->m_cASA = 22;
1596 table3gpp->m_cZSA = 7;
1597 table3gpp->m_uK = 0;
1598 table3gpp->m_sigK = 0;
1599 table3gpp->m_rTau = 2.1;
1600 table3gpp->m_uXpr = 8;
1601 table3gpp->m_sigXpr = 3;
1602 table3gpp->m_perClusterShadowingStd = 3;
1603 table3gpp->m_perClusterRayDcorDistance = 15;
1604 table3gpp->m_blockerDcorDistance = 10;
1605
1606 for (uint8_t row = 0; row < 6; row++)
1607 {
1608 for (uint8_t column = 0; column < 6; column++)
1609 {
1610 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
1611 }
1612 }
1613 }
1614 else //(o2i)
1615 {
1616 table3gpp->m_numOfCluster = 12;
1617 table3gpp->m_raysPerCluster = 20;
1618 table3gpp->m_uLgDS = -6.62;
1619 table3gpp->m_sigLgDS = 0.32;
1620 table3gpp->m_uLgASD = 1.25;
1621 table3gpp->m_sigLgASD = 0.42;
1622 table3gpp->m_uLgASA = 1.76;
1623 table3gpp->m_sigLgASA = 0.16;
1624 table3gpp->m_uLgZSA = 1.01;
1625 table3gpp->m_sigLgZSA = 0.43;
1626 table3gpp->m_uLgZSD = uLgZSD;
1627 table3gpp->m_sigLgZSD = 0.35;
1628 table3gpp->m_offsetZOD = offsetZOD;
1629 table3gpp->m_cDS = 11e-9;
1630 table3gpp->m_cASD = 5;
1631 table3gpp->m_cASA = 8;
1632 table3gpp->m_cZSA = 3;
1633 table3gpp->m_uK = 0;
1634 table3gpp->m_sigK = 0;
1635 table3gpp->m_rTau = 2.2;
1636 table3gpp->m_uXpr = 9;
1637 table3gpp->m_sigXpr = 5;
1638 table3gpp->m_perClusterShadowingStd = 4;
1639 table3gpp->m_perClusterRayDcorDistance = 15;
1640 table3gpp->m_blockerDcorDistance = 5;
1641
1642 for (uint8_t row = 0; row < 6; row++)
1643 {
1644 for (uint8_t column = 0; column < 6; column++)
1645 {
1646 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_O2I[row][column];
1647 }
1648 }
1649 }
1650 }
1651 }
1652 else if (m_scenario == "InH-OfficeMixed" || m_scenario == "InH-OfficeOpen")
1653 {
1654 NS_ASSERT_MSG(!o2i, "The indoor scenario does out support outdoor to indoor");
1655 if (los)
1656 {
1657 table3gpp->m_numOfCluster = 15;
1658 table3gpp->m_raysPerCluster = 20;
1659 table3gpp->m_uLgDS = -0.01 * log10(1 + fcGHz) - 7.692;
1660 table3gpp->m_sigLgDS = 0.18;
1661 table3gpp->m_uLgASD = 1.60;
1662 table3gpp->m_sigLgASD = 0.18;
1663 table3gpp->m_uLgASA = -0.19 * log10(1 + fcGHz) + 1.781;
1664 table3gpp->m_sigLgASA = 0.12 * log10(1 + fcGHz) + 0.119;
1665 table3gpp->m_uLgZSA = -0.26 * log10(1 + fcGHz) + 1.44;
1666 table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.264;
1667 table3gpp->m_uLgZSD = -1.43 * log10(1 + fcGHz) + 2.228;
1668 table3gpp->m_sigLgZSD = 0.13 * log10(1 + fcGHz) + 0.30;
1669 table3gpp->m_offsetZOD = 0;
1670 table3gpp->m_cDS = 3.91e-9;
1671 table3gpp->m_cASD = 5;
1672 table3gpp->m_cASA = 8;
1673 table3gpp->m_cZSA = 9;
1674 table3gpp->m_uK = 7;
1675 table3gpp->m_sigK = 4;
1676 table3gpp->m_rTau = 3.6;
1677 table3gpp->m_uXpr = 11;
1678 table3gpp->m_sigXpr = 4;
1679 table3gpp->m_perClusterShadowingStd = 6;
1680 table3gpp->m_perClusterRayDcorDistance = 10;
1681 table3gpp->m_blockerDcorDistance = 5;
1682
1683 for (uint8_t row = 0; row < 7; row++)
1684 {
1685 for (uint8_t column = 0; column < 7; column++)
1686 {
1687 table3gpp->m_sqrtC[row][column] = sqrtC_office_LOS[row][column];
1688 }
1689 }
1690 }
1691 else
1692 {
1693 table3gpp->m_numOfCluster = 19;
1694 table3gpp->m_raysPerCluster = 20;
1695 table3gpp->m_uLgDS = -0.28 * log10(1 + fcGHz) - 7.173;
1696 table3gpp->m_sigLgDS = 0.1 * log10(1 + fcGHz) + 0.055;
1697 table3gpp->m_uLgASD = 1.62;
1698 table3gpp->m_sigLgASD = 0.25;
1699 table3gpp->m_uLgASA = -0.11 * log10(1 + fcGHz) + 1.863;
1700 table3gpp->m_sigLgASA = 0.12 * log10(1 + fcGHz) + 0.059;
1701 table3gpp->m_uLgZSA = -0.15 * log10(1 + fcGHz) + 1.387;
1702 table3gpp->m_sigLgZSA = -0.09 * log10(1 + fcGHz) + 0.746;
1703 table3gpp->m_uLgZSD = 1.08;
1704 table3gpp->m_sigLgZSD = 0.36;
1705 table3gpp->m_offsetZOD = 0;
1706 table3gpp->m_cDS = 3.91e-9;
1707 table3gpp->m_cASD = 5;
1708 table3gpp->m_cASA = 11;
1709 table3gpp->m_cZSA = 9;
1710 table3gpp->m_uK = 0;
1711 table3gpp->m_sigK = 0;
1712 table3gpp->m_rTau = 3;
1713 table3gpp->m_uXpr = 10;
1714 table3gpp->m_sigXpr = 4;
1715 table3gpp->m_perClusterShadowingStd = 3;
1716 table3gpp->m_perClusterRayDcorDistance = 10;
1717 table3gpp->m_blockerDcorDistance = 5;
1718
1719 for (uint8_t row = 0; row < 6; row++)
1720 {
1721 for (uint8_t column = 0; column < 6; column++)
1722 {
1723 table3gpp->m_sqrtC[row][column] = sqrtC_office_NLOS[row][column];
1724 }
1725 }
1726 }
1727 }
1728 else if (m_scenario == "V2V-Urban")
1729 {
1730 if (channelCondition->IsLos())
1731 {
1732 // 3GPP mentioned that 3.91 ns should be used when the Cluster DS (cDS)
1733 // entry is N/A.
1734 table3gpp->m_numOfCluster = 12;
1735 table3gpp->m_raysPerCluster = 20;
1736 table3gpp->m_uLgDS = -0.2 * log10(1 + fcGHz) - 7.5;
1737 table3gpp->m_sigLgDS = 0.1;
1738 table3gpp->m_uLgASD = -0.1 * log10(1 + fcGHz) + 1.6;
1739 table3gpp->m_sigLgASD = 0.1;
1740 table3gpp->m_uLgASA = -0.1 * log10(1 + fcGHz) + 1.6;
1741 table3gpp->m_sigLgASA = 0.1;
1742 table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
1743 table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
1744 table3gpp->m_uLgZSD = -0.1 * log10(1 + fcGHz) + 0.73;
1745 table3gpp->m_sigLgZSD = -0.04 * log10(1 + fcGHz) + 0.34;
1746 table3gpp->m_offsetZOD = 0;
1747 table3gpp->m_cDS = 5 * 1e-9;
1748 table3gpp->m_cASD = 17;
1749 table3gpp->m_cASA = 17;
1750 table3gpp->m_cZSA = 7;
1751 table3gpp->m_uK = 3.48;
1752 table3gpp->m_sigK = 2;
1753 table3gpp->m_rTau = 3;
1754 table3gpp->m_uXpr = 9;
1755 table3gpp->m_sigXpr = 3;
1756 table3gpp->m_perClusterShadowingStd = 4;
1757
1758 for (uint8_t row = 0; row < 7; row++)
1759 {
1760 for (uint8_t column = 0; column < 7; column++)
1761 {
1762 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1763 }
1764 }
1765 }
1766 else if (channelCondition->IsNlos())
1767 {
1768 table3gpp->m_numOfCluster = 19;
1769 table3gpp->m_raysPerCluster = 20;
1770 table3gpp->m_uLgDS = -0.3 * log10(1 + fcGHz) - 7;
1771 table3gpp->m_sigLgDS = 0.28;
1772 table3gpp->m_uLgASD = -0.08 * log10(1 + fcGHz) + 1.81;
1773 table3gpp->m_sigLgASD = 0.05 * log10(1 + fcGHz) + 0.3;
1774 table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
1775 table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
1776 table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1777 table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1778 table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
1779 table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
1780 table3gpp->m_offsetZOD = 0;
1781 table3gpp->m_cDS = 11 * 1e-9;
1782 table3gpp->m_cASD = 22;
1783 table3gpp->m_cASA = 22;
1784 table3gpp->m_cZSA = 7;
1785 table3gpp->m_uK = 0; // N/A
1786 table3gpp->m_sigK = 0; // N/A
1787 table3gpp->m_rTau = 2.1;
1788 table3gpp->m_uXpr = 8;
1789 table3gpp->m_sigXpr = 3;
1790 table3gpp->m_perClusterShadowingStd = 4;
1791
1792 for (uint8_t row = 0; row < 6; row++)
1793 {
1794 for (uint8_t column = 0; column < 6; column++)
1795 {
1796 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
1797 }
1798 }
1799 }
1800 else if (channelCondition->IsNlosv())
1801 {
1802 table3gpp->m_numOfCluster = 19;
1803 table3gpp->m_raysPerCluster = 20;
1804 table3gpp->m_uLgDS = -0.4 * log10(1 + fcGHz) - 7;
1805 table3gpp->m_sigLgDS = 0.1;
1806 table3gpp->m_uLgASD = -0.1 * log10(1 + fcGHz) + 1.7;
1807 table3gpp->m_sigLgASD = 0.1;
1808 table3gpp->m_uLgASA = -0.1 * log10(1 + fcGHz) + 1.7;
1809 table3gpp->m_sigLgASA = 0.1;
1810 table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1811 table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1812 table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
1813 table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
1814 table3gpp->m_offsetZOD = 0;
1815 table3gpp->m_cDS = 11 * 1e-9;
1816 table3gpp->m_cASD = 22;
1817 table3gpp->m_cASA = 22;
1818 table3gpp->m_cZSA = 7;
1819 table3gpp->m_uK = 0;
1820 table3gpp->m_sigK = 4.5;
1821 table3gpp->m_rTau = 2.1;
1822 table3gpp->m_uXpr = 8;
1823 table3gpp->m_sigXpr = 3;
1824 table3gpp->m_perClusterShadowingStd = 4;
1825
1826 for (uint8_t row = 0; row < 6; row++)
1827 {
1828 for (uint8_t column = 0; column < 6; column++)
1829 {
1830 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1831 }
1832 }
1833 }
1834 else
1835 {
1836 NS_FATAL_ERROR("Unknown channel condition");
1837 }
1838 }
1839 else if (m_scenario == "V2V-Highway")
1840 {
1841 if (channelCondition->IsLos())
1842 {
1843 table3gpp->m_numOfCluster = 12;
1844 table3gpp->m_raysPerCluster = 20;
1845 table3gpp->m_uLgDS = -8.3;
1846 table3gpp->m_sigLgDS = 0.2;
1847 table3gpp->m_uLgASD = 1.4;
1848 table3gpp->m_sigLgASD = 0.1;
1849 table3gpp->m_uLgASA = 1.4;
1850 table3gpp->m_sigLgASA = 0.1;
1851 table3gpp->m_uLgZSA = -0.1 * log10(1 + fcGHz) + 0.73;
1852 table3gpp->m_sigLgZSA = -0.04 * log10(1 + fcGHz) + 0.34;
1853 table3gpp->m_uLgZSD = -0.1 * log10(1 + fcGHz) + 0.73;
1854 table3gpp->m_sigLgZSD = -0.04 * log10(1 + fcGHz) + 0.34;
1855 table3gpp->m_offsetZOD = 0;
1856 table3gpp->m_cDS = 5 * 1e-9;
1857 table3gpp->m_cASD = 17;
1858 table3gpp->m_cASA = 17;
1859 table3gpp->m_cZSA = 7;
1860 table3gpp->m_uK = 9;
1861 table3gpp->m_sigK = 3.5;
1862 table3gpp->m_rTau = 3;
1863 table3gpp->m_uXpr = 9;
1864 table3gpp->m_sigXpr = 3;
1865 table3gpp->m_perClusterShadowingStd = 4;
1866
1867 for (uint8_t row = 0; row < 7; row++)
1868 {
1869 for (uint8_t column = 0; column < 7; column++)
1870 {
1871 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1872 }
1873 }
1874 }
1875 else if (channelCondition->IsNlosv())
1876 {
1877 table3gpp->m_numOfCluster = 19;
1878 table3gpp->m_raysPerCluster = 20;
1879 table3gpp->m_uLgDS = -8.3;
1880 table3gpp->m_sigLgDS = 0.3;
1881 table3gpp->m_uLgASD = 1.5;
1882 table3gpp->m_sigLgASD = 0.1;
1883 table3gpp->m_uLgASA = 1.5;
1884 table3gpp->m_sigLgASA = 0.1;
1885 table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1886 table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1887 table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
1888 table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
1889 table3gpp->m_offsetZOD = 0;
1890 table3gpp->m_cDS = 11 * 1e-9;
1891 table3gpp->m_cASD = 22;
1892 table3gpp->m_cASA = 22;
1893 table3gpp->m_cZSA = 7;
1894 table3gpp->m_uK = 0;
1895 table3gpp->m_sigK = 4.5;
1896 table3gpp->m_rTau = 2.1;
1897 table3gpp->m_uXpr = 8.0;
1898 table3gpp->m_sigXpr = 3;
1899 table3gpp->m_perClusterShadowingStd = 4;
1900
1901 for (uint8_t row = 0; row < 6; row++)
1902 {
1903 for (uint8_t column = 0; column < 6; column++)
1904 {
1905 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_LOS[row][column];
1906 }
1907 }
1908 }
1909 else if (channelCondition->IsNlos())
1910 {
1912 "The fast fading parameters for the NLOS condition in the Highway scenario are not "
1913 "defined in TR 37.885, use the ones defined in TDoc R1-1803671 instead");
1914
1915 table3gpp->m_numOfCluster = 19;
1916 table3gpp->m_raysPerCluster = 20;
1917 table3gpp->m_uLgDS = -0.3 * log10(1 + fcGHz) - 7;
1918 table3gpp->m_sigLgDS = 0.28;
1919 table3gpp->m_uLgASD = -0.08 * log10(1 + fcGHz) + 1.81;
1920 table3gpp->m_sigLgASD = 0.05 * log10(1 + fcGHz) + 0.3;
1921 table3gpp->m_uLgASA = -0.08 * log10(1 + fcGHz) + 1.81;
1922 table3gpp->m_sigLgASA = 0.05 * log10(1 + fcGHz) + 0.3;
1923 table3gpp->m_uLgZSA = -0.04 * log10(1 + fcGHz) + 0.92;
1924 table3gpp->m_sigLgZSA = -0.07 * log10(1 + fcGHz) + 0.41;
1925 table3gpp->m_uLgZSD = -0.04 * log10(1 + fcGHz) + 0.92;
1926 table3gpp->m_sigLgZSD = -0.07 * log10(1 + fcGHz) + 0.41;
1927 table3gpp->m_offsetZOD = 0;
1928 table3gpp->m_cDS = 11 * 1e-9;
1929 table3gpp->m_cASD = 22;
1930 table3gpp->m_cASA = 22;
1931 table3gpp->m_cZSA = 7;
1932 table3gpp->m_uK = 0; // N/A
1933 table3gpp->m_sigK = 0; // N/A
1934 table3gpp->m_rTau = 2.1;
1935 table3gpp->m_uXpr = 8;
1936 table3gpp->m_sigXpr = 3;
1937 table3gpp->m_perClusterShadowingStd = 4;
1938
1939 for (uint8_t row = 0; row < 6; row++)
1940 {
1941 for (uint8_t column = 0; column < 6; column++)
1942 {
1943 table3gpp->m_sqrtC[row][column] = sqrtC_UMi_NLOS[row][column];
1944 }
1945 }
1946 }
1947 else
1948 {
1949 NS_FATAL_ERROR("Unknown channel condition");
1950 }
1951 }
1952 else if (m_scenario.substr(0, 3) == "NTN")
1953 {
1954 std::string freqBand = fcGHz < 13 ? "S" : "Ka";
1955
1956 double elevAngle = 0;
1957 bool isSatellite = false; // flag to indicate if one of the two nodes is a satellite
1958 // if so, parameters will be set accordingly to NOTE 8 of
1959 // Table 6.7.2 from 3GPP 38.811 V15.4.0 (2020-09)
1960
1961 Ptr<MobilityModel> aMobNonConst = ConstCast<MobilityModel>(aMob);
1962 Ptr<MobilityModel> bMobNonConst = ConstCast<MobilityModel>(bMob);
1963
1965 ConstCast<MobilityModel>(aMob)) && // Transform to NS_ASSERT
1967 ConstCast<MobilityModel>(bMob))) // check if aMob and bMob are of type
1968 // GeocentricConstantPositionMobilityModel
1969 {
1974
1975 if (aNTNMob->GetGeographicPosition().z <
1976 bNTNMob->GetGeographicPosition().z) // b is the HAPS/Satellite
1977 {
1978 elevAngle = aNTNMob->GetElevationAngle(bNTNMob);
1979 if (bNTNMob->GetGeographicPosition().z > 50000)
1980 {
1981 isSatellite = true;
1982 }
1983 }
1984 else // a is the HAPS/Satellite
1985 {
1986 elevAngle = bNTNMob->GetElevationAngle(aNTNMob);
1987 if (aNTNMob->GetGeographicPosition().z > 50000)
1988 {
1989 isSatellite = true;
1990 }
1991 }
1992 }
1993 else
1994 {
1995 NS_FATAL_ERROR("Mobility Models needs to be of type Geocentric for NTN scenarios");
1996 }
1997
1998 // Round the elevation angle into a two-digits integer between 10 and 90.
1999 int elevAngleQuantized = elevAngle < 10 ? 10 : round(elevAngle / 10) * 10;
2000
2001 if (m_scenario == "NTN-DenseUrban")
2002 {
2003 if (channelCondition->IsLos())
2004 {
2005 table3gpp->m_uLgDS = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2006 table3gpp->m_sigLgDS =
2007 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2008
2009 // table3gpp->m_uLgASD=-1.79769e+308; //FOR SATELLITES
2010 table3gpp->m_uLgASD = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2011 // table3gpp->m_sigLgASD=0; //FOR SATELLITES
2012 table3gpp->m_sigLgASD =
2013 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2014
2015 table3gpp->m_uLgASA = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2016 table3gpp->m_sigLgASA =
2017 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2018 table3gpp->m_uLgZSA = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2019 table3gpp->m_sigLgZSA =
2020 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2021
2022 // table3gpp->m_uLgZSD=-1.79769e+308; //FOR SATELLITES
2023 table3gpp->m_uLgZSD = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2024 // table3gpp->m_sigLgZSD= 0; //FOR SATELLITES
2025 table3gpp->m_sigLgZSD =
2026 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2027
2028 table3gpp->m_uK = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uK];
2029 table3gpp->m_sigK = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2030 table3gpp->m_rTau = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2031 table3gpp->m_uXpr = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2032 table3gpp->m_sigXpr = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2033 table3gpp->m_numOfCluster =
2034 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2035 table3gpp->m_raysPerCluster =
2036 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2037 table3gpp->m_cDS = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2038 table3gpp->m_cASD = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2039 table3gpp->m_cASA = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2040 table3gpp->m_cZSA = NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2041 table3gpp->m_perClusterShadowingStd =
2042 NTNDenseUrbanLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2043
2044 for (uint8_t row = 0; row < 7; row++)
2045 {
2046 for (uint8_t column = 0; column < 7; column++)
2047 {
2048 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_DenseUrban_LOS[row][column];
2049 }
2050 }
2051 }
2052 else if (channelCondition->IsNlos())
2053 {
2054 NS_LOG_UNCOND("Dense Urban NLOS");
2055 table3gpp->m_uLgDS = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2056 table3gpp->m_sigLgDS =
2057 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2058 table3gpp->m_uLgASD = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2059 table3gpp->m_sigLgASD =
2060 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2061 table3gpp->m_uLgASA = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2062 table3gpp->m_sigLgASA =
2063 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2064 table3gpp->m_uLgZSA = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2065 table3gpp->m_sigLgZSA =
2066 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2067 table3gpp->m_uLgZSD = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2068 table3gpp->m_sigLgZSD =
2069 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2070 table3gpp->m_rTau = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2071 table3gpp->m_uXpr = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2072 table3gpp->m_sigXpr = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2073 table3gpp->m_numOfCluster =
2074 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2075 table3gpp->m_raysPerCluster =
2076 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2077 table3gpp->m_cDS =
2078 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2079 table3gpp->m_cASD = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2080 table3gpp->m_cASA = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2081 table3gpp->m_cZSA = NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2082 table3gpp->m_perClusterShadowingStd =
2083 NTNDenseUrbanNLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2084
2085 for (uint8_t row = 0; row < 6; row++)
2086 {
2087 for (uint8_t column = 0; column < 6; column++)
2088 {
2089 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_DenseUrban_NLOS[row][column];
2090 }
2091 }
2092 }
2093 }
2094 else if (m_scenario == "NTN-Urban")
2095 {
2096 if (channelCondition->IsLos())
2097 {
2098 table3gpp->m_uLgDS = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2099 table3gpp->m_sigLgDS = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2100 table3gpp->m_uLgASD = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2101 table3gpp->m_sigLgASD = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2102 table3gpp->m_uLgASA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2103 table3gpp->m_sigLgASA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2104 table3gpp->m_uLgZSA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2105 table3gpp->m_sigLgZSA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2106 table3gpp->m_uLgZSD = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2107 table3gpp->m_sigLgZSD = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2108 table3gpp->m_uK = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uK];
2109 table3gpp->m_sigK = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2110 table3gpp->m_rTau = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2111 table3gpp->m_uXpr = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2112 table3gpp->m_sigXpr = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2113 table3gpp->m_numOfCluster =
2114 NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2115 table3gpp->m_raysPerCluster =
2116 NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2117 table3gpp->m_cDS = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2118 table3gpp->m_cASD = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2119 table3gpp->m_cASA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2120 table3gpp->m_cZSA = NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2121 table3gpp->m_perClusterShadowingStd =
2122 NTNUrbanLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2123
2124 for (uint8_t row = 0; row < 7; row++)
2125 {
2126 for (uint8_t column = 0; column < 7; column++)
2127 {
2128 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_Urban_LOS[row][column];
2129 }
2130 }
2131 }
2132 else if (channelCondition->IsNlos())
2133 {
2134 table3gpp->m_uLgDS = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2135 table3gpp->m_sigLgDS = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2136 table3gpp->m_uLgASD = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2137 table3gpp->m_sigLgASD = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2138 table3gpp->m_uLgASA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2139 table3gpp->m_sigLgASA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2140 table3gpp->m_uLgZSA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2141 table3gpp->m_sigLgZSA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2142 table3gpp->m_uLgZSD = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2143 table3gpp->m_sigLgZSD = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2144 table3gpp->m_uK = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uK];
2145 table3gpp->m_sigK = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2146 table3gpp->m_rTau = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2147 table3gpp->m_uXpr = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2148 table3gpp->m_sigXpr = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2149 table3gpp->m_numOfCluster =
2150 NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2151 table3gpp->m_raysPerCluster =
2152 NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2153 table3gpp->m_cDS = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2154 table3gpp->m_cASD = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2155 table3gpp->m_cASA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2156 table3gpp->m_cZSA = NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2157 table3gpp->m_perClusterShadowingStd =
2158 NTNUrbanNLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2159
2160 for (uint8_t row = 0; row < 6; row++)
2161 {
2162 for (uint8_t column = 0; column < 6; column++)
2163 {
2164 table3gpp->m_sqrtC[row][column] =
2165 sqrtC_NTN_Urban_NLOS.at(elevAngleQuantized)[row][column];
2166 }
2167 }
2168 }
2169 }
2170 else if (m_scenario == "NTN-Suburban")
2171 {
2172 if (channelCondition->IsLos())
2173 {
2174 table3gpp->m_uLgDS = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2175 table3gpp->m_sigLgDS = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2176 table3gpp->m_uLgASD = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2177 table3gpp->m_sigLgASD =
2178 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2179 table3gpp->m_uLgASA = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2180 table3gpp->m_sigLgASA =
2181 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2182 table3gpp->m_uLgZSA = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2183 table3gpp->m_sigLgZSA =
2184 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2185 table3gpp->m_uLgZSD = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2186 table3gpp->m_sigLgZSD =
2187 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2188 table3gpp->m_uK = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uK];
2189 table3gpp->m_sigK = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2190 table3gpp->m_rTau = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2191 table3gpp->m_uXpr = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2192 table3gpp->m_sigXpr = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2193 table3gpp->m_numOfCluster =
2194 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2195 table3gpp->m_raysPerCluster =
2196 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2197 table3gpp->m_cDS = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2198 table3gpp->m_cASD = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2199 table3gpp->m_cASA = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2200 table3gpp->m_cZSA = NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2201 table3gpp->m_perClusterShadowingStd =
2202 NTNSuburbanLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2203
2204 for (uint8_t row = 0; row < 7; row++)
2205 {
2206 for (uint8_t column = 0; column < 7; column++)
2207 {
2208 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_Suburban_LOS[row][column];
2209 }
2210 }
2211 }
2212 else if (channelCondition->IsNlos())
2213 {
2214 table3gpp->m_uLgDS = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2215 table3gpp->m_sigLgDS = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2216 table3gpp->m_uLgASD = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2217 table3gpp->m_sigLgASD =
2218 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2219 table3gpp->m_uLgASA = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2220 table3gpp->m_sigLgASA =
2221 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2222 table3gpp->m_uLgZSA = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2223 table3gpp->m_sigLgZSA =
2224 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2225 table3gpp->m_uLgZSD = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2226 table3gpp->m_sigLgZSD =
2227 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2228 table3gpp->m_uK = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uK];
2229 table3gpp->m_sigK = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2230 table3gpp->m_rTau = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2231 table3gpp->m_uXpr = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2232 table3gpp->m_sigXpr = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2233 table3gpp->m_numOfCluster =
2234 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2235 table3gpp->m_raysPerCluster =
2236 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2237 table3gpp->m_cDS = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2238 table3gpp->m_cASD = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2239 table3gpp->m_cASA = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2240 table3gpp->m_cZSA = NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2241 table3gpp->m_perClusterShadowingStd =
2242 NTNSuburbanNLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2243
2244 for (uint8_t row = 0; row < 6; row++)
2245 {
2246 for (uint8_t column = 0; column < 6; column++)
2247 {
2248 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_Suburban_NLOS[row][column];
2249 }
2250 }
2251 }
2252 }
2253 else if (m_scenario == "NTN-Rural")
2254 {
2255 if (channelCondition->IsLos())
2256 {
2257 table3gpp->m_uLgDS = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2258 table3gpp->m_sigLgDS = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2259 table3gpp->m_uLgASD = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2260 table3gpp->m_sigLgASD = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2261 table3gpp->m_uLgASA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2262 table3gpp->m_sigLgASA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2263 table3gpp->m_uLgZSA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2264 table3gpp->m_sigLgZSA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2265 table3gpp->m_uLgZSD = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2266 table3gpp->m_sigLgZSD = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2267 table3gpp->m_uK = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uK];
2268 table3gpp->m_sigK = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2269 table3gpp->m_rTau = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2270 table3gpp->m_uXpr = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2271 table3gpp->m_sigXpr = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2272 table3gpp->m_numOfCluster =
2273 NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2274 table3gpp->m_raysPerCluster =
2275 NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2276 table3gpp->m_cDS = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2277 table3gpp->m_cASD = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2278 table3gpp->m_cASA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2279 table3gpp->m_cZSA = NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2280 table3gpp->m_perClusterShadowingStd =
2281 NTNRuralLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2282
2283 for (uint8_t row = 0; row < 7; row++)
2284 {
2285 for (uint8_t column = 0; column < 7; column++)
2286 {
2287 table3gpp->m_sqrtC[row][column] = sqrtC_NTN_Rural_LOS[row][column];
2288 }
2289 }
2290 }
2291 else if (channelCondition->IsNlos())
2292 {
2293 table3gpp->m_uLgDS = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uLgDS];
2294 table3gpp->m_sigLgDS = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigLgDS];
2295 table3gpp->m_uLgASD = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uLgASD];
2296 table3gpp->m_sigLgASD = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASD];
2297 table3gpp->m_uLgASA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uLgASA];
2298 table3gpp->m_sigLgASA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigLgASA];
2299 table3gpp->m_uLgZSA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSA];
2300 table3gpp->m_sigLgZSA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSA];
2301 table3gpp->m_uLgZSD = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uLgZSD];
2302 table3gpp->m_sigLgZSD = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigLgZSD];
2303 table3gpp->m_uK = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uK];
2304 table3gpp->m_sigK = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigK];
2305 table3gpp->m_rTau = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[rTau];
2306 table3gpp->m_uXpr = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[uXpr];
2307 table3gpp->m_sigXpr = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[sigXpr];
2308 table3gpp->m_numOfCluster =
2309 NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[numOfCluster];
2310 table3gpp->m_raysPerCluster =
2311 NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[raysPerCluster];
2312 table3gpp->m_cDS = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[cDS] * 1e-9;
2313 table3gpp->m_cASD = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[cASD];
2314 table3gpp->m_cASA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[cASA];
2315 table3gpp->m_cZSA = NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[cZSA];
2316 table3gpp->m_perClusterShadowingStd =
2317 NTNRuralNLOS.at(freqBand).at(elevAngleQuantized)[perClusterShadowingStd];
2318
2319 if (freqBand == "S")
2320 {
2321 for (uint8_t row = 0; row < 6; row++)
2322 {
2323 for (uint8_t column = 0; column < 6; column++)
2324 {
2325 table3gpp->m_sqrtC[row][column] =
2326 sqrtC_NTN_Rural_NLOS_S.at(elevAngleQuantized)[row][column];
2327 }
2328 }
2329 }
2330 else if (freqBand == "Ka")
2331 {
2332 for (uint8_t row = 0; row < 6; row++)
2333 {
2334 for (uint8_t column = 0; column < 6; column++)
2335 {
2336 table3gpp->m_sqrtC[row][column] =
2337 sqrtC_NTN_Rural_NLOS_Ka.at(elevAngleQuantized)[row][column];
2338 }
2339 }
2340 }
2341 }
2342 }
2343 // Parameters that should be set to -inf are instead set to the minimum
2344 // value of double
2345 if (isSatellite)
2346 {
2347 table3gpp->m_uLgASD = std::numeric_limits<double>::min();
2348 table3gpp->m_sigLgASD = 0;
2349 table3gpp->m_uLgZSD = std::numeric_limits<double>::min();
2350 table3gpp->m_sigLgZSD = 0;
2351 }
2352 }
2353 else
2354 {
2355 NS_FATAL_ERROR("unknown scenarios");
2356 }
2357
2358 return table3gpp;
2359}
2360
2361bool
2365 Ptr<const MobilityModel> bMob) const
2366{
2367 NS_LOG_FUNCTION(this);
2368
2369 if (const auto it = m_channelParamsMap.find(channelParamsKey); it == m_channelParamsMap.end())
2370 {
2371 NS_LOG_DEBUG("New channel parameters will be created for the first time for this link:"
2372 << channelParamsKey);
2373 return true;
2374 }
2375 else
2376 {
2377 if (!m_updatePeriod.IsZero())
2378 {
2379 const double endpointDisplacement =
2381 bMob,
2382 it->second->m_lastPositionFirst,
2383 it->second->m_lastPositionSecond);
2384
2385 if (endpointDisplacement > kMaxConsistencyStepMeters)
2386 {
2387 NS_LOG_DEBUG("New channel parameters needed because endpoint displacement > 1 m");
2388 return true;
2389 }
2390 }
2391
2392 if (!condition->IsEqual(it->second->m_losCondition, it->second->m_o2iCondition))
2393 {
2394 NS_LOG_DEBUG("New channel parameters needed because LOS or O2I condition changed");
2395 return true;
2396 }
2397 }
2398 return false;
2399}
2400
2401bool
2402ThreeGppChannelModel::NewChannelMatrixNeeded(const uint64_t channelMatrixKey,
2405 Ptr<const PhasedArrayModel> bAntenna) const
2406{
2407 NS_LOG_FUNCTION(this);
2408 if (const auto itMatrix = m_channelMatrixMap.find(channelMatrixKey);
2409 itMatrix == m_channelMatrixMap.end())
2410 {
2411 NS_LOG_DEBUG("Generate a new channel matrix. Matrix not found.");
2412 // This function call is needed because of the API of PhasedArrayModel/UniformPlannarArray,
2413 // which assumes that this function is called when the channel is created
2414 const bool channelNeedsToBeCreated = aAntenna->IsChannelOutOfDate(bAntenna);
2415 NS_ASSERT_MSG(channelNeedsToBeCreated,
2416 "This assert is to ensure that this code stays aligned with "
2417 "PhasedArrayModel/UniformPlannarArray "
2418 "API, which expects that this function is being called on the initial "
2419 "channel matrix creation.");
2420 return true;
2421 }
2422 else
2423 {
2424 const Time paramsT = channelParams->m_generatedTime;
2425 const Time matrixT = itMatrix->second->m_generatedTime;
2426 const bool paramsNewer = paramsT > matrixT;
2427 const bool antennaChanged = AntennaSetupChanged(aAntenna, bAntenna, itMatrix->second);
2428
2429 // channel parameters changed
2430 if (paramsNewer || antennaChanged)
2431 {
2432 NS_LOG_DEBUG("Generate a new channel matrix: paramsNewer="
2433 << paramsNewer << " antennaChanged=" << antennaChanged << " paramsT="
2434 << paramsT.As(Time::NS) << " matrixT=" << matrixT.As(Time::NS));
2435 return true;
2436 }
2437 return false;
2438 }
2439}
2440
2441bool
2444 Ptr<const MobilityModel> bMob) const
2445{
2446 NS_LOG_FUNCTION(this);
2447 // Channel updates are disabled
2448 if (m_updatePeriod.IsZero())
2449 {
2450 return false;
2451 }
2452
2453 const double endpointDisplacement =
2455 bMob,
2456 channelParams->m_lastPositionFirst,
2457 channelParams->m_lastPositionSecond);
2458
2459 // No endpoint displacement -> no channel consistency update needed
2460 if (endpointDisplacement < kNoDisplacementEpsMeters)
2461 {
2462 return false;
2463 }
2464
2465 NS_ABORT_MSG_IF(endpointDisplacement > kMaxConsistencyStepMeters,
2466 "Endpoint displacement exceeded 1 m; channel parameters should have been "
2467 "regenerated before attempting a consistency update.");
2468
2469 // if the spatial consistency update period has elapsed, update the channel
2470 if (Simulator::Now() - channelParams->m_generatedTime > m_updatePeriod)
2471 {
2472 NS_LOG_DEBUG("Updating channel generated at "
2473 << channelParams->m_generatedTime.As(Time::NS));
2474 return true;
2475 }
2476 return false;
2477}
2478
2479bool
2482 Ptr<const ChannelMatrix> channelMatrix) const
2483{
2484 NS_LOG_FUNCTION(this);
2485 // This allows changing the number of antenna ports during execution,
2486 // which is used by nr's initial association.
2487 const size_t sAntNumElems = aAntenna->GetNumElems();
2488 const size_t uAntNumElems = bAntenna->GetNumElems();
2489 const size_t chanNumRows = channelMatrix->m_channel.GetNumRows();
2490 const size_t chanNumCols = channelMatrix->m_channel.GetNumCols();
2491 const bool dimsMatchNormal = uAntNumElems == chanNumRows && sAntNumElems == chanNumCols;
2492 const bool dimsMatchSwapped = uAntNumElems == chanNumCols && sAntNumElems == chanNumRows;
2493 const bool dimsMismatch = !(dimsMatchNormal || dimsMatchSwapped);
2494 const bool channelOutOfDate = aAntenna->IsChannelOutOfDate(bAntenna);
2495 const bool changed = dimsMismatch || channelOutOfDate;
2496
2497 if (changed)
2498 {
2499 NS_LOG_DEBUG("AntennaSetupChanged: dimsMismatch="
2500 << dimsMismatch << " channelOutOfDate=" << channelOutOfDate
2501 << " uAntNumElems=" << uAntNumElems << " sAntNumElems=" << sAntNumElems
2502 << " chanNumRows=" << chanNumRows << " chanNumCols=" << chanNumCols
2503 << " aAntennaId=" << aAntenna->GetId() << " bAntennaId=" << bAntenna->GetId());
2504 }
2505
2506 return changed;
2507}
2508
2514{
2515 NS_LOG_FUNCTION(this);
2516
2517 // Compute the channel params key. The key is reciprocal, i.e., key (a, b) = key (b, a)
2518 const uint64_t channelParamsKey =
2519 GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
2520 // retrieve the channel condition
2521 const Ptr<const ChannelCondition> condition =
2522 m_channelConditionModel->GetChannelCondition(aMob, bMob);
2523 // Enforce canonical ordering when selecting scenario parameters.
2524 Ptr<const MobilityModel> aMobOrdered = aMob;
2525 Ptr<const MobilityModel> bMobOrdered = bMob;
2526 if (aMob->GetObject<Node>()->GetId() > bMob->GetObject<Node>()->GetId())
2527 {
2528 std::swap(aMobOrdered, bMobOrdered);
2529 }
2530 // get the 3GPP parameters
2531 const Ptr<const ParamsTable> table3gpp = GetThreeGppTable(aMobOrdered, bMobOrdered, condition);
2532
2533 if (NewChannelParamsNeeded(channelParamsKey, condition, aMob, bMob))
2534 {
2536 "Create new or regenerate the channel parameters because the condition has changed");
2537 m_channelParamsMap.insert_or_assign(
2538 channelParamsKey,
2539 GenerateChannelParameters(condition, table3gpp, aMobOrdered, bMobOrdered));
2540 }
2541 else
2542 {
2543 const auto it = m_channelParamsMap.find(channelParamsKey);
2544 NS_ASSERT(it != m_channelParamsMap.end());
2545 if (ChannelUpdateNeeded(it->second, aMob, bMob))
2546 {
2547 NS_LOG_DEBUG("Update the channel parameters using consistency procedure");
2548 UpdateChannelParameters(it->second, condition, aMob, bMob);
2549 }
2550 else
2551 {
2552 NS_LOG_DEBUG("No channel params update. Using already existing channel params.");
2553 }
2554 }
2555
2556 // Compute the channel matrix key. The key is reciprocal, i.e., key (a, b) = key (b, a)
2557 const uint64_t channelMatrixKey = GetKey(aAntenna->GetId(), bAntenna->GetId());
2558 // If the channel matrix is not present in the map or if it has to be updated
2559 if (NewChannelMatrixNeeded(channelMatrixKey,
2560 m_channelParamsMap.find(channelParamsKey)->second,
2561 aAntenna,
2562 bAntenna))
2563 {
2564 m_channelMatrixMap.insert_or_assign(
2565 channelMatrixKey,
2566 GetNewChannel(m_channelParamsMap.find(channelParamsKey)->second,
2567 table3gpp,
2568 aMob,
2569 bMob,
2570 aAntenna,
2571 bAntenna));
2572 }
2573
2574 NS_ASSERT(m_channelMatrixMap.contains(channelMatrixKey));
2575 return m_channelMatrixMap.find(channelMatrixKey)->second;
2576}
2577
2580{
2581 NS_LOG_FUNCTION(this);
2582
2583 // Compute the channel key. The key is reciprocal, i.e., key (a, b) = key (b, a)
2584 const uint64_t channelParamsKey =
2585 GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
2586
2587 if (m_channelParamsMap.contains(channelParamsKey))
2588 {
2589 return m_channelParamsMap.find(channelParamsKey)->second;
2590 }
2591 NS_LOG_WARN("Channel params map not found. Returning a nullptr.");
2592 return nullptr;
2593}
2594
2597 Ptr<const ParamsTable> table3gpp) const
2598{
2599 NS_LOG_FUNCTION(this);
2600 DoubleVector lspIndepRandomVar;
2601 DoubleVector lsp;
2602 const uint8_t paramNum = losCondition == ChannelCondition::LOS ? 7 : 6;
2603
2604 // Generate paramNum independent LSPs.
2605 for (uint8_t iter = 0; iter < paramNum; iter++)
2606 {
2607 lspIndepRandomVar.push_back(m_normalRv->GetValue());
2608 }
2609 for (uint8_t row = 0; row < paramNum; row++)
2610 {
2611 double temp = 0;
2612 for (uint8_t column = 0; column < paramNum; column++)
2613 {
2614 temp += table3gpp->m_sqrtC[row][column] * lspIndepRandomVar[column];
2615 }
2616 lsp.push_back(temp);
2617 }
2618
2620 // NOTE the shadowing is generated in the propagation loss model
2621 // For LOS, LSP is following the order of [SF,K,DS,ASD,ASA,ZSD,ZSA].
2622 // For NLOS, LSP is following the order of [SF,DS,ASD,ASA,ZSD,ZSA].
2623 if (losCondition == ChannelCondition::LOS)
2624 {
2625 lsps.kFactor = lsp[1] * table3gpp->m_sigK + table3gpp->m_uK;
2626 lsps.DS = pow(10, lsp[2] * table3gpp->m_sigLgDS + table3gpp->m_uLgDS);
2627 lsps.ASD = pow(10, lsp[3] * table3gpp->m_sigLgASD + table3gpp->m_uLgASD);
2628 lsps.ASA = pow(10, lsp[4] * table3gpp->m_sigLgASA + table3gpp->m_uLgASA);
2629 lsps.ZSD = pow(10, lsp[5] * table3gpp->m_sigLgZSD + table3gpp->m_uLgZSD);
2630 lsps.ZSA = pow(10, lsp[6] * table3gpp->m_sigLgZSA + table3gpp->m_uLgZSA);
2631 }
2632 else
2633 {
2634 lsps.DS = pow(10, lsp[1] * table3gpp->m_sigLgDS + table3gpp->m_uLgDS);
2635 lsps.ASD = pow(10, lsp[2] * table3gpp->m_sigLgASD + table3gpp->m_uLgASD);
2636 lsps.ASA = pow(10, lsp[3] * table3gpp->m_sigLgASA + table3gpp->m_uLgASA);
2637 lsps.ZSD = pow(10, lsp[4] * table3gpp->m_sigLgZSD + table3gpp->m_uLgZSD);
2638 lsps.ZSA = pow(10, lsp[5] * table3gpp->m_sigLgZSA + table3gpp->m_uLgZSA);
2639 }
2640 lsps.ASD = std::min(lsps.ASD, 104.0);
2641 lsps.ASA = std::min(lsps.ASA, 104.0);
2642 lsps.ZSD = std::min(lsps.ZSD, 52.0);
2643 lsps.ZSA = std::min(lsps.ZSA, 52.0);
2644 NS_LOG_INFO("K-factor=" << lsps.kFactor << ", DS=" << lsps.DS << ", ASD=" << lsps.ASD
2645 << ", ASA=" << lsps.ASA << ", ZSD=" << lsps.ZSD
2646 << ", ZSA=" << lsps.ZSA);
2647 return lsps;
2648}
2649
2650void
2652 Ptr<const ParamsTable> table3gpp,
2653 double* minTau,
2654 DoubleVector* clusterDelays) const
2655{
2656 NS_LOG_FUNCTION(this);
2657
2658 NS_ASSERT_MSG(DS > 0, "Delay spread must be positive");
2659 NS_ASSERT_MSG(table3gpp->m_numOfCluster > 0, "Number of clusters must be positive");
2660
2661 // Clear output vector and resize
2662 clusterDelays->clear();
2663 clusterDelays->resize(table3gpp->m_numOfCluster);
2664
2665 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2666 {
2667 const double tau = -1 * table3gpp->m_rTau * DS * log(m_uniformRv->GetValue(0, 1)); //(7.5-1)
2668 if (*minTau > tau)
2669 {
2670 *minTau = tau;
2671 }
2672 (*clusterDelays)[cIndex] = tau;
2673 }
2674
2675 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2676 {
2677 (*clusterDelays)[cIndex] -= *minTau;
2678 }
2679 // perform cluster delay sorting as per (7.5-2)
2680 std::ranges::sort(*clusterDelays);
2681}
2682
2683void
2685 DoubleVector* clusterShadowing) const
2686{
2687 NS_LOG_FUNCTION(this);
2688 clusterShadowing->clear();
2689 clusterShadowing->resize(table3gpp->m_numOfCluster);
2690
2691 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2692 {
2693 (*clusterShadowing)[cIndex] = m_normalRv->GetValue() * table3gpp->m_perClusterShadowingStd;
2694 }
2695}
2696
2697void
2699 DoubleVector* clusterShadowing,
2700 const double displacementLength) const
2701{
2702 NS_LOG_FUNCTION(this);
2703 // Normalized auto correlation function 7.4-5
2704 const double R = exp(-1 * displacementLength / table3gpp->m_perClusterRayDcorDistance);
2705
2706 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2707 {
2708 // compute a new correlated shadowing
2709 (*clusterShadowing)[cIndex] =
2710 R * (*clusterShadowing)[cIndex] +
2711 sqrt(1 - R * R) * m_normalRv->GetValue() * table3gpp->m_perClusterShadowingStd;
2712 }
2713}
2714
2715void
2717 const double DS,
2718 Ptr<const ParamsTable> table3gpp,
2719 const DoubleVector& clusterShadowing,
2720 DoubleVector* clusterPowers) const
2721{
2722 NS_LOG_FUNCTION(this);
2723 // Clear and resize the output vector
2724 clusterPowers->clear();
2725 clusterPowers->resize(clusterDelays.size());
2726
2727 double powerSum = 0;
2728 for (size_t cIndex = 0; cIndex < clusterDelays.size(); cIndex++)
2729 {
2730 const double power =
2731 exp(-1 * clusterDelays[cIndex] * (table3gpp->m_rTau - 1) / table3gpp->m_rTau / DS) *
2732 pow(10, -1 * clusterShadowing[cIndex] / 10.0); //(7.5-5)
2733 powerSum += power;
2734 (*clusterPowers)[cIndex] = power;
2735 }
2736
2737 // Normalize cluster powers with NS_ASSERT for division-by-zero protection
2738
2739 NS_ASSERT_MSG(powerSum > 0, "Power sum must be greater than zero. Time: " << Simulator::Now());
2740
2741 for (size_t cIndex = 0; cIndex < clusterPowers->size(); cIndex++)
2742 {
2743 (*clusterPowers)[cIndex] = (*clusterPowers)[cIndex] / powerSum; //(7.5-6)
2744 }
2745
2746 double totalPower = 0;
2747 for (size_t cIndex = 0; cIndex < clusterPowers->size(); cIndex++)
2748 {
2749 totalPower += (*clusterPowers)[cIndex];
2750 }
2751
2752 NS_ASSERT_MSG(std::round(totalPower) == 1, "Total power must be equal to 1");
2753}
2754
2757 DoubleVector* clusterDelays,
2758 const ChannelCondition::LosConditionValue losCondition,
2759 Ptr<const ParamsTable> table3gpp,
2760 const double kFactor,
2761 double* powerMax) const
2762{
2763 NS_LOG_FUNCTION(this);
2764 DoubleVector clusterPowersForAngles;
2765 // this power is only for equation (7.5-9) and (7.5-14), not
2766 // for (7.5-22)
2767
2768 if (losCondition == ChannelCondition::LOS)
2769 {
2770 const double kLinear = pow(10, kFactor / 10.0);
2771
2772 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2773 {
2774 if (cIndex == 0)
2775 {
2776 clusterPowersForAngles.push_back((*clusterPowers)[cIndex] / (1 + kLinear) +
2777 kLinear / (1 + kLinear)); //(7.5-8)
2778 }
2779 else
2780 {
2781 clusterPowersForAngles.push_back((*clusterPowers)[cIndex] /
2782 (1 + kLinear)); //(7.5-8)
2783 }
2784 if (*powerMax < clusterPowersForAngles[cIndex])
2785 {
2786 *powerMax = clusterPowersForAngles[cIndex];
2787 }
2788 }
2789 }
2790 else
2791 {
2792 for (uint8_t cIndex = 0; cIndex < table3gpp->m_numOfCluster; cIndex++)
2793 {
2794 clusterPowersForAngles.push_back((*clusterPowers)[cIndex]); //(7.5-6)
2795 if (*powerMax < clusterPowersForAngles[cIndex])
2796 {
2797 *powerMax = clusterPowersForAngles[cIndex];
2798 }
2799 }
2800 }
2801
2802 // remove clusters with less than -25 dB power compared to the maxim cluster power;
2803 // double thresh = pow(10, -2.5);
2804 double thresh = 0.0032;
2805 for (uint8_t cIndex = table3gpp->m_numOfCluster; cIndex > 0; cIndex--)
2806 {
2807 if (clusterPowersForAngles[cIndex - 1] < thresh * *powerMax)
2808 {
2809 clusterPowersForAngles.erase(clusterPowersForAngles.begin() + cIndex - 1);
2810 clusterPowers->erase(clusterPowers->begin() + cIndex - 1);
2811 clusterDelays->erase(clusterDelays->begin() + cIndex - 1);
2812 }
2813 }
2814 return clusterPowersForAngles;
2815}
2816
2817void
2819 const uint8_t reducedClusterNumber,
2820 const double kFactor) const
2821{
2822 NS_LOG_FUNCTION(this);
2823 const double cTau = 0.7705 - 0.0433 * kFactor + 2e-4 * pow(kFactor, 2) +
2824 17e-6 * pow(kFactor,
2825 3); //(7.5-3)
2826 for (uint8_t cIndex = 0; cIndex < reducedClusterNumber; cIndex++)
2827 {
2828 (*clusterDelays)[cIndex] = (*clusterDelays)[cIndex] / cTau; //(7.5-4)
2829 }
2830}
2831
2832double
2834 Ptr<const ParamsTable> table3gpp,
2835 const double kFactor)
2836{
2837 try
2838 {
2839 double cPhi = cNlosTablePhi.at(table3gpp->m_numOfCluster);
2840 if (losCondition == ChannelCondition::LOS)
2841 {
2842 cPhi *= 1.1035 - 0.028 * kFactor - 2e-3 * pow(kFactor, 2) +
2843 1e-4 * pow(kFactor, 3); // (7.5-10)
2844 }
2845 return cPhi;
2846 }
2847 catch (const std::out_of_range&)
2848 {
2849 NS_FATAL_ERROR("Invalid cluster number in cNlosTablePhi");
2850 }
2851}
2852
2853double
2855 Ptr<const ParamsTable> table3gpp,
2856 const double kFactor)
2857{
2858 try
2859 {
2860 double cTheta = cNlosTableTheta.at(table3gpp->m_numOfCluster);
2861 if (losCondition == ChannelCondition::LOS)
2862 {
2863 cTheta *= 1.3086 + 0.0339 * kFactor - 0.0077 * pow(kFactor, 2) +
2864 2e-4 * pow(kFactor, 3); // (7.5-15)
2865 }
2866 return cTheta;
2867 }
2868 catch (const std::out_of_range&)
2869 {
2870 NS_FATAL_ERROR("Invalid cluster number in cNlosTableTheta");
2871 }
2872}
2873
2874void
2876 std::vector<int>* clusterSign) const
2877{
2878 NS_LOG_FUNCTION(this);
2879 clusterSign->clear();
2880 clusterSign->resize(clusterNumber);
2881
2882 for (uint8_t cIndex = 0; cIndex < clusterNumber; cIndex++)
2883 {
2884 int Xn = 1;
2885 if (m_uniformRv->GetValue(0, 1) < 0.5)
2886 {
2887 Xn = -1;
2888 }
2889
2890 (*clusterSign)[cIndex] = Xn;
2891 }
2892}
2893
2894void
2896 const DoubleVector& clusterPowerForAngles,
2897 const double powerMax,
2898 const double cPhi,
2899 const double cTheta,
2900 const LargeScaleParameters& lsps,
2903 Ptr<const ParamsTable> table3gpp,
2904 Double2DVector* clusterAngles) const
2905{
2906 NS_LOG_FUNCTION(this);
2907 clusterAngles->clear();
2908 clusterAngles->resize(4);
2909
2910 DoubleVector clusterAoa;
2911 DoubleVector clusterAod;
2912 DoubleVector clusterZoa;
2913 DoubleVector clusterZod;
2914
2915 clusterAoa.resize(channelParams->m_reducedClusterNumber);
2916 clusterAod.resize(channelParams->m_reducedClusterNumber);
2917 clusterZoa.resize(channelParams->m_reducedClusterNumber);
2918 clusterZod.resize(channelParams->m_reducedClusterNumber);
2919
2920 for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
2921 {
2922 const double logCalc = -1 * log(clusterPowerForAngles[cIndex] / powerMax);
2923 double angle = 2 * sqrt(logCalc) / 1.4 / cPhi; //(7.5-9)
2924 clusterAoa[cIndex] = lsps.ASA * angle;
2925 clusterAod[cIndex] = lsps.ASD * angle;
2926 angle = logCalc / cTheta; //(7.5-14)
2927 clusterZoa[cIndex] = lsps.ZSA * angle;
2928 clusterZod[cIndex] = lsps.ZSD * angle;
2929 }
2930
2931 const Angles sAngle(bMob->GetPosition(), aMob->GetPosition());
2932 const Angles uAngle(aMob->GetPosition(), bMob->GetPosition());
2933
2934 for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
2935 {
2936 int Xn = 1;
2937 if (m_uniformRv->GetValue(0, 1) < 0.5)
2938 {
2939 Xn = -1;
2940 }
2941
2942 clusterAoa[cIndex] = clusterAoa[cIndex] * Xn + m_normalRv->GetValue() * lsps.ASA / 7.0 +
2943 RadiansToDegrees(uAngle.GetAzimuth()); //(7.5-11)
2944 clusterAod[cIndex] = clusterAod[cIndex] * Xn + m_normalRv->GetValue() * lsps.ASD / 7.0 +
2945 RadiansToDegrees(sAngle.GetAzimuth());
2946 if (channelParams->m_o2iCondition == ChannelCondition::O2I)
2947 {
2948 clusterZoa[cIndex] =
2949 clusterZoa[cIndex] * Xn + m_normalRv->GetValue() * lsps.ZSA / 7.0 + 90;
2950 //(7.5-16)
2951 }
2952 else
2953 {
2954 clusterZoa[cIndex] = clusterZoa[cIndex] * Xn + m_normalRv->GetValue() * lsps.ZSA / 7.0 +
2955 RadiansToDegrees(uAngle.GetInclination()); //(7.5-16)
2956 }
2957 clusterZod[cIndex] = clusterZod[cIndex] * Xn + m_normalRv->GetValue() * lsps.ZSD / 7.0 +
2959 table3gpp->m_offsetZOD; //(7.5-19)
2960 }
2961
2962 if (channelParams->m_losCondition == ChannelCondition::LOS)
2963 {
2964 // The 7.5-12 can be rewrite as Theta_n,ZOA = Theta_n,ZOA - (Theta_1,ZOA - Theta_LOS,ZOA) =
2965 // Theta_n,ZOA - diffZOA, Similar as AOD, ZSA and ZSD.
2966 const double diffAoa = clusterAoa[0] - RadiansToDegrees(uAngle.GetAzimuth());
2967 const double diffAod = clusterAod[0] - RadiansToDegrees(sAngle.GetAzimuth());
2968 const double diffZsa = clusterZoa[0] - RadiansToDegrees(uAngle.GetInclination());
2969 const double diffZsd = clusterZod[0] - RadiansToDegrees(sAngle.GetInclination());
2970
2971 for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
2972 {
2973 clusterAoa[cIndex] -= diffAoa; //(7.5-12)
2974 clusterAod[cIndex] -= diffAod;
2975 clusterZoa[cIndex] -= diffZsa; //(7.5-17)
2976 clusterZod[cIndex] -= diffZsd;
2977 }
2978 }
2979
2980 const double sizeTemp = clusterZoa.size();
2981 for (uint8_t ind = 0; ind < 4; ind++)
2982 {
2983 DoubleVector angleDegree;
2984 switch (ind)
2985 {
2986 case 0:
2987 angleDegree = clusterAoa;
2988 break;
2989 case 1:
2990 angleDegree = clusterZoa;
2991 break;
2992 case 2:
2993 angleDegree = clusterAod;
2994 break;
2995 case 3:
2996 angleDegree = clusterZod;
2997 break;
2998 default:
2999 NS_FATAL_ERROR("Programming Error");
3000 }
3001 for (uint8_t nIndex = 0; nIndex < sizeTemp; nIndex++)
3002 {
3003 while (angleDegree[nIndex] > 360)
3004 {
3005 angleDegree[nIndex] -= 360;
3006 }
3007
3008 while (angleDegree[nIndex] < 0)
3009 {
3010 angleDegree[nIndex] += 360;
3011 }
3012
3013 if (ind == 1 || ind == 3)
3014 {
3015 if (angleDegree[nIndex] > 180)
3016 {
3017 angleDegree[nIndex] = 360 - angleDegree[nIndex];
3018 }
3019 }
3020 }
3021 switch (ind)
3022 {
3023 case 0:
3024 clusterAoa = angleDegree;
3025 break;
3026 case 1:
3027 clusterZoa = angleDegree;
3028 break;
3029 case 2:
3030 clusterAod = angleDegree;
3031 break;
3032 case 3:
3033 clusterZod = angleDegree;
3034 break;
3035 default:
3036 NS_FATAL_ERROR("Programming Error");
3037 }
3038 }
3039
3040 (*clusterAngles)[AOA_INDEX] = clusterAoa;
3041 (*clusterAngles)[AOD_INDEX] = clusterAod;
3042 (*clusterAngles)[ZOA_INDEX] = clusterZoa;
3043 (*clusterAngles)[ZOD_INDEX] = clusterZod;
3044}
3045
3046void
3048 DoubleVector* delayConsistency,
3049 Ptr<const ThreeGppChannelParams> channelParams) const
3050{
3051 NS_LOG_FUNCTION(this);
3052
3053 clusterDelay->clear();
3054 clusterDelay->resize(channelParams->m_reducedClusterNumber);
3055
3056 NS_ASSERT(channelParams->m_delayConsistency.empty() == false);
3057 NS_ASSERT(Simulator::Now() != channelParams->m_generatedTime);
3058
3059 // update cluster delays based on equation (7.6-9)
3060 for (size_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
3061 {
3062 const double timeSeconds = (Simulator::Now() - channelParams->m_generatedTime).GetSeconds();
3063
3064 (*delayConsistency)[cInd] -=
3065 (sin(channelParams->m_angle.at(ZOA_INDEX).at(cInd) * M_PI / 180) *
3066 cos(channelParams->m_angle.at(AOA_INDEX).at(cInd) * M_PI / 180) *
3067 channelParams->m_rxSpeed.x +
3068 sin(channelParams->m_angle.at(ZOA_INDEX).at(cInd) * M_PI / 180) *
3069 sin(channelParams->m_angle.at(AOA_INDEX).at(cInd) * M_PI / 180) *
3070 channelParams->m_rxSpeed.y +
3071 (sin(channelParams->m_angle.at(ZOD_INDEX).at(cInd) * M_PI / 180) *
3072 cos(channelParams->m_angle.at(AOD_INDEX).at(cInd) * M_PI / 180) *
3073 channelParams->m_txSpeed.x +
3074 sin(channelParams->m_angle.at(ZOD_INDEX).at(cInd) * M_PI / 180) *
3075 sin(channelParams->m_angle.at(AOD_INDEX).at(cInd) * M_PI / 180) *
3076 channelParams->m_txSpeed.y)) /
3077 3e8 * timeSeconds;
3078 }
3079
3080 // normalize the delays by removing the min value (7.6-10a)
3081 double minTau = std::numeric_limits<double>::max();
3082 for (size_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
3083 {
3084 if (minTau > (*delayConsistency)[cInd])
3085 {
3086 minTau = (*delayConsistency)[cInd];
3087 }
3088 }
3089
3090 for (size_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
3091 {
3092 // normalize the updated cluster delay (7.6-10a), and then use these cluster delays to
3093 // generate cluster powers
3094 (*clusterDelay)[cInd] = (*delayConsistency)[cInd] - minTau;
3095 }
3096}
3097
3098Vector
3100 const double betaRad,
3101 const double gammaRad,
3102 const double etaRad,
3103 const Vector& speed,
3104 const int Xn) const
3105{
3106 NS_LOG_FUNCTION(this);
3107 // only x and y components are used, no need to calculate the z component
3108 const double sinAlpha = sin(alphaRad);
3109 const double cosAlpha = cos(alphaRad);
3110 const double sinBeta = sin(betaRad);
3111 const double cosBeta = cos(betaRad);
3112 const double sinGamma = sin(gammaRad);
3113 const double cosGamma = cos(gammaRad);
3114 const double sinEta = sin(etaRad);
3115 const double cosEta = cos(etaRad);
3116
3117 Vector vRotated;
3118
3119 vRotated.x = (cosAlpha * cosBeta * cosGamma * cosEta - sinAlpha * sinEta * Xn -
3120 cosAlpha * sinBeta * sinGamma * cosEta) *
3121 speed.x +
3122 (-1 * cosAlpha * cosBeta * cosGamma * sinEta - sinAlpha * cosEta * Xn +
3123 cosAlpha * sinBeta * sinGamma * sinEta) *
3124 speed.y;
3125
3126 vRotated.y = (sinAlpha * cosBeta * cosGamma * cosEta + cosAlpha * sinEta * Xn -
3127 sinAlpha * sinBeta * sinGamma * cosEta) *
3128 speed.x +
3129 (-1 * sinAlpha * cosBeta * cosGamma * sinEta + cosAlpha * cosEta * Xn +
3130 sinAlpha * sinBeta * sinGamma * sinEta) *
3131 speed.y;
3132
3133 vRotated.z = 0;
3134
3135 return vRotated;
3136}
3137
3138void
3140 Double2DVector* clusterAngles,
3141 const DoubleVector& prevClusterDelay) const
3142{
3143 NS_LOG_FUNCTION(this);
3144 NS_ASSERT(prevClusterDelay.size() == channelParams->m_reducedClusterNumber);
3145 const DoubleVector prevClusterAoa = channelParams->m_angle[AOA_INDEX];
3146 const DoubleVector prevClusterZoa = channelParams->m_angle[ZOA_INDEX];
3147 const DoubleVector prevClusterAod = channelParams->m_angle[AOD_INDEX];
3148 const DoubleVector prevClusterZod = channelParams->m_angle[ZOD_INDEX];
3149 const auto rxSpeed = channelParams->m_rxSpeed;
3150 const auto txSpeed = channelParams->m_txSpeed;
3151 const bool los = channelParams->m_losCondition == ChannelCondition::LOS;
3152
3153 for (size_t cInd = 0; cInd < prevClusterDelay.size(); cInd++)
3154 {
3155 // compute cluster relative speed
3156 Vector vPrimeRx;
3157 Vector vPrimeTx;
3158 if (los)
3159 {
3160 vPrimeRx = rxSpeed - txSpeed; // (7.6-10b)
3161 vPrimeTx = txSpeed - rxSpeed; // (7.6-10c)
3162 }
3163 else
3164 {
3165 NS_ASSERT(channelParams->m_clusterXnNlosSign.empty() == false);
3166 NS_ASSERT(channelParams->m_clusterXnNlosSign.size() ==
3167 channelParams->m_reducedClusterNumber);
3168 const int Xn = channelParams->m_clusterXnNlosSign[cInd];
3169 double alphaRad = M_PI + DegreesToRadians(prevClusterAod[cInd]);
3170 const double betaRad = M_PI / 2 - DegreesToRadians(prevClusterZod[cInd]);
3171 const double gammaRad = M_PI / 2 - DegreesToRadians(prevClusterZoa[cInd]);
3172 double etaRad = -1 * DegreesToRadians(prevClusterAoa[cInd]);
3173
3174 Vector rxSpeedPrime =
3175 ApplyVelocityRotation(alphaRad, betaRad, gammaRad, etaRad, rxSpeed, Xn);
3176
3177 alphaRad = -1 * DegreesToRadians(prevClusterAod[cInd]);
3178 etaRad = M_PI + DegreesToRadians(prevClusterAoa[cInd]);
3179
3180 Vector txSpeedPrime =
3181 ApplyVelocityRotation(alphaRad, betaRad, gammaRad, etaRad, txSpeed, Xn);
3182 vPrimeRx = rxSpeedPrime - txSpeed; // (7.6-10b)
3183 vPrimeTx = txSpeedPrime - rxSpeed; // (7.6-10c)
3184 }
3185
3186 using DPV = std::vector<std::pair<double, double>>;
3187 const auto& cachedAngleSincos = channelParams->m_cachedAngleSincos;
3188 const DPV& zoaSinCos = cachedAngleSincos[ZOA_INDEX];
3189 const DPV& zodSinCos = cachedAngleSincos[ZOD_INDEX];
3190 const DPV& aoaSinCos = cachedAngleSincos[AOA_INDEX];
3191 const DPV& aodSinCos = cachedAngleSincos[AOD_INDEX];
3192
3193 const double deltaT =
3194 Simulator::Now().GetSeconds() - channelParams->m_generatedTime.GetSeconds();
3195
3196 // update the angles according to equations (7.6-11) - (7.6-14)
3197 (*clusterAngles)[AOD_INDEX][cInd] =
3198 prevClusterAod[cInd] +
3200 (-aodSinCos[cInd].first * vPrimeRx.x + aodSinCos[cInd].second * vPrimeRx.y) /
3201 (3e8 * prevClusterDelay[cInd] * zodSinCos[cInd].first) * deltaT);
3202
3203 (*clusterAngles)[ZOD_INDEX][cInd] =
3204 prevClusterZod[cInd] +
3205 RadiansToDegrees((zodSinCos[cInd].second * aodSinCos[cInd].second * vPrimeRx.x +
3206 zodSinCos[cInd].second * -aodSinCos[cInd].first * vPrimeRx.y) /
3207 (3e8 * prevClusterDelay[cInd]) * deltaT);
3208
3209 (*clusterAngles)[AOA_INDEX][cInd] =
3210 prevClusterAoa[cInd] +
3212 (-aoaSinCos[cInd].first * vPrimeTx.x + aoaSinCos[cInd].second * vPrimeTx.y) /
3213 (3e8 * prevClusterDelay[cInd] * zoaSinCos[cInd].first) * deltaT);
3214
3215 (*clusterAngles)[ZOA_INDEX][cInd] =
3216 prevClusterZoa[cInd] +
3217 RadiansToDegrees((zoaSinCos[cInd].second * aoaSinCos[cInd].second * vPrimeTx.x +
3218 zoaSinCos[cInd].second * aoaSinCos[cInd].first * vPrimeTx.y) /
3219 (3e8 * prevClusterDelay[cInd]) * deltaT);
3220 }
3221
3222 NS_LOG_DEBUG("Cluster angles updated");
3223}
3224
3225void
3227 Double2DVector* nonSelfBlocking,
3228 DoubleVector* clusterPowers,
3229 DoubleVector* attenuation_dB,
3231 const DoubleVector& clusterAoa,
3232 const DoubleVector& clusterZoa,
3233 Ptr<const ParamsTable> table3gpp) const
3234{
3235 NS_LOG_FUNCTION(this);
3236
3237 if (m_blockage)
3238 {
3239 CalcAttenuationOfBlockage(nonSelfBlocking,
3240 attenuation_dB,
3241 channelParams,
3242 clusterAoa,
3243 clusterZoa,
3244 table3gpp);
3245 for (uint8_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
3246 {
3247 (*clusterPowers)[cInd] =
3248 (*clusterPowers)[cInd] / pow(10, (*attenuation_dB)[cInd] / 10.0);
3249 }
3250 }
3251 else
3252 {
3253 attenuation_dB->push_back(0);
3254 }
3255}
3256
3257void
3259 Ptr<const ParamsTable> table3gpp,
3260 Double2DVector* rayAoaRadian,
3261 Double2DVector* rayAodRadian,
3262 Double2DVector* rayZoaRadian,
3263 Double2DVector* rayZodRadian) const
3264{
3265 NS_LOG_FUNCTION(this);
3266
3267 const DoubleVector& clusterAoa = channelParams->m_angle[AOA_INDEX];
3268 const DoubleVector& clusterAod = channelParams->m_angle[AOD_INDEX];
3269 const DoubleVector& clusterZoa = channelParams->m_angle[ZOA_INDEX];
3270 const DoubleVector& clusterZod = channelParams->m_angle[ZOD_INDEX];
3271
3272 // Resize/initialize containers: [numClusters][raysPerCluster]
3273 *rayAoaRadian = Double2DVector(channelParams->m_reducedClusterNumber,
3274 DoubleVector(table3gpp->m_raysPerCluster, 0));
3275 *rayAodRadian = Double2DVector(channelParams->m_reducedClusterNumber,
3276 DoubleVector(table3gpp->m_raysPerCluster, 0));
3277 *rayZoaRadian = Double2DVector(channelParams->m_reducedClusterNumber,
3278 DoubleVector(table3gpp->m_raysPerCluster, 0));
3279 *rayZodRadian = Double2DVector(channelParams->m_reducedClusterNumber,
3280 DoubleVector(table3gpp->m_raysPerCluster, 0));
3281
3282 const double pow10_uLgZSD = std::pow(10.0, table3gpp->m_uLgZSD);
3283
3284 // NOTE: offSetAlpha[m] must be available in scope (same as previous implementation)
3285 for (uint8_t nInd = 0; nInd < channelParams->m_reducedClusterNumber; nInd++)
3286 {
3287 for (uint8_t mInd = 0; mInd < table3gpp->m_raysPerCluster; mInd++)
3288 {
3289 const double tempAoa =
3290 clusterAoa[nInd] + table3gpp->m_cASA * offSetAlpha[mInd]; // (7.5-13)
3291 const double tempZoa =
3292 clusterZoa[nInd] + table3gpp->m_cZSA * offSetAlpha[mInd]; // (7.5-18)
3293 std::tie((*rayAoaRadian)[nInd][mInd], (*rayZoaRadian)[nInd][mInd]) =
3294 WrapAngles(DegreesToRadians(tempAoa), DegreesToRadians(tempZoa));
3295
3296 const double tempAod =
3297 clusterAod[nInd] + table3gpp->m_cASD * offSetAlpha[mInd]; // (7.5-13)
3298 const double tempZod = clusterZod[nInd] + 0.375 * pow10_uLgZSD * offSetAlpha[mInd];
3299 // (7.5-20)
3300 std::tie((*rayAodRadian)[nInd][mInd], (*rayZodRadian)[nInd][mInd]) =
3301 WrapAngles(DegreesToRadians(tempAod), DegreesToRadians(tempZod));
3302 }
3303 }
3304}
3305
3306void
3308 Double2DVector* rayAoaRadian,
3309 Double2DVector* rayAodRadian,
3310 Double2DVector* rayZoaRadian,
3311 Double2DVector* rayZodRadian) const
3312{
3313 NS_LOG_FUNCTION(this);
3314 for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
3315 {
3316 Shuffle((*rayAodRadian)[cIndex].begin(), (*rayAodRadian)[cIndex].end(), m_uniformRvShuffle);
3317 Shuffle((*rayAoaRadian)[cIndex].begin(), (*rayAoaRadian)[cIndex].end(), m_uniformRvShuffle);
3318 Shuffle((*rayZodRadian)[cIndex].begin(), (*rayZodRadian)[cIndex].end(), m_uniformRvShuffle);
3319 Shuffle((*rayZoaRadian)[cIndex].begin(), (*rayZoaRadian)[cIndex].end(), m_uniformRvShuffle);
3320 }
3321}
3322
3323void
3325 Double2DVector* crossPolarizationPowerRatios,
3326 Double3DVector* clusterPhase,
3327 const uint8_t reducedClusterNumber,
3328 Ptr<const ParamsTable> table3gpp) const
3329{
3330 // a vector containing the cross-polarization power ratios, as defined by 7.5-21
3331 // store the PHI values for all the possible combination of polarizations
3332 clusterPhase->clear();
3333 clusterPhase->resize(reducedClusterNumber);
3334 crossPolarizationPowerRatios->clear();
3335 crossPolarizationPowerRatios->resize(reducedClusterNumber);
3336
3337 const double uXprLinear = pow(10, table3gpp->m_uXpr / 10.0); // convert to linear
3338 const double sigXprLinear = pow(10, table3gpp->m_sigXpr / 10.0); // convert to linear
3339
3340 for (uint8_t clusterIndex = 0; clusterIndex < reducedClusterNumber; clusterIndex++)
3341 {
3342 (*clusterPhase)[clusterIndex].resize(table3gpp->m_raysPerCluster);
3343 (*crossPolarizationPowerRatios)[clusterIndex].resize(table3gpp->m_raysPerCluster);
3344 for (uint8_t rayIndex = 0; rayIndex < table3gpp->m_raysPerCluster; rayIndex++)
3345 {
3346 (*clusterPhase)[clusterIndex][rayIndex].resize(4);
3347 // stores the XPR values
3348 (*crossPolarizationPowerRatios)[clusterIndex][rayIndex] =
3349 std::pow(10, (m_normalRv->GetValue() * sigXprLinear + uXprLinear) / 10.0);
3350 for (uint8_t polIndex = 0; polIndex < 4; polIndex++)
3351 {
3352 // stores the PHI values
3353 (*clusterPhase)[clusterIndex][rayIndex][polIndex] =
3354 m_uniformRv->GetValue(-1 * M_PI, M_PI);
3355 }
3356 }
3357 }
3358}
3359
3360void
3362 Ptr<const ParamsTable> table3gpp,
3363 uint8_t* cluster1st,
3364 uint8_t* cluster2nd,
3365 DoubleVector* clusterDelay,
3366 Double2DVector* angles,
3367 DoubleVector* alpha,
3368 DoubleVector* dTerm,
3369 DoubleVector* clusterPower) const
3370
3371{
3372 NS_LOG_FUNCTION(this);
3373 NS_ABORT_IF(table3gpp == nullptr);
3374 NS_ABORT_IF(channelParams == nullptr);
3375 NS_ABORT_IF(cluster1st == nullptr || cluster2nd == nullptr);
3376 NS_ABORT_IF(clusterDelay == nullptr || angles == nullptr || alpha == nullptr ||
3377 dTerm == nullptr);
3378 NS_ABORT_IF(angles->size() != ZOD_INDEX + 1); // expects 4 directions: AOA/AOD/ZOA/ZOD
3379 NS_ABORT_IF(channelParams->m_reducedClusterNumber == 0);
3380 NS_ABORT_IF(channelParams->m_clusterPower.size() != channelParams->m_reducedClusterNumber);
3381 NS_ABORT_IF(alpha->size() != channelParams->m_reducedClusterNumber);
3382 NS_ABORT_IF(dTerm->size() != channelParams->m_reducedClusterNumber);
3383 NS_ABORT_IF(clusterDelay->size() != channelParams->m_reducedClusterNumber);
3384
3385 for (auto dir : {AOA_INDEX, AOD_INDEX, ZOA_INDEX, ZOD_INDEX})
3386 {
3387 NS_ABORT_IF((*angles)[dir].size() != channelParams->m_reducedClusterNumber);
3388 }
3389
3390 *cluster1st = 0;
3391 *cluster2nd = 0;
3392
3393 if (channelParams->m_reducedClusterNumber > 1)
3394 {
3395 // Find strongest
3396 double maxPower = channelParams->m_clusterPower[0];
3397 for (uint8_t cIndex = 1; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
3398 {
3399 if (maxPower < channelParams->m_clusterPower[cIndex])
3400 {
3401 maxPower = channelParams->m_clusterPower[cIndex];
3402 *cluster1st = cIndex;
3403 }
3404 }
3405 // Initialize second-strongest to "some other index"
3406 *cluster2nd = (*cluster1st == 0) ? 1 : 0;
3407 maxPower = channelParams->m_clusterPower[*cluster2nd];
3408
3409 // Find second-strongest (must be != cluster1st)
3410 for (uint8_t cIndex = 0; cIndex < channelParams->m_reducedClusterNumber; cIndex++)
3411 {
3412 if (cIndex != *cluster1st && maxPower < channelParams->m_clusterPower[cIndex])
3413 {
3414 maxPower = channelParams->m_clusterPower[cIndex];
3415 *cluster2nd = cIndex;
3416 }
3417 }
3418 }
3419 NS_ABORT_IF(channelParams->m_reducedClusterNumber > 1 && *cluster1st == *cluster2nd);
3420 NS_LOG_INFO("1st strongest cluster:" << +*cluster1st
3421 << ", 2nd strongest cluster:" << +*cluster2nd);
3422
3423 // store the values for the subclusters
3424 if (*cluster1st == *cluster2nd)
3425 {
3426 clusterDelay->push_back((*clusterDelay)[*cluster1st] + 1.28 * table3gpp->m_cDS);
3427 clusterDelay->push_back((*clusterDelay)[*cluster1st] + 2.56 * table3gpp->m_cDS);
3428
3429 for (auto dir : {AOA_INDEX, AOD_INDEX, ZOA_INDEX, ZOD_INDEX})
3430 {
3431 auto& v = (*angles)[dir];
3432 v.push_back(v[*cluster1st]);
3433 v.push_back(v[*cluster1st]);
3434 }
3435
3436 alpha->push_back((*alpha)[*cluster1st]);
3437 alpha->push_back((*alpha)[*cluster1st]);
3438 dTerm->push_back((*dTerm)[*cluster1st]);
3439 dTerm->push_back((*dTerm)[*cluster1st]);
3440 clusterPower->push_back((*clusterPower)[*cluster1st]);
3441 clusterPower->push_back((*clusterPower)[*cluster1st]);
3442 }
3443 else
3444 {
3445 const uint8_t min = std::min(*cluster1st, *cluster2nd);
3446 const uint8_t max = std::max(*cluster1st, *cluster2nd);
3447
3448 clusterDelay->push_back((*clusterDelay)[min] + 1.28 * table3gpp->m_cDS);
3449 clusterDelay->push_back((*clusterDelay)[min] + 2.56 * table3gpp->m_cDS);
3450 clusterDelay->push_back((*clusterDelay)[max] + 1.28 * table3gpp->m_cDS);
3451 clusterDelay->push_back((*clusterDelay)[max] + 2.56 * table3gpp->m_cDS);
3452
3453 for (auto dir : {AOA_INDEX, AOD_INDEX, ZOA_INDEX, ZOD_INDEX})
3454 {
3455 auto& v = (*angles)[dir];
3456 v.push_back(v[min]);
3457 v.push_back(v[min]);
3458 v.push_back(v[max]);
3459 v.push_back(v[max]);
3460 }
3461
3462 alpha->push_back((*alpha)[min]);
3463 alpha->push_back((*alpha)[min]);
3464 alpha->push_back((*alpha)[max]);
3465 alpha->push_back((*alpha)[max]);
3466
3467 dTerm->push_back((*dTerm)[min]);
3468 dTerm->push_back((*dTerm)[min]);
3469 dTerm->push_back((*dTerm)[max]);
3470 dTerm->push_back((*dTerm)[max]);
3471
3472 clusterPower->push_back((*clusterPower)[min]);
3473 clusterPower->push_back((*clusterPower)[min]);
3474 clusterPower->push_back((*clusterPower)[max]);
3475 clusterPower->push_back((*clusterPower)[max]);
3476 }
3477
3478 const std::size_t expectedExtra = (*cluster1st == *cluster2nd) ? 2u : 4u;
3479 const std::size_t expectedSize =
3480 static_cast<std::size_t>(channelParams->m_reducedClusterNumber) + expectedExtra;
3481
3482 NS_ABORT_IF(clusterDelay->size() != expectedSize);
3483 NS_ABORT_IF(alpha->size() != expectedSize);
3484 NS_ABORT_IF(dTerm->size() != expectedSize);
3485 for (auto dir : {AOA_INDEX, AOD_INDEX, ZOA_INDEX, ZOD_INDEX})
3486 {
3487 NS_ABORT_IF((*angles)[dir].size() != expectedSize);
3488 }
3489}
3490
3491void
3493 const uint8_t reducedClusterNumber,
3494 DoubleVector* delay,
3495 Double2DVector* angles,
3496 std::vector<std::vector<std::pair<double, double>>>* cachedAngleSincos,
3497 DoubleVector* alpha,
3498 DoubleVector* dTerm,
3499 DoubleVector* clusterPower) const
3500{
3501 NS_LOG_FUNCTION(this);
3502 NS_ABORT_IF(delay == nullptr || angles == nullptr);
3503 NS_ABORT_IF(angles->size() != ZOD_INDEX + 1); // expects 4 directions: AOA/ZOA/AOD/ZOD
3504
3505 const auto totalClusterNumber = delay->size();
3506 if (reducedClusterNumber == totalClusterNumber)
3507 {
3508 NS_LOG_DEBUG("Nothing to trim, the reduced cluster number is equal to the vector of "
3509 "per cluster delays");
3510 return; // already trimmed
3511 }
3512 NS_ABORT_IF(totalClusterNumber < reducedClusterNumber);
3513 const auto extraClusters = totalClusterNumber - reducedClusterNumber;
3514 NS_ABORT_MSG_IF(!(extraClusters == 2 || extraClusters == 4),
3515 "The number of extra clusters to be removed can be either 2 or 4.");
3516 // Trim delay
3517 delay->erase(delay->end() - extraClusters, delay->end());
3518
3519 // Trim angles
3520 for (auto dir : {AOA_INDEX, ZOA_INDEX, AOD_INDEX, ZOD_INDEX})
3521 {
3522 NS_ABORT_IF((*angles)[dir].size() != totalClusterNumber);
3523 (*angles)[dir].erase((*angles)[dir].end() - extraClusters, (*angles)[dir].end());
3524 }
3525
3526 // Trim cached sin/cos angles (used by Doppler)
3527 if (cachedAngleSincos && !cachedAngleSincos->empty())
3528 {
3529 for (auto dir : {AOA_INDEX, ZOA_INDEX, AOD_INDEX, ZOD_INDEX})
3530 {
3531 NS_ABORT_IF((*cachedAngleSincos)[dir].size() != totalClusterNumber);
3532 (*cachedAngleSincos)[dir].erase((*cachedAngleSincos)[dir].end() - extraClusters,
3533 (*cachedAngleSincos)[dir].end());
3534 }
3535 }
3536
3537 // Trim Doppler terms
3538 if (alpha && !alpha->empty())
3539 {
3540 NS_ABORT_IF(alpha->size() != totalClusterNumber);
3541 alpha->erase(alpha->end() - extraClusters, alpha->end());
3542 }
3543 if (dTerm && !dTerm->empty())
3544 {
3545 NS_ABORT_IF(dTerm->size() != totalClusterNumber);
3546 dTerm->erase(dTerm->end() - extraClusters, dTerm->end());
3547 }
3548
3549 NS_ABORT_IF(delay->size() != reducedClusterNumber);
3550 for (auto dir : {AOA_INDEX, ZOA_INDEX, AOD_INDEX, ZOD_INDEX})
3551 {
3552 NS_ABORT_IF((*angles)[dir].size() != reducedClusterNumber);
3553 }
3554
3555 // Trim cluster powers
3556 if (clusterPower && !clusterPower->empty())
3557 {
3558 NS_ABORT_IF(clusterPower->size() != totalClusterNumber);
3559 clusterPower->erase(clusterPower->end() - extraClusters, clusterPower->end());
3560 }
3561}
3562
3563void
3566 std::vector<std::vector<std::pair<double, double>>>* cachedAngleSinCos) const
3567{
3568 NS_LOG_FUNCTION(this);
3569 cachedAngleSinCos->resize(channelParams->m_angle.size());
3570 for (size_t direction = 0; direction < channelParams->m_angle.size(); direction++)
3571 {
3572 (*cachedAngleSinCos)[direction].resize(channelParams->m_angle[direction].size());
3573 for (size_t cluster = 0; cluster < channelParams->m_angle[direction].size(); cluster++)
3574 {
3575 (*cachedAngleSinCos)[direction][cluster] = {
3576 sin(channelParams->m_angle[direction][cluster] * DEG2RAD),
3577 cos(channelParams->m_angle[direction][cluster] * DEG2RAD)};
3578 }
3579 }
3580}
3581
3582void
3583ThreeGppChannelModel::GenerateDopplerTerms(const uint8_t reducedClusterNumber,
3584 DoubleVector* dopplerTermAlpha,
3585 DoubleVector* dopplerTermD) const
3586{
3587 NS_ABORT_IF(dopplerTermAlpha == nullptr || dopplerTermD == nullptr);
3588 dopplerTermAlpha->assign(reducedClusterNumber, 0.0);
3589 dopplerTermD->assign(reducedClusterNumber, 0.0);
3590
3591 for (uint8_t cIndex = 1; cIndex < reducedClusterNumber; ++cIndex)
3592 {
3593 (*dopplerTermAlpha)[cIndex] = m_uniformRvDoppler->GetValue(-1, 1);
3594 (*dopplerTermD)[cIndex] = m_uniformRvDoppler->GetValue(-m_vScatt, m_vScatt);
3595 }
3596}
3597
3598double
3601 const Vector& lastPositionFirst,
3602 const Vector& lastPositionSecond) const
3603
3604{
3605 Ptr<const MobilityModel> firstMob = aMob;
3606 Ptr<const MobilityModel> secondMob = bMob;
3607 if (aMob->GetObject<Node>()->GetId() > bMob->GetObject<Node>()->GetId())
3608 {
3609 std::swap(firstMob, secondMob);
3610 }
3611
3612 const Vector posFirst = firstMob->GetPosition();
3613 const Vector posSecond = secondMob->GetPosition();
3614
3615 const double dispFirst =
3616 std::hypot(posFirst.x - lastPositionFirst.x, posFirst.y - lastPositionFirst.y);
3617 const double dispSecond =
3618 std::hypot(posSecond.x - lastPositionSecond.x, posSecond.y - lastPositionSecond.y);
3619
3620 return std::max(dispFirst, dispSecond);
3621}
3622
3623void
3626 double* distance2D,
3627 double* distance3D,
3628 double* endpointDisplacement2D,
3629 double* relativeDisplacement2D,
3630 Vector* lastPositionFirst,
3631 Vector* lastPositionSecond,
3632 Vector2D* lastRelativePosition2D) const
3633{
3634 NS_LOG_FUNCTION(this);
3635 NS_ASSERT(distance2D && distance3D && endpointDisplacement2D && relativeDisplacement2D &&
3636 lastPositionFirst && lastPositionSecond && lastRelativePosition2D);
3637
3638 // Canonical ordering by node ID (min ID / max ID)
3639 Ptr<const MobilityModel> firstMob = aMob;
3640 Ptr<const MobilityModel> secondMob = bMob;
3641 if (aMob->GetObject<Node>()->GetId() > bMob->GetObject<Node>()->GetId())
3642 {
3643 std::swap(firstMob, secondMob);
3644 }
3645
3646 // Obtain the new positions
3647 const Vector posFirst = firstMob->GetPosition();
3648 const Vector posSecond = secondMob->GetPosition();
3649
3650 const double deltaX = posFirst.x - posSecond.x;
3651 const double deltaY = posFirst.y - posSecond.y;
3652
3653 // Link 2D distance
3654 *distance2D = std::hypot(deltaX, deltaY);
3655
3656 // Link 3D distance
3657 const double deltaZ = posFirst.z - posSecond.z;
3658 *distance3D = std::hypot(*distance2D, deltaZ);
3659
3660 // Relative (geometry) displacement: change in the canonical Tx-Rx position vector
3661 const Vector2D relNow{deltaX, deltaY};
3662 *relativeDisplacement2D = (relNow - *lastRelativePosition2D).GetLength();
3663
3664 // Endpoint displacement: max motion of the two endpoints since the last update
3665 const double dispFirst =
3666 std::hypot(posFirst.x - lastPositionFirst->x, posFirst.y - lastPositionFirst->y);
3667 const double dispSecond =
3668 std::hypot(posSecond.x - lastPositionSecond->x, posSecond.y - lastPositionSecond->y);
3669 *endpointDisplacement2D = std::max(dispFirst, dispSecond);
3670
3671 NS_ASSERT(std::isfinite(*distance2D));
3672 NS_ASSERT(std::isfinite(*distance3D));
3673 NS_ASSERT(std::isfinite(*relativeDisplacement2D));
3674 NS_ASSERT(std::isfinite(*endpointDisplacement2D));
3675 NS_ASSERT(*distance2D >= 0.0);
3676 NS_ASSERT(*distance3D >= 0.0);
3677 NS_ASSERT(*relativeDisplacement2D >= 0.0);
3678 NS_ASSERT(*endpointDisplacement2D >= 0.0);
3679
3680 // Store the new positions for next time (canonically ordered endpoints)
3681 *lastPositionFirst = posFirst;
3682 *lastPositionSecond = posSecond;
3683 *lastRelativePosition2D = relNow;
3684}
3685
3688 Ptr<const ParamsTable> table3gpp,
3690 Ptr<const MobilityModel> bMob) const
3691{
3692 NS_LOG_FUNCTION(this);
3693 // Enforce canonical ordering (by node id) for deterministic parameter generation.
3694 Ptr<const MobilityModel> aMobOrdered = aMob;
3695 Ptr<const MobilityModel> bMobOrdered = bMob;
3696 const uint32_t idA = aMob->GetObject<Node>()->GetId();
3697 const uint32_t idB = bMob->GetObject<Node>()->GetId();
3698 if (idA > idB)
3699 {
3700 std::swap(aMobOrdered, bMobOrdered);
3701 }
3702
3703 // Create new channel parameters instance
3705 // Set basic parameters
3706 channelParams->m_generatedTime = Simulator::Now();
3707 channelParams->m_nodeIds = {aMobOrdered->GetObject<Node>()->GetId(),
3708 bMobOrdered->GetObject<Node>()->GetId()};
3709 channelParams->m_losCondition = channelCondition->GetLosCondition();
3710 channelParams->m_o2iCondition = channelCondition->GetO2iCondition();
3711 // Angles are generated with aMob as transmitter (departure) and bMob as receiver (arrival).
3712 channelParams->m_txSpeed = aMobOrdered->GetVelocity();
3713 channelParams->m_rxSpeed = bMobOrdered->GetVelocity();
3714 UpdateLinkGeometry(aMobOrdered,
3715 bMobOrdered,
3716 &channelParams->m_dis2D,
3717 &channelParams->m_dis3D,
3718 &channelParams->m_endpointDisplacement2D,
3719 &channelParams->m_relativeDisplacement2D,
3720 &channelParams->m_lastPositionFirst,
3721 &channelParams->m_lastPositionSecond,
3722 &channelParams->m_lastRelativePosition2D);
3723
3724 // Step 4: Generate large-scale parameters. All LSPS are uncorrelated.
3725 const LargeScaleParameters lsps = GenerateLSPs(channelParams->m_losCondition, table3gpp);
3726
3727 channelParams->m_DS = lsps.DS;
3728 channelParams->m_K_factor = lsps.kFactor;
3729
3730 // Step 5: Generate Delays and normalize them. Save minTau to be used for channel consistency.
3731 double minTau = 100.0;
3732 GenerateClusterDelays(lsps.DS, table3gpp, &minTau, &channelParams->m_delay);
3733 /* since the scaled Los delays are not to be used in cluster power generation,
3734 * we will generate cluster power first and resume to compute Los cluster delay later.*/
3735
3736 // Step 6: Generate cluster powers.
3737 GenerateClusterShadowingTerm(table3gpp, &channelParams->m_clusterShadowing);
3738 GenerateClusterPowers(channelParams->m_delay,
3739 lsps.DS,
3740 table3gpp,
3741 channelParams->m_clusterShadowing,
3742 &channelParams->m_clusterPower);
3743 double powerMax = 0;
3744 const DoubleVector clusterPowerForAngles = RemoveWeakClusters(&channelParams->m_clusterPower,
3745 &channelParams->m_delay,
3746 channelParams->m_losCondition,
3747 table3gpp,
3748 lsps.kFactor,
3749 &powerMax);
3750
3751 channelParams->m_reducedClusterNumber = channelParams->m_clusterPower.size();
3752 // Resume step 5 to compute the delay for LoS condition.
3753 if (channelParams->m_losCondition == ChannelCondition::LOS)
3754 {
3755 AdjustClusterDelaysForLosCondition(&channelParams->m_delay,
3756 channelParams->m_reducedClusterNumber,
3757 channelParams->m_K_factor);
3758 }
3759
3760 // Step 7: Generate arrival and departure angles for both azimuth and elevation.
3761 const auto cPhi = CalculateCphi(channelParams->m_losCondition, table3gpp, lsps.kFactor);
3762 const auto cTheta = CalculateCtheta(channelParams->m_losCondition, table3gpp, lsps.kFactor);
3763
3764 GenerateClusterAngles(channelParams,
3765 clusterPowerForAngles,
3766 powerMax,
3767 cPhi,
3768 cTheta,
3769 lsps,
3770 aMobOrdered,
3771 bMobOrdered,
3772 table3gpp,
3773 &channelParams->m_angle);
3774
3775 // if blockage enabled, calculate, apply and store attenuation
3776 ApplyAttenuationToClusterPowers(&channelParams->m_nonSelfBlocking,
3777 &channelParams->m_clusterPower,
3778 &channelParams->m_attenuation_dB,
3779 channelParams,
3780 channelParams->m_angle[AOA_INDEX],
3781 channelParams->m_angle[ZOA_INDEX],
3782 table3gpp);
3783 // Step 8: Coupling of rays within a cluster for both azimuth and elevation
3784 // shuffle all the arrays to perform random coupling
3785 // Step a): update per-ray angles around cluster means (no shuffling)
3786 ComputeRayAngles(channelParams,
3787 table3gpp,
3788 &channelParams->m_rayAoaRadian,
3789 &channelParams->m_rayAodRadian,
3790 &channelParams->m_rayZoaRadian,
3791 &channelParams->m_rayZodRadian);
3792
3793 // Step b): random coupling by shuffling rays within each cluster
3794 RandomRaysCoupling(channelParams,
3795 &channelParams->m_rayAoaRadian,
3796 &channelParams->m_rayAodRadian,
3797 &channelParams->m_rayZoaRadian,
3798 &channelParams->m_rayZodRadian);
3799
3800 // Step 9: Generate the cross-polarization power ratios
3801 // Step 10: Draw initial phases
3802 GenerateCrossPolPowerRatiosAndInitialPhases(&channelParams->m_crossPolarizationPowerRatios,
3803 &channelParams->m_clusterPhase,
3804 channelParams->m_reducedClusterNumber,
3805 table3gpp);
3806
3807 // Generate Doppler terms
3808 GenerateDopplerTerms(channelParams->m_reducedClusterNumber,
3809 &channelParams->m_alpha,
3810 &channelParams->m_D);
3811
3812 // save delay consistency for the channel updates with the reduced cluster number
3813 channelParams->m_delayConsistency = channelParams->m_delay;
3814 for (uint8_t cInd = 0; cInd < channelParams->m_reducedClusterNumber; cInd++)
3815 {
3816 // 7.6.3.2, Procedure A, k=0 -> t_0
3817 if (channelParams->m_losCondition != ChannelCondition::LOS)
3818 {
3819 channelParams->m_delayConsistency[cInd] += minTau;
3820 }
3821 // distance between RX antenna and TX antenna at t_0 in [m] divided by the speed of light
3822 // [m/s]
3823 channelParams->m_delayConsistency[cInd] += channelParams->m_dis3D / 3e8;
3824 }
3825
3826 FindStrongestClusters(channelParams,
3827 table3gpp,
3828 &channelParams->m_cluster1st,
3829 &channelParams->m_cluster2nd,
3830 &channelParams->m_delay,
3831 &channelParams->m_angle,
3832 &channelParams->m_alpha,
3833 &channelParams->m_D,
3834 &channelParams->m_clusterPower);
3835
3836 // Precompute angles sincos
3837 PrecomputeAnglesSinCos(channelParams, &channelParams->m_cachedAngleSincos);
3838
3839 return channelParams;
3840}
3841
3842void
3844 Ptr<const ChannelCondition> channelCondition,
3846 Ptr<const MobilityModel> bMob) const
3847{
3848 NS_LOG_FUNCTION(this);
3849 NS_ASSERT_MSG(channelParams != nullptr, "Channel parameters cannot be null");
3850 NS_ASSERT_MSG(channelCondition != nullptr, "Channel condition cannot be null");
3851 NS_ASSERT_MSG(aMob != nullptr, "Mobility model A cannot be null");
3852 NS_ASSERT_MSG(bMob != nullptr, "Mobility model B cannot be null");
3853
3854 // Ensure a/b ordering matches the direction used when the params were generated.
3855 // This keeps angle/speed associations consistent across reciprocal calls.
3856 Ptr<const MobilityModel> aMobOrdered = aMob;
3857 Ptr<const MobilityModel> bMobOrdered = bMob;
3858 const auto storedIds = channelParams->m_nodeIds;
3859 const auto callIds =
3860 std::make_pair(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
3861 const auto reversedIds = std::make_pair(callIds.second, callIds.first);
3862 NS_ASSERT_MSG(storedIds == callIds || storedIds == reversedIds,
3863 "Channel params node ids do not match this link");
3864 if (storedIds != callIds)
3865 {
3866 std::swap(aMobOrdered, bMobOrdered);
3867 }
3868
3869 TrimToBaseClusters(channelParams->m_reducedClusterNumber,
3870 &channelParams->m_delay,
3871 &channelParams->m_angle,
3872 &channelParams->m_cachedAngleSincos,
3873 &channelParams->m_alpha,
3874 &channelParams->m_D,
3875 &channelParams->m_clusterPower);
3876
3877 UpdateLinkGeometry(aMobOrdered,
3878 bMobOrdered,
3879 &channelParams->m_dis2D,
3880 &channelParams->m_dis3D,
3881 &channelParams->m_endpointDisplacement2D,
3882 &channelParams->m_relativeDisplacement2D,
3883 &channelParams->m_lastPositionFirst,
3884 &channelParams->m_lastPositionSecond,
3885 &channelParams->m_lastRelativePosition2D);
3886
3887 channelParams->m_txSpeed = aMobOrdered->GetVelocity();
3888 channelParams->m_rxSpeed = bMobOrdered->GetVelocity();
3889 channelParams->m_losCondition = channelCondition->GetLosCondition();
3890 channelParams->m_o2iCondition = channelCondition->GetO2iCondition();
3891
3892 // Update LSPs for new distance
3893 // Update cluster delay (7.6-9, 7.6-10, 7.6-10aa, 7.6-10a)
3894 const DoubleVector prevClusterDelay = channelParams->m_delayConsistency;
3895 UpdateClusterDelay(&channelParams->m_delay, &channelParams->m_delayConsistency, channelParams);
3896
3897 // Get the 3GPP parameter table
3898 const Ptr<const ParamsTable> table3gpp =
3899 GetThreeGppTable(aMobOrdered, bMobOrdered, channelCondition);
3900
3902 &channelParams->m_clusterShadowing,
3903 channelParams->m_relativeDisplacement2D);
3904 // According to 3GPP 38.901. Procedure A, cluster powers are updated as in Step 6 using the
3905 // cluster delays from Equation (7.6-10a).
3906
3907 GenerateClusterPowers(channelParams->m_delay,
3908 channelParams->m_DS,
3909 table3gpp,
3910 channelParams->m_clusterShadowing,
3911 &channelParams->m_clusterPower);
3912
3913 // draw random signs from cluster angles +1,-1 and save them to reuse them for the channel
3914 // update
3915 if (channelParams->m_losCondition != ChannelCondition::LOS &&
3916 channelParams->m_clusterXnNlosSign.empty())
3917 {
3918 GenerateClusterXnNLos(channelParams->m_reducedClusterNumber,
3919 &channelParams->m_clusterXnNlosSign);
3920 }
3921
3922 // Update cluster departure and arrival angles
3923 const Double2DVector previousAngles = channelParams->m_angle;
3924 UpdateClusterAngles(channelParams, &channelParams->m_angle, prevClusterDelay);
3925
3926 NS_ABORT_IF(channelParams->m_clusterPower.empty());
3927
3928 // if blockage enabled, calculate, apply and store attenuation
3929 ApplyAttenuationToClusterPowers(&channelParams->m_nonSelfBlocking,
3930 &channelParams->m_clusterPower,
3931 &channelParams->m_attenuation_dB,
3932 channelParams,
3933 channelParams->m_angle[AOA_INDEX],
3934 channelParams->m_angle[ZOA_INDEX],
3935 table3gpp);
3936
3938 channelParams,
3939 previousAngles,
3940 &channelParams->m_rayAoaRadian,
3941 &channelParams->m_rayAodRadian,
3942 &channelParams->m_rayZoaRadian,
3943 &channelParams->m_rayZodRadian);
3944
3945 FindStrongestClusters(channelParams,
3946 table3gpp,
3947 &channelParams->m_cluster1st,
3948 &channelParams->m_cluster2nd,
3949 &channelParams->m_delay,
3950 &channelParams->m_angle,
3951 &channelParams->m_alpha,
3952 &channelParams->m_D,
3953 &channelParams->m_clusterPower);
3954
3955 // Precompute angle sin/cos for efficiency
3956 PrecomputeAnglesSinCos(channelParams, &channelParams->m_cachedAngleSincos);
3957
3958 channelParams->m_generatedTime = Simulator::Now(); // Update timing information
3959
3960 NS_LOG_DEBUG("Updated channel parameters for consistency (Procedure A): "
3961 << "Clusters: " << channelParams->m_reducedClusterNumber
3962 << ", DS: " << channelParams->m_DS << ", K-factor: " << channelParams->m_K_factor);
3963}
3964
3967 Ptr<const ParamsTable> table3gpp,
3971 Ptr<const PhasedArrayModel> uAntenna) const
3972{
3973 NS_LOG_FUNCTION(this);
3974
3975 NS_ASSERT_MSG(m_frequency > 0.0, "Set the operating frequency first!");
3976 NS_ASSERT_MSG(channelParams != nullptr, "Channel parameters cannot be null");
3977 NS_ASSERT_MSG(sMob != nullptr && uMob != nullptr, "Mobility models cannot be null");
3978 NS_ASSERT_MSG(sAntenna != nullptr && uAntenna != nullptr, "Antennas cannot be null");
3979 const auto callIds =
3980 std::make_pair(sMob->GetObject<Node>()->GetId(), uMob->GetObject<Node>()->GetId());
3981 const auto reversedIds = std::make_pair(callIds.second, callIds.first);
3982 NS_ASSERT_MSG(channelParams->m_nodeIds == callIds || channelParams->m_nodeIds == reversedIds,
3983 "Channel params node ids do not match this link");
3984
3985 // create a channel matrix instance
3986 Ptr<ChannelMatrix> channelMatrix = Create<ChannelMatrix>();
3987 channelMatrix->m_generatedTime = Simulator::Now();
3988 // save in which order is generated this matrix
3989 channelMatrix->m_nodeIds =
3990 std::make_pair(sMob->GetObject<Node>()->GetId(), uMob->GetObject<Node>()->GetId());
3991 // check if channelParams structure is generated in a direction s-to-u or u-to-s
3992 bool isSameDirection = channelParams->m_nodeIds == channelMatrix->m_nodeIds;
3993 channelMatrix->m_antennaPair =
3994 std::make_pair(sAntenna->GetId(),
3995 uAntenna->GetId()); // save antenna pair, with the exact order of s and u
3996
3997 Double2DVector rayAodRadian;
3998 Double2DVector rayAoaRadian;
3999 Double2DVector rayZodRadian;
4000 Double2DVector rayZoaRadian;
4001
4002 // if channel params is generated in the same direction in which we
4003 // generate the channel matrix, angles and zenith od departure and arrival are ok,
4004 // just set them to corresponding variable that will be used for the generation
4005 // of channel matrix, otherwise we need to flip angles and zeniths of departure and arrival
4006 if (isSameDirection)
4007 {
4008 rayAodRadian = channelParams->m_rayAodRadian;
4009 rayAoaRadian = channelParams->m_rayAoaRadian;
4010 rayZodRadian = channelParams->m_rayZodRadian;
4011 rayZoaRadian = channelParams->m_rayZoaRadian;
4012 }
4013 else
4014 {
4015 rayAodRadian = channelParams->m_rayAoaRadian;
4016 rayAoaRadian = channelParams->m_rayAodRadian;
4017 rayZodRadian = channelParams->m_rayZoaRadian;
4018 rayZoaRadian = channelParams->m_rayZodRadian;
4019 }
4020
4021 // Step 11: Generate channel coefficients for each cluster n and each receiver
4022 // and transmitter element pair u,s.
4023 // where n is cluster index, u and s are receive and transmit antenna element.
4024 size_t uSize = uAntenna->GetNumElems();
4025 size_t sSize = sAntenna->GetNumElems();
4026
4027 // NOTE: Since each of the strongest 2 clusters are divided into 3 sub-clusters,
4028 // the total cluster will generally be numReducedCLuster + 4.
4029 // However, it might be that m_cluster1st = m_cluster2nd. In this case the
4030 // total number of clusters will be numReducedCLuster + 2.
4031 uint16_t numOverallCluster = channelParams->m_cluster1st != channelParams->m_cluster2nd
4032 ? channelParams->m_reducedClusterNumber + 4
4033 : channelParams->m_reducedClusterNumber + 2;
4034 Complex3DVector hUsn(uSize, sSize, numOverallCluster); // channel coefficient hUsn (u, s, n);
4035 NS_ASSERT(channelParams->m_reducedClusterNumber <= channelParams->m_clusterPhase.size());
4036 NS_ASSERT(channelParams->m_reducedClusterNumber <= channelParams->m_clusterPower.size());
4037 NS_ASSERT(channelParams->m_reducedClusterNumber <=
4038 channelParams->m_crossPolarizationPowerRatios.size());
4039 NS_ASSERT(channelParams->m_reducedClusterNumber <= rayZoaRadian.size());
4040 NS_ASSERT(channelParams->m_reducedClusterNumber <= rayZodRadian.size());
4041 NS_ASSERT(channelParams->m_reducedClusterNumber <= rayAoaRadian.size());
4042 NS_ASSERT(channelParams->m_reducedClusterNumber <= rayAodRadian.size());
4043 NS_ASSERT(table3gpp->m_raysPerCluster <= channelParams->m_clusterPhase[0].size());
4044 NS_ASSERT(table3gpp->m_raysPerCluster <=
4045 channelParams->m_crossPolarizationPowerRatios[0].size());
4046 NS_ASSERT(table3gpp->m_raysPerCluster <= rayZoaRadian[0].size());
4047 NS_ASSERT(table3gpp->m_raysPerCluster <= rayZodRadian[0].size());
4048 NS_ASSERT(table3gpp->m_raysPerCluster <= rayAoaRadian[0].size());
4049 NS_ASSERT(table3gpp->m_raysPerCluster <= rayAodRadian[0].size());
4050
4051 double distance3D = channelParams->m_dis3D;
4052
4053 Angles sAngle(uMob->GetPosition(), sMob->GetPosition());
4054 Angles uAngle(sMob->GetPosition(), uMob->GetPosition());
4055
4056 Double2DVector sinCosA; // cached multiplications of sin and cos of the ZoA and AoA angles
4057 Double2DVector sinSinA; // cached multiplications of sines of the ZoA and AoA angles
4058 Double2DVector cosZoA; // cached cos of the ZoA angle
4059 Double2DVector sinCosD; // cached multiplications of sin and cos of the ZoD and AoD angles
4060 Double2DVector sinSinD; // cached multiplications of the cosines of the ZoA and AoA angles
4061 Double2DVector cosZoD; // cached cos of the ZoD angle
4062
4063 // contains part of the ray expression, cached as independent from the u- and s-indexes,
4064 // but calculate it for different polarization angles of s and u
4065 std::map<std::pair<uint8_t, uint8_t>, Complex2DVector> raysPreComp;
4066 for (size_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa)
4067 {
4068 for (size_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa)
4069 {
4070 raysPreComp[std::make_pair(polSa, polUa)] =
4071 Complex2DVector(channelParams->m_reducedClusterNumber, table3gpp->m_raysPerCluster);
4072 }
4073 }
4074
4075 // resize to appropriate dimensions
4076 sinCosA.resize(channelParams->m_reducedClusterNumber);
4077 sinSinA.resize(channelParams->m_reducedClusterNumber);
4078 cosZoA.resize(channelParams->m_reducedClusterNumber);
4079 sinCosD.resize(channelParams->m_reducedClusterNumber);
4080 sinSinD.resize(channelParams->m_reducedClusterNumber);
4081 cosZoD.resize(channelParams->m_reducedClusterNumber);
4082 for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
4083 {
4084 sinCosA[nIndex].resize(table3gpp->m_raysPerCluster);
4085 sinSinA[nIndex].resize(table3gpp->m_raysPerCluster);
4086 cosZoA[nIndex].resize(table3gpp->m_raysPerCluster);
4087 sinCosD[nIndex].resize(table3gpp->m_raysPerCluster);
4088 sinSinD[nIndex].resize(table3gpp->m_raysPerCluster);
4089 cosZoD[nIndex].resize(table3gpp->m_raysPerCluster);
4090 }
4091 // pre-compute the terms which are independent from uIndex and sIndex
4092 for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
4093 {
4094 for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
4095 {
4096 DoubleVector initialPhase = channelParams->m_clusterPhase[nIndex][mIndex];
4097 NS_ASSERT(4 <= initialPhase.size());
4098 double k = channelParams->m_crossPolarizationPowerRatios[nIndex][mIndex];
4099
4100 // cache the component of the "rays" terms which depend on the random angle of arrivals
4101 // and departures and initial phases only
4102 for (uint8_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa)
4103 {
4104 auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern(
4105 Angles(channelParams->m_rayAoaRadian[nIndex][mIndex],
4106 channelParams->m_rayZoaRadian[nIndex][mIndex]),
4107 polUa);
4108 for (uint8_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa)
4109 {
4110 auto [txFieldPatternPhi, txFieldPatternTheta] =
4111 sAntenna->GetElementFieldPattern(
4112 Angles(channelParams->m_rayAodRadian[nIndex][mIndex],
4113 channelParams->m_rayZodRadian[nIndex][mIndex]),
4114 polSa);
4115 raysPreComp[std::make_pair(polSa, polUa)](nIndex, mIndex) =
4116 std::complex(cos(initialPhase[0]), sin(initialPhase[0])) *
4117 rxFieldPatternTheta * txFieldPatternTheta +
4118 std::complex(cos(initialPhase[1]), sin(initialPhase[1])) *
4119 std::sqrt(1.0 / k) * rxFieldPatternTheta * txFieldPatternPhi +
4120 std::complex(cos(initialPhase[2]), sin(initialPhase[2])) *
4121 std::sqrt(1.0 / k) * rxFieldPatternPhi * txFieldPatternTheta +
4122 std::complex(cos(initialPhase[3]), sin(initialPhase[3])) *
4123 rxFieldPatternPhi * txFieldPatternPhi;
4124 }
4125 }
4126
4127 // cache the component of the "rxPhaseDiff" terms which depend on the random angle of
4128 // arrivals only
4129 double sinRayZoa = sin(rayZoaRadian[nIndex][mIndex]);
4130 double sinRayAoa = sin(rayAoaRadian[nIndex][mIndex]);
4131 double cosRayAoa = cos(rayAoaRadian[nIndex][mIndex]);
4132 sinCosA[nIndex][mIndex] = sinRayZoa * cosRayAoa;
4133 sinSinA[nIndex][mIndex] = sinRayZoa * sinRayAoa;
4134 cosZoA[nIndex][mIndex] = cos(rayZoaRadian[nIndex][mIndex]);
4135
4136 // cache the component of the "txPhaseDiff" terms which depend on the random angle of
4137 // departure only
4138 double sinRayZod = sin(rayZodRadian[nIndex][mIndex]);
4139 double sinRayAod = sin(rayAodRadian[nIndex][mIndex]);
4140 double cosRayAod = cos(rayAodRadian[nIndex][mIndex]);
4141 sinCosD[nIndex][mIndex] = sinRayZod * cosRayAod;
4142 sinSinD[nIndex][mIndex] = sinRayZod * sinRayAod;
4143 cosZoD[nIndex][mIndex] = cos(rayZodRadian[nIndex][mIndex]);
4144 }
4145 }
4146
4147 // The following for loops computes the channel coefficients
4148 // Keeps track of how many sub-clusters have been added up to now
4149 uint8_t numSubClustersAdded = 0;
4150 for (uint8_t nIndex = 0; nIndex < channelParams->m_reducedClusterNumber; nIndex++)
4151 {
4152 for (size_t uIndex = 0; uIndex < uSize; uIndex++)
4153 {
4154 Vector uLoc = uAntenna->GetElementLocation(uIndex);
4155
4156 for (size_t sIndex = 0; sIndex < sSize; sIndex++)
4157 {
4158 Vector sLoc = sAntenna->GetElementLocation(sIndex);
4159 // Compute the N-2 weakest cluster, assuming 0 slant angle and a
4160 // polarization slant angle configured in the array (7.5-22)
4161 if (nIndex != channelParams->m_cluster1st && nIndex != channelParams->m_cluster2nd)
4162 {
4163 std::complex<double> rays(0, 0);
4164 for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
4165 {
4166 // lambda_0 is accounted in the antenna spacing uLoc and sLoc.
4167 double rxPhaseDiff =
4168 2 * M_PI *
4169 (sinCosA[nIndex][mIndex] * uLoc.x + sinSinA[nIndex][mIndex] * uLoc.y +
4170 cosZoA[nIndex][mIndex] * uLoc.z);
4171
4172 double txPhaseDiff =
4173 2 * M_PI *
4174 (sinCosD[nIndex][mIndex] * sLoc.x + sinSinD[nIndex][mIndex] * sLoc.y +
4175 cosZoD[nIndex][mIndex] * sLoc.z);
4176 // NOTE Doppler is computed in the CalcBeamformingGain function and is
4177 // simplified to only account for the center angle of each cluster.
4178 rays += raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex),
4179 uAntenna->GetElemPol(uIndex))](nIndex,
4180 mIndex) *
4181 std::complex(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
4182 std::complex(cos(txPhaseDiff), sin(txPhaseDiff));
4183 }
4184 rays *=
4185 sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
4186 hUsn(uIndex, sIndex, nIndex) = rays;
4187 }
4188 else //(7.5-28)
4189 {
4190 std::complex<double> raysSub1(0, 0);
4191 std::complex<double> raysSub2(0, 0);
4192 std::complex<double> raysSub3(0, 0);
4193
4194 for (uint8_t mIndex = 0; mIndex < table3gpp->m_raysPerCluster; mIndex++)
4195 {
4196 // ZML:Just remind me that the angle offsets for the 3 subclusters were not
4197 // generated correctly.
4198 double rxPhaseDiff =
4199 2 * M_PI *
4200 (sinCosA[nIndex][mIndex] * uLoc.x + sinSinA[nIndex][mIndex] * uLoc.y +
4201 cosZoA[nIndex][mIndex] * uLoc.z);
4202
4203 double txPhaseDiff =
4204 2 * M_PI *
4205 (sinCosD[nIndex][mIndex] * sLoc.x + sinSinD[nIndex][mIndex] * sLoc.y +
4206 cosZoD[nIndex][mIndex] * sLoc.z);
4207
4208 std::complex<double> raySub =
4209 raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex),
4210 uAntenna->GetElemPol(uIndex))](nIndex,
4211 mIndex) *
4212 std::complex(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
4213 std::complex(cos(txPhaseDiff), sin(txPhaseDiff));
4214
4215 switch (mIndex)
4216 {
4217 case 9:
4218 case 10:
4219 case 11:
4220 case 12:
4221 case 17:
4222 case 18:
4223 raysSub2 += raySub;
4224 break;
4225 case 13:
4226 case 14:
4227 case 15:
4228 case 16:
4229 raysSub3 += raySub;
4230 break;
4231 default: // case 1,2,3,4,5,6,7,8,19,20
4232 raysSub1 += raySub;
4233 break;
4234 }
4235 }
4236 raysSub1 *=
4237 sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
4238 raysSub2 *=
4239 sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
4240 raysSub3 *=
4241 sqrt(channelParams->m_clusterPower[nIndex] / table3gpp->m_raysPerCluster);
4242 hUsn(uIndex, sIndex, nIndex) = raysSub1;
4243 hUsn(uIndex,
4244 sIndex,
4245 channelParams->m_reducedClusterNumber + numSubClustersAdded) = raysSub2;
4246 hUsn(uIndex,
4247 sIndex,
4248 channelParams->m_reducedClusterNumber + numSubClustersAdded + 1) =
4249 raysSub3;
4250 }
4251 }
4252 }
4253 if (nIndex == channelParams->m_cluster1st || nIndex == channelParams->m_cluster2nd)
4254 {
4255 numSubClustersAdded += 2;
4256 }
4257 }
4258
4259 if (channelParams->m_losCondition == ChannelCondition::LOS) //(7.5-29) && (7.5-30)
4260 {
4261 double lambda = 3.0e8 / m_frequency; // the wavelength of the carrier frequency
4262 std::complex phaseDiffDueToDistance(cos(-2 * M_PI * distance3D / lambda),
4263 sin(-2 * M_PI * distance3D / lambda));
4264
4265 const double sinUAngleIncl = sin(uAngle.GetInclination());
4266 const double cosUAngleIncl = cos(uAngle.GetInclination());
4267 const double sinUAngleAz = sin(uAngle.GetAzimuth());
4268 const double cosUAngleAz = cos(uAngle.GetAzimuth());
4269 const double sinSAngleIncl = sin(sAngle.GetInclination());
4270 const double cosSAngleIncl = cos(sAngle.GetInclination());
4271 const double sinSAngleAz = sin(sAngle.GetAzimuth());
4272 const double cosSAngleAz = cos(sAngle.GetAzimuth());
4273
4274 for (size_t uIndex = 0; uIndex < uSize; uIndex++)
4275 {
4276 Vector uLoc = uAntenna->GetElementLocation(uIndex);
4277 double rxPhaseDiff = 2 * M_PI *
4278 (sinUAngleIncl * cosUAngleAz * uLoc.x +
4279 sinUAngleIncl * sinUAngleAz * uLoc.y + cosUAngleIncl * uLoc.z);
4280
4281 for (size_t sIndex = 0; sIndex < sSize; sIndex++)
4282 {
4283 Vector sLoc = sAntenna->GetElementLocation(sIndex);
4284 std::complex<double> ray(0, 0);
4285 double txPhaseDiff =
4286 2 * M_PI *
4287 (sinSAngleIncl * cosSAngleAz * sLoc.x + sinSAngleIncl * sinSAngleAz * sLoc.y +
4288 cosSAngleIncl * sLoc.z);
4289
4290 auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern(
4291 Angles(uAngle.GetAzimuth(), uAngle.GetInclination()),
4292 uAntenna->GetElemPol(uIndex));
4293 auto [txFieldPatternPhi, txFieldPatternTheta] = sAntenna->GetElementFieldPattern(
4294 Angles(sAngle.GetAzimuth(), sAngle.GetInclination()),
4295 sAntenna->GetElemPol(sIndex));
4296
4297 ray = (rxFieldPatternTheta * txFieldPatternTheta -
4298 rxFieldPatternPhi * txFieldPatternPhi) *
4299 phaseDiffDueToDistance * std::complex(cos(rxPhaseDiff), sin(rxPhaseDiff)) *
4300 std::complex(cos(txPhaseDiff), sin(txPhaseDiff));
4301
4302 double kLinear = pow(10, channelParams->m_K_factor / 10.0);
4303 // the LOS path should be attenuated if blockage is enabled.
4304 hUsn(uIndex, sIndex, 0) =
4305 sqrt(1.0 / (kLinear + 1)) * hUsn(uIndex, sIndex, 0) +
4306 sqrt(kLinear / (1 + kLinear)) * ray /
4307 pow(10,
4308 channelParams->m_attenuation_dB[0] / 10.0); //(7.5-30) for tau = tau1
4309 for (size_t nIndex = 1; nIndex < hUsn.GetNumPages(); nIndex++)
4310 {
4311 hUsn(uIndex, sIndex, nIndex) *=
4312 sqrt(1.0 / (kLinear + 1)); //(7.5-30) for tau = tau2...tauN
4313 }
4314 }
4315 }
4316 }
4317
4318 std::ostringstream oss;
4319 oss << "Husn (sAntenna, uAntenna): " << sAntenna->GetId() << ", " << uAntenna->GetId()
4320 << " | vals=[";
4321
4322 bool first = true;
4323 for (size_t cIndex = 0; cIndex < hUsn.GetNumPages(); ++cIndex)
4324 {
4325 for (size_t rowIdx = 0; rowIdx < hUsn.GetNumRows(); ++rowIdx)
4326 {
4327 for (size_t colIdx = 0; colIdx < hUsn.GetNumCols(); ++colIdx)
4328 {
4329 if (!first)
4330 {
4331 oss << ", ";
4332 }
4333 first = false;
4334 oss << hUsn(rowIdx, colIdx, cIndex);
4335 }
4336 }
4337 }
4338 oss << "]";
4339
4340 NS_LOG_DEBUG(oss.str());
4341
4342 NS_LOG_INFO("size of coefficient matrix (rows, columns, clusters) = ("
4343 << hUsn.GetNumRows() << ", " << hUsn.GetNumCols() << ", " << hUsn.GetNumPages()
4344 << ")");
4345 channelMatrix->m_channel = hUsn;
4346 return channelMatrix;
4347}
4348
4349std::pair<double, double>
4350ThreeGppChannelModel::WrapAngles(double azimuthRad, double inclinationRad)
4351{
4352 inclinationRad = WrapTo2Pi(inclinationRad);
4353 if (inclinationRad > M_PI)
4354 {
4355 // inclination must be in [0, M_PI]
4356 inclinationRad -= M_PI;
4357 azimuthRad += M_PI;
4358 }
4359
4360 azimuthRad = WrapTo2Pi(azimuthRad);
4361
4362 NS_ASSERT_MSG(0 <= inclinationRad && inclinationRad <= M_PI,
4363 "inclinationRad=" << inclinationRad << " not valid, should be in [0, pi]");
4364 NS_ASSERT_MSG(0 <= azimuthRad && azimuthRad <= 2 * M_PI,
4365 "azimuthRad=" << azimuthRad << " not valid, should be in [0, 2*pi]");
4366
4367 return std::make_pair(azimuthRad, inclinationRad);
4368}
4369
4370void
4372 Ptr<const ParamsTable> table3gpp,
4374 const Double2DVector& prevClusterAngles,
4375 Double2DVector* rayAodRadian,
4376 Double2DVector* rayAoaRadian,
4377 Double2DVector* rayZodRadian,
4378 Double2DVector* rayZoaRadian) const
4379{
4380 NS_LOG_FUNCTION(this << channelParams);
4381
4382 for (size_t n = 0; n < channelParams->m_reducedClusterNumber; ++n)
4383 {
4384 // Compute per-cluster deltas (degrees) and convert to radians
4385 const double dAoaRad = DegreesToRadians(prevClusterAngles[AOA_INDEX][n] -
4386 channelParams->m_angle[AOA_INDEX][n]);
4387 const double dAodRad = DegreesToRadians(prevClusterAngles[AOD_INDEX][n] -
4388 channelParams->m_angle[AOD_INDEX][n]);
4389 const double dZoaRad = DegreesToRadians(prevClusterAngles[ZOA_INDEX][n] -
4390 channelParams->m_angle[ZOA_INDEX][n]);
4391 const double dZodRad = DegreesToRadians(prevClusterAngles[ZOD_INDEX][n] -
4392 channelParams->m_angle[ZOD_INDEX][n]);
4393
4394 // Apply deltas to each ray, preserving coupling and offsets
4395 for (size_t m = 0; m < table3gpp->m_raysPerCluster; ++m)
4396 {
4397 std::tie((*rayAoaRadian)[n][m], (*rayZoaRadian)[n][m]) =
4398 WrapAngles((*rayAoaRadian)[n][m] + dAoaRad, (*rayZoaRadian)[n][m] + dZoaRad);
4399 std::tie((*rayAodRadian)[n][m], (*rayZodRadian)[n][m]) =
4400 WrapAngles((*rayAodRadian)[n][m] + dAodRad, (*rayZodRadian)[n][m] + dZodRad);
4401 }
4402 }
4403}
4404
4405void
4407 DoubleVector* powerAttenuation,
4409 const DoubleVector& clusterAOA,
4410 const DoubleVector& clusterZOA,
4411 Ptr<const ParamsTable> table3gpp) const
4412{
4413 NS_LOG_FUNCTION(this);
4414
4415 const auto clusterNum = clusterAOA.size();
4416 // Initial power attenuation for all clusters to be 0 dB
4417 *powerAttenuation = DoubleVector(clusterNum, 0);
4418
4419 // step a: the number of non-self-blocking blockers is stored in m_numNonSelfBlocking.
4420
4421 // step b:Generate the size and location of each blocker
4422 // generate self blocking (i.e., for blockage from the human body)
4423 // table 7.6.4.1-1 Self-blocking region parameters.
4424 // Defaults: landscape mode
4425 double phiSb = 40;
4426 double xSb = 160;
4427 double thetaSb = 110;
4428 double ySb = 75;
4429 if (m_portraitMode)
4430 {
4431 phiSb = 260;
4432 xSb = 120;
4433 thetaSb = 100;
4434 ySb = 80;
4435 }
4436
4437 // generate or update non-self blocking
4438 if (nonSelfBlocking->empty()) // generate new blocking regions
4439 {
4440 for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
4441 {
4442 // draw value from table 7.6.4.1-2 Blocking region parameters
4443 DoubleVector table;
4444 table.push_back(m_normalRv->GetValue()); // phi_k: store the normal RV that will be
4445 // mapped to uniform (0,360) later.
4446 if (m_scenario == "InH-OfficeMixed" || m_scenario == "InH-OfficeOpen")
4447 {
4448 table.push_back(m_uniformRv->GetValue(15, 45)); // x_k
4449 table.push_back(90); // Theta_k
4450 table.push_back(m_uniformRv->GetValue(5, 15)); // y_k
4451 table.push_back(2); // r
4452 }
4453 else
4454 {
4455 table.push_back(m_uniformRv->GetValue(5, 15)); // x_k
4456 table.push_back(90); // Theta_k
4457 table.push_back(5); // y_k
4458 table.push_back(10); // r
4459 }
4460 nonSelfBlocking->push_back(table);
4461 }
4462 }
4463 else
4464 {
4465 // if deltaX and speed are both 0, the autocorrelation is 1, skip updating
4466 if (const double deltaX = channelParams->m_endpointDisplacement2D;
4467 deltaX > 1e-6 || m_blockerSpeed > 1e-6)
4468 {
4469 const double corrDis = table3gpp->m_blockerDcorDistance;
4470 double R;
4471 if (m_blockerSpeed > 1e-6) // speed is not equal to 0
4472 {
4473 const double corrT = corrDis / m_blockerSpeed;
4474 R = exp(-1 * (deltaX / corrDis +
4475 (Now().GetSeconds() - channelParams->m_generatedTime.GetSeconds()) /
4476 corrT));
4477 }
4478 else
4479 {
4480 R = exp(-1 * (deltaX / corrDis));
4481 }
4482
4483 NS_LOG_INFO("Distance change:"
4484 << deltaX << " Speed:" << m_blockerSpeed << " Time difference:"
4485 << Now().GetSeconds() - channelParams->m_generatedTime.GetSeconds()
4486 << " correlation:" << R);
4487
4488 // In order to generate correlated uniform random variables, we first generate
4489 // correlated normal random variables and map the normal RV to uniform RV. Notice the
4490 // correlation will change if the RV is transformed from normal to uniform. To
4491 // compensate the distortion, the correlation of the normal RV is computed such that the
4492 // uniform RV would have the desired correlation when transformed from normal RV.
4493
4494 // The following formula was obtained from MATLAB numerical simulation.
4495
4496 if (R * R * -0.069 + R * 1.074 - 0.002 <
4497 1) // transform only when the correlation of normal RV is smaller than 1
4498 {
4499 R = R * R * -0.069 + R * 1.074 - 0.002;
4500 }
4501 for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
4502 {
4503 // Generate a new correlated normal RV with the following formula
4504 (*nonSelfBlocking)[blockInd][PHI_INDEX] =
4505 R * (*nonSelfBlocking)[blockInd][PHI_INDEX] +
4506 sqrt(1 - R * R) * m_normalRv->GetValue();
4507 }
4508 }
4509 }
4510
4511 // step c: Determine the attenuation of each blocker due to blockers
4512 for (std::size_t cInd = 0; cInd < clusterNum; cInd++)
4513 {
4514 NS_ASSERT_MSG(clusterAOA[cInd] >= 0 && clusterAOA[cInd] <= 360,
4515 "the AOA should be the range of [0,360]");
4516 NS_ASSERT_MSG(clusterZOA[cInd] >= 0 && clusterZOA[cInd] <= 180,
4517 "the ZOA should be the range of [0,180]");
4518
4519 // check self-blocking
4520 NS_LOG_INFO("AOA=" << clusterAOA[cInd] << " Block Region[" << phiSb - xSb / 2.0 << ","
4521 << phiSb + xSb / 2.0 << "]");
4522 NS_LOG_INFO("ZOA=" << clusterZOA[cInd] << " Block Region[" << thetaSb - ySb / 2.0 << ","
4523 << thetaSb + ySb / 2.0 << "]");
4524 if (std::abs(clusterAOA[cInd] - phiSb) < xSb / 2.0 &&
4525 std::abs(clusterZOA[cInd] - thetaSb) < ySb / 2.0)
4526 {
4527 (*powerAttenuation)[cInd] += 30; // attenuate by 30 dB.
4528 NS_LOG_INFO("Cluster[" << +cInd
4529 << "] is blocked by self blocking region and reduce 30 dB power,"
4530 "the attenuation is ["
4531 << (*powerAttenuation)[cInd] << " dB]");
4532 }
4533
4534 // check non-self-blocking
4535 for (uint16_t blockInd = 0; blockInd < m_numNonSelfBlocking; blockInd++)
4536 {
4537 // The normal RV is transformed to a uniform RV with the desired correlation.
4538 double phiK = 0.5 * erfc(-1 * (*nonSelfBlocking)[blockInd][PHI_INDEX] / sqrt(2)) * 360;
4539 while (phiK > 360)
4540 {
4541 phiK -= 360;
4542 }
4543
4544 while (phiK < 0)
4545 {
4546 phiK += 360;
4547 }
4548
4549 const double xK = (*nonSelfBlocking)[blockInd][X_INDEX];
4550 const double thetaK = (*nonSelfBlocking)[blockInd][THETA_INDEX];
4551 const double yK = (*nonSelfBlocking)[blockInd][Y_INDEX];
4552
4553 NS_LOG_INFO("AOA=" << clusterAOA[cInd] << " Block Region[" << phiK - xK << ","
4554 << phiK + xK << "]");
4555 NS_LOG_INFO("ZOA=" << clusterZOA[cInd] << " Block Region[" << thetaK - yK << ","
4556 << thetaK + yK << "]");
4557
4558 if (std::abs(clusterAOA[cInd] - phiK) < xK && std::abs(clusterZOA[cInd] - thetaK) < yK)
4559 {
4560 const double A1 = clusterAOA[cInd] - (phiK + xK / 2.0); //(7.6-24)
4561 const double A2 = clusterAOA[cInd] - (phiK - xK / 2.0); //(7.6-25)
4562 const double Z1 = clusterZOA[cInd] - (thetaK + yK / 2.0); //(7.6-26)
4563 const double Z2 = clusterZOA[cInd] - (thetaK - yK / 2.0); //(7.6-27)
4564 int signA1;
4565 int signA2;
4566 int signZ1;
4567 int signZ2;
4568 // draw sign for the above parameters according to table 7.6.4.1-3 Description of
4569 // signs
4570 if (xK / 2.0 < clusterAOA[cInd] - phiK && clusterAOA[cInd] - phiK <= xK)
4571 {
4572 signA1 = -1;
4573 }
4574 else
4575 {
4576 signA1 = 1;
4577 }
4578 if (-1 * xK < clusterAOA[cInd] - phiK && clusterAOA[cInd] - phiK <= -1 * xK / 2.0)
4579 {
4580 signA2 = -1;
4581 }
4582 else
4583 {
4584 signA2 = 1;
4585 }
4586
4587 if (yK / 2.0 < clusterZOA[cInd] - thetaK && clusterZOA[cInd] - thetaK <= yK)
4588 {
4589 signZ1 = -1;
4590 }
4591 else
4592 {
4593 signZ1 = 1;
4594 }
4595 if (-1 * yK < clusterZOA[cInd] - thetaK &&
4596 clusterZOA[cInd] - thetaK <= -1 * yK / 2.0)
4597 {
4598 signZ2 = -1;
4599 }
4600 else
4601 {
4602 signZ2 = 1;
4603 }
4604 const double lambda = 3e8 / m_frequency;
4605 const double fA1 = atan(signA1 * M_PI / 2.0 *
4606 sqrt(M_PI / lambda * (*nonSelfBlocking)[blockInd][R_INDEX] *
4607 (1.0 / cos(DegreesToRadians(A1)) - 1))) /
4608 M_PI; //(7.6-23)
4609 const double fA2 = atan(signA2 * M_PI / 2.0 *
4610 sqrt(M_PI / lambda * (*nonSelfBlocking)[blockInd][R_INDEX] *
4611 (1.0 / cos(DegreesToRadians(A2)) - 1))) /
4612 M_PI;
4613 const double fZ1 = atan(signZ1 * M_PI / 2.0 *
4614 sqrt(M_PI / lambda * (*nonSelfBlocking)[blockInd][R_INDEX] *
4615 (1.0 / cos(DegreesToRadians(Z1)) - 1))) /
4616 M_PI;
4617 const double fZ2 = atan(signZ2 * M_PI / 2.0 *
4618 sqrt(M_PI / lambda * (*nonSelfBlocking)[blockInd][R_INDEX] *
4619 (1.0 / cos(DegreesToRadians(Z2)) - 1))) /
4620 M_PI;
4621 const double lDb = -20 * log10(1 - (fA1 + fA2) * (fZ1 + fZ2)); //(7.6-22)
4622 (*powerAttenuation)[cInd] += lDb;
4623 NS_LOG_INFO("Cluster[" << +cInd << "] is blocked by no-self blocking, the loss is ["
4624 << lDb << "] dB");
4625 }
4626 }
4627 }
4628}
4629
4630int64_t
4632{
4633 NS_LOG_FUNCTION(this << stream);
4634 m_normalRv->SetStream(stream);
4635 m_uniformRv->SetStream(stream + 1);
4636 m_uniformRvShuffle->SetStream(stream + 2);
4637 m_uniformRvDoppler->SetStream(stream + 3);
4638 return 4;
4639}
4640
4641} // namespace ns3
uint32_t v
Class holding the azimuth and inclination angles of spherical coordinates.
Definition angles.h:107
double GetInclination() const
Getter for inclination angle.
Definition angles.cc:236
double GetAzimuth() const
Getter for azimuth angle.
Definition angles.cc:230
LosConditionValue
Possible values for Line-of-Sight condition.
This class can be used to hold variables of floating point type such as 'double' or 'float'.
Definition double.h:31
Hold a signed integer type.
Definition integer.h:34
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.
static const uint8_t AOA_INDEX
index of the AOA value in the m_angle array
ComplexMatrixArray Complex3DVector
Create an alias for 3D complex vectors.
ComplexMatrixArray Complex2DVector
Create an alias for 2D complex vectors.
static const uint8_t ZOD_INDEX
index of the ZOD value in the m_angle array
static const uint8_t AOD_INDEX
index of the AOD value in the m_angle array
static const uint8_t ZOA_INDEX
index of the ZOA value in the m_angle array
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:46
uint32_t GetId() const
Definition node.cc:106
AttributeValue implementation for Pointer.
Definition pointer.h:37
Smart pointer class similar to boost::intrusive_ptr.
Definition ptr.h:70
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:191
Hold variables of type string.
Definition string.h:45
ThreeGppChannelModel extends MatrixBasedChannelModel and represents a channel model based on 3GPP spe...
int64_t AssignStreams(int64_t stream)
Assign a fixed random variable stream number to the random variables used by this model.
void ComputeRayAngles(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ParamsTable > table3gpp, Double2DVector *rayAoaRadian, Double2DVector *rayAodRadian, Double2DVector *rayZoaRadian, Double2DVector *rayZodRadian) const
Compute per-ray angles (no shuffling), centered on cluster means.
double ComputeEndpointDisplacement2d(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, const Vector &lastPositionFirst, const Vector &lastPositionSecond) const
Compute the effective 2D displacement of a link with respect to previously stored endpoint positions.
Vector ApplyVelocityRotation(double alphaRad, double betaRad, double gammaRad, double etaRad, const Vector &speed, const int Xn) const
Rotate a 3D velocity vector by specified angles, around y-axis and z-axis.
bool m_portraitMode
true if portrait mode, false if landscape
static double CalculateCtheta(const ChannelCondition::LosConditionValue losCondition, Ptr< const ParamsTable > table3gpp, const double kFactor)
Calculates the cTheta parameter for zenith angular spread modeling.
Ptr< NormalRandomVariable > m_normalRv
normal random variable
void ApplyAttenuationToClusterPowers(Double2DVector *nonSelfBlocking, DoubleVector *clusterPowers, DoubleVector *attenuation_dB, Ptr< const ThreeGppChannelParams > channelParams, const DoubleVector &clusterAoa, const DoubleVector &clusterZoa, Ptr< const ParamsTable > table3gpp) const
Applies blockage-based attenuation to the cluster powers according to the 3GPP TR 38....
bool ChannelUpdateNeeded(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const
Check if spatial-consistency requires updating channel parameters.
void FindStrongestClusters(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ParamsTable > table3gpp, uint8_t *cluster1st, uint8_t *cluster2nd, DoubleVector *clusterDelay, Double2DVector *angles, DoubleVector *alpha, DoubleVector *dTerm, DoubleVector *clusterPower) const
Identify the two strongest base clusters and append their derived subclusters.
bool m_blockage
enables the blockage model A
void GenerateCrossPolPowerRatiosAndInitialPhases(Double2DVector *crossPolarizationPowerRatios, Double3DVector *clusterPhase, const uint8_t reducedClusterNumber, Ptr< const ParamsTable > table3gpp) const
Generate cross-polarization power ratios and initial per-ray phases.
LargeScaleParameters GenerateLSPs(const ChannelCondition::LosConditionValue losCondition, Ptr< const ParamsTable > table3gpp) const
Generate large-scale parameters (LSPs) for the current channel state.
Ptr< const ChannelParams > GetParams(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const override
Looks for the channel params associated with the aMob and bMob pair in m_channelParamsMap.
~ThreeGppChannelModel() override
Destructor.
void UpdateClusterDelay(DoubleVector *clusterDelay, DoubleVector *delayConsistency, Ptr< const ThreeGppChannelParams > channelParams) const
Updates the cluster delays for the 3GPP channel model based on the channel parameters and consistency...
void PrecomputeAnglesSinCos(Ptr< const ThreeGppChannelParams > channelParams, std::vector< std::vector< std::pair< double, double > > > *cachedAngleSinCos) const
Precomputes the sine and cosine values for angles.
void TrimToBaseClusters(const uint8_t reducedClusterNumber, DoubleVector *delay, Double2DVector *angles, std::vector< std::vector< std::pair< double, double > > > *cachedAngleSincos, DoubleVector *alpha, DoubleVector *dTerm, DoubleVector *clusterPower) const
Trim per-cluster vectors back to the base (reduced) cluster set.
void CalcAttenuationOfBlockage(Double2DVector *nonSelfBlocking, DoubleVector *powerAttenuation, Ptr< const ThreeGppChannelParams > channelParams, const DoubleVector &clusterAOA, const DoubleVector &clusterZOA, Ptr< const ParamsTable > table3gpp) const
Calculates the per-cluster power attenuation (in dB) due to self-blocking and non-self-blocking effec...
std::unordered_map< uint64_t, Ptr< ThreeGppChannelParams > > m_channelParamsMap
map containing the common channel parameters per a pair of nodes, the key of this map is reciprocal a...
static std::pair< double, double > WrapAngles(double azimuthRad, double inclinationRad)
Wrap an (azimuth, inclination) angle pair in a valid range.
DoubleVector RemoveWeakClusters(DoubleVector *clusterPowers, DoubleVector *clusterDelays, const ChannelCondition::LosConditionValue losCondition, Ptr< const ParamsTable > table3gpp, const double kFactor, double *powerMax) const
Remove the clusters with less power.
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 with the aMob and bMob pair in m_channelMatrixMap.
static constexpr uint8_t Y_INDEX
index of the Y value in the m_nonSelfBlocking array
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 a pair of PhasedAntennaArray instances; the key of this m...
Ptr< UniformRandomVariable > m_uniformRv
uniform random variable
void UpdateLinkGeometry(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, double *distance2D, double *distance3D, double *endpointDisplacement2D, double *relativeDisplacement2D, Vector *lastPositionFirst, Vector *lastPositionSecond, Vector2D *lastRelativePosition2D) const
Update distance- and displacement-related values between two nodes.
void DoDispose() override
Destructor implementation.
void SetScenario(const std::string &scenario)
Sets the propagation scenario.
void GenerateClusterDelays(const double DS, Ptr< const ParamsTable > table3gpp, double *minTau, DoubleVector *clusterDelays) const
Generate the cluster delays.
static constexpr uint8_t X_INDEX
index of the X value in the m_nonSelfBlocking array
bool NewChannelMatrixNeeded(uint64_t channelMatrixKey, Ptr< const ThreeGppChannelParams > channelParams, Ptr< const PhasedArrayModel > aAntenna, Ptr< const PhasedArrayModel > bAntenna) const
Check if channel matrix needs update based on parameter changes or the update period expired.
void RandomRaysCoupling(Ptr< const ThreeGppChannelParams > channelParams, Double2DVector *rayAoaRadian, Double2DVector *rayAodRadian, Double2DVector *rayZoaRadian, Double2DVector *rayZodRadian) const
Randomly couples rays within each cluster by shuffling per-ray angles.
void SetChannelConditionModel(Ptr< ChannelConditionModel > model)
Set the channel condition model.
Ptr< UniformRandomVariable > m_uniformRvDoppler
Uniform random variable, used to compute the additional Doppler contribution.
static double CalculateCphi(const ChannelCondition::LosConditionValue losCondition, Ptr< const ParamsTable > table3gpp, const double kFactor)
Calculates the cPhi parameter for azimuth angular spread modeling.
uint16_t m_numNonSelfBlocking
number of non-self-blocking regions
std::string GetScenario() const
Returns the propagation scenario.
bool NewChannelParamsNeeded(const uint64_t channelParamsKey, Ptr< const ChannelCondition > condition, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const
Check if the channel params has to be updated.
void ShiftRayAnglesToUpdatedClusterMeans(Ptr< const ParamsTable > table3gpp, Ptr< const ThreeGppChannelParams > channelParams, const Double2DVector &prevClusterAngles, Double2DVector *rayAodRadian, Double2DVector *rayAoaRadian, Double2DVector *rayZodRadian, Double2DVector *rayZoaRadian) const
Shift per-ray angles to follow updated cluster mean angles while preserving intra-cluster offsets and...
void GenerateClusterPowers(const DoubleVector &clusterDelays, const double DS, Ptr< const ParamsTable > table3gpp, const DoubleVector &clusterShadowing, DoubleVector *clusterPowers) const
Generate cluster powers.
void GenerateClusterXnNLos(const uint8_t clusterNumber, std::vector< int > *clusterSign) const
Generate a random sign (+1 or -1) for each cluster for XN for NLOS computation for channel consistenc...
void GenerateClusterAngles(Ptr< const ThreeGppChannelParams > channelParams, const DoubleVector &clusterPowerForAngles, double powerMax, double cPhi, double cTheta, const LargeScaleParameters &lsps, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, Ptr< const ParamsTable > table3gpp, Double2DVector *clusterAngles) const
Generate cluster angles for a 3GPP channel model based on provided parameters.
double m_frequency
the operating frequency
void GenerateClusterShadowingTerm(Ptr< const ParamsTable > table3gpp, DoubleVector *clusterShadowing) const
Generate per-cluster shadowing terms (in dB) as specified by 3GPP TR 38.901.
double m_vScatt
Variable used to compute the additional Doppler contribution for the delayed (reflected) paths,...
void AdjustClusterDelaysForLosCondition(DoubleVector *clusterDelays, const uint8_t reducedClusterNumber, const double kFactor) const
Adjusts cluster delays for LOS channel condition based on the K-factor.
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 TypeId GetTypeId()
Get the type ID.
void UpdateChannelParameters(Ptr< ThreeGppChannelParams > channelParams, Ptr< const ChannelCondition > channelCondition, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const
Update channel parameters for spatial consistency using 3GPP TR 38.901 procedure A.
static constexpr uint8_t R_INDEX
index of the R value in the m_nonSelfBlocking array
virtual Ptr< ChannelMatrix > GetNewChannel(Ptr< const ThreeGppChannelParams > channelParams, Ptr< const ParamsTable > table3gpp, Ptr< const MobilityModel > sMob, 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 ...
Ptr< ThreeGppChannelParams > GenerateChannelParameters(Ptr< const ChannelCondition > channelCondition, Ptr< const ParamsTable > table3gpp, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const
Prepare 3gpp channel parameters among the nodes a and b.
static constexpr uint8_t PHI_INDEX
index of the PHI value in the m_nonSelfBlocking array
bool AntennaSetupChanged(Ptr< const PhasedArrayModel > aAntenna, Ptr< const PhasedArrayModel > bAntenna, Ptr< const ChannelMatrix > channelMatrix) const
Check if the channel matrix has to be updated due to changes in the number of antenna ports.
static constexpr uint8_t THETA_INDEX
index of the THETA value in the m_nonSelfBlocking array
void GenerateDopplerTerms(const uint8_t reducedClusterNumber, DoubleVector *dopplerTermAlpha, DoubleVector *dopplerTermD) const
Generate additional Doppler terms for delayed (reflected) paths.
double GetFrequency() const
Returns the center frequency.
void UpdateClusterAngles(Ptr< const ThreeGppChannelParams > channelParams, Double2DVector *clusterAngles, const DoubleVector &prevClusterDelay) const
Update cluster angles based on node mobility according to 3GPP TR 38.901.
Time m_updatePeriod
the channel update period enables spatial consistency, procedure A
Ptr< UniformRandomVariable > m_uniformRvShuffle
uniform random variable used to shuffle an array in GetNewChannel
virtual Ptr< const ParamsTable > GetThreeGppTable(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, Ptr< const ChannelCondition > channelCondition) const
Get the parameters needed to apply the channel generation procedure.
void UpdateClusterShadowingTerm(Ptr< const ParamsTable > table3gpp, DoubleVector *clusterShadowing, const double displacementLength) const
Update shadowing per cluster by using the normalized auto correlation function R, which is defined as...
Simulation virtual time values and global simulation resolution.
Definition nstime.h:95
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:408
double GetSeconds() const
Get an approximation of the time stored in this instance in the indicated unit.
Definition nstime.h:398
@ NS
nanosecond
Definition nstime.h:109
a unique identifier for an interface.
Definition type-id.h:50
TypeId SetGroupName(std::string groupName)
Set the group name.
Definition type-id.cc:1007
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:999
size_t GetNumPages() const
Definition val-array.h:387
size_t GetNumRows() const
Definition val-array.h:373
size_t GetNumCols() const
Definition val-array.h:380
a 2d vector
Definition vector.h:196
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition assert.h:55
#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:75
Ptr< const AttributeAccessor > MakePointerAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition pointer.h:250
Ptr< AttributeChecker > MakePointerChecker()
Create a PointerChecker for a type.
Definition pointer.h:273
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
#define NS_ABORT_MSG_IF(cond, msg)
Abnormal program termination if a condition is true, with a message.
Definition abort.h:97
#define NS_ABORT_IF(cond)
Abnormal program termination if a condition is true.
Definition abort.h:65
#define NS_LOG_UNCOND(msg)
Output the requested message unconditionally.
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:194
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:260
#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:253
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition log.h:267
Ptr< T > CreateObject(Args &&... args)
Create an object by type, with varying number of constructor parameters.
Definition object.h:627
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition object-base.h:35
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Definition ptr.h:454
Time Now()
create an ns3::Time instance which contains the current simulation time.
Definition simulator.cc:288
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1290
Definition first.py:1
Every class exported by the ns3 library is enclosed in the ns3 namespace.
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_office_LOS
The square root matrix for Indoor-Office LOS, which is generated using the Cholesky decomposition acc...
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
static const std::map< int, std::array< std::array< double, 6 >, 6 > > sqrtC_NTN_Rural_NLOS_S
The square root matrix for NTN Rural NLOS S Band, which is generated using the Cholesky decomposition...
Ptr< const AttributeChecker > MakeIntegerChecker()
Definition integer.h:99
static constexpr double kNoDisplacementEpsMeters
Small threshold (in meters) used to treat very small displacements as zero.
Ptr< const AttributeAccessor > MakeIntegerAccessor(T1 a1)
Definition integer.h:35
Table3gppParams
The enumerator used for code clarity when performing parameter assignment in GetThreeGppTable.
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNUrbanLOS
The nested map containing the 3GPP value tables for the NTN Urban LOS scenario.
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_NTN_Suburban_LOS
The square root matrix for NTN Suburban LOS, which is generated using the Cholesky decomposition acco...
constexpr double DEG2RAD
Conversion factor: degrees to radians.
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_UMi_NLOS
The square root matrix for UMi NLOS, which is generated using the Cholesky decomposition according to...
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_UMa_LOS
The square root matrix for UMa LOS, which is generated using the Cholesky decomposition according to ...
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_office_NLOS
The square root matrix for Indoor-Office NLOS, which is generated using the Cholesky decomposition ac...
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Definition nstime.h:1376
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNUrbanNLOS
The nested map containing the 3GPP value tables for the NTN Urban NLOS scenario.
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_UMa_NLOS
The square root matrix for UMa NLOS, which is generated using the Cholesky decomposition according to...
Ptr< const AttributeChecker > MakeDoubleChecker()
Definition double.h:82
static constexpr std::array offSetAlpha
The ray offset angles within a cluster, given for rms angle spread normalized to 1.
constexpr double kMaxConsistencyStepMeters
Maximum 2D displacement (in meters) allowed for a single channel-consistency update step.
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_RMa_LOS
The square root matrix for RMa LOS, which is generated using the Cholesky decomposition according to ...
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_NTN_Rural_LOS
The square root matrix for NTN Rural LOS, which is generated using the Cholesky decomposition accordi...
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_NTN_DenseUrban_NLOS
The square root matrix for NTN Dense Urban NLOS, which is generated using the Cholesky decomposition ...
static const std::unordered_map< int, double > cNlosTableTheta
Table 7.5-4 (38.901) Added case 2, 3 and 4 for the NTN according to table 6.7.2-1ab (28....
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:605
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_NTN_Urban_LOS
The square root matrix for NTN Urban LOS, which is generated using the Cholesky decomposition accordi...
void Shuffle(RND_ACCESS_ITER first, RND_ACCESS_ITER last, Ptr< UniformRandomVariable > rv)
Shuffle the elements in the range first to last.
Definition shuffle.h:48
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNSuburbanLOS
The nested map containing the 3GPP value tables for the NTN Suburban LOS scenario.
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNRuralNLOS
The nested map containing the 3GPP value tables for the NTN Rural NLOS scenario The outer key specifi...
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNDenseUrbanLOS
The nested map containing the 3GPP value tables for the NTN Dense Urban LOS scenario.
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_UMa_O2I
The square root matrix for UMa O2I, which is generated using the Cholesky decomposition according to ...
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_RMa_NLOS
The square root matrix for RMa NLOS, which is generated using the Cholesky decomposition according to...
double DegreesToRadians(double degrees)
converts degrees to radians
Definition angles.cc:28
static const std::map< int, std::array< std::array< double, 6 >, 6 > > sqrtC_NTN_Rural_NLOS_Ka
The square root matrix for NTN Rural NLOS Ka Band, which is generated using the Cholesky decompositio...
Ptr< const AttributeChecker > MakeStringChecker()
Definition string.cc:19
Ptr< const AttributeAccessor > MakeStringAccessor(T1 a1)
Definition string.h:46
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition boolean.h:70
Ptr< const AttributeAccessor > MakeDoubleAccessor(T1 a1)
Definition double.h:32
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_RMa_O2I
The square root matrix for RMa O2I, which is generated using the Cholesky decomposition according to ...
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNDenseUrbanNLOS
The nested map containing the 3GPP value tables for the NTN Dense Urban NLOS scenario.
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_NTN_DenseUrban_LOS
The square root matrix for NTN Dense Urban LOS, which is generated using the Cholesky decomposition a...
static constexpr std::array< std::array< double, 7 >, 7 > sqrtC_UMi_LOS
The square root matrix for UMi LOS, which is generated using the Cholesky decomposition according to ...
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_NTN_Suburban_NLOS
The square root matrix for NTN Suburban NLOS, which is generated using the Cholesky decomposition acc...
Ptr< T1 > ConstCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:593
double WrapTo2Pi(double a)
Wrap angle in [0, 2*M_PI).
Definition angles.cc:106
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNSuburbanNLOS
The nested map containing the 3GPP value tables for the NTN Suburban NLOS scenario.
static constexpr std::array< std::array< double, 6 >, 6 > sqrtC_UMi_O2I
The square root matrix for UMi O2I, which is generated using the Cholesky decomposition according to ...
static const std::unordered_map< int, double > cNlosTablePhi
According to table 7.5-6, only cluster number equal to 8, 10, 11, 12, 19 and 20 is valid.
static const std::map< int, std::array< std::array< double, 6 >, 6 > > sqrtC_NTN_Urban_NLOS
The square root matrix for NTN Urban NLOS, which is generated using the Cholesky decomposition accord...
double RadiansToDegrees(double radians)
converts radians to degrees
Definition angles.cc:34
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1396
static const std::map< std::string, std::map< int, std::array< float, 22 > > > NTNRuralLOS
The nested map containing the 3GPP value tables for the NTN Rural LOS scenario.
Large-scale channel parameters (3GPP TR 38.901).
double kFactor
Rician K-factor [dB] (used in LOS).
double ZSD
Zenith spread of departure [deg].
double ASD
Azimuth spread of departure [deg].
std::string dir