A Discrete-Event Network Simulator
API
multi-model-spectrum-channel.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2009 CTTC
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation;
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * Author: Nicola Baldo <nbaldo@cttc.es>
18  */
19 
21 
22 #include <ns3/angles.h>
23 #include <ns3/antenna-model.h>
24 #include <ns3/double.h>
25 #include <ns3/log.h>
26 #include <ns3/mobility-model.h>
27 #include <ns3/net-device.h>
28 #include <ns3/node.h>
29 #include <ns3/object.h>
30 #include <ns3/packet-burst.h>
31 #include <ns3/packet.h>
32 #include <ns3/propagation-delay-model.h>
33 #include <ns3/propagation-loss-model.h>
34 #include <ns3/simulator.h>
35 #include <ns3/spectrum-converter.h>
36 #include <ns3/spectrum-phy.h>
37 #include <ns3/spectrum-propagation-loss-model.h>
38 
39 #include <algorithm>
40 #include <iostream>
41 #include <utility>
42 
43 namespace ns3
44 {
45 
46 NS_LOG_COMPONENT_DEFINE("MultiModelSpectrumChannel");
47 
48 NS_OBJECT_ENSURE_REGISTERED(MultiModelSpectrumChannel);
49 
56 std::ostream&
57 operator<<(std::ostream& lhs, TxSpectrumModelInfoMap_t& rhs)
58 {
59  for (TxSpectrumModelInfoMap_t::iterator it = rhs.begin(); it != rhs.end(); ++it)
60  {
61  SpectrumConverterMap_t::iterator jt;
62  for (jt = it->second.m_spectrumConverterMap.begin();
63  jt != it->second.m_spectrumConverterMap.end();
64  ++jt)
65  {
66  lhs << "(" << it->first << "," << jt->first << ") ";
67  }
68  }
69  return lhs;
70 }
71 
73  : m_txSpectrumModel(txSpectrumModel)
74 {
75 }
76 
78  : m_rxSpectrumModel(rxSpectrumModel)
79 {
80 }
81 
83  : m_numDevices{0}
84 {
85  NS_LOG_FUNCTION(this);
86 }
87 
88 void
90 {
91  NS_LOG_FUNCTION(this);
95 }
96 
97 TypeId
99 {
100  static TypeId tid = TypeId("ns3::MultiModelSpectrumChannel")
102  .SetGroupName("Spectrum")
103  .AddConstructor<MultiModelSpectrumChannel>()
104 
105  ;
106  return tid;
107 }
108 
109 void
111 {
112  NS_LOG_FUNCTION(this << phy);
113 
114  // remove a previous entry of this phy if it exists
115  // we need to scan for all rxSpectrumModel values since we don't
116  // know which spectrum model the phy had when it was previously added
117  // (it's probably different than the current one)
118  for (RxSpectrumModelInfoMap_t::iterator rxInfoIterator = m_rxSpectrumModelInfoMap.begin();
119  rxInfoIterator != m_rxSpectrumModelInfoMap.end();
120  ++rxInfoIterator)
121  {
122  auto phyIt = std::find(rxInfoIterator->second.m_rxPhys.begin(),
123  rxInfoIterator->second.m_rxPhys.end(),
124  phy);
125  if (phyIt != rxInfoIterator->second.m_rxPhys.end())
126  {
127  rxInfoIterator->second.m_rxPhys.erase(phyIt);
128  --m_numDevices;
129  break; // there should be at most one entry
130  }
131  }
132 }
133 
134 void
136 {
137  NS_LOG_FUNCTION(this << phy);
138 
139  Ptr<const SpectrumModel> rxSpectrumModel = phy->GetRxSpectrumModel();
140 
141  NS_ASSERT_MSG(rxSpectrumModel,
142  "phy->GetRxSpectrumModel () returned 0. Please check that the RxSpectrumModel is "
143  "already set for the phy before calling MultiModelSpectrumChannel::AddRx (phy)");
144 
145  SpectrumModelUid_t rxSpectrumModelUid = rxSpectrumModel->GetUid();
146 
147  RemoveRx(phy);
148 
149  ++m_numDevices;
150 
151  auto [rxInfoIterator, inserted] =
152  m_rxSpectrumModelInfoMap.emplace(rxSpectrumModelUid, RxSpectrumModelInfo(rxSpectrumModel));
153 
154  // rxInfoIterator points either to the newly inserted element or to the element that
155  // prevented insertion. In both cases, add the phy to the element pointed to by rxInfoIterator
156  rxInfoIterator->second.m_rxPhys.push_back(phy);
157 
158  if (inserted)
159  {
160  // create the necessary converters for all the TX spectrum models that we know of
161  for (TxSpectrumModelInfoMap_t::iterator txInfoIterator = m_txSpectrumModelInfoMap.begin();
162  txInfoIterator != m_txSpectrumModelInfoMap.end();
163  ++txInfoIterator)
164  {
165  Ptr<const SpectrumModel> txSpectrumModel = txInfoIterator->second.m_txSpectrumModel;
166  SpectrumModelUid_t txSpectrumModelUid = txSpectrumModel->GetUid();
167 
168  if (rxSpectrumModelUid != txSpectrumModelUid &&
169  !txSpectrumModel->IsOrthogonal(*rxSpectrumModel))
170  {
171  NS_LOG_LOGIC("Creating converter between SpectrumModelUid "
172  << txSpectrumModel->GetUid() << " and " << rxSpectrumModelUid);
173  SpectrumConverter converter(txSpectrumModel, rxSpectrumModel);
174  std::pair<SpectrumConverterMap_t::iterator, bool> ret2;
175  ret2 = txInfoIterator->second.m_spectrumConverterMap.insert(
176  std::make_pair(rxSpectrumModelUid, converter));
177  NS_ASSERT(ret2.second);
178  }
179  }
180  }
181 }
182 
183 TxSpectrumModelInfoMap_t::const_iterator
185  Ptr<const SpectrumModel> txSpectrumModel)
186 {
187  NS_LOG_FUNCTION(this << txSpectrumModel);
188  SpectrumModelUid_t txSpectrumModelUid = txSpectrumModel->GetUid();
189  TxSpectrumModelInfoMap_t::iterator txInfoIterator =
190  m_txSpectrumModelInfoMap.find(txSpectrumModelUid);
191 
192  if (txInfoIterator == m_txSpectrumModelInfoMap.end())
193  {
194  // first time we see this TX SpectrumModel
195  // we add it to the list
196  std::pair<TxSpectrumModelInfoMap_t::iterator, bool> ret;
197  ret = m_txSpectrumModelInfoMap.insert(
198  std::make_pair(txSpectrumModelUid, TxSpectrumModelInfo(txSpectrumModel)));
199  NS_ASSERT(ret.second);
200  txInfoIterator = ret.first;
201 
202  // and we create the converters for all the RX SpectrumModels that we know of
203  for (RxSpectrumModelInfoMap_t::const_iterator rxInfoIterator =
204  m_rxSpectrumModelInfoMap.begin();
205  rxInfoIterator != m_rxSpectrumModelInfoMap.end();
206  ++rxInfoIterator)
207  {
208  Ptr<const SpectrumModel> rxSpectrumModel = rxInfoIterator->second.m_rxSpectrumModel;
209  SpectrumModelUid_t rxSpectrumModelUid = rxSpectrumModel->GetUid();
210 
211  if (rxSpectrumModelUid != txSpectrumModelUid &&
212  !txSpectrumModel->IsOrthogonal(*rxSpectrumModel))
213  {
214  NS_LOG_LOGIC("Creating converter between SpectrumModelUid "
215  << txSpectrumModelUid << " and " << rxSpectrumModelUid);
216 
217  SpectrumConverter converter(txSpectrumModel, rxSpectrumModel);
218  std::pair<SpectrumConverterMap_t::iterator, bool> ret2;
219  ret2 = txInfoIterator->second.m_spectrumConverterMap.insert(
220  std::make_pair(rxSpectrumModelUid, converter));
221  NS_ASSERT(ret2.second);
222  }
223  }
224  }
225  else
226  {
227  NS_LOG_LOGIC("SpectrumModelUid " << txSpectrumModelUid << " already present");
228  }
229  return txInfoIterator;
230 }
231 
232 void
234 {
235  NS_LOG_FUNCTION(this << txParams);
236 
237  NS_ASSERT(txParams->txPhy);
238  NS_ASSERT(txParams->psd);
239  Ptr<SpectrumSignalParameters> txParamsTrace =
240  txParams->Copy(); // copy it since traced value cannot be const (because of potential
241  // underlying DynamicCasts)
242  m_txSigParamsTrace(txParamsTrace);
243 
244  Ptr<MobilityModel> txMobility = txParams->txPhy->GetMobility();
245  SpectrumModelUid_t txSpectrumModelUid = txParams->psd->GetSpectrumModelUid();
246  NS_LOG_LOGIC("txSpectrumModelUid " << txSpectrumModelUid);
247 
248  //
249  TxSpectrumModelInfoMap_t::const_iterator txInfoIteratorerator =
251  NS_ASSERT(txInfoIteratorerator != m_txSpectrumModelInfoMap.end());
252 
253  NS_LOG_LOGIC("converter map for TX SpectrumModel with Uid " << txInfoIteratorerator->first);
254  NS_LOG_LOGIC(
255  "converter map size: " << txInfoIteratorerator->second.m_spectrumConverterMap.size());
256  NS_LOG_LOGIC("converter map first element: "
257  << txInfoIteratorerator->second.m_spectrumConverterMap.begin()->first);
258 
259  for (RxSpectrumModelInfoMap_t::const_iterator rxInfoIterator = m_rxSpectrumModelInfoMap.begin();
260  rxInfoIterator != m_rxSpectrumModelInfoMap.end();
261  ++rxInfoIterator)
262  {
263  SpectrumModelUid_t rxSpectrumModelUid = rxInfoIterator->second.m_rxSpectrumModel->GetUid();
264  NS_LOG_LOGIC("rxSpectrumModelUids " << rxSpectrumModelUid);
265 
266  Ptr<SpectrumValue> convertedTxPowerSpectrum;
267  if (txSpectrumModelUid == rxSpectrumModelUid)
268  {
269  NS_LOG_LOGIC("no spectrum conversion needed");
270  convertedTxPowerSpectrum = txParams->psd;
271  }
272  else
273  {
274  NS_LOG_LOGIC("converting txPowerSpectrum SpectrumModelUids "
275  << txSpectrumModelUid << " --> " << rxSpectrumModelUid);
276  SpectrumConverterMap_t::const_iterator rxConverterIterator =
277  txInfoIteratorerator->second.m_spectrumConverterMap.find(rxSpectrumModelUid);
278  if (rxConverterIterator == txInfoIteratorerator->second.m_spectrumConverterMap.end())
279  {
280  // No converter means TX SpectrumModel is orthogonal to RX SpectrumModel
281  continue;
282  }
283  convertedTxPowerSpectrum = rxConverterIterator->second.Convert(txParams->psd);
284  }
285 
286  for (auto rxPhyIterator = rxInfoIterator->second.m_rxPhys.begin();
287  rxPhyIterator != rxInfoIterator->second.m_rxPhys.end();
288  ++rxPhyIterator)
289  {
290  NS_ASSERT_MSG((*rxPhyIterator)->GetRxSpectrumModel()->GetUid() == rxSpectrumModelUid,
291  "SpectrumModel change was not notified to MultiModelSpectrumChannel "
292  "(i.e., AddRx should be called again after model is changed)");
293 
294  if ((*rxPhyIterator) != txParams->txPhy)
295  {
296  Ptr<NetDevice> rxNetDevice = (*rxPhyIterator)->GetDevice();
297  Ptr<NetDevice> txNetDevice = txParams->txPhy->GetDevice();
298 
299  if (rxNetDevice && txNetDevice)
300  {
301  // we assume that devices are attached to a node
302  if (rxNetDevice->GetNode()->GetId() == txNetDevice->GetNode()->GetId())
303  {
304  NS_LOG_DEBUG(
305  "Skipping the pathloss calculation among different antennas of the "
306  "same node, not supported yet by any pathloss model in ns-3.");
307  continue;
308  }
309  }
310 
311  NS_LOG_LOGIC("copying signal parameters " << txParams);
312  Ptr<SpectrumSignalParameters> rxParams = txParams->Copy();
313  rxParams->psd = Copy<SpectrumValue>(convertedTxPowerSpectrum);
314  Time delay = MicroSeconds(0);
315 
316  Ptr<MobilityModel> receiverMobility = (*rxPhyIterator)->GetMobility();
317 
318  if (txMobility && receiverMobility)
319  {
320  double txAntennaGain = 0;
321  double rxAntennaGain = 0;
322  double propagationGainDb = 0;
323  double pathLossDb = 0;
324  if (rxParams->txAntenna)
325  {
326  Angles txAngles(receiverMobility->GetPosition(), txMobility->GetPosition());
327  txAntennaGain = rxParams->txAntenna->GetGainDb(txAngles);
328  NS_LOG_LOGIC("txAntennaGain = " << txAntennaGain << " dB");
329  pathLossDb -= txAntennaGain;
330  }
331  Ptr<AntennaModel> rxAntenna =
332  DynamicCast<AntennaModel>((*rxPhyIterator)->GetAntenna());
333  if (rxAntenna)
334  {
335  Angles rxAngles(txMobility->GetPosition(), receiverMobility->GetPosition());
336  rxAntennaGain = rxAntenna->GetGainDb(rxAngles);
337  NS_LOG_LOGIC("rxAntennaGain = " << rxAntennaGain << " dB");
338  pathLossDb -= rxAntennaGain;
339  }
340  if (m_propagationLoss)
341  {
342  propagationGainDb =
343  m_propagationLoss->CalcRxPower(0, txMobility, receiverMobility);
344  NS_LOG_LOGIC("propagationGainDb = " << propagationGainDb << " dB");
345  pathLossDb -= propagationGainDb;
346  }
347  NS_LOG_LOGIC("total pathLoss = " << pathLossDb << " dB");
348  // Gain trace
349  m_gainTrace(txMobility,
350  receiverMobility,
351  txAntennaGain,
352  rxAntennaGain,
353  propagationGainDb,
354  pathLossDb);
355  // Pathloss trace
356  m_pathLossTrace(txParams->txPhy, *rxPhyIterator, pathLossDb);
357  if (pathLossDb > m_maxLossDb)
358  {
359  // beyond range
360  continue;
361  }
362  double pathGainLinear = std::pow(10.0, (-pathLossDb) / 10.0);
363  *(rxParams->psd) *= pathGainLinear;
364 
365  if (m_propagationDelay)
366  {
367  delay = m_propagationDelay->GetDelay(txMobility, receiverMobility);
368  }
369  }
370 
371  if (rxNetDevice)
372  {
373  // the receiver has a NetDevice, so we expect that it is attached to a Node
374  uint32_t dstNode = rxNetDevice->GetNode()->GetId();
376  delay,
378  this,
379  rxParams,
380  *rxPhyIterator);
381  }
382  else
383  {
384  // the receiver is not attached to a NetDevice, so we cannot assume that it is
385  // attached to a node
386  Simulator::Schedule(delay,
388  this,
389  rxParams,
390  *rxPhyIterator);
391  }
392  }
393  }
394  }
395 }
396 
397 void
399 {
400  NS_LOG_FUNCTION(this);
402  {
403  params->psd =
404  m_spectrumPropagationLoss->CalcRxPowerSpectralDensity(params,
405  params->txPhy->GetMobility(),
406  receiver->GetMobility());
407  }
409  {
410  Ptr<const PhasedArrayModel> txPhasedArrayModel =
411  DynamicCast<PhasedArrayModel>(params->txPhy->GetAntenna());
412  Ptr<const PhasedArrayModel> rxPhasedArrayModel =
413  DynamicCast<PhasedArrayModel>(receiver->GetAntenna());
414 
415  NS_ASSERT_MSG(txPhasedArrayModel && rxPhasedArrayModel,
416  "PhasedArrayModel instances should be installed at both TX and RX "
417  "SpectrumPhy in order to use PhasedArraySpectrumPropagationLoss.");
418 
419  params->psd = m_phasedArraySpectrumPropagationLoss->CalcRxPowerSpectralDensity(
420  params,
421  params->txPhy->GetMobility(),
422  receiver->GetMobility(),
423  txPhasedArrayModel,
424  rxPhasedArrayModel);
425  }
426  receiver->StartRx(params);
427 }
428 
429 std::size_t
431 {
432  return m_numDevices;
433 }
434 
437 {
438  NS_ASSERT(i < m_numDevices);
439  // this method implementation is computationally intensive. This
440  // method would be faster if we actually used a std::vector for
441  // storing devices, which we don't due to the need to have fast
442  // SpectrumModel conversions and to allow PHY devices to change a
443  // SpectrumModel at run time. Note that having this method slow is
444  // acceptable as it is not used much at run time (often not at all).
445  // On the other hand, having slow SpectrumModel conversion would be
446  // less acceptable.
447  std::size_t j = 0;
448  for (RxSpectrumModelInfoMap_t::const_iterator rxInfoIterator = m_rxSpectrumModelInfoMap.begin();
449  rxInfoIterator != m_rxSpectrumModelInfoMap.end();
450  ++rxInfoIterator)
451  {
452  for (const auto& phyIt : rxInfoIterator->second.m_rxPhys)
453  {
454  if (j == i)
455  {
456  return (*phyIt).GetDevice();
457  }
458  j++;
459  }
460  }
461  NS_FATAL_ERROR("m_numDevices > actual number of devices");
462  return nullptr;
463 }
464 
465 } // namespace ns3
Class holding the azimuth and inclination angles of spherical coordinates.
Definition: angles.h:118
virtual double GetGainDb(Angles a)=0
this method is expected to be re-implemented by each antenna model
Vector GetPosition() const
This SpectrumChannel implementation can handle the presence of SpectrumPhy instances which can use di...
void RemoveRx(Ptr< SpectrumPhy > phy) override
Remove a SpectrumPhy from a channel.
Ptr< NetDevice > GetDevice(std::size_t i) const override
std::size_t GetNDevices() const override
void AddRx(Ptr< SpectrumPhy > phy) override
Add a SpectrumPhy to a channel, so it can receive packets.
TxSpectrumModelInfoMap_t m_txSpectrumModelInfoMap
Data structure holding, for each TX SpectrumModel, all the converters to any RX SpectrumModel,...
std::size_t m_numDevices
Number of devices connected to the channel.
void DoDispose() override
Destructor implementation.
void StartTx(Ptr< SpectrumSignalParameters > params) override
Used by attached PHY instances to transmit signals on the channel.
TxSpectrumModelInfoMap_t::const_iterator FindAndEventuallyAddTxSpectrumModel(Ptr< const SpectrumModel > txSpectrumModel)
This method checks if m_rxSpectrumModelInfoMap contains an entry for the given TX SpectrumModel.
static TypeId GetTypeId()
Get the type ID.
virtual void StartRx(Ptr< SpectrumSignalParameters > params, Ptr< SpectrumPhy > receiver)
Used internally to reschedule transmission after the propagation delay.
RxSpectrumModelInfoMap_t m_rxSpectrumModelInfoMap
Data structure holding, for each RX spectrum model, all the corresponding SpectrumPhy instances.
double CalcRxPower(double txPowerDbm, Ptr< MobilityModel > a, Ptr< MobilityModel > b) const
Returns the Rx Power taking into account all the PropagationLossModel(s) chained to the current one.
The Rx spectrum model information.
RxSpectrumModelInfo(Ptr< const SpectrumModel > rxSpectrumModel)
Constructor.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition: simulator.h:568
static void ScheduleWithContext(uint32_t context, const Time &delay, FUNC f, Ts &&... args)
Schedule an event with the given context.
Definition: simulator.h:587
Defines the interface for spectrum-aware channel implementations.
TracedCallback< Ptr< SpectrumSignalParameters > > m_txSigParamsTrace
Traced callback for SpectrumSignalParameters in StartTx requests.
void DoDispose() override
Destructor implementation.
Ptr< PropagationDelayModel > m_propagationDelay
Propagation delay model to be used with this channel.
Ptr< SpectrumPropagationLossModel > m_spectrumPropagationLoss
Frequency-dependent propagation loss model to be used with this channel.
Ptr< PhasedArraySpectrumPropagationLossModel > m_phasedArraySpectrumPropagationLoss
Frequency-dependent propagation loss model to be used with this channel.
TracedCallback< Ptr< const SpectrumPhy >, Ptr< const SpectrumPhy >, double > m_pathLossTrace
The PathLoss trace source.
TracedCallback< Ptr< const MobilityModel >, Ptr< const MobilityModel >, double, double, double, double > m_gainTrace
The Gain trace source.
Ptr< PropagationLossModel > m_propagationLoss
Single-frequency propagation loss model to be used with this channel.
double m_maxLossDb
Maximum loss [dB].
Class which implements a converter between SpectrumValue which are defined over different SpectrumMod...
bool IsOrthogonal(const SpectrumModel &other) const
Check if another SpectrumModels has bands orthogonal to our bands.
SpectrumModelUid_t GetUid() const
virtual void StartRx(Ptr< SpectrumSignalParameters > params)=0
Notify the SpectrumPhy instance of an incoming signal.
virtual Ptr< Object > GetAntenna() const =0
Get the AntennaModel used by this SpectrumPhy instance for transmission and/or reception.
virtual Ptr< MobilityModel > GetMobility() const =0
Get the associated MobilityModel instance.
virtual Ptr< NetDevice > GetDevice() const =0
Get the associated NetDevice instance.
Ptr< const SpectrumModel > GetSpectrumModel() const
SpectrumModelUid_t GetSpectrumModelUid() const
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
The Tx spectrum model information.
TxSpectrumModelInfo(Ptr< const SpectrumModel > txSpectrumModel)
Constructor.
a unique identifier for an interface.
Definition: type-id.h:60
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition: type-id.cc:935
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
#define NS_FATAL_ERROR(msg)
Report a fatal error with a message and terminate.
Definition: fatal-error.h:179
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
#define NS_LOG_LOGIC(msg)
Use NS_LOG to output a message of level LOG_LOGIC.
Definition: log.h:282
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition: object-base.h:46
std::map< SpectrumModelUid_t, TxSpectrumModelInfo > TxSpectrumModelInfoMap_t
Container: SpectrumModelUid_t, TxSpectrumModelInfo.
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1360
Every class exported by the ns3 library is enclosed in the ns3 namespace.
uint32_t SpectrumModelUid_t
Uid for SpectrumModels.
std::ostream & operator<<(std::ostream &os, const Angles &a)
Definition: angles.cc:129
phy
Definition: third.py:82
params
Fit Fluctuating Two Ray model to the 3GPP TR 38.901 using the Anderson-Darling goodness-of-fit ##.
Ptr< AntennaModel > txAntenna
The AntennaModel instance that was used to transmit this signal.
Ptr< SpectrumPhy > txPhy
The SpectrumPhy instance that is making the transmission.
virtual Ptr< SpectrumSignalParameters > Copy() const
make a "virtual" copy of this class, where "virtual" refers to the fact that if the actual object is ...
Ptr< SpectrumValue > psd
The Power Spectral Density of the waveform, in linear units.