1515 * 3D-urban macro (UMa), and it can be configured changing the value of the
1616 * string "scenario".
1717 * Each node hosts has an antenna array with 4 antenna elements.
18+ *
19+ * The example writes its main results to the file ``snr-trace.txt``.
20+ * Each row contains: (i) the simulation time (s), (ii) the SNR obtained with
21+ * steering vectors (``DoBeamforming``), (iii) the SNR obtained with matched-filter
22+ * combining, and (iv) the propagation gain.
1823 */
1924
2025#include " ns3/channel-condition-model.h"
2126#include " ns3/constant-position-mobility-model.h"
27+ #include " ns3/constant-velocity-mobility-model.h"
2228#include " ns3/core-module.h"
2329#include " ns3/lte-spectrum-value-helper.h"
2430#include " ns3/mobility-model.h"
@@ -57,13 +63,59 @@ struct ComputeSnrParams
5763};
5864
5965/* *
60- * Perform the beamforming using the DFT beamforming method
61- * @param txMob the mobility model of the node performing the beamforming
62- * @param thisAntenna the antenna object associated to thisDevice
63- * @param rxMob the mobility model of the node towards which will point the beam
66+ * Perform a simple (DFT/steering) beamforming toward the other node.
67+ *
68+ * This function builds a per-element complex weight vector based on the geometric
69+ * direction between two nodes and the antenna element locations returned by
70+ * `PhasedArrayModel::GetElementLocation()`. The weights are unit-norm (equal
71+ * power across elements) and have the form:
72+ *
73+ * w[i] = exp(j * phase_i) / sqrt(N),
74+ * phase_i = sign * 2*pi * ( u · r_i )
75+ *
76+ * where `u` is the unit direction corresponding to the azimuth/inclination
77+ * angles of the line-of-sight vector between the nodes, `r_i` is the location of
78+ * element `i` in the array coordinate system, and `N` is the number of antenna
79+ * elements.
80+ *
81+ * Sign convention and how to use this function:
82+ *
83+ * The 3GPP channel coefficients are constructed using spatial phase terms of the
84+ * form exp(+j*2*pi*(u·r)) (see `ThreeGppChannelModel::GetNewChannel()`), i.e., the
85+ * array *response/steering vector* toward a direction is proportional to:
86+ *
87+ * a(theta)[i] = exp(+j*2*pi*(u·r_i)).
88+ *
89+ * In standard array processing, a transmit precoder that steers energy toward
90+ * that direction uses the complex conjugate of the response, therefore
91+ * sign = -1 (phase = -2*pi*(u·r_i)).
92+ *
93+ * For receive combining, the effective scalar channel is computed using the
94+ * Hermitian inner product:
95+ *
96+ * h_eff = w_rx^H * H * w_tx .
97+ *
98+ * To obtain matched-filter (maximum-ratio) receive combining toward the same
99+ * direction, the stored receive weight vector should be proportional to the
100+ * response a(theta), so that `w_rx^H` applies the conjugation at combining time
101+ * sign = +1 (phase = +2*pi*(u·r_i)).
102+ *
103+ * Therefore, when this function is used to set beamforming vectors explicitly in
104+ * this example:
105+ * - Use `sign = -1` when generating TX weights (precoding).
106+ * - Use `sign = +1` when generating RX weights (so that Hermitian combining
107+ * applies the conjugation).
108+ *
109+ * @param txMob The mobility model of the node for which the weights are generated.
110+ * @param thisAntenna The antenna array on which the beamforming vector is set.
111+ * @param rxMob The mobility model of the peer node toward/from which the beam is steered.
112+ * @param sign Phase progression sign: typically -1 for TX weights and +1 for RX weights.
64113 */
65114static void
66- DoBeamforming (Ptr<MobilityModel> txMob, Ptr<PhasedArrayModel> thisAntenna, Ptr<MobilityModel> rxMob)
115+ DoBeamforming (Ptr<MobilityModel> txMob,
116+ Ptr<PhasedArrayModel> thisAntenna,
117+ Ptr<MobilityModel> rxMob,
118+ double sign)
67119{
68120 // retrieve the position of the two nodes
69121 Vector aPos = txMob->GetPosition ();
@@ -91,7 +143,7 @@ DoBeamforming(Ptr<MobilityModel> txMob, Ptr<PhasedArrayModel> thisAntenna, Ptr<M
91143 for (uint64_t ind = 0 ; ind < totNoArrayElements; ind++)
92144 {
93145 Vector loc = thisAntenna->GetElementLocation (ind);
94- double phase = - 2 * M_PI *
146+ double phase = sign * 2 * M_PI *
95147 (sinVAngleRadian * cosHAngleRadian * loc.x +
96148 sinVAngleRadian * sinHAngleRadian * loc.y + cosVAngleRadian * loc.z );
97149 antennaWeights[ind] = exp (std::complex <double >(0 , phase)) * power;
@@ -101,6 +153,76 @@ DoBeamforming(Ptr<MobilityModel> txMob, Ptr<PhasedArrayModel> thisAntenna, Ptr<M
101153 thisAntenna->SetBeamformingVector (antennaWeights);
102154}
103155
156+ /* *
157+ * Build the RX matched-filter (maximum-ratio) combining vector by aggregating
158+ * the contributions of all clusters and normalizing the result:
159+ * \f[
160+ * \mathbf{g} = \sum_{c} \mathbf{H}_{c}\mathbf{w}_{\rm tx}, \qquad
161+ * \mathbf{w}_{\rm rx} = \frac{\mathbf{g}}{\|\mathbf{g}\|}
162+ * \f]
163+ *
164+ * The per-cluster channel matrix \f$\mathbf{H}_{c}\f$ is obtained from
165+ * ``params->m_channel(u, s, c)`` (u: RX element index, s: TX element index, c: cluster index).
166+ *
167+ * @param params Channel parameters containing the per-cluster channel matrix.
168+ * @param txAntenna TX phased-array model.
169+ * @param rxAntenna RX phased-array model.
170+ * @param wTx Unit-norm TX beamforming/precoding vector (size must match
171+ * ``txAntenna->GetNumElems()``).
172+ *
173+ * @return The RX combining vector (size ``rxAntenna->GetNumElems()``), normalized to unit norm
174+ * (unless the channel is all-zero, in which case a zero vector is returned).
175+ */
176+
177+ static ns3::PhasedArrayModel::ComplexVector
178+ BuildRxMatchedFilterOverAllClusters (
179+ ns3::Ptr<const ns3::MatrixBasedChannelModel::ChannelMatrix> params,
180+ ns3::Ptr<const ns3::PhasedArrayModel> txAntenna,
181+ ns3::Ptr<const ns3::PhasedArrayModel> rxAntenna,
182+ const ns3::PhasedArrayModel::ComplexVector& wTx)
183+ {
184+ using C = std::complex <double >;
185+
186+ const uint64_t nTx = txAntenna->GetNumElems ();
187+ const uint64_t nRx = rxAntenna->GetNumElems ();
188+ const size_t numClusters = params->m_channel .GetNumPages ();
189+
190+ NS_ASSERT_MSG (wTx.GetSize() == nTx, "wTx size does not match TX antenna elements");
191+ NS_ASSERT_MSG (params->m_channel.GetNumRows() == nRx, "Channel rows != #RX elements" );
192+ NS_ASSERT_MSG(params->m_channel.GetNumCols() == nTx, " Channel cols != #TX elements" );
193+
194+ ns3::PhasedArrayModel::ComplexVector g(nRx);
195+
196+ // g = sum_c (H_c * wTx)
197+ for (size_t c = 0; c < numClusters; ++c)
198+ {
199+ for (uint64_t u = 0; u < nRx; ++u)
200+ {
201+ C acc{0.0, 0.0};
202+ for (uint64_t s = 0; s < nTx; ++s)
203+ {
204+ acc += params->m_channel(u, s, c) * wTx[s];
205+ }
206+ g[u] += acc;
207+ }
208+ }
209+ // Normalize: wRx = g / ||g||
210+ double norm2 = 0.0;
211+ for (uint64_t u = 0; u < nRx; ++u)
212+ {
213+ norm2 += std::norm(g[u]);
214+ }
215+ if (norm2 > 0.0)
216+ {
217+ const double invNorm = 1.0 / std::sqrt(norm2);
218+ for (uint64_t u = 0; u < nRx; ++u)
219+ {
220+ g[u] *= invNorm;
221+ }
222+ }
223+ return g; // unit-norm (unless channel is all-zero)
224+ }
225+
104226/**
105227 * Compute the average SNR
106228 * @param params A structure that holds the parameters that are needed to perform calculations in
@@ -137,22 +259,49 @@ ComputeSnr(const ComputeSnrParams& params)
137259 NS_ASSERT_MSG(params.txAntenna, " params.txAntenna is nullptr !" );
138260 NS_ASSERT_MSG(params.rxAntenna, " params.rxAntenna is nullptr !" );
139261
262+ // set/update the beamforming vectors on each call
263+ double sign = -1.0;
264+ DoBeamforming(params.txMob, params.txAntenna, params.rxMob, sign);
265+ sign = +1.0;
266+ DoBeamforming(params.rxMob, params.rxAntenna, params.txMob, sign);
140267 // apply the fast fading and the beamforming gain
141268 auto rxParams = m_spectrumLossModel->CalcRxPowerSpectralDensity(txParams,
142269 params.txMob,
143270 params.rxMob,
144271 params.txAntenna,
145272 params.rxAntenna);
146- auto rxPsd = rxParams->psd ;
147- NS_LOG_DEBUG (" Average rx power " << 10 * log10 (Sum(*rxPsd) * 180e3) << " dB");
148-
149- // compute the SNR
150- NS_LOG_DEBUG (" Average SNR " << 10 * log10 (Sum(*rxPsd) / Sum(*noisePsd)) << " dB");
273+ auto rxPsdSteering = rxParams->psd;
274+ NS_LOG_DEBUG(" Average rx power " << 10 * log10(Sum(*rxPsdSteering) * 180e3) << " dB" );
275+ NS_LOG_DEBUG(" Average SNR " << 10 * log10(Sum(*rxPsdSteering) / Sum(*noisePsd)) << " dB" );
276+
277+ Ptr<ThreeGppChannelModel> channelModel =
278+ DynamicCast<ThreeGppChannelModel>(m_spectrumLossModel->GetChannelModel());
279+ Ptr<const ThreeGppChannelModel::ChannelMatrix> channelMatrix =
280+ channelModel->GetChannel(params.txMob, params.rxMob, params.txAntenna, params.rxAntenna);
281+
282+ const auto& wTx = params.txAntenna->GetBeamformingVectorRef();
283+ auto wRx =
284+ BuildRxMatchedFilterOverAllClusters(channelMatrix, params.txAntenna, params.rxAntenna, wTx);
285+ params.rxAntenna->SetBeamformingVector(wRx); // Install RX matched-filter (MRC) combiner
286+
287+ auto rxParamsMatchedFilterComb =
288+ m_spectrumLossModel->CalcRxPowerSpectralDensity(txParams,
289+ params.txMob,
290+ params.rxMob,
291+ params.txAntenna,
292+ params.rxAntenna);
293+
294+ auto rxPsdMatchedFilterComb = rxParamsMatchedFilterComb->psd;
295+ NS_LOG_DEBUG(" Average rx power when using matched filter combining "
296+ << 10 * log10(Sum(*rxPsdMatchedFilterComb) * 180e3) << " dB" );
297+ NS_LOG_DEBUG(" Average SNR when using matched filter combining "
298+ << 10 * log10(Sum(*rxPsdMatchedFilterComb) / Sum(*noisePsd)) << " dB" );
151299
152300 // print the SNR and pathloss values in the snr-trace.txt file
153301 std::ofstream f;
154302 f.open(" snr-trace.txt " , std::ios::out | std::ios::app);
155- f << Simulator::Now().GetSeconds() << " " << 10 * log10(Sum(*rxPsd) / Sum(*noisePsd)) << " "
303+ f << Simulator::Now().GetSeconds() << " " << 10 * log10(Sum(*rxPsdSteering) / Sum(*noisePsd))
304+ << " " << 10 * log10(Sum(*rxPsdMatchedFilterComb) / Sum(*noisePsd)) << " "
156305 << propagationGainDb << std::endl;
157306 f.close();
158307}
@@ -249,8 +398,9 @@ main(int argc, char* argv[])
249398 // create the tx and rx mobility models, set the positions
250399 Ptr<MobilityModel> txMob = CreateObject<ConstantPositionMobilityModel>();
251400 txMob->SetPosition(Vector(0.0, 0.0, 10.0));
252- Ptr<MobilityModel > rxMob = CreateObject<ConstantPositionMobilityModel >();
401+ Ptr<ConstantVelocityMobilityModel > rxMob = CreateObject<ConstantVelocityMobilityModel >();
253402 rxMob->SetPosition(Vector(distance, 0.0, 1.6));
403+ rxMob->SetVelocity(Vector(0.5, 0, 0)); // set small velocity to allow for channel updates
254404
255405 // assign the mobility models to the nodes
256406 nodes.Get(0)->AggregateObject(txMob);
@@ -268,10 +418,6 @@ main(int argc, char* argv[])
268418 " NumRows" ,
269419 UintegerValue(2));
270420
271- // set the beamforming vectors
272- DoBeamforming (txMob, txAntenna, rxMob);
273- DoBeamforming (rxMob, rxAntenna, txMob);
274-
275421 for (int i = 0; i < floor(simTime / timeRes); i++)
276422 {
277423 ComputeSnrParams params{txMob, rxMob, txPow, noiseFigure, txAntenna, rxAntenna};
0 commit comments