Exemplo n.º 1
0
def test_both_gradients_possible(test_spec):
    wav = test_spec[0]
    flux = test_spec[1]
    transmission = test_spec[2]
    __ = rv_precision(wav, flux, mask=transmission, grad=False).value
    __ = rv_precision(wav, flux, mask=transmission, grad=True).value
    assert True
Exemplo n.º 2
0
def test_sqrt_sum_wis_with_mask_with_unit_fails(test_spec, wav_unit, flux_unit,
                                                trans_unit2):
    """Assert a transmission with a unit fails with type error."""
    wav = test_spec[0] * wav_unit
    flux = test_spec[1] * flux_unit
    transmission = np.random.rand(len(wav)) * trans_unit2

    with pytest.raises(TypeError):
        sqrt_sum_wis(wav, flux, mask=transmission**2)

    with pytest.raises(TypeError):
        rv_precision(wav, flux, mask=transmission**2)
Exemplo n.º 3
0
def test_transmission_reduces_precision(test_spec):
    """Check that a transmission vector reduces precision calculation."""
    wav = test_spec[0]
    flux = test_spec[1]
    transmission = test_spec[2]

    # Value should be less then normal if trans <=1
    if transmission is not None:
        assert rv_precision(wav, flux, mask=None) < rv_precision(
            wav, flux, mask=transmission)
    # mask=None is the same as mask of all 1.
    assert rv_precision(wav, flux,
                        mask=None) == rv_precision(wav,
                                                   flux,
                                                   mask=np.ones_like(wav))
Exemplo n.º 4
0
def RVprec_calc_weights_masked(
    wavelength: ndarray, flux: ndarray, mask: Optional[ndarray] = None, **kwargs
) -> Quantity:
    """RV precision setting weights of telluric lines to zero.

    Instead of splitting the spectra after every telluric line and
    individually calculating precision and taking the weighted average
    this just sets the pixel weights to zero.

    Parameters
    ----------
    wavelength: ndarray
        Wavelength array
    flux: ndarray
        Flux array
    mask: array or None
        Mask of transmission cuts. Zero values are excluded and used to cut up
        the spectrum.

    Returns
    -------
    RV_value: Quantity scalar
        RV precision.

    RVprec_calc_weights_masked is just a wrapper around rv_precision.

    """
    return rv_precision(wavelength, flux, mask=mask, **kwargs)
Exemplo n.º 5
0
def test_rvprev_calc_with_lists(test_spec):
    """Test that it can handle list input also."""
    wav = list(test_spec[0])
    flux = list(test_spec[1])
    mask = test_spec[2]
    rv = rv_precision(wav, flux, mask)
    assert not hasattr(rv.value, "__len__")  # assert value is a scalar
    assert isinstance(rv, u.Quantity)
    assert rv.unit == m_per_s
Exemplo n.º 6
0
def test_relation_of_rv_to_sqrtsumwis(test_spec, wav_unit, flux_unit, trans_unit):
    """Test relation of sqrtsumwis to rv_precision."""
    wav = test_spec[0] * wav_unit
    flux = test_spec[1] * flux_unit
    mask = test_spec[2]
    if mask is not None:
        mask *= trans_unit
        mask = mask ** 2
    assert np.all(
        rv_precision(wav, flux, mask=mask) == c / sqrt_sum_wis(wav, flux, mask=mask)
    )
Exemplo n.º 7
0
def test_rvprev_calc(test_spec, wav_unit, flux_unit, trans_unit):
    """Test that rv_precision can handle inputs as Quantities or unitless and returns a scalar Quantity."""
    wav = test_spec[0] * wav_unit
    flux = test_spec[1] * flux_unit
    mask = test_spec[2]
    if test_spec[2] is not None:
        mask *= trans_unit

    rv = rv_precision(wav, flux, mask)
    assert rv.unit == m_per_s
    assert not hasattr(rv.value, "__len__")  # assert value is a scalar
    assert isinstance(rv, u.Quantity)
Exemplo n.º 8
0
def test_sqrt_sum_wis_transmission_outofbounds(test_spec, wav_unit, flux_unit):
    """Transmission must be within 0-1."""
    wav = test_spec[0] * wav_unit
    flux = test_spec[1] * flux_unit
    mask_1 = np.random.randn(len(wav))
    mask_2 = np.random.rand(len(wav))

    mask_1[0] = 5  # Outside 0-1
    mask_2[-1] = -2  # Outside 0-1

    # Higher value
    with pytest.raises(ValueError):
        rv_precision(wav, flux, mask=mask_1)

    with pytest.raises(ValueError):
        sqrt_sum_wis(wav, flux, mask=mask_1)

        # Lower value
    with pytest.raises(ValueError):
        sqrt_sum_wis(wav, flux, mask=mask_2)

    with pytest.raises(ValueError):
        sqrt_sum_wis(wav, flux, mask=mask_2)
Exemplo n.º 9
0
def test_increments_rv_accumulate_same_as_full(real_spec, increment_percent, no_mask):
    """Assuming that the weighted rv from the steps should equal the rv from the band."""
    wav, flux, mask = real_spec[0], real_spec[1], real_spec[2]
    if no_mask:
        # Try with mask= None also.
        mask = None

    rv_full = rv_precision(wav, flux, mask=mask).value
    x, incremented_rv = incremental_rv(wav, flux, mask=mask, percent=increment_percent)
    incremented_weighted = weighted_error(incremented_rv)

    assert np.round(rv_full, 2) == np.round(incremented_weighted, 2)
    assert x[0] > wav[0]
    assert [-1] < wav[-1]
Exemplo n.º 10
0
def do_analysis(
    star_params,
    vsini: float,
    R: float,
    band: str,
    sampling: float = 3.0,
    conv_kwargs=None,
    snr: float = 100.0,
    ref_band: str = "J",
    rv: float = 0.0,
    air: bool = False,
    model: str = "aces",
    verbose: bool = False,
) -> Tuple[Quantity, ...]:
    """Calculate RV precision and Quality for specific parameter set.

    Parameters
    ----------
    star_param:
        Stellar parameters [temp, logg, feh, alpha] for phoenix model libraries.
    vsini: float
       Stellar equatorial rotation.
    R: float
        Instrumental resolution.
    band: str
        Spectral band.
    sampling: float (default=False)
        Per pixel sampling (after convolutions)
    conv_kwargs: Dict (default=None)
        Arguments specific for the convolutions,
        'epsilon', 'fwhm_lim', 'num_procs', 'normalize', 'verbose'.
    snr: float (default=100)
        SNR normalization level. SNR per pixel and the center of the ref_band.
    ref_band: str (default="J")
        Reference band for SNR normalization.
    rv: float
        Radial velocity in km/s (default = 0.0).
    air: bool
        Get model in air wavelengths (default=False).
    model: str
        Name of synthetic library (aces, btsettl) to use. Default = 'aces'.
    verbose:
        Enable verbose (default=False).

    Returns
    -------
    q: astropy.Quality
     Spectral quality.
    result_1: astropy.Quality
        RV precision under condition 1.
    result_2 : astropy.Quality
        RV precision under condition 2.
    result_3: astropy.Quality
        RV precision under condition 3.

    Notes
    -----
        We apply the radial velocity doppler shift after
            - convolution (rotation and resolution)
            - resampling
            - SNR normalization.

        in this way the RV only effects the precision due to the telluric mask interaction.
        Physically the RV should be applied between the rotational and instrumental convolution
        but we assume this effect is negligible.

    """
    if conv_kwargs is None:
        conv_kwargs = {
            "epsilon": 0.6,
            "fwhm_lim": 5.0,
            "num_procs": num_cpu_minus_1,
            "normalize": True,
            "verbose": verbose,
        }

    if ref_band.upper() == "SELF":
        ref_band = band

    model = check_model(model)

    if model == "aces":
        wav, flux = load_aces_spectrum(star_params, photons=True, air=air)
    elif model == "btsettl":
        wav, flux = load_btsettl_spectrum(star_params, photons=True, air=air)
    else:
        raise Exception("Invalid model name reached.")

    wav_grid, sampled_flux = convolve_and_resample(wav, flux, vsini, R, band,
                                                   sampling, **conv_kwargs)

    # Doppler shift
    try:
        if rv != 0:
            sampled_flux = doppler_shift_flux(wav_grid, sampled_flux, vel=rv)
    except Exception as e:
        print("Doppler shift was unsuccessful")
        raise e

    # Scale normalization for precision
    wav_ref, sampled_ref = convolve_and_resample(wav, flux, vsini, R, ref_band,
                                                 sampling, **conv_kwargs)
    snr_normalize = snr_constant_band(wav_ref,
                                      sampled_ref,
                                      snr=snr,
                                      band=ref_band,
                                      sampling=sampling,
                                      verbose=verbose)
    sampled_flux = sampled_flux / snr_normalize

    if (ref_band == band) and verbose:
        mid_point = band_middle(ref_band)
        index_ref = np.searchsorted(
            wav_grid,
            mid_point)  # searching for the index closer to 1.25 micron
        snr_estimate = np.sqrt(
            np.sum(sampled_flux[index_ref - 1:index_ref + 2]))
        print(
            "\tSanity Check: The S/N at {0:4.02} micron = {1:4.2f}, (should be {2:g})."
            .format(mid_point, snr_estimate, snr))

    # Load Atmosphere for this band.
    atm = Atmosphere.from_band(band=band, bary=True).at(wav_grid)
    assert np.allclose(atm.wl,
                       wav_grid), "The atmosphere does not cover the wav_grid"

    # Spectral Quality/Precision
    q = quality(wav_grid, sampled_flux)

    # Precision given by the first condition:
    result_1 = rv_precision(wav_grid, sampled_flux, mask=None)

    # Precision as given by the second condition
    result_2 = rv_precision(wav_grid, sampled_flux, mask=atm.mask)

    # Precision as given by the third condition: M = T**2
    result_3 = rv_precision(wav_grid, sampled_flux, mask=atm.transmission**2)

    # Turn quality back into a Quantity (to give it a .value method)
    q = q * u.dimensionless_unscaled
    return q, result_1, result_2, result_3
Exemplo n.º 11
0
def RVprec_calc_masked(
    wavelength: Union[List[List[Any]], ndarray],
    flux: Union[ndarray, List[List[Any]]],
    mask: Optional[ndarray] = None,
    **kwargs,
) -> Quantity:
    """RV precision for split apart spectra.

    The same as rv_precision, but now wavelength and flux are organized into
    chunks according to the mask and the weighted average formula is used to
    calculate the combined precision.

    When considering the average RV as delivered by several slices of a
    spectrum, the error on the average is given by the error on a weighted
    average.
        mean(RV_rms) = 1 / sqrt(sum_i((1 / RV_rms(i))**2))

    Parameters
    ----------
    wavelength: array-like or list(array-like)
        Wavelength values of chunks.
    flux: array-like or list(array-like)
        Flux values of the chunks.
    mask: array-like of bool or None
        Mask of transmission cuts. Zero values are excluded and used to cut up
        the spectrum.
    kwargs:
        Kwargs for sqrt_sum_wis

    Returns
    -------
    RV_value: Quantity scalar
        Weighted average RV value of spectral chunks.

    Notes
    -----
    A "clump" is defined as a contiguous region of the array.
    Solution for clumping comes from
    https://stackoverflow.com/questions/14605734/numpy-split-1d-array-of-chunks-separated-by-nans-into-a-list-of-the-chunks
    """
    if mask is not None:
        # Turn wavelength and flux into masked arrays
        wavelength_clumps, flux_clumps = mask_clumping(wavelength, flux, mask)

    else:
        # When given a already clumped solution and no mask given.
        assert isinstance(wavelength, list)
        assert isinstance(flux, list)
        wavelength_clumps = wavelength
        flux_clumps = flux

    # Turn an ndarray into quantity array.
    # Need to use np.zeros instead of np.empty. Unassigned zeros are removed after with nonzero.
    # The "empty" values (1e-300) do not get removed and effect precision
    slice_rvs = Quantity(
        np.zeros(len(wavelength_clumps), dtype=float), unit=u.meter / u.second
    )  # Radial velocity of each slice

    for i, (wav_slice, flux_slice) in enumerate(zip(wavelength_clumps, flux_clumps)):
        if len(wav_slice) == 1:
            # Results in infinite rv, can not determine the slope of single point.
            continue

        else:
            wav_slice = np.asarray(wav_slice)
            flux_slice = np.asarray(flux_slice)
            slice_rvs[i] = rv_precision(wav_slice, flux_slice, **kwargs)

    # Zeros created from the initial empty array, when skipping single element chunks)
    slice_rvs = slice_rvs[np.nonzero(slice_rvs)]  # Only use nonzero values.
    return 1.0 / (np.sqrt(np.nansum((1.0 / slice_rvs) ** 2.0)))