Skip to content

Instantly share code, notes, and snippets.

@andrewfowlie
Last active July 27, 2016 05:15
Show Gist options
  • Save andrewfowlie/cd0ed7e6c96f7c9e88f85eb3b9665b97 to your computer and use it in GitHub Desktop.
Save andrewfowlie/cd0ed7e6c96f7c9e88f85eb3b9665b97 to your computer and use it in GitHub Desktop.
Starting point for relativistic Breit-Wigner in scipy.stats
"""
Relativistic Breit-Wigner.
"""
import numpy as np
from numpy import pi
from scipy.stats import rv_continuous
class breit_wigner_gen(rv_continuous):
"""
Probability density function for relativistic Breit-Wigner distribution [1]_.
References
----------
.. [1] https://en.wikipedia.org/wiki/Relativistic_Breit-Wigner_distribution
Notes
-----
Plot a Breit-Wigner distribution and random samples
---------------------------------------------------
>>> import matplotlib.pyplot as plt
>>> BW = breit_wigner(mass=125., width=10.)
>>> mass = np.linspace(0., 200., 500)
>>> pdf = [BW.pdf(m) for m in mass]
>>> pdf_plot = plt.plot(mass, pdf)
>>> samples = BW.rvs(1000)
>>> hist_plot = plt.hist(samples, normed=True, bins=100, range=[0, 200])
>>> plt.show()
Statistics of a Breit-Wigner distribution
-----------------------------------------
Mean and variance:
>>> BW.stats()
(array(122.11538219739913), array(762.7536855273247))
Median:
>>> BW.median()
124.41711775583536
Variance and standard deviation:
>>> BW.var(), BW.std()
(762.75368552732471, 27.617995682658158)
95 per cent interval:
>>> BW.interval(0.95)
(53.719690871945822, 159.27077425799101)
Moments:
>>> [BW.moment(n) for n in range(1, 6)]
[122.11538219739913, 15674.920254744189, inf, 244138132.80994478, 80583978992.641495]
"""
def _argcheck(self, mass, width):
"""
We require mass > 0 and width > 0.
The width -> 0 limit is well-defined mathematically - it's proportional
to a Dirac function. In the m -> 0 limit, the pdf vanishes.
Parameters
----------
mass : float
Mass of resonance
width : float
Width of resonance
Returns
-------
_argcheck : bool
Whether shape parameters are legitimate
"""
return (mass > 0) & (width > 0)
def _pdf(self, m, mass, width):
"""
Parameters
----------
mass : float
Mass of resonance
width : float
Width of resonance
Returns
-------
pdf : float
PDF of Breit-Wigner distribution
>>> breit_wigner(mass=125., width=0.05).pdf(125.)
12.732396211295313
"""
alpha = width / mass
gamma = mass**2 * (1. + alpha**2)**0.5
k = 2.**(3. / 2.) * mass**2 * alpha * gamma / (pi * (mass**2 + gamma)**0.5)
return k / ((m**2 - mass**2)**2 + mass**4 * alpha**2)
def _cdf(self, m, mass, width):
"""
Parameters
----------
mass : float
Mass of resonance
width : float
Width of resonance
Returns
-------
cdf : float
CDF of Breit-Wigner distribution
The CDf was found by Mathematica:
pdf = k/((m^2 - mass^2)^2 + mass^4*alpha^2)
cdf = Integrate[pdf, m]
>>> BW = breit_wigner(mass=125., width=0.05)
>>> BW.cdf(125.)
0.50052268648248666
>>> BW.cdf(1E10)
1.0000000000000004
>>> BW.cdf(0.)
0.0
"""
alpha = width / mass
gamma = mass**2 * (1. + alpha**2)**0.5
k = 2.**(3. / 2.) * mass**2 * alpha * gamma / (pi * (mass**2 + gamma)**0.5)
arg_1 = complex(-1)**(1. / 4.) / (-1j + alpha)**0.5 * m / mass
arg_2 = complex(-1)**(3. / 4.) / (1j + alpha)**0.5 * m / mass
shape = -1j * np.arctan(arg_1) / (-1j + alpha)**0.5 - np.arctan(arg_2) / (1j + alpha)**0.5
norm = complex(-1)**(1. / 4.) * k / (2. * alpha * mass**3)
cdf_ = shape * norm
cdf_ = cdf_.real
return cdf_
breit_wigner = breit_wigner_gen(a=0, b=np.inf, name='Breit-Wigner', shapes='mass, width')
if __name__ == "__main__":
import doctest
doctest.testmod()
@ev-br
Copy link

ev-br commented Jul 26, 2016

When inheriting from rv_continuous class, you want to override the underscored methods _pdf and _cdf, _rvs. You also might want to add

def _argcheck(self, mass, width):
    return (mass > 0) & (width > 0)

so that the framework checks these parameters.

@andrewfowlie
Copy link
Author

Thanks. Getting there, but now I have these types of errors from my doctests:

    **********************************************************************
    File "bw.py", line 62, in __main__.BreitWigner._pdf
    Failed example:
        BreitWigner(mass=125., width=0.05).pdf(125.)
    Exception raised:
        Traceback (most recent call last):
          File "/usr/lib/python2.7/doctest.py", line 1315, in __run
            compileflags, 1) in test.globs
          File "<doctest __main__.BreitWigner._pdf[0]>", line 1, in <module>
            BreitWigner(mass=125., width=0.05).pdf(125.)
          File "/usr/local/lib/python2.7/dist-packages/scipy/stats/_distn_infrastructure.py", line 1576, in pdf
            args, loc, scale = self._parse_args(*args, **kwds)
        TypeError: _parse_args() takes at least 3 arguments (1 given)
    **********************************************************************

I'm obviously not doing the loc, scale thing right. I don't understand how to use them. This distribution isn't well-defined for loc=0 (there are physical reasons for this, as loc=0 would correspond to m=0, and the distribution is related to production and decay of a particle, but massless particles cannot decay).

@andrewfowlie
Copy link
Author

Ah OK, I figured out how it works. Seems to have worked nicely. I've tried to scipy-ify my style, as well as fixing the inheritance of the rv_continuous and issue with the shape parameters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment