This script automatically calibrates stellar evolution models to the Sun using the Dartmouth Stellar Evolution Program (DSEP). It includes functionality for setting up the environment, running simulations with different initial conditions, and analyzing the results to find the best-fit model. It leverages libraries such as numpy, pandas, and scipy for numerical operations, and f90nml for Fortran namelist file manipulation.
import os import numpy as np import argparse import f90nml import shutil import pandas as pd import re from scipy import optimize as op def config_env(args): saveDir = f"output_{args.namelist.split('.')[0]}_test" local = "/home/tboudreaux/d/Astronomy/GraduateSchool/SecondYearProject/DSEP/dsep_Thomas" nml = f"{local}/run/SolarCalibration/{saveDir}" out = f"{local}/run/SolarCalibration/{saveDir}/output" prog = f"{local}/dsepX" zams = f"{local}/zams" prems = f"{local}/prems" opac = f"{local}/opac" atm = f"{local}/surfBC" opal = f"{opac}/OPAL" phx = f"{opac}/phoenix" bcphx = f"{atm}/phoenix/GS98" bckur = f"{atm}/atmk" fort = { 13: f"{local}/run/phys1.low.nml", 14: f"{nml}/", 15: f"{opac}/FERMI.TAB", 38: f"{bckur}/atmk1990p00.tab", 48: f"{opal}/GS98hz", 95: f"{bcphx}/z_p0d0.afe_p0d0.dat", 96: f"{bcphx}/z_m0d5.afe_p0d0.dat", 97: f"{bcphx}/z_m0d7.afe_p0d0.dat", 98: f"{bcphx}/z_m1d0.afe_p0d0.dat", 99: f"{bcphx}/z_m1d5.afe_p0d0.dat", 12: f"{prems}/m100.GS98", 19: f"{out}/", 20: f"{out}/", 37: f"{out}/", 11: f"{out}/", 24: f"{out}/", 25: f"{out}/", 26: f"{out}/" } return saveDir, fort, prog def load_iso(path): header = ["Age", "Log_T", "Log_g", "Log_L", "Log_R", "Y_core", "Z_core", "(Z/X)_surf", "L_H", "L_He", "M_He_core", "M_CO_core"] iso = pd.read_csv(path, delim_whitespace=True, names=header, comment='#') with open(path, 'r') as f: metadata_str = f.readline() Namematch = [x.strip('=') for x in re.findall("[^0-9=.\s]+=", metadata_str)] Valuematch = re.findall("-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?", metadata_str) metadata = dict() for i, (name, value) in enumerate(zip(Namematch, Valuematch)): if name != 'COMP': metadata[name] = float(value) return iso, metadata def get_calibration_result(iso, age): return iso.iloc[(iso['Age']-age).abs().idxmin()] def write_namelist(nml, path): nml.write(path) def clear_fort_files(path): for file in os.listdir(path): if os.path.islink(file): if file.startswith('fort'): print(f'deleting: {file}') os.remove(file) def symlink_file(src, dst): if not os.path.exists(src): with open(src, 'w') as f: pass os.symlink(src, dst) def config_fort_files(fort, run): clear_fort_files('.') runFort = fort.copy() runFort[14] += run runFort[19] += run.replace('.nml', '.track') runFort[20] += run.replace('.nml', '.short') runFort[37] += run.replace('.nml', '.iso') runFort[11] += run.replace('.nml', '.last') runFort[24] += run.replace('.nml', '.pmod') runFort[25] += run.replace('.nml', '.penv') runFort[26] += run.replace('.nml', '.patm') for fortKey in fort: print(f'linking {runFort[fortKey]} -> fort.{fortKey}') symlink_file(runFort[fortKey], f'fort.{fortKey}') def run_dsep(ic, fort, nml, saveDir, prog): print(f"Running DSEP with: {ic}") nml['control']['rsclx'] = ic[0] nml['control']['rsclz'] = ic[1] nml['control']['cmixla'] = [ic[2], ic[2]] nml['control']['endage'] = 4.57e9 filename = f"{ic[0]}-{ic[1]}-{ic[2]}.nml" if os.path.exists(f"{saveDir}/{filename}"): os.remove(f"{saveDir}/{filename}") write_namelist(nml, f"{saveDir}/{filename}") runFort = config_fort_files(fort, filename) os.system(f"{prog}/dsepX.master") iso, meta = load_iso('fort.37') cal_results = get_calibration_result(iso, 4.57e9) R = abs(cal_results['Log_R']) L = abs(cal_results['Log_L']) ZX = abs(cal_results['(Z/X)_surf'] - 0.0229) res = np.array([R, L, ZX]) print(f"Completed Run With: \n\tR:{res[0]}\n\tL:{res[1]}\n\tZX:{res[2]}") return res if __name__ == "__main__": parser = argparse.ArgumentParser(description="Automatically Calibrate Stellar Evolution Models to the Sun") parser.add_argument('namelist', type=str, help="namelist file to base calibraton off of") parser.add_argument("-X", type=float, help="Input X for RESCLX") parser.add_argument("-Z", type=float, help="Input Z for RESCLZ") parser.add_argument("-A", type=float, help="Input alpha for CMIXLA") parser.add_argument("-o", "--output", type=str, help="Directory to save DSEP result too", default="output") args = parser.parse_args() saveDir, fort, prog = config_env(args) nml = f90nml.read(args.namelist) if os.path.exists(saveDir): shutil.rmtree(saveDir) os.mkdir(saveDir) os.mkdir(f"{saveDir}/output") res = run_dsep([args.X, args.Z, args.A], fort, nml, saveDir, prog) with open(os.path.join(saveDir, "calibResults.csv"), "w") as f: f.write(f"{args.X},{args.Z},{args.A},{res[0]},{res[1]},{res[2]}\n") # X = np.linspace(0.7816, 0.72, 400) # Z = np.linspace(0.017, 0.018, 400) # A = np.linspace(1.8, 1.9, 400) # # z = Z[0] # a = A[0] # with open("SolarCalibrate-X-Results.csv", 'w') as f: # for x in X: # res = run_dsep([x, z, a], fort, nml, saveDir, prog) # f.write(f"{x},{z},{a},{res[0]},{res[1]},{res[2]}\n") # x = X[0] # a = A[0] # with open("SolarCalibrate-Z-Results.csv", 'w') as f: # for z in Z: # res = run_dsep([x, z, a], fort, nml, saveDir, prog) # f.write(f"{x},{z},{a},{res[0]},{res[1]},{res[2]}\n") # x = X[0] # z = Z[0] # with open("SolarCalibrate-A-Results.csv", 'w') as f: # for a in A: # res = run_dsep([x, z, a], fort, nml, saveDir, prog) # f.write(f"{x},{z},{a},{res[0]},{res[1]},{res[2]}\n")