A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
sionna-rt-channel-model.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2025 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
3 * Author: Amir Ashtari <aashtari@cttc.es>
4 * SPDX-License-Identifier: GPL-2.0-only
5 */
6
8
10#include "pybind11/numpy.h"
11#include "pybind11/pybind11.h"
12
13#include "ns3/antenna-model.h"
14#include "ns3/double.h"
15#include "ns3/log.h"
16#include "ns3/mobility-model.h"
17#include "ns3/node.h"
18#include "ns3/phased-array-model.h"
19#include "ns3/pointer.h"
20#include "ns3/simulator.h"
21#include "ns3/string.h"
22#include "ns3/three-gpp-antenna-model.h"
23#include "ns3/uniform-planar-array.h"
24
25#include <filesystem>
26namespace py = pybind11;
27
28namespace ns3
29{
30
31NS_LOG_COMPONENT_DEFINE("SionnaRtChannelModel");
32
34
35/// Conversion factor: degrees to radians
36static constexpr double DEG2RAD = M_PI / 180.0;
37
42
47
48void
55
58{
59 static TypeId tid =
60 TypeId("ns3::SionnaRtChannelModel")
61 .SetGroupName("Spectrum")
63 .AddConstructor<SionnaRtChannelModel>()
64 .AddAttribute("Frequency",
65 "The operating Frequency in Hz",
66 DoubleValue(500.0e6),
70 .AddAttribute("UpdatePeriod",
71 "Specify the channel coherence time",
75 .AddAttribute("OutputImageDirectory",
76 "Output directory for rendered Sionna RT images",
77 StringValue("images"),
80 .AddAttribute("OutputImageName",
81 "Base file name for rendered Sionna RT images",
82 StringValue("screenshot"),
85 .AddAttribute("IsImageRenderedEnabled",
86 "Enable or disable scene image rendering",
87 BooleanValue(false),
90 .AddAttribute("CameraPosition",
91 "Camera position for scene rendering",
92 VectorValue(Vector(0.0, 0.0, 300.0)),
95 .AddAttribute("CameraLookAt",
96 "Camera look-at position for scene rendering",
97 VectorValue(Vector(0.0, 0.0, 0.0)),
100 .AddAttribute("IsMergeShapeEnabled",
101 "Enable or disable MEGE shape loading",
102 BooleanValue(false),
105 .AddAttribute(
106 "MaxNumberOfPaths",
107 "The maximum number of paths to be generated by the Sionna RT path solver",
108 DoubleValue(90),
112 .AddAttribute("NormalizeDelays",
113 "Enable normalization of path delays in Sionna RT CIR output",
114 BooleanValue(true),
118 .AddAttribute("Scenario",
119 "The Sionna RT scenario name used to create scenes",
120 StringValue("munich"),
124 return tid;
125}
126
127void
129{
130 NS_LOG_FUNCTION(this);
131 m_sceneConfigs.mergeShapes = cond;
132}
133
134void
136{
137 NS_LOG_FUNCTION(this);
138 NS_ASSERT_MSG(f >= 500.0e6 && f <= 100.0e9,
139 "Frequency should be between 0.5 and 100 GHz but is " << f);
140 NS_LOG_INFO("Setting operating frequency to " << f << " Hz");
141 m_sceneConfigs.frequency = f;
142}
143
144void
146{
147 NS_LOG_FUNCTION(this);
148 NS_LOG_INFO("Setting max number of paths to " << p);
150}
151
152void
154{
155 NS_LOG_FUNCTION(this);
156 NS_LOG_INFO("Setting normalize delays to " << (cond ? "true" : "false"));
157 m_normalizeDelays = cond;
158}
159
160bool
166
167double
169{
170 NS_LOG_FUNCTION(this);
171 NS_LOG_INFO("Getting max number of paths: " << m_maxNumberOfPaths);
172 return m_maxNumberOfPaths;
173}
174
175double
177{
178 NS_LOG_FUNCTION(this);
179 return m_sceneConfigs.frequency;
180}
181
182bool
185 Ptr<const ChannelMatrix> channelMatrix) const
186{
187 // This allows changing the number of antenna ports during execution,
188 // which is used by nr's initial association.
189 size_t sAntNumElems = aAntenna->GetNumElems();
190 size_t uAntNumElems = bAntenna->GetNumElems();
191 size_t chanNumRows = channelMatrix->m_channel.GetNumRows();
192 size_t chanNumCols = channelMatrix->m_channel.GetNumCols();
193
194 // Log a description of what changed if any
195 if (((uAntNumElems != chanNumRows) || (sAntNumElems != chanNumCols)) &&
196 ((uAntNumElems != chanNumCols) || (sAntNumElems != chanNumRows)))
197 {
198 NS_LOG_INFO("Antenna setup changed: aAntenna elems="
199 << sAntNumElems << " bAntenna elems=" << uAntNumElems << " channel dims=("
200 << chanNumRows << ", " << chanNumCols << ")");
201 }
202 bool changed = (((uAntNumElems != chanNumRows) || (sAntNumElems != chanNumCols)) &&
203 ((uAntNumElems != chanNumCols) || (sAntNumElems != chanNumRows))) ||
204 aAntenna->IsChannelOutOfDate(bAntenna);
205
206 if (changed)
207 {
208 NS_LOG_DEBUG("AntennaSetupChanged evaluated to true");
209 }
210 return changed;
211}
212
213bool
215{
216 NS_LOG_FUNCTION(this);
217
218 bool update = false;
219
220 // if the coherence time is over the channel has to be updated
221 if (!m_updatePeriod.IsZero() &&
222 Simulator::Now() - channelMatrix->m_generatedTime > m_updatePeriod)
223 {
224 NS_LOG_DEBUG("Generation time " << channelMatrix->m_generatedTime.As(Time::NS) << " now "
225 << Now().As(Time::NS));
226 update = true;
227 }
228
229 return update;
230}
231
237{
238 NS_LOG_FUNCTION(this);
239
240 // Compute the channel matrix key. The key is reciprocal, i.e., key (a, b) = key (b, a)
241 uint64_t channelMatrixKey = GetKey(aAntenna->GetId(), bAntenna->GetId());
242
243 bool notFound = false;
244 bool update = false;
245 Ptr<ChannelMatrix> channelMatrix;
246
247 if (m_channelMatrixMap.find(channelMatrixKey) != m_channelMatrixMap.end())
248 {
249 // channel matrix present in the map
250 NS_LOG_DEBUG("channel matrix present in the map");
251 channelMatrix = m_channelMatrixMap[channelMatrixKey];
252 update = ChannelMatrixNeedsUpdate(channelMatrix);
253 update |= AntennaSetupChanged(aAntenna, bAntenna, channelMatrix);
254 if (update)
255 {
256 NS_LOG_INFO("Cached channel matrix marked for update (stale or antenna changed)");
257 }
258 }
259 else
260 {
261 NS_LOG_DEBUG("channel matrix not found");
262 notFound = true;
263 }
264
265 // If the channel is not present in the map or if it has to be updated
266 // generate a new realization
267 if (notFound || update)
268 {
269 // channel matrix not found or has to be updated, generate a new one
270 py::module_ rt = py::module_::import("sionna.rt");
271 NS_LOG_INFO("Building scene for antenna pair ("
272 << aAntenna->GetId() << ", " << bAntenna->GetId() << ") and nodes ("
273 << aMob->GetObject<Node>()->GetId() << ", " << bMob->GetObject<Node>()->GetId()
274 << ")");
275
276 py::object scene = CreateScene(rt, aMob, bMob, aAntenna, bAntenna);
277 py::object paths = CalculatePaths(rt, scene);
278
279 channelMatrix = GetNewChannel(paths, aMob, bMob, aAntenna, bAntenna);
280 channelMatrix->m_antennaPair =
281 std::make_pair(aAntenna->GetId(),
282 bAntenna->GetId()); // save antenna pair, with the exact order of s and u
283 // antennas at the moment of the channel generation
284
285 // store or replace the channel matrix in the channel map
286 m_channelMatrixMap[channelMatrixKey] = channelMatrix;
287
288 // each time a new channel is created, also compute the channel params
289 auto channelParams = CalculateChannelParamsFromPaths(paths, aMob, bMob);
290 uint64_t channelParamsKey =
291 GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
292 m_channelParamsMap[channelParamsKey] = channelParams;
293 }
294
295 return channelMatrix;
296}
297
300 const Ptr<const MobilityModel> sMob,
301 const Ptr<const MobilityModel> uMob,
303 Ptr<const PhasedArrayModel> uAntenna) const
304{
305 NS_LOG_FUNCTION(this);
306 NS_LOG_DEBUG("Creating new ChannelMatrix from path information");
308
309 Ptr<ChannelMatrix> channelMatrix = CreateNewMatrixChannel(hUsn, sMob, uMob, sAntenna, uAntenna);
310 NS_LOG_DEBUG("Successfully created new ChannelMatrix");
311 return channelMatrix;
312}
313
316 const Ptr<const MobilityModel> sMob,
317 const Ptr<const MobilityModel> uMob,
319 Ptr<const PhasedArrayModel> uAntenna) const
320{
321 // create a channel matrix instance
323 channelMatrix->m_generatedTime = Simulator::Now();
324 // save in which order is generated this matrix
325 channelMatrix->m_nodeIds =
326 std::make_pair(sMob->GetObject<Node>()->GetId(), uMob->GetObject<Node>()->GetId());
327
328 channelMatrix->m_channel = hUsn;
329 return channelMatrix;
330}
331
332void
333SionnaRtChannelModel::SetScenario(const std::string& scenario)
334{
335 NS_LOG_FUNCTION(this);
336
337 // Check if scenario is an XML file path (ends with .xml)
338 bool isXmlFile = (scenario.length() >= 4 && scenario.substr(scenario.length() - 4) == ".xml");
339
340 if (!isXmlFile && builtInSceneSionna.count(scenario) == 0)
341 {
342 NS_FATAL_ERROR("Unsupported Sionna scenario name: '"
343 << scenario << "'. Allowed scenarios are: "
344 << "box, box_one_screen, box_two_screens, double_reflector, "
345 << "triple_reflector, simple_wedge, Etoile, floor_wall, Florence, "
346 << "Munich, san_francisco, simple_street_canyon, "
347 << "simple_street_canyon_with_cars, simple_reflector, "
348 << "or a custom XML file path ending with .xml");
349 }
350
351 NS_LOG_INFO("Changing scenario to: " << scenario
352 << (isXmlFile ? " (XML file)" : " (built-in)"));
353
354 m_sceneConfigs.sceneName = scenario;
355}
356
357std::string
359{
360 NS_LOG_FUNCTION(this);
361 return m_sceneConfigs.sceneName;
362}
363
364py::object
365SionnaRtChannelModel::LoadScene(const py::module_ rt) const
366{
367 NS_LOG_FUNCTION(this);
368 NS_LOG_INFO("Loading Sionna RT scene: " << m_sceneConfigs.sceneName << " mergeShapes="
369 << (m_sceneConfigs.mergeShapes ? "true" : "false"));
370
371 py::object scene;
372
373 // Check if scenario is an XML file path (ends with .xml)
374 bool isXmlFile =
375 (m_sceneConfigs.sceneName.length() >= 4 &&
376 m_sceneConfigs.sceneName.substr(m_sceneConfigs.sceneName.length() - 4) == ".xml");
377
378 if (isXmlFile)
379 {
380 // Load from XML file using Scene constructor
381 NS_LOG_DEBUG("Loading scene from XML file: " << m_sceneConfigs.sceneName);
382 py::object Scene = rt.attr("Scene");
383 scene = Scene(py::arg("filename") = m_sceneConfigs.sceneName,
384 py::arg("merge_shapes") = m_sceneConfigs.mergeShapes);
385 }
386 else
387 {
388 // Load built-in scenario using load_scene
389 NS_LOG_DEBUG("Loading built-in Sionna RT scenario: " << m_sceneConfigs.sceneName);
390 py::object load_scene = rt.attr("load_scene");
391 py::object scenes = rt.attr("scene");
392 py::object scene_name = scenes.attr(m_sceneConfigs.sceneName.c_str());
393 scene = load_scene(scene_name, py::arg("merge_shapes") = m_sceneConfigs.mergeShapes);
394 }
395
396 NS_LOG_DEBUG("Scene object loaded from Sionna RT");
397 return scene;
398}
399
400std::string
402{
403 NS_LOG_FUNCTION(this);
404 std::string pattern;
405
406 if (DynamicCast<const ThreeGppAntennaModel>(element) != nullptr)
407 {
408 pattern = "tr38901";
409 }
410 else
411 {
412 pattern = "iso";
413 }
414 NS_LOG_DEBUG("Antenna element pattern: " << pattern);
415 return pattern;
416}
417
418std::string
420 const uint8_t numpol) const
421{
422 NS_LOG_FUNCTION(this);
423 std::string polarization = "V";
424 if (numpol == 1)
425 {
426 if (PolSlantAngle == 0.0)
427 {
428 polarization = "V";
429 }
430 else if (PolSlantAngle == M_PI / 2)
431 {
432 polarization = "H";
433 }
434 }
435 else
436 {
437 if (PolSlantAngle == 0.0)
438 {
439 polarization = "VH";
440 }
441 else
442 {
443 polarization = "cross";
444 }
445 }
446 return polarization;
447}
448
449py::object
451 Ptr<const PhasedArrayModel> antenna) const
452{
453 NS_LOG_FUNCTION(this);
454 py::object PlanarArray = rt.attr("PlanarArray");
455 auto upa = DynamicCast<const UniformPlanarArray>(antenna);
456 if (!upa)
457 {
458 NS_LOG_WARN("Antenna is not UniformPlanarArray; using defaults 1x1,tr38901, V-pol.");
459 return PlanarArray(py::arg("num_rows") = 1,
460 py::arg("num_cols") = 1,
461 py::arg("pattern") = "tr38901",
462 py::arg("polarization") = "V");
463 }
464
465 return PlanarArray(py::arg("num_rows") = upa->GetNumRows(),
466 py::arg("num_cols") = upa->GetNumColumns(),
467 py::arg("vertical_spacing") = upa->GetAntennaVerticalSpacing(),
468 py::arg("horizontal_spacing") = upa->GetAntennaHorizontalSpacing(),
469 py::arg("pattern") = GetAntennaElementPattern(upa->GetAntennaElement()),
470 py::arg("polarization") =
471 GetPolarizationFromPolSlantAngle(upa->GetPolSlant(), upa->GetNumPols()));
472}
473
476{
477 NS_LOG_FUNCTION(this);
478
479 // Compute the channel key. The key is reciprocal, i.e., key (a, b) = key (b, a)
480 uint64_t channelParamsKey =
481 GetKey(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
482
483 if (m_channelParamsMap.find(channelParamsKey) != m_channelParamsMap.end())
484 {
485 return m_channelParamsMap.find(channelParamsKey)->second;
486 }
487
488 NS_LOG_WARN("Channel params map not found. Returning a nullptr.");
489 return nullptr;
490}
491
495 Ptr<const MobilityModel> bMob) const
496{
497 NS_LOG_FUNCTION(this);
498 NS_LOG_DEBUG("Calculating ChannelParams from paths");
501
502 channelParams->m_nodeIds =
503 std::make_pair(aMob->GetObject<Node>()->GetId(), bMob->GetObject<Node>()->GetId());
504
505 py::module_ np = py::module_::import("numpy");
506
507 channelParams->m_generatedTime = Simulator::Now();
508
509 channelParams->m_delay = CalculateTauFromPaths(np, paths);
510 channelParams->m_doppler = CalculateDopplerFromPaths(np, paths);
511 channelParams->m_angle.clear();
512 channelParams->m_angle.push_back(CalculateAnglesFromPaths(np, paths, "AOA")); // AOA
513 channelParams->m_angle.push_back(CalculateAnglesFromPaths(np, paths, "ZOA")); // ZOA
514 channelParams->m_angle.push_back(CalculateAnglesFromPaths(np, paths, "AOD")); // AOD
515 channelParams->m_angle.push_back(CalculateAnglesFromPaths(np, paths, "ZOD")); // ZOD
516
517 // Precompute angles sincos
518 channelParams->m_cachedAngleSincos.resize(channelParams->m_angle.size());
519 for (size_t direction = 0; direction < channelParams->m_angle.size(); direction++)
520 {
521 channelParams->m_cachedAngleSincos[direction].resize(
522 channelParams->m_angle[direction].size());
523 for (size_t cluster = 0; cluster < channelParams->m_angle[direction].size(); cluster++)
524 {
525 channelParams->m_cachedAngleSincos[direction][cluster] = {
526 sin(channelParams->m_angle[direction][cluster] * DEG2RAD),
527 cos(channelParams->m_angle[direction][cluster] * DEG2RAD)};
528 }
529 }
530
531 return channelParams;
532}
533
534py::object
536 const Ptr<const MobilityModel> sMob,
537 const Ptr<const MobilityModel> uMob,
539 Ptr<const PhasedArrayModel> uAntenna) const
540{
541 NS_LOG_FUNCTION(this);
542 py::object scene = LoadScene(rt);
543 NS_LOG_DEBUG("Installing antenna arrays on scene (tx/rx)");
544 scene.attr("tx_array") = MakePlannarArray(rt, sAntenna);
545 scene.attr("rx_array") = MakePlannarArray(rt, uAntenna);
546 // --- Create and add a TX/RX
547
548 py::object Transmitter = rt.attr("Transmitter");
549 py::object Receiver = rt.attr("Receiver");
550 Vector pos = sMob->GetPosition();
551 Vector v = sMob->GetVelocity();
552 auto upa = DynamicCast<const UniformPlanarArray>(sAntenna);
553 Vector posRx = uMob->GetPosition();
554 py::object tx =
555 Transmitter(py::arg("name") = "tx",
556 py::arg("position") = py::make_tuple(pos.x, pos.y, pos.z),
557 py::arg("orientation") = py::make_tuple(upa->GetAlpha(), upa->GetBeta(), 0.0),
558 py::arg("velocity") = py::make_tuple(v.x, v.y, v.z));
559
560 Vector vRx = uMob->GetVelocity();
561 auto upau = DynamicCast<const UniformPlanarArray>(uAntenna);
562 py::object rx =
563 Receiver(py::arg("name") = "rx",
564 py::arg("position") = py::make_tuple(posRx.x, posRx.y, posRx.z),
565 py::arg("orientation") = py::make_tuple(upau->GetAlpha(), upau->GetBeta(), 0.0),
566 py::arg("velocity") = py::make_tuple(vRx.x, vRx.y, vRx.z));
567
568 scene.attr("add")(tx);
569 scene.attr("add")(rx);
570
571 // tx.attr("look_at")(rx);
572 scene.attr("frequency") = m_sceneConfigs.frequency;
573 NS_LOG_INFO("Scene created: frequency=" << m_sceneConfigs.frequency << " tx@" << pos << " rx@"
574 << posRx);
575
576 return scene;
577}
578
579void
581{
582 m_RtPathSolverConfig = configs;
583 NS_LOG_INFO("RtPathSolverConfig set: maxDepth="
584 << configs.maxDepth << " los=" << configs.los << " specular_reflection="
585 << configs.specularReflection << " diffuse_reflection=" << configs.diffuseReflection
586 << " refraction=" << configs.refraction << " diffraction=" << configs.diffraction
587 << " edgeDiffraction=" << configs.edgeDiffraction
588 << " synthetic_array=" << configs.syntheticArray << " seed=" << configs.seed);
589}
590
596
597py::object
598SionnaRtChannelModel::CalculatePaths(const py::module_ rt, const py::object scene)
599{
600 NS_LOG_FUNCTION(this);
601 py::object PathSolver = rt.attr("PathSolver");
602 py::object psolver = PathSolver();
603
604 static std::string rt_version = py::str(rt.attr("__version__"));
605 NS_LOG_INFO("Using Sionna RT version: " << rt_version);
606
607 py::object paths;
608 NS_LOG_DEBUG("Invoking Sionna RT PathSolver with configured options");
609
610 if (rt_version == "1.1.0")
611 {
612 NS_LOG_DEBUG("Using Sionna RT PathSolver API == 1.1.0");
613 paths = psolver(py::arg("scene") = scene,
614 py::arg("max_depth") = m_RtPathSolverConfig.maxDepth,
615 py::arg("los") = m_RtPathSolverConfig.los,
616 py::arg("specular_reflection") = m_RtPathSolverConfig.specularReflection,
617 py::arg("diffuse_reflection") = m_RtPathSolverConfig.diffuseReflection,
618 py::arg("refraction") = m_RtPathSolverConfig.refraction,
619 py::arg("synthetic_array") = m_RtPathSolverConfig.syntheticArray,
620 py::arg("seed") = m_RtPathSolverConfig.seed);
621 }
622 else
623 {
624 NS_LOG_DEBUG("Using Sionna RT PathSolver API >= 1.2.0 used");
625 paths = psolver(py::arg("scene") = scene,
626 py::arg("max_depth") = m_RtPathSolverConfig.maxDepth,
627 py::arg("los") = m_RtPathSolverConfig.los,
628 py::arg("specular_reflection") = m_RtPathSolverConfig.specularReflection,
629 py::arg("diffuse_reflection") = m_RtPathSolverConfig.diffuseReflection,
630 py::arg("refraction") = m_RtPathSolverConfig.refraction,
631 py::arg("diffraction") = m_RtPathSolverConfig.diffraction,
632 py::arg("edge_diffraction") = m_RtPathSolverConfig.edgeDiffraction,
633 py::arg("synthetic_array") = m_RtPathSolverConfig.syntheticArray,
634 py::arg("seed") = m_RtPathSolverConfig.seed);
635 }
636
637 NS_LOG_INFO("Path computation finished");
638
639 size_t num_paths = GetNumberOfPathsFromCir(paths);
640 NS_LOG_INFO("Number of generated paths: " << num_paths);
641 if (DoesNumberOfPathsExceedMaximum(num_paths))
642 {
643 NS_LOG_WARN("Number of generated paths (" << num_paths
644 << ") exceeds the configured maximum number of "
645 "paths ("
647 << "). The excess paths will be ignored.");
648
649 m_RtPathSolverConfig.maxDepth -= 1;
650 paths = CalculatePaths(rt, scene);
651 }
652
653 if (m_isImageRendered && num_paths > 0)
654 {
655 NS_LOG_DEBUG("Rendering scene image to disk");
656 SceneRenderImageToFile(rt, paths, scene);
657 }
658 return paths;
659}
660
661void
663 const py::object paths,
664 const py::object scene) const
665{
666 static int count = 0;
667 if (!std::filesystem::exists(m_outputImageDirectory + "/"))
668 {
669 std::filesystem::create_directories(m_outputImageDirectory + "/");
670 }
671
672 py::object camera = rt.attr("Camera");
673 py::object Mycamera = camera(
674 py::arg("position") =
675 py::make_tuple(m_cameraPosition.x, m_cameraPosition.y, m_cameraPosition.z),
676 py::arg("look_at") = py::make_tuple(m_cameraLookAt.x, m_cameraLookAt.y, m_cameraLookAt.z));
677 py::object renderer = scene.attr("render_to_file");
678 py::object image =
679 renderer(py::arg("camera") = Mycamera,
680 py::arg("filename") = m_outputImageDirectory + "/" + m_outputImageName +
681 std::to_string(count) + ".png",
682 py::arg("paths") = paths);
683 NS_LOG_INFO("Scene image rendered");
684 count += 1;
685}
686
689{
690 NS_LOG_FUNCTION(this);
691 py::module_ np = py::module_::import("numpy");
692
693 py::tuple cir = paths.attr("cir")(py::arg("normalize_delays") = m_normalizeDelays,
694 py::arg("out_type") = "numpy");
695
696 py::object a_py = np.attr("asarray")(cir[0], py::arg("dtype") = np.attr("complex128"));
697 py::buffer_info a_info = py::buffer(a_py).request();
698 auto* data = static_cast<std::complex<double>*>(a_info.ptr);
699
700 // Generate channel coefficients for each cluster n and each receiver
701 // and transmitter element pair u,s.
702 // where n is cluster index, u and s are receive and transmit antenna element.
703 // Create the Complex3DVector and copy data a_info.shape[1]:num_ant_rx,
704 // a_info.shape[3]:num_ant_tx, a_info.shape[4]:num_paths
705 Complex3DVector hUsn(a_info.shape[1], a_info.shape[3], a_info.shape[4]);
706 NS_LOG_DEBUG("CIR numpy buffer shapes: shape[1]="
707 << a_info.shape[1] << " shape[3]=" << a_info.shape[3]
708 << " shape[4]=" << a_info.shape[4] << " total size=" << a_info.size);
709
710 for (size_t cIndex = 0; cIndex < hUsn.GetNumPages(); cIndex++)
711 {
712 for (size_t rowIdx = 0; rowIdx < hUsn.GetNumRows(); rowIdx++)
713 {
714 for (size_t colIdx = 0; colIdx < hUsn.GetNumCols(); colIdx++)
715 {
716 size_t idx = (cIndex * hUsn.GetNumRows() + rowIdx) * hUsn.GetNumCols() + colIdx;
717 hUsn(rowIdx, colIdx, cIndex) = data[idx];
718 }
719 }
720 }
721
722 return hUsn;
723}
724
726SionnaRtChannelModel::CalculateTauFromPaths(const py::module_ np, py::object paths) const
727{
728 NS_LOG_FUNCTION(this);
729 // Convert to numpy float64 array
730 py::tuple cir = paths.attr("cir")(py::arg("normalize_delays") = m_normalizeDelays,
731 py::arg("out_type") = "numpy");
732 py::object tau_py = np.attr("asarray")(cir[1], py::arg("dtype") = np.attr("float64"));
733 // Buffer info
734 py::buffer_info tau_info = py::buffer(tau_py).request();
735 auto* data = static_cast<double*>(tau_info.ptr);
736 // Shape of array in case syntheticarray differs in sionna
737 if (m_RtPathSolverConfig.syntheticArray)
738 {
739 return DoubleVector(data, data + tau_info.shape[2]);
740 }
741 else
742 {
743 return DoubleVector(data, data + tau_info.shape[4]);
744 }
745}
746
747std::vector<double>
748SionnaRtChannelModel::CalculateDopplerFromPaths(const py::module_ np, py::object paths) const
749{
750 NS_LOG_FUNCTION(this);
751 py::tuple doppler = paths.attr("doppler");
752 // Convert doppler (DrJit tensor) to numpy complex128 array
753 py::object d_py = np.attr("asarray")(doppler, py::arg("dtype") = np.attr("float64"));
754
755 // Buffer info
756 py::buffer_info d_info = py::buffer(d_py).request();
757 auto* data = static_cast<double*>(d_info.ptr);
758
759 // Shape of array in case syntheticarray differs in sionna
760 size_t dopplerSize =
761 (m_RtPathSolverConfig.syntheticArray) ? size_t(d_info.shape[2]) : size_t(d_info.shape[4]);
762
763 std::vector<double> dopplerVector(dopplerSize);
764 for (size_t i = 0; i < dopplerSize; i++)
765 {
766 dopplerVector[i] = data[i];
767 }
768
769 return dopplerVector;
770}
771
774 py::object paths,
775 std::string Angle) const
776{
777 py::object angle_py;
778 if (Angle == "ZOA")
779 {
780 angle_py = paths.attr("theta_r");
781 }
782 else if (Angle == "ZOD")
783 {
784 angle_py = paths.attr("theta_t");
785 }
786 else if (Angle == "AOA")
787 {
788 angle_py = paths.attr("phi_r");
789 }
790 else if (Angle == "AOD")
791 {
792 angle_py = paths.attr("phi_t");
793 }
794
795 else
796 {
797 NS_LOG_ERROR("Angle type not recognized. Use ZOA, or ZOD, or AOA, or AOD.");
798 }
799
800 // Convert to numpy float64 array
801 py::object angle_np = np.attr("asarray")(angle_py, py::arg("dtype") = py::dtype("float64"));
802 // Buffer info
803
804 py::buffer_info angle_info = py::buffer(angle_np).request();
805 auto* data = static_cast<double*>(angle_info.ptr);
806
807 // Shape of array in case syntheticarray differs in sionna
808 size_t angelSize = (m_RtPathSolverConfig.syntheticArray) ? size_t(angle_info.shape[2])
809 : size_t(angle_info.shape[4]);
810
812 for (size_t i = 0; i < angelSize; ++i)
813 {
814 angleVector.push_back(data[i]);
815 }
816 NS_LOG_DEBUG("Extracted " << angleVector.size() << " values for angle type " << Angle);
817
818 return angleVector;
819}
820
821size_t
823{
824 NS_LOG_FUNCTION(this);
825 py::module_ np = py::module_::import("numpy");
826
827 py::object angle_np =
828 np.attr("asarray")(paths.attr("phi_r"), py::arg("dtype") = py::dtype("float64"));
829 py::buffer_info angle_info = py::buffer(angle_np).request();
830
831 // Get number of paths from shape[4] (or shape[2] if synthetic array)
832 size_t num_paths =
833 m_RtPathSolverConfig.syntheticArray ? angle_info.shape[2] : angle_info.shape[4];
834 return num_paths;
835}
836
837bool
839{
840 NS_LOG_FUNCTION(this << numPaths);
841 return (numPaths > static_cast<size_t>(m_maxNumberOfPaths));
842}
843
844} // namespace ns3
uint32_t v
Receiver application.
This class can be used to hold variables of floating point type such as 'double' or 'float'.
Definition double.h:31
This is an interface for a channel model that can be described by a channel matrix,...
std::vector< double > DoubleVector
Type definition for vectors of doubles.
ComplexMatrixArray Complex3DVector
Create an alias for 3D complex vectors.
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
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
High-level interface for generating MIMO channel matrices using Sionna RT.
static TypeId GetTypeId()
Get the ns-3 TypeId for this class.
std::vector< double > CalculateDopplerFromPaths(const py::module_ np, py::object paths) const
Extract Doppler shifts per path from the Python paths object.
bool DoesNumberOfPathsExceedMaximum(size_t numPaths) const
Determine if the path count exceeds the configured maximum threshold.
void SetMergeShapeEnable(bool cond)
Enable or disable shape merging when loading scenes from sionna.rt.
double m_maxNumberOfPaths
Maximum number of paths to be generated by the path solver.
virtual Ptr< ChannelMatrix > GetNewChannel(py::object paths, const Ptr< const MobilityModel > sMob, const Ptr< const MobilityModel > uMob, Ptr< const PhasedArrayModel > sAntenna, Ptr< const PhasedArrayModel > uAntenna) const
Compute a new channel matrix for the given node pair using Sionna RT.
Vector m_cameraPosition
Camera position/look-at used by the scene rendering pipeline.
std::string m_outputImageDirectory
Output directory.
RtPathSolverConfig GetRtPathSolverConfig() const
Retrieve the current path solver configuration.
bool GetNormalizeDelays() const
Query whether path delay normalization is enabled.
Time m_updatePeriod
Channel update period used to decide whether to re-run path computations.
MatrixBasedChannelModel::DoubleVector CalculateAnglesFromPaths(const py::module_ np, py::object paths, std::string Angle) const
Extract angular measurements (AOD/AOA/ZOA/ZOD) from the paths.
~SionnaRtChannelModel() override
Destructor.
Complex3DVector CalculateCirFromPaths(const py::object paths) const
Convert calculated paths into channel impulse responses (CIRs).
void SetRtPathSolverConfig(RtPathSolverConfig configs)
Configure the Sionna RT path solver behavior.
RtPathSolverConfig m_RtPathSolverConfig
Path solver configuration used when computing paths.
void SetNormalizeDelays(bool cond)
Enable or disable normalization of path delays in Sionna RT.
RtSceneConfig m_sceneConfigs
High-level scene configuration.
std::unordered_map< uint64_t, Ptr< SionnaRtChannelParams > > m_channelParamsMap
map containing the common channel parameters per pair of nodes, the key of this map is reciprocal and...
double GetMaxNumberOfPaths() const
Get the configured maximum number of propagation paths.
bool ChannelMatrixNeedsUpdate(Ptr< const ChannelMatrix > channelMatrix) const
Determine whether an existing ChannelMatrix must be updated.
std::string GetAntennaElementPattern(Ptr< const AntennaModel > element) const
Return a string describing the antenna element pattern for Sionna.
Vector m_cameraLookAt
Camera look-at point for scene rendering.
bool m_isImageRendered
Control for whether we should render a scene image.
py::object MakePlannarArray(const py::module_ rt, Ptr< const PhasedArrayModel > antenna) const
Build a Sionna PlanarArray description from an ns-3 PhasedArrayModel.
bool m_normalizeDelays
Whether Sionna RT should normalize path delays in CIR output.
std::string m_outputImageName
Output image rendering options (file name/directory).
double GetFrequency() const
Get the configured center frequency.
py::object LoadScene(const py::module_ rt) const
Load a Sionna RT scene using the configured scenario name.
py::object CalculatePaths(const py::module_ rt, const py::object scene)
Compute propagation paths between transmitter and receiver.
void SetFrequency(double f)
Set the center frequency used by the model and scene factories.
bool AntennaSetupChanged(Ptr< const PhasedArrayModel > aAntenna, Ptr< const PhasedArrayModel > bAntenna, Ptr< const ChannelMatrix > channelMatrix) const
Check whether antenna configuration changes require regenerating the channel matrix.
Ptr< const ChannelMatrix > GetChannel(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob, Ptr< const PhasedArrayModel > aAntenna, Ptr< const PhasedArrayModel > bAntenna) override
Retrieve or generate the channel matrix for the node pair.
py::object CreateScene(const py::module_ rt, const Ptr< const MobilityModel > sMob, const Ptr< const MobilityModel > uMob, Ptr< const PhasedArrayModel > sAntenna, Ptr< const PhasedArrayModel > uAntenna) const
Create a sionna.rt scene object for the provided endpoints and antennas.
std::unordered_map< uint64_t, Ptr< ChannelMatrix > > m_channelMatrixMap
map containing the channel realizations per pair of PhasedAntennaArray instances, the key of this map...
Ptr< MatrixBasedChannelModel::ChannelMatrix > CreateNewMatrixChannel(const Complex3DVector hUsn, const Ptr< const MobilityModel > sMob, const Ptr< const MobilityModel > uMob, Ptr< const PhasedArrayModel > sAntenna, Ptr< const PhasedArrayModel > uAntenna) const
Create a new ChannelMatrix object wrapping a precomputed CIR tensor.
MatrixBasedChannelModel::DoubleVector CalculateTauFromPaths(const py::module_ np, py::object paths) const
Extract delays (tau) per path from RS paths object.
void SetMaxNumberOfPaths(double p)
Set the maximum number of propagation paths to retain.
void SetScenario(const std::string &scenario)
Set the propagation scenario name used by sionna.rt scene factories.
std::string GetPolarizationFromPolSlantAngle(const double polSlantAngle, const uint8_t numpol) const
Convert a polarization slant angle into Sionna RT string representation.
std::string GetScenario() const
Return the configured propagation scenario name.
Ptr< SionnaRtChannelParams > CalculateChannelParamsFromPaths(const py::object paths, Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const
Build ChannelParams metadata from Sionna RT paths object.
void DoDispose() override
Release references and perform cleanup.
size_t GetNumberOfPathsFromCir(const py::object paths) const
Extract the number of propagation paths from the CIR tensor.
Ptr< const ChannelParams > GetParams(Ptr< const MobilityModel > aMob, Ptr< const MobilityModel > bMob) const override
Get channel parameters (path info) for a node pair if available.
void SceneRenderImageToFile(const py::module_ rt, const py::object paths, const py::object scene) const
Render a visualization of the scene and paths to a PNG image file.
Hold variables of type string.
Definition string.h:45
@ NS
nanosecond
Definition nstime.h:109
a unique identifier for an interface.
Definition type-id.h:50
TypeId SetGroupName(std::string groupName)
Set the group name.
Definition type-id.cc:1007
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:999
#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
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
#define NS_LOG_ERROR(msg)
Use NS_LOG to output a message of level LOG_ERROR.
Definition log.h:246
#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
#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:492
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
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
static constexpr double DEG2RAD
Conversion factor: degrees to radians.
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Definition nstime.h:1376
Ptr< const AttributeChecker > MakeDoubleChecker()
Definition double.h:82
Ptr< const AttributeChecker > MakeVectorChecker()
Definition vector.cc:31
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:643
Ptr< const AttributeChecker > MakeStringChecker()
Definition string.cc:19
Ptr< const AttributeAccessor > MakeStringAccessor(T1 a1)
Definition string.h:46
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition boolean.h:70
Ptr< const AttributeAccessor > MakeDoubleAccessor(T1 a1)
Definition double.h:32
Ptr< const AttributeAccessor > MakeVectorAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition vector.h:394
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
Definition nstime.h:1396
static const std::set< std::string > builtInSceneSionna
uint8_t data[writeSize]
Configuration for the Sionna RT PathSolver.
bool diffuseReflection
Enable diffuse reflections (scattering).
bool syntheticArray
Use synthetic array processing (if supported by scene factory).
bool specularReflection
Enable specular reflections (mirror-like).
bool los
Include direct line-of-sight (LOS) path when true It only controls whether the solver should include ...
int seed
Random seed for stochastic components (e.g., diffuse scattering).
int maxDepth
Maximum number of interactions (reflection/refraction depth).
bool edgeDiffraction
Enable edge diffraction (alternative diffraction handling).
bool refraction
Enable refraction through transparent bodies / materials.