Example #1
0
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
Example #2
0
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
Example #3
0
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']
Example #4
0
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']
Example #5
0
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']
Example #6
0
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'])
Example #7
0
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']
Example #8
0
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']