diff --git a/examples/channel-models/three-gpp-v2v-channel-example.cc b/examples/channel-models/three-gpp-v2v-channel-example.cc index 682cdb8fe..783077656 100644 --- a/examples/channel-models/three-gpp-v2v-channel-example.cc +++ b/examples/channel-models/three-gpp-v2v-channel-example.cc @@ -114,11 +114,12 @@ ComputeSnr(const ComputeSnrParams& params) *(params.txParams->psd) *= propagationGainLinear; // apply the fast fading and the beamforming gain - Ptr rxPsd = m_spectrumLossModel->CalcRxPowerSpectralDensity(params.txParams, - params.txMob, - params.rxMob, - params.txAntenna, - params.rxAntenna); + auto rxParams = m_spectrumLossModel->CalcRxPowerSpectralDensity(params.txParams, + params.txMob, + params.rxMob, + params.txAntenna, + params.rxAntenna); + Ptr rxPsd = rxParams->psd; NS_LOG_DEBUG("Average rx power " << 10 * log10(Sum(*rxPsd) * 180e3) << " dB"); // create the noise psd diff --git a/src/spectrum/doc/spectrum.rst b/src/spectrum/doc/spectrum.rst index d971b589f..49e4df074 100644 --- a/src/spectrum/doc/spectrum.rst +++ b/src/spectrum/doc/spectrum.rst @@ -69,8 +69,14 @@ information for a signal being transmitted/received by PHY devices: * the duration of the signal -* its Power Spectral Density (PSD) of the signal, which is assumed to be constant for - the duration of the signal. +* the Power Spectral Density (PSD) of the signal, which is assumed to be constant for + the duration of the signal + +* the frequency domain 3D spectrum channel matrix that is needed for MIMO computations + when multiple streams are transmitted + +* the 3D precoding matrix that is needed for MIMO computations when multiple + streams are transmitted. The PSD is represented as a set of discrete scalar values each corresponding to a certain subband in frequency. The set of frequency subbands @@ -83,6 +89,17 @@ with PSD instances. Additionally, the ``SpectrumConverter`` class provides means for the conversion of ``SpectrumValue`` instances from one ``SpectrumModel`` to another. +The frequency domain 3D channel matrix is needed in MIMO systems in which +multiple transmit and receive antenna ports can exist, hence the PSD is multidimensional. +The dimensions are: the number of receive antenna ports, the number of +transmit antenna ports, and the number of subbands in frequency (or +resource blocks). + +The 3D precoding matrix is also needed in MIMO systems to be able to correctly +perform the calculations of the received signal and interference. The dimensions are: +the number of transmit antenna ports, the number of transmit +streams, and the number of subbands (or resource blocks). + For a more formal mathematical description of the signal model just described, the reader is referred to [Baldo2009Spectrum]_. @@ -609,7 +626,7 @@ antenna. H :sub:`u,s` :math:`(t,\tau)` is generated by the superposition of N di components, called clusters, each of which composed of M different rays. The channel matrix generation procedure accounts for large and small scale propagation phenomena. The classes ThreeGppSpectrumPropagationLossModel and -ThreeGppChannelModel included in the spectrum module takes care of the generation +ThreeGppChannelModel included in the spectrum module take care of the generation of the channel coefficients and the computation of the frequency-dependent propagation loss. @@ -656,19 +673,28 @@ The class ThreeGppSpectrumPropagationLossModel implements the PhasedArraySpectrumPropagationLossModel interface and enables the modeling of frequency dependent propagation phenomena while taking into account the specific pair of the phased antenna array at the transmitter and the receiver. The main method is -DoCalcRxPowerSpectralDensity, which takes as input the power spectral density (PSD) -of the transmitted signal, the mobility models of the transmitting node and receiving node, -and the phased antenna array of the transmitting node, and of the receiving node. -Finally, it returns the PSD of the received signal. +DoCalcRxPowerSpectralDensity, which takes as input the SpectrumSignalParameters +structure, which contains, among others, the power spectral density (PSD) of the +transmitted signal, and the precoding matrix of the transmitter which is needed +for MIMO computations. Other input parameters are the mobility models of the +transmitting and receiving node, and the phased antenna array of the +transmitting and receiving node. +DoCalcRxPowerSpectralDensity computes the PSD of the received signal +(used in case of single stream transmission), and the frequency domain 3D +channel matrix between receive and transmit antenna ports (needed for MIMO +computations when multiple streams are transmitted). +Finally, it returns the SpectrumSignalParameters which contains the +received PSD and the frequency domain 3D spectrum channel matrix. -Procedure used to compute the PSD of to compute the PSD of the received signal: +Procedure used to compute the PSD of the received signal and the frequency domain 3D +spectrum channel matrix: 1. Retrieve the beamforming vectors To account for the beamforming, ThreeGppSpectrumPropagationLossModel has to retrieve the beamforming vectors of the transmitting and receiving antennas. The method DoCalcRxPowerSpectralDensity uses the antenna objects that are passed as parameters for both the transmitting and receiving devices, -and calls the method GetCurrentBeamformingVector to retrieve the beamforming vectors +and calls the method GetBeamformingVector to retrieve the beamforming vectors of these antenna objects. 2. Retrieve the channel matrix and the channel params @@ -678,12 +704,12 @@ In particular, it makes use of the method GetChannel, which returns a ChannelMatrix object containing the channel matrix, the generation time, the node pair, and the phased antenna array pair among which is created this channel matrix. -Apart from the function GetChannel, there is a function called GetParams which +Additionally, it makes use of the method GetParams which returns a ChannelParams object containing the channel parameters. Notice that the channel information is split into these two structures -(ChannelMatrix and ChannelParams) to support multiple collocate phased antenna arrays at +(ChannelMatrix and ChannelParams) to support multiple collocated phased antenna arrays at TX/RX node. ChannelParams (also its specialization ThreeGppChannelParams structure) -contains parameters which are common for all channels among +contains parameters which are common for all the channels among the same RX/TX node pair, while ChannelMatrix contains the channel matrix for the specific pair of the phased antenna arrays of TX/RX nodes. For example, if the TX and the RX node have multiple collocated antenna arrays, @@ -694,27 +720,35 @@ i.e., they share the same channel condition, cluster powers, cluster delays, AoD, AoA, ZoD, ZoA, K_factor, delay spread, etc. On the other hand, each pair of TX and RX antenna arrays has a specific channel matrix and fading, which depends on the actual antenna element positions and field patterns of -each pair of antennas array subpartitions. +each pair of antenna array subpartitions. The ThreeGppChannelModel instance is automatically -created in the the ThreeGppSpectrumPropagationLossModel constructor and it can -be configured using the method SetChannelModelAttribute (). +created in the ThreeGppSpectrumPropagationLossModel constructor and it can +be configured by using the method SetChannelModelAttribute. -Notice that in MultiModelSpectrumChannel in StartTx function we added a -condition that checks whether the TX/RX SpectrumPhy instances belong to different -TX/RX nodes. This is needed to avoid pathloss models calculations among +Notice that in MultiModelSpectrumChannel it is checked whether the TX/RX +SpectrumPhy instances belong to different TX/RX nodes. +This is needed to avoid pathloss models calculations among the phased antenna arrays of the same node, because there are no models yet in ns-3 that support the calculation of this kind of interference. 4. Compute the long term component The method GetLongTerm returns the long term component obtained by multiplying -the channel matrix and the beamforming vectors. To reduce the computational -load, the long term components associated to the different channels are -stored in the m_longTermMap and recomputed only if the associated channel -matrix is updated or if the transmitting and/or receiving beamforming vectors -have changed. Given the channel reciprocity assumption, for each node pair a -single long term component is saved in the map. +the channel matrix and the beamforming vectors. The function CalculateLongTermComponent +calculates the long term component per RX and TX port pair. Finally, GetLongTerm +returns a 3D long term channel matrix whose dimensions are the number of the +receive antenna ports, the number transmit antenna ports, and +the number of clusters. When multiple ports are being configured note that +the sub-array partition model is adopted for TXRU virtualization, as described +in Section 5.2.2 of 3GPP TR 36.897[TR36897]_, and so equal beam weights are used for all the ports. +Support of the full-connection model for TXRU virtualization would need extensions. +To reduce the computational load, the long term +components associated to the different channels are stored in the m_longTermMap +and recomputed only if the associated channel matrix is updated or if the +transmitting and/or receiving beamforming vectors have changed. Given the channel +reciprocity assumption, for each node pair a single long term component is saved in the map. -5. Apply the small scale fading and compute the channel gain +5. Apply the small scale fading, calculate the channel gain, generate the +frequency domain 3D spectrum channel matrix, and finally compute the received PSD The method CalcBeamformingGain computes the channel gain in each sub-band and applies it to the PSD of the transmitted signal to obtain the received PSD. To compute the sub-band gain, it accounts for the Doppler phenomenon and the @@ -728,6 +762,14 @@ This is done by deviating the Doppler frequency by a random value, whose distribution depends on the parameter :math:`v_{scatt}`. The value of :math:`v_{scatt}` can be configured using the attribute "vScatt" (by default it is set to 0, so that the scattering effect is not considered). +Function GenSpectrumChannelMatrix generates the received PSD for each pair of +the transmit and receive antenna ports. It creates a frequency domain 3D spectrum +channel matrix whose dimensions are the number of receive antena ports, +the number of transmit antenna ports, and the number of resource blocks. +Finally, the frequency domain 3D spectrum channel matrix is used to obtain the +received PSD. In case of multiple ports at the transmitter the PSD is calculated +by summing per each RB the real parts of the diagonal elements of the (H*P)^h * (H*P), +where H is the frequency domain spectrum channel matrix and P is the precoding matrix. ThreeGppChannelModel @@ -751,8 +793,7 @@ and returns a ChannelMatrix object containing: * other channel parameters -The ChannelMatrix objects are saved -in the map m_channelMap and updated when the coherence time +The ChannelMatrix objects are saved in the map m_channelMap and updated when the coherence time expires, or in case the LOS/NLOS channel condition changes. The coherence time can be configured through the attribute "UpdatePeriod", and should be chosen by taking into account all the @@ -780,7 +821,7 @@ to configure the model. Testing ####### -The test suite ThreeGppChannelTestSuite includes three test cases: +The test suite ThreeGppChannelTestSuite includes five test cases: * ThreeGppChannelMatrixComputationTest checks if the channel matrix has the correct dimensions and if it correctly normalized @@ -799,6 +840,11 @@ The test suite ThreeGppChannelTestSuite includes three test cases: the beamforming vectors, 3. Checks if the long term is updated when changing the channel matrix +* ThreeGppCalcLongTermMultiPortTest, which tests that the channel matrices are + correctly generated when multiple transmit and receive antenna ports are used. + +* ThreeGppMimoPolarizationTest, which tests that the channel matrices are + correctly generated when dual-polarized antennas are being used. **Note:** TR 38.901 includes a calibration procedure that can be used to validate the model, but it requires some additional features which are not currently @@ -835,13 +881,16 @@ References ns-3". Submitted to the Workshop on ns-3 (WNS3 '20). 2020. Available: https://arxiv.org/abs/2002.09341 +.. [TR36897] 3GPP. 2015. TR 36.897. Study on elevation beamforming / Full-Dimension (FD) + Multiple Input Multiple Output (MIMO) for LTE. V13.0.0. (2015-06) + Two-Ray fading model ==================== The model aims to provide a performance-oriented alternative to the 3GPP TR 38.901 framework [TR38901]_ which is implemented in the ``ThreeGppSpectrumPropagationLossModel`` and ``ThreeGppChannelModel`` classes and whose implementation is described in [Zugno2020]_. -The overall design follows the general approach of [Polese2018]_, with aim of providing +The overall design described in [Pagin2023]_ follows the general approach of [Polese2018]_, with aim of providing the means for computing a 3GPP TR 38.901-like end-to-end channel gain by combining several statistical terms. The frequency range of applicability is the same as that of [TR38901]_, i.e., 0.5 - 100 GHz. @@ -1018,6 +1067,10 @@ The test suite ``TwoRaySplmTestSuite`` includes three test cases: References ########## +.. [Pagin2023] Pagin, Matteo, Sandra Lagen, Biljana Bojovic, Michele Polese, + Michele Zorzi. 2023. "Improving the Efficiency of MIMO Simulations in ns-3" + In Proceedings of the 2023 Workshop on ns-3, pp. 1–9. 2023 + .. [Zugno2020] Zugno, Tommaso, Michele Polese, Natale Patriciello, Biljana Bojović, Sandra Lagen, Michele Zorzi. "Implementation of a spatial channel model for ns-3." In Proceedings of the 2020 Workshop on ns-3, pp. 49-56. 2020. diff --git a/src/spectrum/examples/three-gpp-channel-example.cc b/src/spectrum/examples/three-gpp-channel-example.cc index a9a945c98..f201d015c 100644 --- a/src/spectrum/examples/three-gpp-channel-example.cc +++ b/src/spectrum/examples/three-gpp-channel-example.cc @@ -131,14 +131,14 @@ ComputeSnr(const ComputeSnrParams& params) { activeRbs0[i] = i; } - Ptr txPsd = + auto txPsd = LteSpectrumValueHelper::CreateTxPowerSpectralDensity(2100, 100, params.txPow, activeRbs0); - Ptr txParams = Create(); + auto txParams = Create(); txParams->psd = txPsd->Copy(); NS_LOG_DEBUG("Average tx power " << 10 * log10(Sum(*txPsd) * 180e3) << " dB"); // create the noise PSD - Ptr noisePsd = + auto noisePsd = LteSpectrumValueHelper::CreateNoisePowerSpectralDensity(2100, 100, params.noiseFigure); NS_LOG_DEBUG("Average noise power " << 10 * log10(Sum(*noisePsd) * 180e3) << " dB"); @@ -152,11 +152,12 @@ ComputeSnr(const ComputeSnrParams& params) NS_ASSERT_MSG(params.rxAntenna, "params.rxAntenna is nullptr!"); // apply the fast fading and the beamforming gain - Ptr rxPsd = m_spectrumLossModel->CalcRxPowerSpectralDensity(txParams, - params.txMob, - params.rxMob, - params.txAntenna, - params.rxAntenna); + auto rxParams = m_spectrumLossModel->CalcRxPowerSpectralDensity(txParams, + params.txMob, + params.rxMob, + params.txAntenna, + params.rxAntenna); + auto rxPsd = rxParams->psd; NS_LOG_DEBUG("Average rx power " << 10 * log10(Sum(*rxPsd) * 180e3) << " dB"); // compute the SNR diff --git a/src/spectrum/examples/three-gpp-two-ray-channel-calibration.cc b/src/spectrum/examples/three-gpp-two-ray-channel-calibration.cc index 15c347b47..b2076ea87 100644 --- a/src/spectrum/examples/three-gpp-two-ray-channel-calibration.cc +++ b/src/spectrum/examples/three-gpp-two-ray-channel-calibration.cc @@ -186,12 +186,12 @@ ComputeEndToEndGain(std::string cond, bArray->SetBeamformingVector(bArray->GetBeamformingVector(angleAtoB)); // Compute the received power due to multipath fading - auto rxPsd = threeGppSpectrumLossModel->DoCalcRxPowerSpectralDensity(signalParams, - aMob, - bMob, - aArray, - bArray); - double rxPower = ComputePowerSpectralDensityOverallPower(rxPsd); + auto rxParams = threeGppSpectrumLossModel->DoCalcRxPowerSpectralDensity(signalParams, + aMob, + bMob, + aArray, + bArray); + double rxPower = ComputePowerSpectralDensityOverallPower(rxParams->psd); return rxPower / txPower; } diff --git a/src/spectrum/model/multi-model-spectrum-channel.cc b/src/spectrum/model/multi-model-spectrum-channel.cc index 19031abf8..e335bd769 100644 --- a/src/spectrum/model/multi-model-spectrum-channel.cc +++ b/src/spectrum/model/multi-model-spectrum-channel.cc @@ -417,7 +417,7 @@ MultiModelSpectrumChannel::StartRx(Ptr params, Ptrpsd = m_phasedArraySpectrumPropagationLoss->CalcRxPowerSpectralDensity( + params = m_phasedArraySpectrumPropagationLoss->CalcRxPowerSpectralDensity( params, params->txPhy->GetMobility(), receiver->GetMobility(), diff --git a/src/spectrum/model/phased-array-spectrum-propagation-loss-model.cc b/src/spectrum/model/phased-array-spectrum-propagation-loss-model.cc index cfb287487..c0e874c2f 100644 --- a/src/spectrum/model/phased-array-spectrum-propagation-loss-model.cc +++ b/src/spectrum/model/phased-array-spectrum-propagation-loss-model.cc @@ -60,7 +60,7 @@ PhasedArraySpectrumPropagationLossModel::SetNext(Ptr +Ptr PhasedArraySpectrumPropagationLossModel::CalcRxPowerSpectralDensity( Ptr params, Ptr a, @@ -71,14 +71,15 @@ PhasedArraySpectrumPropagationLossModel::CalcRxPowerSpectralDensity( // Here we assume that all the models in the chain of models are of type // PhasedArraySpectrumPropagationLossModel that provides the implementation of // this function, i.e. has phased array model of TX and RX as parameters - Ptr rxPsd = + auto rxParams = DoCalcRxPowerSpectralDensity(params, a, b, aPhasedArrayModel, bPhasedArrayModel); + if (m_next) { - rxPsd = + rxParams = m_next->CalcRxPowerSpectralDensity(params, a, b, aPhasedArrayModel, bPhasedArrayModel); } - return rxPsd; + return rxParams; } } // namespace ns3 diff --git a/src/spectrum/model/phased-array-spectrum-propagation-loss-model.h b/src/spectrum/model/phased-array-spectrum-propagation-loss-model.h index a2a2c8af3..732734668 100644 --- a/src/spectrum/model/phased-array-spectrum-propagation-loss-model.h +++ b/src/spectrum/model/phased-array-spectrum-propagation-loss-model.h @@ -70,10 +70,12 @@ class PhasedArraySpectrumPropagationLossModel : public Object * @param aPhasedArrayModel the instance of the phased antenna array of the sender * @param bPhasedArrayModel the instance of the phased antenna array of the receiver * - * @return set of values Vs frequency representing the received - * power in the same units used for the txPower parameter. + * @return SpectrumSignalParameters in which is updated the PSD to contain + * a set of values Vs frequency representing the received + * power in the same units used for the txPower parameter, + * and additional chanSpectrumMatrix is computed to support MIMO systems. */ - Ptr CalcRxPowerSpectralDensity( + Ptr CalcRxPowerSpectralDensity( Ptr txPsd, Ptr a, Ptr b, @@ -92,10 +94,12 @@ class PhasedArraySpectrumPropagationLossModel : public Object * @param aPhasedArrayModel the instance of the phased antenna array of the sender * @param bPhasedArrayModel the instance of the phased antenna array of the receiver * - * @return set of values Vs frequency representing the received - * power in the same units used for the txPower parameter. + * @return SpectrumSignalParameters in which is updated the PSD to contain + * a set of values Vs frequency representing the received + * power in the same units used for the txPower parameter, + * and additional chanSpectrumMatrix is set. */ - virtual Ptr DoCalcRxPowerSpectralDensity( + virtual Ptr DoCalcRxPowerSpectralDensity( Ptr params, Ptr a, Ptr b, diff --git a/src/spectrum/model/spectrum-signal-parameters.cc b/src/spectrum/model/spectrum-signal-parameters.cc index 460b3e07a..20e0e8ae0 100644 --- a/src/spectrum/model/spectrum-signal-parameters.cc +++ b/src/spectrum/model/spectrum-signal-parameters.cc @@ -47,6 +47,10 @@ SpectrumSignalParameters::SpectrumSignalParameters(const SpectrumSignalParameter duration = p.duration; txPhy = p.txPhy; txAntenna = p.txAntenna; + spectrumChannelMatrix = p.spectrumChannelMatrix; // we do not need a deep copy, it will not be + // changed once is created + precodingMatrix = + p.precodingMatrix; // we do not need a deep copy, it will not be changed once is created } Ptr diff --git a/src/spectrum/model/spectrum-signal-parameters.h b/src/spectrum/model/spectrum-signal-parameters.h index e2b5c8865..22a6e18cc 100644 --- a/src/spectrum/model/spectrum-signal-parameters.h +++ b/src/spectrum/model/spectrum-signal-parameters.h @@ -20,9 +20,8 @@ #ifndef SPECTRUM_SIGNAL_PARAMETERS_H #define SPECTRUM_SIGNAL_PARAMETERS_H +#include #include -#include -#include namespace ns3 { @@ -111,6 +110,20 @@ struct SpectrumSignalParameters : public SimpleRefCount txAntenna; + + /** + * The 3D channel matrix where the dimensions are: the number of RX ports, + * the number of TX Ports, the number of resource blocks (RBs). + * Needed in the MIMO system in which multiple TX and RX ports can exist, + * hence the PSD is multidimensional. Elements are the complex numbers. + */ + Ptr spectrumChannelMatrix; + + /** + * The 3D precoding matrix where the dimensions are: the number of TX ports, + * the number of TX streams, the number of RBs. + */ + Ptr precodingMatrix; }; } // namespace ns3 diff --git a/src/spectrum/model/three-gpp-channel-model.cc b/src/spectrum/model/three-gpp-channel-model.cc index 0daebe682..d15e5c6e7 100644 --- a/src/spectrum/model/three-gpp-channel-model.cc +++ b/src/spectrum/model/three-gpp-channel-model.cc @@ -32,6 +32,7 @@ #include #include +#include #include namespace ns3 @@ -1909,9 +1910,6 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara Angles sAngle(uMob->GetPosition(), sMob->GetPosition()); Angles uAngle(sMob->GetPosition(), uMob->GetPosition()); - Complex2DVector raysPreComp(channelParams->m_reducedClusterNumber, - table3gpp->m_raysPerCluster); // stores part of the ray expression, - // cached as independent from the u- and s-indexes Double2DVector sinCosA; // cached multiplications of sin and cos of the ZoA and AoA angles Double2DVector sinSinA; // cached multiplications of sines of the ZoA and AoA angles Double2DVector cosZoA; // cached cos of the ZoA angle @@ -1919,6 +1917,18 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara Double2DVector sinSinD; // cached multiplications of the cosines of the ZoA and AoA angles Double2DVector cosZoD; // cached cos of the ZoD angle + // contains part of the ray expression, cached as independent from the u- and s-indexes, + // but calculate it for different polarization angles of s and u + std::map, Complex2DVector> raysPreComp; + for (size_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa) + { + for (size_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa) + { + raysPreComp[std::make_pair(polSa, polUa)] = + Complex2DVector(channelParams->m_reducedClusterNumber, table3gpp->m_raysPerCluster); + } + } + // resize to appropriate dimensions sinCosA.resize(channelParams->m_reducedClusterNumber); sinSinA.resize(channelParams->m_reducedClusterNumber); @@ -1946,21 +1956,30 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara // cache the component of the "rays" terms which depend on the random angle of arrivals // and departures and initial phases only - auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern( - Angles(channelParams->m_rayAoaRadian[nIndex][mIndex], - channelParams->m_rayZoaRadian[nIndex][mIndex])); - auto [txFieldPatternPhi, txFieldPatternTheta] = sAntenna->GetElementFieldPattern( - Angles(channelParams->m_rayAodRadian[nIndex][mIndex], - channelParams->m_rayZodRadian[nIndex][mIndex])); - raysPreComp(nIndex, mIndex) = - std::complex(cos(initialPhase[0]), sin(initialPhase[0])) * - rxFieldPatternTheta * txFieldPatternTheta + - std::complex(cos(initialPhase[1]), sin(initialPhase[1])) * - std::sqrt(1.0 / k) * rxFieldPatternTheta * txFieldPatternPhi + - std::complex(cos(initialPhase[2]), sin(initialPhase[2])) * - std::sqrt(1.0 / k) * rxFieldPatternPhi * txFieldPatternTheta + - std::complex(cos(initialPhase[3]), sin(initialPhase[3])) * - rxFieldPatternPhi * txFieldPatternPhi; + for (uint8_t polUa = 0; polUa < uAntenna->GetNumPols(); ++polUa) + { + auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern( + Angles(channelParams->m_rayAoaRadian[nIndex][mIndex], + channelParams->m_rayZoaRadian[nIndex][mIndex]), + polUa); + for (uint8_t polSa = 0; polSa < sAntenna->GetNumPols(); ++polSa) + { + auto [txFieldPatternPhi, txFieldPatternTheta] = + sAntenna->GetElementFieldPattern( + Angles(channelParams->m_rayAodRadian[nIndex][mIndex], + channelParams->m_rayZodRadian[nIndex][mIndex]), + polSa); + raysPreComp[std::make_pair(polSa, polUa)](nIndex, mIndex) = + std::complex(cos(initialPhase[0]), sin(initialPhase[0])) * + rxFieldPatternTheta * txFieldPatternTheta + + std::complex(cos(initialPhase[1]), sin(initialPhase[1])) * + std::sqrt(1.0 / k) * rxFieldPatternTheta * txFieldPatternPhi + + std::complex(cos(initialPhase[2]), sin(initialPhase[2])) * + std::sqrt(1.0 / k) * rxFieldPatternPhi * txFieldPatternTheta + + std::complex(cos(initialPhase[3]), sin(initialPhase[3])) * + rxFieldPatternPhi * txFieldPatternPhi; + } + } // cache the component of the "rxPhaseDiff" terms which depend on the random angle of // arrivals only @@ -2011,10 +2030,11 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara 2 * M_PI * (sinCosD[nIndex][mIndex] * sLoc.x + sinSinD[nIndex][mIndex] * sLoc.y + cosZoD[nIndex][mIndex] * sLoc.z); - // NOTE Doppler is computed in the CalcBeamformingGain function and is // simplified to only account for the center angle of each cluster. - rays += raysPreComp(nIndex, mIndex) * + rays += raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex), + uAntenna->GetElemPol(uIndex))](nIndex, + mIndex) * std::complex(cos(rxPhaseDiff), sin(rxPhaseDiff)) * std::complex(cos(txPhaseDiff), sin(txPhaseDiff)); } @@ -2043,7 +2063,9 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara cosZoD[nIndex][mIndex] * sLoc.z); std::complex raySub = - raysPreComp(nIndex, mIndex) * + raysPreComp[std::make_pair(sAntenna->GetElemPol(sIndex), + uAntenna->GetElemPol(uIndex))](nIndex, + mIndex) * std::complex(cos(rxPhaseDiff), sin(rxPhaseDiff)) * std::complex(cos(txPhaseDiff), sin(txPhaseDiff)); @@ -2123,9 +2145,11 @@ ThreeGppChannelModel::GetNewChannel(Ptr channelPara cosSAngleIncl * sLoc.z); auto [rxFieldPatternPhi, rxFieldPatternTheta] = uAntenna->GetElementFieldPattern( - Angles(uAngle.GetAzimuth(), uAngle.GetInclination())); + Angles(uAngle.GetAzimuth(), uAngle.GetInclination()), + uAntenna->GetElemPol(uIndex)); auto [txFieldPatternPhi, txFieldPatternTheta] = sAntenna->GetElementFieldPattern( - Angles(sAngle.GetAzimuth(), sAngle.GetInclination())); + Angles(sAngle.GetAzimuth(), sAngle.GetInclination()), + sAntenna->GetElemPol(sIndex)); ray = (rxFieldPatternTheta * txFieldPatternTheta - rxFieldPatternPhi * txFieldPatternPhi) * diff --git a/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.cc b/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.cc index e428761de..021543b78 100644 --- a/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.cc +++ b/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.cc @@ -111,45 +111,114 @@ ThreeGppSpectrumPropagationLossModel::GetChannelModelAttribute(const std::string m_channelModel->GetAttribute(name, value); } -PhasedArrayModel::ComplexVector +Ptr ThreeGppSpectrumPropagationLossModel::CalcLongTerm( Ptr params, - const PhasedArrayModel::ComplexVector& sW, - const PhasedArrayModel::ComplexVector& uW) const + Ptr sAnt, + Ptr uAnt) const { NS_LOG_FUNCTION(this); - size_t uAntennaNum = uW.GetSize(); - size_t sAntennaNum = sW.GetSize(); - - NS_ASSERT(uAntennaNum == params->m_channel.GetNumRows()); - NS_ASSERT(sAntennaNum == params->m_channel.GetNumCols()); - - NS_LOG_DEBUG("CalcLongTerm with " << uAntennaNum << " u antenna elements and " << sAntennaNum - << " s antenna elements."); - // store the long term part to reduce computation load - // only the small scale fading needs to be updated if the large scale parameters and antenna - // weights remain unchanged. here we calculate long term uW * Husn * sW, the result is an array - // of values per cluster - return params->m_channel.MultiplyByLeftAndRightMatrix(uW.Transpose(), sW); + const PhasedArrayModel::ComplexVector& sW = sAnt->GetBeamformingVectorRef(); + const PhasedArrayModel::ComplexVector& uW = uAnt->GetBeamformingVectorRef(); + size_t sAntNumElems = sW.GetSize(); + size_t uAntNumElems = uW.GetSize(); + NS_ASSERT(uAntNumElems == params->m_channel.GetNumRows()); + NS_ASSERT(sAntNumElems == params->m_channel.GetNumCols()); + NS_LOG_DEBUG("CalcLongTerm with " << uW.GetSize() << " u antenna elements and " << sW.GetSize() + << " s antenna elements, and with " + << " s ports: " << sAnt->GetNumPorts() + << " u ports: " << uAnt->GetNumPorts()); + NS_ASSERT_MSG((sAnt != nullptr) && (uAnt != nullptr), "Improper call to the method"); + size_t numClusters = params->m_channel.GetNumPages(); + // create and initialize the size of the longTerm 3D matrix + Ptr longTerm = + Create(uAnt->GetNumPorts(), + sAnt->GetNumPorts(), + numClusters); + // Calculate long term uW * Husn * sW, the result is a matrix + // with the dimensions #uPorts, #sPorts, #cluster + for (auto sPortIdx = 0; sPortIdx < sAnt->GetNumPorts(); sPortIdx++) + { + for (auto uPortIdx = 0; uPortIdx < uAnt->GetNumPorts(); uPortIdx++) + { + for (size_t cIndex = 0; cIndex < numClusters; cIndex++) + { + longTerm->Elem(uPortIdx, sPortIdx, cIndex) = + CalculateLongTermComponent(params, sAnt, uAnt, sPortIdx, uPortIdx, cIndex); + } + } + } + return longTerm; } -Ptr -ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( - Ptr txPsd, - PhasedArrayModel::ComplexVector longTerm, - Ptr channelMatrix, - Ptr channelParams, - const ns3::Vector& sSpeed, - const ns3::Vector& uSpeed) const +std::complex +ThreeGppSpectrumPropagationLossModel::CalculateLongTermComponent( + Ptr params, + Ptr sAnt, + Ptr uAnt, + uint16_t sPortIdx, + uint16_t uPortIdx, + uint16_t cIndex) const { NS_LOG_FUNCTION(this); + const PhasedArrayModel::ComplexVector& sW = sAnt->GetBeamformingVectorRef(); + const PhasedArrayModel::ComplexVector& uW = uAnt->GetBeamformingVectorRef(); + auto sPortElems = sAnt->GetNumElemsPerPort(); + auto uPortElems = uAnt->GetNumElemsPerPort(); + auto startS = sAnt->ArrayIndexFromPortIndex(sPortIdx, 0); + auto startU = uAnt->ArrayIndexFromPortIndex(uPortIdx, 0); + std::complex txSum(0, 0); + // limiting multiplication operations to the port location + auto sIndex = startS; + // The sub-array partition model is adopted for TXRU virtualization, + // as described in Section 5.2.2 of 3GPP TR 36.897, + // and so equal beam weights are used for all the ports. + // Support of the full-connection model for TXRU virtualization would need extensions. + for (size_t tIndex = 0; tIndex < sPortElems; tIndex++, sIndex++) + { + std::complex rxSum(0, 0); + auto uIndex = startU; + for (size_t rIndex = 0; rIndex < uPortElems; rIndex++, uIndex++) + { + rxSum += uW[uIndex - startU] * params->m_channel(uIndex, sIndex, cIndex); + auto testV = (rIndex % uAnt->GetHElemsPerPort()); + auto ptInc = uAnt->GetHElemsPerPort() - 1; + if (testV == ptInc) + { + auto incVal = uAnt->GetNumColumns() - uAnt->GetHElemsPerPort(); + uIndex += incVal; // Increment by a factor to reach next column in a port + } + } - Ptr tempPsd = Copy(txPsd); + txSum += sW[sIndex - startS] * rxSum; + auto testV = (tIndex % sAnt->GetHElemsPerPort()); + auto ptInc = sAnt->GetHElemsPerPort() - 1; + if (testV == ptInc) + { + size_t incVal = sAnt->GetNumColumns() - sAnt->GetHElemsPerPort(); + sIndex += incVal; // Increment by a factor to reach next column in a port + } + } + return txSum; +} - // channel[cluster][rx][tx] - uint16_t numCluster = channelMatrix->m_channel.GetNumPages(); +Ptr +ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( + Ptr params, + Ptr longTerm, + Ptr channelMatrix, + Ptr channelParams, + const Vector& sSpeed, + const ns3::Vector& uSpeed, + uint8_t numTxPorts, + uint8_t numRxPorts, + bool isReverse) const +{ + NS_LOG_FUNCTION(this); + Ptr rxParams = params->Copy(); + size_t numCluster = channelMatrix->m_channel.GetNumPages(); // compute the doppler term // NOTE the update of Doppler is simplified by only taking the center angle of // each cluster in to consideration. @@ -157,18 +226,15 @@ ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( double factor = 2 * M_PI * slotTime * GetFrequency() / 3e8; PhasedArrayModel::ComplexVector doppler(numCluster); - // The following asserts might seem paranoic, but it is important to - // make sure that all the structures that are passed to this function + // Make sure that all the structures that are passed to this function // are of the correct dimensions before using the operator []. - // If you dont understand the comment read about the difference of .at() - // and [] operators, ... NS_ASSERT(numCluster <= channelParams->m_alpha.size()); NS_ASSERT(numCluster <= channelParams->m_D.size()); NS_ASSERT(numCluster <= channelParams->m_angle[MatrixBasedChannelModel::ZOA_INDEX].size()); NS_ASSERT(numCluster <= channelParams->m_angle[MatrixBasedChannelModel::ZOD_INDEX].size()); NS_ASSERT(numCluster <= channelParams->m_angle[MatrixBasedChannelModel::AOA_INDEX].size()); NS_ASSERT(numCluster <= channelParams->m_angle[MatrixBasedChannelModel::AOD_INDEX].size()); - NS_ASSERT(numCluster <= longTerm.GetSize()); + NS_ASSERT(numCluster <= longTerm->GetNumPages()); // check if channelParams structure is generated in direction s-to-u or u-to-s bool isSameDirection = (channelParams->m_nodeIds == channelMatrix->m_nodeIds); @@ -179,7 +245,7 @@ ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( MatrixBasedChannelModel::DoubleVector aod; // if channel params is generated in the same direction in which we - // generate the channel matrix, angles and zenith od departure and arrival are ok, + // generate the channel matrix, angles and zenith of departure and arrival are ok, // just set them to corresponding variable that will be used for the generation // of channel matrix, otherwise we need to flip angles and zeniths of departure and arrival if (isSameDirection) @@ -197,7 +263,7 @@ ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( aoa = channelParams->m_angle[MatrixBasedChannelModel::AOD_INDEX]; } - for (uint16_t cIndex = 0; cIndex < numCluster; cIndex++) + for (size_t cIndex = 0; cIndex < numCluster; cIndex++) { // Compute alpha and D as described in 3GPP TR 37.885 v15.3.0, Sec. 6.2.3 // These terms account for an additional Doppler contribution due to the @@ -227,44 +293,139 @@ ThreeGppSpectrumPropagationLossModel::CalcBeamformingGain( NS_ASSERT(numCluster <= doppler.GetSize()); - // apply the doppler term and the propagation delay to the long term component - // to obtain the beamforming gain - auto vit = tempPsd->ValuesBegin(); // psd iterator - auto sbit = tempPsd->ConstBandsBegin(); // band iterator - while (vit != tempPsd->ValuesEnd()) + // set the channel matrix + rxParams->spectrumChannelMatrix = GenSpectrumChannelMatrix(rxParams->psd, + longTerm, + channelMatrix, + channelParams, + doppler, + numTxPorts, + numRxPorts, + isReverse); + + // The precoding matrix is not set + if (!rxParams->precodingMatrix) + { + // Update rxParams->Psd. + // Compute RX PSD from the channel matrix + auto vit = rxParams->psd->ValuesBegin(); // psd iterator + size_t rbIdx = 0; + while (vit != rxParams->psd->ValuesEnd()) + { + // Calculate PSD for the first antenna port (correct for SISO) + *vit = std::norm(rxParams->spectrumChannelMatrix->Elem(0, 0, rbIdx)); + vit++; + rbIdx++; + } + } + else + { + NS_ASSERT_MSG(rxParams->psd->GetValuesN() == rxParams->spectrumChannelMatrix->GetNumPages(), + "RX PSD and the spectrum channel matrix should have the same number of RBs "); + // Calculate RX PSD from the spectrum channel matrix, H and + // the precoding matrix, P as: + // PSD = (H*P)^h * (H*P), + // where the dimensions are: + // H (rxPorts,txPorts,numRbs) x P (txPorts,txStreams, numRbs) = + // HxP (rxPorts,txStreams, numRbs) + MatrixBasedChannelModel::Complex3DVector hP = + *rxParams->spectrumChannelMatrix * (*rxParams->precodingMatrix); + // (HxP)^h dimensions are (txStreams, rxPorts, numRbs) + MatrixBasedChannelModel::Complex3DVector hPHerm = hP.HermitianTranspose(); + + // Finally, (HxP)^h x (HxP) = PSD (txStreams, txStreams, numRbs) + MatrixBasedChannelModel::Complex3DVector psd = hPHerm * hP; + // Update rxParams->Psd + for (uint32_t rbIdx = 0; rbIdx < rxParams->psd->GetValuesN(); ++rbIdx) + { + (*rxParams->psd)[rbIdx] = 0.0; + + for (size_t txStream = 0; txStream < psd.GetNumRows(); ++txStream) + { + (*rxParams->psd)[rbIdx] += std::real(psd(txStream, txStream, rbIdx)); + } + } + } + return rxParams; +} + +Ptr +ThreeGppSpectrumPropagationLossModel::GenSpectrumChannelMatrix( + Ptr inPsd, + Ptr longTerm, + Ptr channelMatrix, + Ptr channelParams, + PhasedArrayModel::ComplexVector doppler, + uint8_t numTxPorts, + uint8_t numRxPorts, + bool isReverse) const +{ + size_t numCluster = channelMatrix->m_channel.GetNumPages(); + auto numRb = inPsd->GetValuesN(); + + auto directionalLongTerm = isReverse ? longTerm->Transpose() : (*longTerm); + + Ptr chanSpct = + Create(numRxPorts, numTxPorts, (uint16_t)numRb); + + // If "params" (ChannelMatrix) and longTerm were computed for the reverse direction (e.g. this + // is a DL transmission but params and longTerm were last updated during UL), then the elements + // in longTerm start from different offsets. + + auto vit = inPsd->ValuesBegin(); // psd iterator + auto sbit = inPsd->ConstBandsBegin(); // band iterator + size_t iRb = 0; + // Compute the frequency-domain channel matrix + while (vit != inPsd->ValuesEnd()) { if ((*vit) != 0.00) { - std::complex subsbandGain(0.0, 0.0); double fsb = (*sbit).fc; // center frequency of the sub-band - for (uint16_t cIndex = 0; cIndex < numCluster; cIndex++) + for (auto rxPortIdx = 0; rxPortIdx < numRxPorts; rxPortIdx++) { - double delay = -2 * M_PI * fsb * (channelParams->m_delay[cIndex]); - subsbandGain = subsbandGain + longTerm[cIndex] * doppler[cIndex] * - std::complex(cos(delay), sin(delay)); + for (auto txPortIdx = 0; txPortIdx < numTxPorts; txPortIdx++) + { + std::complex subsbandGain(0.0, 0.0); + + for (size_t cIndex = 0; cIndex < numCluster; cIndex++) + { + double delay = -2 * M_PI * fsb * (channelParams->m_delay[cIndex]); + subsbandGain += directionalLongTerm(rxPortIdx, txPortIdx, cIndex) * + doppler[cIndex] * + std::complex(cos(delay), sin(delay)); + } + // Multiply with the square root of the input PSD so that the norm (absolute + // value squared) of chanSpct will be the output PSD + chanSpct->Elem(rxPortIdx, txPortIdx, iRb) = sqrt(*vit) * subsbandGain; + } } - *vit = (*vit) * (norm(subsbandGain)); } vit++; sbit++; + iRb++; } - return tempPsd; + return chanSpct; } -PhasedArrayModel::ComplexVector +Ptr ThreeGppSpectrumPropagationLossModel::GetLongTerm( Ptr channelMatrix, Ptr aPhasedArrayModel, Ptr bPhasedArrayModel) const { - PhasedArrayModel::ComplexVector + Ptr longTerm; // vector containing the long term component for each cluster // check if the channel matrix was generated considering a as the s-node and // b as the u-node or vice-versa + auto isReverse = + channelMatrix->IsReverse(aPhasedArrayModel->GetId(), bPhasedArrayModel->GetId()); + auto sAntenna = isReverse ? bPhasedArrayModel : aPhasedArrayModel; + auto uAntenna = isReverse ? aPhasedArrayModel : bPhasedArrayModel; + PhasedArrayModel::ComplexVector sW; PhasedArrayModel::ComplexVector uW; - if (!channelMatrix->IsReverse(aPhasedArrayModel->GetId(), bPhasedArrayModel->GetId())) + if (!isReverse) { sW = aPhasedArrayModel->GetBeamformingVector(); uW = bPhasedArrayModel->GetBeamformingVector(); @@ -305,24 +466,24 @@ ThreeGppSpectrumPropagationLossModel::GetLongTerm( { NS_LOG_DEBUG("compute the long term"); // compute the long term component - longTerm = CalcLongTerm(channelMatrix, sW, uW); - - // store the long term + longTerm = CalcLongTerm(channelMatrix, sAntenna, uAntenna); Ptr longTermItem = Create(); longTermItem->m_longTerm = longTerm; longTermItem->m_channel = channelMatrix; - longTermItem->m_sW = sW; - longTermItem->m_uW = uW; - + longTermItem->m_sW = std::move(sW); + longTermItem->m_uW = std::move(uW); + // store the long term to reduce computation load + // only the small scale fading needs to be updated if the large scale parameters and antenna + // weights remain unchanged. m_longTermMap[longTermId] = longTermItem; } return longTerm; } -Ptr +Ptr ThreeGppSpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( - Ptr params, + Ptr spectrumSignalParams, Ptr a, Ptr b, Ptr aPhasedArrayModel, @@ -336,14 +497,12 @@ ThreeGppSpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( NS_ASSERT_MSG(a->GetDistanceFrom(b) > 0.0, "The position of a and b devices cannot be the same"); - Ptr rxPsd = Copy(params->psd); - // retrieve the antenna of device a NS_ASSERT_MSG(aPhasedArrayModel, "Antenna not found for node " << aId); NS_LOG_DEBUG("a node " << a->GetObject() << " antenna " << aPhasedArrayModel); // retrieve the antenna of the device b - NS_ASSERT_MSG(bPhasedArrayModel, "Antenna not found for device " << bId); + NS_ASSERT_MSG(bPhasedArrayModel, "Antenna not found for node " << bId); NS_LOG_DEBUG("b node " << bId << " antenna " << bPhasedArrayModel); Ptr channelMatrix = @@ -352,18 +511,22 @@ ThreeGppSpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( m_channelModel->GetParams(a, b); // retrieve the long term component - PhasedArrayModel::ComplexVector longTerm = + Ptr longTerm = GetLongTerm(channelMatrix, aPhasedArrayModel, bPhasedArrayModel); - // apply the beamforming gain - rxPsd = CalcBeamformingGain(rxPsd, - longTerm, - channelMatrix, - channelParams, - a->GetVelocity(), - b->GetVelocity()); + auto isReverse = + channelMatrix->IsReverse(aPhasedArrayModel->GetId(), bPhasedArrayModel->GetId()); - return rxPsd; + // apply the beamforming gain + return CalcBeamformingGain(spectrumSignalParams, + longTerm, + channelMatrix, + channelParams, + a->GetVelocity(), + b->GetVelocity(), + aPhasedArrayModel->GetNumPorts(), + bPhasedArrayModel->GetNumPorts(), + isReverse); } } // namespace ns3 diff --git a/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.h b/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.h index 2fb056680..52018413f 100644 --- a/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.h +++ b/src/spectrum/model/three-gpp-spectrum-propagation-loss-model.h @@ -112,27 +112,27 @@ class ThreeGppSpectrumPropagationLossModel : public PhasedArraySpectrumPropagati * a certain channel is cached and recomputed only when the channel realization * is updated, or when the beamforming vectors change. * - * \param params tx parameters + * \param spectrumSignalParams spectrum signal tx parameters * \param a first node mobility model * \param b second node mobility model * \param aPhasedArrayModel the antenna array of the first node * \param bPhasedArrayModel the antenna array of the second node * \return the received PSD */ - Ptr DoCalcRxPowerSpectralDensity( - Ptr params, + Ptr DoCalcRxPowerSpectralDensity( + Ptr spectrumSignalParams, Ptr a, Ptr b, Ptr aPhasedArrayModel, Ptr bPhasedArrayModel) const override; - private: + protected: /** * Data structure that stores the long term component for a tx-rx pair */ struct LongTerm : public SimpleRefCount { - PhasedArrayModel::ComplexVector + Ptr m_longTerm; //!< vector containing the long term component for each cluster Ptr m_channel; //!< pointer to the channel matrix used to compute the long term @@ -142,6 +142,29 @@ class ThreeGppSpectrumPropagationLossModel : public PhasedArraySpectrumPropagati m_uW; //!< the beamforming vector for the node u used to compute the long term }; + /** + * Computes the frequency-domain channel matrix with the dimensions numRxPorts*numTxPorts*numRBs + * \param inPsd the input PSD + * \param longTerm the long term component + * \param channelMatrix the channel matrix structure + * \param channelParams the channel parameters, including delays + * \param doppler the doppler for each cluster + * \param numTxPorts the number of antenna ports at the transmitter + * \param numRxPorts the number of antenna ports at the receiver + * \param isReverse true if params and longTerm were computed with RX->TX switched + * \return 3D spectrum channel matrix with dimensions numRxPorts * numTxPorts * numRBs + */ + Ptr GenSpectrumChannelMatrix( + Ptr inPsd, + Ptr longTerm, + Ptr channelMatrix, + Ptr channelParams, + PhasedArrayModel::ComplexVector doppler, + uint8_t numTxPorts, + uint8_t numRxPorts, + bool isReverse) const; + + private: /** * Get the operating frequency * \return the operating frequency in Hz @@ -157,39 +180,64 @@ class ThreeGppSpectrumPropagationLossModel : public PhasedArraySpectrumPropagati * \param bPhasedArrayModel the antenna array of the rx device * \return vector containing the long term component for each cluster */ - PhasedArrayModel::ComplexVector GetLongTerm( + Ptr GetLongTerm( Ptr channelMatrix, Ptr aPhasedArrayModel, Ptr bPhasedArrayModel) const; /** * Computes the long term component * \param channelMatrix the channel matrix H - * \param sW the beamforming vector of the s device - * \param uW the beamforming vector of the u device + * \param sAnt the pointer to the antenna of the s device + * \param uAnt the pointer to the antenna of the u device * \return the long term component */ - PhasedArrayModel::ComplexVector CalcLongTerm( + Ptr CalcLongTerm( Ptr channelMatrix, - const PhasedArrayModel::ComplexVector& sW, - const PhasedArrayModel::ComplexVector& uW) const; + Ptr sAnt, + Ptr uAnt) const; /** - * Computes the beamforming gain and applies it to the tx PSD - * \param txPsd the tx PSD - * \param longTerm the long term component - * \param channelMatrix The channel matrix structure - * \param channelParams The channel params structure - * \param sSpeed speed of the first node - * \param uSpeed speed of the second node - * \return the rx PSD + * \brief Computes a longTerm component from a specific port of s device to the + * specific port of u device and for a specific cluster index + * \param params The params that include the channel matrix + * \param sAnt pointer to first antenna + * \param uAnt uAnt pointer to second antenna + * \param sPortIdx the port index of the s device + * \param uPortIdx the port index of the u device + * \param cIndex the cluster index + * \return longTerm component for port pair and for a specific cluster index */ - Ptr CalcBeamformingGain( - Ptr txPsd, - PhasedArrayModel::ComplexVector longTerm, + std::complex CalculateLongTermComponent( + Ptr params, + Ptr sAnt, + Ptr uAnt, + uint16_t sPortIdx, + uint16_t uPortIdx, + uint16_t cIndex) const; + + /** + * \brief Computes the beamforming gain and applies it to the TX PSD + * \param params SpectrumSignalParameters holding TX PSD + * \param longTerm the long term component + * \param channelMatrix the channel matrix structure + * \param channelParams the channel params structure + * \param sSpeed the speed of the first node + * \param uSpeed the speed of the second node + * \param numTxPorts the number of the ports of the first node + * \param numRxPorts the number of the porst of the second node + * \param isReverse indicator that tells whether the channel matrix is reverse + * \return + */ + Ptr CalcBeamformingGain( + Ptr params, + Ptr longTerm, Ptr channelMatrix, Ptr channelParams, const Vector& sSpeed, - const Vector& uSpeed) const; + const Vector& uSpeed, + uint8_t numTxPorts, + uint8_t numRxPorts, + bool isReverse) const; mutable std::unordered_map> m_longTermMap; //!< map containing the long term components diff --git a/src/spectrum/model/two-ray-spectrum-propagation-loss-model.cc b/src/spectrum/model/two-ray-spectrum-propagation-loss-model.cc index c358a171d..348bf9470 100644 --- a/src/spectrum/model/two-ray-spectrum-propagation-loss-model.cc +++ b/src/spectrum/model/two-ray-spectrum-propagation-loss-model.cc @@ -1112,7 +1112,7 @@ TwoRaySpectrumPropagationLossModel::GetFtrFastFading(const FtrParams& params) co return norm(h); } -Ptr +Ptr TwoRaySpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( Ptr params, Ptr a, @@ -1128,8 +1128,6 @@ TwoRaySpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( NS_ASSERT_MSG(a->GetDistanceFrom(b) > 0.0, "The position of a and b devices cannot be the same"); - Ptr rxPsd = Copy(params->psd); - // Retrieve the antenna of device a NS_ASSERT_MSG(aPhasedArrayModel, "Antenna not found for node " << aId); NS_LOG_DEBUG("a node " << a->GetObject() << " antenna " << aPhasedArrayModel); @@ -1147,10 +1145,11 @@ TwoRaySpectrumPropagationLossModel::DoCalcRxPowerSpectralDensity( // Compute the beamforming gain double bfGain = CalcBeamformingGain(a, b, aPhasedArrayModel, bPhasedArrayModel); - // Apply the above terms to the TX PSD - *rxPsd *= (fading * bfGain); + Ptr rxParams = params->Copy(); + // Apply the above terms to the TX PSD to calculate RX PSD + (*(rxParams->psd)) *= (fading * bfGain); - return rxPsd; + return rxParams; } std::size_t diff --git a/src/spectrum/model/two-ray-spectrum-propagation-loss-model.h b/src/spectrum/model/two-ray-spectrum-propagation-loss-model.h index b5ffeac81..d2c57974c 100644 --- a/src/spectrum/model/two-ray-spectrum-propagation-loss-model.h +++ b/src/spectrum/model/two-ray-spectrum-propagation-loss-model.h @@ -186,9 +186,9 @@ class TwoRaySpectrumPropagationLossModel : public PhasedArraySpectrumPropagation * \param b second node mobility model * \param aPhasedArrayModel the antenna array of the first node * \param bPhasedArrayModel the antenna array of the second node - * \return the PSD of the received signal + * \return SpectrumSignalParameters including the PSD of the received signal */ - Ptr DoCalcRxPowerSpectralDensity( + Ptr DoCalcRxPowerSpectralDensity( Ptr txPsd, Ptr a, Ptr b, diff --git a/src/spectrum/test/three-gpp-channel-test-suite.cc b/src/spectrum/test/three-gpp-channel-test-suite.cc index a06b74156..9840b139f 100644 --- a/src/spectrum/test/three-gpp-channel-test-suite.cc +++ b/src/spectrum/test/three-gpp-channel-test-suite.cc @@ -53,7 +53,10 @@ class ThreeGppChannelMatrixComputationTest : public TestCase /** * Constructor */ - ThreeGppChannelMatrixComputationTest(); + ThreeGppChannelMatrixComputationTest(uint32_t txAntennaElements = 2, + uint32_t rxAntennaElements = 2, + uint32_t txPorts = 1, + uint32_t rxPorts = 1); /** * Destructor @@ -81,11 +84,23 @@ class ThreeGppChannelMatrixComputationTest : public TestCase Ptr rxAntenna); std::vector m_normVector; //!< each element is the norm of a channel realization + uint32_t m_txAntennaElements{4}; //!< number of rows and columns of tx antenna array + uint32_t m_rxAntennaElements{4}; //!< number of rows and columns of rx antenna array + uint32_t m_txPorts{1}; //!< number of horizontal and vertical ports of tx antenna array + uint32_t m_rxPorts{1}; //!< number of horizontal and vertical ports of rx antenna array }; -ThreeGppChannelMatrixComputationTest::ThreeGppChannelMatrixComputationTest() +ThreeGppChannelMatrixComputationTest::ThreeGppChannelMatrixComputationTest( + uint32_t txAntennaElements, + uint32_t rxAntennaElements, + uint32_t txPorts, + uint32_t rxPorts) : TestCase("Check the dimensions and the norm of the channel matrix") { + m_txAntennaElements = txAntennaElements; + m_rxAntennaElements = rxAntennaElements; + m_txPorts = txPorts; + m_rxPorts = rxPorts; } ThreeGppChannelMatrixComputationTest::~ThreeGppChannelMatrixComputationTest() @@ -127,9 +142,7 @@ void ThreeGppChannelMatrixComputationTest::DoRun() { // Build the scenario for the test - uint8_t txAntennaElements[]{2, 2}; // tx antenna dimensions - uint8_t rxAntennaElements[]{2, 2}; // rx antenna dimensions - uint32_t updatePeriodMs = 100; // update period in ms + uint32_t updatePeriodMs = 100; // update period in ms // create the channel condition model Ptr channelConditionModel = @@ -169,19 +182,27 @@ ThreeGppChannelMatrixComputationTest::DoRun() // create the tx and rx antennas and set the their dimensions Ptr txAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(txAntennaElements[0]), + UintegerValue(m_txAntennaElements), "NumRows", - UintegerValue(txAntennaElements[1]), + UintegerValue(m_txAntennaElements), "AntennaElement", - PointerValue(CreateObject())); + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_txPorts), + "NumHorizontalPorts", + UintegerValue(m_txPorts)); + Ptr rxAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(rxAntennaElements[0]), + UintegerValue(m_rxAntennaElements), "NumRows", - UintegerValue(rxAntennaElements[1]), + UintegerValue(m_rxAntennaElements), "AntennaElement", - PointerValue(CreateObject())); - + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_rxPorts), + "NumHorizontalPorts", + UintegerValue(m_rxPorts)); // generate the channel matrix Ptr channelMatrix = channelModel->GetChannel(txMob, rxMob, txAntenna, rxAntenna); @@ -189,11 +210,11 @@ ThreeGppChannelMatrixComputationTest::DoRun() // check the channel matrix dimensions, expected H[cluster][rx][tx] NS_TEST_ASSERT_MSG_EQ( channelMatrix->m_channel.GetNumCols(), - txAntennaElements[0] * txAntennaElements[1], + m_txAntennaElements * m_txAntennaElements, "The third dimension of H should be equal to the number of tx antenna elements"); NS_TEST_ASSERT_MSG_EQ( channelMatrix->m_channel.GetNumRows(), - rxAntennaElements[0] * rxAntennaElements[1], + m_rxAntennaElements * m_rxAntennaElements, "The second dimension of H should be equal to the number of rx antenna elements"); // test if the channel matrix is correctly generated @@ -232,8 +253,8 @@ ThreeGppChannelMatrixComputationTest::DoRun() // the hypothesis "E [|H|^2] = M*N, where |H| indicates the Frobenius norm of // H, M is the number of transmit antenna elements, and N is the number of // the receive antenna elements" - double t = (sampleMean - txAntennaElements[0] * txAntennaElements[1] * rxAntennaElements[0] * - rxAntennaElements[1]) / + double t = (sampleMean - m_txAntennaElements * m_txAntennaElements * m_rxAntennaElements * + m_rxAntennaElements) / (sampleStd / std::sqrt(numIt)); // Using a significance level of 0.05, we reject the null hypothesis if |t| is @@ -260,7 +281,10 @@ class ThreeGppChannelMatrixUpdateTest : public TestCase /** * Constructor */ - ThreeGppChannelMatrixUpdateTest(); + ThreeGppChannelMatrixUpdateTest(uint32_t txAntennaElements = 2, + uint32_t rxAntennaElements = 4, + uint32_t txPorts = 1, + uint32_t rxPorts = 1); /** * Destructor @@ -291,12 +315,23 @@ class ThreeGppChannelMatrixUpdateTest : public TestCase bool update); Ptr - m_currentChannel; //!< used by DoGetChannel to store the current channel matrix + m_currentChannel; //!< used by DoGetChannel to store the current channel matrix + uint32_t m_txAntennaElements{4}; //!< number of rows and columns of tx antenna array + uint32_t m_rxAntennaElements{4}; //!< number of rows and columns of rx antenna array + uint32_t m_txPorts{1}; //!< number of horizontal and vertical ports of tx antenna array + uint32_t m_rxPorts{1}; //!< number of horizontal and vertical ports of rx antenna array }; -ThreeGppChannelMatrixUpdateTest::ThreeGppChannelMatrixUpdateTest() +ThreeGppChannelMatrixUpdateTest::ThreeGppChannelMatrixUpdateTest(uint32_t txAntennaElements, + uint32_t rxAntennaElements, + uint32_t txPorts, + uint32_t rxPorts) : TestCase("Check if the channel realizations are correctly updated during the simulation") { + m_txAntennaElements = txAntennaElements; + m_rxAntennaElements = rxAntennaElements; + m_txPorts = txPorts; + m_rxPorts = rxPorts; } ThreeGppChannelMatrixUpdateTest::~ThreeGppChannelMatrixUpdateTest() @@ -324,7 +359,7 @@ ThreeGppChannelMatrixUpdateTest::DoGetChannel(Ptr channelM else { // compare the old and the new channel matrices - NS_TEST_ASSERT_MSG_EQ((m_currentChannel != channelMatrix), + NS_TEST_ASSERT_MSG_EQ((m_currentChannel->m_channel != channelMatrix->m_channel), update, Simulator::Now().GetMilliSeconds() << " The channel matrix is not correctly updated"); @@ -335,10 +370,7 @@ void ThreeGppChannelMatrixUpdateTest::DoRun() { // Build the scenario for the test - - uint8_t txAntennaElements[]{2, 2}; // tx antenna dimensions - uint8_t rxAntennaElements[]{4, 4}; // rx antenna dimensions - uint32_t updatePeriodMs = 100; // update period in ms + uint32_t updatePeriodMs = 100; // update period in ms // create the channel condition model Ptr channelConditionModel = @@ -378,18 +410,27 @@ ThreeGppChannelMatrixUpdateTest::DoRun() // create the tx and rx antennas and set the their dimensions Ptr txAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(txAntennaElements[0]), + UintegerValue(m_txAntennaElements), "NumRows", - UintegerValue(txAntennaElements[1]), + UintegerValue(m_txAntennaElements), "AntennaElement", - PointerValue(CreateObject())); + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_txPorts), + "NumHorizontalPorts", + UintegerValue(m_txPorts)); + Ptr rxAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(rxAntennaElements[0]), + UintegerValue(m_rxAntennaElements), "NumRows", - UintegerValue(rxAntennaElements[1]), + UintegerValue(m_rxAntennaElements), "AntennaElement", - PointerValue(CreateObject())); + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_rxPorts), + "NumHorizontalPorts", + UintegerValue(m_rxPorts)); // check if the channel matrix is correctly updated @@ -468,8 +509,10 @@ class ThreeGppSpectrumPropagationLossModelTest : public TestCase /** * Constructor */ - ThreeGppSpectrumPropagationLossModelTest(); - + ThreeGppSpectrumPropagationLossModelTest(uint32_t txAntennaElements = 4, + uint32_t rxAntennaElements = 4, + uint32_t txPorts = 1, + uint32_t rxPorts = 1); /** * Destructor */ @@ -501,18 +544,23 @@ class ThreeGppSpectrumPropagationLossModelTest : public TestCase */ void CheckLongTermUpdate(const CheckLongTermUpdateParams& params); - /** - * Checks if two PSDs are equal - * \param first the first PSD - * \param second the second PSD - * \return true if first and second are equal, false otherwise - */ - static bool ArePsdEqual(Ptr first, Ptr second); + uint32_t m_txAntennaElements{4}; //!< number of rows and columns of tx antenna array + uint32_t m_rxAntennaElements{4}; //!< number of rows and columns of rx antenna array + uint32_t m_txPorts{1}; //!< number of horizontal and vertical ports of tx antenna array + uint32_t m_rxPorts{1}; //!< number of horizontal and vertical ports of rx antenna array }; -ThreeGppSpectrumPropagationLossModelTest::ThreeGppSpectrumPropagationLossModelTest() +ThreeGppSpectrumPropagationLossModelTest::ThreeGppSpectrumPropagationLossModelTest( + uint32_t txAntennaElements, + uint32_t rxAntennaElements, + uint32_t txPorts, + uint32_t rxPorts) : TestCase("Test case for the ThreeGppSpectrumPropagationLossModel class") { + m_txAntennaElements = txAntennaElements; + m_rxAntennaElements = rxAntennaElements; + m_txPorts = txPorts; + m_rxPorts = rxPorts; } ThreeGppSpectrumPropagationLossModelTest::~ThreeGppSpectrumPropagationLossModelTest() @@ -536,32 +584,16 @@ ThreeGppSpectrumPropagationLossModelTest::DoBeamforming(Ptr thisDevic thisAntenna->SetBeamformingVector(antennaWeights); } -bool -ThreeGppSpectrumPropagationLossModelTest::ArePsdEqual(Ptr first, - Ptr second) -{ - bool ret = true; - for (uint8_t i = 0; i < first->GetSpectrumModel()->GetNumBands(); i++) - { - if ((*first)[i] != (*second)[i]) - { - ret = false; - continue; - } - } - return ret; -} - void ThreeGppSpectrumPropagationLossModelTest::CheckLongTermUpdate( const CheckLongTermUpdateParams& params) { - Ptr rxPsdNew = params.lossModel->DoCalcRxPowerSpectralDensity(params.txParams, - params.txMob, - params.rxMob, - params.txAntenna, - params.rxAntenna); - NS_TEST_ASSERT_MSG_EQ(ArePsdEqual(params.rxPsdOld, rxPsdNew), + auto rxPsdNewParams = params.lossModel->DoCalcRxPowerSpectralDensity(params.txParams, + params.txMob, + params.rxMob, + params.txAntenna, + params.rxAntenna); + NS_TEST_ASSERT_MSG_EQ((*params.rxPsdOld == *rxPsdNewParams->psd), false, "The long term is not updated when the channel matrix is recomputed"); } @@ -572,9 +604,6 @@ ThreeGppSpectrumPropagationLossModelTest::DoRun() // Build the scenario for the test Config::SetDefault("ns3::ThreeGppChannelModel::UpdatePeriod", TimeValue(MilliSeconds(100))); - uint8_t txAntennaElements[]{4, 4}; // tx antenna dimensions - uint8_t rxAntennaElements[]{4, 4}; // rx antenna dimensions - // create the ChannelConditionModel object to be used to retrieve the // channel condition Ptr condModel = CreateObject(); @@ -618,18 +647,27 @@ ThreeGppSpectrumPropagationLossModelTest::DoRun() // create the tx and rx antennas and set the their dimensions Ptr txAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(txAntennaElements[0]), + UintegerValue(m_txAntennaElements), "NumRows", - UintegerValue(txAntennaElements[1]), + UintegerValue(m_rxAntennaElements), "AntennaElement", - PointerValue(CreateObject())); + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_txPorts), + "NumHorizontalPorts", + UintegerValue(m_txPorts)); + Ptr rxAntenna = CreateObjectWithAttributes( "NumColumns", - UintegerValue(rxAntennaElements[0]), + UintegerValue(m_rxAntennaElements), "NumRows", - UintegerValue(rxAntennaElements[1]), + UintegerValue(m_rxAntennaElements), "AntennaElement", - PointerValue(CreateObject())); + PointerValue(CreateObject()), + "NumVerticalPorts", + UintegerValue(m_rxPorts), + "NumHorizontalPorts", + UintegerValue(m_rxPorts)); // set the beamforming vectors DoBeamforming(txDev, txAntenna, rxDev, rxAntenna); @@ -644,13 +682,13 @@ ThreeGppSpectrumPropagationLossModelTest::DoRun() txParams->psd = txPsd->Copy(); // compute the rx psd - Ptr rxPsdOld = + auto rxParamsOld = lossModel->DoCalcRxPowerSpectralDensity(txParams, txMob, rxMob, txAntenna, rxAntenna); // 1) check that the rx PSD is equal for both the direct and the reverse channel - Ptr rxPsdNew = + auto rxParamsNew = lossModel->DoCalcRxPowerSpectralDensity(txParams, rxMob, txMob, rxAntenna, txAntenna); - NS_TEST_ASSERT_MSG_EQ(ArePsdEqual(rxPsdOld, rxPsdNew), + NS_TEST_ASSERT_MSG_EQ((*rxParamsOld->psd == *rxParamsNew->psd), true, "The long term for the direct and the reverse channel are different"); @@ -661,18 +699,23 @@ ThreeGppSpectrumPropagationLossModelTest::DoRun() txBfVector[0] = std::complex(0.0, 0.0); txAntenna->SetBeamformingVector(txBfVector); - rxPsdNew = + rxParamsNew = lossModel->DoCalcRxPowerSpectralDensity(txParams, rxMob, txMob, rxAntenna, txAntenna); - NS_TEST_ASSERT_MSG_EQ(ArePsdEqual(rxPsdOld, rxPsdNew), + NS_TEST_ASSERT_MSG_EQ((*rxParamsOld->psd == *rxParamsNew->psd), false, "Changing the BF vectors the rx PSD does not change"); + NS_TEST_ASSERT_MSG_EQ( + (*rxParamsOld->spectrumChannelMatrix == *rxParamsNew->spectrumChannelMatrix), + false, + "Changing the BF should change de frequency domain channel matrix"); + // update rxPsdOld - rxPsdOld = rxPsdNew; + rxParamsOld = rxParamsNew; // 3) check if the long term is updated when the channel matrix is recomputed CheckLongTermUpdateParams - params{lossModel, txParams, txMob, rxMob, rxPsdOld, txAntenna, rxAntenna}; + params{lossModel, txParams, txMob, rxMob, rxParamsOld->psd, txAntenna, rxAntenna}; Simulator::Schedule(MilliSeconds(101), &ThreeGppSpectrumPropagationLossModelTest::CheckLongTermUpdate, this, @@ -699,9 +742,19 @@ class ThreeGppChannelTestSuite : public TestSuite ThreeGppChannelTestSuite::ThreeGppChannelTestSuite() : TestSuite("three-gpp-channel", UNIT) { - AddTestCase(new ThreeGppChannelMatrixComputationTest, TestCase::QUICK); - AddTestCase(new ThreeGppChannelMatrixUpdateTest, TestCase::QUICK); - AddTestCase(new ThreeGppSpectrumPropagationLossModelTest, TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixComputationTest(2, 2, 1, 1), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixComputationTest(4, 2, 1, 1), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixComputationTest(2, 2, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixComputationTest(4, 4, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixComputationTest(4, 2, 2, 1), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixUpdateTest(2, 4, 1, 1), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixUpdateTest(2, 2, 1, 1), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixUpdateTest(2, 4, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppChannelMatrixUpdateTest(2, 2, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppSpectrumPropagationLossModelTest(4, 4, 1, 1), TestCase::QUICK); + AddTestCase(new ThreeGppSpectrumPropagationLossModelTest(4, 4, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppSpectrumPropagationLossModelTest(4, 2, 2, 2), TestCase::QUICK); + AddTestCase(new ThreeGppSpectrumPropagationLossModelTest(4, 2, 2, 1), TestCase::QUICK); } /// Static variable for test initialization diff --git a/src/spectrum/test/two-ray-splm-test-suite.cc b/src/spectrum/test/two-ray-splm-test-suite.cc index e5deb9418..b13369609 100644 --- a/src/spectrum/test/two-ray-splm-test-suite.cc +++ b/src/spectrum/test/two-ray-splm-test-suite.cc @@ -561,15 +561,15 @@ OverallGainAverageTest::DoRun() auto rxBfVec = rxArray->GetBeamformingVector(Angles(txPosVec, rxPosVec)); rxArray->SetBeamformingVector(rxBfVec); - auto twoRayRxPsd = + auto twoRayRxParams = twoRaySplm->DoCalcRxPowerSpectralDensity(signalParams, txMob, rxMob, txArray, rxArray); - auto threeGppRayRxPsd = threeGppSplm->DoCalcRxPowerSpectralDensity(signalParams, - txMob, - rxMob, - txArray, - rxArray); - double twoRayRxPower = ComputePowerSpectralDensityOverallPower(twoRayRxPsd); - double threeGppRxPower = ComputePowerSpectralDensityOverallPower(threeGppRayRxPsd); + auto threeGppRayRxParams = threeGppSplm->DoCalcRxPowerSpectralDensity(signalParams, + txMob, + rxMob, + txArray, + rxArray); + double twoRayRxPower = ComputePowerSpectralDensityOverallPower(twoRayRxParams->psd); + double threeGppRxPower = ComputePowerSpectralDensityOverallPower(threeGppRayRxParams->psd); twoRayGainMean += (twoRayRxPower / txPower); threeGppGainMean += (threeGppRxPower / txPower);