# coding: utf-8
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Test the Quantity class and related."""

import copy
import decimal
import numbers
import pickle
from fractions import Fraction

import numpy as np
import pytest
from numpy.testing import assert_allclose, assert_array_almost_equal, assert_array_equal

from astropy import units as u
from astropy.units.quantity import _UNIT_NOT_INITIALISED
from astropy.utils import isiterable, minversion
from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyWarning

""" The Quantity class will represent a number + unit + uncertainty """


class TestQuantityCreation:

    def test_1(self):
        # create objects through operations with Unit objects:

        quantity = 11.42 * u.meter  # returns a Quantity object
        assert isinstance(quantity, u.Quantity)
        quantity = u.meter * 11.42  # returns a Quantity object
        assert isinstance(quantity, u.Quantity)

        quantity = 11.42 / u.meter
        assert isinstance(quantity, u.Quantity)
        quantity = u.meter / 11.42
        assert isinstance(quantity, u.Quantity)

        quantity = 11.42 * u.meter / u.second
        assert isinstance(quantity, u.Quantity)

        with pytest.raises(TypeError):
            quantity = 182.234 + u.meter

        with pytest.raises(TypeError):
            quantity = 182.234 - u.meter

        with pytest.raises(TypeError):
            quantity = 182.234 % u.meter

    def test_2(self):

        # create objects using the Quantity constructor:
        _ = u.Quantity(11.412, unit=u.meter)
        _ = u.Quantity(21.52, "cm")
        q3 = u.Quantity(11.412)

        # By default quantities that don't specify a unit are unscaled
        # dimensionless
        assert q3.unit == u.Unit(1)

        with pytest.raises(TypeError):
            u.Quantity(object(), unit=u.m)

    def test_3(self):
        # with pytest.raises(u.UnitsError):
        with pytest.raises(ValueError):  # Until @mdboom fixes the errors in units
            u.Quantity(11.412, unit="testingggg")

    def test_nan_inf(self):
        # Not-a-number
        q = u.Quantity('nan', unit='cm')
        assert np.isnan(q.value)

        q = u.Quantity('NaN', unit='cm')
        assert np.isnan(q.value)

        q = u.Quantity('-nan', unit='cm')  # float() allows this
        assert np.isnan(q.value)

        q = u.Quantity('nan cm')
        assert np.isnan(q.value)
        assert q.unit == u.cm

        # Infinity
        q = u.Quantity('inf', unit='cm')
        assert np.isinf(q.value)

        q = u.Quantity('-inf', unit='cm')
        assert np.isinf(q.value)

        q = u.Quantity('inf cm')
        assert np.isinf(q.value)
        assert q.unit == u.cm

        q = u.Quantity('Infinity', unit='cm')  # float() allows this
        assert np.isinf(q.value)

        # make sure these strings don't parse...
        with pytest.raises(TypeError):
            q = u.Quantity('', unit='cm')

        with pytest.raises(TypeError):
            q = u.Quantity('spam', unit='cm')

    def test_unit_property(self):
        # test getting and setting 'unit' attribute
        q1 = u.Quantity(11.4, unit=u.meter)

        with pytest.raises(AttributeError):
            q1.unit = u.cm

    def test_preserve_dtype(self):
        """Test that if an explicit dtype is given, it is used, while if not,
        numbers are converted to float (including decimal.Decimal, which
        numpy converts to an object; closes #1419)
        """
        # If dtype is specified, use it, but if not, convert int, bool to float
        q1 = u.Quantity(12, unit=u.m / u.s, dtype=int)
        assert q1.dtype == int

        q2 = u.Quantity(q1)
        assert q2.dtype == float
        assert q2.value == float(q1.value)
        assert q2.unit == q1.unit

        # but we should preserve any float32 or even float16
        a3_32 = np.array([1., 2.], dtype=np.float32)
        q3_32 = u.Quantity(a3_32, u.yr)
        assert q3_32.dtype == a3_32.dtype
        a3_16 = np.array([1., 2.], dtype=np.float16)
        q3_16 = u.Quantity(a3_16, u.yr)
        assert q3_16.dtype == a3_16.dtype
        # items stored as objects by numpy should be converted to float
        # by default
        q4 = u.Quantity(decimal.Decimal('10.25'), u.m)
        assert q4.dtype == float

        q5 = u.Quantity(decimal.Decimal('10.25'), u.m, dtype=object)
        assert q5.dtype == object

    def test_copy(self):

        # By default, a new quantity is constructed, but not if copy=False

        a = np.arange(10.)

        q0 = u.Quantity(a, unit=u.m / u.s)
        assert q0.base is not a

        q1 = u.Quantity(a, unit=u.m / u.s, copy=False)
        assert q1.base is a

        q2 = u.Quantity(q0)
        assert q2 is not q0
        assert q2.base is not q0.base

        q2 = u.Quantity(q0, copy=False)
        assert q2 is q0
        assert q2.base is q0.base

        q3 = u.Quantity(q0, q0.unit, copy=False)
        assert q3 is q0
        assert q3.base is q0.base

        q4 = u.Quantity(q0, u.cm / u.s, copy=False)
        assert q4 is not q0
        assert q4.base is not q0.base

    def test_subok(self):
        """Test subok can be used to keep class, or to insist on Quantity"""
        class MyQuantitySubclass(u.Quantity):
            pass

        myq = MyQuantitySubclass(np.arange(10.), u.m)
        # try both with and without changing the unit
        assert type(u.Quantity(myq)) is u.Quantity
        assert type(u.Quantity(myq, subok=True)) is MyQuantitySubclass
        assert type(u.Quantity(myq, u.km)) is u.Quantity
        assert type(u.Quantity(myq, u.km, subok=True)) is MyQuantitySubclass

    def test_order(self):
        """Test that order is correctly propagated to np.array"""
        ac = np.array(np.arange(10.), order='C')
        qcc = u.Quantity(ac, u.m, order='C')
        assert qcc.flags['C_CONTIGUOUS']
        qcf = u.Quantity(ac, u.m, order='F')
        assert qcf.flags['F_CONTIGUOUS']
        qca = u.Quantity(ac, u.m, order='A')
        assert qca.flags['C_CONTIGUOUS']
        # check it works also when passing in a quantity
        assert u.Quantity(qcc, order='C').flags['C_CONTIGUOUS']
        assert u.Quantity(qcc, order='A').flags['C_CONTIGUOUS']
        assert u.Quantity(qcc, order='F').flags['F_CONTIGUOUS']

        af = np.array(np.arange(10.), order='F')
        qfc = u.Quantity(af, u.m, order='C')
        assert qfc.flags['C_CONTIGUOUS']
        qff = u.Quantity(ac, u.m, order='F')
        assert qff.flags['F_CONTIGUOUS']
        qfa = u.Quantity(af, u.m, order='A')
        assert qfa.flags['F_CONTIGUOUS']
        assert u.Quantity(qff, order='C').flags['C_CONTIGUOUS']
        assert u.Quantity(qff, order='A').flags['F_CONTIGUOUS']
        assert u.Quantity(qff, order='F').flags['F_CONTIGUOUS']

    def test_ndmin(self):
        """Test that ndmin is correctly propagated to np.array"""
        a = np.arange(10.)
        q1 = u.Quantity(a, u.m, ndmin=1)
        assert q1.ndim == 1 and q1.shape == (10,)
        q2 = u.Quantity(a, u.m, ndmin=2)
        assert q2.ndim == 2 and q2.shape == (1, 10)
        # check it works also when passing in a quantity
        q3 = u.Quantity(q1, u.m, ndmin=3)
        assert q3.ndim == 3 and q3.shape == (1, 1, 10)

        # see github issue #10063
        assert u.Quantity(u.Quantity(1, 'm'), 'm', ndmin=1).ndim == 1
        assert u.Quantity(u.Quantity(1, 'cm'), 'm', ndmin=1).ndim == 1

    def test_non_quantity_with_unit(self):
        """Test that unit attributes in objects get recognized."""
        class MyQuantityLookalike(np.ndarray):
            pass

        a = np.arange(3.)
        mylookalike = a.copy().view(MyQuantityLookalike)
        mylookalike.unit = 'm'
        q1 = u.Quantity(mylookalike)
        assert isinstance(q1, u.Quantity)
        assert q1.unit is u.m
        assert np.all(q1.value == a)

        q2 = u.Quantity(mylookalike, u.mm)
        assert q2.unit is u.mm
        assert np.all(q2.value == 1000.*a)

        q3 = u.Quantity(mylookalike, copy=False)
        assert np.all(q3.value == mylookalike)
        q3[2] = 0
        assert q3[2] == 0.
        assert mylookalike[2] == 0.

        mylookalike = a.copy().view(MyQuantityLookalike)
        mylookalike.unit = u.m
        q4 = u.Quantity(mylookalike, u.mm, copy=False)
        q4[2] = 0
        assert q4[2] == 0.
        assert mylookalike[2] == 2.

        mylookalike.unit = 'nonsense'
        with pytest.raises(TypeError):
            u.Quantity(mylookalike)

    def test_creation_via_view(self):
        # This works but is no better than 1. * u.m
        q1 = 1. << u.m
        assert isinstance(q1, u.Quantity)
        assert q1.unit == u.m
        assert q1.value == 1.
        # With an array, we get an actual view.
        a2 = np.arange(10.)
        q2 = a2 << u.m / u.s
        assert isinstance(q2, u.Quantity)
        assert q2.unit == u.m / u.s
        assert np.all(q2.value == a2)
        a2[9] = 0.
        assert np.all(q2.value == a2)
        # But with a unit change we get a copy.
        q3 = q2 << u.mm / u.s
        assert isinstance(q3, u.Quantity)
        assert q3.unit == u.mm / u.s
        assert np.all(q3.value == a2 * 1000.)
        a2[8] = 0.
        assert q3[8].value == 8000.
        # Without a unit change, we do get a view.
        q4 = q2 << q2.unit
        a2[7] = 0.
        assert np.all(q4.value == a2)
        with pytest.raises(u.UnitsError):
            q2 << u.s
        # But one can do an in-place unit change.
        a2_copy = a2.copy()
        q2 <<= u.mm / u.s
        assert q2.unit == u.mm / u.s
        # Of course, this changes a2 as well.
        assert np.all(q2.value == a2)
        # Sanity check on the values.
        assert np.all(q2.value == a2_copy * 1000.)
        a2[8] = -1.
        # Using quantities, one can also work with strings.
        q5 = q2 << 'km/hr'
        assert q5.unit == u.km / u.hr
        assert np.all(q5 == q2)
        # Finally, we can use scalar quantities as units.
        not_quite_a_foot = 30. * u.cm
        a6 = np.arange(5.)
        q6 = a6 << not_quite_a_foot
        assert q6.unit == u.Unit(not_quite_a_foot)
        assert np.all(q6.to_value(u.cm) == 30. * a6)

    def test_rshift_warns(self):
        with pytest.raises(TypeError), \
                pytest.warns(AstropyWarning, match='is not implemented') as warning_lines:
            1 >> u.m
        assert len(warning_lines) == 1
        q = 1. * u.km
        with pytest.raises(TypeError), \
                pytest.warns(AstropyWarning, match='is not implemented') as warning_lines:
            q >> u.m
        assert len(warning_lines) == 1
        with pytest.raises(TypeError), \
                pytest.warns(AstropyWarning, match='is not implemented') as warning_lines:
            q >>= u.m
        assert len(warning_lines) == 1
        with pytest.raises(TypeError), \
                pytest.warns(AstropyWarning, match='is not implemented') as warning_lines:
            1. >> q
        assert len(warning_lines) == 1


class TestQuantityOperations:
    q1 = u.Quantity(11.42, u.meter)
    q2 = u.Quantity(8.0, u.centimeter)

    def test_addition(self):
        # Take units from left object, q1
        new_quantity = self.q1 + self.q2
        assert new_quantity.value == 11.5
        assert new_quantity.unit == u.meter

        # Take units from left object, q2
        new_quantity = self.q2 + self.q1
        assert new_quantity.value == 1150.0
        assert new_quantity.unit == u.centimeter

        new_q = u.Quantity(1500.1, u.m) + u.Quantity(13.5, u.km)
        assert new_q.unit == u.m
        assert new_q.value == 15000.1

    def test_subtraction(self):
        # Take units from left object, q1
        new_quantity = self.q1 - self.q2
        assert new_quantity.value == 11.34
        assert new_quantity.unit == u.meter

        # Take units from left object, q2
        new_quantity = self.q2 - self.q1
        assert new_quantity.value == -1134.0
        assert new_quantity.unit == u.centimeter

    def test_multiplication(self):
        # Take units from left object, q1
        new_quantity = self.q1 * self.q2
        assert new_quantity.value == 91.36
        assert new_quantity.unit == (u.meter * u.centimeter)

        # Take units from left object, q2
        new_quantity = self.q2 * self.q1
        assert new_quantity.value == 91.36
        assert new_quantity.unit == (u.centimeter * u.meter)

        # Multiply with a number
        new_quantity = 15. * self.q1
        assert new_quantity.value == 171.3
        assert new_quantity.unit == u.meter

        # Multiply with a number
        new_quantity = self.q1 * 15.
        assert new_quantity.value == 171.3
        assert new_quantity.unit == u.meter

    def test_division(self):
        # Take units from left object, q1
        new_quantity = self.q1 / self.q2
        assert_array_almost_equal(new_quantity.value, 1.4275, decimal=5)
        assert new_quantity.unit == (u.meter / u.centimeter)

        # Take units from left object, q2
        new_quantity = self.q2 / self.q1
        assert_array_almost_equal(new_quantity.value, 0.70052539404553416,
                                  decimal=16)
        assert new_quantity.unit == (u.centimeter / u.meter)

        q1 = u.Quantity(11.4, unit=u.meter)
        q2 = u.Quantity(10.0, unit=u.second)
        new_quantity = q1 / q2
        assert_array_almost_equal(new_quantity.value, 1.14, decimal=10)
        assert new_quantity.unit == (u.meter / u.second)

        # divide with a number
        new_quantity = self.q1 / 10.
        assert new_quantity.value == 1.142
        assert new_quantity.unit == u.meter

        # divide with a number
        new_quantity = 11.42 / self.q1
        assert new_quantity.value == 1.
        assert new_quantity.unit == u.Unit("1/m")

    def test_commutativity(self):
        """Regression test for issue #587."""

        new_q = u.Quantity(11.42, 'm*s')

        assert self.q1 * u.s == u.s * self.q1 == new_q
        assert self.q1 / u.s == u.Quantity(11.42, 'm/s')
        assert u.s / self.q1 == u.Quantity(1 / 11.42, 's/m')

    def test_power(self):
        # raise quantity to a power
        new_quantity = self.q1 ** 2
        assert_array_almost_equal(new_quantity.value, 130.4164, decimal=5)
        assert new_quantity.unit == u.Unit("m^2")

        new_quantity = self.q1 ** 3
        assert_array_almost_equal(new_quantity.value, 1489.355288, decimal=7)
        assert new_quantity.unit == u.Unit("m^3")

    def test_matrix_multiplication(self):
        a = np.eye(3)
        q = a * u.m
        result1 = q @ a
        assert np.all(result1 == q)
        result2 = a @ q
        assert np.all(result2 == q)
        result3 = q @ q
        assert np.all(result3 == a * u.m ** 2)
        # less trivial case.
        q2 = np.array([[[1., 0., 0.],
                        [0., 1., 0.],
                        [0., 0., 1.]],
                       [[0., 1., 0.],
                        [0., 0., 1.],
                        [1., 0., 0.]],
                       [[0., 0., 1.],
                        [1., 0., 0.],
                        [0., 1., 0.]]]) / u.s
        result4 = q @ q2
        assert np.all(result4 == np.matmul(a, q2.value) * q.unit * q2.unit)

    def test_unary(self):

        # Test the minus unary operator

        new_quantity = -self.q1
        assert new_quantity.value == -self.q1.value
        assert new_quantity.unit == self.q1.unit

        new_quantity = -(-self.q1)
        assert new_quantity.value == self.q1.value
        assert new_quantity.unit == self.q1.unit

        # Test the plus unary operator

        new_quantity = +self.q1
        assert new_quantity.value == self.q1.value
        assert new_quantity.unit == self.q1.unit

    def test_abs(self):

        q = 1. * u.m / u.s
        new_quantity = abs(q)
        assert new_quantity.value == q.value
        assert new_quantity.unit == q.unit

        q = -1. * u.m / u.s
        new_quantity = abs(q)
        assert new_quantity.value == -q.value
        assert new_quantity.unit == q.unit

    def test_incompatible_units(self):
        """ When trying to add or subtract units that aren't compatible, throw an error """

        q1 = u.Quantity(11.412, unit=u.meter)
        q2 = u.Quantity(21.52, unit=u.second)

        with pytest.raises(u.UnitsError):
            q1 + q2

    def test_non_number_type(self):
        q1 = u.Quantity(11.412, unit=u.meter)
        with pytest.raises(TypeError) as exc:
            q1 + {'a': 1}
        assert exc.value.args[0].startswith(
            "Unsupported operand type(s) for ufunc add:")

        with pytest.raises(TypeError):
            q1 + u.meter

    def test_dimensionless_operations(self):
        # test conversion to dimensionless
        dq = 3. * u.m / u.km
        dq1 = dq + 1. * u.mm / u.km
        assert dq1.value == 3.001
        assert dq1.unit == dq.unit

        dq2 = dq + 1.
        assert dq2.value == 1.003
        assert dq2.unit == u.dimensionless_unscaled

        # this test will check that operations with dimensionless Quantities
        # don't work
        with pytest.raises(u.UnitsError):
            self.q1 + u.Quantity(0.1, unit=u.Unit(""))

        with pytest.raises(u.UnitsError):
            self.q1 - u.Quantity(0.1, unit=u.Unit(""))

        # and test that scaling of integers works
        q = u.Quantity(np.array([1, 2, 3]), u.m / u.km, dtype=int)
        q2 = q + np.array([4, 5, 6])
        assert q2.unit == u.dimensionless_unscaled
        assert_allclose(q2.value, np.array([4.001, 5.002, 6.003]))
        # but not if doing it inplace
        with pytest.raises(TypeError):
            q += np.array([1, 2, 3])
        # except if it is actually possible
        q = np.array([1, 2, 3]) * u.km / u.m
        q += np.array([4, 5, 6])
        assert q.unit == u.dimensionless_unscaled
        assert np.all(q.value == np.array([1004, 2005, 3006]))

    def test_complicated_operation(self):
        """ Perform a more complicated test """
        from astropy.units import imperial

        # Multiple units
        distance = u.Quantity(15., u.meter)
        time = u.Quantity(11., u.second)

        velocity = (distance / time).to(imperial.mile / u.hour)
        assert_array_almost_equal(
            velocity.value, 3.05037, decimal=5)

        G = u.Quantity(6.673E-11, u.m ** 3 / u.kg / u.s ** 2)
        _ = ((1. / (4. * np.pi * G)).to(u.pc ** -3 / u.s ** -2 * u.kg))

        # Area
        side1 = u.Quantity(11., u.centimeter)
        side2 = u.Quantity(7., u.centimeter)
        area = side1 * side2
        assert_array_almost_equal(area.value, 77., decimal=15)
        assert area.unit == u.cm * u.cm

    def test_comparison(self):
        # equality/ non-equality is straightforward for quantity objects
        assert (1 / (u.cm * u.cm)) == 1 * u.cm ** -2
        assert 1 * u.m == 100 * u.cm
        assert 1 * u.m != 1 * u.cm

        # when one is a unit, Quantity does not know what to do,
        # but unit is fine with it, so it still works
        unit = u.cm**3
        q = 1. * unit
        assert q.__eq__(unit) is NotImplemented
        assert unit.__eq__(q) is True
        assert q == unit
        q = 1000. * u.mm**3
        assert q == unit

        # mismatched types should never work
        assert not 1. * u.cm == 1.
        assert 1. * u.cm != 1.

        # comparison with zero should raise a deprecation warning
        for quantity in (1. * u.cm, 1. * u.dimensionless_unscaled):
            with pytest.warns(AstropyDeprecationWarning, match='The truth value of '
                              'a Quantity is ambiguous. In the future this will '
                              'raise a ValueError.'):
                bool(quantity)

    def test_numeric_converters(self):
        # float, int, long, and __index__ should only work for single
        # quantities, of appropriate type, and only if they are dimensionless.
        # for index, this should be unscaled as well
        # (Check on __index__ is also a regression test for #1557)

        # quantities with units should never convert, or be usable as an index
        q1 = u.Quantity(1, u.m)

        converter_err_msg = ("only dimensionless scalar quantities "
                             "can be converted to Python scalars")
        index_err_msg = ("only integer dimensionless scalar quantities "
                         "can be converted to a Python index")
        with pytest.raises(TypeError) as exc:
            float(q1)
        assert exc.value.args[0] == converter_err_msg

        with pytest.raises(TypeError) as exc:
            int(q1)
        assert exc.value.args[0] == converter_err_msg

        # We used to test `q1 * ['a', 'b', 'c'] here, but that that worked
        # at all was a really odd confluence of bugs.  Since it doesn't work
        # in numpy >=1.10 any more, just go directly for `__index__` (which
        # makes the test more similar to the `int`, `long`, etc., tests).
        with pytest.raises(TypeError) as exc:
            q1.__index__()
        assert exc.value.args[0] == index_err_msg

        # dimensionless but scaled is OK, however
        q2 = u.Quantity(1.23, u.m / u.km)

        assert float(q2) == float(q2.to_value(u.dimensionless_unscaled))
        assert int(q2) == int(q2.to_value(u.dimensionless_unscaled))

        with pytest.raises(TypeError) as exc:
            q2.__index__()
        assert exc.value.args[0] == index_err_msg

        # dimensionless unscaled is OK, though for index needs to be int
        q3 = u.Quantity(1.23, u.dimensionless_unscaled)

        assert float(q3) == 1.23
        assert int(q3) == 1

        with pytest.raises(TypeError) as exc:
            q3.__index__()
        assert exc.value.args[0] == index_err_msg

        # integer dimensionless unscaled is good for all
        q4 = u.Quantity(2, u.dimensionless_unscaled, dtype=int)

        assert float(q4) == 2.
        assert int(q4) == 2

        assert q4.__index__() == 2

        # but arrays are not OK
        q5 = u.Quantity([1, 2], u.m)
        with pytest.raises(TypeError) as exc:
            float(q5)
        assert exc.value.args[0] == converter_err_msg

        with pytest.raises(TypeError) as exc:
            int(q5)
        assert exc.value.args[0] == converter_err_msg

        with pytest.raises(TypeError) as exc:
            q5.__index__()
        assert exc.value.args[0] == index_err_msg

    # See https://github.com/numpy/numpy/issues/5074
    # It seems unlikely this will be resolved, so xfail'ing it.
    @pytest.mark.xfail(reason="list multiplication only works for numpy <=1.10")
    def test_numeric_converter_to_index_in_practice(self):
        """Test that use of __index__ actually works."""
        q4 = u.Quantity(2, u.dimensionless_unscaled, dtype=int)
        assert q4 * ['a', 'b', 'c'] == ['a', 'b', 'c', 'a', 'b', 'c']

    def test_array_converters(self):

        # Scalar quantity
        q = u.Quantity(1.23, u.m)
        assert np.all(np.array(q) == np.array([1.23]))

        # Array quantity
        q = u.Quantity([1., 2., 3.], u.m)
        assert np.all(np.array(q) == np.array([1., 2., 3.]))


def test_quantity_conversion():
    q1 = u.Quantity(0.1, unit=u.meter)
    value = q1.value
    assert value == 0.1
    value_in_km = q1.to_value(u.kilometer)
    assert value_in_km == 0.0001
    new_quantity = q1.to(u.kilometer)
    assert new_quantity.value == 0.0001

    with pytest.raises(u.UnitsError):
        q1.to(u.zettastokes)
    with pytest.raises(u.UnitsError):
        q1.to_value(u.zettastokes)


def test_quantity_value_views():
    q1 = u.Quantity([1., 2.], unit=u.meter)
    # views if the unit is the same.
    v1 = q1.value
    v1[0] = 0.
    assert np.all(q1 == [0., 2.] * u.meter)
    v2 = q1.to_value()
    v2[1] = 3.
    assert np.all(q1 == [0., 3.] * u.meter)
    v3 = q1.to_value('m')
    v3[0] = 1.
    assert np.all(q1 == [1., 3.] * u.meter)
    q2 = q1.to('m', copy=False)
    q2[0] = 2 * u.meter
    assert np.all(q1 == [2., 3.] * u.meter)
    v4 = q1.to_value('cm')
    v4[0] = 0.
    # copy if different unit.
    assert np.all(q1 == [2., 3.] * u.meter)


def test_quantity_conversion_with_equiv():
    q1 = u.Quantity(0.1, unit=u.meter)
    v2 = q1.to_value(u.Hz, equivalencies=u.spectral())
    assert_allclose(v2, 2997924580.0)
    q2 = q1.to(u.Hz, equivalencies=u.spectral())
    assert_allclose(q2.value, v2)

    q1 = u.Quantity(0.4, unit=u.arcsecond)
    v2 = q1.to_value(u.au, equivalencies=u.parallax())
    q2 = q1.to(u.au, equivalencies=u.parallax())
    v3 = q2.to_value(u.arcminute, equivalencies=u.parallax())
    q3 = q2.to(u.arcminute, equivalencies=u.parallax())

    assert_allclose(v2, 515662.015)
    assert_allclose(q2.value, v2)
    assert q2.unit == u.au
    assert_allclose(v3, 0.0066666667)
    assert_allclose(q3.value, v3)
    assert q3.unit == u.arcminute


def test_quantity_conversion_equivalency_passed_on():
    class MySpectral(u.Quantity):
        _equivalencies = u.spectral()

        def __quantity_view__(self, obj, unit):
            return obj.view(MySpectral)

        def __quantity_instance__(self, *args, **kwargs):
            return MySpectral(*args, **kwargs)

    q1 = MySpectral([1000, 2000], unit=u.Hz)
    q2 = q1.to(u.nm)
    assert q2.unit == u.nm
    q3 = q2.to(u.Hz)
    assert q3.unit == u.Hz
    assert_allclose(q3.value, q1.value)
    q4 = MySpectral([1000, 2000], unit=u.nm)
    q5 = q4.to(u.Hz).to(u.nm)
    assert q5.unit == u.nm
    assert_allclose(q4.value, q5.value)

# Regression test for issue #2315, divide-by-zero error when examining 0*unit


def test_self_equivalency():
    assert u.deg.is_equivalent(0*u.radian)
    assert u.deg.is_equivalent(1*u.radian)


def test_si():
    q1 = 10. * u.m * u.s ** 2 / (200. * u.ms) ** 2  # 250 meters
    assert q1.si.value == 250
    assert q1.si.unit == u.m

    q = 10. * u.m  # 10 meters
    assert q.si.value == 10
    assert q.si.unit == u.m

    q = 10. / u.m  # 10 1 / meters
    assert q.si.value == 10
    assert q.si.unit == (1 / u.m)


def test_cgs():
    q1 = 10. * u.cm * u.s ** 2 / (200. * u.ms) ** 2  # 250 centimeters
    assert q1.cgs.value == 250
    assert q1.cgs.unit == u.cm

    q = 10. * u.m  # 10 centimeters
    assert q.cgs.value == 1000
    assert q.cgs.unit == u.cm

    q = 10. / u.cm  # 10 1 / centimeters
    assert q.cgs.value == 10
    assert q.cgs.unit == (1 / u.cm)

    q = 10. * u.Pa  # 10 pascals
    assert q.cgs.value == 100
    assert q.cgs.unit == u.barye


class TestQuantityComparison:

    def test_quantity_equality(self):
        assert u.Quantity(1000, unit='m') == u.Quantity(1, unit='km')
        assert not (u.Quantity(1, unit='m') == u.Quantity(1, unit='km'))
        # for ==, !=, return False, True if units do not match
        assert (u.Quantity(1100, unit=u.m) != u.Quantity(1, unit=u.s)) is True
        assert (u.Quantity(1100, unit=u.m) == u.Quantity(1, unit=u.s)) is False
        assert (u.Quantity(0, unit=u.m) == u.Quantity(0, unit=u.s)) is False
        # But allow comparison with 0, +/-inf if latter unitless
        assert u.Quantity(0, u.m) == 0.
        assert u.Quantity(1, u.m) != 0.
        assert u.Quantity(1, u.m) != np.inf
        assert u.Quantity(np.inf, u.m) == np.inf

    def test_quantity_equality_array(self):
        a = u.Quantity([0., 1., 1000.], u.m)
        b = u.Quantity(1., u.km)
        eq = a == b
        ne = a != b
        assert np.all(eq == [False, False, True])
        assert np.all(eq != ne)
        # For mismatched units, we should just get True, False
        c = u.Quantity(1., u.s)
        eq = a == c
        ne = a != c
        assert eq is False
        assert ne is True
        # Constants are treated as dimensionless, so False too.
        eq = a == 1.
        ne = a != 1.
        assert eq is False
        assert ne is True
        # But 0 can have any units, so we can compare.
        eq = a == 0
        ne = a != 0
        assert np.all(eq == [True, False, False])
        assert np.all(eq != ne)
        # But we do not extend that to arrays; they should have the same unit.
        d = np.array([0, 1., 1000.])
        eq = a == d
        ne = a != d
        assert eq is False
        assert ne is True

    def test_quantity_comparison(self):
        assert u.Quantity(1100, unit=u.meter) > u.Quantity(1, unit=u.kilometer)
        assert u.Quantity(900, unit=u.meter) < u.Quantity(1, unit=u.kilometer)

        with pytest.raises(u.UnitsError):
            assert u.Quantity(1100, unit=u.meter) > u.Quantity(1, unit=u.second)

        with pytest.raises(u.UnitsError):
            assert u.Quantity(1100, unit=u.meter) < u.Quantity(1, unit=u.second)

        assert u.Quantity(1100, unit=u.meter) >= u.Quantity(1, unit=u.kilometer)
        assert u.Quantity(1000, unit=u.meter) >= u.Quantity(1, unit=u.kilometer)

        assert u.Quantity(900, unit=u.meter) <= u.Quantity(1, unit=u.kilometer)
        assert u.Quantity(1000, unit=u.meter) <= u.Quantity(1, unit=u.kilometer)

        with pytest.raises(u.UnitsError):
            assert u.Quantity(
                1100, unit=u.meter) >= u.Quantity(1, unit=u.second)

        with pytest.raises(u.UnitsError):
            assert u.Quantity(1100, unit=u.meter) <= u.Quantity(1, unit=u.second)

        assert u.Quantity(1200, unit=u.meter) != u.Quantity(1, unit=u.kilometer)


class TestQuantityDisplay:
    scalarintq = u.Quantity(1, unit='m', dtype=int)
    scalarfloatq = u.Quantity(1.3, unit='m')
    arrq = u.Quantity([1, 2.3, 8.9], unit='m')

    scalar_complex_q = u.Quantity(complex(1.0, 2.0))
    scalar_big_complex_q = u.Quantity(complex(1.0, 2.0e27) * 1e25)
    scalar_big_neg_complex_q = u.Quantity(complex(-1.0, -2.0e27) * 1e36)
    arr_complex_q = u.Quantity(np.arange(3) * (complex(-1.0, -2.0e27) * 1e36))
    big_arr_complex_q = u.Quantity(np.arange(125) * (complex(-1.0, -2.0e27) * 1e36))

    def test_dimensionless_quantity_repr(self):
        q2 = u.Quantity(1., unit='m-1')
        q3 = u.Quantity(1, unit='m-1', dtype=int)
        assert repr(self.scalarintq * q2) == "<Quantity 1.>"
        assert repr(self.arrq * q2) == "<Quantity [1. , 2.3, 8.9]>"
        assert repr(self.scalarintq * q3) == "<Quantity 1>"

    def test_dimensionless_quantity_str(self):
        q2 = u.Quantity(1., unit='m-1')
        q3 = u.Quantity(1, unit='m-1', dtype=int)
        assert str(self.scalarintq * q2) == "1.0"
        assert str(self.scalarintq * q3) == "1"
        assert str(self.arrq * q2) == "[1.  2.3 8.9]"

    def test_dimensionless_quantity_format(self):
        q1 = u.Quantity(3.14)
        assert format(q1, '.2f') == '3.14'

    def test_scalar_quantity_str(self):
        assert str(self.scalarintq) == "1 m"
        assert str(self.scalarfloatq) == "1.3 m"

    def test_scalar_quantity_repr(self):
        assert repr(self.scalarintq) == "<Quantity 1 m>"
        assert repr(self.scalarfloatq) == "<Quantity 1.3 m>"

    def test_array_quantity_str(self):
        assert str(self.arrq) == "[1.  2.3 8.9] m"

    def test_array_quantity_repr(self):
        assert repr(self.arrq) == "<Quantity [1. , 2.3, 8.9] m>"

    def test_scalar_quantity_format(self):
        assert format(self.scalarintq, '02d') == "01 m"
        assert format(self.scalarfloatq, '.1f') == "1.3 m"
        assert format(self.scalarfloatq, '.0f') == "1 m"

    def test_uninitialized_unit_format(self):
        bad_quantity = np.arange(10.).view(u.Quantity)
        assert str(bad_quantity).endswith(_UNIT_NOT_INITIALISED)
        assert repr(bad_quantity).endswith(_UNIT_NOT_INITIALISED + '>')

    def test_to_string(self):
        qscalar = u.Quantity(1.5e14, 'm/s')

        # __str__ is the default `format`
        assert str(qscalar) == qscalar.to_string()

        res = 'Quantity as KMS: 150000000000.0 km / s'
        assert f"Quantity as KMS: {qscalar.to_string(unit=u.km / u.s)}" == res

        # With precision set
        res = 'Quantity as KMS: 1.500e+11 km / s'
        assert f"Quantity as KMS: {qscalar.to_string(precision=3, unit=u.km / u.s)}" == res

        res = r'$1.5 \times 10^{14} \; \mathrm{\frac{m}{s}}$'
        assert qscalar.to_string(format="latex") == res
        assert qscalar.to_string(format="latex", subfmt="inline") == res
        res = r'$\displaystyle 1.5 \times 10^{14} \; \mathrm{\frac{m}{s}}$'
        assert qscalar.to_string(format="latex", subfmt="display") == res

        res = r'$1.5 \times 10^{14} \; \mathrm{m\,s^{-1}}$'
        assert qscalar.to_string(format="latex_inline") == res
        assert qscalar.to_string(format="latex_inline", subfmt="inline") == res
        res = r'$\displaystyle 1.5 \times 10^{14} \; \mathrm{m\,s^{-1}}$'
        assert qscalar.to_string(format="latex_inline", subfmt="display") == res

        res = '[0 1 2] (Unit not initialised)'
        assert np.arange(3).view(u.Quantity).to_string() == res

    def test_repr_latex(self):
        from astropy.units.quantity import conf

        q2scalar = u.Quantity(1.5e14, 'm/s')
        assert self.scalarintq._repr_latex_() == r'$1 \; \mathrm{m}$'
        assert self.scalarfloatq._repr_latex_() == r'$1.3 \; \mathrm{m}$'
        assert (q2scalar._repr_latex_() ==
                r'$1.5 \times 10^{14} \; \mathrm{\frac{m}{s}}$')
        assert self.arrq._repr_latex_() == r'$[1,~2.3,~8.9] \; \mathrm{m}$'

        # Complex quantities
        assert self.scalar_complex_q._repr_latex_() == r'$(1+2i) \; \mathrm{}$'
        assert (self.scalar_big_complex_q._repr_latex_() ==
                r'$(1 \times 10^{25}+2 \times 10^{52}i) \; \mathrm{}$')
        assert (self.scalar_big_neg_complex_q._repr_latex_() ==
                r'$(-1 \times 10^{36}-2 \times 10^{63}i) \; \mathrm{}$')
        assert (self.arr_complex_q._repr_latex_() ==
                (r'$[(0-0i),~(-1 \times 10^{36}-2 \times 10^{63}i),'
                 r'~(-2 \times 10^{36}-4 \times 10^{63}i)] \; \mathrm{}$'))
        assert r'\dots' in self.big_arr_complex_q._repr_latex_()

        qmed = np.arange(100)*u.m
        qbig = np.arange(1000)*u.m
        qvbig = np.arange(10000)*1e9*u.m

        pops = np.get_printoptions()
        oldlat = conf.latex_array_threshold
        try:
            # check precision behavior
            q = u.Quantity(987654321.123456789, 'm/s')
            qa = np.array([7.89123, 123456789.987654321, 0]) * u.cm
            np.set_printoptions(precision=8)
            assert q._repr_latex_() == r'$9.8765432 \times 10^{8} \; \mathrm{\frac{m}{s}}$'
            assert qa._repr_latex_() == r'$[7.89123,~1.2345679 \times 10^{8},~0] \; \mathrm{cm}$'
            np.set_printoptions(precision=2)
            assert q._repr_latex_() == r'$9.9 \times 10^{8} \; \mathrm{\frac{m}{s}}$'
            assert qa._repr_latex_() == r'$[7.9,~1.2 \times 10^{8},~0] \; \mathrm{cm}$'

            # check thresholding behavior
            conf.latex_array_threshold = 100  # should be default
            lsmed = qmed._repr_latex_()
            assert r'\dots' not in lsmed
            lsbig = qbig._repr_latex_()
            assert r'\dots' in lsbig
            lsvbig = qvbig._repr_latex_()
            assert r'\dots' in lsvbig

            conf.latex_array_threshold = 1001
            lsmed = qmed._repr_latex_()
            assert r'\dots' not in lsmed
            lsbig = qbig._repr_latex_()
            assert r'\dots' not in lsbig
            lsvbig = qvbig._repr_latex_()
            assert r'\dots' in lsvbig

            conf.latex_array_threshold = -1  # means use the numpy threshold
            np.set_printoptions(threshold=99)
            lsmed = qmed._repr_latex_()
            assert r'\dots' in lsmed
            lsbig = qbig._repr_latex_()
            assert r'\dots' in lsbig
            lsvbig = qvbig._repr_latex_()
            assert r'\dots' in lsvbig
            assert lsvbig.endswith(',~1 \\times 10^{13}] \\; \\mathrm{m}$')
        finally:
            # prevent side-effects from influencing other tests
            np.set_printoptions(**pops)
            conf.latex_array_threshold = oldlat

        qinfnan = [np.inf, -np.inf, np.nan] * u.m
        assert qinfnan._repr_latex_() == r'$[\infty,~-\infty,~{\rm NaN}] \; \mathrm{m}$'


def test_decompose():
    q1 = 5 * u.N
    assert q1.decompose() == (5 * u.kg * u.m * u.s ** -2)


def test_decompose_regression():
    """
    Regression test for bug #1163

    If decompose was called multiple times on a Quantity with an array and a
    scale != 1, the result changed every time. This is because the value was
    being referenced not copied, then modified, which changed the original
    value.
    """
    q = np.array([1, 2, 3]) * u.m / (2. * u.km)
    assert np.all(q.decompose().value == np.array([0.0005, 0.001, 0.0015]))
    assert np.all(q == np.array([1, 2, 3]) * u.m / (2. * u.km))
    assert np.all(q.decompose().value == np.array([0.0005, 0.001, 0.0015]))


def test_arrays():
    """
    Test using quantites with array values
    """

    qsec = u.Quantity(np.arange(10), u.second)
    assert isinstance(qsec.value, np.ndarray)
    assert not qsec.isscalar

    # len and indexing should work for arrays
    assert len(qsec) == len(qsec.value)
    qsecsub25 = qsec[2:5]
    assert qsecsub25.unit == qsec.unit
    assert isinstance(qsecsub25, u.Quantity)
    assert len(qsecsub25) == 3

    # make sure isscalar, len, and indexing behave correctly for non-arrays.
    qsecnotarray = u.Quantity(10., u.second)
    assert qsecnotarray.isscalar
    with pytest.raises(TypeError):
        len(qsecnotarray)
    with pytest.raises(TypeError):
        qsecnotarray[0]

    qseclen0array = u.Quantity(np.array(10), u.second, dtype=int)
    # 0d numpy array should act basically like a scalar
    assert qseclen0array.isscalar
    with pytest.raises(TypeError):
        len(qseclen0array)
    with pytest.raises(TypeError):
        qseclen0array[0]
    assert isinstance(qseclen0array.value, numbers.Integral)

    a = np.array([(1., 2., 3.), (4., 5., 6.), (7., 8., 9.)],
                 dtype=[('x', float),
                        ('y', float),
                        ('z', float)])
    qkpc = u.Quantity(a, u.kpc)
    assert not qkpc.isscalar
    qkpc0 = qkpc[0]
    assert qkpc0.value == a[0]
    assert qkpc0.unit == qkpc.unit
    assert isinstance(qkpc0, u.Quantity)
    assert qkpc0.isscalar
    qkpcx = qkpc['x']
    assert np.all(qkpcx.value == a['x'])
    assert qkpcx.unit == qkpc.unit
    assert isinstance(qkpcx, u.Quantity)
    assert not qkpcx.isscalar
    qkpcx1 = qkpc['x'][1]
    assert qkpcx1.unit == qkpc.unit
    assert isinstance(qkpcx1, u.Quantity)
    assert qkpcx1.isscalar
    qkpc1x = qkpc[1]['x']
    assert qkpc1x.isscalar
    assert qkpc1x == qkpcx1

    # can also create from lists, will auto-convert to arrays
    qsec = u.Quantity(list(range(10)), u.second)
    assert isinstance(qsec.value, np.ndarray)

    # quantity math should work with arrays
    assert_array_equal((qsec * 2).value, (np.arange(10) * 2))
    assert_array_equal((qsec / 2).value, (np.arange(10) / 2))
    # quantity addition/subtraction should *not* work with arrays b/c unit
    # ambiguous
    with pytest.raises(u.UnitsError):
        assert_array_equal((qsec + 2).value, (np.arange(10) + 2))
    with pytest.raises(u.UnitsError):
        assert_array_equal((qsec - 2).value, (np.arange(10) + 2))

    # should create by unit multiplication, too
    qsec2 = np.arange(10) * u.second
    qsec3 = u.second * np.arange(10)

    assert np.all(qsec == qsec2)
    assert np.all(qsec2 == qsec3)

    # make sure numerical-converters fail when arrays are present
    with pytest.raises(TypeError):
        float(qsec)
    with pytest.raises(TypeError):
        int(qsec)


def test_array_indexing_slicing():
    q = np.array([1., 2., 3.]) * u.m
    assert q[0] == 1. * u.m
    assert np.all(q[0:2] == u.Quantity([1., 2.], u.m))


def test_array_setslice():
    q = np.array([1., 2., 3.]) * u.m
    q[1:2] = np.array([400.]) * u.cm
    assert np.all(q == np.array([1., 4., 3.]) * u.m)


def test_inverse_quantity():
    """
    Regression test from issue #679
    """
    q = u.Quantity(4., u.meter / u.second)
    qot = q / 2
    toq = 2 / q
    npqot = q / np.array(2)

    assert npqot.value == 2.0
    assert npqot.unit == (u.meter / u.second)

    assert qot.value == 2.0
    assert qot.unit == (u.meter / u.second)

    assert toq.value == 0.5
    assert toq.unit == (u.second / u.meter)


def test_quantity_mutability():
    q = u.Quantity(9.8, u.meter / u.second / u.second)

    with pytest.raises(AttributeError):
        q.value = 3

    with pytest.raises(AttributeError):
        q.unit = u.kg


def test_quantity_initialized_with_quantity():
    q1 = u.Quantity(60, u.second)

    q2 = u.Quantity(q1, u.minute)
    assert q2.value == 1

    q3 = u.Quantity([q1, q2], u.second)
    assert q3[0].value == 60
    assert q3[1].value == 60

    q4 = u.Quantity([q2, q1])
    assert q4.unit == q2.unit
    assert q4[0].value == 1
    assert q4[1].value == 1


def test_quantity_string_unit():
    q1 = 1. * u.m / 's'
    assert q1.value == 1
    assert q1.unit == (u.m / u.s)

    q2 = q1 * "m"
    assert q2.unit == ((u.m * u.m) / u.s)


def test_quantity_invalid_unit_string():
    with pytest.raises(ValueError):
        "foo" * u.m


def test_implicit_conversion():
    q = u.Quantity(1.0, u.meter)
    # Manually turn this on to simulate what might happen in a subclass
    q._include_easy_conversion_members = True
    assert_allclose(q.centimeter, 100)
    assert_allclose(q.cm, 100)
    assert_allclose(q.parsec, 3.240779289469756e-17)


def test_implicit_conversion_autocomplete():
    q = u.Quantity(1.0, u.meter)
    # Manually turn this on to simulate what might happen in a subclass
    q._include_easy_conversion_members = True
    q.foo = 42

    attrs = dir(q)
    assert 'centimeter' in attrs
    assert 'cm' in attrs
    assert 'parsec' in attrs
    assert 'foo' in attrs
    assert 'to' in attrs
    assert 'value' in attrs
    # Something from the base class, object
    assert '__setattr__' in attrs

    with pytest.raises(AttributeError):
        q.l


def test_quantity_iterability():
    """Regressiont est for issue #878.

    Scalar quantities should not be iterable and should raise a type error on
    iteration.
    """

    q1 = [15.0, 17.0] * u.m
    assert isiterable(q1)

    q2 = next(iter(q1))
    assert q2 == 15.0 * u.m
    assert not isiterable(q2)
    pytest.raises(TypeError, iter, q2)


def test_copy():

    q1 = u.Quantity(np.array([[1., 2., 3.], [4., 5., 6.]]), unit=u.m)
    q2 = q1.copy()

    assert np.all(q1.value == q2.value)
    assert q1.unit == q2.unit
    assert q1.dtype == q2.dtype
    assert q1.value is not q2.value

    q3 = q1.copy(order='F')
    assert q3.flags['F_CONTIGUOUS']
    assert np.all(q1.value == q3.value)
    assert q1.unit == q3.unit
    assert q1.dtype == q3.dtype
    assert q1.value is not q3.value

    q4 = q1.copy(order='C')
    assert q4.flags['C_CONTIGUOUS']
    assert np.all(q1.value == q4.value)
    assert q1.unit == q4.unit
    assert q1.dtype == q4.dtype
    assert q1.value is not q4.value


def test_deepcopy():
    q1 = u.Quantity(np.array([1., 2., 3.]), unit=u.m)
    q2 = copy.deepcopy(q1)

    assert isinstance(q2, u.Quantity)
    assert np.all(q1.value == q2.value)
    assert q1.unit == q2.unit
    assert q1.dtype == q2.dtype

    assert q1.value is not q2.value


def test_equality_numpy_scalar():
    """
    A regression test to ensure that numpy scalars are correctly compared
    (which originally failed due to the lack of ``__array_priority__``).
    """
    assert 10 != 10. * u.m
    assert np.int64(10) != 10 * u.m
    assert 10 * u.m != np.int64(10)


def test_quantity_pickelability():
    """
    Testing pickleability of quantity
    """

    q1 = np.arange(10) * u.m

    q2 = pickle.loads(pickle.dumps(q1))

    assert np.all(q1.value == q2.value)
    assert q1.unit.is_equivalent(q2.unit)
    assert q1.unit == q2.unit


def test_quantity_initialisation_from_string():
    q = u.Quantity('1')
    assert q.unit == u.dimensionless_unscaled
    assert q.value == 1.
    q = u.Quantity('1.5 m/s')
    assert q.unit == u.m/u.s
    assert q.value == 1.5
    assert u.Unit(q) == u.Unit('1.5 m/s')
    q = u.Quantity('.5 m')
    assert q == u.Quantity(0.5, u.m)
    q = u.Quantity('-1e1km')
    assert q == u.Quantity(-10, u.km)
    q = u.Quantity('-1e+1km')
    assert q == u.Quantity(-10, u.km)
    q = u.Quantity('+.5km')
    assert q == u.Quantity(.5, u.km)
    q = u.Quantity('+5e-1km')
    assert q == u.Quantity(.5, u.km)
    q = u.Quantity('5', u.m)
    assert q == u.Quantity(5., u.m)
    q = u.Quantity('5 km', u.m)
    assert q.value == 5000.
    assert q.unit == u.m
    q = u.Quantity('5Em')
    assert q == u.Quantity(5., u.Em)

    with pytest.raises(TypeError):
        u.Quantity('')
    with pytest.raises(TypeError):
        u.Quantity('m')
    with pytest.raises(TypeError):
        u.Quantity('1.2.3 deg')
    with pytest.raises(TypeError):
        u.Quantity('1+deg')
    with pytest.raises(TypeError):
        u.Quantity('1-2deg')
    with pytest.raises(TypeError):
        u.Quantity('1.2e-13.3m')
    with pytest.raises(TypeError):
        u.Quantity(['5'])
    with pytest.raises(TypeError):
        u.Quantity(np.array(['5']))
    with pytest.raises(ValueError):
        u.Quantity('5E')
    with pytest.raises(ValueError):
        u.Quantity('5 foo')


def test_unsupported():
    q1 = np.arange(10) * u.m

    with pytest.raises(TypeError):
        np.bitwise_and(q1, q1)


def test_unit_identity():
    q = 1.0 * u.hour
    assert q.unit is u.hour


def test_quantity_to_view():
    q1 = np.array([1000, 2000]) * u.m
    q2 = q1.to(u.km)
    assert q1.value[0] == 1000
    assert q2.value[0] == 1


def test_quantity_tuple_power():
    with pytest.raises(ValueError):
        (5.0 * u.m) ** (1, 2)


def test_quantity_fraction_power():
    q = (25.0 * u.m**2) ** Fraction(1, 2)
    assert q.value == 5.
    assert q.unit == u.m
    # Regression check to ensure we didn't create an object type by raising
    # the value of the quantity to a Fraction. [#3922]
    assert q.dtype.kind == 'f'


def test_quantity_from_table():
    """
    Checks that units from tables are respected when converted to a Quantity.
    This also generically checks the use of *anything* with a `unit` attribute
    passed into Quantity
    """
    from astropy.table import Table

    t = Table(data=[np.arange(5), np.arange(5)], names=['a', 'b'])
    t['a'].unit = u.kpc

    qa = u.Quantity(t['a'])
    assert qa.unit == u.kpc
    assert_array_equal(qa.value, t['a'])

    qb = u.Quantity(t['b'])
    assert qb.unit == u.dimensionless_unscaled
    assert_array_equal(qb.value, t['b'])

    # This does *not* auto-convert, because it's not necessarily obvious that's
    # desired.  Instead we revert to standard `Quantity` behavior
    qap = u.Quantity(t['a'], u.pc)
    assert qap.unit == u.pc
    assert_array_equal(qap.value, t['a'] * 1000)

    qbp = u.Quantity(t['b'], u.pc)
    assert qbp.unit == u.pc
    assert_array_equal(qbp.value, t['b'])

    # Also check with a function unit (regression test for gh-8430)
    t['a'].unit = u.dex(u.cm/u.s**2)
    fq = u.Dex(t['a'])
    assert fq.unit == u.dex(u.cm/u.s**2)
    assert_array_equal(fq.value, t['a'])

    fq2 = u.Quantity(t['a'], subok=True)
    assert isinstance(fq2, u.Dex)
    assert fq2.unit == u.dex(u.cm/u.s**2)
    assert_array_equal(fq2.value, t['a'])

    with pytest.raises(u.UnitTypeError):
        u.Quantity(t['a'])


def test_assign_slice_with_quantity_like():
    # Regression tests for gh-5961
    from astropy.table import Column, Table

    # first check directly that we can use a Column to assign to a slice.
    c = Column(np.arange(10.), unit=u.mm)
    q = u.Quantity(c)
    q[:2] = c[:2]
    # next check that we do not fail the original problem.
    t = Table()
    t['x'] = np.arange(10) * u.mm
    t['y'] = np.ones(10) * u.mm
    assert type(t['x']) is Column

    xy = np.vstack([t['x'], t['y']]).T * u.mm
    ii = [0, 2, 4]

    assert xy[ii, 0].unit == t['x'][ii].unit
    # should not raise anything
    xy[ii, 0] = t['x'][ii]


def test_insert():
    """
    Test Quantity.insert method.  This does not test the full capabilities
    of the underlying np.insert, but hits the key functionality for
    Quantity.
    """
    q = [1, 2] * u.m

    # Insert a compatible float with different units
    q2 = q.insert(0, 1 * u.km)
    assert np.all(q2.value == [1000, 1, 2])
    assert q2.unit is u.m
    assert q2.dtype.kind == 'f'

    if minversion(np, '1.8.0'):
        q2 = q.insert(1, [1, 2] * u.km)
        assert np.all(q2.value == [1, 1000, 2000, 2])
        assert q2.unit is u.m

    # Cannot convert 1.5 * u.s to m
    with pytest.raises(u.UnitsError):
        q.insert(1, 1.5 * u.s)

    # Tests with multi-dim quantity
    q = [[1, 2], [3, 4]] * u.m
    q2 = q.insert(1, [10, 20] * u.m, axis=0)
    assert np.all(q2.value == [[1, 2],
                               [10, 20],
                               [3, 4]])

    q2 = q.insert(1, [10, 20] * u.m, axis=1)
    assert np.all(q2.value == [[1, 10, 2],
                               [3, 20, 4]])

    q2 = q.insert(1, 10 * u.m, axis=1)
    assert np.all(q2.value == [[1, 10, 2],
                               [3, 10, 4]])


def test_repr_array_of_quantity():
    """
    Test print/repr of object arrays of Quantity objects with different
    units.

    Regression test for the issue first reported in
    https://github.com/astropy/astropy/issues/3777
    """

    a = np.array([1 * u.m, 2 * u.s], dtype=object)
    assert repr(a) == 'array([<Quantity 1. m>, <Quantity 2. s>], dtype=object)'
    assert str(a) == '[<Quantity 1. m> <Quantity 2. s>]'


class TestSpecificTypeQuantity:
    def setup(self):
        class Length(u.SpecificTypeQuantity):
            _equivalent_unit = u.m

        class Length2(Length):
            _default_unit = u.m

        class Length3(Length):
            _unit = u.m

        self.Length = Length
        self.Length2 = Length2
        self.Length3 = Length3

    def test_creation(self):
        l = self.Length(np.arange(10.)*u.km)
        assert type(l) is self.Length
        with pytest.raises(u.UnitTypeError):
            self.Length(np.arange(10.) * u.hour)

        with pytest.raises(u.UnitTypeError):
            self.Length(np.arange(10.))

        l2 = self.Length2(np.arange(5.))
        assert type(l2) is self.Length2
        assert l2._default_unit is self.Length2._default_unit

        with pytest.raises(u.UnitTypeError):
            self.Length3(np.arange(10.))

    def test_view(self):
        l = (np.arange(5.) * u.km).view(self.Length)
        assert type(l) is self.Length
        with pytest.raises(u.UnitTypeError):
            (np.arange(5.) * u.s).view(self.Length)

        v = np.arange(5.).view(self.Length)
        assert type(v) is self.Length
        assert v._unit is None

        l3 = np.ones((2, 2)).view(self.Length3)
        assert type(l3) is self.Length3
        assert l3.unit is self.Length3._unit

    def test_operation_precedence_and_fallback(self):
        l = self.Length(np.arange(5.)*u.cm)
        sum1 = l + 1.*u.m
        assert type(sum1) is self.Length
        sum2 = 1.*u.km + l
        assert type(sum2) is self.Length
        sum3 = l + l
        assert type(sum3) is self.Length
        res1 = l * (1.*u.m)
        assert type(res1) is u.Quantity
        res2 = l * l
        assert type(res2) is u.Quantity


def test_unit_class_override():
    class MyQuantity(u.Quantity):
        pass

    my_unit = u.Unit("my_deg", u.deg)
    my_unit._quantity_class = MyQuantity
    q1 = u.Quantity(1., my_unit)
    assert type(q1) is u.Quantity
    q2 = u.Quantity(1., my_unit, subok=True)
    assert type(q2) is MyQuantity


class QuantityMimic:
    def __init__(self, value, unit):
        self.value = value
        self.unit = unit

    def __array__(self):
        return np.array(self.value)


class QuantityMimic2(QuantityMimic):
    def to(self, unit):
        return u.Quantity(self.value, self.unit).to(unit)

    def to_value(self, unit):
        return u.Quantity(self.value, self.unit).to_value(unit)


class TestQuantityMimics:
    """Test Quantity Mimics that are not ndarray subclasses."""
    @pytest.mark.parametrize('Mimic', (QuantityMimic, QuantityMimic2))
    def test_mimic_input(self, Mimic):
        value = np.arange(10.)
        mimic = Mimic(value, u.m)
        q = u.Quantity(mimic)
        assert q.unit == u.m
        assert np.all(q.value == value)
        q2 = u.Quantity(mimic, u.cm)
        assert q2.unit == u.cm
        assert np.all(q2.value == 100 * value)

    @pytest.mark.parametrize('Mimic', (QuantityMimic, QuantityMimic2))
    def test_mimic_setting(self, Mimic):
        mimic = Mimic([1., 2.], u.m)
        q = u.Quantity(np.arange(10.), u.cm)
        q[8:] = mimic
        assert np.all(q[:8].value == np.arange(8.))
        assert np.all(q[8:].value == [100., 200.])

    def test_mimic_function_unit(self):
        mimic = QuantityMimic([1., 2.], u.dex(u.cm/u.s**2))
        d = u.Dex(mimic)
        assert isinstance(d, u.Dex)
        assert d.unit == u.dex(u.cm/u.s**2)
        assert np.all(d.value == [1., 2.])
        q = u.Quantity(mimic, subok=True)
        assert isinstance(q, u.Dex)
        assert q.unit == u.dex(u.cm/u.s**2)
        assert np.all(q.value == [1., 2.])
        with pytest.raises(u.UnitTypeError):
            u.Quantity(mimic)


def test_masked_quantity_str_repr():
    """Ensure we don't break masked Quantity representation."""
    # Really, masked quantities do not work well, but at least let the
    # basics work.
    masked_quantity = np.ma.array([1, 2, 3, 4] * u.kg,
                                  mask=[True, False, True, False])
    str(masked_quantity)
    repr(masked_quantity)


class TestQuantitySubclassAboveAndBelow:
    @classmethod
    def setup_class(self):
        class MyArray(np.ndarray):
            def __array_finalize__(self, obj):
                super_array_finalize = super().__array_finalize__
                if super_array_finalize is not None:
                    super_array_finalize(obj)
                if hasattr(obj, 'my_attr'):
                    self.my_attr = obj.my_attr

        self.MyArray = MyArray
        self.MyQuantity1 = type('MyQuantity1', (u.Quantity, MyArray),
                                dict(my_attr='1'))
        self.MyQuantity2 = type('MyQuantity2', (MyArray, u.Quantity),
                                dict(my_attr='2'))

    def test_setup(self):
        mq1 = self.MyQuantity1(10, u.m)
        assert isinstance(mq1, self.MyQuantity1)
        assert mq1.my_attr == '1'
        assert mq1.unit is u.m
        mq2 = self.MyQuantity2(10, u.m)
        assert isinstance(mq2, self.MyQuantity2)
        assert mq2.my_attr == '2'
        assert mq2.unit is u.m

    def test_attr_propagation(self):
        mq1 = self.MyQuantity1(10, u.m)
        mq12 = self.MyQuantity2(mq1)
        assert isinstance(mq12, self.MyQuantity2)
        assert not isinstance(mq12, self.MyQuantity1)
        assert mq12.my_attr == '1'
        assert mq12.unit is u.m

        mq2 = self.MyQuantity2(10, u.m)
        mq21 = self.MyQuantity1(mq2)
        assert isinstance(mq21, self.MyQuantity1)
        assert not isinstance(mq21, self.MyQuantity2)
        assert mq21.my_attr == '2'
        assert mq21.unit is u.m
