Esempio n. 1
0
def get_sun(time):
    """
    Determines the location of the sun at a given time (or times, if the input
    is an array `~astropy.time.Time` object), in geocentric coordinates.

    Parameters
    ----------
    time : `~astropy.time.Time`
        The time(s) at which to compute the location of the sun.

    Returns
    -------
    newsc : `~astropy.coordinates.SkyCoord`
        The location of the sun as a `~astropy.coordinates.SkyCoord` in the
        `~astropy.coordinates.GCRS` frame.


    Notes
    -----
    The algorithm for determining the sun/earth relative position is based
    on the simplified version of VSOP2000 that is part of ERFA. Compared to
    JPL's ephemeris, it should be good to about 4 km (in the Sun-Earth
    vector) from 1900-2100 C.E., 8 km for the 1800-2200 span, and perhaps
    250 km over the 1000-3000.

    """
    earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(time, 'tdb'))

    # We have to manually do aberration because we're outputting directly into
    # GCRS
    earth_p = earth_pv_helio['p']
    earth_v = earth_pv_bary['v']

    # convert barycentric velocity to units of c, but keep as array for passing in to erfa
    earth_v /= c.to_value(u.au/u.d)

    dsun = np.sqrt(np.sum(earth_p**2, axis=-1))
    invlorentz = (1-np.sum(earth_v**2, axis=-1))**0.5
    properdir = erfa.ab(earth_p/dsun.reshape(dsun.shape + (1,)),
                        -earth_v, dsun, invlorentz)

    cartrep = CartesianRepresentation(x=-dsun*properdir[..., 0] * u.AU,
                                      y=-dsun*properdir[..., 1] * u.AU,
                                      z=-dsun*properdir[..., 2] * u.AU)
    return SkyCoord(cartrep, frame=GCRS(obstime=time))
Esempio n. 2
0
def get_sun(time):
    """
    Determines the location of the sun at a given time (or times, if the input
    is an array `~astropy.time.Time` object), in geocentric coordinates.

    Parameters
    ----------
    time : `~astropy.time.Time`
        The time(s) at which to compute the location of the sun.

    Returns
    -------
    newsc : `~astropy.coordinates.SkyCoord`
        The location of the sun as a `~astropy.coordinates.SkyCoord` in the
        `~astropy.coordinates.GCRS` frame.


    Notes
    -----
    The algorithm for determining the sun/earth relative position is based
    on the simplified version of VSOP2000 that is part of ERFA. Compared to
    JPL's ephemeris, it should be good to about 4 km (in the Sun-Earth
    vector) from 1900-2100 C.E., 8 km for the 1800-2200 span, and perhaps
    250 km over the 1000-3000.

    """
    earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(time, 'tdb'))

    # We have to manually do aberration because we're outputting directly into
    # GCRS
    earth_p = earth_pv_helio['p']
    earth_v = earth_pv_bary['v']

    # convert barycentric velocity to units of c, but keep as array for passing in to erfa
    earth_v /= c.to_value(u.au / u.d)

    dsun = np.sqrt(np.sum(earth_p**2, axis=-1))
    invlorentz = (1 - np.sum(earth_v**2, axis=-1))**0.5
    properdir = erfa.ab(earth_p / dsun.reshape(dsun.shape + (1, )), -earth_v,
                        dsun, invlorentz)

    cartrep = CartesianRepresentation(x=-dsun * properdir[..., 0] * u.AU,
                                      y=-dsun * properdir[..., 1] * u.AU,
                                      z=-dsun * properdir[..., 2] * u.AU)
    return SkyCoord(cartrep, frame=GCRS(obstime=time))
Esempio n. 3
0
def calc_Vpix(nu_obs, nu_emit, pixel_nu, survey, cosmo):
    assert survey in ['TIME', 'CONCERTO', 'CCAT-p'], "Survey not recognized!"

    totaldeg = 4. * np.pi * (180. / np.pi)**2
    z = (nu_emit - nu_obs) / nu_obs

    if survey == 'CCAT-p':
        thetabeam = 53. / 3600.
        omegabeam = 2. * np.pi * (thetabeam / 2.355)**2
    else:
        D = 12  # m
        line = c.to_value(u.m * u.GHz) / nu_obs
        thetabeam = 1.22 * line / D
        thetabeam *= (180. / np.pi)
        omegabeam = 2. * np.pi * (thetabeam / 2.355)**2

    dup_pix = comoving_distance_at_freq(nu_obs - pixel_nu / 2, nu_emit, cosmo)
    dlow_pix = comoving_distance_at_freq(nu_obs + pixel_nu / 2, nu_emit, cosmo)
    Vpix = (omegabeam / totaldeg) * (4. * np.pi / 3.) * (dup_pix**3 -
                                                         dlow_pix**3)
    return Vpix
Esempio n. 4
0
import astropy.units as apu
from astropy.constants import h, c, u
import numpy as np

one_arcsec = 1.0/3600.0

erg_per_eV = apu.eV.to("erg")
erg_per_keV = erg_per_eV * 1.0e3
keV_per_erg = 1.0 / erg_per_keV
eV_per_erg = 1.0 / erg_per_eV

hc = (h*c).to("keV*angstrom").value
clight = c.to("cm/s").value
ckms = c.to_value("km/s")

sigma_to_fwhm = 2.*np.sqrt(2.*np.log(2.))
sqrt2pi = np.sqrt(2.*np.pi)

m_u = u.to("g").value

elem_names = ["", "H", "He", "Li", "Be", "B", "C", "N", "O",
              "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S",
              "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr",
              "Mn", "Fe", "Co", "Ni", "Cu", "Zn"]

cosmic_elem = [1, 2, 3, 4, 5, 9, 11, 15, 17, 19,
               21, 22, 23, 24, 25, 27, 29, 30]
metal_elem = [6, 7, 8, 10, 12, 13, 14, 16, 18, 20, 26, 28]

atomic_weights = np.array([0.0, 1.00794, 4.00262, 6.941, 9.012182, 10.811,
                           12.0107, 14.0067, 15.9994, 18.9984, 20.1797,
Esempio n. 5
0
def line_width_equiv(rest):
    from astropy.constants import c
    ckms = c.to_value('km/s')
    forward = lambda x: rest * x / ckms
    backward = lambda x: x / rest * ckms
    return [(u.km / u.s, u.keV, forward, backward)]
Esempio n. 6
0
    '3-2': 866,
    '4-3': 651,
    '5-4': 521,
    '6-5': 434,
    '7-6': 372,
    '8-7': 325,
    '9-8': 289,
    '10-9': 260,
    '11-10': 237,
    '12-11': 217,
    '13-12': 200,
    'CII': 157.7
}

# Convert CO wavelengths to GHz
speed_of_light = c.to_value(u.micron * u.GHz)

CO_lines = {key: speed_of_light / wave for key, wave in CO_lines_wave.items()}

n = 111.52
CO_lines_LT16 = {
    '1-0': n,
    '2-1': 2 * n,
    '3-2': 3 * n,
    '4-3': 4 * n,
    '5-4': 5 * n,
    '6-5': 6 * n,
    '7-6': 7 * n,
    '8-7': 8 * n,
    '9-8': 9 * n,
    '10-9': 10 * n,
Esempio n. 7
0
import astropy.units as apu
from astropy.constants import h, c, u
import numpy as np

one_arcsec = 1.0 / 3600.0

erg_per_eV = apu.eV.to("erg")
erg_per_keV = erg_per_eV * 1.0e3
keV_per_erg = 1.0 / erg_per_keV
eV_per_erg = 1.0 / erg_per_eV

hc = (h * c).to("keV*angstrom").value
clight = c.to("cm/s").value
ckms = c.to_value("km/s")

sigma_to_fwhm = 2. * np.sqrt(2. * np.log(2.))
sqrt2pi = np.sqrt(2. * np.pi)

m_u = u.to("g").value

elem_names = [
    "", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al",
    "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe",
    "Co", "Ni", "Cu", "Zn"
]

cosmic_elem = [
    1, 2, 3, 4, 5, 9, 11, 15, 17, 19, 21, 22, 23, 24, 25, 27, 29, 30
]
metal_elem = [6, 7, 8, 10, 12, 13, 14, 16, 18, 20, 26, 28]
Esempio n. 8
0
    def run(self, spectra, cross_correlation_reference):
        max_nsysrem = self.max_sysrem_iterations
        max_nsysrem_after = self.max_sysrem_iterations_afterwards
        rv_range = self.rv_range
        rv_points = self.rv_points
        rv_step = 2 * rv_range / rv_points
        skip = self.skip_segments

        skip_mask = np.full(spectra.shape[1], True)
        for seg in skip:
            skip_mask[spectra.segments[seg] : spectra.segments[seg + 1]] = False

        # reference = cross_correlation_reference

        flux = spectra.flux.to_value(1)
        flux = flux
        if spectra.uncertainty is not None:
            unc = spectra.uncertainty.array
        else:
            unc = np.ones_like(flux)

        correlation = {}
        for n in tqdm(range(max_nsysrem), desc="Sysrem N"):
            corrected_flux = sysrem(flux, num_errors=n, errors=unc)

            # Normalize by the standard deviation in this wavelength column
            std = np.nanstd(corrected_flux, axis=0)
            std[std == 0] = 1
            corrected_flux /= std

            # reference_flux = np.copy(reference.flux.to_value(1))
            # reference_flux -= np.nanmean(reference_flux, axis=1)[:, None]
            # reference_flux /= np.nanstd(reference_flux, axis=1)[:, None]
            wave_noshift = spectra.wavelength[0].to_value("AA")
            c_light = c.to_value("km/s")

            # Run the cross correlation for all times and radial velocity offsets
            corr = np.zeros((len(spectra), int(rv_points)))
            for i in tqdm(range(len(spectra) - 1), leave=False, desc="Observation"):
                for j in tqdm(range(rv_points), leave=False, desc="radial velocity",):
                    # Doppler Shift the next spectrum
                    rv = -rv_range + j * rv_step
                    wave_shift = wave_noshift * (1 + rv / c_light)
                    newspectra = np.interp(
                        wave_shift, wave_noshift, corrected_flux[i + 1]
                    )

                    # Mask bad pixels
                    m = np.isfinite(corrected_flux[i])
                    m &= np.isfinite(newspectra[i + 1])
                    m &= skip_mask

                    # Cross correlate!
                    corr[i, j] += np.correlate(
                        corrected_flux[i][m], newspectra[m], "valid",
                    )
                    # Normalize to the number of data points used
                    corr[i, j] *= m.size / np.count_nonzero(m)

            correlation[f"{n}"] = np.copy(corr)
            for i in tqdm(
                range(max_nsysrem_after),
                leave=False,
                desc="Sysrem on Cross Correlation",
            ):
                correlation[f"{n}.{i}"] = sysrem(np.copy(corr), i)

        self.save(correlation)
        return correlation
Esempio n. 9
0
def line_width_equiv(rest):
    from astropy.constants import c
    ckms = c.to_value('km/s')
    forward = lambda x: rest*x/ckms
    backward = lambda x: x/rest*ckms
    return [(u.km/u.s, u.keV, forward, backward)]
Esempio n. 10
0
def combine_observations(spectra: SpectrumArray):
    # TODO: The telluric spectrum will change between observations
    # and therefore influence the recovered stellar parameters
    # Especially when we combine data from different transits!

    # for i in range(spectra.shape[0]):
    #     plt.plot(spectra.wavelength[i], spectra.flux[i], "r")

    # Shift to the same reference frame (telescope)
    print("Shift observations to the telescope restframe")
    spectra = spectra.shift("telescope", inplace=True)

    # Arbitrarily choose the central grid as the common one
    print("Combine all observations")
    wavelength = spectra.wavelength[len(spectra) // 2]
    spectra = spectra.resample(wavelength, inplace=True, method="linear")
    # Detects ouliers based on the Median absolute deviation
    mask = detect_ouliers(spectra)

    # TODO: other approach
    # s(i) = f(i) (1 - w / dw * g) + f(i+1) w / dw * g
    # g = sqrt((1 + beta) / (1 - beta)) - 1
    rv = np.zeros(len(spectra))
    for i in range(len(spectra)):
        rv[i] = spectra.reference_frame.to_barycentric(spectra.datetime[i]).to_value(
            "km/s"
        )
    rv -= np.mean(rv)
    rv *= -1
    rv /= c.to_value("km/s")
    rv = np.sqrt((1 + rv) / (1 - rv)) - 1

    wave = np.copy(wavelength.to_value("AA"))
    for l, r in zip(spectra.segments[:-1], spectra.segments[1:]):
        wave[l:r] /= np.gradient(wave[l:r])

    g = wave[None, :] * rv[:, None]

    # TODO: the tellurics are scaled by the airmass, which we should account for here, when creating the master stellar
    # TODO: Could we fit a linear polynomial to each wavelength point? as a function of time/airmass?
    # TODO: Then the spectrum would be constant, since there is no change, but for tellurics we would see a difference
    # TODO: But there is a different offset for the tellurics, and the stellar features
    yflux = spectra.flux.to_value(1)
    flux = np.zeros(yflux.shape)
    coeff = np.zeros((yflux.shape[1], 2))
    airmass = spectra.airmass
    mask = ~mask
    for i in tqdm(range(spectra.flux.shape[1])):
        coeff[i] = np.polyfit(airmass[mask[:, i]], yflux[:, i][mask[:, i]], 1)
        flux[:, i] = np.polyval(coeff[i], airmass)

    # fitfunc = lambda t0, t1, f: (t0[None, :] + t1[None, :] * airmass[:, None]) * (
    #     f + g * np.diff(f, append=f[-2])
    # )

    def plotfunc(airmass, t0, t1, f, fp, g):
        tell = t0 + t1 * airmass[:, None]
        tell = np.clip(tell, 0, 1)
        stel = f + g * (fp - f)  # np.diff(f, append=2 * f[-1] - f[-2])
        obs = tell * stel
        return obs

    def fitfunc(param):
        t0 = 1
        n = param.size // 2
        t1 = param[:n]
        f = param[n:]
        fp = np.roll(f, -1)
        model = plotfunc(airmass, t0, t1, f, fp, g[:, l:r])
        resid = model - yflux[:, l:r]
        return resid.ravel()

    def regularization(param):
        n = param.size // 2
        t1 = param[:n]
        f = param[n:]
        d1 = np.gradient(t1)
        d2 = np.gradient(f)
        reg = np.concatenate((d1, d2))
        return reg ** 2

    t0 = np.ones_like(coeff[:, 1])
    t1 = coeff[:, 0] / coeff[:, 1]
    t1[(t1 > 0.1) | (t1 < -2)] = -2
    f = np.copy(coeff[:, 1])

    for k in tqdm(range(1)):
        for l, r in tqdm(
            zip(spectra.segments[:-1], spectra.segments[1:]),
            total=spectra.nseg,
            leave=False,
        ):
            n = r - l

            # Smooth first guess
            mu = gaussian_filter1d(t1[l:r], 1)
            var = gaussian_filter1d((t1[l:r] - mu) ** 2, 11)
            sig = np.sqrt(var) * 80 + 0.5
            sig = np.nan_to_num(sig)
            smax = int(np.ceil(np.nanmax(sig))) + 1
            points = [t1[l:r]] + [gaussian_filter1d(t1[l:r], i) for i in range(1, smax)]
            smooth = RegularGridInterpolator((np.arange(smax), np.arange(n)), points)(
                (sig, np.arange(n))
            )
            t1[l:r] = smooth

            # plt.plot(t1[l:r])
            # plt.plot(f[l:r])
            # plt.show()

            # fold = np.copy(f[l:r])
            # told = np.copy(t1[l:r])

            # Bounds for the optimisation
            bounds = np.zeros((2, 2 * n))
            bounds[0, :n], bounds[0, n:] = -2, 0
            bounds[1, :n], bounds[1, n:] = 0, 1
            x0 = np.concatenate((t1[l:r], f[l:r]))
            x0 = np.nan_to_num(x0)
            x0 = np.clip(x0, bounds[0], bounds[1])

            res = least_squares(
                fitfunc,
                x0,
                method="trf",
                bounds=bounds,
                max_nfev=200,
                tr_solver="lsmr",
                tr_options={"atol": 1e-2, "btol": 1e-2},
                jac_sparsity="auto",
                regularization=regularization,
                r_scale=0.2,
                verbose=2,
                diff_step=0.01,
            )
            t0[l:r] = 1
            t1[l:r] = res.x[:n]
            f[l:r] = res.x[n:]

            # lower, upper = [-2, 0, 0], [0, 1, 1]
            # for i in tqdm(range(l, r - 1), leave=False):
            #     x0 = [t1[i], f[i], f[i + 1]]
            #     x0 = np.nan_to_num(x0)
            #     x0 = np.clip(x0, lower, upper)
            #     res = least_squares(fitfunc, x0, method="trf", bounds=[lower, upper])
            #     t0[i] = 1
            #     t1[i], f[i] = res.x[0], res.x[1]

            # t0[l:r] = gaussian_filter1d(t0[l:r], 0.5)
            # t1[l:r] = gaussian_filter1d(t1[l:r], 0.5)
            # f[l:r] = gaussian_filter1d(f[l:r], 0.5)

        # total = 0
        # for i in range(len(spectra)):
        #     total += np.sum(
        #         (plotfunc(airmass[i], t0, t1, f, np.roll(f, -1), g[i]) - yflux[i]) ** 2
        #     )
        # print(total)

    # TODO: t0 should be 1 in theory, however it is not in practice because ?
    tell = t0 + t1 * airmass[:, None]
    tell = np.clip(tell, 0, 1)
    tell = tell << spectra.flux.unit

    # i = 10
    # plt.plot(wavelength, yflux[i], label="observation")
    # plt.plot(
    #     wavelength,
    #     plotfunc(airmass[i], t0, t1, f, np.roll(f, -1), g[i]),
    #     label="combined",
    # )
    # plt.plot(wavelength, tell[i], label="telluric")
    # plt.plot(wavelength, f, label="stellar")
    # plt.legend()
    # plt.show()

    flux = f + g * (np.roll(f, -1, axis=0) - f)
    # flux = np.tile(f, (len(spectra), 1))
    flux = flux << spectra.flux.unit
    wave = np.tile(wavelength, (len(spectra), 1)) << spectra.wavelength.unit
    uncs = np.nanstd(flux, axis=0)
    uncs = np.tile(uncs, (len(spectra), 1))
    uncs = StdDevUncertainty(uncs, copy=False)

    spec = SpectrumArray(
        flux=flux,
        spectral_axis=wave,
        uncertainty=uncs,
        segments=spectra.segments,
        datetime=spectra.datetime,
        star=spectra.star,
        planet=spectra.planet,
        observatory_location=spectra.observatory_location,
        reference_frame="telescope",
    )

    tell = SpectrumArray(
        flux=tell,
        spectral_axis=wave,
        uncertainty=uncs,
        segments=spectra.segments,
        datetime=spectra.datetime,
        star=spectra.star,
        planet=spectra.planet,
        observatory_location=spectra.observatory_location,
        reference_frame="telescope",
    )

    # print("Shift observations to the telescope restframe")
    # spec = spec.shift("barycentric", inplace=True)

    # spec = spec.shift("telescope", inplace=True)
    spec = spec.resample(spectra.wavelength, method="linear", inplace=True)
    tell = tell.resample(spectra.wavelength, method="linear", inplace=True)

    return spec, tell