Search code examples
pythonrandomscipymontecarlo

Getting random numbers from a truncated Maxwell-Boltzmann distribution


I would like to generate random numbers using a truncated Maxwell-Boltzmann distribution. I know that scipy has built-in Maxwell random variables, but there is no truncated version of it (I am also aware of a truncated normal distribution, which is irrelevant here). I have tried to write my own random variables using rvs_continuous:

import scipy.stats as st

class maxwell_boltzmann_pdf(st.rv_continuous):

    def _pdf(self,x):
        n_0 = np.power(np.pi,3/2)*np.square(v_0)*(v_0*erf(v_esc/v_0)-(2/np.sqrt(np.pi))*v_esc*np.exp(-np.square(v_esc/v_0)))
        return (1/n_0)*(4*np.pi*np.square(x))*np.exp(-np.square(x/v_0))*np.heaviside(v_esc-x,0)

maxwell_boltzmann_cv = maxwell_boltzmann_pdf(a=0, b=v_esc, name='maxwell_boltzmann_pdf')

This does exactly what I want, but it is way too slow for my purpose (I am doing Monte Carlo simulations), even if I draw all the random velocities outside of all the loops. I have also thought of using Inverse transform sampling method, but the inverse of the CDF does not have an analytic form and I will need to do a bisection for every number I draw. It would be great if there is a convenient way for me to generate random numbers from a truncated Maxwell-Boltzmann distribution with decent speed.


Solution

  • It turns out that there is a way to generate a truncated Maxwell-Boltzmann distribution with the inverse transform sampling method using the ppf feature of scipy. I am posting the code here for future reference.

    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.special import erf
    from scipy.stats import maxwell
    
    # parameters
    v_0 = 220 #km/s
    v_esc = 550 #km/s
    N = 10000
    
    # CDF(v_esc)
    cdf_v_esc = maxwell.cdf(v_esc,scale=v_0/np.sqrt(2))
    
    # pdf for the distribution
    def f_MB(v_mag):
        n_0 = np.power(np.pi,3/2)*np.square(v_0)*(v_0*erf(v_esc/v_0)-(2/np.sqrt(np.pi))*v_esc*np.exp(-np.square(v_esc/v_0)))
        return (1/n_0)*(4*np.pi*np.square(v_mag))*np.exp(-np.square(v_mag/v_0))*np.heaviside(v_esc-v_mag,0)
    
    # plot the pdf
    x = np.arange(600)
    y = [f_MB(i) for i in x]
    plt.plot(x,y,label='pdf')
    
    # use inverse transform sampling to get the truncated Maxwell-Boltzmann distribution
    sample = maxwell.ppf(np.random.rand(N)*cdf_v_esc,scale=v_0/np.sqrt(2))
    
    # plot the histogram of the samples
    plt.hist(sample,bins=100,histtype='step',density=True,label='histogram')
    plt.xlabel('v_mag')
    plt.legend()
    plt.show()
    

    This code generates the required random variables and compare its histogram with the analytic form of the pdf, which matches with each other pretty well.