import copy
import datetime
import math
import operator as op
import warnings
from unittest.mock import patch

from pint import DimensionalityError, OffsetUnitCalculusError, UnitRegistry
from pint.compat import BehaviorChangeWarning, np
from pint.testsuite import QuantityTestCase, helpers
from pint.testsuite.parameterized import ParameterizedTestCase
from pint.unit import UnitsContainer


class FakeWrapper:
    # Used in test_upcast_type_rejection_on_creation
    def __init__(self, q):
        self.q = q


class TestQuantity(QuantityTestCase):

    FORCE_NDARRAY = False

    def test_quantity_creation(self):
        for args in (
            (4.2, "meter"),
            (4.2, UnitsContainer(meter=1)),
            (4.2, self.ureg.meter),
            ("4.2*meter",),
            ("4.2/meter**(-1)",),
            (self.Q_(4.2, "meter"),),
        ):
            x = self.Q_(*args)
            self.assertEqual(x.magnitude, 4.2)
            self.assertEqual(x.units, UnitsContainer(meter=1))

        x = self.Q_(4.2, UnitsContainer(length=1))
        y = self.Q_(x)
        self.assertEqual(x.magnitude, y.magnitude)
        self.assertEqual(x.units, y.units)
        self.assertIsNot(x, y)

        x = self.Q_(4.2, None)
        self.assertEqual(x.magnitude, 4.2)
        self.assertEqual(x.units, UnitsContainer())

        with self.capture_log() as buffer:
            self.assertEqual(4.2 * self.ureg.meter, self.Q_(4.2, 2 * self.ureg.meter))
            self.assertEqual(len(buffer), 1)

    def test_quantity_bool(self):
        self.assertTrue(self.Q_(1, None))
        self.assertTrue(self.Q_(1, "meter"))
        self.assertFalse(self.Q_(0, None))
        self.assertFalse(self.Q_(0, "meter"))
        self.assertRaises(ValueError, bool, self.Q_(0, "degC"))
        self.assertFalse(self.Q_(0, "delta_degC"))

    def test_quantity_comparison(self):
        x = self.Q_(4.2, "meter")
        y = self.Q_(4.2, "meter")
        z = self.Q_(5, "meter")
        j = self.Q_(5, "meter*meter")

        # identity for single object
        self.assertTrue(x == x)
        self.assertFalse(x != x)

        # identity for multiple objects with same value
        self.assertTrue(x == y)
        self.assertFalse(x != y)

        self.assertTrue(x <= y)
        self.assertTrue(x >= y)
        self.assertFalse(x < y)
        self.assertFalse(x > y)

        self.assertFalse(x == z)
        self.assertTrue(x != z)
        self.assertTrue(x < z)

        self.assertTrue(z != j)

        self.assertNotEqual(z, j)
        self.assertEqual(self.Q_(0, "meter"), self.Q_(0, "centimeter"))
        self.assertNotEqual(self.Q_(0, "meter"), self.Q_(0, "second"))

        self.assertLess(self.Q_(10, "meter"), self.Q_(5, "kilometer"))

    def test_quantity_comparison_convert(self):
        self.assertEqual(self.Q_(1000, "millimeter"), self.Q_(1, "meter"))
        self.assertEqual(
            self.Q_(1000, "millimeter/min"), self.Q_(1000 / 60, "millimeter/s")
        )

    def test_quantity_repr(self):
        x = self.Q_(4.2, UnitsContainer(meter=1))
        self.assertEqual(str(x), "4.2 meter")
        self.assertEqual(repr(x), "<Quantity(4.2, 'meter')>")

    def test_quantity_hash(self):
        x = self.Q_(4.2, "meter")
        x2 = self.Q_(4200, "millimeter")
        y = self.Q_(2, "second")
        z = self.Q_(0.5, "hertz")
        self.assertEqual(hash(x), hash(x2))

        # Dimensionless equality
        self.assertEqual(hash(y * z), hash(1.0))

        # Dimensionless equality from a different unit registry
        ureg2 = UnitRegistry(force_ndarray=self.FORCE_NDARRAY)
        y2 = ureg2.Quantity(2, "second")
        z2 = ureg2.Quantity(0.5, "hertz")
        self.assertEqual(hash(y * z), hash(y2 * z2))

    def test_quantity_format(self):
        x = self.Q_(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1))
        for spec, result in (
            ("{}", str(x)),
            ("{!s}", str(x)),
            ("{!r}", repr(x)),
            ("{.magnitude}", str(x.magnitude)),
            ("{.units}", str(x.units)),
            ("{.magnitude!s}", str(x.magnitude)),
            ("{.units!s}", str(x.units)),
            ("{.magnitude!r}", repr(x.magnitude)),
            ("{.units!r}", repr(x.units)),
            ("{:.4f}", f"{x.magnitude:.4f} {x.units!s}"),
            (
                "{:L}",
                r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
            ),
            ("{:P}", "4.12345678 kilogram·meter²/second"),
            ("{:H}", r"\[4.12345678\ kilogram\ meter^2/second\]"),
            ("{:C}", "4.12345678 kilogram*meter**2/second"),
            ("{:~}", "4.12345678 kg * m ** 2 / s"),
            (
                "{:L~}",
                r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}",
            ),
            ("{:P~}", "4.12345678 kg·m²/s"),
            ("{:H~}", r"\[4.12345678\ kg\ m^2/s\]"),
            ("{:C~}", "4.12345678 kg*m**2/s"),
            ("{:Lx}", r"\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}"),
        ):
            with self.subTest(spec):
                self.assertEqual(spec.format(x), result)

        # Check the special case that prevents e.g. '3 1 / second'
        x = self.Q_(3, UnitsContainer(second=-1))
        self.assertEqual(f"{x}", "3 / second")

    @helpers.requires_numpy()
    def test_quantity_array_format(self):
        x = self.Q_(
            np.array([1e-16, 1.0000001, 10000000.0, 1e12, np.nan, np.inf]),
            "kg * m ** 2",
        )
        for spec, result in (
            ("{}", str(x)),
            ("{.magnitude}", str(x.magnitude)),
            (
                "{:e}",
                "[1.000000e-16 1.000000e+00 1.000000e+07 1.000000e+12 nan inf] kilogram * meter ** 2",
            ),
            (
                "{:E}",
                "[1.000000E-16 1.000000E+00 1.000000E+07 1.000000E+12 NAN INF] kilogram * meter ** 2",
            ),
            (
                "{:.2f}",
                "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kilogram * meter ** 2",
            ),
            ("{:.2f~P}", "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kg·m²"),
            ("{:g~P}", "[1e-16 1 1e+07 1e+12 nan inf] kg·m²"),
        ):
            with self.subTest(spec):
                self.assertEqual(spec.format(x), result)

    def test_format_compact(self):
        q1 = (200e-9 * self.ureg.s).to_compact()
        q1b = self.Q_(200.0, "nanosecond")
        self.assertAlmostEqual(q1.magnitude, q1b.magnitude)
        self.assertEqual(q1.units, q1b.units)

        q2 = (1e-2 * self.ureg("kg m/s^2")).to_compact("N")
        q2b = self.Q_(10.0, "millinewton")
        self.assertEqual(q2.magnitude, q2b.magnitude)
        self.assertEqual(q2.units, q2b.units)

        q3 = (-1000.0 * self.ureg("meters")).to_compact()
        q3b = self.Q_(-1.0, "kilometer")
        self.assertEqual(q3.magnitude, q3b.magnitude)
        self.assertEqual(q3.units, q3b.units)

        self.assertEqual(f"{q1:#.1f}", f"{q1b}")
        self.assertEqual(f"{q2:#.1f}", f"{q2b}")
        self.assertEqual(f"{q3:#.1f}", f"{q3b}")

    def test_default_formatting(self):
        ureg = UnitRegistry()
        x = ureg.Quantity(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1))
        for spec, result in (
            (
                "L",
                r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
            ),
            ("P", "4.12345678 kilogram·meter²/second"),
            ("H", r"\[4.12345678\ kilogram\ meter^2/second\]"),
            ("C", "4.12345678 kilogram*meter**2/second"),
            ("~", "4.12345678 kg * m ** 2 / s"),
            ("L~", r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"),
            ("P~", "4.12345678 kg·m²/s"),
            ("H~", r"\[4.12345678\ kg\ m^2/s\]"),
            ("C~", "4.12345678 kg*m**2/s"),
        ):
            with self.subTest(spec):
                ureg.default_format = spec
                self.assertEqual(f"{x}", result)

    def test_exponent_formatting(self):
        ureg = UnitRegistry()
        x = ureg.Quantity(1e20, "meter")
        self.assertEqual(f"{x:~H}", r"\[1×10^{20}\ m\]")
        self.assertEqual(f"{x:~L}", r"1\times 10^{20}\ \mathrm{m}")
        self.assertEqual(f"{x:~P}", r"1×10²⁰ m")

        x /= 1e40
        self.assertEqual(f"{x:~H}", r"\[1×10^{-20}\ m\]")
        self.assertEqual(f"{x:~L}", r"1\times 10^{-20}\ \mathrm{m}")
        self.assertEqual(f"{x:~P}", r"1×10⁻²⁰ m")

    def test_ipython(self):
        alltext = []

        class Pretty:
            @staticmethod
            def text(text):
                alltext.append(text)

            @classmethod
            def pretty(cls, data):
                try:
                    data._repr_pretty_(cls, False)
                except AttributeError:
                    alltext.append(str(data))

        ureg = UnitRegistry()
        x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1))
        self.assertEqual(x._repr_html_(), r"\[3.5\ kilogram\ meter^2/second\]")
        self.assertEqual(
            x._repr_latex_(),
            r"$3.5\ \frac{\mathrm{kilogram} \cdot "
            r"\mathrm{meter}^{2}}{\mathrm{second}}$",
        )
        x._repr_pretty_(Pretty, False)
        self.assertEqual("".join(alltext), "3.5 kilogram·meter²/second")
        ureg.default_format = "~"
        self.assertEqual(x._repr_html_(), r"\[3.5\ kg\ m^2/s\]")
        self.assertEqual(
            x._repr_latex_(),
            r"$3.5\ \frac{\mathrm{kg} \cdot " r"\mathrm{m}^{2}}{\mathrm{s}}$",
        )
        alltext = []
        x._repr_pretty_(Pretty, False)
        self.assertEqual("".join(alltext), "3.5 kg·m²/s")

    def test_to_base_units(self):
        x = self.Q_("1*inch")
        self.assertQuantityAlmostEqual(x.to_base_units(), self.Q_(0.0254, "meter"))
        x = self.Q_("1*inch*inch")
        self.assertQuantityAlmostEqual(
            x.to_base_units(), self.Q_(0.0254 ** 2.0, "meter*meter")
        )
        x = self.Q_("1*inch/minute")
        self.assertQuantityAlmostEqual(
            x.to_base_units(), self.Q_(0.0254 / 60.0, "meter/second")
        )

    def test_convert(self):
        self.assertQuantityAlmostEqual(
            self.Q_("2 inch").to("meter"), self.Q_(2.0 * 0.0254, "meter")
        )
        self.assertQuantityAlmostEqual(
            self.Q_("2 meter").to("inch"), self.Q_(2.0 / 0.0254, "inch")
        )
        self.assertQuantityAlmostEqual(
            self.Q_("2 sidereal_year").to("second"), self.Q_(63116297.5325, "second")
        )
        self.assertQuantityAlmostEqual(
            self.Q_("2.54 centimeter/second").to("inch/second"),
            self.Q_("1 inch/second"),
        )
        self.assertAlmostEqual(self.Q_("2.54 centimeter").to("inch").magnitude, 1)
        self.assertAlmostEqual(self.Q_("2 second").to("millisecond").magnitude, 2000)

    @helpers.requires_numpy()
    def test_convert_numpy(self):

        # Conversions with single units take a different codepath than
        # Conversions with more than one unit.
        src_dst1 = UnitsContainer(meter=1), UnitsContainer(inch=1)
        src_dst2 = UnitsContainer(meter=1, second=-1), UnitsContainer(inch=1, minute=-1)
        for src, dst in (src_dst1, src_dst2):
            a = np.ones((3, 1))
            ac = np.ones((3, 1))

            q = self.Q_(a, src)
            qac = self.Q_(ac, src).to(dst)
            r = q.to(dst)
            self.assertQuantityAlmostEqual(qac, r)
            self.assertIsNot(r, q)
            self.assertIsNot(r._magnitude, a)

    def test_convert_from(self):
        x = self.Q_("2*inch")
        meter = self.ureg.meter

        # from quantity
        self.assertQuantityAlmostEqual(meter.from_(x), self.Q_(2.0 * 0.0254, "meter"))
        self.assertQuantityAlmostEqual(meter.m_from(x), 2.0 * 0.0254)

        # from unit
        self.assertQuantityAlmostEqual(
            meter.from_(self.ureg.inch), self.Q_(0.0254, "meter")
        )
        self.assertQuantityAlmostEqual(meter.m_from(self.ureg.inch), 0.0254)

        # from number
        self.assertQuantityAlmostEqual(
            meter.from_(2, strict=False), self.Q_(2.0, "meter")
        )
        self.assertQuantityAlmostEqual(meter.m_from(2, strict=False), 2.0)

        # from number (strict mode)
        self.assertRaises(ValueError, meter.from_, 2)
        self.assertRaises(ValueError, meter.m_from, 2)

    @helpers.requires_numpy()
    def test_retain_unit(self):
        # Test that methods correctly retain units and do not degrade into
        # ordinary ndarrays.  List contained in __copy_units.
        a = np.ones((3, 2))
        q = self.Q_(a, "km")
        self.assertEqual(q.u, q.reshape(2, 3).u)
        self.assertEqual(q.u, q.swapaxes(0, 1).u)
        self.assertEqual(q.u, q.mean().u)
        self.assertEqual(q.u, np.compress((q == q[0, 0]).any(0), q).u)

    def test_context_attr(self):
        self.assertEqual(self.ureg.meter, self.Q_(1, "meter"))

    def test_both_symbol(self):
        self.assertEqual(self.Q_(2, "ms"), self.Q_(2, "millisecond"))
        self.assertEqual(self.Q_(2, "cm"), self.Q_(2, "centimeter"))

    def test_dimensionless_units(self):
        self.assertAlmostEqual(
            self.Q_(360, "degree").to("radian").magnitude, 2 * math.pi
        )
        self.assertAlmostEqual(self.Q_(2 * math.pi, "radian"), self.Q_(360, "degree"))
        self.assertEqual(self.Q_(1, "radian").dimensionality, UnitsContainer())
        self.assertTrue(self.Q_(1, "radian").dimensionless)
        self.assertFalse(self.Q_(1, "radian").unitless)

        self.assertEqual(self.Q_(1, "meter") / self.Q_(1, "meter"), 1)
        self.assertEqual((self.Q_(1, "meter") / self.Q_(1, "mm")).to(""), 1000)

        self.assertEqual(self.Q_(10) // self.Q_(360, "degree"), 1)
        self.assertEqual(self.Q_(400, "degree") // self.Q_(2 * math.pi), 1)
        self.assertEqual(self.Q_(400, "degree") // (2 * math.pi), 1)
        self.assertEqual(7 // self.Q_(360, "degree"), 1)

    def test_offset(self):
        self.assertQuantityAlmostEqual(
            self.Q_(0, "kelvin").to("kelvin"), self.Q_(0, "kelvin")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(0, "degC").to("kelvin"), self.Q_(273.15, "kelvin")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(0, "degF").to("kelvin"), self.Q_(255.372222, "kelvin"), rtol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(100, "kelvin").to("kelvin"), self.Q_(100, "kelvin")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "degC").to("kelvin"), self.Q_(373.15, "kelvin")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "degF").to("kelvin"),
            self.Q_(310.92777777, "kelvin"),
            rtol=0.01,
        )

        self.assertQuantityAlmostEqual(
            self.Q_(0, "kelvin").to("degC"), self.Q_(-273.15, "degC")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "kelvin").to("degC"), self.Q_(-173.15, "degC")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(0, "kelvin").to("degF"), self.Q_(-459.67, "degF"), rtol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "kelvin").to("degF"), self.Q_(-279.67, "degF"), rtol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(32, "degF").to("degC"), self.Q_(0, "degC"), atol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "degC").to("degF"), self.Q_(212, "degF"), atol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(54, "degF").to("degC"), self.Q_(12.2222, "degC"), atol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(12, "degC").to("degF"), self.Q_(53.6, "degF"), atol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(12, "kelvin").to("degC"), self.Q_(-261.15, "degC"), atol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(12, "degC").to("kelvin"), self.Q_(285.15, "kelvin"), atol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(12, "kelvin").to("degR"), self.Q_(21.6, "degR"), atol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(12, "degR").to("kelvin"), self.Q_(6.66666667, "kelvin"), atol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(12, "degC").to("degR"), self.Q_(513.27, "degR"), atol=0.01
        )
        self.assertQuantityAlmostEqual(
            self.Q_(12, "degR").to("degC"), self.Q_(-266.483333, "degC"), atol=0.01
        )

    def test_offset_delta(self):
        self.assertQuantityAlmostEqual(
            self.Q_(0, "delta_degC").to("kelvin"), self.Q_(0, "kelvin")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(0, "delta_degF").to("kelvin"), self.Q_(0, "kelvin"), rtol=0.01
        )

        self.assertQuantityAlmostEqual(
            self.Q_(100, "kelvin").to("delta_degC"), self.Q_(100, "delta_degC")
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "kelvin").to("delta_degF"),
            self.Q_(180, "delta_degF"),
            rtol=0.01,
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "delta_degF").to("kelvin"),
            self.Q_(55.55555556, "kelvin"),
            rtol=0.01,
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "delta_degC").to("delta_degF"),
            self.Q_(180, "delta_degF"),
            rtol=0.01,
        )
        self.assertQuantityAlmostEqual(
            self.Q_(100, "delta_degF").to("delta_degC"),
            self.Q_(55.55555556, "delta_degC"),
            rtol=0.01,
        )

        self.assertQuantityAlmostEqual(
            self.Q_(12.3, "delta_degC").to("delta_degF"),
            self.Q_(22.14, "delta_degF"),
            rtol=0.01,
        )

    def test_pickle(self):
        import pickle

        def pickle_test(q):
            self.assertEqual(q, pickle.loads(pickle.dumps(q)))

        pickle_test(self.Q_(32, ""))
        pickle_test(self.Q_(2.4, ""))
        pickle_test(self.Q_(32, "m/s"))
        pickle_test(self.Q_(2.4, "m/s"))

    @helpers.requires_numpy()
    def test_from_sequence(self):
        u_array_ref = self.Q_([200, 1000], "g")
        u_array_ref_reversed = self.Q_([1000, 200], "g")
        u_seq = [self.Q_("200g"), self.Q_("1kg")]
        u_seq_reversed = u_seq[::-1]

        u_array = self.Q_.from_sequence(u_seq)
        self.assertTrue(all(u_array == u_array_ref))

        u_array_2 = self.Q_.from_sequence(u_seq_reversed)
        self.assertTrue(all(u_array_2 == u_array_ref_reversed))
        self.assertFalse(u_array_2.u == u_array_ref_reversed.u)

        u_array_3 = self.Q_.from_sequence(u_seq_reversed, units="g")
        self.assertTrue(all(u_array_3 == u_array_ref_reversed))
        self.assertTrue(u_array_3.u == u_array_ref_reversed.u)

        with self.assertRaises(ValueError):
            self.Q_.from_sequence([])

        u_array_5 = self.Q_.from_list(u_seq)
        self.assertTrue(all(u_array_5 == u_array_ref))

    @helpers.requires_numpy()
    def test_iter(self):
        # Verify that iteration gives element as Quantity with same units
        x = self.Q_([0, 1, 2, 3], "m")
        self.assertQuantityEqual(next(iter(x)), self.Q_(0, "m"))

    def test_notiter(self):
        # Verify that iter() crashes immediately, without needing to draw any
        # element from it, if the magnitude isn't iterable
        x = self.Q_(1, "m")
        with self.assertRaises(TypeError):
            iter(x)

    @helpers.requires_array_function_protocol()
    @patch("pint.quantity.SKIP_ARRAY_FUNCTION_CHANGE_WARNING", False)
    def test_array_function_warning_on_creation(self):
        # Test that warning is raised on first creation, but not second
        with self.assertWarns(BehaviorChangeWarning):
            self.Q_([])
        with warnings.catch_warnings():
            warnings.filterwarnings("error")
            self.Q_([])

    @helpers.requires_not_numpy()
    def test_no_ndarray_coercion_without_numpy(self):
        self.assertRaises(ValueError, self.Q_(1, "m").__array__)

    @patch("pint.compat.upcast_types", [FakeWrapper])
    def test_upcast_type_rejection_on_creation(self):
        self.assertRaises(TypeError, self.Q_, FakeWrapper(42), "m")
        self.assertEqual(FakeWrapper(self.Q_(42, "m")).q, self.Q_(42, "m"))


class TestQuantityToCompact(QuantityTestCase):
    def assertQuantityAlmostIdentical(self, q1, q2):
        self.assertEqual(q1.units, q2.units)
        self.assertAlmostEqual(q1.magnitude, q2.magnitude)

    def compareQuantity_compact(self, q, expected_compact, unit=None):
        self.assertQuantityAlmostIdentical(q.to_compact(unit=unit), expected_compact)

    def test_dimensionally_simple_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(1 * ureg.m, 1 * ureg.m)
        self.compareQuantity_compact(1e-9 * ureg.m, 1 * ureg.nm)

    def test_power_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(900 * ureg.m ** 2, 900 * ureg.m ** 2)
        self.compareQuantity_compact(1e7 * ureg.m ** 2, 10 * ureg.km ** 2)

    def test_inverse_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(1 / ureg.m, 1 / ureg.m)
        self.compareQuantity_compact(100e9 / ureg.m, 100 / ureg.nm)

    def test_inverse_square_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(1 / ureg.m ** 2, 1 / ureg.m ** 2)
        self.compareQuantity_compact(1e11 / ureg.m ** 2, 1e5 / ureg.mm ** 2)

    def test_fractional_units(self):
        ureg = self.ureg
        # Typing denominator first to provoke potential error
        self.compareQuantity_compact(20e3 * ureg("hr^(-1) m"), 20 * ureg.km / ureg.hr)

    def test_fractional_exponent_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(1 * ureg.m ** 0.5, 1 * ureg.m ** 0.5)
        self.compareQuantity_compact(1e-2 * ureg.m ** 0.5, 10 * ureg.um ** 0.5)

    def test_derived_units(self):
        ureg = self.ureg
        self.compareQuantity_compact(0.5 * ureg.megabyte, 500 * ureg.kilobyte)
        self.compareQuantity_compact(1e-11 * ureg.N, 10 * ureg.pN)

    def test_unit_parameter(self):
        ureg = self.ureg
        self.compareQuantity_compact(
            self.Q_(100e-9, "kg m / s^2"), 100 * ureg.nN, ureg.N
        )
        self.compareQuantity_compact(
            self.Q_(101.3e3, "kg/m/s^2"), 101.3 * ureg.kPa, ureg.Pa
        )

    def test_limits_magnitudes(self):
        ureg = self.ureg
        self.compareQuantity_compact(0 * ureg.m, 0 * ureg.m)
        self.compareQuantity_compact(float("inf") * ureg.m, float("inf") * ureg.m)

    def test_nonnumeric_magnitudes(self):
        ureg = self.ureg
        x = "some string" * ureg.m
        with self.assertWarns(RuntimeWarning):
            self.compareQuantity_compact(x, x)


class TestQuantityBasicMath(QuantityTestCase):

    FORCE_NDARRAY = False

    def _test_inplace(self, operator, value1, value2, expected_result, unit=None):
        if isinstance(value1, str):
            value1 = self.Q_(value1)
        if isinstance(value2, str):
            value2 = self.Q_(value2)
        if isinstance(expected_result, str):
            expected_result = self.Q_(expected_result)

        if unit is not None:
            value1 = value1 * unit
            value2 = value2 * unit
            expected_result = expected_result * unit

        value1 = copy.copy(value1)
        value2 = copy.copy(value2)
        id1 = id(value1)
        id2 = id(value2)
        value1 = operator(value1, value2)
        value2_cpy = copy.copy(value2)
        self.assertQuantityAlmostEqual(value1, expected_result)
        self.assertEqual(id1, id(value1))
        self.assertQuantityAlmostEqual(value2, value2_cpy)
        self.assertEqual(id2, id(value2))

    def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None):
        if isinstance(value1, str):
            value1 = self.Q_(value1)
        if isinstance(value2, str):
            value2 = self.Q_(value2)
        if isinstance(expected_result, str):
            expected_result = self.Q_(expected_result)

        if unit is not None:
            value1 = value1 * unit
            value2 = value2 * unit
            expected_result = expected_result * unit

        id1 = id(value1)
        id2 = id(value2)

        value1_cpy = copy.copy(value1)
        value2_cpy = copy.copy(value2)

        result = operator(value1, value2)

        self.assertQuantityAlmostEqual(expected_result, result)
        self.assertQuantityAlmostEqual(value1, value1_cpy)
        self.assertQuantityAlmostEqual(value2, value2_cpy)
        self.assertNotEqual(id(result), id1)
        self.assertNotEqual(id(result), id2)

    def _test_quantity_add_sub(self, unit, func):
        x = self.Q_(unit, "centimeter")
        y = self.Q_(unit, "inch")
        z = self.Q_(unit, "second")
        a = self.Q_(unit, None)

        func(op.add, x, x, self.Q_(unit + unit, "centimeter"))
        func(op.add, x, y, self.Q_(unit + 2.54 * unit, "centimeter"))
        func(op.add, y, x, self.Q_(unit + unit / (2.54 * unit), "inch"))
        func(op.add, a, unit, self.Q_(unit + unit, None))
        self.assertRaises(DimensionalityError, op.add, 10, x)
        self.assertRaises(DimensionalityError, op.add, x, 10)
        self.assertRaises(DimensionalityError, op.add, x, z)

        func(op.sub, x, x, self.Q_(unit - unit, "centimeter"))
        func(op.sub, x, y, self.Q_(unit - 2.54 * unit, "centimeter"))
        func(op.sub, y, x, self.Q_(unit - unit / (2.54 * unit), "inch"))
        func(op.sub, a, unit, self.Q_(unit - unit, None))
        self.assertRaises(DimensionalityError, op.sub, 10, x)
        self.assertRaises(DimensionalityError, op.sub, x, 10)
        self.assertRaises(DimensionalityError, op.sub, x, z)

    def _test_quantity_iadd_isub(self, unit, func):
        x = self.Q_(unit, "centimeter")
        y = self.Q_(unit, "inch")
        z = self.Q_(unit, "second")
        a = self.Q_(unit, None)

        func(op.iadd, x, x, self.Q_(unit + unit, "centimeter"))
        func(op.iadd, x, y, self.Q_(unit + 2.54 * unit, "centimeter"))
        func(op.iadd, y, x, self.Q_(unit + unit / 2.54, "inch"))
        func(op.iadd, a, unit, self.Q_(unit + unit, None))
        self.assertRaises(DimensionalityError, op.iadd, 10, x)
        self.assertRaises(DimensionalityError, op.iadd, x, 10)
        self.assertRaises(DimensionalityError, op.iadd, x, z)

        func(op.isub, x, x, self.Q_(unit - unit, "centimeter"))
        func(op.isub, x, y, self.Q_(unit - 2.54, "centimeter"))
        func(op.isub, y, x, self.Q_(unit - unit / 2.54, "inch"))
        func(op.isub, a, unit, self.Q_(unit - unit, None))
        self.assertRaises(DimensionalityError, op.sub, 10, x)
        self.assertRaises(DimensionalityError, op.sub, x, 10)
        self.assertRaises(DimensionalityError, op.sub, x, z)

    def _test_quantity_mul_div(self, unit, func):
        func(op.mul, unit * 10.0, "4.2*meter", "42*meter", unit)
        func(op.mul, "4.2*meter", unit * 10.0, "42*meter", unit)
        func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit)
        func(op.truediv, unit * 42, "4.2*meter", "10/meter", unit)
        func(op.truediv, "4.2*meter", unit * 10.0, "0.42*meter", unit)
        func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)

    def _test_quantity_imul_idiv(self, unit, func):
        # func(op.imul, 10.0, '4.2*meter', '42*meter')
        func(op.imul, "4.2*meter", 10.0, "42*meter", unit)
        func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit)
        # func(op.truediv, 42, '4.2*meter', '10/meter')
        func(op.itruediv, "4.2*meter", unit * 10.0, "0.42*meter", unit)
        func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)

    def _test_quantity_floordiv(self, unit, func):
        a = self.Q_("10*meter")
        b = self.Q_("3*second")
        self.assertRaises(DimensionalityError, op.floordiv, a, b)
        self.assertRaises(DimensionalityError, op.floordiv, 3, b)
        self.assertRaises(DimensionalityError, op.floordiv, a, 3)
        self.assertRaises(DimensionalityError, op.ifloordiv, a, b)
        self.assertRaises(DimensionalityError, op.ifloordiv, 3, b)
        self.assertRaises(DimensionalityError, op.ifloordiv, a, 3)
        func(op.floordiv, unit * 10.0, "4.2*meter/meter", 2, unit)
        func(op.floordiv, "10*meter", "4.2*inch", 93, unit)

    def _test_quantity_mod(self, unit, func):
        a = self.Q_("10*meter")
        b = self.Q_("3*second")
        self.assertRaises(DimensionalityError, op.mod, a, b)
        self.assertRaises(DimensionalityError, op.mod, 3, b)
        self.assertRaises(DimensionalityError, op.mod, a, 3)
        self.assertRaises(DimensionalityError, op.imod, a, b)
        self.assertRaises(DimensionalityError, op.imod, 3, b)
        self.assertRaises(DimensionalityError, op.imod, a, 3)
        func(op.mod, unit * 10.0, "4.2*meter/meter", 1.6, unit)

    def _test_quantity_ifloordiv(self, unit, func):
        func(op.ifloordiv, 10.0, "4.2*meter/meter", 2, unit)
        func(op.ifloordiv, "10*meter", "4.2*inch", 93, unit)

    def _test_quantity_divmod_one(self, a, b):
        if isinstance(a, str):
            a = self.Q_(a)
        if isinstance(b, str):
            b = self.Q_(b)

        q, r = divmod(a, b)
        self.assertEqual(q, a // b)
        self.assertEqual(r, a % b)
        self.assertEqual(a, (q * b) + r)
        self.assertEqual(q, math.floor(q))
        if b > (0 * b):
            self.assertTrue((0 * b) <= r < b)
        else:
            self.assertTrue((0 * b) >= r > b)
        if isinstance(a, self.Q_):
            self.assertEqual(r.units, a.units)
        else:
            self.assertTrue(r.unitless)
        self.assertTrue(q.unitless)

        copy_a = copy.copy(a)
        a %= b
        self.assertEqual(a, r)
        copy_a //= b
        self.assertEqual(copy_a, q)

    def _test_quantity_divmod(self):
        self._test_quantity_divmod_one("10*meter", "4.2*inch")
        self._test_quantity_divmod_one("-10*meter", "4.2*inch")
        self._test_quantity_divmod_one("-10*meter", "-4.2*inch")
        self._test_quantity_divmod_one("10*meter", "-4.2*inch")

        self._test_quantity_divmod_one("400*degree", "3")
        self._test_quantity_divmod_one("4", "180 degree")
        self._test_quantity_divmod_one(4, "180 degree")
        self._test_quantity_divmod_one("20", 4)
        self._test_quantity_divmod_one("300*degree", "100 degree")

        a = self.Q_("10*meter")
        b = self.Q_("3*second")
        self.assertRaises(DimensionalityError, divmod, a, b)
        self.assertRaises(DimensionalityError, divmod, 3, b)
        self.assertRaises(DimensionalityError, divmod, a, 3)

    def _test_numeric(self, unit, ifunc):
        self._test_quantity_add_sub(unit, self._test_not_inplace)
        self._test_quantity_iadd_isub(unit, ifunc)
        self._test_quantity_mul_div(unit, self._test_not_inplace)
        self._test_quantity_imul_idiv(unit, ifunc)
        self._test_quantity_floordiv(unit, self._test_not_inplace)
        self._test_quantity_mod(unit, self._test_not_inplace)
        self._test_quantity_divmod()
        # self._test_quantity_ifloordiv(unit, ifunc)

    def test_float(self):
        self._test_numeric(1.0, self._test_not_inplace)

    def test_fraction(self):
        import fractions

        self._test_numeric(fractions.Fraction(1, 1), self._test_not_inplace)

    @helpers.requires_numpy()
    def test_nparray(self):
        self._test_numeric(np.ones((1, 3)), self._test_inplace)

    def test_quantity_abs_round(self):

        x = self.Q_(-4.2, "meter")
        y = self.Q_(4.2, "meter")

        for fun in (abs, round, op.pos, op.neg):
            zx = self.Q_(fun(x.magnitude), "meter")
            zy = self.Q_(fun(y.magnitude), "meter")
            rx = fun(x)
            ry = fun(y)
            self.assertEqual(rx, zx, "while testing {0}".format(fun))
            self.assertEqual(ry, zy, "while testing {0}".format(fun))
            self.assertIsNot(rx, zx, "while testing {0}".format(fun))
            self.assertIsNot(ry, zy, "while testing {0}".format(fun))

    def test_quantity_float_complex(self):
        x = self.Q_(-4.2, None)
        y = self.Q_(4.2, None)
        z = self.Q_(1, "meter")
        for fun in (float, complex):
            self.assertEqual(fun(x), fun(x.magnitude))
            self.assertEqual(fun(y), fun(y.magnitude))
            self.assertRaises(DimensionalityError, fun, z)


class TestDimensions(QuantityTestCase):

    FORCE_NDARRAY = False

    def test_get_dimensionality(self):
        get = self.ureg.get_dimensionality
        self.assertEqual(get("[time]"), UnitsContainer({"[time]": 1}))
        self.assertEqual(
            get(UnitsContainer({"[time]": 1})), UnitsContainer({"[time]": 1})
        )
        self.assertEqual(get("seconds"), UnitsContainer({"[time]": 1}))
        self.assertEqual(
            get(UnitsContainer({"seconds": 1})), UnitsContainer({"[time]": 1})
        )
        self.assertEqual(get("[speed]"), UnitsContainer({"[length]": 1, "[time]": -1}))
        self.assertEqual(
            get("[acceleration]"), UnitsContainer({"[length]": 1, "[time]": -2})
        )

    def test_dimensionality(self):
        x = self.Q_(42, "centimeter")
        x.to_base_units()
        x = self.Q_(42, "meter*second")
        self.assertEqual(
            x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 1.0})
        )
        x = self.Q_(42, "meter*second*second")
        self.assertEqual(
            x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 2.0})
        )
        x = self.Q_(42, "inch*second*second")
        self.assertEqual(
            x.dimensionality, UnitsContainer({"[length]": 1.0, "[time]": 2.0})
        )
        self.assertTrue(self.Q_(42, None).dimensionless)
        self.assertFalse(self.Q_(42, "meter").dimensionless)
        self.assertTrue((self.Q_(42, "meter") / self.Q_(1, "meter")).dimensionless)
        self.assertFalse((self.Q_(42, "meter") / self.Q_(1, "second")).dimensionless)
        self.assertTrue((self.Q_(42, "meter") / self.Q_(1, "inch")).dimensionless)

    def test_inclusion(self):
        dim = self.Q_(42, "meter").dimensionality
        self.assertTrue("[length]" in dim)
        self.assertFalse("[time]" in dim)
        dim = (self.Q_(42, "meter") / self.Q_(11, "second")).dimensionality
        self.assertTrue("[length]" in dim)
        self.assertTrue("[time]" in dim)
        dim = self.Q_(20.785, "J/(mol)").dimensionality
        for dimension in ("[length]", "[mass]", "[substance]", "[time]"):
            self.assertTrue(dimension in dim)
        self.assertFalse("[angle]" in dim)


class TestQuantityWithDefaultRegistry(TestDimensions):
    @classmethod
    def setUpClass(cls):
        from pint import _DEFAULT_REGISTRY

        cls.ureg = _DEFAULT_REGISTRY
        cls.Q_ = cls.ureg.Quantity


class TestDimensionsWithDefaultRegistry(TestDimensions):
    @classmethod
    def setUpClass(cls):
        from pint import _DEFAULT_REGISTRY

        cls.ureg = _DEFAULT_REGISTRY
        cls.Q_ = cls.ureg.Quantity


class TestOffsetUnitMath(QuantityTestCase, ParameterizedTestCase):
    def setup(self):
        self.ureg.autoconvert_offset_to_baseunit = False
        self.ureg.default_as_delta = True

    additions = [
        # --- input tuple -------------------- | -- expected result --
        (((100, "kelvin"), (10, "kelvin")), (110, "kelvin")),
        (((100, "kelvin"), (10, "degC")), "error"),
        (((100, "kelvin"), (10, "degF")), "error"),
        (((100, "kelvin"), (10, "degR")), (105.56, "kelvin")),
        (((100, "kelvin"), (10, "delta_degC")), (110, "kelvin")),
        (((100, "kelvin"), (10, "delta_degF")), (105.56, "kelvin")),
        (((100, "degC"), (10, "kelvin")), "error"),
        (((100, "degC"), (10, "degC")), "error"),
        (((100, "degC"), (10, "degF")), "error"),
        (((100, "degC"), (10, "degR")), "error"),
        (((100, "degC"), (10, "delta_degC")), (110, "degC")),
        (((100, "degC"), (10, "delta_degF")), (105.56, "degC")),
        (((100, "degF"), (10, "kelvin")), "error"),
        (((100, "degF"), (10, "degC")), "error"),
        (((100, "degF"), (10, "degF")), "error"),
        (((100, "degF"), (10, "degR")), "error"),
        (((100, "degF"), (10, "delta_degC")), (118, "degF")),
        (((100, "degF"), (10, "delta_degF")), (110, "degF")),
        (((100, "degR"), (10, "kelvin")), (118, "degR")),
        (((100, "degR"), (10, "degC")), "error"),
        (((100, "degR"), (10, "degF")), "error"),
        (((100, "degR"), (10, "degR")), (110, "degR")),
        (((100, "degR"), (10, "delta_degC")), (118, "degR")),
        (((100, "degR"), (10, "delta_degF")), (110, "degR")),
        (((100, "delta_degC"), (10, "kelvin")), (110, "kelvin")),
        (((100, "delta_degC"), (10, "degC")), (110, "degC")),
        (((100, "delta_degC"), (10, "degF")), (190, "degF")),
        (((100, "delta_degC"), (10, "degR")), (190, "degR")),
        (((100, "delta_degC"), (10, "delta_degC")), (110, "delta_degC")),
        (((100, "delta_degC"), (10, "delta_degF")), (105.56, "delta_degC")),
        (((100, "delta_degF"), (10, "kelvin")), (65.56, "kelvin")),
        (((100, "delta_degF"), (10, "degC")), (65.56, "degC")),
        (((100, "delta_degF"), (10, "degF")), (110, "degF")),
        (((100, "delta_degF"), (10, "degR")), (110, "degR")),
        (((100, "delta_degF"), (10, "delta_degC")), (118, "delta_degF")),
        (((100, "delta_degF"), (10, "delta_degF")), (110, "delta_degF")),
    ]

    @ParameterizedTestCase.parameterize(("input", "expected_output"), additions)
    def test_addition(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        qin1, qin2 = input_tuple
        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
        # update input tuple with new values to have correct values on failure
        input_tuple = q1, q2
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.add, q1, q2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.add(q1, q2).units, expected.units)
            self.assertQuantityAlmostEqual(op.add(q1, q2), expected, atol=0.01)

    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(("input", "expected_output"), additions)
    def test_inplace_addition(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        (q1v, q1u), (q2v, q2u) = input_tuple
        # update input tuple with new values to have correct values on failure
        input_tuple = (
            (np.array([q1v] * 2, dtype=np.float), q1u),
            (np.array([q2v] * 2, dtype=np.float), q2u),
        )
        Q_ = self.Q_
        qin1, qin2 = input_tuple
        q1, q2 = Q_(*qin1), Q_(*qin2)
        q1_cp = copy.copy(q1)
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.iadd, q1_cp, q2)
        else:
            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
            self.assertEqual(op.iadd(q1_cp, q2).units, Q_(*expected).units)
            q1_cp = copy.copy(q1)
            self.assertQuantityAlmostEqual(op.iadd(q1_cp, q2), Q_(*expected), atol=0.01)

    subtractions = [
        (((100, "kelvin"), (10, "kelvin")), (90, "kelvin")),
        (((100, "kelvin"), (10, "degC")), (-183.15, "kelvin")),
        (((100, "kelvin"), (10, "degF")), (-160.93, "kelvin")),
        (((100, "kelvin"), (10, "degR")), (94.44, "kelvin")),
        (((100, "kelvin"), (10, "delta_degC")), (90, "kelvin")),
        (((100, "kelvin"), (10, "delta_degF")), (94.44, "kelvin")),
        (((100, "degC"), (10, "kelvin")), (363.15, "delta_degC")),
        (((100, "degC"), (10, "degC")), (90, "delta_degC")),
        (((100, "degC"), (10, "degF")), (112.22, "delta_degC")),
        (((100, "degC"), (10, "degR")), (367.59, "delta_degC")),
        (((100, "degC"), (10, "delta_degC")), (90, "degC")),
        (((100, "degC"), (10, "delta_degF")), (94.44, "degC")),
        (((100, "degF"), (10, "kelvin")), (541.67, "delta_degF")),
        (((100, "degF"), (10, "degC")), (50, "delta_degF")),
        (((100, "degF"), (10, "degF")), (90, "delta_degF")),
        (((100, "degF"), (10, "degR")), (549.67, "delta_degF")),
        (((100, "degF"), (10, "delta_degC")), (82, "degF")),
        (((100, "degF"), (10, "delta_degF")), (90, "degF")),
        (((100, "degR"), (10, "kelvin")), (82, "degR")),
        (((100, "degR"), (10, "degC")), (-409.67, "degR")),
        (((100, "degR"), (10, "degF")), (-369.67, "degR")),
        (((100, "degR"), (10, "degR")), (90, "degR")),
        (((100, "degR"), (10, "delta_degC")), (82, "degR")),
        (((100, "degR"), (10, "delta_degF")), (90, "degR")),
        (((100, "delta_degC"), (10, "kelvin")), (90, "kelvin")),
        (((100, "delta_degC"), (10, "degC")), (90, "degC")),
        (((100, "delta_degC"), (10, "degF")), (170, "degF")),
        (((100, "delta_degC"), (10, "degR")), (170, "degR")),
        (((100, "delta_degC"), (10, "delta_degC")), (90, "delta_degC")),
        (((100, "delta_degC"), (10, "delta_degF")), (94.44, "delta_degC")),
        (((100, "delta_degF"), (10, "kelvin")), (45.56, "kelvin")),
        (((100, "delta_degF"), (10, "degC")), (45.56, "degC")),
        (((100, "delta_degF"), (10, "degF")), (90, "degF")),
        (((100, "delta_degF"), (10, "degR")), (90, "degR")),
        (((100, "delta_degF"), (10, "delta_degC")), (82, "delta_degF")),
        (((100, "delta_degF"), (10, "delta_degF")), (90, "delta_degF")),
    ]

    @ParameterizedTestCase.parameterize(("input", "expected_output"), subtractions)
    def test_subtraction(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        qin1, qin2 = input_tuple
        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
        input_tuple = q1, q2
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.sub, q1, q2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.sub(q1, q2).units, expected.units)
            self.assertQuantityAlmostEqual(op.sub(q1, q2), expected, atol=0.01)

    #    @unittest.expectedFailure
    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(("input", "expected_output"), subtractions)
    def test_inplace_subtraction(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        (q1v, q1u), (q2v, q2u) = input_tuple
        # update input tuple with new values to have correct values on failure
        input_tuple = (
            (np.array([q1v] * 2, dtype=np.float), q1u),
            (np.array([q2v] * 2, dtype=np.float), q2u),
        )
        Q_ = self.Q_
        qin1, qin2 = input_tuple
        q1, q2 = Q_(*qin1), Q_(*qin2)
        q1_cp = copy.copy(q1)
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.isub, q1_cp, q2)
        else:
            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
            self.assertEqual(op.isub(q1_cp, q2).units, Q_(*expected).units)
            q1_cp = copy.copy(q1)
            self.assertQuantityAlmostEqual(op.isub(q1_cp, q2), Q_(*expected), atol=0.01)

    multiplications = [
        (((100, "kelvin"), (10, "kelvin")), (1000, "kelvin**2")),
        (((100, "kelvin"), (10, "degC")), "error"),
        (((100, "kelvin"), (10, "degF")), "error"),
        (((100, "kelvin"), (10, "degR")), (1000, "kelvin*degR")),
        (((100, "kelvin"), (10, "delta_degC")), (1000, "kelvin*delta_degC")),
        (((100, "kelvin"), (10, "delta_degF")), (1000, "kelvin*delta_degF")),
        (((100, "degC"), (10, "kelvin")), "error"),
        (((100, "degC"), (10, "degC")), "error"),
        (((100, "degC"), (10, "degF")), "error"),
        (((100, "degC"), (10, "degR")), "error"),
        (((100, "degC"), (10, "delta_degC")), "error"),
        (((100, "degC"), (10, "delta_degF")), "error"),
        (((100, "degF"), (10, "kelvin")), "error"),
        (((100, "degF"), (10, "degC")), "error"),
        (((100, "degF"), (10, "degF")), "error"),
        (((100, "degF"), (10, "degR")), "error"),
        (((100, "degF"), (10, "delta_degC")), "error"),
        (((100, "degF"), (10, "delta_degF")), "error"),
        (((100, "degR"), (10, "kelvin")), (1000, "degR*kelvin")),
        (((100, "degR"), (10, "degC")), "error"),
        (((100, "degR"), (10, "degF")), "error"),
        (((100, "degR"), (10, "degR")), (1000, "degR**2")),
        (((100, "degR"), (10, "delta_degC")), (1000, "degR*delta_degC")),
        (((100, "degR"), (10, "delta_degF")), (1000, "degR*delta_degF")),
        (((100, "delta_degC"), (10, "kelvin")), (1000, "delta_degC*kelvin")),
        (((100, "delta_degC"), (10, "degC")), "error"),
        (((100, "delta_degC"), (10, "degF")), "error"),
        (((100, "delta_degC"), (10, "degR")), (1000, "delta_degC*degR")),
        (((100, "delta_degC"), (10, "delta_degC")), (1000, "delta_degC**2")),
        (((100, "delta_degC"), (10, "delta_degF")), (1000, "delta_degC*delta_degF")),
        (((100, "delta_degF"), (10, "kelvin")), (1000, "delta_degF*kelvin")),
        (((100, "delta_degF"), (10, "degC")), "error"),
        (((100, "delta_degF"), (10, "degF")), "error"),
        (((100, "delta_degF"), (10, "degR")), (1000, "delta_degF*degR")),
        (((100, "delta_degF"), (10, "delta_degC")), (1000, "delta_degF*delta_degC")),
        (((100, "delta_degF"), (10, "delta_degF")), (1000, "delta_degF**2")),
    ]

    @ParameterizedTestCase.parameterize(("input", "expected_output"), multiplications)
    def test_multiplication(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        qin1, qin2 = input_tuple
        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
        input_tuple = q1, q2
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.mul(q1, q2).units, expected.units)
            self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)

    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(("input", "expected_output"), multiplications)
    def test_inplace_multiplication(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        (q1v, q1u), (q2v, q2u) = input_tuple
        # update input tuple with new values to have correct values on failure
        input_tuple = (
            (np.array([q1v] * 2, dtype=np.float), q1u),
            (np.array([q2v] * 2, dtype=np.float), q2u),
        )
        Q_ = self.Q_
        qin1, qin2 = input_tuple
        q1, q2 = Q_(*qin1), Q_(*qin2)
        q1_cp = copy.copy(q1)
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2)
        else:
            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
            self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units)
            q1_cp = copy.copy(q1)
            self.assertQuantityAlmostEqual(op.imul(q1_cp, q2), Q_(*expected), atol=0.01)

    divisions = [
        (((100, "kelvin"), (10, "kelvin")), (10, "")),
        (((100, "kelvin"), (10, "degC")), "error"),
        (((100, "kelvin"), (10, "degF")), "error"),
        (((100, "kelvin"), (10, "degR")), (10, "kelvin/degR")),
        (((100, "kelvin"), (10, "delta_degC")), (10, "kelvin/delta_degC")),
        (((100, "kelvin"), (10, "delta_degF")), (10, "kelvin/delta_degF")),
        (((100, "degC"), (10, "kelvin")), "error"),
        (((100, "degC"), (10, "degC")), "error"),
        (((100, "degC"), (10, "degF")), "error"),
        (((100, "degC"), (10, "degR")), "error"),
        (((100, "degC"), (10, "delta_degC")), "error"),
        (((100, "degC"), (10, "delta_degF")), "error"),
        (((100, "degF"), (10, "kelvin")), "error"),
        (((100, "degF"), (10, "degC")), "error"),
        (((100, "degF"), (10, "degF")), "error"),
        (((100, "degF"), (10, "degR")), "error"),
        (((100, "degF"), (10, "delta_degC")), "error"),
        (((100, "degF"), (10, "delta_degF")), "error"),
        (((100, "degR"), (10, "kelvin")), (10, "degR/kelvin")),
        (((100, "degR"), (10, "degC")), "error"),
        (((100, "degR"), (10, "degF")), "error"),
        (((100, "degR"), (10, "degR")), (10, "")),
        (((100, "degR"), (10, "delta_degC")), (10, "degR/delta_degC")),
        (((100, "degR"), (10, "delta_degF")), (10, "degR/delta_degF")),
        (((100, "delta_degC"), (10, "kelvin")), (10, "delta_degC/kelvin")),
        (((100, "delta_degC"), (10, "degC")), "error"),
        (((100, "delta_degC"), (10, "degF")), "error"),
        (((100, "delta_degC"), (10, "degR")), (10, "delta_degC/degR")),
        (((100, "delta_degC"), (10, "delta_degC")), (10, "")),
        (((100, "delta_degC"), (10, "delta_degF")), (10, "delta_degC/delta_degF")),
        (((100, "delta_degF"), (10, "kelvin")), (10, "delta_degF/kelvin")),
        (((100, "delta_degF"), (10, "degC")), "error"),
        (((100, "delta_degF"), (10, "degF")), "error"),
        (((100, "delta_degF"), (10, "degR")), (10, "delta_degF/degR")),
        (((100, "delta_degF"), (10, "delta_degC")), (10, "delta_degF/delta_degC")),
        (((100, "delta_degF"), (10, "delta_degF")), (10, "")),
    ]

    @ParameterizedTestCase.parameterize(("input", "expected_output"), divisions)
    def test_truedivision(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        qin1, qin2 = input_tuple
        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
        input_tuple = q1, q2
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.truediv, q1, q2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.truediv(q1, q2).units, expected.units)
            self.assertQuantityAlmostEqual(op.truediv(q1, q2), expected, atol=0.01)

    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(("input", "expected_output"), divisions)
    def test_inplace_truedivision(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = False
        (q1v, q1u), (q2v, q2u) = input_tuple
        # update input tuple with new values to have correct values on failure
        input_tuple = (
            (np.array([q1v] * 2, dtype=np.float), q1u),
            (np.array([q2v] * 2, dtype=np.float), q2u),
        )
        Q_ = self.Q_
        qin1, qin2 = input_tuple
        q1, q2 = Q_(*qin1), Q_(*qin2)
        q1_cp = copy.copy(q1)
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.itruediv, q1_cp, q2)
        else:
            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
            self.assertEqual(op.itruediv(q1_cp, q2).units, Q_(*expected).units)
            q1_cp = copy.copy(q1)
            self.assertQuantityAlmostEqual(
                op.itruediv(q1_cp, q2), Q_(*expected), atol=0.01
            )

    multiplications_with_autoconvert_to_baseunit = [
        (((100, "kelvin"), (10, "degC")), (28315.0, "kelvin**2")),
        (((100, "kelvin"), (10, "degF")), (26092.78, "kelvin**2")),
        (((100, "degC"), (10, "kelvin")), (3731.5, "kelvin**2")),
        (((100, "degC"), (10, "degC")), (105657.42, "kelvin**2")),
        (((100, "degC"), (10, "degF")), (97365.20, "kelvin**2")),
        (((100, "degC"), (10, "degR")), (3731.5, "kelvin*degR")),
        (((100, "degC"), (10, "delta_degC")), (3731.5, "kelvin*delta_degC")),
        (((100, "degC"), (10, "delta_degF")), (3731.5, "kelvin*delta_degF")),
        (((100, "degF"), (10, "kelvin")), (3109.28, "kelvin**2")),
        (((100, "degF"), (10, "degC")), (88039.20, "kelvin**2")),
        (((100, "degF"), (10, "degF")), (81129.69, "kelvin**2")),
        (((100, "degF"), (10, "degR")), (3109.28, "kelvin*degR")),
        (((100, "degF"), (10, "delta_degC")), (3109.28, "kelvin*delta_degC")),
        (((100, "degF"), (10, "delta_degF")), (3109.28, "kelvin*delta_degF")),
        (((100, "degR"), (10, "degC")), (28315.0, "degR*kelvin")),
        (((100, "degR"), (10, "degF")), (26092.78, "degR*kelvin")),
        (((100, "delta_degC"), (10, "degC")), (28315.0, "delta_degC*kelvin")),
        (((100, "delta_degC"), (10, "degF")), (26092.78, "delta_degC*kelvin")),
        (((100, "delta_degF"), (10, "degC")), (28315.0, "delta_degF*kelvin")),
        (((100, "delta_degF"), (10, "degF")), (26092.78, "delta_degF*kelvin")),
    ]

    @ParameterizedTestCase.parameterize(
        ("input", "expected_output"), multiplications_with_autoconvert_to_baseunit
    )
    def test_multiplication_with_autoconvert(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = True
        qin1, qin2 = input_tuple
        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
        input_tuple = q1, q2
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.mul, q1, q2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.mul(q1, q2).units, expected.units)
            self.assertQuantityAlmostEqual(op.mul(q1, q2), expected, atol=0.01)

    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(
        ("input", "expected_output"), multiplications_with_autoconvert_to_baseunit
    )
    def test_inplace_multiplication_with_autoconvert(self, input_tuple, expected):
        self.ureg.autoconvert_offset_to_baseunit = True
        (q1v, q1u), (q2v, q2u) = input_tuple
        # update input tuple with new values to have correct values on failure
        input_tuple = (
            (np.array([q1v] * 2, dtype=np.float), q1u),
            (np.array([q2v] * 2, dtype=np.float), q2u),
        )
        Q_ = self.Q_
        qin1, qin2 = input_tuple
        q1, q2 = Q_(*qin1), Q_(*qin2)
        q1_cp = copy.copy(q1)
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.imul, q1_cp, q2)
        else:
            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
            self.assertEqual(op.imul(q1_cp, q2).units, Q_(*expected).units)
            q1_cp = copy.copy(q1)
            self.assertQuantityAlmostEqual(op.imul(q1_cp, q2), Q_(*expected), atol=0.01)

    multiplications_with_scalar = [
        (((10, "kelvin"), 2), (20.0, "kelvin")),
        (((10, "kelvin**2"), 2), (20.0, "kelvin**2")),
        (((10, "degC"), 2), (20.0, "degC")),
        (((10, "1/degC"), 2), "error"),
        (((10, "degC**0.5"), 2), "error"),
        (((10, "degC**2"), 2), "error"),
        (((10, "degC**-2"), 2), "error"),
    ]

    @ParameterizedTestCase.parameterize(
        ("input", "expected_output"), multiplications_with_scalar
    )
    def test_multiplication_with_scalar(self, input_tuple, expected):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple:
            in1, in2 = self.Q_(*in1), in2
        else:
            in1, in2 = in1, self.Q_(*in2)
        input_tuple = in1, in2  # update input_tuple for better tracebacks
        if expected == "error":
            self.assertRaises(OffsetUnitCalculusError, op.mul, in1, in2)
        else:
            expected = self.Q_(*expected)
            self.assertEqual(op.mul(in1, in2).units, expected.units)
            self.assertQuantityAlmostEqual(op.mul(in1, in2), expected, atol=0.01)

    divisions_with_scalar = [  # without / with autoconvert to base unit
        (((10, "kelvin"), 2), [(5.0, "kelvin"), (5.0, "kelvin")]),
        (((10, "kelvin**2"), 2), [(5.0, "kelvin**2"), (5.0, "kelvin**2")]),
        (((10, "degC"), 2), ["error", "error"]),
        (((10, "degC**2"), 2), ["error", "error"]),
        (((10, "degC**-2"), 2), ["error", "error"]),
        ((2, (10, "kelvin")), [(0.2, "1/kelvin"), (0.2, "1/kelvin")]),
        ((2, (10, "degC")), ["error", (2 / 283.15, "1/kelvin")]),
        ((2, (10, "degC**2")), ["error", "error"]),
        ((2, (10, "degC**-2")), ["error", "error"]),
    ]

    @ParameterizedTestCase.parameterize(
        ("input", "expected_output"), divisions_with_scalar
    )
    def test_division_with_scalar(self, input_tuple, expected):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple:
            in1, in2 = self.Q_(*in1), in2
        else:
            in1, in2 = in1, self.Q_(*in2)
        input_tuple = in1, in2  # update input_tuple for better tracebacks
        expected_copy = expected[:]
        for i, mode in enumerate([False, True]):
            self.ureg.autoconvert_offset_to_baseunit = mode
            if expected_copy[i] == "error":
                self.assertRaises(OffsetUnitCalculusError, op.truediv, in1, in2)
            else:
                expected = self.Q_(*expected_copy[i])
                self.assertEqual(op.truediv(in1, in2).units, expected.units)
                self.assertQuantityAlmostEqual(op.truediv(in1, in2), expected)

    exponentiation = [  # resuls without / with autoconvert
        (((10, "degC"), 1), [(10, "degC"), (10, "degC")]),
        (((10, "degC"), 0.5), ["error", (283.15 ** 0.5, "kelvin**0.5")]),
        (((10, "degC"), 0), [(1.0, ""), (1.0, "")]),
        (((10, "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]),
        (((10, "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]),
        (((0, "degC"), -2), ["error", (1 / (273.15) ** 2, "kelvin**-2")]),
        (((10, "degC"), (2, "")), ["error", ((283.15) ** 2, "kelvin**2")]),
        (((10, "degC"), (10, "degK")), ["error", "error"]),
        (((10, "kelvin"), (2, "")), [(100.0, "kelvin**2"), (100.0, "kelvin**2")]),
        ((2, (2, "kelvin")), ["error", "error"]),
        ((2, (500.0, "millikelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
        ((2, (0.5, "kelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
        (
            ((10, "degC"), (500.0, "millikelvin/kelvin")),
            ["error", (283.15 ** 0.5, "kelvin**0.5")],
        ),
    ]

    @ParameterizedTestCase.parameterize(("input", "expected_output"), exponentiation)
    def test_exponentiation(self, input_tuple, expected):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple and type(in2) is tuple:
            in1, in2 = self.Q_(*in1), self.Q_(*in2)
        elif not type(in1) is tuple and type(in2) is tuple:
            in2 = self.Q_(*in2)
        else:
            in1 = self.Q_(*in1)
        input_tuple = in1, in2
        expected_copy = expected[:]
        for i, mode in enumerate([False, True]):
            self.ureg.autoconvert_offset_to_baseunit = mode
            if expected_copy[i] == "error":
                self.assertRaises(
                    (OffsetUnitCalculusError, DimensionalityError), op.pow, in1, in2
                )
            else:
                if type(expected_copy[i]) is tuple:
                    expected = self.Q_(*expected_copy[i])
                    self.assertEqual(op.pow(in1, in2).units, expected.units)
                else:
                    expected = expected_copy[i]
                self.assertQuantityAlmostEqual(op.pow(in1, in2), expected)

    @helpers.requires_numpy()
    @ParameterizedTestCase.parameterize(("input", "expected_output"), exponentiation)
    def test_inplace_exponentiation(self, input_tuple, expected):
        self.ureg.default_as_delta = False
        in1, in2 = input_tuple
        if type(in1) is tuple and type(in2) is tuple:
            (q1v, q1u), (q2v, q2u) = in1, in2
            in1 = self.Q_(*(np.array([q1v] * 2, dtype=np.float), q1u))
            in2 = self.Q_(q2v, q2u)
        elif not type(in1) is tuple and type(in2) is tuple:
            in2 = self.Q_(*in2)
        else:
            in1 = self.Q_(*in1)

        input_tuple = in1, in2

        expected_copy = expected[:]
        for i, mode in enumerate([False, True]):
            self.ureg.autoconvert_offset_to_baseunit = mode
            in1_cp = copy.copy(in1)
            if expected_copy[i] == "error":
                self.assertRaises(
                    (OffsetUnitCalculusError, DimensionalityError), op.ipow, in1_cp, in2
                )
            else:
                if type(expected_copy[i]) is tuple:
                    expected = self.Q_(
                        np.array([expected_copy[i][0]] * 2, dtype=np.float),
                        expected_copy[i][1],
                    )
                    self.assertEqual(op.ipow(in1_cp, in2).units, expected.units)
                else:
                    expected = np.array([expected_copy[i]] * 2, dtype=np.float)

                in1_cp = copy.copy(in1)
                self.assertQuantityAlmostEqual(op.ipow(in1_cp, in2), expected)

    # matmul is only a ufunc since 1.16
    @helpers.requires_numpy_at_least("1.16")
    def test_matmul_with_numpy(self):
        A = [[1, 2], [3, 4]] * self.ureg.m
        B = np.array([[0, -1], [-1, 0]])
        b = [[1], [0]] * self.ureg.m
        self.assertQuantityEqual(A @ B, [[-2, -1], [-4, -3]] * self.ureg.m)
        self.assertQuantityEqual(A @ b, [[1], [3]] * self.ureg.m ** 2)
        self.assertQuantityEqual(B @ b, [[0], [-1]] * self.ureg.m)


class TestDimensionReduction(QuantityTestCase):
    def _calc_mass(self, ureg):
        density = 3 * ureg.g / ureg.L
        volume = 32 * ureg.milliliter
        return density * volume

    def _icalc_mass(self, ureg):
        res = ureg.Quantity(3.0, "gram/liter")
        res *= ureg.Quantity(32.0, "milliliter")
        return res

    def test_mul_and_div_reduction(self):
        ureg = UnitRegistry(auto_reduce_dimensions=True)
        mass = self._calc_mass(ureg)
        self.assertEqual(mass.units, ureg.g)
        ureg = UnitRegistry(auto_reduce_dimensions=False)
        mass = self._calc_mass(ureg)
        self.assertEqual(mass.units, ureg.g / ureg.L * ureg.milliliter)

    @helpers.requires_numpy()
    def test_imul_and_div_reduction(self):
        ureg = UnitRegistry(auto_reduce_dimensions=True, force_ndarray=True)
        mass = self._icalc_mass(ureg)
        self.assertEqual(mass.units, ureg.g)
        ureg = UnitRegistry(auto_reduce_dimensions=False, force_ndarray=True)
        mass = self._icalc_mass(ureg)
        self.assertEqual(mass.units, ureg.g / ureg.L * ureg.milliliter)

    def test_reduction_to_dimensionless(self):
        ureg = UnitRegistry(auto_reduce_dimensions=True)
        x = (10 * ureg.feet) / (3 * ureg.inches)
        self.assertEqual(x.units, UnitsContainer({}))
        ureg = UnitRegistry(auto_reduce_dimensions=False)
        x = (10 * ureg.feet) / (3 * ureg.inches)
        self.assertEqual(x.units, ureg.feet / ureg.inches)

    def test_nocoerce_creation(self):
        ureg = UnitRegistry(auto_reduce_dimensions=True)
        x = 1 * ureg.foot
        self.assertEqual(x.units, ureg.foot)


class TestTimedelta(QuantityTestCase):
    def test_add_sub(self):
        d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24)
        after = d + 3 * self.ureg.second
        self.assertEqual(d + datetime.timedelta(seconds=3), after)
        after = 3 * self.ureg.second + d
        self.assertEqual(d + datetime.timedelta(seconds=3), after)
        after = d - 3 * self.ureg.second
        self.assertEqual(d - datetime.timedelta(seconds=3), after)
        with self.assertRaises(DimensionalityError):
            3 * self.ureg.second - d

    def test_iadd_isub(self):
        d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24)
        after = copy.copy(d)
        after += 3 * self.ureg.second
        self.assertEqual(d + datetime.timedelta(seconds=3), after)
        after = 3 * self.ureg.second
        after += d
        self.assertEqual(d + datetime.timedelta(seconds=3), after)
        after = copy.copy(d)
        after -= 3 * self.ureg.second
        self.assertEqual(d - datetime.timedelta(seconds=3), after)
        after = 3 * self.ureg.second
        with self.assertRaises(DimensionalityError):
            after -= d


class TestCompareZero(QuantityTestCase):
    """This test case checks the special treatment that the zero value
    receives in the comparisons: pint>=0.9 supports comparisons against zero
    even for non-dimensionless quantities

    Parameters
    ----------

    Returns
    -------

    """

    def test_equal_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        self.assertTrue(ureg.Quantity(0, ureg.J) == 0)
        self.assertFalse(ureg.Quantity(0, ureg.J) == ureg.Quantity(0, ""))
        self.assertFalse(ureg.Quantity(5, ureg.J) == 0)

    @helpers.requires_numpy()
    def test_equal_zero_NP(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        aeq = np.testing.assert_array_equal
        aeq(ureg.Quantity(0, ureg.J) == np.zeros(3), np.asarray([True, True, True]))
        aeq(ureg.Quantity(5, ureg.J) == np.zeros(3), np.asarray([False, False, False]))
        aeq(
            ureg.Quantity(np.arange(3), ureg.J) == np.zeros(3),
            np.asarray([True, False, False]),
        )
        self.assertFalse(ureg.Quantity(np.arange(4), ureg.J) == np.zeros(3))

    def test_offset_equal_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        q0 = ureg.Quantity(-273.15, "degC")
        q1 = ureg.Quantity(0, "degC")
        q2 = ureg.Quantity(5, "degC")
        self.assertRaises(OffsetUnitCalculusError, q0.__eq__, 0)
        self.assertRaises(OffsetUnitCalculusError, q1.__eq__, 0)
        self.assertRaises(OffsetUnitCalculusError, q2.__eq__, 0)
        self.assertFalse(q0 == ureg.Quantity(0, ""))

    def test_offset_autoconvert_equal_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = True
        q0 = ureg.Quantity(-273.15, "degC")
        q1 = ureg.Quantity(0, "degC")
        q2 = ureg.Quantity(5, "degC")
        self.assertTrue(q0 == 0)
        self.assertFalse(q1 == 0)
        self.assertFalse(q2 == 0)
        self.assertFalse(q0 == ureg.Quantity(0, ""))

    def test_gt_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        q0 = ureg.Quantity(0, "J")
        q0m = ureg.Quantity(0, "m")
        q0less = ureg.Quantity(0, "")
        qpos = ureg.Quantity(5, "J")
        qneg = ureg.Quantity(-5, "J")
        self.assertTrue(qpos > q0)
        self.assertTrue(qpos > 0)
        self.assertFalse(qneg > 0)
        self.assertRaises(DimensionalityError, qpos.__gt__, q0less)
        self.assertRaises(DimensionalityError, qpos.__gt__, q0m)

    @helpers.requires_numpy()
    def test_gt_zero_NP(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        qpos = ureg.Quantity(5, "J")
        qneg = ureg.Quantity(-5, "J")
        aeq = np.testing.assert_array_equal
        aeq(qpos > np.zeros(3), np.asarray([True, True, True]))
        aeq(qneg > np.zeros(3), np.asarray([False, False, False]))
        aeq(
            ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3),
            np.asarray([False, False, True]),
        )
        aeq(
            ureg.Quantity(np.arange(-1, 2), ureg.J) > np.zeros(3),
            np.asarray([False, False, True]),
        )
        self.assertRaises(
            ValueError, ureg.Quantity(np.arange(-1, 2), ureg.J).__gt__, np.zeros(4)
        )

    def test_offset_gt_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = False
        q0 = ureg.Quantity(-273.15, "degC")
        q1 = ureg.Quantity(0, "degC")
        q2 = ureg.Quantity(5, "degC")
        self.assertRaises(OffsetUnitCalculusError, q0.__gt__, 0)
        self.assertRaises(OffsetUnitCalculusError, q1.__gt__, 0)
        self.assertRaises(OffsetUnitCalculusError, q2.__gt__, 0)
        self.assertRaises(DimensionalityError, q1.__gt__, ureg.Quantity(0, ""))

    def test_offset_autoconvert_gt_zero(self):
        ureg = self.ureg
        ureg.autoconvert_offset_to_baseunit = True
        q0 = ureg.Quantity(-273.15, "degC")
        q1 = ureg.Quantity(0, "degC")
        q2 = ureg.Quantity(5, "degC")
        self.assertFalse(q0 > 0)
        self.assertTrue(q1 > 0)
        self.assertTrue(q2 > 0)
        self.assertRaises(DimensionalityError, q1.__gt__, ureg.Quantity(0, ""))
