Metrics#
- compute_papr(x_data: object, unit: str = 'natural', axis: int | None = None, reduction: str = 'none') ndarray | float[source]#
Compute the Peak-to-Average Power Ratio (PAPR) of the input signal.
Signal Model#
With the peak amplitude \(x_{max} = \max_n |x[n]|\) and the mean power \(P_x = \mathbb{E}\left[|x[n]|^2\right]\) of the input signal \(x[n]\), the returned metric is:
for natural units (amplitude ratio):
\[\mathrm{PAPR} = \frac{x_{max}}{\sqrt{P_x}}\]for dB units (power ratio):
\[\mathrm{PAPR_{dB}} = 10 \log_{10} \left( \frac{x_{max}^2}{P_x} \right)\]Axes: declared axis – the peak \(x_{max}\) and the mean power \(P_x\) are reduced along
axis(defaultNone: over the whole array).axisnames the axis one waveform lies along, so an array of OFDM symbols(T, F)givesTvalues withaxis=-1;reductionthen says what to do with those, which saves the caller a second call to reduce them by hand.- param x_data:
Input signal \(x[n]\) for which the PAPR needs to be calculated.
- type x_data:
np.ndarray
- param unit:
The unit for PAPR calculation. It can be either “natural” for natural units or “dB” for logarithmic units. Default is “natural”.
- type unit:
{“natural”, “dB”}, optional
- param axis:
The axis one waveform lies along. If None, the PAPR is computed over the entire array. Default is None.
- type axis:
int or None, optional
- param reduction:
What to do with the several PAPR values an array of waveforms produces.
"none"(default) returns them all; the others reduce them to a scalar, in the unit asked for – the mean of a set of decibel values is not the decibel of their mean, and this returns the first.- type reduction:
{“none”, “mean”, “max”, “min”}, optional
- returns:
float or np.ndarray – The computed PAPR value(s). With
axisgiven andreduction="none", one value per waveform.- raises TypeError:
If x_data is not a NumPy array.
- raises ValueError:
If reduction is not one of the values above.
- raises NotImplementedError:
If the specified unit is not supported.
References
R. van Nee, R. Prasad, OFDM for Wireless Multimedia Communications, Artech House, 2000, Chapter 6.
Examples
>>> import numpy as np >>> data = np.array([1, 2, 3, 4]) >>> print(round(float(compute_papr(data, unit="natural")), 4)) 1.4606 >>> print(round(float(compute_papr(data, unit="dB")), 4)) 3.2906 >>> data_2d = np.array([[1, 2], [3, 4]]) >>> print(np.round(compute_papr(data_2d, unit="natural", axis=0), 4)) [1.3416 1.2649]
One value per OFDM symbol, or their average, without reshaping anything:
>>> blocks = np.array([[1, 2, 3, 4], [1, 1, 1, 4]]) >>> print(np.round(compute_papr(blocks, unit="dB", axis=-1), 3)) [3.291 5.274] >>> print(round(float(compute_papr(blocks, unit="dB", axis=-1, ... reduction="mean")), 3)) 4.282
- compute_papr_ccdf_theo(threshold: ndarray | float, n_sub: int, oversampling: int = 1, unit: str = 'natural', method: str = 'effective') ndarray | float[source]#
Probability that the PAPR of one OFDM symbol exceeds a threshold.
Signal Model#
After the IDFT each time sample is a sum of \(N_{sub}\) independent terms, so for \(N_{sub}\) large the central limit theorem makes it a circular complex Gaussian and its normalized power \(\gamma\) exponential. If the \(N\) samples of a symbol were independent, the peak would exceed \(\gamma\) unless all of them stayed below it:
\[\mathrm{CCDF}(\gamma) = \Pr\left\{\mathrm{PAPR} > \gamma\right\} = 1 - \left(1 - e^{-\gamma}\right)^{\alpha N_{sub}}\]Oversampled samples are not independent – the waveform is band limited, so neighbouring samples are correlated – and there are two usual ways to account for it.
method="effective"keeps the expression and replaces the count by an effective one \(\alpha N_{sub}\), with \(\alpha = 1\) at the Nyquist rate and \(\alpha = 2.8\) for an oversampled waveform, the value van Nee and Prasad report for an oversampling of 4 or more.method="level_crossing"replaces the fitted constant by a term derived from how often a Gaussian process crosses a level, which makes the effective count grow with the threshold:\[\mathrm{CCDF}(\gamma) \simeq 1 - \exp\left(-N_{sub} \sqrt{\frac{\pi}{3}}\, \sqrt{\gamma}\, e^{-\gamma}\right)\]That form has no fitted constant, but it comes with two conditions. It describes the peak of the continuous-time waveform, which any finite oversampling underestimates, so it reads high on sampled data and approaches the measurement as the oversampling grows. And it is a large-threshold approximation: near the top of the curve, where the probability approaches one, it is not even ordered against the other model.
oversamplingis not used by it.Axes: element-wise – the result has the shape of
threshold.- param threshold:
PAPR threshold \(\gamma\), in the unit given by
unit.- type threshold:
np.ndarray or float
- param n_sub:
Number \(N_{sub}\) of modulated subcarriers.
- type n_sub:
int
- param oversampling:
Oversampling factor of the waveform the PAPR is measured on. Default 1, i.e. Nyquist-rate sampling.
- type oversampling:
int, optional
- param unit:
Unit of
threshold, with the convention ofcompute_papr():"natural"is the amplitude ratio,"dB"the power ratio in decibels. Default"natural".- type unit:
{“natural”, “dB”}, optional
- param method:
Which of the two models above. Default
"effective", the one that matches a sampled waveform;"level_crossing"is the continuous-time approximation and ignoresoversampling.- type method:
{“effective”, “level_crossing”}, optional
- returns:
np.ndarray or float – The probability of exceeding each threshold.
- raises ValueError:
If
n_suboroversamplingis not positive, or ifunitormethodis not one of the values above.
References
R. van Nee, R. Prasad, OFDM for Wireless Multimedia Communications, Artech House, 2000, Chapter 6 (the \(\alpha = 2.8\) fit); H. Ochiai and H. Imai, “On the distribution of the peak-to-average power ratio in OFDM signals”, IEEE Trans. Commun. 49(2), 2001 (the level-crossing form); S. H. Han and J. H. Lee, “An overview of peak-to-average power ratio reduction techniques for multicarrier transmission”, IEEE Wireless Communications 12(2), 2005.
Examples
>>> import numpy as np >>> print(round(float(compute_papr_ccdf_theo(8.0, 256, unit="dB")), 4)) 0.3725 >>> print(np.round(compute_papr_ccdf_theo( ... np.array([8.0, 11.0]), 256, oversampling=4, unit="dB"), 4)) [0.7288 0.0024]
The continuous-time form reads higher, as it should:
>>> print(np.round(compute_papr_ccdf_theo( ... 11.0, 256, unit="dB", method="level_crossing"), 4)) 0.0032