Skip to content

Commit 281e7a4

Browse files
committed
spectrum: Refine 3GPP channel consistency procedure, tests, and documentation
This commit refines the 3GPP TR 38.901 channel consistency implementation (Procedure A, Sec. 7.6.3.2) to better align with the specification and to clarify behavior across different scenarios (including mobility). The changes include: Correct handling of cluster-level correlation terms (e.g., shadowing) and cached per-link randomness needed for cluster generation (incl. NLOS Xn signs) Updates to ray/cluster evolution during channel updates and removal of random coupling between update steps Clear distinction between channel re-generation vs cached-channel update behavior, controlled by UpdatePeriod Consistent displacement computation across code and correct TX/RX speed mapping Fix channel-consistency triggering for moving endpoints and handle NLOSv cases Enforce canonical ordering for link direction, make updates order-independent, and tighten assertions Ensure per-cluster state vectors are consistently sized to the reduced cluster number, with consistent trim/add logic across generation and update paths. Add test, example and plot script for spatial consistency feature. Add documentation for spatial consistency feature (spectrum.rst, CHANGES.md, RELEASE_NOTES.md) clarifying that TR 38.901 Sec. 7.6.3.1 correlation across multiple initial locations is not implemented. Extend/fix test cases for realistic channel updates and improve robustness of the 3GPP channel tests. Refactoring and cleanup of ThreeGppChannelModel and related examples/test; run/update relevant example coverage (incl. V2V) Clarify Procedure A vs map/field-based approaches. Address comments by Tom Henderson, Tommaso Pecorella, Peter Barnes and Eduardo Almeida. Co-authored-by: Sandra Lagen sandra.lagen@cttc.cat
1 parent 8d7ee8f commit 281e7a4

18 files changed

+3746
-2131
lines changed

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ fixes, use `./ns3 run "clang-tidy -fix"`.
5252

5353
* (wifi) `CcaEdThreshold` can be changed at run-time.
5454
* (internet): Updated `TcpLedbat::CongestionAvoidance()` to compute `max_cwnd` as `flightsize + AllowedIncrease × MSS` by adding `m_allowedIncrease * tcb->m_segmentSize`, in accordance with RFC 6817.
55+
* (spectrum): Added a spatial-consistency update technique aligned with 3GPP TR 38.901 Procedure A
56+
(Sec. 7.6.3.2) to the 3GPP channel model. When the `UpdatePeriod` attribute of `ThreeGppChannelModel`
57+
is set to a non-zero value, the model evolves channel parameters using Procedure A update equations
58+
to preserve correlation across consecutive channel evaluations, with ns-3-specific update triggering
59+
suited to discrete-event simulation. In prior ns-3 releases (before ns-3.47), `UpdatePeriod` caused
60+
independent (i.i.d.) re-generation of channel realizations. With the updated behavior, an update is
61+
attempted when the channel is evaluated and `UpdatePeriod` has elapsed since the last parameter
62+
generation/update. If the maximum endpoint displacement between evaluations is within 1 m,
63+
Procedure A updates are applied; if it exceeds 1 m, channel parameters are re-generated (new realization).
64+
For stationary links, the channel remains unchanged. See `spectrum.rst` for ns-3 update triggering and
65+
1 m step-size handling.
5566

5667
## Changes from ns-3.46 to ns-3.46.1
5768

RELEASE_NOTES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ been tested on Linux. As of this release, the latest known version to work with
4545
- (internet) !2405 Added support for TCP FACK (Forward Acknowledgement).
4646
- (lr-wpan) !2592 Added PHY preamble and RSSI support.
4747
- (visualizer) Add Lr-Wpan NetDevices support to the Pyviz visualizer.
48+
- (spectrum) The ns-3 3GPP channel model supports a spatial-consistency update technique
49+
aligned with TR 38.901 Procedure A (Sec. 7.6.3.2), enabled via the `UpdatePeriod`
50+
attribute of `ThreeGppChannelModel` (see `spectrum.rst` for ns-3 update triggering and
51+
1 m step-size handling). Default per-cluster shadowing correlation distances are
52+
provided for terrestrial cellular scenarios (UMa/UMi/RMa/Indoor). For V2V and NTN,
53+
the corresponding 3GPP reports do not provide standardized per-cluster correlation
54+
distances; users should configure appropriate values in the parameter tables (the
55+
default is 1 m, yielding weak correlation unless adjusted).
4856

4957
### Bugs fixed
5058

examples/channel-models/examples-to-run.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
# See test.py for more information.
99
cpp_examples = [
1010
("three-gpp-v2v-channel-example", "True", "True"),
11+
(
12+
"three-gpp-v2v-channel-example --updatePeriodMs=1",
13+
"True",
14+
"True",
15+
),
1116
]
1217

1318
# A list of Python examples to run in order to ensure that they remain

examples/channel-models/three-gpp-v2v-channel-example.cc

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@
66
*/
77

88
/*
9-
* This is an example on how to configure the channel model classes to simulate
9+
* @file
10+
* This is an example of how to configure the channel model classes to simulate
1011
* a vehicular environment.
1112
* The channel condition is determined using the model specified in [1], Table 6.2-1.
1213
* The pathloss is determined using the model specified in [1], Table 6.2.1-1.
1314
* The model for the fast fading is the one described in 3GPP TR 38.901 v15.0.0,
1415
* the model parameters are those specified in [1], Table 6.2.3-1.
1516
*
16-
* This example generates the output file 'example-output.txt'. Each row of the
17-
* file is organized as follows:
18-
* Time[s] TxPosX[m] TxPosY[m] RxPosX[m] RxPosY[m] ChannelState SNR[dB] Pathloss[dB]
19-
* We also provide a bash script which reads the output file and generates two
17+
* This example generates the output file 'three-gpp-v2v-channel-example-output.txt'. Each row of
18+
* the file is organized as follows: Time[s] TxPosX[m] TxPosY[m] RxPosX[m] RxPosY[m] ChannelState
19+
* SNR[dB] Pathloss[dB] We also provide a bash script which reads the output file and generates two
2020
* figures:
21-
* (i) map.gif, a GIF representing the simulation scenario and vehicle mobility;
22-
* (ii) snr.png, which represents the behavior of the SNR.
21+
* (i) three-gpp-v2v-channel-example-map.gif, a GIF representing the simulation scenario and vehicle
22+
* mobility;
23+
* (ii) three-gpp-v2v-channel-example-snr.png, which represents the behavior of the SNR.
2324
*
2425
* [1] 3GPP TR 37.885, v15.3.0
2526
*/
@@ -64,45 +65,34 @@ struct ComputeSnrParams
6465

6566
/**
6667
* Perform the beamforming using the DFT beamforming method
67-
* @param thisDevice the device performing the beamforming
68+
* @param txMob the mobility model of the node performing the beamforming
6869
* @param thisAntenna the antenna object associated to thisDevice
69-
* @param otherDevice the device towards which point the beam
70+
* @param rxMob the mobility model of the node towards which will point the beam
7071
*/
7172
static void
72-
DoBeamforming(Ptr<NetDevice> thisDevice,
73-
Ptr<PhasedArrayModel> thisAntenna,
74-
Ptr<NetDevice> otherDevice)
73+
DoBeamforming(Ptr<MobilityModel> txMob, Ptr<PhasedArrayModel> thisAntenna, Ptr<MobilityModel> rxMob)
7574
{
76-
PhasedArrayModel::ComplexVector antennaWeights;
77-
78-
// retrieve the position of the two devices
79-
Vector aPos = thisDevice->GetNode()->GetObject<MobilityModel>()->GetPosition();
80-
Vector bPos = otherDevice->GetNode()->GetObject<MobilityModel>()->GetPosition();
81-
8275
// compute the azimuth and the elevation angles
83-
Angles completeAngle(bPos, aPos);
84-
76+
Angles completeAngle(rxMob->GetPosition(), txMob->GetPosition());
8577
PhasedArrayModel::ComplexVector bf = thisAntenna->GetBeamformingVector(completeAngle);
8678
thisAntenna->SetBeamformingVector(bf);
8779
}
8880

8981
/**
9082
* Compute the average SNR
91-
* @param params A structure that holds a bunch of parameters needed by ComputSnr function to
83+
* @param params A structure that holds a bunch of parameters needed by ComputeSnr function to
9284
* calculate the average SNR
9385
*/
9486
static void
9587
ComputeSnr(const ComputeSnrParams& params)
9688
{
9789
// check the channel condition
9890
Ptr<ChannelCondition> cond = m_condModel->GetChannelCondition(params.txMob, params.rxMob);
99-
10091
// apply the pathloss
10192
double propagationGainDb = m_propagationLossModel->CalcRxPower(0, params.txMob, params.rxMob);
10293
NS_LOG_DEBUG("Pathloss " << -propagationGainDb << " dB");
10394
double propagationGainLinear = std::pow(10.0, (propagationGainDb) / 10.0);
10495
*(params.txParams->psd) *= propagationGainLinear;
105-
10696
// apply the fast fading and the beamforming gain
10797
auto rxParams = m_spectrumLossModel->CalcRxPowerSpectralDensity(params.txParams,
10898
params.txMob,
@@ -120,13 +110,11 @@ ComputeSnr(const ComputeSnrParams& params)
120110
double noisePowerSpectralDensity = kT_W_Hz * noiseFigureLinear;
121111
Ptr<SpectrumValue> noisePsd = Create<SpectrumValue>(params.txParams->psd->GetSpectrumModel());
122112
(*noisePsd) = noisePowerSpectralDensity;
123-
124-
// compute the SNR
113+
// log the average SNR
125114
NS_LOG_DEBUG("Average SNR " << 10 * log10(Sum(*rxPsd) / Sum(*noisePsd)) << " dB");
126-
127-
// print the SNR and pathloss values in the snr-trace.txt file
115+
// print the SNR and pathloss values in the three-gpp-v2v-channel-example-output.txt file
128116
std::ofstream f;
129-
f.open("example-output.txt", std::ios::out | std::ios::app);
117+
f.open("three-gpp-v2v-channel-example-output.txt", std::ios::out | std::ios::app);
130118
f << Simulator::Now().GetSeconds() << " " // time [s]
131119
<< params.txMob->GetPosition().x << " " << params.txMob->GetPosition().y << " "
132120
<< params.rxMob->GetPosition().x << " " << params.rxMob->GetPosition().y << " "
@@ -170,28 +158,20 @@ main(int argc, char* argv[])
170158
double vScatt = 0; // maximum speed of the vehicles in the scenario [m/s]
171159
double subCarrierSpacing = 60e3; // subcarrier spacing in kHz
172160
uint32_t numRb = 275; // number of resource blocks
161+
uint32_t updatePeriodMs = 0;
173162

174163
CommandLine cmd(__FILE__);
175164
cmd.AddValue("frequency", "operating frequency in Hz", frequency);
176165
cmd.AddValue("txPow", "tx power in dBm", txPow_dbm);
177166
cmd.AddValue("noiseFigure", "noise figure in dB", noiseFigure);
178167
cmd.AddValue("scenario", "3GPP propagation scenario, V2V-Urban or V2V-Highway", scenario);
168+
cmd.AddValue("updatePeriodMs", "the channel update period", updatePeriodMs);
179169
cmd.Parse(argc, argv);
180170

181171
// create the nodes
182172
NodeContainer nodes;
183173
nodes.Create(2);
184174

185-
// create the tx and rx devices
186-
Ptr<SimpleNetDevice> txDev = CreateObject<SimpleNetDevice>();
187-
Ptr<SimpleNetDevice> rxDev = CreateObject<SimpleNetDevice>();
188-
189-
// associate the nodes and the devices
190-
nodes.Get(0)->AddDevice(txDev);
191-
txDev->SetNode(nodes.Get(0));
192-
nodes.Get(1)->AddDevice(rxDev);
193-
rxDev->SetNode(nodes.Get(1));
194-
195175
// create the antenna objects and set their dimensions
196176
Ptr<PhasedArrayModel> txAntenna =
197177
CreateObjectWithAttributes<UniformPlanarArray>("NumColumns",
@@ -321,6 +301,7 @@ main(int argc, char* argv[])
321301
channelModel->SetAttribute("Frequency", DoubleValue(frequency));
322302
channelModel->SetAttribute("ChannelConditionModel", PointerValue(m_condModel));
323303
channelModel->SetAttribute("vScatt", DoubleValue(vScatt));
304+
channelModel->SetAttribute("UpdatePeriod", TimeValue(MilliSeconds(updatePeriodMs)));
324305

325306
// create the spectrum propagation loss model
326307
m_spectrumLossModel = CreateObjectWithAttributes<ThreeGppSpectrumPropagationLossModel>(
@@ -330,8 +311,8 @@ main(int argc, char* argv[])
330311
BuildingsHelper::Install(nodes);
331312

332313
// set the beamforming vectors
333-
DoBeamforming(txDev, txAntenna, rxDev);
334-
DoBeamforming(rxDev, rxAntenna, txDev);
314+
DoBeamforming(txMob, txAntenna, rxMob);
315+
DoBeamforming(rxMob, rxAntenna, txMob);
335316

336317
// create the tx power spectral density
337318
Bands rbs;
@@ -340,17 +321,17 @@ main(int argc, char* argv[])
340321
{
341322
BandInfo rb;
342323
rb.fl = freqSubBand;
343-
freqSubBand += subCarrierSpacing / 2;
324+
freqSubBand += (subCarrierSpacing * 12) / 2;
344325
rb.fc = freqSubBand;
345-
freqSubBand += subCarrierSpacing / 2;
326+
freqSubBand += (subCarrierSpacing * 12) / 2;
346327
rb.fh = freqSubBand;
347328
rbs.push_back(rb);
348329
}
349330
Ptr<SpectrumModel> spectrumModel = Create<SpectrumModel>(rbs);
350331
Ptr<SpectrumValue> txPsd = Create<SpectrumValue>(spectrumModel);
351332
Ptr<SpectrumSignalParameters> txParams = Create<SpectrumSignalParameters>();
352333
double txPow_w = std::pow(10., (txPow_dbm - 30) / 10);
353-
double txPowDens = (txPow_w / (numRb * subCarrierSpacing));
334+
double txPowDens = (txPow_w / (numRb * subCarrierSpacing * 12));
354335
(*txPsd) = txPowDens;
355336
txParams->psd = txPsd->Copy();
356337

@@ -362,13 +343,13 @@ main(int argc, char* argv[])
362343

363344
// initialize the output file
364345
std::ofstream f;
365-
f.open("example-output.txt", std::ios::out);
346+
f.open("three-gpp-v2v-channel-example-output.txt", std::ios::out);
366347
f << "Time[s] TxPosX[m] TxPosY[m] RxPosX[m] RxPosY[m] ChannelState SNR[dB] Pathloss[dB]"
367348
<< std::endl;
368349
f.close();
369350

370351
// print the list of buildings to file
371-
PrintGnuplottableBuildingListToFile("buildings.txt");
352+
PrintGnuplottableBuildingListToFile("three-gpp-v2v-channel-example-buildings.txt");
372353

373354
Simulator::Run();
374355
Simulator::Destroy();

examples/channel-models/three-gpp-v2v-channel-example.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import pandas as pd
1616

1717
# Load the data
18-
df = pd.read_csv("example-output.txt", sep=r"\s+", comment="#")
18+
df = pd.read_csv("three-gpp-v2v-channel-example-output.txt", sep=r"\s+", comment="#")
1919
df = df.iloc[::10, :]
2020

2121
# Column indices (adjust if needed)
@@ -48,7 +48,9 @@
4848
ax_snr.grid(True)
4949
(snr_line,) = ax_snr.plot([], [], "k-")
5050

51-
buildings = pd.read_csv("buildings.txt", sep=r"\s+", comment="#", header=None)
51+
buildings = pd.read_csv(
52+
"three-gpp-v2v-channel-example-buildings.txt", sep=r"\s+", comment="#", header=None
53+
)
5254
building_patches = []
5355
for idx, row in buildings.iterrows():
5456
x0, y0, x1, y1 = row
@@ -73,7 +75,7 @@ def update(frame):
7375
ani = animation.FuncAnimation(fig, update, frames=len(df), interval=0.001, blit=False)
7476

7577
# Save animation
76-
ani.save("map.gif", writer="pillow")
78+
ani.save("three-gpp-v2v-channel-example-map.gif", writer="pillow")
7779

7880
# Save final SNR plot separately
7981
plt.figure()
@@ -83,4 +85,4 @@ def update(frame):
8385
plt.grid(True)
8486
plt.xlim(0, 40)
8587
plt.ylim(-20, 100)
86-
plt.savefig("snr.png")
88+
plt.savefig("three-gpp-v2v-channel-example-snr.png")

0 commit comments

Comments
 (0)