Empty plot and zero values for mass function

Question:

This code gives empty plot in logscale because the n values for M>1e13 are zero and also for different z values the results are exactly the same. Can anyone help to solve this issues?

import numpy as np
import matplotlib.pyplot as plt

# cosmological parameters from Planck 2015 results
h = 0.678
Omega_m = 0.308
delta_c = 1.686
A = 0.3222/h**3
a = 0.707

# define the mass range
logMmin = 12  # minimum log mass in solar masses
logMmax = 15  # maximum log mass in solar masses
nM = 100  # number of mass bins
logM = np.linspace(logMmin, logMmax, nM)
M = 10**logM/h  # convert to units of h^-1 Msun


# calculate the rms fluctuation of the linear density field
def sigma(M):
    k = 0.1/h  # normalization scale
    Pk = lambda x: 2*np.pi**2/x**3 * (1+(x/2.3)**-1.5)  # Eisenstein & Hu transfer function
    R = (3*M/(4*np.pi*Omega_m*1e11))**(1/3)  # convert mass to comoving radius in Mpc/h
    integrand = lambda x: x**2*Pk(x)*np.exp(-x**2*R**2)
    s2 = (1/(2*np.pi**2)) * (k**3 * np.trapz(integrand(np.geomspace(k, 10*k, 100)), np.geomspace(k, 10*k, 100)))**(1/2)
    return s2

# calculate the slope of the power spectrum
def dlnsigma_dlnM(M):
    eps = 1e-5 * M
    return (np.log(sigma(M+eps)) - np.log(sigma(M-eps))) / (2*np.log(M+eps/M-eps))

# calculate the ST mass function
def n_H(M, z):
    rho_m = Omega_m * 2.775e11  # mean matter density in h^2 Msun/Mpc^3
    s = sigma(M)
    ds_dlnM = dlnsigma_dlnM(M)
    return A * np.sqrt(2*a/np.pi) * rho_m/M * ds_dlnM * (1+(a*delta_c**2/(s**2)))*(s/a/delta_c**2) * np.exp(-(a*delta_c**2/(2*s**2))) / h**4
    
# Define redshift array
z_arr = [0, 0.1, 0.5, 2, 4, 5]

# plot the mass function for different redshifts
fig, ax = plt.subplots()
for z in z_arr:
    n = [n_H(m, z) for m in M]
    ax.plot(M, n, label=f"z = {z}")
    #print(logM, n)
    # Save logM and n for different redshifts to a file with three columns
    data = np.column_stack((M, n))
    np.savetxt(f'mass_function_z{z:.1f}.txt', data, header='M n', fmt='%.6e')
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('$log_{10}(M)[h^{-1}M_odot]$')
ax.set_ylabel('$[h^4Mpc^{-3}M_{odot}^{-3}]$')
ax.set_title('Sheth-Tormen Mass Function')
ax.legend()
#ax.set_xlim([10**12, 10**15])
#ax.set_ylim([10**-7, 10**2])
plt.show()
Asked By: dove

||

Answers:

Based on what I observe the issue lies with the negative values of the x axis which are unable to be represented in log format.
One rough solution for that included performing an np.abs(n) as the x-input to the plot function.

ax.plot(M, np.abs(n), label=f"z = {z}")

This might not be the correct solution, but it fixes the issue with the values not plotting. You will have to convert them to positive values to enable log scale. I think the values are overlapping.
The updated function

Answered By: Anurag Reddy

I noticed that in the n_H(M, z) there is no z dependency. That’s why z is not applied on n_H(M, z) function. I tried to use other functions of cosmology and I modified the code to this:

import numpy as np
from scipy.integrate import quad
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt

# Planck 2018 cosmological parameters
h = 0.6736
Omega_m = 0.3153
sigma_8 = 0.8111

# range of masses in solar masses
M = np.logspace(7, 16, 1000)


delta_c = 1.686
A = 0.3222/h**3
a = 0.707

# calculate the growth factor at the given redshift
def D(z):
    Omega_L = 1 - Omega_m
    Omega_k = 1 - Omega_m - Omega_L
    a_z = 1 / (1+z)
    E_z = np.sqrt(Omega_m*(1+z)**3 + Omega_k*(1+z)**2 + Omega_L)
    integrand = lambda a: a**2 * E_z / np.sqrt(Omega_m/a + Omega_k + Omega_L*a**2)
    D_z, _ = quad(integrand, a_z, 1)
    D_z /= a_z * E_z
    return D_z

# calculate the linear power spectrum at the given redshift
def power_spectrum(k, z):
    Omega_b = 0.048
    Omega_cdm = Omega_m - Omega_b
    h = 0.678
    ns = 0.961
    sigma8 = 0.829
    As = 2.142e-9
    k_pivot = 0.05/h
    T_cdm = lambda x: np.log(1 + 0.171*x) / (0.171*x)
    T_b = lambda x: np.log(1 + 0.171*x) / (0.171*x) * (1 + 3.832*x + 4.048*x**2 + 2.208*x**3 + 0.642*x**4) / (1 + 21.085*x + 40.369*x**2 + 46.176*x**3 + 29.086*x**4 + 10.865*x**5 + 1.985*x**6)
    T_tot = lambda x: (Omega_cdm*T_cdm(x) + Omega_b*T_b(x)) / (Omega_cdm + Omega_b)
    Delta_H = lambda x: (2.41*x)**4 / (1 + 1.78*x + 1.18*x**2 + 0.399*x**3 + 0.0497*x**4)**0.5
    Delta_H_kp = Delta_H(k_pivot)
    Delta_H_z = Delta_H(k/h)
    return As * (k/k_pivot)**(ns-1) * T_tot(k)**2 * Delta_H_z**2 / Delta_H_kp**2

# calculate the variance of the density field at the given redshift and smoothing scale
def sigma(M, z):
    R = (3*M/(4*np.pi*A*Omega_m))**(1/3)
    integrand = lambda k: k**2 * power_spectrum(k, z) * (3 * (np.sin(k*R) - k*R*np.cos(k*R)) / (k*R)**3)**2
    sigma_squared, _ = quad(integrand, 0, np.inf)
    return np.sqrt(sigma_squared)

def dlnsigma_dlnM(M, z):
    k = np.logspace(-5, 5, 1000)
    dlnsigma_dlnk = np.gradient(np.log(sigma(M, z)), np.log(k))
    dlnk_dlnM = -3/(1+z) # assumes the linear growth factor is D(z) = (1+z)^(-1)
    return dlnsigma_dlnk * dlnk_dlnM


def mass_function(M, z):
    # M is the mass in units of solar masses
    # z is the redshift
    # delta_c is the critical overdensity for collapse (default value is 1.686)
    delta_c=1.686
    # calculate the rms fluctuation sigma
    k = np.logspace(-5, 5, 1000) # define a range of k values
    try:
        Pk = power_spectrum(k, z) # calculate the power spectrum at redshift z
    except:
        print("Error calculating power spectrum")
        return None
    R = (3*M / (4*np.pi* rho_m(z)))**(1/3) # calculate the smoothing scale
    W = 3*(np.sin(k*R) - k*R*np.cos(k*R)) / (k*R)**3 # calculate the Fourier transform of the top-hat window function
    sigma2 = np.trapz(Pk * W**2 * k**2, k) / (2*np.pi**2) # calculate the rms fluctuation
    sigma = np.sqrt(sigma2)

    # calculate the nu parameter
    nu = delta_c / sigma

    # calculate the mass function
    f_nu = np.sqrt(2/np.pi) * nu * np.exp(-nu**2/2)
    return rho_m(z) * f_nu / M * np.abs(dlnsigma_dlnM(M, z))


def halo_mass_function(M, z):
    return mass_function(M, z) * rho_m(z) / M

def subhalo_mass_function(M, z):
    return mass_function(M, z) * rho_m(z) / M * f_subhalo(M, z)


# mean matter density of the universe at redshift z
def rho_m(z):
    return Omega_m * (1 + z)**3 * 2.775e11 * h**2

# subhalo fraction
def f_subhalo(M, z):
    return 0.1 * (M / 1e12)**-0.5 * (1 + z)**-0.5

# range of n_H values in units of h^4 Mpc^-3 M_sun^-1
n_H = np.logspace(-23, -13, 1000)

# redshifts to consider
z = [0.1, 0.5, 2, 4, 5]

# plot the global mass functions for halos and subhalos
fig, ax = plt.subplots()

for z_val in z:
    hmf_halo = halo_mass_function(M, z_val)
    hmf_subhalo = subhalo_mass_function(M, z_val)

    for n_H_val in n_H:
        ax.plot(M, hmf_halo * n_H_val, color='blue', alpha=0.05)
        ax.plot(M, hmf_subhalo * n_H_val, color='red', alpha=0.05)

ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('Mass (M_sun)')
ax.set_ylabel('n(M,z) (h^4 Mpc^-3)')
ax.set_xlim([1e7, 1e16])
ax.set_ylim([1e-10, 1e6])
ax.legend(['Halo z=0.1', 'Subhalo z=0.1', 'Halo z=0.5', 'Subhalo z=0.5', 'Halo z=2', 'Subhalo z=2', 'Halo z=4', 'Subhalo z=4', 'Halo z=5', 'Subhalo z=5'])

plt.show()

However, I am not able to plot because I constantly receive this error:

TypeError: only size-1 arrays can be converted to Python scalars
Answered By: dove
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.