mirror of
https://codeberg.org/Yael-II/MSc1-Internship-Stellar-Cluster.git
synced 2026-03-15 03:16:26 +01:00
445 lines
14 KiB
Python
445 lines
14 KiB
Python
"""
|
|
* HR Maker: Building HR diagram from BVR.csv files and fit the main sequence
|
|
* Version 2.2 - 2024-04-16
|
|
@ Yaël Moussouni
|
|
@ Unistra, P&E, MSc1-MoFP
|
|
@ Observatory of Strasbourg (Intern)
|
|
"""
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
import os
|
|
from scipy.interpolate import LinearNDInterpolator
|
|
from amuse.community.seba.interface import SeBa
|
|
from amuse.datamodel import Particles
|
|
from amuse.units import units as u
|
|
from astropy import units as u2
|
|
import scipy.constants as const
|
|
from astropy.io import fits
|
|
from astropy.coordinates import SkyCoord
|
|
from dustmaps.bayestar import BayestarQuery
|
|
|
|
# * Parameters
|
|
|
|
if "YII" in plt.style.available: plt.style.use("YII")
|
|
|
|
# * Variables
|
|
in_directory = "./Cluster_files/"
|
|
fig_directory = "../Figs/HR_Maker_figs/"
|
|
|
|
Cluster_prefix = "Cluster_"
|
|
MS_prefix = "MS_"
|
|
BVR_suffix = "_BVR.csv"
|
|
|
|
metallicity = 0
|
|
ZSun = 0.0134
|
|
bessel_directory = "YBC_tables/ubvrijhklm/regrid/" # Bessel
|
|
atlas9_prefix = "Avodonnell94Rv3.1f" # atlas9
|
|
atlas9_suffix = "k2odfnew.BC.fits" # atlas9
|
|
|
|
phoenix_filename = "Avodonnell94Rv3.1BT-Settl_M-0.0_a+0.0.BC.fits" # phoenix
|
|
atlas12_filename = "Avodonnell94Rv3.147TucP1.BC.fits" # atlas12
|
|
|
|
if metallicity >= 0: atlas9_filename = atlas9_prefix + "p{:02d}".format(metallicity) + atlas9_suffix
|
|
else: atlas9_filename = atlas9_prefix + "m{:02d}".format(metallicity) + atlas9_suffix
|
|
|
|
M_bol_sun = 4.74 # Solar bolometric magnitude
|
|
sigma = const.Stefan_Boltzmann | u.W * u.m**-2 * u.K**-4 # Stefan-Boltzmann constant
|
|
|
|
YES = ["Y", "YES", "1", "OUI"]
|
|
|
|
stellar = SeBa()
|
|
t_min = 0 # Myr
|
|
t_max = 2e5 # Myr
|
|
N_t = int(t_max//10+1)
|
|
N_color = 1000
|
|
N_stars = 500
|
|
|
|
print("\033[94m"+"Info: loading extinction map..."+"\033[0m")
|
|
ext_query = BayestarQuery(version='bayestar2019')
|
|
print("\033[94m"+"Info: done!"+"\033[0m")
|
|
|
|
coeff_ext_BV = 3.626-2.742
|
|
coeff_ext_V = 2.742
|
|
R_ext = 3.1
|
|
|
|
# * Observational data import
|
|
Cluster_filelist = np.sort(os.listdir(in_directory))
|
|
Cluster_filelist = [f for f in Cluster_filelist if Cluster_prefix in f]
|
|
MS_filelist = np.sort(os.listdir(in_directory))
|
|
MS_filelist = [f for f in MS_filelist if MS_prefix in f]
|
|
Cluster_sources = []
|
|
MS_sources = []
|
|
sources = []
|
|
for i in range(len(Cluster_filelist)):
|
|
k = Cluster_filelist[i][len(Cluster_prefix):].find("_BVR")
|
|
source = Cluster_filelist[i][len(Cluster_prefix):k+len(Cluster_prefix)]
|
|
if source not in Cluster_sources: Cluster_sources.append(source)
|
|
for i in range(len(MS_filelist)):
|
|
k = MS_filelist[i][len(MS_prefix):].find("_BVR")
|
|
source = MS_filelist[i][len(MS_prefix):k+len(MS_prefix)]
|
|
if source not in MS_sources: MS_sources.append(source)
|
|
|
|
sources = np.intersect1d(Cluster_sources, MS_sources)
|
|
|
|
print("\033[36m"+"Available source:"+"\033[0m")
|
|
for i in range(len(sources)):
|
|
print("\033[36m"+"\t{}: ".format(i+1) + "\033[0m" + "{}".format(sources[i]))
|
|
k = int(input("\033[32m"+"Select source number: "+"\033[0m"))-1
|
|
print("")
|
|
source = sources[k]
|
|
|
|
BVR_Cluster = np.loadtxt(in_directory+Cluster_prefix+source+BVR_suffix, skiprows=1, usecols=(0,1,2,3,4,5,6,7), comments="#", delimiter=",")
|
|
BVR_MS = np.loadtxt(in_directory+MS_prefix+source+BVR_suffix, skiprows=1, usecols=(0,1,2,3,4,5,6,7), comments="#", delimiter=",")
|
|
|
|
RA_Cluster = BVR_Cluster[:,0]
|
|
DE_Cluster = BVR_Cluster[:,1]
|
|
B_Cluster = BVR_Cluster[:,2]
|
|
V_Cluster = BVR_Cluster[:,3]
|
|
R_Cluster = BVR_Cluster[:,4]
|
|
dB_Cluster = BVR_Cluster[:,5]
|
|
dV_Cluster = BVR_Cluster[:,6]
|
|
dR_Cluster = BVR_Cluster[:,7]
|
|
|
|
B_MS = BVR_MS[:,2]
|
|
V_MS = BVR_MS[:,3]
|
|
R_MS = BVR_MS[:,4]
|
|
dB_MS = BVR_MS[:,5]
|
|
dV_MS = BVR_MS[:,6]
|
|
dR_MS = BVR_MS[:,7]
|
|
|
|
N_MS = len(B_MS)
|
|
N_Cluster = len(B_Cluster)
|
|
|
|
ra = np.mean(RA_Cluster)
|
|
de = np.mean(DE_Cluster)
|
|
|
|
Coords_Cluster = SkyCoord(ra*u2.deg, de*u2.deg, distance=0*u2.pc, frame='icrs')
|
|
print("\033[36m"+"Cluster coordinates: "+"\033[0m" + Coords_Cluster.to_string('hmsdms'))
|
|
# * Interpolation data import
|
|
|
|
hdul = fits.open(bessel_directory+atlas9_filename)
|
|
# hdul = fits.open(bessel_directory+phoenix_filename)
|
|
# hdul = fits.open(bessel_directory+atlas12_filename)
|
|
interp_data = hdul[1].data
|
|
|
|
# * Main sequence generation
|
|
print("\033[94m"+"Info: generating and evolving stars..."+"\033[0m")
|
|
|
|
|
|
def stars_MS(M_min = .5, M_max = 5, z=ZSun, N = 100):
|
|
M_stars = np.linspace(M_min, M_max, N) | u.MSun
|
|
stars = Particles(len(M_stars), mass=M_stars)
|
|
stellar = SeBa()
|
|
stellar.particles.add_particles(stars)
|
|
stellar.set_metallicity(z)
|
|
stellar.commit_particles()
|
|
stellar.update_particles(stars)
|
|
return stars
|
|
|
|
# * Interpolation functions
|
|
|
|
interp_logTeff = interp_data.field(0)
|
|
interp_logg = interp_data.field(1)
|
|
BC_B_bessel = interp_data.field(3)
|
|
BC_V_bessel = interp_data.field(4)
|
|
BC_R_bessel = interp_data.field(5)
|
|
|
|
"""
|
|
Interpolation using YBC tables (Yang, 2019)
|
|
@ https://ui.adsabs.harvard.edu/abs/2019A%26A...632A.105C/abstract
|
|
@ https://sec.center/YBC/
|
|
"""
|
|
interp_BC_B = LinearNDInterpolator(list(zip(interp_logTeff, interp_logg)), BC_B_bessel)
|
|
interp_BC_V = LinearNDInterpolator(list(zip(interp_logTeff, interp_logg)), BC_V_bessel)
|
|
interp_BC_R = LinearNDInterpolator(list(zip(interp_logTeff, interp_logg)), BC_R_bessel)
|
|
|
|
# * Interpolation quantities computation
|
|
|
|
def extract_BVR_stars(stars):
|
|
L_stars = stars.luminosity.value_in(u.LSun)
|
|
Teff_stars = ((stars.luminosity / (4*np.pi * stars.radius**2 * sigma))**(1/4)).value_in(u.K)
|
|
G_stars = (u.constants.G * stars.mass / stars.radius**2).value_in(u.m * u.s**(-2))
|
|
# type_stars = stars.stellar_type.value_in(u.stellar_type)
|
|
BC_B_stars = interp_BC_B(np.log10(Teff_stars),np.log10(G_stars))
|
|
BC_V_stars = interp_BC_V(np.log10(Teff_stars),np.log10(G_stars))
|
|
BC_R_stars = interp_BC_R(np.log10(Teff_stars),np.log10(G_stars))
|
|
|
|
M_bol_stars = M_bol_sun - 2.5*np.log10(L_stars)
|
|
|
|
B_abs_stars = M_bol_stars-BC_B_stars
|
|
V_abs_stars = M_bol_stars-BC_V_stars
|
|
R_abs_stars = M_bol_stars-BC_R_stars
|
|
|
|
# w1_not = np.where(type_stars != 1)
|
|
|
|
# B_abs_stars[w1_not] = np.nan
|
|
# V_abs_stars[w1_not] = np.nan
|
|
# R_abs_stars[w1_not] = np.nan
|
|
|
|
return B_abs_stars, V_abs_stars, R_abs_stars
|
|
|
|
|
|
stars = stars_MS(N = N_stars, z=0.2)
|
|
N_stars = len(stars)
|
|
stellar.particles.add_particles(stars)
|
|
stellar.commit_particles()
|
|
|
|
Time_stars = np.linspace(t_min, t_max, N_t)
|
|
|
|
def BVR_abs_stars(stellar, stars):
|
|
B_abs_stars = np.empty((N_t,N_stars))
|
|
V_abs_stars = np.empty((N_t,N_stars))
|
|
R_abs_stars = np.empty((N_t,N_stars))
|
|
for i in range(N_t):
|
|
t = Time_stars[i]
|
|
stellar.evolve_model(t|u.Myr)
|
|
stellar.update_particles(stars)
|
|
B_abs_stars[i], V_abs_stars[i], R_abs_stars[i] = extract_BVR_stars(stars)
|
|
|
|
return B_abs_stars, V_abs_stars, R_abs_stars
|
|
|
|
|
|
B_abs_stars, V_abs_stars, R_abs_stars = BVR_abs_stars(stellar, stars)
|
|
|
|
def V(t, mu):
|
|
i = np.argmin(np.abs(Time_stars-t))
|
|
return V_abs_stars[i]+mu
|
|
|
|
# * Theoretical values
|
|
BV_MS = B_MS-V_MS
|
|
|
|
BV_Cluster = B_Cluster-V_Cluster
|
|
|
|
print("\033[94m"+"Info: done!"+"\033[0m")
|
|
|
|
# * Figure and fitting
|
|
|
|
def print_results(t_fit, dt_fit, d_fit, dd_fit, ext, ext_BV, ext_V):
|
|
print("\033[36m"+"t = "+"\033[0m" + "{:.2f}±{:.2f}".format(t_fit, dt_fit)+"\033[36m"+" Myr"+"\033[0m")
|
|
print("\033[36m"+"d = "+"\033[0m" + "{:.2f}±{:.2f}".format(d_fit/1000, dd_fit/1000)+"\033[36m"+" kpc"+"\033[0m")
|
|
print("\033[36m"+"ext = "+"\033[0m" + "{:.2f}".format(ext)+"\033[36m"+" mag"+"\033[0m")
|
|
print("\033[36m"+"E(B-V) = "+"\033[0m" + "{:.2f}".format(ext_BV)+"\033[36m"+" mag"+"\033[0m")
|
|
print("\033[36m"+"A_V = "+"\033[0m" + "{:.2f}".format(ext_V)+"\033[36m"+" mag"+"\033[0m")
|
|
return None
|
|
|
|
def mu(d): return 5*np.log10(d)-5
|
|
fig1, ax1 = plt.subplots(1)
|
|
fig4, ax4 = plt.subplots(1)
|
|
|
|
w = np.argwhere(ext_query.distances.to(u2.pc).value < 5000)
|
|
ext_tot = ext_query(SkyCoord(ra*u2.deg, de*u2.deg, frame='icrs'), mode='mean')
|
|
ax4.plot(ext_query.distances.to(u2.pc).value[w], ext_tot[w], color="C0")
|
|
|
|
sct = ax4.plot(0,0, color="C4", marker="+", linestyle="", alpha=1, markersize=5)
|
|
sctp = ax4.plot(0,0, color="C1", marker="+", linestyle="", alpha=0.65, markersize=3)
|
|
sctm = ax4.plot(0,0, color="C1", marker="+", linestyle="", alpha=0.65, markersize=3)
|
|
|
|
|
|
ax4.set_xlabel("Distance [pc]")
|
|
ax4.set_ylabel("Reddening")
|
|
|
|
|
|
t_fit = 0
|
|
dt_fit = 0
|
|
d_fit = 10
|
|
dd_fit = 0
|
|
ext = 0
|
|
ext_BV = 0
|
|
ext_V = 0
|
|
|
|
continue_fit = True
|
|
auto_ext = True
|
|
|
|
ax1.scatter(BV_Cluster, V_Cluster, s=1, marker='.', color="C3")
|
|
sct_BV = ax1.scatter(BV_MS, V_MS, s=1, marker='.', color="C0")
|
|
|
|
|
|
X_BV = B_abs_stars[0] - V_abs_stars[0]
|
|
|
|
Y = V(t_fit, mu(d_fit))
|
|
Yp = V(t_fit+dt_fit, mu(d_fit-dd_fit))
|
|
Ym = V(t_fit-dt_fit, mu(d_fit+dd_fit))
|
|
|
|
fit_BV = ax1.plot(X_BV, Y, color="C4", marker=".", linestyle="", markersize=2)
|
|
err_BVp = ax1.plot(X_BV, Yp, color="C1", marker=".", linestyle="", alpha=0.65, markersize=1.5)
|
|
err_BVm = ax1.plot(X_BV, Ym, color="C1", marker=".", linestyle="", alpha=0.65, markersize=1.5)
|
|
|
|
ax1.set_xlabel("Color $B-V$")
|
|
ax1.set_ylabel("Magnitude $V$")
|
|
ax1.invert_yaxis()
|
|
ax1.legend([sct_BV, fit_BV[0]], ["Observational data (main sequence)", "$t =$ {:.2f} Myr, $d =$ {:.2f} kpc".format(t_fit, d_fit/1000)], loc="upper right")
|
|
fig1.tight_layout()
|
|
|
|
plt.show(block=False)
|
|
|
|
print("\033[36m"+"Actions:"+"\033[0m")
|
|
print("\033[36m"+"\t- t+/t- [int/float, Myr] (time increase/decrease)"+"\033[0m")
|
|
print("\033[36m"+"\t- d+/d- [int/float, pc] (distance increase/decrease)"+"\033[0m")
|
|
print("\033[36m"+"\t- vt+/vt- [int/float, Myr] (time uncertainty increase/decrease)"+"\033[0m")
|
|
print("\033[36m"+"\t- vd+/vd- [int/float, pc] (distance uncertainty increase/decrease)"+"\033[0m")
|
|
print("\033[36m"+"\t- ext+/ext- [int/float] (extinction value)"+"\033[0m")
|
|
print("\033[36m"+"\t- ext auto [int/float] (auto extinction value)"+"\033[0m")
|
|
print("\033[36m"+"\t- adjust (adjust plots)"+"\033[0m")
|
|
print("\033[36m"+"\t- print (show results)"+"\033[0m")
|
|
print("\033[36m"+"\t- ok (end)"+"\033[0m")
|
|
print("\033[36m"+"\t- cancel (end)"+"\033[0m")
|
|
|
|
|
|
while continue_fit:
|
|
var = input(("\033[32m"+"Enter action: "+"\033[0m"))
|
|
if var == "":
|
|
continue
|
|
elif var == "ok":
|
|
continue_fit = False
|
|
elif var == "cancel":
|
|
continue_fit = False
|
|
plt.close()
|
|
exit()
|
|
elif var == "print":
|
|
print_results(t_fit, dt_fit, d_fit, dd_fit, ext, ext_BV, ext_V)
|
|
elif var == "adjust":
|
|
ax1.set_ylim(np.max(V_Cluster), np.min(V_Cluster))
|
|
ax1.set_xlim(np.min(B_Cluster-V_Cluster), np.max(B_Cluster-V_Cluster))
|
|
elif var[0:3] == "ext":
|
|
if "auto" in var:
|
|
auto_ext = True
|
|
else:
|
|
auto_ext = False
|
|
if var[3] == "=":
|
|
val = float(var[4:])
|
|
ext = val
|
|
else:
|
|
val = float(var[3:])
|
|
ext += val
|
|
ext = np.clip(ext, 0, 10)
|
|
extp = ext
|
|
extm = ext
|
|
elif var[0:2] == "dd":
|
|
print("\033[33m"+"Warning: please use vd instead!"+ "\033[0m")
|
|
if var[2] == "=":
|
|
val = float(var[3:])
|
|
dd_fit = val
|
|
else:
|
|
val = float(var[2:])
|
|
dd_fit += val
|
|
dd_fit = np.clip(dd_fit, 0, d_fit)
|
|
elif var[0:2] == "dt":
|
|
print("\033[33m"+"Warning: please use vt instead!"+ "\033[0m")
|
|
if var[2] == "=":
|
|
val = float(var[3:])
|
|
dt_fit = val
|
|
else:
|
|
val = float(var[2:])
|
|
dt_fit += val
|
|
dt_fit = np.clip(dt_fit, 0, t_fit)
|
|
elif var[0] == "d":
|
|
if var[1] == "=":
|
|
val = float(var[2:])
|
|
d_fit = val
|
|
else:
|
|
val = float(var[1:])
|
|
d_fit += val
|
|
d_fit = np.clip(d_fit, 1, 1e12)
|
|
elif var[0] == "t":
|
|
if var[1] == "=":
|
|
val = float(var[2:])
|
|
t_fit = val
|
|
else:
|
|
val = float(var[1:])
|
|
t_fit += val
|
|
t_fit = np.clip(t_fit, t_min, t_max)
|
|
elif var[0] == "v":
|
|
if var[1] == "d":
|
|
if var[2] == "=":
|
|
val = float(var[3:])
|
|
dd_fit = val
|
|
else:
|
|
val = float(var[2:])
|
|
dd_fit += val
|
|
dd_fit = np.clip(dd_fit, 0, d_fit)
|
|
if var[1] == "t":
|
|
if var[2] == "=":
|
|
val = float(var[3:])
|
|
dt_fit = val
|
|
else:
|
|
val = float(var[2:])
|
|
dt_fit += val
|
|
dt_fit = np.clip(dt_fit, 0, t_fit)
|
|
else:
|
|
print("\033[33m"+"Warning: not found!"+ "\033[0m")
|
|
|
|
|
|
# * Extinction computation
|
|
i = np.argmin(np.abs(Time_stars-t_fit))
|
|
ip = np.argmin(np.abs(Time_stars-(t_fit+dt_fit)))
|
|
im = np.argmin(np.abs(Time_stars-(t_fit-dt_fit)))
|
|
X_BV = B_abs_stars[i] - V_abs_stars[i]
|
|
Xp_BV = B_abs_stars[ip] - V_abs_stars[ip]
|
|
Xm_BV = B_abs_stars[im] - V_abs_stars[im]
|
|
|
|
if auto_ext:
|
|
Coords_Cluster = SkyCoord(ra*u2.deg, de*u2.deg, distance=d_fit*u2.pc, frame='icrs')
|
|
ext = ext_query(Coords_Cluster, mode='mean')
|
|
Coords_Clusterp = SkyCoord(ra*u2.deg, de*u2.deg, distance=(d_fit-dd_fit)*u2.pc, frame='icrs')
|
|
extp = ext_query(Coords_Clusterp, mode='mean')
|
|
Coords_Clusterm = SkyCoord(ra*u2.deg, de*u2.deg, distance=(d_fit+dd_fit)*u2.pc, frame='icrs')
|
|
extm = ext_query(Coords_Clusterm, mode='mean')
|
|
|
|
ext_BV = coeff_ext_BV * ext
|
|
ext_V = coeff_ext_V * ext
|
|
X_BV_corr = X_BV + ext_BV
|
|
|
|
extp_BV = coeff_ext_BV * extp
|
|
extp_V = coeff_ext_V * extp
|
|
Xp_BV_corr = Xp_BV + extp_BV
|
|
|
|
extm_BV = coeff_ext_BV * extm
|
|
extm_V = coeff_ext_V * extm
|
|
Xm_BV_corr = Xm_BV + extm_BV
|
|
|
|
Y = V(t_fit, mu(d_fit)) + ext_V
|
|
Yp = V(t_fit+dt_fit, mu(d_fit-dd_fit)) + extp_V
|
|
Ym = V(t_fit-dt_fit, mu(d_fit+dd_fit)) + extm_V
|
|
|
|
fit_BV[0].set_xdata(X_BV_corr)
|
|
|
|
fit_BV[0].set_ydata(Y)
|
|
|
|
err_BVp[0].set_xdata(Xp_BV_corr)
|
|
err_BVm[0].set_xdata(Xm_BV_corr)
|
|
|
|
err_BVp[0].set_ydata(Yp)
|
|
err_BVm[0].set_ydata(Ym)
|
|
|
|
sct[0].set_xdata([d_fit])
|
|
sct[0].set_ydata([ext])
|
|
sctp[0].set_xdata([d_fit-dd_fit])
|
|
sctp[0].set_ydata([extp])
|
|
sctm[0].set_xdata([d_fit+dd_fit])
|
|
sctm[0].set_ydata([extm])
|
|
|
|
|
|
ax1.legend([sct_BV, fit_BV[0]], ["Observational data", "$t =$ {:.2f} $\\pm$ {:.2f} Myr, $d =$ {:.2f} $\\pm$ {:.2f} kpc, $\\mathrm{{ext}} =$ {:.2f}".format(t_fit, dt_fit, d_fit/1000, dd_fit/1000, ext)], loc="upper right")
|
|
|
|
fig1.canvas.draw_idle()
|
|
fig1.canvas.flush_events()
|
|
|
|
fig4.canvas.draw_idle()
|
|
fig4.canvas.flush_events()
|
|
|
|
|
|
print_results(t_fit, dt_fit, d_fit, dd_fit, ext, ext_BV, ext_V)
|
|
|
|
|
|
save_fig = input("\033[32m"+"Save figure (y/n): "+"\033[0m").upper() in YES
|
|
|
|
if save_fig:
|
|
fig1.savefig(fig_directory+source+"_HR_B-V_fit")
|
|
fig4.savefig(fig_directory+source+"_HR_ext_fit")
|
|
|
|
plt.show(block=False)
|
|
input(("\033[33m"+"(Press enter to quit)"+"\033[0m"))
|
|
plt.close()
|
|
|