Skip to content

physicallayer: StochasticErrorModel derives the missing error rates (#1429) - #1431

Open
tabgab wants to merge 3 commits into
masterfrom
fix/stochastic-errormodel
Open

tabgab wants to merge 3 commits into
masterfrom
fix/stochastic-errormodel

Conversation

@tabgab

@tabgab tabgab commented Nov 7, 2026

Copy link
Copy Markdown

Fixes #1429. On master 7ec0d4d, three commits.

Problem

StochasticErrorModel could not be used in an IEEE 802.11 network (details and reproduction in #1429):

  • initialize() did not call ErrorModelBase::initialize(), so corruptionMode stayed undefined and the first lost frame ended the run with Unknown corruption mode.
  • computeBitErrorRate() threw Not yet implemented unless bitErrorRate was set, although FlatReceiverBase asks for the packet, bit and symbol error rate of every reception.
  • computePacketErrorRate() dereferenced the bit model, which is null for scalar and dimensional 802.11 transmissions (segmentation fault).

The three commits, and why they are separate

  1. physicallayer: add: accessors for the header/data length and modulation of flat transmissions
    Adds IFlatTransmission (header length, data length, header modulation, data modulation; b(-1) is an unknown length, nullptr an unknown modulation) and IModulation::getBitsPerSymbol() (default 0 = unknown). Implemented by ApskTransmission, Ieee802154Transmission and Ieee80211Transmission, and by ApskModulationBase, DpskModulationBase and Ieee80211OfdmModulation. Ieee80211Transmission computes the lengths from the mode and the PHY header, the way Ieee80211ErrorModelBase::computePacketErrorRate does. No behavior change. It is a contract change, so it stands alone. It lets the fix in commit 2 use one interface cast instead of casts to technology classes, so common/ does not depend on ieee80211/, apsk/ or ieee802154/, and radios outside the tree can implement the interface.
  2. physicallayer: fix: StochasticErrorModel initializes the base and derives the missing error rates consistently (#1429)
    The fix itself, with the NED documentation and the tests.
  3. physicallayer: fix: ErrorModelBase and FlatReceiverBase report a missing error rate instead of looping or deciding on NaN
    Guards in two base classes. They change behavior for every error model, so they are separate from the model fix and have their own tests. The ber == 0 infinite loop is named in the commit body.

Each commit builds, and the whole tests/module suite gives the same results after commits 1, 2 and 3 as before, plus the new tests.

Design of the fix (commit 2)

Under the assumption of independent errors, with L the length of the packet (or of its header or data part) in bits and k the bits per symbol of the modulation:

PER = 1 - (1 - BER)^L        BER = 1 - (1 - PER)^(1/L)
SER = 1 - (1 - BER)^k        BER = 1 - (1 - SER)^(1/k)

(computed with expm1/log1p). Each compute*ErrorRate() returns its own parameter if it is set, otherwise it derives the value:

result rule
packet error rate, WHOLE packetErrorRate if set. Otherwise composed from the parts: 1 - (1 - PER_header) * (1 - PER_data), in log space
packet error rate, HEADER / DATA if packetErrorRate is set, the share 1 - (1 - PER)^(L_part / L_whole). Otherwise PER_part = 1 - (1 - BER_part)^L_part, where BER_part uses the modulation of that part. PREAMBLE is 0
bit error rate bitErrorRate if set; else from symbolErrorRate if it is set and the modulation of the part has bits per symbol; else from packetErrorRate over the whole packet (header + data)
symbol error rate symbolErrorRate if set; else from the bit error rate with the bits per symbol of the part

Precedence when several parameters are set: a rate that is set is returned as is. A rate that is not set is derived from the bit error rate if set, else from the symbol error rate, else from the packet error rate (bit error rate over symbol error rate over packet error rate). A rate that cannot be derived is NaN, which ErrorRateInd allows.

Where the header and data lengths and modulations come from: IFlatTransmission (one dynamic_cast), else the bit model if the transmission has one, else the whole packet is data with no modulation. A negative length counts as unknown, in one place (getPartLengths). UWB-IR transmissions are the case that has b(-1) lengths, but the UWB-IR receiver is a ReceiverBase, not a FlatReceiverBase, so it cannot use an error model; that path is not reachable in a simulation and is not tested.

If only symbolErrorRate is set and the modulation of a part has no bits per symbol (CCK of 802.11b/g at 5.5 and 11 Mbit/s, FHSS, IR, and the FSK/PPM/MFSK modulations in general), the run stops at the first reception with an error that names the part, the transmission class and the modulation class (the CCK data mode has no modulation object, so the message says modulation not specified for it). Other combinations return NaN for the rates that cannot be derived.

The parameters are checked to be NaN (unset) or in [0, 1] in initialize().

Approximations (documented in the NED file)

  • Coding rate is ignored. k is the number of coded bits that a constellation symbol carries, while L is the length of the packet after decoding (PSDU plus tail and padding bits for 802.11 OFDM). At 24 Mbit/s (16-QAM, rate 1/2) a symbol carries 4 coded and 2 data bits. The symbol error rate is therefore not the symbol error rate of the decoded packet.
  • Which symbol: for 802.11 OFDM it is a symbol of the subcarrier modulation (a 16-QAM symbol), not an OFDM symbol (Ieee80211OfdmModulation::getBitsPerSymbol() returns the bits of the subcarrier modulation).
  • Per-part modulation: the header and the data part of an 802.11 OFDM frame have different modulations (BPSK and 16-QAM at 24 Mbit/s). Decisions use the modulation of each part and the WHOLE rate is composed from the parts, so the WHOLE, HEADER and DATA rates are consistent. The WHOLE bit and symbol error rates, which are only reported in ErrorRateInd, use the modulation of the data part.

Behavior changes of commit 3, for all error models

  • FlatReceiverBase::computeIsReceptionSuccessful(): a NaN packet error rate stops the run with Error model <class> cannot provide the packet error rate that the reception decision requires. Before, a debug build failed an ASSERT and a release build lost the frame silently (dblrand() > NaN is false). This applies to every error model that can return NaN there.
  • ErrorModelBase::computeCorruptedPacket(const ISnir *):
    • computeBitErrorRate() is only called for the corruption modes chunk, byte and bit (it was called for packet, where the value was unused).
    • a NaN bit error rate stops the run with Error model <class> cannot provide a bit error rate for corruptionMode = "<mode>"; use corruptionMode = "packet" or an error model that computes a bit error rate. Before, a debug build failed an ASSERT and a release build looped forever.
    • a bit error rate of 0 (or less) for a packet whose reception already failed stops the run with Error model <class> lost a packet but returned a bit error rate of 0, with which corruptionMode = "<mode>" cannot corrupt it. Before, computeCorruptedPacket(packet, ber) looped forever because hasProbabilisticError() never returns true for ber == 0.
    • the 802.11 error models (Ieee80211Nist, Yans, BerTable) return no bit error rate, so corruptionMode chunk, byte or bit with them now fails with this message instead of Unimplemented corruption mode from Ieee80211ErrorModelBase. It was an error before as well.
  • an error model that never called ErrorModelBase::initialize() (corruptionMode stays undefined, as in StochasticErrorModel before this fix) fails with Error model <class> did not initialize its corruption mode, its initialize() must call ErrorModelBase::initialize(). Before, it ended with Unknown corruption mode from the corruption loop. This is checked first, so the other messages never mention a corruption mode that was not set. Ieee80211BerTableErrorModel is such a model; fixing it is not part of this PR.
  • corruptionMode = "packet" and finite packet error rates are not affected; no reception decision changes.

The messages name the class of the error model and the corruption mode and no parameter, since the check runs for every error model.

Tests

tests/module/lib got StochasticErrorModelTest.ned (network), stochastic-errormodel*.ini (one common file and one per technology: IEEE 802.11, APSK, IEEE 802.15.4), stochastic-errormodel.awk (verdict), StochasticErrorModelProbe (a StochasticErrorModel that records the smallest and largest value returned for each rate and part, and computes the HEADER and DATA rates on every request, which the receivers do not ask for) and, in commit 3, ConstantRatesErrorModel. Each test is a .test file with its parameters and an include of the technology file, as the existing Ieee8021adPolicyInvalid_* tests do. The expected values are computed by an independent implementation of the closure, not by the C++ code. The frame counts use 4 sigma of the binomial with n = 500 (a miss probability of about 6e-5 for a correct model, deterministic seeds); rates are compared to a relative precision of 1e-6.

test commit what
1 2 802.11 ERP 24 Mbit/s, packetErrorRate = 0.3 (packet mode)
2 2 same, corruptionMode = "chunk"
3 2 bitErrorRate only
4 2 symbolErrorRate only: BPSK header, 16-QAM data, WHOLE composed from the parts
5, 6 2 precedence: PER + BER, SER + BER
7 2 symbolErrorRate only, dimensional analog model
8 2 packetErrorRate = 1, chunk: BER 1, all frames lost
9 2 bitErrorRate = 0, chunk: nothing lost, nothing to corrupt
10 2 802.11b 11 Mbit/s (CCK), packetErrorRate only: SER of the data is NaN
11 2 802.11b 11 Mbit/s, symbolErrorRate only: error naming the modulation
12 2 parameter outside [0, 1]
13, 14 2 APSK radio (64-QAM): symbolErrorRate, packetErrorRate
15, 16 2 IEEE 802.15.4 narrowband (DSSS-OQPSK-16): symbolErrorRate, packetErrorRate
17 2 APSK with the bit-level ApskLayeredTransmitter (transmissions with a bit model) and the packet level receiver
18 3 NaN packet error rate in FlatReceiverBase
19 3 Ieee80211NistErrorModel with chunk: NaN bit error rate
20 3 packet lost with bit error rate 0 (the ber == 0 loop)
21 3 packet lost with NaN bit error rate, chunk
22 3 corruptionMode = "packet" needs no bit error rate (passes before and after, by design)
23 3 StochasticErrorModel with no rate set
24 3 an error model that never called ErrorModelBase::initialize() (corruption mode undefined)

Verification

Run on macOS (Apple clang, OMNeT++ 6.5.0, -j8, ccache), release (MODE=release) and debug (MODE=debug) builds.

tests/module, release and debug, the whole directory (826 .test files on master, built the way the removed tests/module/runtest did: lib copied into a work directory, one executable for all tests, opp_test run in four parallel shards):

revision release (pass / fail / error) debug (pass / fail / error)
master 7ec0d4d 444 / 379 / 3 (of 826) 772 / 52 / 2 (of 826)
commit 1 same results for all 826 same results for all 826
commit 2 same for the 826, plus the 17 new tests pass same for the 826, plus the 17 new tests pass
commit 3 (final head) 468 / 379 / 3 (of 850) 796 / 52 / 2 (of 850)

The per-test result lists differ between master and the final head only by the 24 added tests, which all pass. The failures on master are not touched by this change. I did not analyze each of them. The ones I looked at: four tests whose own C++ sources do not compile with this toolchain (Ieee80211BlockAckDeferral_1, Ieee80211BlockAckDeferredCollision_1, Ieee80211EdcaDataLifetimeArbitration_1, Ieee80211MgmtApHcfRtsTimeout_1; they are left out of the test executable and fail the same way before and after), ExternalProcess_* (External processes are only supported on Linux), and in release builds a large group whose expected output does not match (e.g. tcp_*, IPv6_*); in debug builds the IPv6/MLD tests, LogNormalShadowing_Correlated_* and others fail on master too.

Ieee80211BitDomain (and Ieee80211SymbolDomain, ConvolutionalCoder12, ConvolutionalCoder34) use the C++ test library in tests/module/lib. They pass in the debug run and fail in the release run, identically on master and on commit 3. I built them with the library and checked the results; they fail in release on master as well.

tests/unit, release and debug (148 .test files; 7 sources that do not compile are left out): same results on master and on commit 3 in both modes (release 125 pass, debug 134 pass).

examples/wireless/corruptionmode, all four configurations (PacketCorruptionMode, ChunkCorruptionMode, ByteCorruptionMode, BitCorruptionMode), release and debug, run 0: the result files (about 2100 scalar lines each) are identical on master, after commit 1 and after commit 3, in both modes.

Fingerprints. This INET keeps the fingerprints in tests/fingerprint/store.json (there are no CSV files, and the opp_repl runner was not installed here), so I wrote a small runner that runs the same entries (inet -u Cmdenv -f <ini> -c <config> -r <run> --sim-time-limit=<t> --fingerprint=0000-0000/<ingredients>, --fingerprintcalculator-class=inet::FingerprintCalculator for ~ ingredients, computed checksum/FCS modes for ~tND) and records the calculated value. 1197 entries from the wireless-related directories (examples/wireless/*, examples/adhoc/ieee80211, idealwireless, ieee8021as, showcases/wireless/*, tutorials/wireless, showcases/visualizer/canvas/ieee80211; ingredients tplx 410, ~tNl 410, ~tND 377; tyf is graphical and was skipped), release build only: master, commit 1 and commit 3 give the identical calculated fingerprint for all 1197 entries. Against the stored values, 409 of 410 tplx, 409 of 410 ~tNl and 374 of 377 ~tND match, on all three revisions. The 5 that do not match are the same before and after: showcases/wireless/sensornetwork StatisticLMac (stored 0000-0000; the run stops with I am still sending a message, while a new slot is starting!) for tplx and ~tNl, and three examples/ieee8021as ~tND entries. This includes every 802.11 example with the default Ieee80211NistErrorModel, which returns a NaN bit error rate, in packet mode. Fingerprints after commit 2 were not run (commit 2 is covered by the tests/module suite), and fingerprints were not run in a debug build.

Each new test fails without the change. Run on master (7ec0d4d3f3 with the test files of this branch added); a hang was stopped after 150 s:

tests master, release master, debug
1, 2, 4, 7, 8, 10, 13-17 fail: Not yet implemented same
3, 6, 9 fail: segmentation fault (exit 139) same
5 fail: Unknown corruption mode same
11 fail: Not yet implemented (expected message missing) same
12 fail: Not yet implemented (no range check) fail: ASSERT in FlatReceiverBase
18 fail: runs to the end, exit 0 (frames lost silently) fail: ASSERT in FlatReceiverBase
19 fail: Unimplemented corruption mode same
20, 21 fail: infinite loop (stopped after 150 s) fail: ASSERT in hasProbabilisticError
23 fail: Not yet implemented same
24 fail: Unknown corruption mode same
22 pass (by design) pass

Tests 18-24 also fail on commit 2 alone (release: 18 exit 0, 19 message, 20 and 21 loop, 23 exit 0, 24 Unknown corruption mode; debug: all message/assertion), so they test commit 3. All 24 pass on the final head, in both modes.

Not run: the tests/misc/ns3 validation tests (they need ns-3, and their Ns3Test.ned does not parse on this tree). tests/misc/ns3/lib/ns3test.ini sets bitErrorRate = 0 on the 802.11a scalar radio. On master that configuration crashes in computePacketErrorRate (the null bit model; test 9 reproduces it on 802.11g). With the fix the same setting runs and loses no frame (test 9, on 802.11g); I did not run the ns-3 scenario itself. Also not run: the other fingerprint directories, tests/packet, tests/protocol, tests/validation and the showcases beyond their fingerprints.

Notes on the evidence. The table of failures on master was assembled from two runs: all tests on master, then tests 18, 21 and 23 again after their final form (they were reworked once). The tests/module and fingerprint runs for master ~tND rows were done twice (the first attempt lacked the computed checksum modes), only the second was compared. Of the transmissions in the tree that carry a bit model, all also implement IFlatTransmission with known lengths, so test 17 takes the interface branch; the bit-model and "whole packet is data" fallbacks in StochasticErrorModel are not exercised by a test (generic transmissions have no error model). No ChangeLog entry is added for the behavior change of commit 3 (INET fills src/ChangeLog at release). Not part of this PR: Ieee80211BerTableErrorModel::initialize() has the same missing ErrorModelBase::initialize() call that caused #1429.

Final head. After the last change (the undefined-corruption-mode check and test 24) I re-ran on the final head, release and debug: tests 18-24 (all pass), the whole tests/module suite (850 tests; release 468 pass / 379 fail / 3 error, debug 796 / 52 / 2; the result lists differ from master only by the 24 added tests, all PASS) and the four corruptionmode configurations (scalars identical to master in both modes). The numbers for commits 1 and 2, tests/unit and the fingerprints were measured on the earlier heads of this branch; the source of the final head differs from those only by the commit 3 check above, a NED comment sentence and test 10's description, and fingerprints were not repeated on the final head.

tabgab added 3 commits November 7, 2026 03:11
…on of flat transmissions

Add the IFlatTransmission interface, which returns the length and the
modulation of the header and of the data part of a transmission (b(-1) for an
unknown length, nullptr for an unknown modulation), and
IModulation::getBitsPerSymbol(), which returns the number of coded bits that
a symbol carries (0, the default, if unknown).

IFlatTransmission is implemented by ApskTransmission and
Ieee802154Transmission (from the lengths and the modulation they already
store; the lengths of UWB-IR transmissions are b(-1)) and by
Ieee80211Transmission. The latter has no bit model for scalar and
dimensional signals, so it computes the lengths from the mode and the PHY
header, the way Ieee80211ErrorModelBase::computePacketErrorRate does.
getBitsPerSymbol() is implemented by ApskModulationBase (code word size),
DpskModulationBase (log2 of the constellation size) and
Ieee80211OfdmModulation (the subcarrier modulation).

This is groundwork for deriving the missing error rates in
StochasticErrorModel (#1429) without common/ depending on technology
packages. No behavior change.
…ives the missing error rates consistently (#1429)

StochasticErrorModel::initialize() did not call ErrorModelBase::initialize(),
so corruptionMode, snirMode and snirOffset were never read and the first lost
frame ended the run with "Unknown corruption mode". computeBitErrorRate()
threw "Not yet implemented" unless bitErrorRate was set, although
FlatReceiverBase asks for the packet, bit and symbol error rate of every
reception. computePacketErrorRate() dereferenced the bit model, which is null
for scalar and dimensional IEEE 802.11 transmissions, and estimated the
header share of the packet error rate from the signal durations.

The three rates are now consistent under the assumption of independent errors:
PER = 1 - (1 - BER)^L over the L bits of the packet (or of its header or data
part), and SER = 1 - (1 - BER)^k with k bits per symbol. A rate that is set is
returned as is; otherwise it is derived: the packet error rate from the bit
error rate, the bit error rate from the symbol error rate (if the modulation
has bits per symbol) or else from the packet error rate over the whole packet,
and the symbol error rate from the bit error rate. A rate that cannot be
derived is NaN, which ErrorRateInd allows. The packet error rate of the whole
packet is composed from those of the header and the data part (each with its
own modulation), as the other error models do.

The lengths and modulations come from IFlatTransmission, then from the bit
model, then the whole packet is data. A negative length is unknown. If
symbolErrorRate is the only parameter set and the modulation of a part has no
bits per symbol (CCK of IEEE 802.11b, FHSS, PPM, FSK), the run stops with an
error that names the part and the transmission. The coding rate is ignored in
the symbol/bit relation; the NED documentation says so. The parameters are
checked to be NaN or in [0, 1].

Tests: tests/module/StochasticErrorModel_{1..17}.test, with the network, the
verdict script and a probe error model (which also records the header and
data rates) in tests/module/lib.
…ing error rate instead of looping or deciding on NaN

ErrorModelBase::computeCorruptedPacket(const ISnir *) passed the bit error
rate of the error model to the corruption modes chunk, byte and bit without
looking at it. With a bit error rate of 0 no chunk, byte or bit is ever
corrupted, so the loop that corrupts a packet whose reception has already
failed ran forever. With a NaN bit error rate the ASSERT in
hasProbabilisticError() failed in a debug build, and a release build looped
forever as well. Now the bit error rate is only requested for the corruption
modes that use it, and a NaN or non-positive value stops the run with an error
that names the error model and the corruption mode.

FlatReceiverBase::computeIsReceptionSuccessful() compared a NaN packet error
rate with 0 and 1 and then asserted on it (debug), or lost the reception
silently, because dblrand() > NaN is false (release). It now stops the run
with an error that names the error model.

An error model that never called ErrorModelBase::initialize() (corruptionMode stays
undefined, as in StochasticErrorModel before the previous commit) now fails with
an error that names the missing call instead of the misleading message about
corruptionMode = "packet" (or, before, "Unknown corruption mode").

Behavior change for all error models: a run that used to loop forever, fail
an assertion or lose every frame silently now ends with an error. Packet error
rates that are numbers, and corruptionMode = "packet", are not affected. The
error models of IEEE 802.11 (Nist, Yans, BerTable) return no bit error rate,
so that corruptionMode chunk, byte or bit with them now fails with the new
message at the first lost frame instead of with "Unimplemented corruption
mode" from Ieee80211ErrorModelBase.

Tests: tests/module/StochasticErrorModel_{18..24}.test, with the helper error
model ConstantRatesErrorModel in tests/module/lib.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StochasticErrorModel: "Unknown corruption mode" at the first lost frame, "Not yet implemented" with only packetErrorRate, crash with only bitErrorRate

1 participant