Distributions and Sampling
Why Statistics Is Central to Wireless Simulation
Every wireless system is inherently stochastic: noise is random, fading is random, user locations are random, and data bits are random. To simulate these systems faithfully, you need to draw samples from the correct distributions, verify your random number generators produce the right statistics, and generate correlated random vectors to model realistic channel conditions.
This section shows you how scipy.stats gives you a unified interface
to over 100 distributions, how to fit distributions to data, and how
to generate correlated Gaussian vectors via the Cholesky decomposition.
Definition: Probability Density Function (PDF)
Probability Density Function (PDF)
A continuous random variable has probability density function satisfying:
In scipy.stats, every distribution object provides pdf(x),
cdf(x), rvs(size), fit(data), and ppf(q) (quantile function).
from scipy.stats import norm
X = norm(loc=0, scale=1) # standard normal
X.pdf(0.0) # f(0) = 0.3989...
X.cdf(1.96) # P(X <= 1.96) = 0.975
samples = X.rvs(size=10000) # draw 10000 samples
The loc and scale parameters shift and scale any distribution.
For norm, loc is the mean and scale is the standard deviation.
Definition: CDF and Quantile (Percent-Point) Function
CDF and Quantile (Percent-Point) Function
The cumulative distribution function (CDF) is:
Its inverse is the quantile function (ppf in SciPy): for , returns the value such that .
from scipy.stats import norm
norm.ppf(0.975) # 1.96 β the 97.5th percentile
norm.isf(0.025) # 1.96 β inverse survival function
The quantile function is essential for computing confidence intervals and critical values in hypothesis testing.
Definition: Gaussian (Normal) Distribution
Gaussian (Normal) Distribution
The Gaussian distribution has PDF:
The -function gives the tail probability and appears throughout BER analysis.
from scipy.stats import norm
from scipy.special import erfc
Q = lambda x: 0.5 * erfc(x / np.sqrt(2))
# Equivalent:
Q_scipy = lambda x: norm.sf(x) # survival function
For BPSK over AWGN, the BER is exactly . This is the single most important formula in digital communications.
Definition: Circularly Symmetric Complex Gaussian
Circularly Symmetric Complex Gaussian
A circularly symmetric complex Gaussian (CSCG) random variable is defined as:
The envelope follows a Rayleigh distribution and the power follows an exponential distribution with mean .
rng = np.random.default_rng(42)
sigma2 = 1.0
z = rng.standard_normal(10000) * np.sqrt(sigma2/2) \
+ 1j * rng.standard_normal(10000) * np.sqrt(sigma2/2)
# |z| is Rayleigh, |z|^2 is exponential
Definition: Multivariate Gaussian and Covariance Matrix
Multivariate Gaussian and Covariance Matrix
A random vector has joint PDF:
where is the covariance matrix (positive semi-definite).
from scipy.stats import multivariate_normal
R = np.array([[1.0, 0.8], [0.8, 1.0]])
rv = multivariate_normal(mean=[0, 0], cov=R)
samples = rv.rvs(size=5000)
Theorem: Linear Transformation of Gaussian Vectors
If and , then:
In particular, if and , then .
A linear transformation of a Gaussian is still Gaussian. The mean transforms linearly and the covariance picks up on both sides (the "sandwich" formula).
Mean
.
Covariance
.
Theorem: Maximum Likelihood Estimation for Gaussian Parameters
Given i.i.d. samples from , the maximum likelihood estimators are:
The MLE for is biased; the unbiased estimator divides by
(Bessel's correction), which is what np.var(x, ddof=1) computes.
The log-likelihood is a concave quadratic in and . Setting the gradient to zero gives the sample mean and sample variance.
Log-likelihood
.
Optimize over $\mu$
.
Theorem: Goodness of Fit via KS Statistic
The Kolmogorov-Smirnov statistic between the empirical CDF and a reference CDF is:
Under , where has the
Kolmogorov distribution. scipy.stats.kstest returns .
The KS statistic measures the worst-case vertical gap between the empirical and theoretical CDFs. A small (large -value) means the data is consistent with .
Example: Exploring Distributions with scipy.stats
Use scipy.stats to create Rayleigh, Ricean, and Nakagami distributions,
compute their PDFs, and verify that rvs() samples match the theoretical PDF.
Create distribution objects
from scipy.stats import rayleigh, rice, nakagami
import numpy as np
sigma = 1.0
ray = rayleigh(scale=sigma) # Rayleigh(sigma)
K_factor = 3.0
nu = np.sqrt(K_factor / (1 + K_factor))
ric = rice(nu / sigma, scale=sigma) # Rice(K)
m = 2.0
nak = nakagami(m, scale=np.sqrt(2*m)) # Nakagami(m, Omega=2m)
Compare PDF to histogram
x = np.linspace(0, 5, 200)
samples = ray.rvs(size=100000)
# Histogram should match ray.pdf(x)
print(f"Mean: theoretical={ray.mean():.4f}, sample={samples.mean():.4f}")
print(f"Var: theoretical={ray.var():.4f}, sample={samples.var():.4f}")
Fit distribution to data
# Fit Rayleigh parameters from data
params = rayleigh.fit(samples)
print(f"Fitted loc={params[0]:.4f}, scale={params[1]:.4f}")
Example: Generating Correlated Fading Samples
Generate 10000 pairs of correlated Rayleigh fading coefficients with correlation using the Cholesky method, and verify the sample correlation matches the target.
Build covariance matrix and factor
import numpy as np
rho = 0.8
R = np.array([[1.0, rho], [rho, 1.0]])
L = np.linalg.cholesky(R)
Generate correlated complex Gaussian
rng = np.random.default_rng(42)
N = 10000
z = (rng.standard_normal((2, N))
+ 1j * rng.standard_normal((2, N))) / np.sqrt(2)
h = L @ z # correlated complex Gaussian
Verify
h_env = np.abs(h) # Rayleigh envelopes
rho_hat = np.corrcoef(h_env[0], h_env[1])[0, 1]
print(f"Target correlation: {rho:.2f}")
print(f"Sample envelope correlation: {rho_hat:.4f}")
# Note: envelope correlation != complex correlation
Example: Fitting a Distribution to Channel Measurement Data
Given simulated channel power data, fit Rayleigh, Rice, and Nakagami distributions and determine which best fits the data using the KS test.
Generate synthetic data (Rice distributed)
from scipy.stats import rice, rayleigh, nakagami, kstest
import numpy as np
rng = np.random.default_rng(42)
K = 5.0 # Ricean K-factor
nu = np.sqrt(K / (1 + K))
data = rice.rvs(nu, size=5000, random_state=42)
Fit each distribution
ray_params = rayleigh.fit(data)
ric_params = rice.fit(data)
nak_params = nakagami.fit(data)
KS test for goodness of fit
for name, dist, params in [
("Rayleigh", rayleigh, ray_params),
("Rice", rice, ric_params),
("Nakagami", nakagami, nak_params),
]:
D, p = kstest(data, dist.cdf, args=params)
print(f"{name:10s}: D={D:.4f}, p={p:.4f}")
# Rice should have the highest p-value (best fit)
Distribution Explorer
Compare PDF, CDF, and histogram of scipy.stats distributions. Adjust distribution parameters and sample size to see how well the empirical distribution matches the theoretical one.
Parameters
Correlated Gaussian Sampling via Cholesky
Visualize how the Cholesky decomposition transforms i.i.d. Gaussian samples into correlated pairs. Adjust the correlation coefficient and observe the scatter plot and marginal histograms.
Parameters
Central Limit Theorem in Action
Watch the distribution of the sample mean converge to a Gaussian as the number of averaged samples increases. Start with any distribution (uniform, exponential, Rayleigh) and see the CLT at work.
Parameters
Wireless Distribution Family Tree
Distributions and Sampling
# Code from: ch09/python/distributions.py
# Load from backend supplements endpointQuick Check
To generate via Cholesky, you compute and then set where . What happens if is not positive definite?
The samples will have the wrong mean
np.linalg.cholesky raises a LinAlgError
The samples will be uncorrelated
The resulting distribution will be Rayleigh instead of Gaussian
Cholesky decomposition requires a positive-definite matrix. If R is only PSD (has zero eigenvalues) or indefinite, it will fail.
Common Mistake: Using Global Seeds Instead of RNG Objects
Mistake:
Using np.random.seed(42) and then np.random.randn(...) for
reproducibility. This uses the legacy global state, which is not
thread-safe and causes subtle bugs in parallel simulations.
Correction:
Use the new np.random.default_rng(42) and call methods on the
returned Generator object. Each parallel worker should get its own
RNG with a different seed (or use SeedSequence.spawn()).
Common Mistake: Complex Gaussian Variance Convention
Mistake:
Writing z = rng.standard_normal(N) + 1j * rng.standard_normal(N)
to get . This produces ,
not .
Correction:
Divide by : z = (rng.standard_normal(N) + 1j * rng.standard_normal(N)) / np.sqrt(2).
Now as intended.
Why This Matters: From Distributions to Channel Models
The distributions in this section are not abstract mathematics β they are the building blocks of wireless channel models. A Rayleigh fading channel coefficient is literally ; a Ricean channel with -factor adds a deterministic LOS component. The Cholesky method for generating correlated samples is exactly how correlated MIMO channels are simulated in Section 9.4.
See full treatment in Generating Fading Channels
Historical Note: Gauss and the Normal Distribution
19th centuryCarl Friedrich Gauss derived the normal distribution in 1809 while studying the distribution of astronomical observation errors. The characteristic bell curve bears his name in many languages ("Gauss-Verteilung" in German). The Central Limit Theorem, which explains why the Gaussian is so ubiquitous, was proven rigorously by Lyapunov in 1901.
Historical Note: Lord Rayleigh and the Distribution of Wave Amplitudes
19th centuryJohn William Strutt, 3rd Baron Rayleigh, derived the Rayleigh distribution in 1880 while studying the intensity of sound waves resulting from many superimposed sources. The same distribution describes the envelope of a narrowband signal that is the sum of many scattered multipath components β the physical basis of Rayleigh fading in wireless communications.
Probability Density Function (PDF)
A function such that .
Cumulative Distribution Function (CDF)
, a non-decreasing function from 0 to 1.
Related: Probability Density Function (PDF)
Cholesky Decomposition
Factorization of a positive-definite matrix as where is lower triangular. Used to generate correlated random samples.
Related: Covariance Matrix
Covariance Matrix
, a positive semi-definite matrix encoding pairwise correlations.
Related: Cholesky Decomposition
Maximum Likelihood Estimation (MLE)
A method that finds parameter values maximizing the likelihood function .
Q-function
The tail probability of the standard normal: . Appears in BER expressions for many modulation schemes.