# 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 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.0, 2.0], dtype=np.float32)
        q3_32 = u.Quantity(a3_32, u.yr)
        assert q3_32.dtype == a3_32.dtype
        a3_16 = np.array([1.0, 2.0], 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_numpy_style_dtype_inspect(self):
        """Test that if ``dtype=None``, NumPy's dtype inspection is used."""
        q2 = u.Quantity(12, dtype=None)
        assert np.issubdtype(q2.dtype, np.integer)

    def test_float_dtype_promotion(self):
        """Test that if ``dtype=numpy.inexact``, the minimum precision is float64."""
        q1 = u.Quantity(12, dtype=np.inexact)
        assert not np.issubdtype(q1.dtype, np.integer)
        assert q1.dtype == np.float64

        q2 = u.Quantity(np.float64(12), dtype=np.inexact)
        assert q2.dtype == np.float64

        q3 = u.Quantity(np.float32(12), dtype=np.inexact)
        assert q3.dtype == np.float32

        if hasattr(np, "float16"):
            q3 = u.Quantity(np.float16(12), dtype=np.inexact)
            assert q3.dtype == np.float16

        if hasattr(np, "float128"):
            q4 = u.Quantity(np.float128(12), dtype=np.inexact)
            assert q4.dtype == np.float128

    def test_copy(self):
        # By default, a new quantity is constructed, but not if copy=False

        a = np.arange(10.0)

        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.0), 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.0), 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.0), 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.0)
        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.0)
        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.0 * a)

        q3 = u.Quantity(mylookalike, copy=False)
        assert np.all(q3.value == mylookalike)
        q3[2] = 0
        assert q3[2] == 0.0
        assert mylookalike[2] == 0.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.0
        assert mylookalike[2] == 2.0

        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.0 << u.m
        assert isinstance(q1, u.Quantity)
        assert q1.unit == u.m
        assert q1.value == 1.0
        # With an array, we get an actual view.
        a2 = np.arange(10.0)
        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.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.0)
        a2[8] = 0.0
        assert q3[8].value == 8000.0
        # Without a unit change, we do get a view.
        q4 = q2 << q2.unit
        a2[7] = 0.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.0)
        a2[8] = -1.0
        # 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.0 * u.cm
        a6 = np.arange(5.0)
        q6 = a6 << not_quite_a_foot
        assert q6.unit == u.Unit(not_quite_a_foot)
        assert np.all(q6.to_value(u.cm) == 30.0 * 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.0 * 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.0 >> 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.0 * self.q1
        assert new_quantity.value == 171.3
        assert new_quantity.unit == u.meter

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

        # Multiple with a unit.
        new_quantity = self.q1 * u.s
        assert new_quantity.value == 11.42
        assert new_quantity.unit == u.Unit("m s")

        # Reverse multiple with a unit.
        new_quantity = u.s * self.q1
        assert new_quantity.value == 11.42
        assert new_quantity.unit == u.Unit("m s")

    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.0
        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.0
        assert new_quantity.unit == u.Unit("1/m")

        # Divide by a unit.
        new_quantity = self.q1 / u.s
        assert new_quantity.value == 11.42
        assert new_quantity.unit == u.Unit("m/s")

        # Divide into a unit.
        new_quantity = u.s / self.q1
        assert new_quantity.value == 1 / 11.42
        assert new_quantity.unit == u.Unit("s/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)
        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  # fmt: skip
        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.0 * u.m / u.s
        new_quantity = abs(q)
        assert new_quantity.value == q.value
        assert new_quantity.unit == q.unit

        q = -1.0 * 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, match=r"Unsupported operand type\(s\) for ufunc .*"
        ):
            q1 + {"a": 1}

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

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

        dq2 = dq + 1.0
        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.0, u.meter)
        time = u.Quantity(11.0, 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.0 / (4.0 * np.pi * G)).to(u.pc**-3 / u.s**-2 * u.kg)

        # Area
        side1 = u.Quantity(11.0, u.centimeter)
        side2 = u.Quantity(7.0, u.centimeter)
        area = side1 * side2
        assert_array_almost_equal(area.value, 77.0, 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.0 * unit
        assert q.__eq__(unit) is NotImplemented
        assert unit.__eq__(q) is True
        assert q == unit
        q = 1000.0 * u.mm**3
        assert q == unit

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

        for quantity in (1.0 * u.cm, 1.0 * u.dimensionless_unscaled):
            with pytest.raises(ValueError, match="ambiguous"):
                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.0
        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.0, 2.0, 3.0], u.m)
        assert np.all(np.array(q) == np.array([1.0, 2.0, 3.0]))


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_ilshift():  # in-place conversion
    q = u.Quantity(10, unit=u.one)

    # Incompatible units. This goes through ilshift and hits a
    # UnitConversionError first in ilshift, then in the unit's rlshift.
    with pytest.raises(u.UnitConversionError):
        q <<= u.rad

    # unless the equivalency is enabled
    with u.add_enabled_equivalencies(u.dimensionless_angles()):
        q <<= u.rad

    assert np.isclose(q, 10 * u.rad)


def test_regression_12964():
    # This will fail if the fix to
    # https://github.com/astropy/astropy/issues/12964 doesn't work.
    x = u.Quantity(10, u.km, dtype=int)
    x <<= u.pc

    # We add a test that this worked.
    assert x.unit is u.pc
    assert x.dtype == np.float64


def test_quantity_value_views():
    q1 = u.Quantity([1.0, 2.0], unit=u.meter)
    # views if the unit is the same.
    v1 = q1.value
    v1[0] = 0.0
    assert np.all(q1 == [0.0, 2.0] * u.meter)
    v2 = q1.to_value()
    v2[1] = 3.0
    assert np.all(q1 == [0.0, 3.0] * u.meter)
    v3 = q1.to_value("m")
    v3[0] = 1.0
    assert np.all(q1 == [1.0, 3.0] * u.meter)
    q2 = q1.to("m", copy=False)
    q2[0] = 2 * u.meter
    assert np.all(q1 == [2.0, 3.0] * u.meter)
    v4 = q1.to_value("cm")
    v4[0] = 0.0
    # copy if different unit.
    assert np.all(q1 == [2.0, 3.0] * 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.0 * u.m * u.s**2 / (200.0 * u.ms) ** 2  # 250 meters
    assert q1.si.value == 250
    assert q1.si.unit == u.m

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

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


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

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

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

    q = 10.0 * 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.0
        assert u.Quantity(1, u.m) != 0.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.0, 1.0, 1000.0], u.m)
        b = u.Quantity(1.0, 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.0, 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.0
        ne = a != 1.0
        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.0, 1000.0])
        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.0, 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.0, 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"
        assert f"{q1:cds}" == "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"
        assert f"{self.scalarintq:cds}" == "1 m"
        assert f"{self.scalarfloatq:cds}" == "1.3 m"

    def test_uninitialized_unit_format(self):
        bad_quantity = np.arange(10.0).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.0 * 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.0 * 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.0, 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.0, 2.0, 3.0), (4.0, 5.0, 6.0), (7.0, 8.0, 9.0)],
        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.0, 2.0, 3.0]) * u.m
    assert q[0] == 1.0 * u.m
    assert np.all(q[0:2] == u.Quantity([1.0, 2.0], u.m))


def test_array_setslice():
    q = np.array([1.0, 2.0, 3.0]) * u.m
    q[1:2] = np.array([400.0]) * u.cm
    assert np.all(q == np.array([1.0, 4.0, 3.0]) * u.m)


def test_inverse_quantity():
    """
    Regression test from issue #679
    """
    q = u.Quantity(4.0, 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.0 * 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.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), 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.0, 2.0, 3.0]), 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.0 * 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.0
    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(0.5, u.km)
    q = u.Quantity("+5e-1km")
    assert q == u.Quantity(0.5, u.km)
    q = u.Quantity("5", u.m)
    assert q == u.Quantity(5.0, u.m)
    q = u.Quantity("5 km", u.m)
    assert q.value == 5000.0
    assert q.unit == u.m
    q = u.Quantity("5Em")
    assert q == u.Quantity(5.0, 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.0
    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.0), 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_method(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.0) * u.km)
        assert type(l) is self.Length
        with pytest.raises(u.UnitTypeError):
            self.Length(np.arange(10.0) * u.hour)

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

        l2 = self.Length2(np.arange(5.0))
        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.0))

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

        v = np.arange(5.0).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.0) * u.cm)
        sum1 = l + 1.0 * u.m
        assert type(sum1) is self.Length
        sum2 = 1.0 * u.km + l
        assert type(sum2) is self.Length
        sum3 = l + l
        assert type(sum3) is self.Length
        res1 = l * (1.0 * 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.0, my_unit)
    assert type(q1) is u.Quantity
    q2 = u.Quantity(1.0, 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.0)
        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.0, 2.0], u.m)
        q = u.Quantity(np.arange(10.0), u.cm)
        q[8:] = mimic
        assert np.all(q[:8].value == np.arange(8.0))
        assert np.all(q[8:].value == [100.0, 200.0])

    def test_mimic_function_unit(self):
        mimic = QuantityMimic([1.0, 2.0], 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.0, 2.0])
        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.0, 2.0])
        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
