def estimate_initial_ap(spectrum, precomputed_dir, resolution, linemasks, default_teff=5000., default_logg=2.5, default_MH=0.0, default_alpha=0.0, default_vmic=1.0, default_vmac=0.0, default_vsini=0.0, default_limb_darkening_coeff=0.00): """ Estimate the initial atmospheric parameters by using a pre-computed grid at a given resolution. The comparison will be based on the linemasks. """ reference_grid_filename = precomputed_dir + "/convolved_grid_%i.fits.gz" % resolution estimation_found = False if not os.path.exists(reference_grid_filename): logging.warning("Pre-computed grid does not exists for R = %i" % resolution) else: try: grid = fits.open(reference_grid_filename) grid_waveobs = np.asarray(grid['WAVELENGTHS'].data, dtype=float) resampled_spectrum = resample_spectrum(spectrum, grid_waveobs, method="linear") fsegment = create_wavelength_filter(resampled_spectrum, regions=linemasks) fsegment = np.logical_and(fsegment, resampled_spectrum['flux'] > 0.0) isegment = np.where(fsegment == True)[0] # http://en.wikipedia.org/wiki/Goodness_of_fit#Example residuals = grid['PRIMARY'].data[:, isegment] - resampled_spectrum[ 'flux'][isegment] chisq = np.sum((residuals)**2, axis=1) min_j = np.argmin(chisq) initial_teff, initial_logg, initial_MH, initial_alpha, initial_vmic, initial_vmac, initial_vsini, initial_limb_darkening_coeff = grid[ 'PARAMS'].data[min_j] estimation_found = True except Exception as e: print("Initial parameters could not be estimated") print(type(e), e.message) pass finally: grid.close() if estimation_found: return initial_teff, initial_logg, initial_MH, initial_alpha, initial_vmic, initial_vmac, initial_vsini, initial_limb_darkening_coeff else: return default_teff, default_logg, default_MH, default_alpha, default_vmic, default_vmac, default_vsini, default_limb_darkening_coeff
def apply_post_fundamental_effects(waveobs, fluxes, segments, macroturbulence = 3.0, vsini = 2.0, limb_darkening_coeff = 0.60, R=500000, vrad=(0,), verbose=0): """ Apply macroturbulence, rotation (visini), limb darkening coeff and resolution to already generated fundamental synthetic spectrum. """ # Avoid zero fluxes, set a minimum value so that when it is convolved it # changes. This way we reduce the impact of the following problem: # SPECTRUM + MARCS makes some strong lines to have zero fluxes (i.e. 854.21nm) zeros = np.where(fluxes <= 1.0e-10)[0] fluxes[zeros] = 1.0e-10 spectrum = create_spectrum_structure(waveobs, fluxes) spectrum.sort(order=['waveobs']) if (macroturbulence is not None and macroturbulence > 0) or (vsini is not None and vsini > 0): # Build spectrum with sampling uniform in velocity (required by vmac and vsini broadening): wave_base = spectrum['waveobs'][0] wave_top = spectrum['waveobs'][-1] velocity_step = __determine_velocity_step(spectrum) waveobs_uniform_in_velocity = _sampling_uniform_in_velocity(wave_base, wave_top, velocity_step) fluxes_uniform_in_velocity = np.interp(waveobs_uniform_in_velocity, spectrum['waveobs'], spectrum['flux'], left=0.0, right=0.0) # Apply broadening #fluxes_uniform_in_velocity = __vsini_broadening_limbdarkening2(waveobs_uniform_in_velocity, fluxes_uniform_in_velocity, velocity_step, vsini, limb_darkening_coeff) fluxes_uniform_in_velocity = __vsini_broadening_limbdarkening(fluxes_uniform_in_velocity, velocity_step, vsini, limb_darkening_coeff) fluxes_uniform_in_velocity = __vmac_broadening(fluxes_uniform_in_velocity, velocity_step, macroturbulence) # Resample to origin wavelength grid fluxes = np.interp(spectrum['waveobs'], waveobs_uniform_in_velocity, fluxes_uniform_in_velocity, left=0.0, right=0.0) spectrum['flux'] = fluxes if R is not None and R > 0: # Convolve (here it is not needed to be with a sampling uniform in velocity, the function is capable of dealing with that) fluxes = convolve_spectrum(spectrum, R, from_resolution=None, frame=None)['flux'] # Make sure original zeros are set to 1.0 and not modified by the previous broadening operations fluxes[zeros] = 1.0e-10 if type(vrad) not in (tuple, list, np.ndarray): raise Exception("Velocity should be an array") if np.any(np.asarray(vrad) != 0): if len(vrad) != len(segments): raise Exception("Velocity should be an array with as many numbers as segments when segments are provided") modified = waveobs < 0 # All to false for velocity, segment in zip(vrad, segments): wave_base = segment['wave_base'] wave_top = segment['wave_top'] wfilter = np.logical_and(waveobs >= wave_base, waveobs <= wave_top) modified = np.logical_or(modified, wfilter) spectrum = create_spectrum_structure(waveobs[wfilter], fluxes[wfilter]) spectrum = correct_velocity(spectrum, velocity) spectrum = resample_spectrum(spectrum, waveobs[wfilter], method="linear", zero_edges=True) fluxes[wfilter] = spectrum['flux'] fluxes[~modified] = 1. return fluxes
def generate_spectrum(waveobs, atmosphere_layers, teff, logg, MH, alpha, linelist, isotopes, abundances, fixed_abundances, microturbulence_vel, verbose=0, atmosphere_layers_file=None, linelist_file=None, regions=None, R=None, macroturbulence=None, vsini=None, limb_darkening_coeff=None, use_molecules=False, tmp_dir=None, timeout=1800): if not is_turbospectrum_support_enabled(): raise Exception("Turbospectrum support is not enabled") ispec_dir = os.path.dirname(os.path.realpath(__file__)) + "/../../" turbospectrum_dir = ispec_dir + "/synthesizer/turbospectrum/" turbospectrum_data = turbospectrum_dir + "/DATA/" turbospectrum_bsyn_lu = turbospectrum_dir + "bin/bsyn_lu" molecules_dir = ispec_dir + "input/linelists/turbospectrum/molecules/" if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1,), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top else: global_wave_base = np.min(regions['wave_base']) global_wave_top = np.max(regions['wave_top']) # TODO: decide how to stablish wave_step waveobs = waveobs.copy() waveobs.sort() #wave_step = waveobs[1] - waveobs[0] # 0.001 wave_step = np.max((0.001, np.min(waveobs[1:] - waveobs[:-1]))) # Limit linelist linelist = _filter_linelist(linelist, regions) # Turbospectrum is not going to scale the abundances because we are indicating # our abundances in the input and that overrides any other prescription, thus # we have to manually scale (but do not change Hydrogen and Helium!) atom_abundances = abundances[abundances['code'] <= 92] if len(atom_abundances) != 92: raise Exception("No abundances for all 92 elements!") efilter = np.logical_and(atom_abundances['code'] != 1, atom_abundances['code'] != 2) atom_abundances['Abund'][efilter] += MH # Update abundances with the ones that should be fixed to a given value and # not affected by metallicity scalation if fixed_abundances is not None and len(fixed_abundances) > 0: atom_abundances = atom_abundances.copy() for fixed_abundance in fixed_abundances: index = np.where(atom_abundances['code'] == fixed_abundance['code'])[0] atom_abundances['Abund'][index] = fixed_abundance['Abund'] radius = atmosphere_layers[0][-1] nvalues = len(atmosphere_layers[0]) if nvalues == 11 and radius > 2.0: # Compare to 2.0 instead of 1.0 to avoid floating point imprecisions logging.info("Spherical model atmosphere with radius %.2e cm" % (radius)) spherical_model = True else: spherical_model = False # Turbospectrum cannot compute in a single run a big chunk of wavelength so # we split the computation in several pieces max_segment = 100. # nm if (global_wave_top - global_wave_base)/wave_step > max_segment/wave_step: segment_wave_base = np.arange(global_wave_base, global_wave_top, max_segment) segments = np.recarray((len(segment_wave_base),), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'] = segment_wave_base segments['wave_top'] = segment_wave_base + max_segment - wave_step segments['wave_top'][-1] = global_wave_top # Last segment should not over pass the original global limits else: segments = np.recarray((1,), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'][0] = global_wave_base segments['wave_top'][0] = global_wave_top remove_tmp_atm_file = False remove_tmp_linelist_file = False if atmosphere_layers_file is None: remove_tmp_atm_file = True atmosphere_layers_file = write_atmosphere(atmosphere_layers, teff, logg, MH, atmosphere_filename=atmosphere_layers_file, code="turbospectrum", tmp_dir=tmp_dir) is_marcs_model = len(atmosphere_layers[0]) == 11 opacities_file = calculate_opacities(atmosphere_layers_file, atom_abundances, MH, microturbulence_vel, global_wave_base-10, global_wave_top+10, wave_step, verbose=verbose, opacities_filename=None, tmp_dir=tmp_dir, is_marcs_model=is_marcs_model) if linelist_file is None: remove_tmp_linelist_file = True linelist_filename = write_atomic_linelist(linelist, linelist_filename=linelist_file, code="turbospectrum", tmp_dir=tmp_dir) else: linelist_filename = linelist_file synth_fluxes = [] synth_waveobs = [] non_positive_result = False for segment in segments: wave_base = segment['wave_base'] wave_top = segment['wave_top'] # Temporary file out = tempfile.NamedTemporaryFile(mode="wt", delete=False, dir=tmp_dir, encoding='utf-8') out.close() synth_spectrum_filename = out.name # Temporary dir tmp_execution_dir = tempfile.mkdtemp(dir=tmp_dir) os.symlink(turbospectrum_data, tmp_execution_dir+"/DATA") os.symlink(molecules_dir, tmp_execution_dir+"/molecules") previous_cwd = os.getcwd() os.chdir(tmp_execution_dir) command = turbospectrum_bsyn_lu command_input = "'LAMBDA_MIN:' '"+str(wave_base*10.)+"'\n" command_input += "'LAMBDA_MAX:' '"+str(wave_top*10.)+"'\n" command_input += "'LAMBDA_STEP:' '"+str(wave_step*10.)+"'\n" for region in regions: command_input += "'LAMBDA_MIN:' '"+str(region['wave_base']*10.)+"'\n" command_input += "'LAMBDA_MAX:' '"+str(region['wave_top']*10.)+"'\n" command_input += "'INTENSITY/FLUX:' 'Flux'\n" command_input += "'COS(THETA) :' '1.00'\n" command_input += "'ABFIND :' '.false.'\n" if is_marcs_model: command_input += "'MARCS-FILE :' '.true.'\n" else: command_input += "'MARCS-FILE :' '.false.'\n" command_input += "'MODELOPAC:' '"+opacities_file+"'\n" command_input += "'RESULTFILE :' '"+synth_spectrum_filename+"'\n" #command_input += "'METALLICITY:' '"+str(MH)+"'\n" command_input += "'METALLICITY:' '0.00'\n" # We have done the abundance changes already command_input += "'ALPHA/Fe :' '0.00'\n" command_input += "'HELIUM :' '0.00'\n" command_input += "'R-PROCESS :' '0.00'\n" command_input += "'S-PROCESS :' '0.00'\n" #command_input += "'INDIVIDUAL ABUNDANCES:' '1'\n" #command_input += "3 1.05\n" command_input += "'INDIVIDUAL ABUNDANCES:' '"+str(len(atom_abundances))+"'\n" for atom_abundance in atom_abundances: abund = 12.036 + atom_abundance['Abund'] # From SPECTRUM format to Turbospectrum command_input += "%i %.2f\n" % (atom_abundance['code'], abund) #command_input += "'ISOTOPES : ' '2'\n" #command_input += "3.006 0.075\n" #command_input += "3.007 0.925\n" command_input += "'ISOTOPES : ' '"+str(len(isotopes))+"'\n" for isotope in isotopes: command_input += "%i.%03i %.3f\n" % (isotope['atomic_code'], isotope['mass_number'], isotope['relative_abundance_in_the_solar_system']) num_molecules_files = 0 molecules = "" if use_molecules: for filename in glob.glob("molecules/*.bsyn"): name, file_wave_base, file_wave_top = re.match("(.*)_(\d+)-(\d+)\.bsyn", os.path.basename(filename)).groups() file_wave_base = float(file_wave_base) file_wave_top = float(file_wave_top) if (file_wave_base >= wave_base and file_wave_top <= wave_top) or \ (wave_base >= file_wave_base and wave_base <= file_wave_top ) or \ (wave_top >= file_wave_base and wave_top <= file_wave_top ): molecules += filename + "\n" num_molecules_files += 1 command_input += "'NFILES :' '%i'\n" % (2 + num_molecules_files) command_input += molecules else: command_input += "'NFILES :' '2'\n" command_input += "DATA/Hlinedata\n" command_input += linelist_filename + "\n" if spherical_model: command_input += "'SPHERICAL:' 'T'\n" else: command_input += "'SPHERICAL:' 'F'\n" command_input += " 30\n" command_input += " 300.00\n" command_input += " 15\n" command_input += " 1.30\n" # If timeout command exists in PATH, then use it to control turbospectrum execution time (it might get blocked sometimes) if which("timeout") is not None: command = "timeout %i " % (timeout) + command if verbose == 1: proc = subprocess.Popen(command.split(), stdin=subprocess.PIPE) else: proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error("A timeout has occurred in the turbospectrum synthesis process.") raise Exception("Timeout: Synthesis failed!") os.chdir(previous_cwd) try: data = np.loadtxt(synth_spectrum_filename) if len(data) == 0: raise Exception() except: print(out) sys.stdout.flush() raise Exception("Synthesis failed!") #synth_waveobs_tmp = np.linspace(wave_base, wave_top, len(synth_fluxes_tmp)) # Not exactly identical to turbospectrum wavelengths synth_waveobs_tmp = data[:,0] / 10. # Armstrong to nm synth_waveobs = np.hstack((synth_waveobs, synth_waveobs_tmp)) synth_fluxes_tmp = data[:,1] if len(synth_fluxes_tmp) > 1 and np.isnan(synth_fluxes_tmp[-1]): synth_fluxes_tmp[-1] = synth_fluxes_tmp[-2] # Turbospectrum bug with gfortran, last flux is always NaN synth_fluxes = np.hstack((synth_fluxes, synth_fluxes_tmp)) os.remove(synth_spectrum_filename) shutil.rmtree(tmp_execution_dir) os.remove(opacities_file) if remove_tmp_atm_file: os.remove(atmosphere_layers_file) if remove_tmp_linelist_file: os.remove(linelist_filename) synth_spectrum = create_spectrum_structure(synth_waveobs, synth_fluxes) synth_spectrum.sort(order=['waveobs']) # Make sure we return the number of expected fluxes if not np.array_equal(synth_spectrum['waveobs'], waveobs): synth_spectrum = resample_spectrum(synth_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0,) synth_spectrum['flux'] = apply_post_fundamental_effects(synth_spectrum['waveobs'], synth_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) return synth_spectrum['flux']
def __execute_spectrum(waveobs, atmosphere_layers, teff, logg, MH, alpha, linelist, isotopes, abundances, fixed_abundances, microturbulence_vel, verbose=0, atmosphere_layers_file=None, linelist_file=None, molecules_files=None, regions=None, R=None, macroturbulence=None, vsini=None, limb_darkening_coeff=None, tmp_dir=None, timeout=1800): """ Unused function. It demonstrates how to call spectrum as an external program (similar to MOOG, synthe and turbospectrum) instead of as an integrated python module. """ if not is_spectrum_support_enabled(): raise Exception("spectrum support is not enabled") if len(linelist) > 1000000: raise Exception("Linelist too big for SPECTRUM: %i (limit 1000000)" % (len(linelist))) if fixed_abundances is None: # No fixed abundances fixed_abundances = np.recarray((0, ), dtype=[('code', int), ('Abund', float), ('element', '|U30')]) ispec_dir = os.path.dirname(os.path.realpath(__file__)) + "/../../" spectrum_dir = ispec_dir + "synthesizer/spectrum/" spectrum_executable = spectrum_dir + "spectrum" if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top else: global_wave_base = np.min(regions['wave_base']) global_wave_top = np.max(regions['wave_top']) # TODO: decide how to stablish wave_step waveobs = waveobs.copy() waveobs.sort() #wave_step = waveobs[1] - waveobs[0] # 0.001 wave_step = np.max((0.001, np.min(waveobs[1:] - waveobs[:-1]))) # Limit linelist linelist = _filter_linelist(linelist, regions) atmosphere_layers_file = write_atmosphere(atmosphere_layers, teff, logg, MH, code="spectrum", tmp_dir=tmp_dir) abundances_filename = write_solar_abundances(abundances, tmp_dir=tmp_dir) fixed_abundances_filename = write_fixed_abundances(fixed_abundances, tmp_dir=tmp_dir) isotope_filename = write_isotope_data(isotopes, tmp_dir=tmp_dir) nlayers = len(atmosphere_layers) synth_fluxes = [] synth_waveobs = [] for i, region in enumerate(regions): # It is better not to spectrum in a single run a big chunk of wavelength so # we split the computation in several pieces max_segment = 100. # nm if (region['wave_top'] - region['wave_base']) / wave_step > max_segment / wave_step: segment_wave_base = np.arange(region['wave_base'], region['wave_top'], max_segment) segments = np.recarray((len(segment_wave_base), ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'] = segment_wave_base segments['wave_top'] = segment_wave_base + max_segment - wave_step segments['wave_top'][-1] = region[ 'wave_top'] # Last segment should not over pass the original global limits else: segments = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'][0] = region['wave_base'] segments['wave_top'][0] = region['wave_top'] for segment in segments: wave_base = segment['wave_base'] wave_top = segment['wave_top'] # Provide some margin or near-by deep lines might be omitted margin = 2. # 2 nm wfilter = np.logical_and(linelist['wave_nm'] >= wave_base - margin, linelist['wave_nm'] <= wave_top + margin) linelist_filename = write_atomic_linelist(linelist[wfilter], code="spectrum", tmp_dir=tmp_dir) tmp_spec_filename = tempfile.mktemp() + str( int(random.random() * 100000000)) spectrum_switches = "aixn" # custom abundances + isotopes + fixed abudnances + silence command = spectrum_executable + " " + spectrum_switches command_input = "{}\n".format(atmosphere_layers_file) command_input += "{}\n".format(linelist_filename) command_input += "{}\n".format(abundances_filename) command_input += "{}\n".format(isotope_filename) command_input += "{}\n".format(fixed_abundances_filename) command_input += "{}\n".format(tmp_spec_filename) command_input += "{}\n".format(microturbulence_vel) command_input += "{},{}\n".format(wave_base * 10., wave_top * 10.) command_input += "{}\n".format(wave_step * 10.) # If timeout command exists in PATH, then use it to control spectrum execution time which_timeout = which("timeout") if which_timeout is not None: command = "timeout %i " % (timeout) + command #if verbose == 1: #proc = subprocess.Popen(command.split(), stdin=subprocess.PIPE) #else: proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode #print out if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the spectrum process.") raise Exception("Timeout: synthesis failed!") try: data = np.loadtxt(tmp_spec_filename) except: #print out sys.stdout.flush() raise Exception("synthesis failed!") synth_waveobs_tmp = data[:, 0] / 10. # Armstrong to nm # NOTE: We provided an artificially bigger wavelength range, so that spectrum will consider near-by deep lines # Now we correct that and we reduce the wavelength range to the correct one: wfilter = np.logical_and(synth_waveobs_tmp >= wave_base, synth_waveobs_tmp <= wave_top) synth_waveobs = np.hstack( (synth_waveobs, synth_waveobs_tmp[wfilter])) synth_fluxes_tmp = data[:, 1] synth_fluxes = np.hstack((synth_fluxes, synth_fluxes_tmp[wfilter])) os.remove(linelist_filename) os.remove(tmp_spec_filename) os.remove(atmosphere_layers_file) os.remove(abundances_filename) os.remove(fixed_abundances_filename) os.remove(isotope_filename) synth_spectrum = create_spectrum_structure(synth_waveobs, synth_fluxes) synth_spectrum.sort(order=['waveobs']) # Make sure we return the number of expected fluxes if not np.array_equal(synth_spectrum['waveobs'], waveobs): synth_spectrum = resample_spectrum(synth_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0, ) synth_spectrum['flux'] = apply_post_fundamental_effects(synth_spectrum['waveobs'], synth_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) return synth_spectrum['flux']
def generate_spectrum(waveobs, atmosphere_layers, teff, logg, MH, alpha, linelist, isotopes, abundances, fixed_abundances, microturbulence_vel, verbose=0, atmosphere_layers_file=None, linelist_file=None, molecules_files=None, regions=None, R=None, macroturbulence=None, vsini=None, limb_darkening_coeff=None, tmp_dir=None, timeout=1800): if not is_synthe_support_enabled(): raise Exception("Synthe support is not enabled") ispec_dir = os.path.dirname(os.path.realpath(__file__)) + "/../../" atmos_dir = ispec_dir + "/synthesizer/atmos/" system_64bits = sys.maxsize > 2**32 if system_64bits: xnfpelsyn_executable = atmos_dir + "bin.amd64/xnfpelsyn.exe" synbeg_executable = atmos_dir + "bin.amd64/synbeg.exe" #rline2.exe # It does not exist in the source code! rgfallinesnew_executable = atmos_dir + "bin.amd64/rgfalllinesnew.exe" rmolescasc_executable = atmos_dir + "bin.amd64/rmolecasc.exe" synthe_executable = atmos_dir + "bin.amd64/synthe.exe" spectrv_executable = atmos_dir + "bin.amd64/spectrv.exe" rotate_executable = atmos_dir + "bin.amd64/rotate.exe" syntoascanga_executable = atmos_dir + "bin.amd64/syntoascanga.exe" else: logging.warning("*************************************************") logging.warning("Synthe does not work properly in 32 bits systems!") logging.warning("*************************************************") xnfpelsyn_executable = atmos_dir + "bin.ia32/xnfpelsyn.exe" synbeg_executable = atmos_dir + "bin.ia32/synbeg.exe" #rline2.exe # It does not exist in the source code! rgfallinesnew_executable = atmos_dir + "bin.ia32/rgfalllinesnew.exe" rmolescasc_executable = atmos_dir + "bin.ia32/rmolecasc.exe" synthe_executable = atmos_dir + "bin.ia32/synthe.exe" spectrv_executable = atmos_dir + "bin.ia32/spectrv.exe" rotate_executable = atmos_dir + "bin.ia32/rotate.exe" syntoascanga_executable = atmos_dir + "bin.ia32/syntoascanga.exe" atmos_molecules = atmos_dir + "lines/molecules.dat" atmos_helium = atmos_dir + "lines/he1tables.dat" atmos_continua = atmos_dir + "lines/continua.dat" if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top else: global_wave_base = np.min(regions['wave_base']) global_wave_top = np.max(regions['wave_top']) # TODO: decide how to stablish wave_step waveobs = waveobs.copy() waveobs.sort() #wave_step = waveobs[1] - waveobs[0] # 0.001 wave_step = np.max((0.001, np.min(waveobs[1:] - waveobs[:-1]))) # Limit linelist linelist = _filter_linelist(linelist, regions) # Update abundances with the ones that should be fixed to a given value and # not affected by metallicity scalation if fixed_abundances is not None and len(fixed_abundances) > 0: abundances = abundances.copy() for fixed_abundance in fixed_abundances: index = np.where(abundances['code'] == fixed_abundance['code'])[0] abundances['Abund'][index] = fixed_abundance['Abund'] - MH synth_fluxes = [] synth_waveobs = [] for i, region in enumerate(regions): # It is better not to synthesize in a single run a big chunk of wavelength so # we split the computation in several pieces max_segment = 100. # nm if (region['wave_top'] - region['wave_base']) / wave_step > max_segment / wave_step: segment_wave_base = np.arange(region['wave_base'], region['wave_top'], max_segment) segments = np.recarray((len(segment_wave_base), ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'] = segment_wave_base segments['wave_top'] = segment_wave_base + max_segment - wave_step segments['wave_top'][-1] = region[ 'wave_top'] # Last segment should not over pass the original global limits else: segments = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'][0] = region['wave_base'] segments['wave_top'][0] = region['wave_top'] for segment in segments: wave_base = segment['wave_base'] wave_top = segment['wave_top'] tmp_execution_dir = tempfile.mkdtemp(dir=tmp_dir) os.symlink(atmos_molecules, tmp_execution_dir + "/fort.2") os.symlink(atmos_helium, tmp_execution_dir + "/fort.18") os.symlink(atmos_continua, tmp_execution_dir + "/fort.17") previous_cwd = os.getcwd() os.chdir(tmp_execution_dir) # XNFPELSYN pretabulates continuum opacities and number densities for # different chemical elements and writes them to fort.10 command = xnfpelsyn_executable command_input = "SURFACE INTENSI 17 1.,.9,.8,.7,.6,.5,.4,.3,.25,.2,.15,.125,.1,.075,.05,.025,.01\n" #command_input = "SURFACE FLUX\n" command_input += "ITERATIONS 1 PRINT 2 PUNCH 2\n" command_input += "CORRECTION OFF\n" command_input += "PRESSURE OFF\n" command_input += "READ MOLECULES\n" command_input += "MOLECULES ON\n" command_input += "TEFF %.0f GRAVITY %.5f LTE\n" % (teff, logg) command_input += "TITLE ISPEC\n" command_input += " OPACITY IFOP 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 0 0 0\n" mixing_length_param = 1.25 command_input += " CONVECTION ON %.2f TURBULENCE OFF 0.00 0.00 0.00 0.00\n" % ( mixing_length_param) abundance_scale = 10**MH ## Fraction in number of the total atoms from WIDTH example: # hydrogen_number_atom_fraction = 0.92080 # helium_number_atom_fraction = 0.07837 # Fraction in mass from MARCS model (X=Hydrogen, Y=Helium, Z=Metals): # 0.74732 0.25260 7.81E-05 are X, Y and Z, 12C/13C=89 (=solar) # Transform to number fraction: # Y = 0.25260 / (4-3*0.25260) = 0.07791 # X = 1 - Y = 0.92209 #hydrogen_number_atom_fraction = 0.92209 #helium_number_atom_fraction = 0.07791 hydrogen_number_atom_fraction = 0.92080 # It does not seem to have any effect on synthesis helium_number_atom_fraction = 0.07837 # It does not seem to have any effect on synthesis command_input += "ABUNDANCE SCALE %.5f ABUNDANCE CHANGE 1 %.5f 2 %.5f\n" % ( abundance_scale, hydrogen_number_atom_fraction, helium_number_atom_fraction) # command_input += " ABUNDANCE CHANGE 3 -10.99 4 -10.66 5 -9.34 6 -3.65 7 -4.26 8 -3.38\n" # command_input += " ABUNDANCE CHANGE 9 -7.48 10 -4.20 11 -5.87 12 -4.51 13 -5.67 14 -4.53\n" atom_abundances = abundances[np.logical_and( abundances['code'] > 2, abundances['code'] <= 92)] num_added_abundances = 0 for atom_abundance in atom_abundances: # abund = 12.036 + atom_abundance['Abund'] # From SPECTRUM format to Turbospectrum #command_input += " ABUNDANCE CHANGE %i %.2f\n" % (atom_abundance['code'], abund) if num_added_abundances == 0: command_input += " ABUNDANCE CHANGE" abund = atom_abundance['Abund'] command_input += " %2i %6.2f" % (atom_abundance['code'], abund) num_added_abundances += 1 if num_added_abundances == 6: command_input += "\n" num_added_abundances = 0 command_input += " ABUNDANCE CHANGE 93 -20.00 94 -20.00 95 -20.00 96 -20.00 97 -20.00 98 -20.00 \n" command_input += " ABUNDANCE CHANGE 99 -20.00 \n" command_input += "READ DECK6 %i RHOX,T,P,XNE,ABROSS,ACCRAD,VTURB\n" % ( len(atmosphere_layers)) #command_input += " 6.12960183E-04 3686.1 1.679E+01 2.580E+09 2.175E-04 4.386E-02 1.000E+05\n" #atm_kurucz.write("%.8e %.1f %.3e %.3e %.3e %.3e %.3e" % (rhox[i], temperature[i], pgas[i], xne[i], abross[i], accrad[i], vturb[i]) ) #command_input += "\n".join([" %.8E %8.1f %.3E %.3E %.3E %.3E %.3E" % (layer[0], layer[1], layer[2], layer[3], layer[4], layer[5], layer[6]) for layer in atmosphere_layers]) #command_input += "\n".join([" %.8E %8.1f %.3E %.3E %.3E %.3E %.3E" % (layer[0], layer[1], layer[2], layer[3], layer[4], layer[5], 1.0e5) for layer in atmosphere_layers]) # Force microturbulence in model to zero because later it will be used to add to the real microturbulence that we want: command_input += "\n".join([ " %.8E %8.1f %.3E %.3E %.3E %.3E %.3E" % (layer[0], layer[1], layer[2], layer[3], layer[4], layer[5], 0.0e5) for layer in atmosphere_layers ]) command_input += "\nPRADK 1.4878E+00\n" command_input += "READ MOLECULES\n" command_input += "MOLECULES ON\n" command_input += "BEGIN ITERATION 15 COMPLETED\n" model = command_input # If timeout command exists in PATH, then use it to control synthe execution time which_timeout = which("timeout") if which_timeout is not None: command = "timeout %i " % (timeout) + command # Never verbose because WIDTH's output is printed on stdout (not saved on a file) # and it is needed by iSpec #if verbose == 1: #proc = subprocess.Popen(command.split(), stdin=subprocess.PIPE) #else: proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode #print out if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") # synbeg: Reads the fundamental parameters of the process: wavelength range, # resolution of the final spectrum before degrading, whether unclassified # (pre-dicted) lines should be included, etc., and writes them to fort.93 command = synbeg_executable # If timeout command exists in PATH, then use it to control synthe execution time if which_timeout is not None: command = "timeout %i " % (timeout) + command # microturbulence is added by summing the squares to the microturbulence indicated in the mode atmosphere, which we have forced to zero before turbv = microturbulence_vel # NOTE: For the wavelength range, it does not work as other codes... it will ignore any lines outside the provided range # even if they are strong near-by lines that will affect the region of interest. So we increase the region to synthesize # and we will cut later. # Provide some margin or near-by deep lines might be omitted margin = 2. # 2 nm #command_input = "AIR %-9.1f %-9.1f 600000. %9.2f 0 30 .0001 1 0\n" % (wave_base-margin, wave_top+margin, turbv) command_input = "AIR %-9.1f %-9.1f 600000. %9.2f 0 10 .001 0 0\n" % ( wave_base - margin, wave_top + margin, turbv) command_input += "AIRorVAC WLBEG WLEND RESOLU TURBV IFNLTE LINOUT CUTOFF NREAD\n" #AIR indicates that the wavelengths are in AIR. VAC would provide vacuum wavelengths #WLBEG and WLEND are the starting and ending points of the synthesis, in nanometers #RESOLU is the resolution at which the calculation is performed. Practically, SYNTHE calculates the transfer through the atmosphere at wavelength intervals with such spacing. Of course, reducing the resolution will lead to a faster calculation, but also to a poorer sampling of the radiative transfer through the atmosphere. We thus suggest not to go below a resolution of 100000. This value is adequate for comparison with high resolution observed spectra. #TURBV is the microturbulence we want SYNTHE to add to the one in the atmosphere model. Since microturbulence is added by summing the squares, and we have a VTURB=1 model, we need to add 1.67 to obtain the final 1.95 km/s. #IFNLTE is set to 0 because we want a LTE calculation #LINOUT if it is negative, line data are not saved and it speeds up the process #CUTOFF is used to keep the weakest transitions out of the output files. With this setting, any absorption subtracting at its center less than 1/10000 of the intensity will be cut off. proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode #print out if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") if linelist_file is None: # Provide some margin or near-by deep lines might be omitted margin = 2. # 2 nm wfilter = np.logical_and( linelist['wave_nm'] >= wave_base - margin, linelist['wave_nm'] <= wave_top + margin) linelist_filename, molecules_filenames = write_atomic_linelist( linelist[wfilter], code="synthe", tmp_dir=tmp_execution_dir) else: linelist_filename = linelist_file molecules_filenames = molecules_files ## Compute atomic lines os.symlink(linelist_filename, tmp_execution_dir + "/fort.11") # rgfallinesnew: adds line information and line opacity data by reading # the adequate opacity file from fort.11. # The line opacity data are sent to fort.12 (and the line identification # to fort.14) if the calculation is LTE; it goes to fort.19 (line # identification to fort.20) if not. command = rgfallinesnew_executable proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate() #print out #print "-"*80 errcode = proc.returncode os.remove(tmp_execution_dir + "/fort.11") # Compute molecules if molecules_filenames is not None: for molecules_filename in molecules_filenames: os.symlink(molecules_filename, tmp_execution_dir + "/fort.11") # RMOLEC is the homologue of RGFALLTEST for diatomic molecules # It writes line opacity to fort.12 and line identifications # to fort. 14 command = rmolescasc_executable proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate() errcode = proc.returncode os.remove(tmp_execution_dir + "/fort.11") #print "Filename:", molecules_filename #print out # SYNTHE computes line opacity data based on the fundamental parameters of # the model (the ones written by SYNBEG to fort.93) and writes them to fort.93 command = synthe_executable # If timeout command exists in PATH, then use it to control synthe execution time if which_timeout is not None: command = "timeout %i " % (timeout) + command proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate() errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") config_data = "0.0 0. 1. 0. 0. 0. 0. 0.\n" config_data += "0.\n" config_data += "RHOXJ R1 R101 PH1 PC1 PSI1 PRDDOP PRDPOW\n" config = open(tmp_execution_dir + "/fort.25", "w") config.write(config_data) config.close() # SPECTRV reads from unit 9 the file written by the program SYNTHE and com- # putes the continuum opacities and the overall synthetic spectrum # (intensities at 17 angles); this spectrum is then written to fort.7 command = spectrv_executable command_input = model # If timeout command exists in PATH, then use it to control synthe execution time if which_timeout is not None: command = "timeout %i " % (timeout) + command proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") # ROTATE handles rotational broadening of spectral lines. It also integrates the # intensity data given by SPECTRV to compute total flux. # The arguments are the number of values of the projected rotational # velocity v sin i (first row) and the velocities themselves (second row) # # Working notes: "It is recommended to always use SURFACE INTENSITY instead of SURFACE FLUX. # we can get flux spectra of non-rotating stars with SURFACE INTENSITY and # v sin i = 0" command = rotate_executable intensities_filename = tmp_execution_dir + "/intensities.bin" if os.path.exists(tmp_execution_dir + "/fort.7"): os.rename(tmp_execution_dir + "/fort.7", intensities_filename) else: raise Exception( "No intensities were calculated, synthesis failed!") #os.remove(tmp_execution_dir+"/fort.1") os.symlink(intensities_filename, tmp_execution_dir + "/fort.1") #command_input = " 1 50\n" command_input = " 1\n" command_input += "0.\n" # If timeout command exists in PATH, then use it to control synthe execution time if which_timeout is not None: command = "timeout %i " % (timeout) + command proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") flux_filename = tmp_execution_dir + "/flux.bin" os.rename(tmp_execution_dir + "/ROT1", flux_filename) os.remove(tmp_execution_dir + "/fort.1") os.remove(tmp_execution_dir + "/fort.2") #os.remove(tmp_execution_dir+"/fort.3") os.symlink(flux_filename, tmp_execution_dir + "/fort.1") os.symlink(tmp_execution_dir + "/lines.txt", tmp_execution_dir + "/fort.3") os.symlink(tmp_execution_dir + "/spectrum.txt", tmp_execution_dir + "/fort.2") os.symlink(tmp_execution_dir + "/dump.txt", tmp_execution_dir + "/fort.4") command = syntoascanga_executable # If timeout command exists in PATH, then use it to control synthe execution time if which_timeout is not None: command = "timeout %i " % (timeout) + command proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate() errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the synthe synthesis process.") raise Exception("Timeout: Synthesis failed!") try: data = np.loadtxt(tmp_execution_dir + "/spectrum.txt") except: #print out sys.stdout.flush() raise Exception("Synthesis failed!") synth_waveobs_tmp = data[:, 0] / 10. # Armstrong to nm # NOTE: We provided an artificially bigger wavelength range, so that synthe will consider near-by deep lines # Now we correct that and we reduce the wavelength range to the correct one: wfilter = np.logical_and(synth_waveobs_tmp >= wave_base, synth_waveobs_tmp <= wave_top) synth_waveobs = np.hstack( (synth_waveobs, synth_waveobs_tmp[wfilter])) synth_fluxes_tmp = data[:, 3] synth_fluxes = np.hstack((synth_fluxes, synth_fluxes_tmp[wfilter])) os.chdir(previous_cwd) shutil.rmtree(tmp_execution_dir) synth_spectrum = create_spectrum_structure(synth_waveobs, synth_fluxes) synth_spectrum.sort(order=['waveobs']) # Make sure we return the number of expected fluxes if not np.array_equal(synth_spectrum['waveobs'], waveobs): synth_spectrum = resample_spectrum(synth_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0, ) synth_spectrum['flux'] = apply_post_fundamental_effects(synth_spectrum['waveobs'], synth_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) return synth_spectrum['flux']
def __sme_true_generate_spectrum(process_communication_queue, waveobs, atmosphere_layers, teff, logg, MH, alpha, linelist, isotopes, abundances, fixed_abundances, microturbulence_vel, verbose=0, regions=None, R=None, macroturbulence=None, vsini=None, limb_darkening_coeff=None, tmp_dir=None): if not is_sme_support_enabled(): raise Exception("SME support is not enabled") tmp_execution_dir = tempfile.mkdtemp(dir=tmp_dir) ispec_dir = os.path.dirname(os.path.realpath(__file__)) + "/../../" sme_dir = ispec_dir + "/synthesizer/sme/" sme_shorter_dir = os.path.join(tmp_execution_dir, "sme") os.symlink(sme_dir, sme_shorter_dir) sme_shorter_dir += "/" from sys import platform as _platform system_64bits = sys.maxsize > 2**32 if _platform == "linux" or _platform == "linux2": # linux if system_64bits: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.linux.x86_64.64g") else: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.linux.x86.32g") elif _platform == "darwin": # OS X if system_64bits: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.darwin.x86_64.64g") else: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.darwin.i386.32") else: # Windows if system_64bits: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.Win32.x86_64.64g") else: sme = ctypes.CDLL(sme_shorter_dir + "/sme_synth.so.Win32.x86.32") #logging.warning("SME does not support isotope modifications") waveobs = waveobs.copy() waveobs.sort() #wave_step = waveobs[1] - waveobs[0] # 0.001 wave_step = np.max((0.001, np.min(waveobs[1:] - waveobs[:-1]))) if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1,), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top else: global_wave_base = np.min(regions['wave_base']) global_wave_top = np.max(regions['wave_top']) # Limit linelist linelist = _filter_linelist(linelist, regions) # Update abundances with the ones that should be fixed to a given value and # not affected by metallicity scalation atom_abundances = abundances[abundances['code'] <= 92] if fixed_abundances is not None and len(fixed_abundances) > 0: atom_abundances = atom_abundances.copy() for fixed_abundance in fixed_abundances: index = np.where(atom_abundances['code'] == fixed_abundance['code'])[0] # WARNING: Fix the abundance BUT also substract MH because SME will scale it later on atom_abundances['Abund'][index] = fixed_abundance['Abund'] - MH radius = atmosphere_layers[0][-1] nvalues = len(atmosphere_layers[0]) if nvalues == 11 and radius > 2.0: # Compare to 2.0 instead of 1.0 to avoid floating point imprecisions logging.info("Spherical model atmosphere with radius %.2e cm" % (radius)) spherical_model = True else: spherical_model = False #--------------------------------------------------------------------------- # *.- Entry.SMELibraryVersion #msg = _sme_librrayversion(sme) #--------------------------------------------------------------------------- # 0.- Entry.InputLineList (passlinelist.pro) if verbose == 1: logging.info("SME InputLineList") msg = _sme_inputlinelist(sme, linelist) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 1.- Entry.InputModel (passmodel.pro) if verbose == 1: logging.info("SME InputModel") msg = _sme_inputmodel(sme, teff, logg, MH, microturbulence_vel, atmosphere_layers, atom_abundances, spherical_model) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 2.- Entry.InputNLTE #--------------------------------------------------------------------------- # 3.- Entry.InputAbund (passabund.pro) if verbose == 1: logging.info("SME InputAbund") msg = _sme_inputabund(sme, atom_abundances, MH) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 4.- Entry.Ionization if verbose == 1: logging.info("SME Ionization") msg = _sme_ionization(sme) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 5.- Entry.SetVWscale if verbose == 1: logging.info("SME SetVWscale") msg = _sme_setvwscale(sme) if msg != "''": logging.warning(msg) synth_fluxes = [] synth_waveobs = [] for i, region in enumerate(regions): # It is better not to synthesize in a single run a big chunk of wavelength so # we split the computation in several pieces max_segment = 100. # nm if (region['wave_top'] - region['wave_base']) / wave_step > max_segment / wave_step: segment_wave_base = np.arange(region['wave_base'], region['wave_top'], max_segment) segments = np.recarray((len(segment_wave_base),), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'] = segment_wave_base segments['wave_top'] = segment_wave_base + max_segment - wave_step segments['wave_top'][-1] = region['wave_top'] # Last segment should not over pass the original global limits else: segments = np.recarray((1,), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'][0] = region['wave_base'] segments['wave_top'][0] = region['wave_top'] for segment in segments: wave_base = segment['wave_base'] wave_top = segment['wave_top'] if verbose == 1: logging.info("Segment %.2f - %.2f" % (wave_base, wave_top)) #--------------------------------------------------------------------------- # 6.- Entry.InputWaveRange (passwaverange.pro) if verbose == 1: logging.info("SME InputWaveRange") msg = _sme_inputwaverange(sme, wave_base*10., wave_top*10.) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 7.- Entry.Opacity if verbose == 1: logging.info("SME Opacity") msg = _sme_opacity(sme) if msg != "''": logging.warning(msg) #--------------------------------------------------------------------------- # 8.- Entry.Transf if verbose == 1: logging.info("SME Transf") first_execution = i == 0 #nwmax = int((wave_top - wave_base) / wave_step) * 2 nwmax = 200000 synth_waveobs_tmp, synth_fluxes_tmp = _sme_transf(sme, sme_shorter_dir.encode('utf-8'), nwmax, keep_lineop=not first_execution) if msg != "''": logging.warning(msg) synth_waveobs = np.hstack((synth_waveobs, synth_waveobs_tmp)) synth_fluxes = np.hstack((synth_fluxes, synth_fluxes_tmp)) synth_spectrum = create_spectrum_structure(synth_waveobs/10., synth_fluxes) synth_spectrum.sort(order=['waveobs']) # Make sure we return the number of expected fluxes if not np.array_equal(synth_spectrum['waveobs'], waveobs): synth_spectrum = resample_spectrum(synth_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0,) synth_spectrum['flux'] = apply_post_fundamental_effects(synth_spectrum['waveobs'], synth_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) shutil.rmtree(tmp_execution_dir) process_communication_queue.put(synth_spectrum['flux'])
def generate_spectrum(waveobs, atmosphere_layers, teff, logg, MH, alpha, linelist, isotopes, abundances, fixed_abundances, microturbulence_vel, verbose=0, atmosphere_layers_file=None, regions=None, R=None, macroturbulence=None, vsini=None, limb_darkening_coeff=None, tmp_dir=None, timeout=1800): if not is_moog_support_enabled(): raise Exception("MOOG support is not enabled") ispec_dir = os.path.dirname(os.path.realpath(__file__)) + "/../../" moog_dir = ispec_dir + "/synthesizer/moog/" moog_executable = moog_dir + "MOOGSILENT" #molecules = linelist['molecule'] == "T" #if len(np.where(molecules)[0]) > 0: #logging.warning("The molecules have been removed from the linelist because if not MOOG is unreasonably slow") #linelist = linelist[~molecules] if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top else: global_wave_base = np.min(regions['wave_base']) global_wave_top = np.max(regions['wave_top']) # TODO: decide how to stablish wave_step waveobs = waveobs.copy() waveobs.sort() #wave_step = waveobs[1] - waveobs[0] # 0.001 wave_step = np.max((0.001, np.min(waveobs[1:] - waveobs[:-1]))) # Limit linelist linelist = _filter_linelist(linelist, regions) # MOOG does not have hard-coded lines, we use a external file: #hydrogen_lines_file = moog_dir + "/hydrogen_moog_lines.txt" #hydrogen_lines = ascii.read(hydrogen_lines_file, names=["wave_A", "spectrum_moog_species", "lower_state_eV", "loggf"], encoding='utf-8') hydrogen_lines_file = moog_dir + "DATA/Hlinedata" hydrogen_lines = ascii.read(hydrogen_lines_file, names=[ "wave_A", "spectrum_moog_species", "lower_state_eV", "loggf", "designation" ], encoding='utf-8') # MOOG is not going to scale the abundances because we are indicating # our abundances in the input and that overrides any other prescription, thus # we have to manually scale (but do not change Hydrogen and Helium!) abundances = abundances.copy() efilter = np.logical_and(abundances['code'] != 1, abundances['code'] != 2) efilter = np.logical_and(efilter, abundances['code'] <= 92) abundances['Abund'][efilter] += MH # Update abundances with the ones that should be fixed to a given value and # not affected by metallicity scalation if fixed_abundances is not None and len(fixed_abundances) > 0: for fixed_abundance in fixed_abundances: index = np.where(abundances['code'] == fixed_abundance['code'])[0] abundances['Abund'][index] = fixed_abundance['Abund'] synth_fluxes = [] synth_waveobs = [] for i, region in enumerate(regions): # It is better not to synthesize in a single run a big chunk of wavelength so # we split the computation in several pieces max_segment = 100. # nm if (region['wave_top'] - region['wave_base']) / wave_step > max_segment / wave_step: segment_wave_base = np.arange(region['wave_base'], region['wave_top'], max_segment) segments = np.recarray((len(segment_wave_base), ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'] = segment_wave_base segments['wave_top'] = segment_wave_base + max_segment - wave_step segments['wave_top'][-1] = region[ 'wave_top'] # Last segment should not over pass the original global limits else: segments = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) segments['wave_base'][0] = region['wave_base'] segments['wave_top'][0] = region['wave_top'] for segment in segments: wave_base = segment['wave_base'] wave_top = segment['wave_top'] tmp_execution_dir = tempfile.mkdtemp(dir=tmp_dir) os.makedirs(tmp_execution_dir + "/DATA/") atmosphere_filename = tmp_execution_dir + "/model.in" linelist_filename = tmp_execution_dir + "/lines.in" barklem_linelist_filename = tmp_execution_dir + "/DATA/Barklem.dat" stronglinelist_file = tmp_execution_dir + "/stronglines.in" if atmosphere_layers_file is None: atmosphere_filename = write_atmosphere( atmosphere_layers, teff, logg, MH, code="moog", atmosphere_filename=atmosphere_filename, tmp_dir=tmp_dir) else: shutil.copyfile(atmosphere_layers_file, atmosphere_filename) # Write (MOOG requires a separate file for damping coeff if we want to provide rad coeff and alpha from ABO theory) wfilter = np.logical_and(linelist['wave_nm'] >= wave_base, linelist['wave_nm'] <= wave_top) linelist_filename = write_atomic_linelist( linelist[wfilter], linelist_filename=linelist_filename, code="moog", tmp_dir=tmp_dir) barklem_linelist_filename = write_atomic_linelist( linelist[wfilter], linelist_filename=barklem_linelist_filename, code="moog_barklem", tmp_dir=tmp_dir) molecules = linelist['molecule'][wfilter] == 'T' num_molecules = len(np.where(molecules)[0]) # Append microturbulence, solar abundances and metallicity moog_atmosphere = open(atmosphere_filename, "a") atom_abundances = abundances[np.logical_and( abundances['code'] > 1, abundances['code'] <= 92)] # Don't update hydrogen or helium abundances moog_atmosphere.write(" %.2f\n" % (microturbulence_vel)) moog_atmosphere.write("NATOMS= %i %.2f\n" % (len(atom_abundances), MH)) for atom_abundance in atom_abundances: abund = 12.036 + atom_abundance[ 'Abund'] # From SPECTRUM format to Turbospectrum moog_atmosphere.write("%i %.2f\n" % (atom_abundance['code'], abund)) # Molecules are required always, even if it is a linelist without molecules, # or MOOG will not compute molecular equilibrium which might affect other internal calculations #if num_molecules > 0: #unique_molecules = np.unique(linelist['spectrum_moog_species'][wfilter][molecules]) #moog_atmosphere.write("NMOL %i\n" % (len(unique_molecules))) #for specie in unique_molecules: #moog_atmosphere.write(" %s\n" % (specie)) # Molecule list as used by Jorge Melendez (private communication) moog_atmosphere.write("NMOL 28\n") moog_atmosphere.write( " 101.0 106.0 107.0 108.0 112.0 126.0\n") moog_atmosphere.write(" 606.0 607.0 608.0\n") moog_atmosphere.write(" 707.0 708.0\n") moog_atmosphere.write(" 808.0 812.0 822.0 823.0 840.0\n") moog_atmosphere.write(" 10108.0 10820.0 60808.0\n") moog_atmosphere.write( " 6.1 7.1 8.1 12.1 20.1 22.1 23.1 26.1 40.1\n") moog_atmosphere.close() # Add hydrogen lines # Provide some margin or near-by deep lines might be omitted margin = 2. # 2 nm wfilter = np.logical_and( hydrogen_lines['wave_A'] >= (wave_base - margin) * 10., hydrogen_lines['wave_A'] <= (wave_top + margin) * 10.) selected_hydrogen_lines = hydrogen_lines[wfilter] if len(selected_hydrogen_lines) > 40: # TODO: Find a work around to this raise Exception( "Wavelength range too big, it includes too many hydrogen lines" ) out = open(stronglinelist_file, "w") for line in selected_hydrogen_lines: out.write("%10.3f%10s%10.3f%10.3f%10s%10s%10s%10s\n" \ % (line['wave_A'], line['spectrum_moog_species'], line['lower_state_eV'], line['loggf'], "", "", "", "")) out.close() par_file = open(tmp_execution_dir + "/batch.par", "w") par_file.write("synth\n") par_file.write("standard_out moog.std\n") par_file.write("summary_out moog.sum\n") par_file.write("smoothed_out moog.spec\n") par_file.write("model_in model.in\n") par_file.write("lines_in lines.in\n") par_file.write("stronglines_in stronglines.in\n") par_file.write("atmosphere 0\n" ) # controls the output of atmosphere quantities #if num_molecules > 0: #par_file.write("molecules 1\n") # controls the molecular equilibrium calculations (1 = do molecular equilibrium but do not print results) #else: #par_file.write("molecules 0\n") # molecules should be always on or moog does not run par_file.write( "molecules 1\n" ) # controls the molecular equilibrium calculations (1 = do molecular equilibrium but do not print results par_file.write( "lines 1\n" ) # controls the output of line data (print out standard information about the input line list) par_file.write("strong 1\n") par_file.write( "flux/int 0\n" ) # choses integrated flux or central intensity (0 = integrated flux calculations) par_file.write( "damping 1\n" ) # Use Barklem.dat file with waals, alpha and rad damping coeff, if not found then do like damping = 0 par_file.write( "freeform 0\n" ) # Linelist format of 7 columns with numbers %10.3f and comment %10s par_file.write("plot 3\n") par_file.write("abundances 0 1\n") par_file.write("isotopes 0 1\n") par_file.write("synlimits\n") wave_range_of_line_influence = 20.0 # Amstrong par_file.write(" %.2f %.2f %.2f %.1f\n" % (wave_base * 10., wave_top * 10., wave_step * 10., wave_range_of_line_influence)) par_file.write("obspectrum 5\n") #par_file.write("plotpars 0\n") par_file.write("plotpars 1\n") par_file.write(" %.2f %.2f 0.0 1.10\n" % (wave_base * 10., wave_top * 10.)) # Ploting limits par_file.write(" 0.0 0.0 0.00 1.0\n" ) # vshift lamshift obsadd obsmult par_file.write( " r 0.0 0.0 0.0 0.00 0.0" ) # smooth-type FWHM-Gauss vsini LimbDarkeningCoeff FWHM-Macro FWHM-Loren par_file.close() previous_cwd = os.getcwd() os.chdir(tmp_execution_dir) command = moog_executable command_input = "" # If timeout command exists in PATH, then use it to control moog execution time if which("timeout") is not None: command = "timeout %i " % (timeout) + command # MOOG clears the screen, so it is better to always don't do verbose this part # also the information printed is not specially useful #if verbose == 1: #proc = subprocess.Popen(command.split(), stdin=subprocess.PIPE) #else: proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) # wait for the process to terminate out, err = proc.communicate(input=command_input.encode('utf-8')) errcode = proc.returncode if errcode == 124: # TIMEOUT logging.error( "A timeout has occurred in the moog synthesis process.") raise Exception("Timeout: Synthesis failed!") os.chdir(previous_cwd) try: data = np.loadtxt(tmp_execution_dir + "/moog.spec", skiprows=2) except: print(out) sys.stdout.flush() raise Exception("Synthesis failed!") #synth_waveobs_tmp = np.linspace(wave_base, wave_top, len(synth_fluxes_tmp)) # Not exactly identical to turbospectrum wavelengths synth_waveobs_tmp = data[:, 0] / 10. # Armstrong to nm synth_waveobs = np.hstack((synth_waveobs, synth_waveobs_tmp)) synth_fluxes_tmp = data[:, 1] if len(synth_fluxes_tmp) > 1 and np.isnan(synth_fluxes_tmp[-1]): synth_fluxes_tmp[-1] = synth_fluxes_tmp[ -2] # Turbospectrum bug with gfortran, last flux is always NaN synth_fluxes = np.hstack((synth_fluxes, synth_fluxes_tmp)) shutil.rmtree(tmp_execution_dir) # Zero values, when convolved, remain zero so we give a very tiny flux to avoid this problem synth_fluxes[synth_fluxes <= 0] = 10e-9 synth_spectrum = create_spectrum_structure(synth_waveobs, synth_fluxes) synth_spectrum.sort(order=['waveobs']) # Make sure we return the number of expected fluxes if not np.array_equal(synth_spectrum['waveobs'], waveobs): synth_spectrum = resample_spectrum(synth_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0, ) synth_spectrum['flux'] = apply_post_fundamental_effects(synth_spectrum['waveobs'], synth_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) return synth_spectrum['flux']
def generate_spectrum(grid, waveobs, teff, logg, MH, alpha, microturbulence_vel, macroturbulence=0.0, vsini=0.0, limb_darkening_coeff=0.00, R=0, regions=None): existing_points, free_parameters, filenames, read_point_value, value_fields, delaunay_triangulation, kdtree, ranges, base_dirname = grid if regions is None: global_wave_base = np.min(waveobs) global_wave_top = np.max(waveobs) regions = np.recarray((1, ), dtype=[('wave_base', float), ('wave_top', float)]) regions['wave_base'][0] = global_wave_base regions['wave_top'][0] = global_wave_top # Only read the part of the spectrum that needs to be interpolated def read_point_value(f, regions=None): return read_spectrum(f, apply_filters=False, sort=False, regions=regions) custom_read_point_value = lambda f: read_point_value(f, regions=regions) target_point = [] target_point = _add_target_if_possible(free_parameters, target_point, 'teff', teff) target_point = _add_target_if_possible(free_parameters, target_point, 'logg', logg) target_point = _add_target_if_possible(free_parameters, target_point, 'MH', MH) target_point = _add_target_if_possible(free_parameters, target_point, 'alpha', alpha) target_point = _add_target_if_possible(free_parameters, target_point, 'vmic', microturbulence_vel) interpolated = _interpolate(delaunay_triangulation, kdtree, existing_points, filenames, custom_read_point_value, value_fields, target_point) interpolated_spectrum = create_spectrum_structure(interpolated['waveobs'], interpolated['flux']) # Make sure we return the number of expected fluxes if not np.array_equal(interpolated_spectrum['waveobs'], waveobs): interpolated_spectrum = resample_spectrum(interpolated_spectrum, waveobs, method="linear", zero_edges=True) segments = None vrad = (0, ) interpolated_spectrum['flux'] = apply_post_fundamental_effects(interpolated_spectrum['waveobs'], interpolated_spectrum['flux'], segments, \ macroturbulence=macroturbulence, vsini=vsini, \ limb_darkening_coeff=limb_darkening_coeff, R=R, vrad=vrad) return interpolated_spectrum['flux']