From 6a71294dac29ee80ed886406b19dec1ef4a076c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 13 Oct 2026 12:01:08 +0200 Subject: [PATCH 1/4] IPathLoss: rename the distance form to computeDistancePathLoss() IPathLoss had two computePathLoss() overloads: one for a transmission and an arrival, one for a propagation speed, a frequency and a distance. Every path loss model derived from PathLossBase overrides only the latter, and a class that overrides one overload hides the other in its own scope; the next commit replaces the former with a method that computes the path loss of a reception, so the overload set would only grow more confusing. computePathLoss(propagationSpeed, frequency, distance) is renamed to computeDistancePathLoss(), in IPathLoss, PathLossBase, the eleven models and its three callers (PathLossBase, FrequencyDependentAttenuationFunction and the medium visualizer's PathLossFunction). Its documentation states what the models already do: the value is independent of the transmitter and the receiver, or NaN when the model cannot tell it from the distance alone, as TwoRayInterference. API change, stated in WHATSNEW. --- WHATSNEW | 14 ++++++++++++++ .../common/base/packetlevel/PathLossBase.cc | 2 +- .../common/base/packetlevel/PathLossBase.h | 2 +- .../common/contract/packetlevel/IPathLoss.h | 8 +++++--- .../wireless/common/pathloss/BreakpointPathLoss.cc | 2 +- .../wireless/common/pathloss/BreakpointPathLoss.h | 2 +- .../wireless/common/pathloss/FreeSpacePathLoss.cc | 2 +- .../wireless/common/pathloss/FreeSpacePathLoss.h | 2 +- .../wireless/common/pathloss/LogNormalShadowing.cc | 2 +- .../wireless/common/pathloss/LogNormalShadowing.h | 2 +- .../wireless/common/pathloss/NakagamiFading.cc | 2 +- .../wireless/common/pathloss/NakagamiFading.h | 2 +- .../wireless/common/pathloss/RayleighFading.cc | 2 +- .../wireless/common/pathloss/RayleighFading.h | 2 +- .../wireless/common/pathloss/RicianFading.cc | 2 +- .../wireless/common/pathloss/RicianFading.h | 2 +- .../wireless/common/pathloss/SuiPathLoss.cc | 2 +- .../wireless/common/pathloss/SuiPathLoss.h | 2 +- .../wireless/common/pathloss/TwoRayInterference.cc | 2 +- .../wireless/common/pathloss/TwoRayInterference.h | 2 +- .../common/pathloss/UwbIrStochasticPathLoss.cc | 2 +- .../common/pathloss/UwbIrStochasticPathLoss.h | 2 +- .../wireless/common/signal/PowerFunctions.h | 4 ++-- 23 files changed, 41 insertions(+), 25 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index 1b07baf740a..ad2253ee030 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -400,6 +400,20 @@ Notable backward incompatible changes are the following: This change requires the modification of C++ code that implements IRadioMedium without deriving from RadioMedium. +23. The distance form of the path loss is computeDistancePathLoss() + + IPathLoss::computePathLoss(propagationSpeed, frequency, distance) was + renamed to computeDistancePathLoss(), so that it is no longer an overload + of the method that computes the path loss of a reception (see the next + item): a model that overrode one of the overloads hid the other. Its + documentation now states that the value is independent of the transmitter + and the receiver, and may be NaN when the model cannot tell the loss from + the distance alone (TwoRayInterference). + + This change requires the modification of C++ code that implements or calls + computePathLoss(propagationSpeed, frequency, distance): every path loss + model derived from PathLossBase overrides it. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics diff --git a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc index f77f17568ab..5f37ac472f2 100644 --- a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc +++ b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc @@ -23,7 +23,7 @@ double PathLossBase::computePathLoss(const ITransmission *transmission, const IA mps propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); Hz centerFrequency = Hz(analogModel->getCenterFrequency()); m distance = m(arrival->getStartPosition().distance(transmission->getStartPosition())); - return computePathLoss(propagationSpeed, centerFrequency, distance); + return computeDistancePathLoss(propagationSpeed, centerFrequency, distance); } } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h index 451fd3b66c3..aa195b769fb 100644 --- a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h +++ b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h @@ -18,7 +18,7 @@ namespace physicallayer { class INET_API PathLossBase : public Module, public IPathLoss { public: - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override = 0; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override = 0; virtual double computePathLoss(const ITransmission *transmission, const IArrival *arrival) const override; }; diff --git a/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h b/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h index 7d7daaed842..6eddb913c63 100644 --- a/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h +++ b/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h @@ -31,10 +31,12 @@ class INET_API IPathLoss : public virtual IPrintableObject /** * Returns the loss factor as a function of propagation speed, carrier - * frequency and distance. The value is in the range [0, 1] where 1 means - * no loss at all and 0 means all power is lost. + * frequency and distance, independently of the transmitter and the + * receiver. The value is in the range [0, 1] where 1 means no loss at all + * and 0 means all power is lost, or NaN if the model cannot tell the loss + * from the distance alone. */ - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const = 0; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const = 0; /** * Returns the range for the given loss factor. The value is in the range diff --git a/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.cc b/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.cc index 3d50ef9faa2..2fd27671be2 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.cc @@ -42,7 +42,7 @@ std::ostream& BreakpointPathLoss::printToStream(std::ostream& stream, int level, return stream; } -double BreakpointPathLoss::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double BreakpointPathLoss::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { // PL(d) = PL0 + 10 alpha log10 (d/d0) // 10 ^ { PL(d)/10 } = 10 ^{PL0 + 10 alpha log10 (d/d0)}/10 diff --git a/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.h b/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.h index 023d2e18aca..890a8833a86 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.h +++ b/src/inet/physicallayer/wireless/common/pathloss/BreakpointPathLoss.h @@ -30,7 +30,7 @@ class INET_API BreakpointPathLoss : public PathLossBase public: BreakpointPathLoss(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; virtual m computeRange(mps propagationSpeed, Hz frequency, double loss) const override; }; diff --git a/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.cc b/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.cc index c1e12d7bd3f..b322d9801b2 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.cc @@ -49,7 +49,7 @@ double FreeSpacePathLoss::computeFreeSpacePathLoss(m waveLength, m distance, dou return distance.get() == 0.0 ? 1.0 : (waveLength * waveLength).get() / (16 * M_PI * M_PI * systemLoss * pow(distance.get(), alpha)); } -double FreeSpacePathLoss::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double FreeSpacePathLoss::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m waveLength = propagationSpeed / frequency; return computeFreeSpacePathLoss(waveLength, distance, alpha, systemLoss); diff --git a/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.h b/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.h index 853d9953666..d15906c0416 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.h +++ b/src/inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.h @@ -39,7 +39,7 @@ class INET_API FreeSpacePathLoss : public PathLossBase public: FreeSpacePathLoss(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; virtual m computeRange(mps propagationSpeed, Hz frequency, double loss) const override; }; diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc index 6324e8c68cf..e36708578ca 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc @@ -43,7 +43,7 @@ std::ostream& LogNormalShadowing::printToStream(std::ostream& stream, int level, return stream; } -double LogNormalShadowing::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double LogNormalShadowing::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m d0 = m(1.0); // reference path loss diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h index bcaa21f4725..ba2c0b0a4eb 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h @@ -35,7 +35,7 @@ class INET_API LogNormalShadowing : public FreeSpacePathLoss public: LogNormalShadowing(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.cc b/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.cc index 4c90a751b93..4db42cd4a15 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.cc @@ -43,7 +43,7 @@ std::ostream& NakagamiFading::printToStream(std::ostream& stream, int level, int return stream; } -double NakagamiFading::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double NakagamiFading::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m waveLength = propagationSpeed / frequency; double freeSpacePathLoss = computeFreeSpacePathLoss(waveLength, distance, alpha, systemLoss); diff --git a/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.h b/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.h index f508698c43e..d407871e68c 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.h +++ b/src/inet/physicallayer/wireless/common/pathloss/NakagamiFading.h @@ -35,7 +35,7 @@ class INET_API NakagamiFading : public FreeSpacePathLoss public: NakagamiFading(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.cc b/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.cc index c4bccdf77d7..c36c8de7587 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.cc @@ -29,7 +29,7 @@ std::ostream& RayleighFading::printToStream(std::ostream& stream, int level, int return stream; } -double RayleighFading::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double RayleighFading::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m waveLength = propagationSpeed / frequency; double freeSpacePathLoss = computeFreeSpacePathLoss(waveLength, distance, alpha, systemLoss); diff --git a/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.h b/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.h index 3e9d224a6a2..5cfa80986c3 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.h +++ b/src/inet/physicallayer/wireless/common/pathloss/RayleighFading.h @@ -31,7 +31,7 @@ class INET_API RayleighFading : public FreeSpacePathLoss { public: virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/pathloss/RicianFading.cc b/src/inet/physicallayer/wireless/common/pathloss/RicianFading.cc index e760e608969..fe86f1ef777 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/RicianFading.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/RicianFading.cc @@ -43,7 +43,7 @@ std::ostream& RicianFading::printToStream(std::ostream& stream, int level, int e return stream; } -double RicianFading::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double RicianFading::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m waveLength = propagationSpeed / frequency; double c = 1.0 / (2.0 * (k + 1)); diff --git a/src/inet/physicallayer/wireless/common/pathloss/RicianFading.h b/src/inet/physicallayer/wireless/common/pathloss/RicianFading.h index 7d28bdd0cf5..15d32a17c13 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/RicianFading.h +++ b/src/inet/physicallayer/wireless/common/pathloss/RicianFading.h @@ -37,7 +37,7 @@ class INET_API RicianFading : public FreeSpacePathLoss public: RicianFading(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.cc b/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.cc index 30200e053bd..8469cc0bd75 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.cc @@ -81,7 +81,7 @@ std::ostream& SuiPathLoss::printToStream(std::ostream& stream, int level, int ev return stream; } -double SuiPathLoss::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double SuiPathLoss::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { m R = distance; m R0 = m(100.0); diff --git a/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.h b/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.h index 4609dfa87c8..ac2d53fbec3 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.h +++ b/src/inet/physicallayer/wireless/common/pathloss/SuiPathLoss.h @@ -44,7 +44,7 @@ class INET_API SuiPathLoss : public PathLossBase public: SuiPathLoss(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; virtual m computeRange(mps propagationSpeed, Hz frequency, double loss) const override { return m(NaN); } }; diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc index a2239f05583..5e5b6acb5e3 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc @@ -100,7 +100,7 @@ double TwoRayInterference::computeTwoRayInterference(const Coord& pos_t, const C return friis_term * interference_term; } -double TwoRayInterference::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double TwoRayInterference::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { return NaN; } diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h index 48ffb3cfdfb..34c814a25cb 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h @@ -25,7 +25,7 @@ class INET_API TwoRayInterference : public Module, public IPathLoss TwoRayInterference(); void initialize(int stage) override; double computePathLoss(const ITransmission *, const IArrival *) const override; - double computePathLoss(mps propagation, Hz frequency, m distance) const override; + double computeDistancePathLoss(mps propagation, Hz frequency, m distance) const override; m computeRange(mps propagation, Hz frequency, double loss) const override; std::ostream& printToStream(std::ostream&, int level, int evFlags = 0) const override; diff --git a/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.cc b/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.cc index 383867b211f..bcc2a9e7e32 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.cc @@ -91,7 +91,7 @@ double UwbIrStochasticPathLoss::getGhassemzadehPathLoss(double gamma, double S, return math::dB2fraction(attenuation); } -double UwbIrStochasticPathLoss::computePathLoss(mps propagationSpeed, Hz frequency, m distance) const +double UwbIrStochasticPathLoss::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const { double n1 = simtruncnormal(0, 1, n1_limit, 1); double n2 = simtruncnormal(0, 1, n2_limit, 2); diff --git a/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.h b/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.h index 4ca5eeb1a71..62949430843 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.h +++ b/src/inet/physicallayer/wireless/common/pathloss/UwbIrStochasticPathLoss.h @@ -75,7 +75,7 @@ class INET_API UwbIrStochasticPathLoss : public PathLossBase public: UwbIrStochasticPathLoss(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; + virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; virtual m computeRange(mps propagationSpeed, Hz frequency, double loss) const override { return m(NaN); } }; diff --git a/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h b/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h index 6bc65812fe2..e9c234acebf 100644 --- a/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h +++ b/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h @@ -46,7 +46,7 @@ class INET_API FrequencyDependentAttenuationFunction : public FunctionBase& p) const override { Hz frequency = std::get<1>(p); auto propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); - auto pathLoss = radioMedium->getPathLoss()->computePathLoss(propagationSpeed, frequency, distance); + auto pathLoss = radioMedium->getPathLoss()->computeDistancePathLoss(propagationSpeed, frequency, distance); auto obstacleLoss = radioMedium->getObstacleLoss() ? radioMedium->getObstacleLoss()->computeObstacleLoss(frequency, transmissionPosition, receptionPosition) : 1; double gain = transmitterAntennaGain * receiverAntennaGain * pathLoss * obstacleLoss; ASSERT(!std::isnan(gain)); @@ -383,7 +383,7 @@ class INET_API PathLossFunction : public FunctionBase mps propagationSpeed = std::get<0>(p); Hz frequency = std::get<2>(p); m distance = std::get<1>(p); - return pathLoss->computePathLoss(propagationSpeed, frequency, distance); + return pathLoss->computeDistancePathLoss(propagationSpeed, frequency, distance); } virtual void printStructure(std::ostream& os, int level = 0) const override { os << "(" << *pathLoss << ")"; } From ecb2ee8a282367f33fc04e3fb638a6b96d625642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 13 Oct 2026 12:03:29 +0200 Subject: [PATCH 2/4] IPathLoss: computeReceptionPathLoss(), the path loss of one reception IPathLoss::computePathLoss(transmission, arrival) saw the transmitter (through the transmission) but not the receiver, so no path loss could keep per-link state. DimensionalMediumAnalogModel did not call it at all: it reduced the link to its distance and called computeDistancePathLoss() for every frequency it sampled, so a path loss could not use the link's geometry there either. computeReceptionPathLoss(receiverRadio, transmission, arrival) replaces it and returns an IReceptionPathLoss, the path loss of that one reception, whose computePathLoss(frequency) gives the loss factor at any frequency: the same shape as IAntenna::getGain() and IAntennaGain, and like those it keeps the dimensional math out of the contract. Both medium analog models call it once per reception, with the receiver radio they already had: the scalar one evaluates the result at the center frequency, the dimensional one samples it across the band, through FrequencyDependentAttenuationFunction, which takes the reception's path loss in place of calling the path loss model itself. The dimensional model evaluates its attenuation lazily, possibly several times and at any later time, and the medium may delete the transmission and the arrival before the last evaluation, so the returned object holds values only (and may refer to the path loss model): a path loss that keeps per-link state resolves it in the call. The PathLossBase default returns a DistanceReceptionPathLoss, which calls computeDistancePathLoss() for the link's distance whenever it is evaluated, with the same arguments and at the same moments as both analog models did before. TwoRayGroundReflection and TwoRayInterference, the two models that overrode the replaced method, each return a nested ReceptionPathLoss that holds the link's geometry; under the dimensional model they now use it too, where before the former acted as FreeSpacePathLoss and the latter returned NaN. API change, stated in WHATSNEW. --- WHATSNEW | 29 ++++++++++++ .../DimensionalMediumAnalogModel.cc | 3 +- .../scalar/ScalarMediumAnalogModel.cc | 2 +- .../common/base/packetlevel/PathLossBase.cc | 22 ++++++---- .../common/base/packetlevel/PathLossBase.h | 19 +++++++- .../common/contract/packetlevel/IPathLoss.h | 16 +++++-- .../contract/packetlevel/IReceptionPathLoss.h | 44 +++++++++++++++++++ .../common/pathloss/TwoRayGroundReflection.cc | 20 ++++++--- .../common/pathloss/TwoRayGroundReflection.h | 23 +++++++++- .../common/pathloss/TwoRayInterference.cc | 18 ++++---- .../common/pathloss/TwoRayInterference.h | 21 ++++++++- .../wireless/common/signal/PowerFunctions.h | 10 ++--- 12 files changed, 191 insertions(+), 36 deletions(-) create mode 100644 src/inet/physicallayer/wireless/common/contract/packetlevel/IReceptionPathLoss.h diff --git a/WHATSNEW b/WHATSNEW index ad2253ee030..f5f5a02e125 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -414,6 +414,35 @@ Notable backward incompatible changes are the following: computePathLoss(propagationSpeed, frequency, distance): every path loss model derived from PathLossBase overrides it. +24. The path loss of a reception knows its receiver + + IPathLoss::computePathLoss(transmission, arrival) was replaced by + computeReceptionPathLoss(receiverRadio, transmission, arrival). It returns + an IReceptionPathLoss, the path loss of that one reception, whose + computePathLoss(frequency) gives the loss factor at any frequency, the way + IAntenna::getGain() returns an IAntennaGain. Together with the + transmission's transmitter radio, the receiver radio identifies the link, + so a path loss can now keep per-link state (for example a shadowing that + is correlated over distance). Both medium analog models call it once per + reception: ScalarMediumAnalogModel evaluates the result at the center + frequency, DimensionalMediumAnalogModel across the band, in place of the + computeDistancePathLoss() it called before. A path loss with per-link + state resolves that state in the call, and the returned object holds + values only, so the value of a reception does not depend on when or how + often the dimensional model evaluates it. The PathLossBase default + evaluates computeDistancePathLoss() for the link's distance, so the + results of models that only implement that form are unchanged. The + FrequencyDependentAttenuationFunction constructor takes the reception's + path loss as its second argument. + + TwoRayGroundReflection and TwoRayInterference now take the geometry of the + link into account under DimensionalMediumAnalogModel too: before, the + former acted as FreeSpacePathLoss there, and the latter returned NaN. + + This change requires the modification of C++ code that implements or calls + computePathLoss(transmission, arrival), or constructs + FrequencyDependentAttenuationFunction objects. + Notable backward compatible changes are the following: 1. IEEE 802.11 per-station rate statistics diff --git a/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalMediumAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalMediumAnalogModel.cc index 4258def750c..963713bc7e6 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalMediumAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/dimensional/DimensionalMediumAnalogModel.cc @@ -50,7 +50,8 @@ const Ptr>> DimensionalMediumAnalogMode EV_TRACE << "Transmission power end" << endl; Point propagationShift(simsec(arrival->getStartTime() - transmission->getStartTime()), Hz(0)); const auto& propagatedTransmissionPowerFunction = makeShared>>(transmissionPowerFunction, propagationShift); - Ptr>> attenuationFunction = makeShared(radioMedium, transmitterAntennaGain, receiverAntennaGain, transmissionStartPosition, receptionStartPosition); + const auto& receptionPathLoss = radioMedium->getPathLoss()->computeReceptionPathLoss(receiverRadio, transmission, arrival); + Ptr>> attenuationFunction = makeShared(radioMedium, receptionPathLoss, transmitterAntennaGain, receiverAntennaGain, transmissionStartPosition, receptionStartPosition); Ptr>> receptionPower; if (attenuateWithCenterFrequency) { const auto& constantAttenuationFunction = makeShared>>(attenuationFunction->getValue(Point(simsec(0), analogModel->getCenterFrequency()))); diff --git a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc index 96cfcca0efa..0d1031cf886 100644 --- a/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc +++ b/src/inet/physicallayer/wireless/common/analogmodel/scalar/ScalarMediumAnalogModel.cc @@ -52,7 +52,7 @@ W ScalarMediumAnalogModel::computeReceptionPower(const IRadio *receiverRadio, co // TODO could be used for doppler shift? const Coord& receptionEndPosition = arrival->getEndPosition(); double transmitterAntennaGain = computeAntennaGain(transmission->getTransmitterAntennaGain(), transmission->getStartPosition(), arrival->getStartPosition(), transmission->getStartOrientation()); double receiverAntennaGain = computeAntennaGain(receiverRadio->getAntenna()->getGain().get(), arrival->getStartPosition(), transmission->getStartPosition(), arrival->getStartOrientation()); - double pathLoss = radioMedium->getPathLoss()->computePathLoss(transmission, arrival); + double pathLoss = radioMedium->getPathLoss()->computeReceptionPathLoss(receiverRadio, transmission, arrival)->computePathLoss(analogModel->getCenterFrequency()); double obstacleLoss = radioMedium->getObstacleLoss() ? radioMedium->getObstacleLoss()->computeObstacleLoss(analogModel->getCenterFrequency(), transmission->getStartPosition(), receptionStartPosition) : 1; W transmissionPower = analogModel->getPower(); ASSERT(!std::isnan(transmissionPower.get())); diff --git a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc index 5f37ac472f2..914d8424699 100644 --- a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc +++ b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.cc @@ -8,22 +8,26 @@ #include "inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" -#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioSignal.h" - -#include "inet/physicallayer/wireless/common/analogmodel/scalar/ScalarTransmitterAnalogModel.h" namespace inet { namespace physicallayer { -double PathLossBase::computePathLoss(const ITransmission *transmission, const IArrival *arrival) const +std::ostream& PathLossBase::DistanceReceptionPathLoss::printToStream(std::ostream& stream, int level, int evFlags) const +{ + stream << "DistanceReceptionPathLoss"; + if (level <= PRINT_LEVEL_DEBUG) + stream << EV_FIELD(distance); + if (level <= PRINT_LEVEL_TRACE) + stream << EV_FIELD(propagationSpeed) << EV_FIELD(pathLoss, printFieldToString(pathLoss, level + 1, evFlags)); + return stream; +} + +Ptr PathLossBase::computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const { - auto radioMedium = transmission->getMedium(); - auto analogModel = check_and_cast(transmission->getAnalogModel()); - mps propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); - Hz centerFrequency = Hz(analogModel->getCenterFrequency()); + mps propagationSpeed = transmission->getMedium()->getPropagation()->getPropagationSpeed(); m distance = m(arrival->getStartPosition().distance(transmission->getStartPosition())); - return computeDistancePathLoss(propagationSpeed, centerFrequency, distance); + return makeShared(this, propagationSpeed, distance); } } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h index aa195b769fb..c0f9546152c 100644 --- a/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h +++ b/src/inet/physicallayer/wireless/common/base/packetlevel/PathLossBase.h @@ -17,9 +17,26 @@ namespace physicallayer { class INET_API PathLossBase : public Module, public IPathLoss { + protected: + /** + * The path loss of a reception reduced to its distance: computed by + * computeDistancePathLoss() at each frequency. + */ + class INET_API DistanceReceptionPathLoss : public IReceptionPathLoss { + protected: + const PathLossBase *pathLoss; + mps propagationSpeed; + m distance; + + public: + DistanceReceptionPathLoss(const PathLossBase *pathLoss, mps propagationSpeed, m distance) : pathLoss(pathLoss), propagationSpeed(propagationSpeed), distance(distance) {} + virtual double computePathLoss(Hz frequency) const override { return pathLoss->computeDistancePathLoss(propagationSpeed, frequency, distance); } + virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + }; + public: virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override = 0; - virtual double computePathLoss(const ITransmission *transmission, const IArrival *arrival) const override; + virtual Ptr computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h b/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h index 6eddb913c63..59fba2d352d 100644 --- a/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h +++ b/src/inet/physicallayer/wireless/common/contract/packetlevel/IPathLoss.h @@ -9,12 +9,15 @@ #define __INET_IPATHLOSS_H #include "inet/physicallayer/wireless/common/contract/packetlevel/IArrival.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IReceptionPathLoss.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/ITransmission.h" namespace inet { namespace physicallayer { +class IRadio; + /** * This interface models path loss (or path attenuation) that is the reduction * in power density of a radio signal as it propagates through space. @@ -23,11 +26,16 @@ class INET_API IPathLoss : public virtual IPrintableObject { public: /** - * Returns the loss factor for the provided transmission and arrival. - * The value is in the range [0, 1] where 1 means no loss at all and 0 - * means all power is lost. + * Returns the path loss of the reception of the provided transmission by + * the provided receiver radio, for the provided arrival. The medium + * analog model calls this once per reception, when it computes the + * reception, and evaluates the result at one or more frequencies, at that + * time or later. A model that keeps per-link state resolves it in this + * call. The returned object may refer to the path loss model, but not to + * the transmission, the arrival or the radios: the medium may delete them + * while the reception's power is still evaluated. */ - virtual double computePathLoss(const ITransmission *transmission, const IArrival *arrival) const = 0; + virtual Ptr computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const = 0; /** * Returns the loss factor as a function of propagation speed, carrier diff --git a/src/inet/physicallayer/wireless/common/contract/packetlevel/IReceptionPathLoss.h b/src/inet/physicallayer/wireless/common/contract/packetlevel/IReceptionPathLoss.h new file mode 100644 index 00000000000..57e4f1722d9 --- /dev/null +++ b/src/inet/physicallayer/wireless/common/contract/packetlevel/IReceptionPathLoss.h @@ -0,0 +1,44 @@ +// +// Copyright (C) 2026 OpenSim Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +#ifndef __INET_IRECEPTIONPATHLOSS_H +#define __INET_IRECEPTIONPATHLOSS_H + +#include "inet/common/IPrintableObject.h" +#include "inet/common/Ptr.h" +#include "inet/common/Units.h" + +namespace inet { +namespace physicallayer { + +using namespace inet::units::values; + +/** + * This interface represents the path loss of one reception, that is between + * one transmission and one receiver, over frequency. It is created by + * IPathLoss::computeReceptionPathLoss() when the reception is computed, and + * it may be evaluated at any later time, any number of times. + */ +class INET_API IReceptionPathLoss : public virtual IPrintableObject +#if INET_PTR_IMPLEMENTATION == INET_INTRUSIVE_PTR + , public IntrusivePtrCounter +#endif +{ + public: + /** + * Returns the loss factor at the given frequency. The value is in the + * range [0, 1] where 1 means no loss at all and 0 means all power is + * lost. For the same frequency the value is the same, however often and + * however late it is asked for. + */ + virtual double computePathLoss(Hz frequency) const = 0; +}; + +} // namespace physicallayer +} // namespace inet + +#endif diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.cc b/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.cc index fd88bffbb22..e6fd717f0c5 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.cc @@ -15,7 +15,6 @@ #include "inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.h" #include "inet/common/ModuleAccess.h" -#include "inet/physicallayer/wireless/common/contract/packetlevel/INarrowbandSignalAnalogModel.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" namespace inet { @@ -42,18 +41,29 @@ std::ostream& TwoRayGroundReflection::printToStream(std::ostream& stream, int le return stream; } -double TwoRayGroundReflection::computePathLoss(const ITransmission *transmission, const IArrival *arrival) const +std::ostream& TwoRayGroundReflection::ReceptionPathLoss::printToStream(std::ostream& stream, int level, int evFlags) const +{ + stream << "TwoRayGroundReflection::ReceptionPathLoss"; + if (level <= PRINT_LEVEL_DEBUG) + stream << EV_FIELD(distance) << EV_FIELD(transmitterAltitude) << EV_FIELD(receiverAltitude); + return stream; +} + +Ptr TwoRayGroundReflection::computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const { auto radioMedium = transmission->getMedium(); - auto narrowbandSignalAnalogModel = check_and_cast(transmission->getAnalogModel()); auto transmitterPosition = transmission->getStartPosition(); auto recepiverPosition = arrival->getStartPosition(); mps propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); - Hz centerFrequency = narrowbandSignalAnalogModel->getCenterFrequency(); m distance = m(recepiverPosition.distance(transmitterPosition)); m transmitterAltitude = m(transmitterPosition.distance(physicalEnvironment->getGround()->computeGroundProjection(transmitterPosition))); m receiverAltitude = m(recepiverPosition.distance(physicalEnvironment->getGround()->computeGroundProjection(recepiverPosition))); - m waveLength = propagationSpeed / centerFrequency; + return makeShared(this, propagationSpeed, distance, transmitterAltitude, receiverAltitude); +} + +double TwoRayGroundReflection::computeTwoRayGroundReflection(mps propagationSpeed, Hz frequency, m distance, m transmitterAltitude, m receiverAltitude) const +{ + m waveLength = propagationSpeed / frequency; /** * At the cross over distance two ray model and free space model predict the same power * diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.h b/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.h index 327a37d8889..cbb72dc0ef5 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.h +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayGroundReflection.h @@ -27,15 +27,36 @@ namespace physicallayer { */ class INET_API TwoRayGroundReflection : public FreeSpacePathLoss { + protected: + /** + * The path loss of one reception: the distance and the altitudes of the + * two ends are taken when the reception is computed. + */ + class INET_API ReceptionPathLoss : public IReceptionPathLoss { + protected: + const TwoRayGroundReflection *pathLoss; + mps propagationSpeed; + m distance; + m transmitterAltitude; + m receiverAltitude; + + public: + ReceptionPathLoss(const TwoRayGroundReflection *pathLoss, mps propagationSpeed, m distance, m transmitterAltitude, m receiverAltitude) : + pathLoss(pathLoss), propagationSpeed(propagationSpeed), distance(distance), transmitterAltitude(transmitterAltitude), receiverAltitude(receiverAltitude) {} + virtual double computePathLoss(Hz frequency) const override { return pathLoss->computeTwoRayGroundReflection(propagationSpeed, frequency, distance, transmitterAltitude, receiverAltitude); } + virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + }; + protected: const physicalenvironment::IPhysicalEnvironment *physicalEnvironment = nullptr; protected: virtual void initialize(int stage) override; + virtual double computeTwoRayGroundReflection(mps propagationSpeed, Hz frequency, m distance, m transmitterAltitude, m receiverAltitude) const; public: virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; - virtual double computePathLoss(const ITransmission *transmission, const IArrival *arrival) const override; + virtual Ptr computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const override; }; } // namespace physicallayer diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc index 5e5b6acb5e3..be861b208d0 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.cc @@ -1,7 +1,6 @@ #include "inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h" -#include "inet/physicallayer/wireless/common/contract/packetlevel/INarrowbandSignalAnalogModel.h" #include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" namespace inet { @@ -48,15 +47,18 @@ std::ostream& TwoRayInterference::printToStream(std::ostream& os, int level, int return os; } -double TwoRayInterference::computePathLoss(const ITransmission *transmission, const IArrival *arrival) const +std::ostream& TwoRayInterference::ReceptionPathLoss::printToStream(std::ostream& stream, int level, int evFlags) const { - auto radioMedium = transmission->getMedium(); - auto narrowbandSignalAnalogModel = check_and_cast(transmission->getAnalogModel()); - mps propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); - Hz centerFrequency = Hz(narrowbandSignalAnalogModel->getCenterFrequency()); - const m waveLength = propagationSpeed / centerFrequency; + stream << "TwoRayInterference::ReceptionPathLoss"; + if (level <= PRINT_LEVEL_DEBUG) + stream << EV_FIELD(transmitterPosition) << EV_FIELD(receiverPosition); + return stream; +} - return computeTwoRayInterference(transmission->getStartPosition(), arrival->getStartPosition(), waveLength); +Ptr TwoRayInterference::computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const +{ + mps propagationSpeed = transmission->getMedium()->getPropagation()->getPropagationSpeed(); + return makeShared(this, propagationSpeed, transmission->getStartPosition(), arrival->getStartPosition()); } double TwoRayInterference::computeTwoRayInterference(const Coord& pos_t, const Coord& pos_r, m lambda) const diff --git a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h index 34c814a25cb..ea9e4453837 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h +++ b/src/inet/physicallayer/wireless/common/pathloss/TwoRayInterference.h @@ -24,11 +24,30 @@ class INET_API TwoRayInterference : public Module, public IPathLoss public: TwoRayInterference(); void initialize(int stage) override; - double computePathLoss(const ITransmission *, const IArrival *) const override; + Ptr computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const override; double computeDistancePathLoss(mps propagation, Hz frequency, m distance) const override; m computeRange(mps propagation, Hz frequency, double loss) const override; std::ostream& printToStream(std::ostream&, int level, int evFlags = 0) const override; + protected: + /** + * The path loss of one reception: the positions of the two ends are + * taken when the reception is computed. + */ + class INET_API ReceptionPathLoss : public IReceptionPathLoss { + protected: + const TwoRayInterference *pathLoss; + mps propagationSpeed; + Coord transmitterPosition; + Coord receiverPosition; + + public: + ReceptionPathLoss(const TwoRayInterference *pathLoss, mps propagationSpeed, const Coord& transmitterPosition, const Coord& receiverPosition) : + pathLoss(pathLoss), propagationSpeed(propagationSpeed), transmitterPosition(transmitterPosition), receiverPosition(receiverPosition) {} + virtual double computePathLoss(Hz frequency) const override { return pathLoss->computeTwoRayInterference(transmitterPosition, receiverPosition, propagationSpeed / frequency); } + virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + }; + protected: double epsilon_r; char polarization; diff --git a/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h b/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h index e9c234acebf..10881d83eb5 100644 --- a/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h +++ b/src/inet/physicallayer/wireless/common/signal/PowerFunctions.h @@ -31,6 +31,7 @@ class INET_API FrequencyDependentAttenuationFunction : public FunctionBase receptionPathLoss; const double transmitterAntennaGain; const double receiverAntennaGain; const Coord transmissionPosition; @@ -38,15 +39,14 @@ class INET_API FrequencyDependentAttenuationFunction : public FunctionBase& receptionPathLoss, const double transmitterAntennaGain, const double receiverAntennaGain, const Coord& transmissionPosition, const Coord& receptionPosition) : + radioMedium(radioMedium), receptionPathLoss(receptionPathLoss), transmitterAntennaGain(transmitterAntennaGain), receiverAntennaGain(receiverAntennaGain), transmissionPosition(transmissionPosition), receptionPosition(receptionPosition), distance(m(transmissionPosition.distance(receptionPosition))) { } virtual double getValue(const Point& p) const override { Hz frequency = std::get<1>(p); - auto propagationSpeed = radioMedium->getPropagation()->getPropagationSpeed(); - auto pathLoss = radioMedium->getPathLoss()->computeDistancePathLoss(propagationSpeed, frequency, distance); + auto pathLoss = receptionPathLoss->computePathLoss(frequency); auto obstacleLoss = radioMedium->getObstacleLoss() ? radioMedium->getObstacleLoss()->computeObstacleLoss(frequency, transmissionPosition, receptionPosition) : 1; double gain = transmitterAntennaGain * receiverAntennaGain * pathLoss * obstacleLoss; ASSERT(!std::isnan(gain)); @@ -63,7 +63,7 @@ class INET_API FrequencyDependentAttenuationFunction : public FunctionBasegetPathLoss() << ")"; + os << "(" << *receptionPathLoss << ")"; os << "\n" << std::string(level + 2, ' '); os << "(" << *radioMedium->getObstacleLoss() << "))"; } From b8f4206099d66e29b3109139fbd807975d274ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 13 Oct 2026 12:09:57 +0200 Subject: [PATCH 3/4] LogNormalShadowing: optional per-link correlation distance LogNormalShadowing drew a new shadowing value on every path loss computation: two receptions of the same link at the same position could differ by several sigma. With computeReceptionPathLoss() knowing the receiver radio, a path loss can keep per-link state; this is the first to do so. New parameter correlationDistance, default NaN = the memoryless draw as before. With a distance, the value is kept per pair of radios, keyed by the smaller radio id first, together with the position of each radio at the draw, and drawn again when either radio has moved farther than the distance from its position then. Shadowing is caused by the obstacles on the path between the two radios: it is the same in both directions, and it changes when either end moves (with a receiver-only rule, the uplink of a node moving away from a stationary base station would keep its first value forever). The shadowing draw moved into a helper, computeShadowedPathLoss(); in the default mode it is drawn once per computation at the same point as before. The value is resolved in computeReceptionPathLoss(), once per reception, and the returned ReceptionPathLoss holds it, so it does not depend on when or how often the medium analog model evaluates it: with DimensionalMediumAnalogModel the whole band of a reception has the same shadowing, as with the scalar model. The values of a radio's links are dropped when the radio is removed from the medium, so that in a simulation where nodes come and go the map does not grow with every radio that ever existed. When a correlation distance is set, the module subscribes to IRadioMedium::radioRemovedSignal at its parent module, the medium, which emits the signal on itself; the parent is checked to be an IRadioMedium, as the obstacle loss models and MediumLimitCache look up theirs. With the default nothing is subscribed. Tests: LogNormalShadowing_Correlated_1..5 (stationary nodes: one power for all eight receptions, both directions; the default: a new value every time; host2 circling a stationary host1 at constant distance: a new value per ping, reused by its reply, four powers each received in both directions; host2 deleted after two pings: its one pair is dropped; the stationary case on a dimensional medium: one power spectral density for all eight receptions). --- WHATSNEW | 13 +++ .../common/pathloss/LogNormalShadowing.cc | 72 ++++++++++++++- .../common/pathloss/LogNormalShadowing.h | 40 ++++++++- .../common/pathloss/LogNormalShadowing.ned | 1 + .../LogNormalShadowing_Correlated_1.test | 81 +++++++++++++++++ .../LogNormalShadowing_Correlated_2.test | 79 ++++++++++++++++ .../LogNormalShadowing_Correlated_3.test | 89 +++++++++++++++++++ .../LogNormalShadowing_Correlated_4.test | 84 +++++++++++++++++ .../LogNormalShadowing_Correlated_5.test | 82 +++++++++++++++++ 9 files changed, 537 insertions(+), 4 deletions(-) create mode 100644 tests/module/LogNormalShadowing_Correlated_1.test create mode 100644 tests/module/LogNormalShadowing_Correlated_2.test create mode 100644 tests/module/LogNormalShadowing_Correlated_3.test create mode 100644 tests/module/LogNormalShadowing_Correlated_4.test create mode 100644 tests/module/LogNormalShadowing_Correlated_5.test diff --git a/WHATSNEW b/WHATSNEW index f5f5a02e125..9d67769aacb 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -551,6 +551,19 @@ Notable backward compatible changes are the following: reservation time, and support limits. The feature does not implement full shared TXNAV or admission of a complete exchange against the remaining budget. +9. Correlated log-normal shadowing + + LogNormalShadowing has a new correlationDistance parameter. By default + (NaN) every path loss computation draws a new shadowing value, as before. + With a distance, the value is kept per pair of radios, the same in both + directions, and drawn again only when either radio has moved farther than + that distance from where it was when the value was drawn, which is how + shadowing behaves physically. The value is resolved once per reception, + so with DimensionalMediumAnalogModel the whole band of a reception has + the same shadowing. The values of a radio's links are dropped when the + radio is removed from the medium. + + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc index e36708578ca..6c0c5ce3245 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc @@ -14,6 +14,9 @@ #include "inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadio.h" +#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h" + namespace inet { namespace physicallayer { @@ -30,6 +33,12 @@ void LogNormalShadowing::initialize(int stage) FreeSpacePathLoss::initialize(stage); if (stage == INITSTAGE_LOCAL) { sigma = par("sigma"); + correlationDistance = m(par("correlationDistance")); + if (!std::isnan(correlationDistance.get())) { + auto medium = check_and_cast(getParentModule()); + // the medium, whose submodule this is, emits this signal on itself + check_and_cast(medium)->subscribe(IRadioMedium::radioRemovedSignal, this); + } } } @@ -39,18 +48,75 @@ std::ostream& LogNormalShadowing::printToStream(std::ostream& stream, int level, if (level <= PRINT_LEVEL_TRACE) stream << EV_FIELD(alpha) << EV_FIELD(systemLoss) - << EV_FIELD(sigma); + << EV_FIELD(sigma) + << EV_FIELD(correlationDistance); return stream; } +void LogNormalShadowing::receiveSignal(cComponent *source, simsignal_t signal, cObject *object, cObject *details) +{ + Enter_Method("%s", cComponent::getSignalName(signal)); + if (signal == IRadioMedium::radioRemovedSignal) { + int radioId = check_and_cast(object)->getId(); + int count = 0; + for (auto it = linkShadowings.begin(); it != linkShadowings.end(); ) { + if (it->first.first == radioId || it->first.second == radioId) { + it = linkShadowings.erase(it); + count++; + } + else + ++it; + } + EV_DEBUG << "Dropped the shadowing values of a removed radio" << EV_FIELD(radioId) << EV_FIELD(count) << EV_ENDL; + } + else + throw cRuntimeError("Unknown signal"); +} + +std::ostream& LogNormalShadowing::ReceptionPathLoss::printToStream(std::ostream& stream, int level, int evFlags) const +{ + stream << "LogNormalShadowing::ReceptionPathLoss"; + if (level <= PRINT_LEVEL_DEBUG) + stream << EV_FIELD(distance) << EV_FIELD(shadowing); + return stream; +} + +Ptr LogNormalShadowing::computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const +{ + if (std::isnan(correlationDistance.get())) + return PathLossBase::computeReceptionPathLoss(receiverRadio, transmission, arrival); + // shadowing is reciprocal: one value per pair of radios, the smaller id first + int transmitterId = transmission->getTransmitterRadioId(); + int receiverId = receiverRadio->getId(); + const Coord& transmitterPosition = transmission->getStartPosition(); + const Coord& receiverPosition = arrival->getStartPosition(); + bool transmitterFirst = transmitterId < receiverId; + auto key = transmitterFirst ? std::make_pair(transmitterId, receiverId) : std::make_pair(receiverId, transmitterId); + const Coord& position1 = transmitterFirst ? transmitterPosition : receiverPosition; + const Coord& position2 = transmitterFirst ? receiverPosition : transmitterPosition; + auto it = linkShadowings.find(key); + if (it == linkShadowings.end() + || m(it->second.position1.distance(position1)) > correlationDistance + || m(it->second.position2.distance(position2)) > correlationDistance) + it = linkShadowings.insert_or_assign(key, LinkShadowing{position1, position2, normal(0.0, sigma)}).first; + mps propagationSpeed = transmission->getMedium()->getPropagation()->getPropagationSpeed(); + m distance = m(receiverPosition.distance(transmitterPosition)); + return makeShared(this, propagationSpeed, distance, it->second.shadowing); +} + double LogNormalShadowing::computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const +{ + return computeShadowedPathLoss(propagationSpeed, frequency, distance, normal(0.0, sigma)); +} + +double LogNormalShadowing::computeShadowedPathLoss(mps propagationSpeed, Hz frequency, m distance, double shadowing) const { m d0 = m(1.0); // reference path loss double freeSpacePathLoss = computeFreeSpacePathLoss(propagationSpeed / frequency, d0, alpha, systemLoss); double PL_d0_db = 10.0 * log10(1 / freeSpacePathLoss); - // path loss at distance d + normal distribution with sigma standard deviation - double PL_db = PL_d0_db + 10 * alpha * log10((distance / d0).get()) + normal(0.0, sigma); + // path loss at distance d + the shadowing (dB) + double PL_db = PL_d0_db + 10 * alpha * log10((distance / d0).get()) + shadowing; return math::dB2fraction(-PL_db); } diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h index ba2c0b0a4eb..d7e348800d8 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h @@ -15,6 +15,8 @@ #ifndef __INET_LOGNORMALSHADOWING_H #define __INET_LOGNORMALSHADOWING_H +#include + #include "inet/physicallayer/wireless/common/pathloss/FreeSpacePathLoss.h" namespace inet { @@ -23,18 +25,54 @@ namespace physicallayer { /** * This class implements the log normal shadowing model. + * + * By default every path loss computation draws a new shadowing value. With a + * correlation distance, the value is kept per pair of radios, the same in + * both directions, and drawn again only when either radio has moved farther + * than that distance from where it was when the value was drawn. The values + * of a radio's links are dropped when the radio is removed from its medium. */ -class INET_API LogNormalShadowing : public FreeSpacePathLoss +class INET_API LogNormalShadowing : public FreeSpacePathLoss, public cListener { + protected: + /** + * The path loss of one reception, with the shadowing value resolved for + * it when the reception is computed. + */ + class INET_API ReceptionPathLoss : public IReceptionPathLoss { + protected: + const LogNormalShadowing *pathLoss; + mps propagationSpeed; + m distance; + double shadowing; // dB + + public: + ReceptionPathLoss(const LogNormalShadowing *pathLoss, mps propagationSpeed, m distance, double shadowing) : + pathLoss(pathLoss), propagationSpeed(propagationSpeed), distance(distance), shadowing(shadowing) {} + virtual double computePathLoss(Hz frequency) const override { return pathLoss->computeShadowedPathLoss(propagationSpeed, frequency, distance, shadowing); } + virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + }; + + struct LinkShadowing { + Coord position1; // the position of the radio with the smaller id at the draw + Coord position2; // the position of the radio with the larger id at the draw + double shadowing; // dB + }; + protected: double sigma; + m correlationDistance = m(NaN); + mutable std::map, LinkShadowing> linkShadowings; // by (smaller radio id, larger radio id) protected: virtual void initialize(int stage) override; + virtual double computeShadowedPathLoss(mps propagationSpeed, Hz frequency, m distance, double shadowing) const; + virtual void receiveSignal(cComponent *source, simsignal_t signal, cObject *object, cObject *details) override; public: LogNormalShadowing(); virtual std::ostream& printToStream(std::ostream& stream, int level, int evFlags = 0) const override; + virtual Ptr computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const override; virtual double computeDistancePathLoss(mps propagationSpeed, Hz frequency, m distance) const override; }; diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned index 6f9f0c6d6c1..2ac91e8036f 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned @@ -19,6 +19,7 @@ module LogNormalShadowing extends FreeSpacePathLoss { parameters: double sigma = default(1); + double correlationDistance @unit(m) = default(nan m); // NaN: a new shadowing value on every computation; otherwise the value is kept per pair of radios, the same in both directions, until either radio has moved farther than this distance from where it was when the value was drawn @class(LogNormalShadowing); } diff --git a/tests/module/LogNormalShadowing_Correlated_1.test b/tests/module/LogNormalShadowing_Correlated_1.test new file mode 100644 index 00000000000..766dc3ae216 --- /dev/null +++ b/tests/module/LogNormalShadowing_Correlated_1.test @@ -0,0 +1,81 @@ +%description: + +LogNormalShadowing with a correlation distance, both nodes stationary: the +pair of radios keeps the shadowing value drawn for it, the same in both +directions, so all eight receptions (four requests, four replies) have the +same power. + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.common.medium.ScalarRadioMedium; + +network Test +{ + submodules: + radioMedium: ScalarRadioMedium; + configurator: Ipv4NetworkConfigurator; + host1: WirelessHost; + host2: WirelessHost; +} + +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Test +sim-time-limit = 4.5s +ned-path = .;../../../../src +cmdenv-express-mode = false +cmdenv-event-banners = false +**.arp.typename = "GlobalArp" + +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 1000m +**.mobility.constraintAreaMaxY = 1000m +**.mobility.constraintAreaMaxZ = 0m +**.mobility.initFromDisplayString = false +**.mobility.initialY = 500m +**.mobility.initialZ = 0m +*.host1.mobility.initialX = 400m +*.host2.mobility.initialX = 500m + +*.radioMedium.backgroundNoise.power = -110dBm +*.radioMedium.pathLoss.typename = "LogNormalShadowing" +*.radioMedium.pathLoss.sigma = 8 +*.radioMedium.pathLoss.correlationDistance = 10m +*.host*.wlan[*].typename = "AckingWirelessInterface" +*.host*.wlan[*].bitrate = 2Mbps +*.host*.wlan[*].mac.headerLength = 10B +*.host*.wlan[*].radio.typename = "ApskScalarRadio" +*.host*.wlan[*].radio.centerFrequency = 2.4GHz +*.host*.wlan[*].radio.bandwidth = 2MHz +*.host*.wlan[*].radio.transmitter.power = 100mW +*.host*.wlan[*].radio.transmitter.preambleDuration = 0s +*.host*.wlan[*].radio.transmitter.headerLength = 96b +*.host*.wlan[*].radio.receiver.energyDetection = -95dBm +*.host*.wlan[*].radio.receiver.sensitivity = -95dBm +*.host*.wlan[*].radio.receiver.snirThreshold = 4dB + +*.host1.numApps = 1 +*.host1.app[0].typename = "PingApp" +*.host1.app[0].destAddr = "host2" +*.host1.app[0].count = 4 +*.host1.app[0].sendInterval = 1s +*.host1.app[0].startTime = 0.5s + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep -a "Reception started.*WHOLE as Reception" test.out | sed -E 's/.*receiverdRadioId = ([0-9]+),.*analogModel = \{ ScalarSignalAnalogModel, power = ([^,]*),.*/\1 \2/' | sort -u > powers.out; echo "receptions=$(grep -a -c 'Reception started.*WHOLE as Reception' test.out) distinct=$(wc -l < powers.out) links=$(cut -d' ' -f1 powers.out | sort -u | wc -l) powers=$(cut -d' ' -f2 powers.out | sort -u | wc -l)" > summary.out +%contains: summary.out +receptions=8 distinct=2 links=2 powers=1 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/LogNormalShadowing_Correlated_2.test b/tests/module/LogNormalShadowing_Correlated_2.test new file mode 100644 index 00000000000..16f788da20b --- /dev/null +++ b/tests/module/LogNormalShadowing_Correlated_2.test @@ -0,0 +1,79 @@ +%description: + +LogNormalShadowing with the default correlation distance (NaN), both nodes +stationary: every computation draws a new shadowing value, so all eight +receptions have different powers, as before the parameter existed. + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.common.medium.ScalarRadioMedium; + +network Test +{ + submodules: + radioMedium: ScalarRadioMedium; + configurator: Ipv4NetworkConfigurator; + host1: WirelessHost; + host2: WirelessHost; +} + +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Test +sim-time-limit = 4.5s +ned-path = .;../../../../src +cmdenv-express-mode = false +cmdenv-event-banners = false +**.arp.typename = "GlobalArp" + +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 1000m +**.mobility.constraintAreaMaxY = 1000m +**.mobility.constraintAreaMaxZ = 0m +**.mobility.initFromDisplayString = false +**.mobility.initialY = 500m +**.mobility.initialZ = 0m +*.host1.mobility.initialX = 400m +*.host2.mobility.initialX = 500m + +*.radioMedium.backgroundNoise.power = -110dBm +*.radioMedium.pathLoss.typename = "LogNormalShadowing" +*.radioMedium.pathLoss.sigma = 8 +*.host*.wlan[*].typename = "AckingWirelessInterface" +*.host*.wlan[*].bitrate = 2Mbps +*.host*.wlan[*].mac.headerLength = 10B +*.host*.wlan[*].radio.typename = "ApskScalarRadio" +*.host*.wlan[*].radio.centerFrequency = 2.4GHz +*.host*.wlan[*].radio.bandwidth = 2MHz +*.host*.wlan[*].radio.transmitter.power = 100mW +*.host*.wlan[*].radio.transmitter.preambleDuration = 0s +*.host*.wlan[*].radio.transmitter.headerLength = 96b +*.host*.wlan[*].radio.receiver.energyDetection = -95dBm +*.host*.wlan[*].radio.receiver.sensitivity = -95dBm +*.host*.wlan[*].radio.receiver.snirThreshold = 4dB + +*.host1.numApps = 1 +*.host1.app[0].typename = "PingApp" +*.host1.app[0].destAddr = "host2" +*.host1.app[0].count = 4 +*.host1.app[0].sendInterval = 1s +*.host1.app[0].startTime = 0.5s + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep -a "Reception started.*WHOLE as Reception" test.out | sed -E 's/.*receiverdRadioId = ([0-9]+),.*analogModel = \{ ScalarSignalAnalogModel, power = ([^,]*),.*/\1 \2/' | sort -u > powers.out; echo "receptions=$(grep -a -c 'Reception started.*WHOLE as Reception' test.out) distinct=$(wc -l < powers.out) links=$(cut -d' ' -f1 powers.out | sort -u | wc -l)" > summary.out +%contains: summary.out +receptions=8 distinct=8 links=2 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/LogNormalShadowing_Correlated_3.test b/tests/module/LogNormalShadowing_Correlated_3.test new file mode 100644 index 00000000000..0bdb0d2e247 --- /dev/null +++ b/tests/module/LogNormalShadowing_Correlated_3.test @@ -0,0 +1,89 @@ +%description: + +LogNormalShadowing with a 10 m correlation distance, host2 circling host1 +at a constant 100 m, 20 m/s: the distance and thus the deterministic path +loss never change, so only a redraw changes the power. host2 moves about +20 m between pings, so each request draws a new value, although host1 never +moves; the reply, a few milliseconds later, reuses it: four distinct +powers, each received in both directions. + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.common.medium.ScalarRadioMedium; + +network Test +{ + submodules: + radioMedium: ScalarRadioMedium; + configurator: Ipv4NetworkConfigurator; + host1: WirelessHost; + host2: WirelessHost; +} + +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Test +sim-time-limit = 4.5s +ned-path = .;../../../../src +cmdenv-express-mode = false +cmdenv-event-banners = false +**.arp.typename = "GlobalArp" + +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 1000m +**.mobility.constraintAreaMaxY = 1000m +**.mobility.constraintAreaMaxZ = 0m +**.mobility.initFromDisplayString = false +**.mobility.initialY = 500m +**.mobility.initialZ = 0m +*.host1.mobility.initialX = 400m +*.host2.mobility.initialX = 500m +*.host2.mobility.typename = "CircleMobility" +*.host2.mobility.cx = 400m +*.host2.mobility.cy = 500m +*.host2.mobility.r = 100m +*.host2.mobility.speed = 20mps +*.host2.mobility.startAngle = 0deg + +*.radioMedium.backgroundNoise.power = -110dBm +*.radioMedium.pathLoss.typename = "LogNormalShadowing" +*.radioMedium.pathLoss.sigma = 8 +*.radioMedium.pathLoss.correlationDistance = 10m +*.host*.wlan[*].typename = "AckingWirelessInterface" +*.host*.wlan[*].bitrate = 2Mbps +*.host*.wlan[*].mac.headerLength = 10B +*.host*.wlan[*].radio.typename = "ApskScalarRadio" +*.host*.wlan[*].radio.centerFrequency = 2.4GHz +*.host*.wlan[*].radio.bandwidth = 2MHz +*.host*.wlan[*].radio.transmitter.power = 100mW +*.host*.wlan[*].radio.transmitter.preambleDuration = 0s +*.host*.wlan[*].radio.transmitter.headerLength = 96b +*.host*.wlan[*].radio.receiver.energyDetection = -95dBm +*.host*.wlan[*].radio.receiver.sensitivity = -95dBm +*.host*.wlan[*].radio.receiver.snirThreshold = 4dB + +*.host1.numApps = 1 +*.host1.app[0].typename = "PingApp" +*.host1.app[0].destAddr = "host2" +*.host1.app[0].count = 4 +*.host1.app[0].sendInterval = 1s +*.host1.app[0].startTime = 0.5s + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep -a "Reception started.*WHOLE as Reception" test.out | sed -E 's/.*receiverdRadioId = ([0-9]+),.*analogModel = \{ ScalarSignalAnalogModel, power = ([^,]*),.*/\1 \2/' | sort -u > powers.out; echo "receptions=$(grep -a -c 'Reception started.*WHOLE as Reception' test.out) distinct=$(wc -l < powers.out) links=$(cut -d' ' -f1 powers.out | sort -u | wc -l) powers=$(cut -d' ' -f2 powers.out | sort -u | wc -l)" > summary.out +%contains: summary.out +receptions=8 distinct=8 links=2 powers=4 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/LogNormalShadowing_Correlated_4.test b/tests/module/LogNormalShadowing_Correlated_4.test new file mode 100644 index 00000000000..470df28ca4d --- /dev/null +++ b/tests/module/LogNormalShadowing_Correlated_4.test @@ -0,0 +1,84 @@ +%description: + +LogNormalShadowing with a correlation distance keeps a value per pair of +radios; when a radio is removed from the medium, the values of its pairs are +dropped. After two pings host2 is deleted, which removes its radio: its one +pair (with host1, both directions) is dropped. + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.common.medium.ScalarRadioMedium; +import inet.common.scenario.ScenarioManager; + +network Test +{ + submodules: + radioMedium: ScalarRadioMedium; + configurator: Ipv4NetworkConfigurator; + host1: WirelessHost; + host2: WirelessHost; + scenarioManager: ScenarioManager; +} + +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Test +sim-time-limit = 3s +ned-path = .;../../../../src +cmdenv-express-mode = false +cmdenv-event-banners = false +**.arp.typename = "GlobalArp" + +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 1000m +**.mobility.constraintAreaMaxY = 1000m +**.mobility.constraintAreaMaxZ = 0m +**.mobility.initFromDisplayString = false +**.mobility.initialY = 500m +**.mobility.initialZ = 0m +*.host1.mobility.initialX = 400m +*.host2.mobility.initialX = 500m + +*.radioMedium.backgroundNoise.power = -110dBm +*.radioMedium.pathLoss.typename = "LogNormalShadowing" +*.radioMedium.pathLoss.sigma = 8 +*.radioMedium.pathLoss.correlationDistance = 10m +*.host*.wlan[*].typename = "AckingWirelessInterface" +*.host*.wlan[*].bitrate = 2Mbps +*.host*.wlan[*].mac.headerLength = 10B +*.host*.wlan[*].radio.typename = "ApskScalarRadio" +*.host*.wlan[*].radio.centerFrequency = 2.4GHz +*.host*.wlan[*].radio.bandwidth = 2MHz +*.host*.wlan[*].radio.transmitter.power = 100mW +*.host*.wlan[*].radio.transmitter.preambleDuration = 0s +*.host*.wlan[*].radio.transmitter.headerLength = 96b +*.host*.wlan[*].radio.receiver.energyDetection = -95dBm +*.host*.wlan[*].radio.receiver.sensitivity = -95dBm +*.host*.wlan[*].radio.receiver.snirThreshold = 4dB + +*.host1.numApps = 1 +*.host1.app[0].typename = "PingApp" +*.host1.app[0].destAddr = "host2" +*.host1.app[0].count = 2 +*.host1.app[0].sendInterval = 1s +*.host1.app[0].startTime = 0.5s + +*.scenarioManager.script = xml("") + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%contains: stdout +Dropped the shadowing values of a removed radio, radioId = 1, count = 1. +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- diff --git a/tests/module/LogNormalShadowing_Correlated_5.test b/tests/module/LogNormalShadowing_Correlated_5.test new file mode 100644 index 00000000000..95eb11b841d --- /dev/null +++ b/tests/module/LogNormalShadowing_Correlated_5.test @@ -0,0 +1,82 @@ +%description: + +LogNormalShadowing with a correlation distance on a medium whose analog model +is DimensionalMediumAnalogModel, both nodes stationary: as with the scalar +model, the pair of radios keeps the shadowing value drawn for it, the same in +both directions, so all eight receptions (four requests, four replies) have +the same power spectral density. + +%file: test.ned + +import inet.networklayer.configurator.ipv4.Ipv4NetworkConfigurator; +import inet.node.inet.WirelessHost; +import inet.physicallayer.wireless.common.medium.DimensionalRadioMedium; + +network Test +{ + submodules: + radioMedium: DimensionalRadioMedium; + configurator: Ipv4NetworkConfigurator; + host1: WirelessHost; + host2: WirelessHost; +} + +%inifile: omnetpp.ini + +[General] +record-vector-results = false +network = Test +sim-time-limit = 4.5s +ned-path = .;../../../../src +cmdenv-express-mode = false +cmdenv-event-banners = false +**.arp.typename = "GlobalArp" + +**.mobility.constraintAreaMinX = 0m +**.mobility.constraintAreaMinY = 0m +**.mobility.constraintAreaMinZ = 0m +**.mobility.constraintAreaMaxX = 1000m +**.mobility.constraintAreaMaxY = 1000m +**.mobility.constraintAreaMaxZ = 0m +**.mobility.initFromDisplayString = false +**.mobility.initialY = 500m +**.mobility.initialZ = 0m +*.host1.mobility.initialX = 400m +*.host2.mobility.initialX = 500m + +*.radioMedium.backgroundNoise.powerSpectralDensity = -113dBmWpMHz +*.radioMedium.pathLoss.typename = "LogNormalShadowing" +*.radioMedium.pathLoss.sigma = 8 +*.radioMedium.pathLoss.correlationDistance = 10m +*.host*.wlan[*].typename = "AckingWirelessInterface" +*.host*.wlan[*].bitrate = 2Mbps +*.host*.wlan[*].mac.headerLength = 10B +*.host*.wlan[*].radio.typename = "ApskDimensionalRadio" +*.host*.wlan[*].radio.centerFrequency = 2.4GHz +*.host*.wlan[*].radio.bandwidth = 2MHz +*.host*.wlan[*].radio.transmitter.power = 100mW +*.host*.wlan[*].radio.transmitter.preambleDuration = 0s +*.host*.wlan[*].radio.transmitter.headerLength = 96b +*.host*.wlan[*].radio.receiver.energyDetection = -95dBm +*.host*.wlan[*].radio.receiver.sensitivity = -95dBm +*.host*.wlan[*].radio.receiver.snirThreshold = 4dB + +*.host1.numApps = 1 +*.host1.app[0].typename = "PingApp" +*.host1.app[0].destAddr = "host2" +*.host1.app[0].count = 4 +*.host1.app[0].sendInterval = 1s +*.host1.app[0].startTime = 0.5s + +%#-------------------------------------------------------------------------------------------------------------- +%# remove colorizing +%subst: /\x1B\[[0-9;]*m// +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep -a "Reception started.*WHOLE as Reception" test.out | sed -E 's/.*receiverdRadioId = ([0-9]+),.*analogModel = \{ DimensionalSignalAnalogModel, powerMax = ([^,]*), powerMin = ([^,]*),.*/\1 \2-\3/' | sort -u > powers.out; echo "receptions=$(grep -a -c 'Reception started.*WHOLE as Reception' test.out) distinct=$(wc -l < powers.out) links=$(cut -d' ' -f1 powers.out | sort -u | wc -l) powers=$(cut -d' ' -f2 powers.out | sort -u | wc -l)" > summary.out +%contains: summary.out +receptions=8 distinct=2 links=2 powers=1 +%#-------------------------------------------------------------------------------------------------------------- +%postrun-command: grep "undisposed object:" test.out > test_undisposed.out || true +%not-contains: test_undisposed.out +undisposed object: ( +%#-------------------------------------------------------------------------------------------------------------- From d83f1eeac5eb56a0cfdf81104fb092b84ece5de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 13 Oct 2026 12:18:00 +0200 Subject: [PATCH 4/4] LogNormalShadowing: one shadowing value per reception Without a correlation distance, computeReceptionPathLoss() returned the PathLossBase default, which calls computeDistancePathLoss() on every evaluation, and that draws a new shadowing value each time. DimensionalMediumAnalogModel evaluates the attenuation per frequency sample and possibly again later, so the samples of one reception had independent shadowing values, and a repeated evaluation gave a different one. computeReceptionPathLoss() now draws the value once, when the reception is computed, and returns a ReceptionPathLoss that holds it, as with a correlation distance. The scalar model and the dimensional one with the default attenuateWithCenterFrequency evaluate it once, right after the call, so the draws happen in the same order as before. computeDistancePathLoss() keeps drawing per call; only callers without a link use it. --- WHATSNEW | 9 +++++++++ .../wireless/common/pathloss/LogNormalShadowing.cc | 11 ++++++----- .../wireless/common/pathloss/LogNormalShadowing.h | 9 +++++---- .../wireless/common/pathloss/LogNormalShadowing.ned | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/WHATSNEW b/WHATSNEW index 9d67769aacb..1917d71e9c4 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -563,6 +563,15 @@ Notable backward compatible changes are the following: the same shadowing. The values of a radio's links are dropped when the radio is removed from the medium. +10. Log-normal shadowing once per reception + + Without a correlation distance, LogNormalShadowing draws one shadowing + value per reception. With DimensionalMediumAnalogModel it drew one per + evaluation of the attenuation, so the frequencies of one reception had + independent values, and an evaluation repeated later gave a different + one. With ScalarMediumAnalogModel, or with the dimensional model + attenuating with the center frequency (the default), nothing changes. + INET-4.7 (July 2026) — feature release -------------------------------------- diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc index 6c0c5ce3245..df328800ad0 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.cc @@ -83,13 +83,16 @@ std::ostream& LogNormalShadowing::ReceptionPathLoss::printToStream(std::ostream& Ptr LogNormalShadowing::computeReceptionPathLoss(const IRadio *receiverRadio, const ITransmission *transmission, const IArrival *arrival) const { + const Coord& transmitterPosition = transmission->getStartPosition(); + const Coord& receiverPosition = arrival->getStartPosition(); + mps propagationSpeed = transmission->getMedium()->getPropagation()->getPropagationSpeed(); + m distance = m(receiverPosition.distance(transmitterPosition)); + // without a correlation distance: one new value per reception, however often it is evaluated if (std::isnan(correlationDistance.get())) - return PathLossBase::computeReceptionPathLoss(receiverRadio, transmission, arrival); + return makeShared(this, propagationSpeed, distance, normal(0.0, sigma)); // shadowing is reciprocal: one value per pair of radios, the smaller id first int transmitterId = transmission->getTransmitterRadioId(); int receiverId = receiverRadio->getId(); - const Coord& transmitterPosition = transmission->getStartPosition(); - const Coord& receiverPosition = arrival->getStartPosition(); bool transmitterFirst = transmitterId < receiverId; auto key = transmitterFirst ? std::make_pair(transmitterId, receiverId) : std::make_pair(receiverId, transmitterId); const Coord& position1 = transmitterFirst ? transmitterPosition : receiverPosition; @@ -99,8 +102,6 @@ Ptr LogNormalShadowing::computeReceptionPathLoss(const || m(it->second.position1.distance(position1)) > correlationDistance || m(it->second.position2.distance(position2)) > correlationDistance) it = linkShadowings.insert_or_assign(key, LinkShadowing{position1, position2, normal(0.0, sigma)}).first; - mps propagationSpeed = transmission->getMedium()->getPropagation()->getPropagationSpeed(); - m distance = m(receiverPosition.distance(transmitterPosition)); return makeShared(this, propagationSpeed, distance, it->second.shadowing); } diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h index d7e348800d8..f4a91d0c5ad 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.h @@ -26,10 +26,11 @@ namespace physicallayer { /** * This class implements the log normal shadowing model. * - * By default every path loss computation draws a new shadowing value. With a - * correlation distance, the value is kept per pair of radios, the same in - * both directions, and drawn again only when either radio has moved farther - * than that distance from where it was when the value was drawn. The values + * By default every reception draws a new shadowing value, which holds for + * its whole band. With a correlation distance, the value is kept per pair of + * radios, the same in both directions, and drawn again only when either radio + * has moved farther than that distance from where it was when the value was + * drawn. The values * of a radio's links are dropped when the radio is removed from its medium. */ class INET_API LogNormalShadowing : public FreeSpacePathLoss, public cListener diff --git a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned index 2ac91e8036f..569580e469c 100644 --- a/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned +++ b/src/inet/physicallayer/wireless/common/pathloss/LogNormalShadowing.ned @@ -19,7 +19,7 @@ module LogNormalShadowing extends FreeSpacePathLoss { parameters: double sigma = default(1); - double correlationDistance @unit(m) = default(nan m); // NaN: a new shadowing value on every computation; otherwise the value is kept per pair of radios, the same in both directions, until either radio has moved farther than this distance from where it was when the value was drawn + double correlationDistance @unit(m) = default(nan m); // NaN: a new shadowing value for every reception; otherwise the value is kept per pair of radios, the same in both directions, until either radio has moved farther than this distance from where it was when the value was drawn @class(LogNormalShadowing); }