Skip to content

Commit 93d3f83

Browse files
committed
spectrum: Enhance three-gpp-channel-example traces/docs; add matched-filter SNR
1 parent 80c131f commit 93d3f83

File tree

1 file changed

+163
-17
lines changed

1 file changed

+163
-17
lines changed

src/spectrum/examples/three-gpp-channel-example.cc

Lines changed: 163 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
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
*/
65114
static 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

Comments
 (0)