Implementation:Pyro ppl Pyro Stable
| Attribute | Value |
|---|---|
| Sources | pyro/distributions/stable.py |
| Domains | Probabilistic Programming, Heavy-Tailed Distributions, Financial Modeling, Levy Stable Distributions |
| Last Updated | 2026-02-09 |
Overview
Description
The Stable distribution implements the Levy alpha-stable family of distributions, a rich class of heavy-tailed distributions parameterized by four parameters: stability (alpha), skew (beta), scale (sigma), and location (mu). This family generalizes the Gaussian distribution (alpha=2) and the Cauchy distribution (alpha=1, beta=0), and is the only family of distributions that is closed under convolution (i.e., sums of i.i.d. stable random variables are also stable).
Pyro uses Nolan's S0 parameterization by default, which ensures continuity and differentiability of the distribution as a function of its parameters. This corresponds to the notation S^0_alpha(beta, sigma, mu_0). The alternative S parameterization (as used in scipy) is also available via coords="S", but it is discontinuous at stability=1 and has poor geometry for inference.
The implementation provides:
- Reparameterized sampling (
has_rsample = True) via the Chambers-Mallows-Stuck (CMS) method as corrected by Weron and simplified by Nolan - Log probability computation via numerical integration (relatively expensive)
- A hole-filling interpolation mechanism around alpha=1 to ensure numerical stability
The module also defines StableWithLogProb, a subclass that prevents automatic reparameterization by MinimalReparam, useful when the log_prob computation is explicitly desired.
For faster inference, Pyro provides several reparameterizers: LatentStableReparam, SymmetricStableReparam, and StableReparam, as well as likelihood-free methods such as EnergyDistance.
Usage
Stable distributions are widely used in financial modeling for modeling asset returns with heavy tails, in signal processing for impulsive noise, and in physics for modeling anomalous diffusion. In Pyro, they can be used directly or via reparameterizers for efficient inference.
Code Reference
Source Location
| Property | Value |
|---|---|
| File | pyro/distributions/stable.py
|
| Module | pyro.distributions.stable
|
| Repository | pyro-ppl/pyro |
Signature
class Stable(TorchDistribution):
has_rsample = True
arg_constraints = {
"stability": constraints.interval(0, 2), # half-open (0, 2]
"skew": constraints.interval(-1, 1), # closed [-1, 1]
"scale": constraints.positive,
"loc": constraints.real,
}
support = constraints.real
def __init__(self, stability, skew, scale=1.0, loc=0.0, coords="S0", validate_args=None):
...
def expand(self, batch_shape, _instance=None):
...
def log_prob(self, value):
...
def rsample(self, sample_shape=torch.Size()):
...
@property
def mean(self):
...
@property
def variance(self):
...
class StableWithLogProb(Stable):
"""Same as Stable but will not undergo automatic reparameterization."""
...
Import
from pyro.distributions import Stable, StableWithLogProb
# Or from the module directly:
from pyro.distributions.stable import Stable, StableWithLogProb
I/O Contract
Constructor Parameters
| Parameter | Type | Constraint | Default | Description |
|---|---|---|---|---|
stability |
torch.Tensor |
(0, 2] |
required | Levy stability parameter alpha. alpha=2 gives Gaussian; alpha=1 gives Cauchy-like. |
skew |
torch.Tensor |
[-1, 1] |
required | Skewness parameter beta. beta=0 gives a symmetric distribution. |
scale |
torch.Tensor |
positive |
1.0 |
Scale parameter sigma. |
loc |
torch.Tensor |
real |
0.0 |
Location parameter. Interpretation depends on coords.
|
coords |
str |
"S0" or "S" |
"S0" |
Parameterization choice. "S0" is Nolan's continuous parameterization; "S" is the scipy-style parameterization (discontinuous at stability=1). |
validate_args |
bool or None |
-- | None |
Whether to validate input arguments. |
Distribution Properties
| Property | Type | Description |
|---|---|---|
mean |
torch.Tensor |
The mean of the distribution. Returns NaN when stability <= 1 (mean is undefined). For S0 coords, adjusted by -scale * skew * tan(pi/2 * stability).
|
variance |
torch.Tensor |
Returns 2 * scale^2 when stability == 2 (Gaussian case), otherwise inf.
|
has_rsample |
bool |
True -- supports reparameterized sampling
|
support |
constraints.real |
The entire real line |
Methods
| Method | Return Type | Description |
|---|---|---|
log_prob(value) |
torch.Tensor |
Computes log probability via numerical integration using the CMS algorithm. Uses double precision internally for accuracy. |
rsample(sample_shape) |
torch.Tensor |
Draws reparameterized samples via the CMS method with hole-filling interpolation around alpha=1. |
expand(batch_shape) |
Stable |
Returns a new Stable instance with expanded batch dimensions. |
Internal Helper Functions
| Function | Description |
|---|---|
_unsafe_standard_stable(alpha, beta, V, W, coords) |
Core CMS sampling implementation. Transforms uniform noise V and exponential noise W into standard stable random variables. Fails near alpha=1. |
_standard_stable(alpha, beta, aux_uniform, aux_exponential, coords) |
Safe wrapper that uses interpolation around the hole at alpha=1 (within radius RADIUS=0.01).
|
Usage Examples
Basic Stable Distribution
import torch
import pyro.distributions as dist
# Standard Cauchy distribution (alpha=1, beta=0)
cauchy = dist.Stable(stability=torch.tensor(1.0), skew=torch.tensor(0.0))
samples = cauchy.rsample(torch.Size([1000]))
print(samples.shape) # torch.Size([1000])
# Gaussian distribution (alpha=2, beta=0)
gaussian = dist.Stable(
stability=torch.tensor(2.0),
skew=torch.tensor(0.0),
scale=torch.tensor(1.0),
loc=torch.tensor(0.0),
)
print(gaussian.mean) # tensor(0.)
print(gaussian.variance) # tensor(2.)
Using Reparameterizers for Efficient Inference
import torch
import pyro
import pyro.distributions as dist
from pyro.infer.reparam import StableReparam
import pyro.poutine as poutine
def model(data):
stability = pyro.sample("stability", dist.Uniform(0.5, 2.0))
skew = pyro.sample("skew", dist.Uniform(-1.0, 1.0))
scale = pyro.sample("scale", dist.LogNormal(0.0, 1.0))
loc = pyro.sample("loc", dist.Normal(0.0, 10.0))
with pyro.plate("data", len(data)):
pyro.sample("obs", dist.Stable(stability, skew, scale, loc), obs=data)
# Wrap with reparameterizer for faster inference
reparam_model = poutine.reparam(model, config={"obs": StableReparam()})
Using MinimalReparam for Automatic Reparameterization
import torch
import pyro
import pyro.distributions as dist
from pyro.infer.reparam import MinimalReparam
@MinimalReparam()
def model():
x = pyro.sample("x", dist.Stable(
stability=torch.tensor(1.5),
skew=torch.tensor(0.3),
scale=torch.tensor(1.0),
loc=torch.tensor(0.0),
))
return x
StableWithLogProb for Explicit Log Probability
import torch
import pyro.distributions as dist
# StableWithLogProb prevents automatic reparameterization
d = dist.StableWithLogProb(
stability=torch.tensor(1.5),
skew=torch.tensor(0.2),
scale=torch.tensor(1.0),
loc=torch.tensor(0.0),
)
samples = d.rsample(torch.Size([50]))
log_probs = d.log_prob(samples)
print(log_probs.shape) # torch.Size([50])
Comparing S0 and S Parameterizations
import torch
import pyro.distributions as dist
# S0 parameterization (default, continuous)
d_s0 = dist.Stable(
stability=torch.tensor(1.5),
skew=torch.tensor(0.5),
coords="S0",
)
# S parameterization (scipy-style, discontinuous at alpha=1)
d_s = dist.Stable(
stability=torch.tensor(1.5),
skew=torch.tensor(0.5),
coords="S",
)
# Both produce valid samples, but S0 has better geometry for inference
print(d_s0.rsample().item())
print(d_s.rsample().item())
Related Pages
- StableReparam -- Reparameterizer for Stable distributions enabling efficient SVI
- LatentStableReparam -- Latent-variable reparameterizer for Stable distributions
- SymmetricStableReparam -- Reparameterizer for symmetric (skew=0) Stable distributions
- SoftLaplace -- A smooth heavy-tailed distribution as an alternative to Stable
- Distributions_Init -- Central registry of all Pyro distributions