def calc_LogU(nuin0, specin0, age, zmet, T, mstar=1.0, file_output=True): ''' Claculates the number of lyman ionizing photons for given a spectrum Input spectrum must be in ergs/s/Hz!! Q = int(Lnu/hnu dnu, nu_0, inf) , number of hydrogen ionizing photons mstar is in units of solar mass Rin is in units of cm-3 nh is in units of cm-3 ''' c = constants.c.cgs.value # cm/s h = constants.h.cgs.value # erg/s alpha = 2.5e-13*((T/(10**4))**(-0.85)) # cm3/s lam_0 = 911.6 * 1e-8 # Halpha wavelength in cm nh = cfg.par.HII_nh nuin = np.asarray(nuin0) specin = np.asarray(specin0) nu_0 = c / lam_0 inds, = np.where(nuin >= nu_0) hlam, hflu = nuin[inds], specin[inds] nu = hlam[::-1] f_nu = hflu[::-1] integrand = f_nu / (h * nu) Q = simps(integrand, x=nu)*mstar U = (np.log10((Q*nh*(alpha**2))/(4*np.pi*(c**3))))*(1./3.) if file_output: logu_diagnostic(U, Q, mstar, age, zmet) #Saves important parameters in an output file. return U
def newstars_gen(stars_list): global sp if sp is None: sp = fsps.StellarPopulation() #the newstars (particle type 4; so, for cosmological runs, this is all #stars) are calculated in a separate function with just one argument so that it is can be fed #into pool.map for multithreading. #sp = fsps.StellarPopulation() sp.params["tage"] = stars_list[0].age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[0].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model sp.params['gas_logu'] = cfg.par.gas_logu if cfg.par.FORCE_gas_logz == False: sp.params['gas_logz'] = np.log10(stars_list[0].metals / cfg.par.solar) else: sp.params['gas_logz'] = cfg.par.gas_logz #first figure out how many wavelengths there are spec = sp.get_spectrum(tage=stars_list[0].age, zmet=stars_list[0].fsps_zmet) nu = 1.e8 * constants.c.cgs.value / spec[0] nlam = len(nu) stellar_nu = np.zeros([nlam]) stellar_fnu = np.zeros([len(stars_list), nlam]) minage = 13 #Gyr for i in range(len(stars_list)): if stars_list[i].age < minage: minage = stars_list[i].age tesc_age = np.log10((minage + cfg.par.birth_cloud_clearing_age) * 1.e9) # Get the number of ionizing photons from SED #calculate the SEDs for new stars for i in range(len(stars_list)): sp.params["tage"] = stars_list[i].age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[i].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model if cfg.par.FORCE_gas_logz == False: LogZ = np.log10(stars_list[i].metals / cfg.par.solar) else: LogZ = cfg.par.gas_logz if cfg.par.CF_on == True: sp.params["dust_type"] = 0 sp.params["dust1"] = 1 sp.params["dust2"] = 0 sp.params["dust_tesc"] = tesc_age #sp = fsps.StellarPopulation(tage=stars_list[i].age,imf_type=2,sfh=0,zmet=stars_list[i].fsps_zmet) spec = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) f = spec[1] #Only including particles below the maximum age limit for calulating nebular emission if cfg.par.add_neb_emission and stars_list[ i].age <= cfg.par.HII_max_age: num_HII_clusters = int( np.floor((stars_list[i].mass / constants.M_sun.cgs.value) / (cfg.par.stellar_cluster_mass))) f = np.zeros(nlam) neb_file_output = cfg.par.neb_file_output sp.params["add_neb_emission"] = False spec = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) if cfg.par.FORCE_gas_logu: alpha = 2.5e-13 * ((cfg.par.HII_T / (10**4))**(-0.85)) LogU = cfg.par.gas_logu LogQ = np.log10((10**(3 * LogU)) * (36 * np.pi * (constants.c.cgs.value**3)) / ((alpha**2) * cfg.par.HII_nh)) Rin = ((3 * (10**LogQ)) / (4 * np.pi * (cfg.par.HII_nh**2) * alpha))**(1. / 3.) else: LogQ, Rin, LogU = calc_LogU( 1.e8 * constants.c.cgs.value / spec[0], spec[1] * constants.L_sun.cgs.value, cfg.par.HII_nh, cfg.par.HII_T, mstar=cfg.par.stellar_cluster_mass) if cfg.par.FORCE_logq: LogQ = cfg.par.source_logq if cfg.par.FORCE_inner_radius: Rin = cfg.par.inner_radius if neb_file_output: logu_diagnostic(LogQ, Rin, LogU, cfg.par.stellar_cluster_mass, stars_list[i].age, stars_list[i].fsps_zmet, append=True) neb_file_output = False sp.params['gas_logu'] = LogU sp.params['gas_logz'] = LogZ sp.params["add_neb_emission"] = True if cfg.par.use_cloudy_tables: lam_neb, spec_neb = sp.get_spectrum( tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) else: try: # Calculating ionizing photons again but for 1 Msun in order to scale the output for FSPS LogQ_1, Rin_1, LogU_1 = calc_LogU( 1.e8 * constants.c.cgs.value / spec[0], spec[1] * constants.L_sun.cgs.value, cfg.par.HII_nh, cfg.par.HII_T) spec_neb = get_nebular(spec[0], spec[1], cfg.par.HII_nh, LogQ, Rin, LogU, LogZ, LogQ_1, abund=cfg.par.neb_abund, useq=cfg.par.use_Q, clean_up=cfg.par.cloudy_cleanup) except ValueError as err: lam_neb, spec_neb = sp.get_spectrum( tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) f = spec_neb * num_HII_clusters stellar_nu[:] = 1.e8 * constants.c.cgs.value / spec[0] stellar_fnu[i, :] = f return stellar_fnu
def allstars_sed_gen(stars_list, cosmoflag, sp): #NOTE this part is just for the gadget simulations - this will #eventually become obviated as it gets passed into a function to #populate the stars_list with objects as we start to feed in new #types of simulation results. nstars = len(stars_list) #get just the wavelength array sp.params["tage"] = stars_list[0].age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[0].fsps_zmet sp.params["add_neb_emission"] = cfg.par.add_neb_emission sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model sp.params['gas_logu'] = cfg.par.gas_logu if cfg.par.FORCE_gas_logz == False: sp.params['gas_logz'] = np.log10(stars_list[0].metals / cfg.par.solar) else: sp.params['gas_logz'] = cfg.par.gas_logz ''' sp = fsps.StellarPopulation(tage=stars_list[0].age,imf_type=cfg.par.imf_type,pagb = cfg.par.pagb,sfh=0,zmet=stars_list[0].fsps_zmet, add_neb_emission = cfg.par.add_neb_emission, add_agb_dust_model=cfg.par.add_agb_dust_model) ''' spec = sp.get_spectrum(tage=stars_list[0].age, zmet=stars_list[0].fsps_zmet) nu = 1.e8 * constants.c.cgs.value / spec[0] nlam = len(nu) nprocesses = np.min( [cfg.par.n_processes, len(stars_list)] ) #the pool.map will barf if there are less star bins than process threads #initializing the logU file newly logu_diagnostic(None, None, None, None, None, None, append=False) #save the emission lines from the newstars# if cfg.par.add_neb_emission: calc_emline(stars_list) #initialize the process pool and build the chunks p = Pool(processes=nprocesses) nchunks = nprocesses chunk_start_indices = [] chunk_start_indices.append(0) #the start index is obviously 0 #this should just be int(nstars/nchunks) but in case nstars < nchunks, we need to ensure that this is at least 1 delta_chunk_indices = np.max([int(nstars / nchunks), 1]) print('delta_chunk_indices = ', delta_chunk_indices) for n in range(1, nchunks): chunk_start_indices.append(chunk_start_indices[n - 1] + delta_chunk_indices) ''' chunk_start_indices = list(np.fix(np.arange(0,nstars,np.fix(nstars/nchunks)))) #because this can result in too many chunks sometimes given the number of processors: chunk_start_indices = chunk_start_indices[0:nchunks] ''' print('Entering Pool.map multiprocessing for Stellar SED generation') list_of_chunks = [] for n in range(nchunks): stars_list_chunk = stars_list[ chunk_start_indices[n]:chunk_start_indices[n] + delta_chunk_indices] #if we're on the last chunk, we might not have the full list included, so need to make sure that we have that here if n == nchunks - 1: stars_list_chunk = stars_list[chunk_start_indices[n]::] list_of_chunks.append(stars_list_chunk) t1 = datetime.now() chunk_sol = p.map(newstars_gen, [arg for arg in list_of_chunks]) t2 = datetime.now() print('Execution time for SED generation in Pool.map multiprocessing = ' + str(t2 - t1)) stellar_fnu = np.zeros([nstars, nlam]) star_counter = 0 for i in range(nchunks): fnu_list = chunk_sol[ i] #this is a list of the stellar_fnu's returned by that chunk for j in range(len(fnu_list)): stellar_fnu[star_counter, :] = fnu_list[j, :] star_counter += 1 p.close() p.terminate() p.join() stellar_nu = nu if cosmoflag == False: #calculate the SED for disk stars; note, this gets calculated #whether or not disk stars actually exist. if they don't exist, #bogus values for the disk age and metallicity are assigned based #on whatever par.disk_stars_age and metallicity are. it's no big #deal since these SEDs don't end up getting added to the model in #source_creation. #note, even if there are no disk/bulge stars, these are still #created since they're completely based on input parameters in #parameters_master. they just won't get used at a later point #as there will be no disk/bulge star positions to add them to. #dust_tesc is an absolute value (not relative to min star age) as the ages of these stars are input by the user # Load in the metallicity legend fsps_metals = np.loadtxt(cfg.par.metallicity_legend) sp.params["tage"] = cfg.par.disk_stars_age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = cfg.par.disk_stars_metals sp.params["add_neb_emission"] = cfg.par.add_neb_emission sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model sp.params['gas_logu'] = cfg.par.gas_logu if cfg.par.FORCE_gas_logz == False: sp.params['gas_logz'] = np.log10( fsps_metals[cfg.par.disk_stars_metals] / cfg.par.solar) else: sp.params['gas_logz'] = cfg.par.gas_logz spec = sp.get_spectrum(tage=cfg.par.disk_stars_age, zmet=cfg.par.disk_stars_metals) disk_fnu = spec[1] #calculate the SED for bulge stars sp.params["tage"] = cfg.par.bulge_stars_age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = cfg.par.bulge_stars_metals sp.params["add_neb_emission"] = cfg.par.add_neb_emission sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model sp.params['gas_logu'] = cfg.par.gas_logu if cfg.par.FORCE_gas_logz == False: sp.params['gas_logz'] = np.log10( fsps_metals[cfg.par.bulge_stars_metals] / cfg.par.solar) else: sp.params['gas_logz'] = cfg.par.gas_logz spec = sp.get_spectrum(tage=cfg.par.bulge_stars_age, zmet=cfg.par.bulge_stars_metals) bulge_fnu = spec[1] else: #we have a cosmological simulation disk_fnu = [] bulge_fnu = [] total_lum_in_sed_gen = 0. for i in range(stellar_fnu.shape[0]): total_lum_in_sed_gen += np.absolute(np.trapz(stellar_fnu[i, :], x=nu)) print('[SED_gen: ] total_lum_in_sed_gen = ', total_lum_in_sed_gen) #return positions,disk_positions,bulge_positions,mass,stellar_nu,stellar_fnu,disk_masses,disk_fnu,bulge_masses,bulge_fnu return stellar_nu, stellar_fnu, disk_fnu, bulge_fnu
df_nu = o['nu'] df_chi = o['chi'] df.close() # add sources to hyperion stars_list, diskstars_list, bulgestars_list, reg = sg.star_list_gen( boost, dx, dy, dz, reg, ds, sp) nstars = len(stars_list) # figure out N_METAL_BINS: fsps_metals = np.array(sp.zlegend) N_METAL_BINS = len(fsps_metals) #initializing the nebular diagnostic file newly if cfg.par.add_neb_emission and cfg.par.NEB_DEBUG: logu_diagnostic(None, None, None, None, None, None, None, append=False) if cfg.par.add_neb_emission and cfg.par.dump_emlines: dump_emlines(None, None, append=False) if cfg.par.BH_SED == True: BH_source_add(m, reg, df_nu, boost) if cfg.par.FORCE_BINNED == False: m = direct_add_stars(df_nu, stars_list, diskstars_list, bulgestars_list, ds.cosmological_simulation, m, sp) # note - the generation of the SEDs is called within # add_binned_seds itself, unlike add_newstars, which requires # that sg.allstars_sed_gen() be called first. m = add_binned_seds(df_nu, stars_list, diskstars_list, bulgestars_list, ds.cosmological_simulation, m, sp)
# add sources to hyperion stars_list, diskstars_list, bulgestars_list, reg = sg.star_list_gen(boost, dx, dy, dz, reg, ds) nstars = len(stars_list) if cfg.par.BH_SED == True: BH_source_add(m, reg, df_nu, boost) # figure out N_METAL_BINS: fsps_metals = np.loadtxt(cfg.par.metallicity_legend) N_METAL_BINS = len(fsps_metals) #initializing the logU file newly if cfg.par.add_neb_emission: logu_diagnostic(None,None,None,None,None,None,None,None,append=False) if cfg.par.FORCE_BINNED == False: m = direct_add_stars(df_nu, stars_list, diskstars_list, bulgestars_list, ds.cosmological_simulation, m, sp) # note - the generation of the SEDs is called within # add_binned_seds itself, unlike add_newstars, which requires # that sg.allstars_sed_gen() be called first. m = add_binned_seds(df_nu, stars_list, diskstars_list,bulgestars_list, ds.cosmological_simulation, m, sp) #set the random seets if cfg.par.FORCE_RANDOM_SEED == False: m.set_seed(random.randrange(0,10000)*-1) else:
df_nu = o['nu'] df_chi = o['chi'] df.close() # add sources to hyperion stars_list, diskstars_list, bulgestars_list, reg = sg.star_list_gen(boost, dx, dy, dz, reg, ds, sp, m) nstars = len(stars_list) # figure out N_METAL_BINS: fsps_metals = np.array(sp.zlegend) N_METAL_BINS = len(fsps_metals) #initializing the nebular diagnostic file newly if cfg.par.add_neb_emission and cfg.par.NEB_DEBUG: logu_diagnostic(None,None,None,None,None,None,None,append=False) if cfg.par.add_neb_emission and cfg.par.dump_emlines: dump_emlines(None,append=False) if cfg.par.add_neb_emission and (cfg.par.SAVE_NEB_SEDS or cfg.par.add_DIG_neb): dump_NEB_SEDs(None, None, None, append=False) if cfg.par.BH_SED == True: BH_source_add(m, reg, df_nu, boost) if cfg.par.FORCE_BINNED == False: m = direct_add_stars(df_nu, stars_list, diskstars_list, bulgestars_list, ds.cosmological_simulation, m, sp) # note - the generation of the SEDs is called within # add_binned_seds itself, unlike add_newstars, which requires # that sg.allstars_sed_gen() be called first. m = add_binned_seds(df_nu, stars_list, diskstars_list,bulgestars_list, ds.cosmological_simulation, m, sp)
def newstars_gen(stars_list): global sp if sp is None: sp = fsps.StellarPopulation() #the newstars (particle type 4; so, for cosmological runs, this is all #stars) are calculated in a separate function with just one argument so that it is can be fed #into pool.map for multithreading. #sp = fsps.StellarPopulation() sp.params["tage"] = stars_list[0].age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[0].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model #first figure out how many wavelengths there are spec = sp.get_spectrum(tage=stars_list[0].age, zmet=stars_list[0].fsps_zmet) nu = 1.e8 * constants.c.cgs.value / spec[0] nlam = len(nu) stellar_nu = np.zeros([nlam]) stellar_fnu = np.zeros([len(stars_list), nlam]) mfrac = np.zeros([len(stars_list)]) minage = 13 #Gyr for i in range(len(stars_list)): if stars_list[i].age < minage: minage = stars_list[i].age tesc_age = np.log10((minage + cfg.par.birth_cloud_clearing_age) * 1.e9) # Get the number of ionizing photons from SED #calculate the SEDs for new stars for i in range(len(stars_list)): sp.params["tage"] = stars_list[i].age sp.params["imf_type"] = cfg.par.imf_type sp.params["imf1"] = cfg.par.imf1 sp.params["imf2"] = cfg.par.imf2 sp.params["imf3"] = cfg.par.imf3 sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[i].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model if cfg.par.CF_on == True: sp.params["dust_type"] = 0 sp.params["dust1"] = 1 sp.params["dust2"] = 0 sp.params["dust_tesc"] = tesc_age spec_noneb = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) f = spec_noneb[1] #NOTE: FSPS SSP/CSP spectra are scaled by *formed* mass, not current mass. i.e., the SFHs of the SSP/CSP are normalized such that 1 solar mass #is formed over the history. This means that the stellar spectra are normalized by the integral of the SFH =/= current #(surviving, observed, etc.) stellar mass. In simulations, we only know the current star particle mass. To get the formed mass for an SSP, #we generate the surviving mass fraction (sp.stellar_mass) to extrapolate the initial mass from the current mass, metallicity, and age #this 'mfrac' is used to scale the FSPS SSP luminosities in source_creation mfrac[i] = sp.stellar_mass pagb = cfg.par.add_pagb_stars and cfg.par.PAGB_min_age <= stars_list[ i].age <= cfg.par.PAGB_max_age young_star = cfg.par.add_young_stars and cfg.par.HII_min_age <= stars_list[ i].age <= cfg.par.HII_max_age if (cfg.par.add_neb_emission or cfg.par.use_cmdf) and (young_star or pagb): # For each star particle we break it into a collection or cluster of star particles which have the same property as the parent star particle # but their masses and ages follow a power-law distribution if use_cmdf and use_age_distribution are set to True respectively and it falls # under the constraints set in parameters_master. To do so we create 1D arrays that store the masses of each cluster (cluster_mass), num of particles # in that cluster (num_cluster), and age of that cluster (age_cluster). So for example (assuming default values), a 1e6 solar mass particle # will first be broken down into 6 particles with different masses ranging from 10^3.5 Msun to 10^5 Msun. Each of these particles will be further # broken down into 5 particles with their ages are distributed as per the age distribution. Thus in total, this one particle will be broken # down into 30 particles and these arrays will store the properties of all the 30 particles. This allows us to consider these as 30 individual # particles rest of the calculation and their fluxes are combined in end to get the final result for this one particle. cluster_mass = [ np.log10(stars_list[i].mass / constants.M_sun.cgs.value) ] num_clusters = [1] age_clusters = [stars_list[i].age] if stars_list[ i].mass / constants.M_sun.cgs.value > 10**cfg.par.cmdf_max_mass and cfg.par.use_cmdf: cluster_mass, num_clusters = cmdf( stars_list[i].mass / constants.M_sun.cgs.value, int(cfg.par.cmdf_bins), cfg.par.cmdf_min_mass, cfg.par.cmdf_max_mass, cfg.par.cmdf_beta) age_clusters = [] for k in range(len(cluster_mass)): age_clusters.append(stars_list[i].age) if cfg.par.use_age_distribution: num_clusters_cmdf = num_clusters cluster_mass_cmdf = cluster_mass num_clusters = [] cluster_mass = [] age_clusters = [] for k in range(len(cluster_mass_cmdf)): num, t = age_dist(num_clusters_cmdf[k], stars_list[i].age) rescale = np.sum(num_clusters_cmdf[k]) / np.sum(num) for l in range(len(num)): if num[l] == 0: continue num_clusters.append(num[l]) cluster_mass.append( np.log10((10**cluster_mass_cmdf[k]) * rescale)) age_clusters.append(t[l]) cluster_mass = np.array(cluster_mass) num_clusters = np.array(num_clusters) age_clusters = np.array(age_clusters) f = np.zeros(nlam) cloudy_nlam = len( np.genfromtxt(cfg.par.pd_source_dir + "/powderday/nebular_emission/data/refLines.dat", delimiter=',')) line_em = np.zeros([cloudy_nlam]) for j in range(len(cluster_mass)): num_HII_clusters = num_clusters[j] age = age_clusters[j] neb_file_output = cfg.par.NEB_DEBUG sp.params["add_neb_emission"] = False if cfg.par.add_neb_emission: # id_val = 0, 1, 2 for young stars, Post-AGB star and AGNs respectively. if young_star: id_val = 0 Rinner_per_Rs = cfg.par.HII_Rinner_per_Rs nh = cfg.par.HII_nh escape_fraction = cfg.par.HII_escape_fraction elif pagb: id_val = 1 Rinner_per_Rs = cfg.par.PAGB_Rinner_per_Rs nh = cfg.par.PAGB_nh escape_fraction = cfg.par.PAGB_escape_fraction if cfg.par.HII_alpha_enhance: #Setting Zstar based on Fe/H Fe = stars_list[i].all_metals[-1] # Gizmo metallicity structure, photospheric abundances from Asplund et al. 2009: # Photospheric mass fraction of H = 0.7381 # Photospheric mass fraction of Fe = 1.31e-3 # Converting from mass fraction to atomic fraction of Fe # Taking atmoic mass of H = 1.008u # Taking atmoic mass of Fe = 55.845u FeH = (Fe / 0.7381) * (1.008 / 55.845) # Solar atomic fraction of Fe. Calculated by substituting Fe = 1.31e-3 in the previous equation FeH_sol = 3.22580645e-5 Logzsol = np.log10(FeH / FeH_sol) sp1 = fsps.StellarPopulation(zcontinuous=1) sp1.params["tage"] = age sp1.params["imf_type"] = cfg.par.imf_type sp1.params["imf1"] = cfg.par.imf1 sp1.params["imf2"] = cfg.par.imf2 sp1.params["imf3"] = cfg.par.imf3 sp1.params["pagb"] = cfg.par.pagb sp1.params["sfh"] = 0 sp1.params["zmet"] = stars_list[i].fsps_zmet sp1.params["add_neb_emission"] = False sp1.params[ "add_agb_dust_model"] = cfg.par.add_agb_dust_model sp1.params["logzsol"] = Logzsol if cfg.par.CF_on == True: sp1.params["dust_type"] = 0 sp1.params["dust1"] = 1 sp1.params["dust2"] = 0 sp1.params["dust_tesc"] = tesc_age spec = sp1.get_spectrum(tage=age) mfrac_neb = sp1.stellar_mass else: spec = sp.get_spectrum(tage=age, zmet=stars_list[i].fsps_zmet) mfrac_neb = sp.stellar_mass alpha = 2.5e-13 # Recombination Rate (assuming T = 10^4 K) if cfg.par.FORCE_gas_logu[id_val]: LogU = cfg.par.gas_logu[id_val] LogQ = np.log10( (10**(3 * LogU)) * (36 * np.pi * (constants.c.cgs.value**3)) / ((alpha**2) * nh)) Rs = ((3 * (10**LogQ)) / (4 * np.pi * (nh**2) * alpha))**(1. / 3.) elif cfg.par.FORCE_logq[id_val]: LogQ = cfg.par.source_logq[id_val] Rs = ((3 * (10**LogQ)) / (4 * np.pi * (nh**2) * alpha))**(1. / 3.) LogU = np.log10( (10**LogQ) / (4 * np.pi * Rs * Rs * nh * constants.c.cgs.value)) else: LogQ = calc_LogQ(1.e8 * constants.c.cgs.value / spec[0], spec[1] * constants.L_sun.cgs.value, efrac=escape_fraction, mstar=10**cluster_mass[j], mfrac=mfrac_neb) Rs = ((3 * (10**LogQ)) / (4 * np.pi * (nh**2) * alpha))**(1. / 3.) LogU = np.log10( (10**LogQ) / (4 * np.pi * Rs * Rs * nh * constants.c.cgs.value) ) + cfg.par.gas_logu_init[id_val] LogQ = np.log10( (10**(3 * LogU)) * (36 * np.pi * (constants.c.cgs.value**3)) / ((alpha**2) * nh)) Rs = ((3 * (10**LogQ)) / (4 * np.pi * (nh**2) * alpha))**(1. / 3.) if cfg.par.FORCE_inner_radius[id_val]: Rin = cfg.par.inner_radius[id_val] else: Rin = Rinner_per_Rs * Rs if cfg.par.FORCE_gas_logz[id_val]: LogZ = cfg.par.gas_logz[id_val] else: LogZ = np.log10(stars_list[i].metals / cfg.par.solar) if neb_file_output: if cfg.par.use_cloudy_tables: Rin = 1.e19 # Rinner is fixed at 1.e19 cm for lookup tables if cfg.par.FORCE_inner_radius[id_val]: Rin = cfg.par.inner_radius[id_val] LogU = np.log10( (10**LogQ) / (4 * np.pi * Rin * Rin * nh * constants.c.cgs.value)) logu_diagnostic(LogQ, LogU, LogZ, Rs, 10**cluster_mass[j], num_HII_clusters, age, append=True) neb_file_output = False sp.params['gas_logu'] = LogU sp.params['gas_logz'] = LogZ sp.params["add_neb_emission"] = True if cfg.par.use_cloudy_tables: lam_neb, spec_neb = sp.get_spectrum( tage=age, zmet=stars_list[i].fsps_zmet) line_lum = sp.emline_luminosity wave_line = sp.emline_wavelengths else: try: # Calculating ionizing photons again but for 1 Msun in order to scale the output for FSPS LogQ_1 = calc_LogQ( 1.e8 * constants.c.cgs.value / spec[0], spec[1] * constants.L_sun.cgs.value, efrac=escape_fraction) #LogQ_1 = LogQ_1 + cfg.par.gas_logu_init[id_val] spec_neb, wave_line, line_lum = get_nebular( spec[0], spec[1], nh, stars_list[i].all_metals, logq=LogQ, radius=Rin, logu=LogU, logz=LogZ, logq_1=LogQ_1, Dust=False, abund=cfg.par.neb_abund[id_val], clean_up=cfg.par.cloudy_cleanup, index=id_val) except ValueError as err: # If the CLOUDY run crashes we switch to using lookup tables for young stars but throw an error for post-AGB stars. if young_star: print( "WARNING: Switching to using lookup tables pre-packed with FSPS to calculate nebular emission for this particle." ) print( "WARNING: The emission line fluxes repoted may not be accurate if the particle lies outside the range of the lookup table paramters." ) lam_neb, spec_neb = sp.get_spectrum( tage=age, zmet=stars_list[i].fsps_zmet) line_lum = sp.emline_luminosity wave_line = sp.emline_wavelengths else: print( "ERROR: Can't switch to using lookup tables." ) print( "ERROR: Please check the CLOUDY output file to figure out why the run was unsuccessful" ) raise ValueError('CLOUDY run was unsucessful') else: lam_neb, spec_neb = sp.get_spectrum( tage=age, zmet=stars_list[i].fsps_zmet) weight = num_HII_clusters * (10**cluster_mass[j]) / ( stars_list[i].mass / constants.M_sun.cgs.value) f = f + spec_neb * weight if cfg.par.add_neb_emission and cfg.par.dump_emlines: line_em = line_em + line_lum * weight if cfg.par.add_neb_emission and cfg.par.dump_emlines: #the stellar population returns the calculation in units of Lsun/1 Msun: https://github.com/dfm/python-fsps/issues/117#issuecomment-546513619 line_em = line_em * (stars_list[i].mass * u.g).to( u.Msun).value * 3.839e33 # Units: ergs/s line_em = np.append(line_em, age) dump_emlines(wave_line, line_em) stellar_nu[:] = 1.e8 * constants.c.cgs.value / spec[0] stellar_fnu[i, :] = f return stellar_fnu, mfrac
def newstars_gen(stars_list): global sp if sp is None: sp = fsps.StellarPopulation() #the newstars (particle type 4; so, for cosmological runs, this is all #stars) are calculated in a separate function with just one argument so that it is can be fed #into pool.map for multithreading. #sp = fsps.StellarPopulation() sp.params["tage"] = stars_list[0].age sp.params["imf_type"] = cfg.par.imf_type sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[0].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model #first figure out how many wavelengths there are spec = sp.get_spectrum(tage=stars_list[0].age,zmet=stars_list[0].fsps_zmet) nu = 1.e8*constants.c.cgs.value/spec[0] nlam = len(nu) stellar_nu = np.zeros([nlam]) stellar_fnu = np.zeros([len(stars_list),nlam]) minage = 13 #Gyr for i in range(len(stars_list)): if stars_list[i].age < minage: minage = stars_list[i].age tesc_age = np.log10((minage+cfg.par.birth_cloud_clearing_age)*1.e9) # Get the number of ionizing photons from SED #calculate the SEDs for new stars for i in range(len(stars_list)): sp.params["tage"] = stars_list[i].age sp.params["imf_type"] = cfg.par.imf_type sp.params["imf1"] = cfg.par.imf1 sp.params["imf2"] = cfg.par.imf2 sp.params["imf3"] = cfg.par.imf3 sp.params["pagb"] = cfg.par.pagb sp.params["sfh"] = 0 sp.params["zmet"] = stars_list[i].fsps_zmet sp.params["add_neb_emission"] = False sp.params["add_agb_dust_model"] = cfg.par.add_agb_dust_model if cfg.par.CF_on == True: sp.params["dust_type"] = 0 sp.params["dust1"] = 1 sp.params["dust2"] = 0 sp.params["dust_tesc"] = tesc_age spec_noneb = sp.get_spectrum(tage=stars_list[i].age,zmet=stars_list[i].fsps_zmet) f = spec_noneb[1] pagb = cfg.par.add_pagb_stars and cfg.par.PAGB_min_age <= stars_list[i].age <= cfg.par.PAGB_max_age young_star = cfg.par.add_young_stars and stars_list[i].age <= cfg.par.HII_max_age if (cfg.par.add_neb_emission or cfg.par.use_cmdf) and (young_star or pagb): # Cluster Mass Distribution Funtion is used only when the star particle's mass is gretaer than the maximum cluster mass and use_cmdf is True. if stars_list[i].mass/constants.M_sun.cgs.value > 10**cfg.par.cmdf_max_mass and cfg.par.use_cmdf: cluster_mass, num_clusters = cmdf(stars_list[i].mass/constants.M_sun.cgs.value,int(cfg.par.cmdf_bins),cfg.par.cmdf_min_mass, cfg.par.cmdf_max_mass, cfg.par.cmdf_beta) else: cluster_mass = [np.log10(stars_list[i].mass/constants.M_sun.cgs.value)] num_clusters = [1] f = np.zeros(nlam) cloudy_nlam = len(np.genfromtxt(cfg.par.pd_source_dir + "/powderday/nebular_emission/data/refLines.dat", delimiter=',')) line_em = np.zeros([cloudy_nlam]) for j in range(len(cluster_mass)): num_HII_clusters = num_clusters[j] neb_file_output = cfg.par.NEB_DEBUG sp.params["add_neb_emission"] = False if cfg.par.add_neb_emission: # id_val = 0, 1, 2 for young stars, Post-AGB star and AGNs respectively. if young_star: id_val = 0 Rinner_per_Rs = cfg.par.HII_Rinner_per_Rs nh = cfg.par.HII_nh escape_fraction = cfg.par.HII_escape_fraction elif pagb: id_val = 1 Rinner_per_Rs = cfg.par.PAGB_Rinner_per_Rs nh = cfg.par.PAGB_nh escape_fraction = cfg.par.PAGB_escape_fraction spec = sp.get_spectrum(tage=stars_list[i].age,zmet=stars_list[i].fsps_zmet) alpha = 2.5e-13 # Recombination Rate (assuming T = 10^4 K) if cfg.par.FORCE_gas_logu[id_val]: LogU = cfg.par.gas_logu[id_val] LogQ = np.log10((10 ** (3*LogU))*(36*np.pi*(constants.c.cgs.value**3))/((alpha**2)*nh)) Rs = ((3*(10 ** LogQ))/(4*np.pi*(nh**2)*alpha))**(1./3.) elif cfg.par.FORCE_logq[id_val]: LogQ = cfg.par.source_logq[id_val] Rs = ((3*(10 ** LogQ))/(4*np.pi*(nh**2)*alpha))**(1./3.) LogU = np.log10((10**LogQ)/(4*np.pi*Rs*Rs*nh*constants.c.cgs.value)) else: LogQ = calc_LogQ(1.e8*constants.c.cgs.value/spec[0], spec[1]*constants.L_sun.cgs.value , efrac=escape_fraction, mstar=10**cluster_mass[j]) Rs = ((3*(10 ** LogQ))/(4*np.pi*(nh**2)*alpha))**(1./3.) LogU = np.log10((10**LogQ)/(4*np.pi*Rs*Rs*nh*constants.c.cgs.value))+cfg.par.gas_logu_init[id_val] LogQ = np.log10((10 ** (3*LogU))*(36*np.pi*(constants.c.cgs.value**3))/((alpha**2)*nh)) Rs = ((3*(10 ** LogQ))/(4*np.pi*(nh**2)*alpha))**(1./3.) if cfg.par.FORCE_inner_radius[id_val]: Rin = cfg.par.inner_radius[id_val] else: Rin = Rinner_per_Rs*Rs if cfg.par.FORCE_gas_logz[id_val]: LogZ = cfg.par.gas_logz[id_val] else: LogZ = np.log10(stars_list[i].metals/cfg.par.solar) if neb_file_output: if cfg.par.use_cloudy_tables: Rin = 1.e19 # Rinner is fixed at 1.e19 cm for lookup tables logu_diagnostic(LogQ, LogU, LogZ, Rin, 10**cluster_mass[j], num_HII_clusters, stars_list[i].age, append=True) neb_file_output = False sp.params['gas_logu'] = LogU sp.params['gas_logz'] = LogZ sp.params["add_neb_emission"] = True if cfg.par.use_cloudy_tables: lam_neb, spec_neb = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) line_lum = sp.emline_luminosity wave_line = sp.emline_wavelengths else: try: # Calculating ionizing photons again but for 1 Msun in order to scale the output for FSPS LogQ_1 = calc_LogQ(1.e8 * constants.c.cgs.value / spec[0], spec[1] * constants.L_sun.cgs.value, efrac=escape_fraction) spec_neb, wave_line, line_lum = get_nebular(spec[0], spec[1], nh, LogQ, Rin, LogU, LogZ, LogQ_1, stars_list[i].all_metals, Dust=False, abund=cfg.par.neb_abund[id_val], clean_up = cfg.par.cloudy_cleanup, index=id_val) except ValueError as err: # If the CLOUDY run crashes we switch to using lookup tables for young stars but throw an error for post-AGB stars. if young_star: print ("WARNING: Switching to using lookup tables pre-packed with FSPS to calculate nebular emission for this particle.") print ("WARNING: The emission line fluxes repoted may not be accurate if the particle lies outside the range of the lookup table paramters.") lam_neb, spec_neb = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) line_lum = sp.emline_luminosity wave_line = sp.emline_wavelengths else: print ("ERROR: Can't switch to using lookup tables. ") print ("ERROR: Please check the CLOUDY output file to figure out why the run was unsuccessful" ) raise ValueError('CLOUDY run was unsucessful') else: lam_neb, spec_neb = sp.get_spectrum(tage=stars_list[i].age, zmet=stars_list[i].fsps_zmet) weight = num_HII_clusters*(10**cluster_mass[j])/(stars_list[i].mass/constants.M_sun.cgs.value) f = f + spec_neb*weight if cfg.par.add_neb_emission and cfg.par.dump_emlines: line_em = line_em + line_lum*weight if cfg.par.add_neb_emission and cfg.par.dump_emlines: #the stellar population returns the calculation in units of Lsun/1 Msun: https://github.com/dfm/python-fsps/issues/117#issuecomment-546513619 line_em = line_em * ((stars_list[i].mass*u.g).to(u.Msun).value) * (3.839e33) # Units: ergs/s line_em = np.append(line_em, stars_list[i].age) dump_emlines(wave_line, line_em) stellar_nu[:] = 1.e8*constants.c.cgs.value/spec[0] stellar_fnu[i,:] = f return stellar_fnu