示例#1
0
def clip_spectrum(wl,fx,wlmin,wlmax,pad=0):
    """This crops a spectrum wl,fx to wlmin and wlmax.
    If pad !=0, it should be set to a positive velocity
    by which the output spectrum will be padded around wlmin,wlmax,
    which is returned as well as the narrower cropped spectrum.

        Parameters
        ----------
        wl : np.ndarray()
            The stellar model wavelength(s) in nm.

        fx : np.ndarray()
            The stellar model flux.

        wlmin: float
            The minimum cropping wavelength.

        wlmax: float
            The maximum cropping wavelength.

        pad: float, m/s, optional
            A velocity corresponding to a shift around which the cropped array will
            be padded.

        Returns
        -------
        wl,fx: np.array(), np.array()
            The wavelength and flux of the integrated spectrum.
            """
    import lib.test as test
    import numpy as np
    #First run standard tests on input
    test.typetest(wl,np.ndarray,varname='wl in clip_spectrum')
    test.typetest(fx,np.ndarray,varname='fx in clip_spectrum')
    test.typetest(wlmin,[int,float],varname='wlmin in clip_spectrum')
    test.typetest(wlmax,[int,float],varname='wlmax in clip_spectrum')
    test.typetest(pad,[int,float],varname='pad in clip_spectrum')
    test.notnegativetest(pad,varname='pad in clip_spectrum')
    test.nantest(wlmin,varname='wlmin in clip_spectrum')
    test.nantest(wlmax,varname='wlmax in clip_spectrum')
    test.nantest(pad,varname='pad in clip_spectrum')
    if wlmin >= wlmax:
        raise ValueError('wlmin in clip_spectrum should be smaller than wlmax.')
    wlc = wl[(wl >= wlmin) & (wl <= wlmax)]#This is the wavelength grid onto which we will interpolate the final result.
    fxc = fx[(wl >= wlmin) & (wl <= wlmax)]

    if pad > 0:
        wlmin_wide = wlmin/doppler(pad)
        wlmax_wide = wlmax*doppler(pad)
        wlc_wide = wl[(wl >= wlmin_wide) & (wl <= wlmax_wide)]
        fxc_wide = fx[(wl >= wlmin_wide) & (wl <= wlmax_wide)]
        return(wlc,fxc,wlc_wide,fxc_wide)
    else:
        return(wlc,fxc)
示例#2
0
def input_tests_local(xp, yp, RpRs):
    """This wraps input tests for the local integrator functions, as these are the same in both cases."""
    import lib.test as test
    #xp and yp should be ints or floats, and may not be NaN.
    test.typetest(xp, [int, float], varname='xp in build_local_spectrum_fast')
    test.typetest(yp, [int, float], varname='xp in build_local_spectrum_fast')
    test.nantest(xp, varname='xp in build_local_spectrum_fast')
    test.nantest(yp, varname='yp in build_local_spectrum_fast')

    #RpRs should be a non-negative float, and may not be NaN:
    test.typetest(RpRs, float, varname='RpRs in build_local_spectrum_fast')
    test.nantest(RpRs, varname='RpRs in build_local_spectrum_fast')
    test.notnegativetest(RpRs, varname='RpRs in build_local_spectrum_fast')
示例#3
0
def calc_vel_stellar(x,y,i_stellar, vel_eq, diff_rot_rate, proj_obliquity):
    """
    based on Cegla+2016. See Fig. 3 and equations 2 - 8
    https://arxiv.org/pdf/1602.00322.pdf
    It takes the stellar parameters and then calculates the stellar velocities in all bins for one quarter of the stellar disk
    input: x, y: 1D numpy arrays to create the stellar grid in units of stellar radius
           i_stellar: inclination in degrees
           vel_eq: equatorial stellar velocity
           diff_rot_rate: differential rotation rate
           proj_obliquity: projected obliquity
    output: vel_stellar_grid: 2D numpy array of stellar velocities over one quarter of the stellar disk
    """
    import numpy as np
    import pdb
    import lib.test as test

    #Standard tests on input
    test.typetest(x,np.ndarray,varname='x in calc_vel_stellar')
    test.typetest(y,np.ndarray,varname='x in calc_vel_stellar')
    test.typetest(i_stellar,float,varname='i_stellar in calc_vel_stellar')
    test.typetest(vel_eq,float,varname='vel_eq in calc_vel_stellar')
    test.typetest(diff_rot_rate,float,varname='diff_rot_rate in calc_vel_stellar')
    test.typetest(proj_obliquity,float,varname='proj_obliquity in calc_vel_stellar')
    test.nantest(x,varname='x in calc_vel_stellar')
    test.nantest(y,varname='y in calc_vel_stellar')
    test.nantest(i_stellar,varname='i_stellar in calc_vel_stellar')
    test.nantest(vel_eq,varname='vel_eq in calc_vel_stellar')
    test.nantest(diff_rot_rate,varname='diff_rot_rate in calc_vel_stellar')
    test.nantest(proj_obliquity,varname='proj_obliquity in calc_vel_stellar')
    test.notnegativetest(vel_eq,varname='vel_eq in calc_vel_stellar')

    #Careful! all angles have to be in radians!
    #Think carefully about which ones of these have to be transposed

    #convert angles to rad
    alpha = np.radians(proj_obliquity)
    beta = np.radians(i_stellar)
    #pre calculate matrices
    z,x_full,y_full = calc_z(x,y)
    #equation 8 from Cegla+2016
#    vel_stellar_grid = (x_full*np.cos(alpha)-y_full*np.sin(alpha))*vel_eq*np.sin(beta)*(1.-diff_rot_rate*(z*np.sin(np.pi/2.-beta)+np.cos(np.pi/2.-beta)*(x_full*np.sin(alpha)-y_full*np.cos(alpha))))

     #this has the star aligned to the coordinate system. Like this the projected obliquity is not incorporated here. The tilt of the star compared to the planet has to be incorporated in the planet's path.
    vel_stellar_grid = x_full*vel_eq*np.sin(beta)*(1.-diff_rot_rate*(z*np.sin(np.pi/2.-beta)+np.cos(np.pi/2.-beta)*y_full))

    return(vel_stellar_grid)
示例#4
0
def crop_spectrum(wl,fx,pad):
    """This crops a spectrum wl,fx to wlmin and wlmax.
    If pad !=0, it should be set to a positive velocity
    by which the output spectrum will be padded within wlmin,wlmax, to allow
    for velocity shifts. The difference with clip_spectrum above is that this
    routine pads towards the inside (only returning the narrow spectrum), while
    clip_spectrum pads towards the outside, returning both the cropped and the
    padded cropped spectra.

        Parameters
        ----------
        wl : np.ndarray()
            The stellar model wavelength(s) in nm.

        fx : np.ndarray()
            The stellar model flux.

        pad: float, m/s
            A velocity corresponding to a shift around which the cropped array will
            be padded.

        Returns
        -------
        wl,fx: np.array(), np.array()
            The wavelength and flux of the integrated spectrum.
            """
    import lib.test as test
    import numpy as np
    wlmin = min(wl)
    wlmax = max(wl)
    #First run standard tests on input
    test.typetest(wl,np.ndarray,varname=       'wl in crop_spectrum')
    test.typetest(fx,np.ndarray,varname=       'fx in crop_spectrum')
    test.typetest(pad,[int,float],varname=    'pad in crop_spectrum')
    test.notnegativetest(pad,varname=         'pad in crop_spectrum')
    test.nantest(pad,varname=                 'pad in crop_spectrum')
    if wlmin >= wlmax:
        raise ValueError('wlmin in crop_spectrum should be smaller than wlmax.')
    wlmin_wide = wlmin*doppler(pad)#Crop towards the inside
    wlmax_wide = wlmax/doppler(pad)#Crop towards the inside
    wlc_narrow = wl[(wl >= wlmin_wide) & (wl <= wlmax_wide)]
    fxc_narrow = fx[(wl >= wlmin_wide) & (wl <= wlmax_wide)]
    return(wlc_narrow,fxc_narrow)
示例#5
0
def vactoair(wlnm):
    """Convert vaccuum to air wavelengths.

        Parameters
        ----------
        wlnm : float, np.array()
            The wavelength(s) in nm.

        Returns
        -------
        wl: float, np.array()
            The wavelengths.
    """
    import lib.test as test
    import numpy
    #First run standard tests on the input
    test.typetest(wlnm,[int,float,numpy.ndarray],varname='wlnm in vactoair')
    test.notnegativetest(wlnm,varname='wlnm in vactoair')
    test.nantest(wlnm,varname='wlnm in vactoair')
    wlA = wlnm*10.0
    s = 1e4/wlA
    f = 1.0 + 5.792105e-2/(238.0185e0 - s**2) + 1.67917e-3/( 57.362e0 - s**2)
    return(wlA/f/10.0)
示例#6
0
    def read_system(self,star_path='demo_star.txt',planet_path='demo_planet.txt',obs_path='demo_observations.txt'):
        """Reads in the stellar, planet and observation parameters from file; performing
        tests on the input and lifting the read variables to the class object.

        Parameters
        ----------
            star_path : str
                Path to parameter file defining the star.
            planet_path: str
                Path to the parameter file defining the planet and its orbit.
            obs_path: str
                Path to the parameter file defining the timestamps of the observations.
        """
        planetparams = open(planet_path,'r').read().splitlines()
        starparams = open(star_path,'r').read().splitlines()
        obsparams = open(obs_path,'r').read().splitlines()
        self.velStar = float(starparams[0].split()[0])
        self.stelinc = float(starparams[1].split()[0])
        self.drr = float(starparams[2].split()[0])
        self.T = float(starparams[3].split()[0])
        self.Z = float(starparams[4].split()[0])
        self.logg = float(starparams[5].split()[0])
        self.u1 = float(starparams[6].split()[0])
        self.u2 = float(starparams[7].split()[0])
        self.mus = float(starparams[8].split()[0])
        self.R = float(starparams[9].split()[0])
        self.sma_Rs = float(planetparams[0].split()[0])
        self.ecc = float(planetparams[1].split()[0])
        self.omega = float(planetparams[2].split()[0])
        self.orbinc = float(planetparams[3].split()[0])
        self.pob = float(planetparams[4].split()[0])#Obliquity.
        self.Rp_Rs = float(planetparams[5].split()[0])
        self.orb_p = float(planetparams[6].split()[0])
        self.transitC = float(planetparams[7].split()[0])
        self.mode = planetparams[8].split()[0]#This is a string.
        times = [] #These are in JD-24000000.0 or in orbital phase.
        self.exptimes = []
        for i in obsparams:
            times.append(float(i.split()[0]))
        self.times = np.array(times)
        self.Nexp = len(self.times)#Number of exposures.
        if self.mode == 'times':
            for i in obsparams:
                self.exptimes.append(float(i.split()[1]))
            self.exptimes = np.array(self.exptimes)
        try:
            test.typetest(self.wave_start,float,varname='wave_start in input')
            test.nantest(self.wave_start,varname='wave_start in input')
            test.notnegativetest(self.wave_start,varname='wave_start in input')
            test.notnegativetest(self.velStar,varname='velStar in input')
            test.notnegativetest(self.stelinc,varname='stelinc in input')
        #add all the other input parameters
        except ValueError as err:
            print("Parser: ",err.args)

        if self.mus != 0:
            self.mus = np.linspace(0.0,1.0,self.mus)#Uncomment this to run in CLV mode with SPECTRUM.
示例#7
0
def airtovac(wlnm):
    """Convert air to vaccuum wavelengths.

        Parameters
        ----------
        wlnm : float, np.array()
            The wavelength(s) in nm.

        Returns
        -------
        wl: float, np.array()
            The wavelengths.
    """
    import lib.test as test
    import numpy
    #First run standard tests on the input
    test.typetest(wlnm,[int,float,numpy.ndarray],varname='wlnm in airtovac')
    test.notnegativetest(wlnm,varname='wlnm in airtovac')
    test.nantest(wlnm,varname='wlnm in airtovac')
    #Would still need to test that wl is in a physical range.
    wlA=wlnm*10.0
    s = 1e4 / wlA
    n = 1 + 0.00008336624212083 + 0.02408926869968 / (130.1065924522 - s**2) + 0.0001599740894897 / (38.92568793293 - s**2)
    return(wlA*n/10.0)
def read_spectrum(T, logg, metallicity=0.0, alpha=0.0):
    """Wrapper for the function get_spectrum() above, that checks that the input
        T, log(g), metallicity and alpha are in accordance with what is provided by
        PHOENIX (as of November 1st, 2019), and passes them on to get_spectrum().


        Parameters
        ----------
        T : int,float
            The model photosphere temperature. Acceptable values are:
            2300 - 7000 in steps of 100, and 7200 - 12000 in steps of 200.
        logg : int,float
            The model log(g) value. Acceptable values are:
            0.0 - 6.0 in steps of 0.5.
        metallicity : int,float (optional, default = 0)
            The model metallicity [Fe/H] value. Acceptable values are:
            -4.0, -3.0, -2.0 and -1.5 to +1.0 in steps of 0.5.
            If no location is given, the ``location`` attribute of the Time
            object is used.
        alpha : int,float (optional, default = 0)
            The model alpha element enhancement [alpha/M]. Acceptable values are:
            -0.2 to 1.2 in steps of 0.2, but only for Fe/H of -3.0 to 0.0.

        Returns
        -------
        wl,f : np.array(),np.array()
            The wavelength (nm) and flux (erg/s/cm^2/cm) axes of the requested
            spectrum.
        """
    import numpy as np
    import sys
    import astropy.io.fits as fits
    import lib.test as test
    phoenix_factor = np.pi  #This is a factor to correct the PHOENIX spectra to produce the correctly calibrated output flux.
    #If you compare PHOENIX to SPECTRUM, a factor of 3 seems to be missing. Brett Morris encountered this problem when trying
    #to use PHOENIX to predict the received flux of real sources, to construct an ETC. He also needed a factor of 3, which he
    #interpreted as a missing factor of pi. So pi it is, until something else is deemed better (or until PHOENIX updates
    #their grid).

    #First run standard tests on the input:
    test.typetest(T, [int, float], varname='T in read_spectrum')
    test.typetest(logg, [int, float], varname='logg in read_spectrum')
    test.typetest(metallicity, [int, float],
                  varname='metallicity in read_spectrum')
    test.typetest(alpha, [int, float], varname='alpha in read_spectrum')
    test.nantest(T, varname='T in get_spectrum')
    test.nantest(logg, varname='logg in get_spectrum')
    test.nantest(metallicity, varname='Z in get_spectrum')
    test.nantest(alpha, varname='a in get_spectrum')
    test.notnegativetest(T, varname='T in get_spectrum')

    #These contain the acceptable values.
    T_a = np.concatenate((np.arange(2300, 7100,
                                    100), np.arange(7200, 12200, 200)))
    logg_a = np.arange(0, 6.5, 0.5)
    FeH_a = np.concatenate((np.arange(-4, -1, 1), np.arange(-1.5, 1.5, 0.5)))
    alpha_a = np.arange(0, 1.6, 0.2) - 0.2

    #Check that the input is contained in them:
    if T in T_a and metallicity in FeH_a and alpha in alpha_a and logg in logg_a:
        if (metallicity < -3 or metallicity > 0.0) and alpha != 0:
            print(
                'Error: Alpha element enhancement != 0 only available for -3.0<[Fe/H]<0.0'
            )
            sys.exit()
        #If so, retrieve the spectra:
        wavename, specname = get_spectrum(T, logg, metallicity, alpha)
        f = fits.getdata(specname)
        w = fits.getdata(wavename)
        return (w / 10.0, f / phoenix_factor)  #Implicit unit conversion here.
    else:
        print(
            'Error: Provided combination of T, log(g), Fe/H and a/M is out of bounds.'
        )
        print('T = %s, log(g) = %s, Fe/H = %s, a/M = %s' %
              (T, logg, metallicity, alpha))
        print('The following values are accepted:')
        print('T:')
        print(T_a)
        print('Log(g):')
        print(logg_a)
        print('Fe/H:')
        print(FeH_a)
        print('a/M:')
        print(alpha_a)
        print(
            'Alpha element enhancement != 0 only available for -3.0<[Fe/H]<0.0'
        )
        sys.exit()
def get_spectrum(T, logg, Z, a):
    """Querying a PHOENIX photosphere model, either from disk or from the
        PHOENIX website if the spectrum hasn't been downloaded before, and
        returning the names of the files in the data subfolder. These may then
        be read by the wrapper function read_spectrum() below.
        However, if limbs is set to a positive value, the code is switched to a
        mode in which the CLV effect is taken into account. In this event,
        PHOENIX spectra cannot be used, and the SPECTRUM package will be used to
        calculate spectra. For this functionality to work, the user needs to have
        built the SPECTRUM source code (included in this distribution). See the
        documentation for instructions.

        Parameters
        ----------
        T : int, float
            The model photosphere temperature. Acceptable values are:
            2300 - 7000 in steps of 100, and 7200 - 12000 in steps of 200.
        logg : int, float
            The model log(g) value. Acceptable values are:
            0.0 - 6.0 in steps of 0.5.
        Z : int, float
            The model metallicity [Fe/H] value. Acceptable values are:
            -4.0, -3.0, -2.0 and -1.5 to +1.0 in steps of 0.5.
        a : int, float
            The model alpha element enhancement [alpha/M]. Acceptable values are:
            -0.2 to 1.2 in steps of 0.2, but only for Fe/H of -3.0 to 0.0.

        Returns
        -------
        wlname, specname: str, str
            The names of the files containing the wavelength and flux axes of
            the requested spectrum.
        """
    import requests
    import shutil
    import urllib.request as request
    from contextlib import closing
    import sys
    import os.path
    import lib.test as test

    #First run standard tests on the input
    test.typetest(T, [int, float], varname='T in get_spectrum')
    test.typetest(logg, [int, float], varname='logg in get_spectrum')
    test.typetest(Z, [int, float], varname='metallicity in get_spectrum')
    test.typetest(a, [int, float], varname='alpha in get_spectrum')
    test.nantest(T, varname='T in get_spectrum')
    test.nantest(logg, varname='logg in get_spectrum')
    test.nantest(Z, varname='Z in get_spectrum')
    test.nantest(a, varname='a in get_spectrum')
    test.notnegativetest(T, varname='T in get_spectrum')

    #This is where phoenix spectra are located.
    root = 'ftp://phoenix.astro.physik.uni-goettingen.de/HiResFITS/'

    #We assemble a combination of strings to parse the user input into the URL,
    z_string = '{:.1f}'.format(float(Z))
    if Z > 0:
        z_string = '+' + z_string
    else:
        z_string = '-' + z_string
    a_string = ''
    if a > 0:
        a_string = '.Alpha=+' + '{:.2f}'.format(float(a))
    if a < 0:
        a_string = '.Alpha=' + '{:.2f}'.format(float(a))
    t_string = str(int(T))
    if T < 10000:
        t_string = '0' + t_string
    g_string = '-' + '{:.2f}'.format(float(logg))

    #These are URLS for the input files.
    waveurl = root + 'WAVE_PHOENIX-ACES-AGSS-COND-2011.fits'
    specurl = root + 'PHOENIX-ACES-AGSS-COND-2011/Z' + z_string + a_string + '/lte' + t_string + g_string + z_string + a_string + '.PHOENIX-ACES-AGSS-COND-2011-HiRes.fits'

    #These are the output filenames, they will also be returned so that the wrapper
    #of this function can take them in.
    wavename = 'data/PHOENIX/WAVE.fits'

    if os.path.isdir('data/PHOENIX/') == False:
        os.makedirs('data/PHOENIX/')

    specname = 'data/PHOENIX/lte' + t_string + g_string + z_string + a_string + '.PHOENIX-ACES-AGSS-COND-2011-HiRes.fits'

    #If it doesn't already exists, we download them, otherwise, we just pass them on:
    if os.path.exists(wavename) == False:
        with closing(request.urlopen(waveurl)) as r:
            with open(wavename, 'wb') as f:
                shutil.copyfileobj(r, f)
    if os.path.exists(specname) == False:
        print(specurl)
        with closing(request.urlopen(specurl)) as r:
            with open(specname, 'wb') as f:
                shutil.copyfileobj(r, f)
    return (wavename, specname)