def test_raise_to_power(self, power):
        """Check that raising LogQuantities to some power is only possible when
        the physical unit is dimensionless, and that conversion is turned off
        when the resulting logarithmic unit (say, mag**2) is incompatible."""
        lq = u.Magnitude(np.arange(1., 4.)*u.Jy)

        if power == 0:
            assert np.all(lq ** power == 1.)
        elif power == 1:
            assert np.all(lq ** power == lq)
        else:
            with pytest.raises(u.UnitsError):
                lq ** power

        # with dimensionless, it works, but falls back to normal quantity
        # (except for power=1)
        lq2 = u.Magnitude(np.arange(10.))

        t = lq2**power
        if power == 0:
            assert t.unit is u.dimensionless_unscaled
            assert np.all(t.value == 1.)
        elif power == 1:
            assert np.all(t == lq2)
        else:
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit ** power
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(u.dimensionless_unscaled)
Exemple #2
0
    def test_raise_to_power(self, power):
        """Check that raising LogUnits to some power is only possible when the
        physical unit is dimensionless, and that conversion is turned off when
        the resulting logarithmic unit (such as mag**2) is incompatible."""
        lu1 = u.mag(u.Jy)

        if power == 0:
            assert lu1**power == u.dimensionless_unscaled
        elif power == 1:
            assert lu1**power == lu1
        else:
            with pytest.raises(u.UnitsError):
                lu1**power

        # With dimensionless, though, it works, but returns a normal unit.
        lu2 = u.mag(u.dimensionless_unscaled)

        t = lu2**power
        if power == 0:
            assert t == u.dimensionless_unscaled
        elif power == 1:
            assert t == lu2
        else:
            assert not isinstance(t, type(lu2))
            assert t == lu2.function_unit**power
            # also check we roundtrip
            t2 = t**(1. / power)
            assert t2 == lu2.function_unit
            with u.set_enabled_equivalencies(u.logarithmic()):
                assert_allclose(t2.to(u.dimensionless_unscaled, np.arange(3.)),
                                lu2.to(lu2.physical_unit, np.arange(3.)))
    def test_raise_to_power(self, power):
        """Check that raising LogUnits to some power is only possible when the
        physical unit is dimensionless, and that conversion is turned off when
        the resulting logarithmic unit (such as mag**2) is incompatible."""
        lu1 = u.mag(u.Jy)

        if power == 0:
            assert lu1 ** power == u.dimensionless_unscaled
        elif power == 1:
            assert lu1 ** power == lu1
        else:
            with pytest.raises(u.UnitsError):
                lu1 ** power

        # With dimensionless, though, it works, but returns a normal unit.
        lu2 = u.mag(u.dimensionless_unscaled)

        t = lu2**power
        if power == 0:
            assert t == u.dimensionless_unscaled
        elif power == 1:
            assert t == lu2
        else:
            assert not isinstance(t, type(lu2))
            assert t == lu2.function_unit**power
            # also check we roundtrip
            t2 = t**(1./power)
            assert t2 == lu2.function_unit
            with u.set_enabled_equivalencies(u.logarithmic()):
                assert_allclose(t2.to(u.dimensionless_unscaled, np.arange(3.)),
                                lu2.to(lu2.physical_unit, np.arange(3.)))
Exemple #4
0
    def test_raise_to_power(self, power):
        """Check that raising LogQuantities to some power is only possible when
        the physical unit is dimensionless, and that conversion is turned off
        when the resulting logarithmic unit (say, mag**2) is incompatible."""
        lq = u.Magnitude(np.arange(1., 4.) * u.Jy)

        if power == 0:
            assert np.all(lq**power == 1.)
        elif power == 1:
            assert np.all(lq**power == lq)
        else:
            with pytest.raises(u.UnitsError):
                lq**power

        # with dimensionless, it works, but falls back to normal quantity
        # (except for power=1)
        lq2 = u.Magnitude(np.arange(10.))

        t = lq2**power
        if power == 0:
            assert t.unit is u.dimensionless_unscaled
            assert np.all(t.value == 1.)
        elif power == 1:
            assert np.all(t == lq2)
        else:
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit**power
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(u.dimensionless_unscaled)
Exemple #5
0
    def to_color(self, wfb):
        r"""Express as a color index.


        Parameters
        ----------
        wfb : two-element `~astropy.units.Quantity` or tuple
            Wavelengths, frequencies, or bandpasses of the
            measurement.  If a bandpass, the effective wavelength of a
            solar spectrum will be used.  Bandpasses may be a string
            (name) or `~synphot.SpectralElement` (see
            :func:`~sbpy.spectroscopy.sun.Sun.filt`).

        Notes
        -----
        Color index is computed from:

        .. math::

            α = \frac{1 + S Δλ / 2}{1 - S * Δλ / 2}

        where S is the spectral gradient at the mean of λ0 and λ1, and:

        .. math::

            α = R(λ1) / R(λ0) = 10^{0.4 color_index}

            color_index = Δm - C_{sun}

        Δλ is typically expressed in units of 100 nm.


        Returns
        -------
        color : `~astropy.units.Quantity`
            ``blue - red`` color in magnitudes, dimensionless and
            excludes the solar color.


        Examples
        --------
        >>> import astropy.units as u
        >>> from sbpy.units import hundred_nm
        >>> S = SpectralGradient(10 * u.percent / hundred_nm,
        ...                      wave0=0.55 * u.um)
        >>> C = S.to_color((525, 575) * u.nm)
        >>> print(C)    # doctest: +FLOAT_CMP
        0.05429812423309064 mag

        """

        lambda_eff = self._lambda_eff(wfb)

        S = self.renormalize(lambda_eff.mean())
        dw = lambda_eff[0] - lambda_eff[1]
        beta = (S * dw / 2).decompose()  # dimensionless
        color = ((1 + beta) / (1 - beta)).to(u.mag, u.logarithmic())

        return color
Exemple #6
0
def test_logarithmic(log_unit):
    # check conversion of mag, dB, and dex to dimensionless and vice versa
    with pytest.raises(u.UnitsError):
        log_unit.to(1, 0.)
    with pytest.raises(u.UnitsError):
        u.dimensionless_unscaled.to(log_unit)

    assert log_unit.to(1, 0., equivalencies=u.logarithmic()) == 1.
    assert u.dimensionless_unscaled.to(log_unit,
                                       equivalencies=u.logarithmic()) == 0.
    # also try with quantities

    q_dex = np.array([0., -1., 1., 2.]) * u.dex
    q_expected = 10.**q_dex.value * u.dimensionless_unscaled
    q_log_unit = q_dex.to(log_unit)
    assert np.all(q_log_unit.to(1, equivalencies=u.logarithmic()) ==
                  q_expected)
    assert np.all(q_expected.to(log_unit, equivalencies=u.logarithmic()) ==
                  q_log_unit)
    with u.set_enabled_equivalencies(u.logarithmic()):
        assert np.all(np.abs(q_log_unit - q_expected.to(log_unit)) <
                      1.e-10*log_unit)
def test_logarithmic(log_unit):
    # check conversion of mag, dB, and dex to dimensionless and vice versa
    with pytest.raises(u.UnitsError):
        log_unit.to(1, 0.)
    with pytest.raises(u.UnitsError):
        u.dimensionless_unscaled.to(log_unit)

    assert log_unit.to(1, 0., equivalencies=u.logarithmic()) == 1.
    assert u.dimensionless_unscaled.to(log_unit,
                                       equivalencies=u.logarithmic()) == 0.
    # also try with quantities

    q_dex = np.array([0., -1., 1., 2.]) * u.dex
    q_expected = 10.**q_dex.value * u.dimensionless_unscaled
    q_log_unit = q_dex.to(log_unit)
    assert np.all(q_log_unit.to(1, equivalencies=u.logarithmic()) ==
                  q_expected)
    assert np.all(q_expected.to(log_unit, equivalencies=u.logarithmic()) ==
                  q_log_unit)
    with u.set_enabled_equivalencies(u.logarithmic()):
        assert np.all(np.abs(q_log_unit - q_expected.to(log_unit)) <
                      1.e-10*log_unit)
    def test_multiplication_division(self):
        """Check that multiplication/division with other quantities is only
        possible when the physical unit is dimensionless, and that this turns
        the result into a normal quantity."""
        lq = u.Magnitude(np.arange(1., 11.)*u.Jy)

        with pytest.raises(u.UnitsError):
            lq * (1.*u.m)

        with pytest.raises(u.UnitsError):
            (1.*u.m) * lq

        with pytest.raises(u.UnitsError):
            lq / lq

        for unit in (u.m, u.mag, u.dex):
            with pytest.raises(u.UnitsError):
                lq / unit

        lq2 = u.Magnitude(np.arange(1, 11.))

        with pytest.raises(u.UnitsError):
            lq2 * lq

        with pytest.raises(u.UnitsError):
            lq2 / lq

        with pytest.raises(u.UnitsError):
            lq / lq2

        # but dimensionless_unscaled can be cancelled
        r = lq2 / u.Magnitude(2.)
        assert r.unit == u.dimensionless_unscaled
        assert np.all(r.value == lq2.value/2.)

        # with dimensionless, normal units OK, but return normal quantities
        tf = lq2 * u.m
        tr = u.m * lq2
        for t in (tf, tr):
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit * u.m
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(lq2.unit.physical_unit)

        t = tf / (50.*u.cm)
        # now we essentially have the same quantity but with a prefactor of 2
        assert t.unit.is_equivalent(lq2.unit.function_unit)
        assert_allclose(t.to(lq2.unit.function_unit), lq2._function_view*2)
Exemple #9
0
    def test_multiplication_division(self):
        """Check that multiplication/division with other quantities is only
        possible when the physical unit is dimensionless, and that this turns
        the result into a normal quantity."""
        lq = u.Magnitude(np.arange(1., 11.) * u.Jy)

        with pytest.raises(u.UnitsError):
            lq * (1. * u.m)

        with pytest.raises(u.UnitsError):
            (1. * u.m) * lq

        with pytest.raises(u.UnitsError):
            lq / lq

        for unit in (u.m, u.mag, u.dex):
            with pytest.raises(u.UnitsError):
                lq / unit

        lq2 = u.Magnitude(np.arange(1, 11.))

        with pytest.raises(u.UnitsError):
            lq2 * lq

        with pytest.raises(u.UnitsError):
            lq2 / lq

        with pytest.raises(u.UnitsError):
            lq / lq2

        # but dimensionless_unscaled can be cancelled
        r = lq2 / u.Magnitude(2.)
        assert r.unit == u.dimensionless_unscaled
        assert np.all(r.value == lq2.value / 2.)

        # with dimensionless, normal units OK, but return normal quantities
        tf = lq2 * u.m
        tr = u.m * lq2
        for t in (tf, tr):
            assert not isinstance(t, type(lq2))
            assert t.unit == lq2.unit.function_unit * u.m
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(lq2.unit.physical_unit)

        t = tf / (50. * u.cm)
        # now we essentially have the same quantity but with a prefactor of 2
        assert t.unit.is_equivalent(lq2.unit.function_unit)
        assert_allclose(t.to(lq2.unit.function_unit), lq2._function_view * 2)
Exemple #10
0
    def test_inplace_addition_subtraction(self, other):
        """Check that inplace addition/subtraction with quantities with
        magnitude or MagUnit units works, and that it changes the physical
        units appropriately."""
        lq = u.Magnitude(np.arange(1., 10.) * u.Jy)
        other_physical = other.to(getattr(other.unit, 'physical_unit',
                                          u.dimensionless_unscaled),
                                  equivalencies=u.logarithmic())
        lq_sf = lq.copy()
        lq_sf += other
        assert_allclose(lq_sf.physical, lq.physical * other_physical)

        lq_df = lq.copy()
        lq_df -= other
        assert_allclose(lq_df.physical, lq.physical / other_physical)
    def test_inplace_addition_subtraction(self, other):
        """Check that inplace addition/subtraction with quantities with
        magnitude or MagUnit units works, and that it changes the physical
        units appropriately."""
        lq = u.Magnitude(np.arange(1., 10.)*u.Jy)
        other_physical = other.to(getattr(other.unit, 'physical_unit',
                                          u.dimensionless_unscaled),
                                  equivalencies=u.logarithmic())
        lq_sf = lq.copy()
        lq_sf += other
        assert_allclose(lq_sf.physical, lq.physical * other_physical)

        lq_df = lq.copy()
        lq_df -= other
        assert_allclose(lq_df.physical, lq.physical / other_physical)
    def test_multiplication_division(self):
        """Check that multiplication/division with other units is only
        possible when the physical unit is dimensionless, and that this
        turns the unit into a normal one."""
        lu1 = u.mag(u.Jy)

        with pytest.raises(u.UnitsError):
            lu1 * u.m

        with pytest.raises(u.UnitsError):
            u.m * lu1

        with pytest.raises(u.UnitsError):
            lu1 / lu1

        for unit in (u.dimensionless_unscaled, u.m, u.mag, u.dex):
            with pytest.raises(u.UnitsError):
                lu1 / unit

        lu2 = u.mag(u.dimensionless_unscaled)

        with pytest.raises(u.UnitsError):
            lu2 * lu1

        with pytest.raises(u.UnitsError):
            lu2 / lu1

        # But dimensionless_unscaled can be cancelled.
        assert lu2 / lu2 == u.dimensionless_unscaled

        # With dimensionless, normal units are OK, but we return a plain unit.
        tf = lu2 * u.m
        tr = u.m * lu2
        for t in (tf, tr):
            assert not isinstance(t, type(lu2))
            assert t == lu2.function_unit * u.m
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(lu2.physical_unit)
        # Now we essentially have a LogUnit with a prefactor of 100,
        # so should be equivalent again.
        t = tf / u.cm
        with u.set_enabled_equivalencies(u.logarithmic()):
            assert t.is_equivalent(lu2.function_unit)
            assert_allclose(t.to(u.dimensionless_unscaled, np.arange(3.)/100.),
                            lu2.to(lu2.physical_unit, np.arange(3.)))

        # If we effectively remove lu1, a normal unit should be returned.
        t2 = tf / lu2
        assert not isinstance(t2, type(lu2))
        assert t2 == u.m
        t3 = tf / lu2.function_unit
        assert not isinstance(t3, type(lu2))
        assert t3 == u.m

        # For completeness, also ensure non-sensical operations fail
        with pytest.raises(TypeError):
            lu1 * object()
        with pytest.raises(TypeError):
            slice(None) * lu1
        with pytest.raises(TypeError):
            lu1 / []
        with pytest.raises(TypeError):
            1 / lu1
Exemple #13
0
    def from_color(cls, wfb, color):
        r"""Initialize from observed color.


        Parameters
        ----------
        wfb : two-element `~astropy.units.Quantity` or tuple
            Wavelengths, frequencies, or bandpasses of the
            measurement.  If a bandpass, the effective wavelength of a
            solar spectrum will be used.  Bandpasses may be a string
            (name) or `~synphot.SpectralElement` (see
            :func:`~sbpy.spectroscopy.sun.Sun.filt`).

        color : `~astropy.units.Quantity`, optional
            Observed color, ``blue - red`` for magnitudes, ``red
            / blue`` for linear units.  Must be dimensionless and have
            the solar color removed.


        Notes
        -----

        Computes spectral gradient from ``color_index``.
        ``wfb[0]`` is the blue-ward of the two measurements

        .. math::

           S &= \frac{R(λ1) - R(λ0)}{R(λ1) + R(λ0)} \frac{2}{Δλ} \\
           &= \frac{α - 1}{α + 1} \frac{2}{Δλ}

        where R(λ) is the reflectivity, and:

        .. math::

            α = R(λ1) / R(λ0) = 10^{0.4 color_index}

            color_index = Δm - C_{sun}

        Δλ is typically expressed in units of 100 nm.


        Examples
        --------
        >>> import astropy.units as u
        >>> w = [0.4719, 0.6185] * u.um
        >>> S = SpectralGradient.from_color(w, 0.10 * u.mag)
        >>> print(S)                            # doctest: +FLOAT_CMP
        6.27819572 % / 100 nm

        """
        from ..units import hundred_nm

        lambda_eff = SpectralGradient._lambda_eff(wfb)

        try:
            # works for u.Magnitudes and dimensionless u.Quantity
            alpha = u.Quantity(color, u.dimensionless_unscaled)
        except u.UnitConversionError:
            # works for u.mag
            alpha = color.to(u.dimensionless_unscaled, u.logarithmic())

        dw = lambda_eff[0] - lambda_eff[1]
        S = ((2 / dw * (alpha - 1) / (alpha + 1)).to(u.percent / hundred_nm))

        return SpectralGradient(S, wave=lambda_eff)
Exemple #14
0
    def to_ref(self, eph, normalized=None, append_results=False, **kwargs):
        """Calculate phase function in average bidirectional reflectance

        Parameters
        ----------
        eph : `~sbpy.data.Ephem`, numbers, iterables of numbers, or
            `~astropy.units.Quantity`
            If `~sbpy.data.Ephem` or dict_like, ephemerides of the object that
            can include phase angle, heliocentric and geocentric distances via
            keywords `phase`, `r` and `delta`.  If float or array_like, then
            the phase angle of object.  If any distance (heliocentric and
            geocentric) is not provided, then it will be assumed to be 1 au.
            If no unit is provided via type `~astropy.units.Quantity`, then
            radians is assumed for phase angle, and au is assumed for
            distances.
        normalized : number, `~astropy.units.Quantity`
            The angle to which the reflectance is normalized.
        append_results : bool
            Controls the return of this method.
        **kwargs : optional parameters accepted by
            `astropy.modeling.Model.__call__`

        Returns
        -------
        `~astropy.units.Quantity`, array if ``append_results == False``
        `~sbpy.data.Ephem` if ``append_results == True``

        When ``append_results == False``: The calculated reflectance will be
        returned.

        When ``append_results == True``:  If ``eph`` is a `~sbpy.data.Ephem`
        object, then the calculated reflectance will be appended to ``eph`` as
        a new column.  Otherwise a new `~sbpy.data.Ephem` object is created to
        contain the input ``eph`` and the calculated reflectance in two
        columns.

        Examples
        --------
        >>> import numpy as np
        >>> from astropy import units as u
        >>> from sbpy.calib import solar_fluxd
        >>> from sbpy.photometry import HG
        >>> from sbpy.data import Ephem
        >>> ceres_hg = HG(3.34 * u.mag, 0.12, radius = 480 * u.km, wfb= 'V')
        >>> # parameter `eph` as `~sbpy.data.Ephem` type
        >>> eph = Ephem.from_dict({'alpha': np.linspace(0,np.pi*0.9,200)*u.rad,
        ...              'r': np.repeat(2.7*u.au, 200),
        ...              'delta': np.repeat(1.8*u.au, 200)})
        >>> with solar_fluxd.set({'V': -26.77 * u.mag}):
        ...     ref1 = ceres_hg.to_ref(eph)
        ...     # parameter `eph` as numpy array
        ...     pha = np.linspace(0, 170, 200) * u.deg
        ...     ref2 = ceres_hg.to_ref(pha)
        """
        self._check_unit()
        pha = eph['alpha']
        if len(pha) == 1:
            pha = pha[0]
        out = self(pha, **kwargs)
        if normalized is not None:
            norm = self(normalized, **kwargs)
        if self._unit == 'ref':
            if normalized is not None:
                out /= norm
        else:
            if normalized is None:
                if self.radius is None:
                    raise ValueError(
                        'Cannot calculate phase function in reflectance unit'
                        ' because the size of object is unknown.  Normalized'
                        ' phase function can be calculated.')
                if self.wfb is None:
                    raise ValueError('Wavelength/Frequency/Band is unknown.')
                out = out.to('1/sr', reflectance(self.wfb,
                        cross_section=np.pi*self.radius**2))
            else:
                out = out - norm
                out = out.to('', u.logarithmic())
        if append_results:
            name = 'ref'
            i = 1
            while name in eph.field_names:
                name = 'ref'+str(i)
                i += 1
            eph.table.add_column(Column(out, name=name))
            return eph
        else:
            return out
Exemple #15
0
    def to_mag(self, eph, unit=None, append_results=False, **kwargs):
        """Calculate phase function in magnitude

        Parameters
        ----------
        eph : `~sbpy.data.Ephem`, numbers, iterables of numbers, or
            `~astropy.units.Quantity`
            If `~sbpy.data.Ephem` or dict_like, ephemerides of the object that
            can include phase angle, heliocentric and geocentric distances via
            keywords `phase`, `r` and `delta`.  If float or array_like, then
            the phase angle of object.  If any distance (heliocentric and
            geocentric) is not provided, then it will be assumed to be 1 au.
            If no unit is provided via type `~astropy.units.Quantity`, then
            radians is assumed for phase angle, and au is assumed for
            distances.
        unit : `astropy.units.mag`, `astropy.units.MagUnit`, optional
            The unit of output magnitude.  The corresponding solar magnitude
            must be available either through `~sbpy.calib.sun` module or set
            by `~sbpy.calib.solar_fluxd.set`.
        append_results : bool, optional
            Controls the return of this method.
        **kwargs : optional parameters accepted by
            `astropy.modeling.Model.__call__`

        Returns
        -------
        `~astropy.units.Quantity`, array if ``append_results == False``
        `~sbpy.data.Ephem` if ``append_results == True``

        When ``append_results == False``: The calculated magnitude will be
        returned.

        When ``append_results == True``:  If ``eph`` is a `~sbpy.data.Ephem`
        object, then the calculated magnitude will be appended to ``eph`` as
        a new column.  Otherwise a new `~sbpy.data.Ephem` object is created to
        contain the input ``eph`` and the calculated magnitude in two columns.

        Examples
        --------
        >>> import numpy as np
        >>> from astropy import units as u
        >>> from sbpy.photometry import HG
        >>> from sbpy.data import Ephem
        >>> ceres_hg = HG(3.34 * u.mag, 0.12)
        >>> # parameter `eph` as `~sbpy.data.Ephem` type
        >>> eph = Ephem.from_dict({'alpha': np.linspace(0,np.pi*0.9,200)*u.rad,
        ...              'r': np.repeat(2.7*u.au, 200),
        ...              'delta': np.repeat(1.8*u.au, 200)})
        >>> mag1 = ceres_hg.to_mag(eph)
        >>> # parameter `eph` as numpy array
        >>> pha = np.linspace(0, 170, 200) * u.deg
        >>> mag2 = ceres_hg.to_mag(pha)
        """
        self._check_unit()
        pha = eph['alpha']
        if len(pha) == 1:
            pha = pha[0]
        out = self(pha, **kwargs)
        if self._unit == 'ref':
            if unit is None:
                raise ValueError('Magnitude unit is not specified.')
            if self.radius is None:
                raise ValueError(
                    'Cannot calculate phase function in magnitude because the'
                    ' size of object is unknown.')
            if self.wfb is None:
                raise ValueError('Wavelength/Frequency/Band is unknown.')
            out = out.to(unit, reflectance(self.wfb,
                    cross_section=np.pi * self.radius**2))
        dist_corr = self._distance_module(eph)
        dist_corr = u.Quantity(dist_corr).to(u.mag, u.logarithmic())
        out = out - dist_corr
        if append_results:
            name = 'mag'
            i = 1
            while name in eph.field_names:
                name = 'mag'+str(i)
                i += 1
            eph.table.add_column(Column(out, name=name))
            return eph
        else:
            return out
Exemple #16
0
    def from_obs(cls, obs, fitter, fields='mag', init=None, **kwargs):
        """Instantiate a photometric model class object from data

        Parameters
        ----------
        obs : `~sbpy.data.DataClass`, dict_like
            If `~sbpy.data.DataClass` or dict_like, must contain
            ``'phaseangle'`` or the equivalent names (see
            `~sbpy.data.DataClass`).  If any distance (heliocentric and
            geocentric) is provided, then they will be used to correct
            magnitude to 1 au before fitting.
        fitter : `~astropy.modeling.fitting.Fitter`
            The fitter to be used for fitting.
        fields : str or array_like of str
            The field name or names in ``obs`` to be fitted.  If an array_like
            str, then multiple fields will be fitted one by one and a model
            set will be returned.  In this case, ``.meta['fields']`` of the
            returned object contains the names of fields fitted.
        init : numpy array, `~astropy.units.Quantity`, optional
            The initial parameters for model fitting.  Its first dimension has
            the length of the model parameters, and its second dimension has
            the length of ``n_model`` if multiple models are fitted.
        **kwargs : optional parameters accepted by `fitter()`.
            Note that the magnitude uncertainty can also be supplied to the fit
            via `weights` keyword for all fitters provided by
            `~astropy.modeling.fitting`.

        Returns
        -------
        Object of `DiskIntegratedPhaseFunc` subclass
            The best-fit model class object.

        Examples
        --------
        >>> from sbpy.photometry import HG # doctest: +SKIP
        >>> from sbpy.data import Misc # doctest: +SKIP
        >>> from astropy.modeling.fitting import LevMarLSQFitter
        >>> fitter = LevMarLSQFitter()
        >>> obs = Misc.mpc_observations('Bennu') # doctest: +SKIP
        >>> hg = HG() # doctest: +SKIP
        >>> best_hg = hg.from_obs(obs, eph['mag'], fitter) # doctest: +SKIP
        """
        pha = obs['alpha']

        if isinstance(fields, (str, bytes)):
            n_models = 1
        else:
            n_models = len(fields)
        if init is not None:
            init = np.asanyarray(init)

        dist_corr = cls()._distance_module(obs)
        if n_models == 1:
            mag = obs[fields]
            if isinstance(mag, u.Quantity):
                dist_corr = u.Quantity(dist_corr).to(u.mag, u.logarithmic())
            else:
                dist_corr = -2.5 * alog10(dist_corr)
            mag0 = mag + dist_corr
            if init is None:
                m0 = cls()
            else:
                m0 = cls(*init)
            return fitter(m0, pha, mag0, **kwargs)
        else:
            if init is not None:
                sz1 = init.shape
                sz2 = len(cls.param_names), n_models
                if sz1 != sz2:
                    raise ValueError('`init` must have a shape of ({}, {}),'
                                     ' shape {} is given.'.format(sz2[0],
                                     sz2[1], sz1))
            par = np.zeros((len(cls.param_names), n_models))
            for i in range(n_models):
                mag = obs[fields[i]]
                if isinstance(mag, u.Quantity):
                    dist_corr1 = u.Quantity(dist_corr).to(u.mag,
                            u.logarithmic())
                else:
                    dist_corr1 = -2.5 * alog10(dist_corr)
                mag0 = mag + dist_corr1
                if init is None:
                    m0 = cls()
                else:
                    m0 = cls(*init[:, i])
                m = fitter(m0, pha, mag0, **kwargs)
                par[:, i] = m.parameters
            pars_list = []
            for i, p_name in enumerate(cls.param_names):
                p = getattr(m, p_name)
                if p.unit is None:
                    pars_list.append(par[i])
                else:
                    pars_list.append(par[i]*p.unit)
            model = cls(*pars_list, n_models=n_models)
            if not isinstance(model.meta, dict):
                model.meta = OrderedDict()
            model.meta['fields'] = fields
            return model
Exemple #17
0
def reflectance(wfb, cross_section=None, reflectance=None, **kwargs):
    """Reflectance related equivalencies.

    Supports conversion from/to reflectance and scattering
    cross-section to/from total flux or magnitude at 1 au for both
    heliocentric and observer distances.  Uses `sbpy`'s photometric
    calibration system: `~sbpy.calib.solar_spectrum` and
    `~sbpy.calib.solar_fluxd`.

    Spectral flux density equivalencies for Vega are automatically
    used, if possible.  Dimensionless logarithmic units are also supported
    if the corresponding solar value is set by `~sbpy.calib.solar_fluxd.set`.


    Parameters
    ----------
    wfb : `astropy.units.Quantity`, `synphot.SpectralElement`, string
        Wavelength, frequency, or a bandpass corresponding to the flux
        density being converted.

    cross_section : `astropy.units.Qauntity`, optional
        Total scattering cross-section.  One of `cross_section` or
        `reflectance` is required.

    reflectance : `astropy.units.Quantity`, optional
        Average reflectance.  One of `cross_section` or `reflectance`
        is required.

    **kwargs
        Keyword arguments for `~Sun.observe()`.


    Returns
    -------
    equiv : list
        List of equivalencies


    Examples
    --------
    Convertion between scattering cross-section and reflectance
    >>> import numpy as np
    >>> from astropy import units as u
    >>> from sbpy.units import reflectance, VEGAmag, spectral_density_vega
    >>> from sbpy.calib import solar_fluxd, vega_fluxd
    >>>
    >>> solar_fluxd.set({'V': -26.77471503 * VEGAmag})
    ...                                             # doctest: +IGNORE_OUTPUT
    >>> vega_fluxd.set({'V': 3.5885e-08 * u.Unit('W / (m2 um)')})
    ...                                             # doctest: +IGNORE_OUTPUT
    >>> mag = 3.4 * VEGAmag
    >>> cross_sec = np.pi * (460 * u.km)**2
    >>> ref = mag.to('1/sr', reflectance('V', cross_section=cross_sec))
    >>> print('{0:.4f}'.format(ref))
    0.0287 1 / sr
    >>> mag1 = ref.to(VEGAmag, reflectance('V', cross_section=cross_sec))
    >>> print('{0:.2f}'.format(mag1))
    3.40 mag(VEGA)

    >>> # Convertion between magnitude and scattering cross-section
    >>> ref = 0.0287 / u.sr
    >>> cross_sec = mag.to('km2', reflectance('V', reflectance=ref))
    >>> radius = np.sqrt(cross_sec/np.pi)
    >>> print('{0:.2f}'.format(radius))
    459.69 km
    >>> mag2 = cross_sec.to(VEGAmag, reflectance('V', reflectance=ref))
    >>> print('{0:.2f}'.format(mag2))
    3.40 mag(VEGA)

    """

    # Solar flux density at 1 au in different units
    f_sun = []
    sun = Sun.from_default()
    for unit in ('W/(m2 um)', 'W/(m2 Hz)', VEGA):
        try:
            f_sun.append(sun.observe(wfb, unit=unit, **kwargs))
        except SinglePointSpectrumError:
            f_sun.append(sun(wfb, unit=unit))
        except (u.UnitConversionError, FilterLookupError):
            pass
    if len(f_sun) == 0:
        try:
            f_sun.append(sun.observe(wfb, **kwargs))
        except (SinglePointSpectrumError, u.UnitConversionError,
                FilterLookupError):
            pass

    # pass fluxd0 as an optional argument to dereference it,
    # otherwise both equivalencies will use the fluxd0 for
    # the last item in f_sun
    equiv = []
    if cross_section is not None:
        xsec = cross_section.to('au2').value
        for fluxd0 in f_sun:
            if fluxd0.unit in [u.mag, u.dB, u.dex]:
                equiv.append(
                    (fluxd0.unit, u.sr**-1, lambda mag, mag0=fluxd0.value: u
                     .Quantity(mag - mag0, fluxd0.unit).to(
                         '', u.logarithmic()).value / xsec,
                     lambda ref, mag0=fluxd0.value: u.Quantity(ref * xsec).to(
                         fluxd0.unit, u.logarithmic()).value + mag0))
            else:
                equiv.append(
                    (fluxd0.unit, u.sr**-1,
                     lambda fluxd, fluxd0=fluxd0.value: fluxd /
                     (fluxd0 * xsec),
                     lambda ref, fluxd0=fluxd0.value: ref * fluxd0 * xsec))
    elif reflectance is not None:
        ref = reflectance.to('1/sr').value
        au2km = (const.au.to('km')**2).value
        for fluxd0 in f_sun:
            if fluxd0.unit in [u.mag, u.dB, u.dex]:
                equiv.append(
                    (fluxd0.unit, u.km**2, lambda mag, mag0=fluxd0.value: u
                     .Quantity(mag - mag0, fluxd0.unit).to(
                         '', u.logarithmic()).value / ref * au2km, lambda xsec,
                     mag0=fluxd0.value: u.Quantity(ref * xsec / au2km).to(
                         fluxd0.unit, u.logarithmic()).value + mag0))
            else:
                equiv.append(
                    (fluxd0.unit, u.km**2, lambda fluxd, fluxd0=fluxd0.value:
                     (fluxd / (fluxd0 * ref) * au2km),
                     lambda xsec, fluxd0=fluxd0.value:
                     (fluxd0 * ref * xsec / au2km)))
    return equiv
Exemple #18
0
    def test_multiplication_division(self):
        """Check that multiplication/division with other units is only
        possible when the physical unit is dimensionless, and that this
        turns the unit into a normal one."""
        lu1 = u.mag(u.Jy)

        with pytest.raises(u.UnitsError):
            lu1 * u.m

        with pytest.raises(u.UnitsError):
            u.m * lu1

        with pytest.raises(u.UnitsError):
            lu1 / lu1

        for unit in (u.dimensionless_unscaled, u.m, u.mag, u.dex):
            with pytest.raises(u.UnitsError):
                lu1 / unit

        lu2 = u.mag(u.dimensionless_unscaled)

        with pytest.raises(u.UnitsError):
            lu2 * lu1

        with pytest.raises(u.UnitsError):
            lu2 / lu1

        # But dimensionless_unscaled can be cancelled.
        assert lu2 / lu2 == u.dimensionless_unscaled

        # With dimensionless, normal units are OK, but we return a plain unit.
        tf = lu2 * u.m
        tr = u.m * lu2
        for t in (tf, tr):
            assert not isinstance(t, type(lu2))
            assert t == lu2.function_unit * u.m
            with u.set_enabled_equivalencies(u.logarithmic()):
                with pytest.raises(u.UnitsError):
                    t.to(lu2.physical_unit)
        # Now we essentially have a LogUnit with a prefactor of 100,
        # so should be equivalent again.
        t = tf / u.cm
        with u.set_enabled_equivalencies(u.logarithmic()):
            assert t.is_equivalent(lu2.function_unit)
            assert_allclose(
                t.to(u.dimensionless_unscaled,
                     np.arange(3.) / 100.),
                lu2.to(lu2.physical_unit, np.arange(3.)))

        # If we effectively remove lu1, a normal unit should be returned.
        t2 = tf / lu2
        assert not isinstance(t2, type(lu2))
        assert t2 == u.m
        t3 = tf / lu2.function_unit
        assert not isinstance(t3, type(lu2))
        assert t3 == u.m

        # For completeness, also ensure non-sensical operations fail
        with pytest.raises(TypeError):
            lu1 * object()
        with pytest.raises(TypeError):
            slice(None) * lu1
        with pytest.raises(TypeError):
            lu1 / []
        with pytest.raises(TypeError):
            1 / lu1