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()
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.
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
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()
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.
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