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
AttributeValue implementation for Boolean.
Definition boolean.h:26
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
AttributeValue implementation for Time.
Definition nstime.h:1375
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 AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition boolean.h:70
Ptr< const AttributeChecker > MakeDoubleChecker()
Definition double.h:82
Ptr< const AttributeAccessor > MakeDoubleAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition double.h:32
Ptr< const AttributeChecker > MakeIntegerChecker()
Definition integer.h:99
Ptr< const AttributeAccessor > MakeIntegerAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition integer.h:35
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
Ptr< const AttributeChecker > MakeStringChecker()
Definition string.cc:19
Ptr< const AttributeAccessor > MakeStringAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition string.h:46
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition nstime.h:1376
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1396
#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...
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...
static constexpr double kNoDisplacementEpsMeters
Small threshold (in meters) used to treat very small displacements as zero.
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...
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...
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...
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
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