10#include "pybind11/numpy.h"
11#include "pybind11/pybind11.h"
13#include "ns3/antenna-model.h"
14#include "ns3/double.h"
16#include "ns3/mobility-model.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"
26namespace py = pybind11;
36static constexpr double DEG2RAD = M_PI / 180.0;
60 TypeId(
"ns3::SionnaRtChannelModel")
63 .AddConstructor<SionnaRtChannelModel>()
64 .AddAttribute(
"Frequency",
65 "The operating Frequency in Hz",
70 .AddAttribute(
"UpdatePeriod",
71 "Specify the channel coherence time",
75 .AddAttribute(
"OutputImageDirectory",
76 "Output directory for rendered Sionna RT images",
80 .AddAttribute(
"OutputImageName",
81 "Base file name for rendered Sionna RT images",
85 .AddAttribute(
"IsImageRenderedEnabled",
86 "Enable or disable scene image rendering",
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",
107 "The maximum number of paths to be generated by the Sionna RT path solver",
112 .AddAttribute(
"NormalizeDelays",
113 "Enable normalization of path delays in Sionna RT CIR output",
118 .AddAttribute(
"Scenario",
119 "The Sionna RT scenario name used to create scenes",
139 "Frequency should be between 0.5 and 100 GHz but is " << f);
140 NS_LOG_INFO(
"Setting operating frequency to " << f <<
" Hz");
148 NS_LOG_INFO(
"Setting max number of paths to " << p);
156 NS_LOG_INFO(
"Setting normalize delays to " << (cond ?
"true" :
"false"));
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();
195 if (((uAntNumElems != chanNumRows) || (sAntNumElems != chanNumCols)) &&
196 ((uAntNumElems != chanNumCols) || (sAntNumElems != chanNumRows)))
198 NS_LOG_INFO(
"Antenna setup changed: aAntenna elems="
199 << sAntNumElems <<
" bAntenna elems=" << uAntNumElems <<
" channel dims=("
200 << chanNumRows <<
", " << chanNumCols <<
")");
202 bool changed = (((uAntNumElems != chanNumRows) || (sAntNumElems != chanNumCols)) &&
203 ((uAntNumElems != chanNumCols) || (sAntNumElems != chanNumRows))) ||
204 aAntenna->IsChannelOutOfDate(bAntenna);
241 uint64_t channelMatrixKey =
GetKey(aAntenna->GetId(), bAntenna->GetId());
243 bool notFound =
false;
256 NS_LOG_INFO(
"Cached channel matrix marked for update (stale or antenna changed)");
267 if (notFound || update)
270 py::module_ rt = py::module_::import(
"sionna.rt");
272 << aAntenna->GetId() <<
", " << bAntenna->GetId() <<
") and nodes ("
276 py::object scene =
CreateScene(rt, aMob, bMob, aAntenna, bAntenna);
279 channelMatrix =
GetNewChannel(paths, aMob, bMob, aAntenna, bAntenna);
280 channelMatrix->m_antennaPair =
281 std::make_pair(aAntenna->GetId(),
290 uint64_t channelParamsKey =
295 return channelMatrix;
306 NS_LOG_DEBUG(
"Creating new ChannelMatrix from path information");
311 return channelMatrix;
325 channelMatrix->m_nodeIds =
328 channelMatrix->m_channel = hUsn;
329 return channelMatrix;
338 bool isXmlFile = (scenario.length() >= 4 && scenario.substr(scenario.length() - 4) ==
".xml");
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");
352 << (isXmlFile ?
" (XML file)" :
" (built-in)"));
382 py::object Scene = rt.attr(
"Scene");
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);
420 const uint8_t numpol)
const
423 std::string polarization =
"V";
426 if (PolSlantAngle == 0.0)
430 else if (PolSlantAngle == M_PI / 2)
437 if (PolSlantAngle == 0.0)
443 polarization =
"cross";
454 py::object PlanarArray = rt.attr(
"PlanarArray");
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");
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(),
470 py::arg(
"polarization") =
480 uint64_t channelParamsKey =
488 NS_LOG_WARN(
"Channel params map not found. Returning a nullptr.");
502 channelParams->m_nodeIds =
505 py::module_ np = py::module_::import(
"numpy");
511 channelParams->m_angle.clear();
518 channelParams->m_cachedAngleSincos.resize(channelParams->m_angle.size());
519 for (
size_t direction = 0; direction < channelParams->m_angle.size(); direction++)
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++)
525 channelParams->m_cachedAngleSincos[direction][cluster] = {
526 sin(channelParams->m_angle[direction][cluster] *
DEG2RAD),
527 cos(channelParams->m_angle[direction][cluster] *
DEG2RAD)};
531 return channelParams;
543 NS_LOG_DEBUG(
"Installing antenna arrays on scene (tx/rx)");
548 py::object Transmitter = rt.attr(
"Transmitter");
549 py::object
Receiver = rt.attr(
"Receiver");
550 Vector pos = sMob->GetPosition();
551 Vector
v = sMob->GetVelocity();
553 Vector posRx = uMob->GetPosition();
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));
560 Vector vRx = uMob->GetVelocity();
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));
568 scene.attr(
"add")(tx);
569 scene.attr(
"add")(rx);
584 << configs.
maxDepth <<
" los=" << configs.
los <<
" specular_reflection="
601 py::object PathSolver = rt.attr(
"PathSolver");
602 py::object psolver = PathSolver();
604 static std::string rt_version = py::str(rt.attr(
"__version__"));
605 NS_LOG_INFO(
"Using Sionna RT version: " << rt_version);
608 NS_LOG_DEBUG(
"Invoking Sionna RT PathSolver with configured options");
610 if (rt_version ==
"1.1.0")
612 NS_LOG_DEBUG(
"Using Sionna RT PathSolver API == 1.1.0");
613 paths = psolver(py::arg(
"scene") = scene,
624 NS_LOG_DEBUG(
"Using Sionna RT PathSolver API >= 1.2.0 used");
625 paths = psolver(py::arg(
"scene") = scene,
640 NS_LOG_INFO(
"Number of generated paths: " << num_paths);
643 NS_LOG_WARN(
"Number of generated paths (" << num_paths
644 <<
") exceeds the configured maximum number of "
647 <<
"). The excess paths will be ignored.");
663 const py::object paths,
664 const py::object scene)
const
666 static int count = 0;
672 py::object camera = rt.attr(
"Camera");
673 py::object Mycamera = camera(
674 py::arg(
"position") =
677 py::object renderer = scene.attr(
"render_to_file");
679 renderer(py::arg(
"camera") = Mycamera,
681 std::to_string(count) +
".png",
682 py::arg(
"paths") = paths);
691 py::module_ np = py::module_::import(
"numpy");
693 py::tuple cir = paths.attr(
"cir")(py::arg(
"normalize_delays") =
m_normalizeDelays,
694 py::arg(
"out_type") =
"numpy");
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);
705 Complex3DVector hUsn(a_info.shape[1], a_info.shape[3], a_info.shape[4]);
707 << a_info.shape[1] <<
" shape[3]=" << a_info.shape[3]
708 <<
" shape[4]=" << a_info.shape[4] <<
" total size=" << a_info.size);
710 for (
size_t cIndex = 0; cIndex < hUsn.GetNumPages(); cIndex++)
712 for (
size_t rowIdx = 0; rowIdx < hUsn.GetNumRows(); rowIdx++)
714 for (
size_t colIdx = 0; colIdx < hUsn.GetNumCols(); colIdx++)
716 size_t idx = (cIndex * hUsn.GetNumRows() + rowIdx) * hUsn.GetNumCols() + colIdx;
717 hUsn(rowIdx, colIdx, cIndex) =
data[idx];
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"));
734 py::buffer_info tau_info = py::buffer(tau_py).request();
735 auto*
data =
static_cast<double*
>(tau_info.ptr);
751 py::tuple doppler = paths.attr(
"doppler");
753 py::object d_py = np.attr(
"asarray")(doppler, py::arg(
"dtype") = np.attr(
"float64"));
756 py::buffer_info d_info = py::buffer(d_py).request();
757 auto*
data =
static_cast<double*
>(d_info.ptr);
763 std::vector<double> dopplerVector(dopplerSize);
764 for (
size_t i = 0; i < dopplerSize; i++)
766 dopplerVector[i] =
data[i];
769 return dopplerVector;
775 std::string Angle)
const
780 angle_py = paths.attr(
"theta_r");
782 else if (Angle ==
"ZOD")
784 angle_py = paths.attr(
"theta_t");
786 else if (Angle ==
"AOA")
788 angle_py = paths.attr(
"phi_r");
790 else if (Angle ==
"AOD")
792 angle_py = paths.attr(
"phi_t");
797 NS_LOG_ERROR(
"Angle type not recognized. Use ZOA, or ZOD, or AOA, or AOD.");
801 py::object angle_np = np.attr(
"asarray")(angle_py, py::arg(
"dtype") = py::dtype(
"float64"));
804 py::buffer_info angle_info = py::buffer(angle_np).request();
805 auto*
data =
static_cast<double*
>(angle_info.ptr);
809 : size_t(angle_info.shape[4]);
812 for (
size_t i = 0; i < angelSize; ++i)
814 angleVector.push_back(
data[i]);
816 NS_LOG_DEBUG(
"Extracted " << angleVector.size() <<
" values for angle type " << Angle);
825 py::module_ np = py::module_::import(
"numpy");
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();
This class can be used to hold variables of floating point type such as 'double' or 'float'.
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,...
Smart pointer class similar to boost::intrusive_ptr.
static Time Now()
Return the current simulation virtual time.
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.
SionnaRtChannelModel()
Constructor.
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.
a unique identifier for an interface.
TypeId SetGroupName(std::string groupName)
Set the group name.
TypeId SetParent(TypeId tid)
Set the parent TypeId.
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
#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.
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
#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.
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Ptr< T > Create(Ts &&... args)
Create class instances by constructors with varying numbers of arguments and return them by Ptr.
Time Now()
create an ns3::Time instance which contains the current simulation time.
Time MilliSeconds(uint64_t value)
Construct a Time in the indicated unit.
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
static constexpr double DEG2RAD
Conversion factor: degrees to radians.
Ptr< const AttributeAccessor > MakeTimeAccessor(T1 a1)
Ptr< const AttributeChecker > MakeDoubleChecker()
Ptr< const AttributeChecker > MakeVectorChecker()
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Ptr< const AttributeChecker > MakeStringChecker()
Ptr< const AttributeAccessor > MakeStringAccessor(T1 a1)
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Ptr< const AttributeAccessor > MakeDoubleAccessor(T1 a1)
Ptr< const AttributeAccessor > MakeVectorAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Ptr< const AttributeChecker > MakeTimeChecker()
Helper to make an unbounded Time checker.
static const std::set< std::string > builtInSceneSionna
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 diffraction
Enable diffraction.
bool refraction
Enable refraction through transparent bodies / materials.