1import copy
2import datetime
3import logging
4import math
5import operator as op
6import pickle
7import warnings
8from unittest.mock import patch
9
10import pytest
11
12from pint import (
13    DimensionalityError,
14    OffsetUnitCalculusError,
15    Quantity,
16    UnitRegistry,
17    get_application_registry,
18)
19from pint.compat import np
20from pint.testsuite import QuantityTestCase, helpers
21from pint.unit import UnitsContainer
22
23
24class FakeWrapper:
25    # Used in test_upcast_type_rejection_on_creation
26    def __init__(self, q):
27        self.q = q
28
29
30class TestQuantity(QuantityTestCase):
31
32    kwargs = dict(autoconvert_offset_to_baseunit=False)
33
34    def test_quantity_creation(self, caplog):
35        for args in (
36            (4.2, "meter"),
37            (4.2, UnitsContainer(meter=1)),
38            (4.2, self.ureg.meter),
39            ("4.2*meter",),
40            ("4.2/meter**(-1)",),
41            (self.Q_(4.2, "meter"),),
42        ):
43            x = self.Q_(*args)
44            assert x.magnitude == 4.2
45            assert x.units == UnitsContainer(meter=1)
46
47        x = self.Q_(4.2, UnitsContainer(length=1))
48        y = self.Q_(x)
49        assert x.magnitude == y.magnitude
50        assert x.units == y.units
51        assert x is not y
52
53        x = self.Q_(4.2, None)
54        assert x.magnitude == 4.2
55        assert x.units == UnitsContainer()
56
57        with caplog.at_level(logging.DEBUG):
58            assert 4.2 * self.ureg.meter == self.Q_(4.2, 2 * self.ureg.meter)
59        assert len(caplog.records) == 1
60
61    def test_quantity_with_quantity(self):
62        x = self.Q_(4.2, "m")
63        assert self.Q_(x, "m").magnitude == 4.2
64        assert self.Q_(x, "cm").magnitude == 420.0
65
66    def test_quantity_bool(self):
67        assert self.Q_(1, None)
68        assert self.Q_(1, "meter")
69        assert not self.Q_(0, None)
70        assert not self.Q_(0, "meter")
71        with pytest.raises(ValueError):
72            bool(self.Q_(0, "degC"))
73        assert not self.Q_(0, "delta_degC")
74
75    def test_quantity_comparison(self):
76        x = self.Q_(4.2, "meter")
77        y = self.Q_(4.2, "meter")
78        z = self.Q_(5, "meter")
79        j = self.Q_(5, "meter*meter")
80
81        # Include a comparison to the application registry
82        k = 5 * get_application_registry().meter
83        m = Quantity(5, "meter")  # Include a comparison to a directly created Quantity
84
85        # identity for single object
86        assert x == x
87        assert not (x != x)
88
89        # identity for multiple objects with same value
90        assert x == y
91        assert not (x != y)
92
93        assert x <= y
94        assert x >= y
95        assert not (x < y)
96        assert not (x > y)
97
98        assert not (x == z)
99        assert x != z
100        assert x < z
101
102        # Compare with items to the separate application registry
103        assert k >= m  # These should both be from application registry
104        with pytest.raises(ValueError):
105            z > m  # One from local registry, one from application registry
106
107        assert z != j
108
109        assert z != j
110        assert self.Q_(0, "meter") == self.Q_(0, "centimeter")
111        assert self.Q_(0, "meter") != self.Q_(0, "second")
112
113        assert self.Q_(10, "meter") < self.Q_(5, "kilometer")
114
115    def test_quantity_comparison_convert(self):
116        assert self.Q_(1000, "millimeter") == self.Q_(1, "meter")
117        assert self.Q_(1000, "millimeter/min") == self.Q_(1000 / 60, "millimeter/s")
118
119    def test_quantity_repr(self):
120        x = self.Q_(4.2, UnitsContainer(meter=1))
121        assert str(x) == "4.2 meter"
122        assert repr(x) == "<Quantity(4.2, 'meter')>"
123
124    def test_quantity_hash(self):
125        x = self.Q_(4.2, "meter")
126        x2 = self.Q_(4200, "millimeter")
127        y = self.Q_(2, "second")
128        z = self.Q_(0.5, "hertz")
129        assert hash(x) == hash(x2)
130
131        # Dimensionless equality
132        assert hash(y * z) == hash(1.0)
133
134        # Dimensionless equality from a different unit registry
135        ureg2 = UnitRegistry(**self.kwargs)
136        y2 = ureg2.Quantity(2, "second")
137        z2 = ureg2.Quantity(0.5, "hertz")
138        assert hash(y * z) == hash(y2 * z2)
139
140    def test_quantity_format(self, subtests):
141        x = self.Q_(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1))
142        for spec, result in (
143            ("{}", str(x)),
144            ("{!s}", str(x)),
145            ("{!r}", repr(x)),
146            ("{.magnitude}", str(x.magnitude)),
147            ("{.units}", str(x.units)),
148            ("{.magnitude!s}", str(x.magnitude)),
149            ("{.units!s}", str(x.units)),
150            ("{.magnitude!r}", repr(x.magnitude)),
151            ("{.units!r}", repr(x.units)),
152            ("{:.4f}", f"{x.magnitude:.4f} {x.units!s}"),
153            (
154                "{:L}",
155                r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
156            ),
157            ("{:P}", "4.12345678 kilogram·meter²/second"),
158            ("{:H}", "4.12345678 kilogram meter<sup>2</sup>/second"),
159            ("{:C}", "4.12345678 kilogram*meter**2/second"),
160            ("{:~}", "4.12345678 kg * m ** 2 / s"),
161            (
162                "{:L~}",
163                r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}",
164            ),
165            ("{:P~}", "4.12345678 kg·m²/s"),
166            ("{:H~}", "4.12345678 kg m<sup>2</sup>/s"),
167            ("{:C~}", "4.12345678 kg*m**2/s"),
168            ("{:Lx}", r"\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}"),
169        ):
170            with subtests.test(spec):
171                assert spec.format(x) == result
172
173        # Check the special case that prevents e.g. '3 1 / second'
174        x = self.Q_(3, UnitsContainer(second=-1))
175        assert f"{x}" == "3 / second"
176
177    @helpers.requires_numpy
178    def test_quantity_array_format(self, subtests):
179        x = self.Q_(
180            np.array([1e-16, 1.0000001, 10000000.0, 1e12, np.nan, np.inf]),
181            "kg * m ** 2",
182        )
183        for spec, result in (
184            ("{}", str(x)),
185            ("{.magnitude}", str(x.magnitude)),
186            (
187                "{:e}",
188                "[1.000000e-16 1.000000e+00 1.000000e+07 1.000000e+12 nan inf] kilogram * meter ** 2",
189            ),
190            (
191                "{:E}",
192                "[1.000000E-16 1.000000E+00 1.000000E+07 1.000000E+12 NAN INF] kilogram * meter ** 2",
193            ),
194            (
195                "{:.2f}",
196                "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kilogram * meter ** 2",
197            ),
198            ("{:.2f~P}", "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kg·m²"),
199            ("{:g~P}", "[1e-16 1 1e+07 1e+12 nan inf] kg·m²"),
200            (
201                "{:.2f~H}",
202                (
203                    "<table><tbody><tr><th>Magnitude</th><td style='text-align:left;'>"
204                    "<pre>[0.00 1.00 10000000.00 1000000000000.00 nan inf]</pre></td></tr>"
205                    "<tr><th>Units</th><td style='text-align:left;'>kg m<sup>2</sup></td></tr>"
206                    "</tbody></table>"
207                ),
208            ),
209        ):
210            with subtests.test(spec):
211                assert spec.format(x) == result
212
213    @helpers.requires_numpy
214    def test_quantity_array_scalar_format(self, subtests):
215        x = self.Q_(np.array(4.12345678), "kg * m ** 2")
216        for spec, result in (
217            ("{:.2f}", "4.12 kilogram * meter ** 2"),
218            ("{:.2fH}", "4.12 kilogram meter<sup>2</sup>"),
219        ):
220            with subtests.test(spec):
221                assert spec.format(x) == result
222
223    def test_format_compact(self):
224        q1 = (200e-9 * self.ureg.s).to_compact()
225        q1b = self.Q_(200.0, "nanosecond")
226        assert round(abs(q1.magnitude - q1b.magnitude), 7) == 0
227        assert q1.units == q1b.units
228
229        q2 = (1e-2 * self.ureg("kg m/s^2")).to_compact("N")
230        q2b = self.Q_(10.0, "millinewton")
231        assert q2.magnitude == q2b.magnitude
232        assert q2.units == q2b.units
233
234        q3 = (-1000.0 * self.ureg("meters")).to_compact()
235        q3b = self.Q_(-1.0, "kilometer")
236        assert q3.magnitude == q3b.magnitude
237        assert q3.units == q3b.units
238
239        assert f"{q1:#.1f}" == f"{q1b}"
240        assert f"{q2:#.1f}" == f"{q2b}"
241        assert f"{q3:#.1f}" == f"{q3b}"
242
243    def test_default_formatting(self, subtests):
244        ureg = UnitRegistry()
245        x = ureg.Quantity(4.12345678, UnitsContainer(meter=2, kilogram=1, second=-1))
246        for spec, result in (
247            (
248                "L",
249                r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
250            ),
251            ("P", "4.12345678 kilogram·meter²/second"),
252            ("H", "4.12345678 kilogram meter<sup>2</sup>/second"),
253            ("C", "4.12345678 kilogram*meter**2/second"),
254            ("~", "4.12345678 kg * m ** 2 / s"),
255            ("L~", r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"),
256            ("P~", "4.12345678 kg·m²/s"),
257            ("H~", "4.12345678 kg m<sup>2</sup>/s"),
258            ("C~", "4.12345678 kg*m**2/s"),
259        ):
260            with subtests.test(spec):
261                ureg.default_format = spec
262                assert f"{x}" == result
263
264    def test_exponent_formatting(self):
265        ureg = UnitRegistry()
266        x = ureg.Quantity(1e20, "meter")
267        assert f"{x:~H}" == r"1×10<sup>20</sup> m"
268        assert f"{x:~L}" == r"1\times 10^{20}\ \mathrm{m}"
269        assert f"{x:~P}" == r"1×10²⁰ m"
270
271        x /= 1e40
272        assert f"{x:~H}" == r"1×10<sup>-20</sup> m"
273        assert f"{x:~L}" == r"1\times 10^{-20}\ \mathrm{m}"
274        assert f"{x:~P}" == r"1×10⁻²⁰ m"
275
276    def test_ipython(self):
277        alltext = []
278
279        class Pretty:
280            @staticmethod
281            def text(text):
282                alltext.append(text)
283
284            @classmethod
285            def pretty(cls, data):
286                try:
287                    data._repr_pretty_(cls, False)
288                except AttributeError:
289                    alltext.append(str(data))
290
291        ureg = UnitRegistry()
292        x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1))
293        assert x._repr_html_() == "3.5 kilogram meter<sup>2</sup>/second"
294        assert (
295            x._repr_latex_() == r"$3.5\ \frac{\mathrm{kilogram} \cdot "
296            r"\mathrm{meter}^{2}}{\mathrm{second}}$"
297        )
298        x._repr_pretty_(Pretty, False)
299        assert "".join(alltext) == "3.5 kilogram·meter²/second"
300        ureg.default_format = "~"
301        assert x._repr_html_() == "3.5 kg m<sup>2</sup>/s"
302        assert (
303            x._repr_latex_() == r"$3.5\ \frac{\mathrm{kg} \cdot "
304            r"\mathrm{m}^{2}}{\mathrm{s}}$"
305        )
306        alltext = []
307        x._repr_pretty_(Pretty, False)
308        assert "".join(alltext) == "3.5 kg·m²/s"
309
310    def test_to_base_units(self):
311        x = self.Q_("1*inch")
312        helpers.assert_quantity_almost_equal(
313            x.to_base_units(), self.Q_(0.0254, "meter")
314        )
315        x = self.Q_("1*inch*inch")
316        helpers.assert_quantity_almost_equal(
317            x.to_base_units(), self.Q_(0.0254 ** 2.0, "meter*meter")
318        )
319        x = self.Q_("1*inch/minute")
320        helpers.assert_quantity_almost_equal(
321            x.to_base_units(), self.Q_(0.0254 / 60.0, "meter/second")
322        )
323
324    def test_convert(self):
325        helpers.assert_quantity_almost_equal(
326            self.Q_("2 inch").to("meter"), self.Q_(2.0 * 0.0254, "meter")
327        )
328        helpers.assert_quantity_almost_equal(
329            self.Q_("2 meter").to("inch"), self.Q_(2.0 / 0.0254, "inch")
330        )
331        helpers.assert_quantity_almost_equal(
332            self.Q_("2 sidereal_year").to("second"), self.Q_(63116297.5325, "second")
333        )
334        helpers.assert_quantity_almost_equal(
335            self.Q_("2.54 centimeter/second").to("inch/second"),
336            self.Q_("1 inch/second"),
337        )
338        assert round(abs(self.Q_("2.54 centimeter").to("inch").magnitude - 1), 7) == 0
339        assert (
340            round(abs(self.Q_("2 second").to("millisecond").magnitude - 2000), 7) == 0
341        )
342
343    @helpers.requires_numpy
344    def test_convert_numpy(self):
345
346        # Conversions with single units take a different codepath than
347        # Conversions with more than one unit.
348        src_dst1 = UnitsContainer(meter=1), UnitsContainer(inch=1)
349        src_dst2 = UnitsContainer(meter=1, second=-1), UnitsContainer(inch=1, minute=-1)
350        for src, dst in (src_dst1, src_dst2):
351            a = np.ones((3, 1))
352            ac = np.ones((3, 1))
353
354            q = self.Q_(a, src)
355            qac = self.Q_(ac, src).to(dst)
356            r = q.to(dst)
357            helpers.assert_quantity_almost_equal(qac, r)
358            assert r is not q
359            assert r._magnitude is not a
360
361    def test_convert_from(self):
362        x = self.Q_("2*inch")
363        meter = self.ureg.meter
364
365        # from quantity
366        helpers.assert_quantity_almost_equal(
367            meter.from_(x), self.Q_(2.0 * 0.0254, "meter")
368        )
369        helpers.assert_quantity_almost_equal(meter.m_from(x), 2.0 * 0.0254)
370
371        # from unit
372        helpers.assert_quantity_almost_equal(
373            meter.from_(self.ureg.inch), self.Q_(0.0254, "meter")
374        )
375        helpers.assert_quantity_almost_equal(meter.m_from(self.ureg.inch), 0.0254)
376
377        # from number
378        helpers.assert_quantity_almost_equal(
379            meter.from_(2, strict=False), self.Q_(2.0, "meter")
380        )
381        helpers.assert_quantity_almost_equal(meter.m_from(2, strict=False), 2.0)
382
383        # from number (strict mode)
384        with pytest.raises(ValueError):
385            meter.from_(2)
386        with pytest.raises(ValueError):
387            meter.m_from(2)
388
389    @helpers.requires_numpy
390    def test_retain_unit(self):
391        # Test that methods correctly retain units and do not degrade into
392        # ordinary ndarrays.  List contained in __copy_units.
393        a = np.ones((3, 2))
394        q = self.Q_(a, "km")
395        assert q.u == q.reshape(2, 3).u
396        assert q.u == q.swapaxes(0, 1).u
397        assert q.u == q.mean().u
398        assert q.u == np.compress((q == q[0, 0]).any(0), q).u
399
400    def test_context_attr(self):
401        assert self.ureg.meter == self.Q_(1, "meter")
402
403    def test_both_symbol(self):
404        assert self.Q_(2, "ms") == self.Q_(2, "millisecond")
405        assert self.Q_(2, "cm") == self.Q_(2, "centimeter")
406
407    def test_dimensionless_units(self):
408        assert (
409            round(abs(self.Q_(360, "degree").to("radian").magnitude - 2 * math.pi), 7)
410            == 0
411        )
412        assert (
413            round(abs(self.Q_(2 * math.pi, "radian") - self.Q_(360, "degree")), 7) == 0
414        )
415        assert self.Q_(1, "radian").dimensionality == UnitsContainer()
416        assert self.Q_(1, "radian").dimensionless
417        assert not self.Q_(1, "radian").unitless
418
419        assert self.Q_(1, "meter") / self.Q_(1, "meter") == 1
420        assert (self.Q_(1, "meter") / self.Q_(1, "mm")).to("") == 1000
421
422        assert self.Q_(10) // self.Q_(360, "degree") == 1
423        assert self.Q_(400, "degree") // self.Q_(2 * math.pi) == 1
424        assert self.Q_(400, "degree") // (2 * math.pi) == 1
425        assert 7 // self.Q_(360, "degree") == 1
426
427    def test_offset(self):
428        helpers.assert_quantity_almost_equal(
429            self.Q_(0, "kelvin").to("kelvin"), self.Q_(0, "kelvin")
430        )
431        helpers.assert_quantity_almost_equal(
432            self.Q_(0, "degC").to("kelvin"), self.Q_(273.15, "kelvin")
433        )
434        helpers.assert_quantity_almost_equal(
435            self.Q_(0, "degF").to("kelvin"), self.Q_(255.372222, "kelvin"), rtol=0.01
436        )
437
438        helpers.assert_quantity_almost_equal(
439            self.Q_(100, "kelvin").to("kelvin"), self.Q_(100, "kelvin")
440        )
441        helpers.assert_quantity_almost_equal(
442            self.Q_(100, "degC").to("kelvin"), self.Q_(373.15, "kelvin")
443        )
444        helpers.assert_quantity_almost_equal(
445            self.Q_(100, "degF").to("kelvin"),
446            self.Q_(310.92777777, "kelvin"),
447            rtol=0.01,
448        )
449
450        helpers.assert_quantity_almost_equal(
451            self.Q_(0, "kelvin").to("degC"), self.Q_(-273.15, "degC")
452        )
453        helpers.assert_quantity_almost_equal(
454            self.Q_(100, "kelvin").to("degC"), self.Q_(-173.15, "degC")
455        )
456        helpers.assert_quantity_almost_equal(
457            self.Q_(0, "kelvin").to("degF"), self.Q_(-459.67, "degF"), rtol=0.01
458        )
459        helpers.assert_quantity_almost_equal(
460            self.Q_(100, "kelvin").to("degF"), self.Q_(-279.67, "degF"), rtol=0.01
461        )
462
463        helpers.assert_quantity_almost_equal(
464            self.Q_(32, "degF").to("degC"), self.Q_(0, "degC"), atol=0.01
465        )
466        helpers.assert_quantity_almost_equal(
467            self.Q_(100, "degC").to("degF"), self.Q_(212, "degF"), atol=0.01
468        )
469
470        helpers.assert_quantity_almost_equal(
471            self.Q_(54, "degF").to("degC"), self.Q_(12.2222, "degC"), atol=0.01
472        )
473        helpers.assert_quantity_almost_equal(
474            self.Q_(12, "degC").to("degF"), self.Q_(53.6, "degF"), atol=0.01
475        )
476
477        helpers.assert_quantity_almost_equal(
478            self.Q_(12, "kelvin").to("degC"), self.Q_(-261.15, "degC"), atol=0.01
479        )
480        helpers.assert_quantity_almost_equal(
481            self.Q_(12, "degC").to("kelvin"), self.Q_(285.15, "kelvin"), atol=0.01
482        )
483
484        helpers.assert_quantity_almost_equal(
485            self.Q_(12, "kelvin").to("degR"), self.Q_(21.6, "degR"), atol=0.01
486        )
487        helpers.assert_quantity_almost_equal(
488            self.Q_(12, "degR").to("kelvin"), self.Q_(6.66666667, "kelvin"), atol=0.01
489        )
490
491        helpers.assert_quantity_almost_equal(
492            self.Q_(12, "degC").to("degR"), self.Q_(513.27, "degR"), atol=0.01
493        )
494        helpers.assert_quantity_almost_equal(
495            self.Q_(12, "degR").to("degC"), self.Q_(-266.483333, "degC"), atol=0.01
496        )
497
498    def test_offset_delta(self):
499        helpers.assert_quantity_almost_equal(
500            self.Q_(0, "delta_degC").to("kelvin"), self.Q_(0, "kelvin")
501        )
502        helpers.assert_quantity_almost_equal(
503            self.Q_(0, "delta_degF").to("kelvin"), self.Q_(0, "kelvin"), rtol=0.01
504        )
505
506        helpers.assert_quantity_almost_equal(
507            self.Q_(100, "kelvin").to("delta_degC"), self.Q_(100, "delta_degC")
508        )
509        helpers.assert_quantity_almost_equal(
510            self.Q_(100, "kelvin").to("delta_degF"),
511            self.Q_(180, "delta_degF"),
512            rtol=0.01,
513        )
514        helpers.assert_quantity_almost_equal(
515            self.Q_(100, "delta_degF").to("kelvin"),
516            self.Q_(55.55555556, "kelvin"),
517            rtol=0.01,
518        )
519        helpers.assert_quantity_almost_equal(
520            self.Q_(100, "delta_degC").to("delta_degF"),
521            self.Q_(180, "delta_degF"),
522            rtol=0.01,
523        )
524        helpers.assert_quantity_almost_equal(
525            self.Q_(100, "delta_degF").to("delta_degC"),
526            self.Q_(55.55555556, "delta_degC"),
527            rtol=0.01,
528        )
529
530        helpers.assert_quantity_almost_equal(
531            self.Q_(12.3, "delta_degC").to("delta_degF"),
532            self.Q_(22.14, "delta_degF"),
533            rtol=0.01,
534        )
535
536    def test_pickle(self, subtests):
537        for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
538            for magnitude, unit in ((32, ""), (2.4, ""), (32, "m/s"), (2.4, "m/s")):
539                with subtests.test(protocol=protocol, magnitude=magnitude, unit=unit):
540                    q1 = self.Q_(magnitude, unit)
541                    q2 = pickle.loads(pickle.dumps(q1, protocol))
542                    assert q1 == q2
543
544    @helpers.requires_numpy
545    def test_from_sequence(self):
546        u_array_ref = self.Q_([200, 1000], "g")
547        u_array_ref_reversed = self.Q_([1000, 200], "g")
548        u_seq = [self.Q_("200g"), self.Q_("1kg")]
549        u_seq_reversed = u_seq[::-1]
550
551        u_array = self.Q_.from_sequence(u_seq)
552        assert all(u_array == u_array_ref)
553
554        u_array_2 = self.Q_.from_sequence(u_seq_reversed)
555        assert all(u_array_2 == u_array_ref_reversed)
556        assert not (u_array_2.u == u_array_ref_reversed.u)
557
558        u_array_3 = self.Q_.from_sequence(u_seq_reversed, units="g")
559        assert all(u_array_3 == u_array_ref_reversed)
560        assert u_array_3.u == u_array_ref_reversed.u
561
562        with pytest.raises(ValueError):
563            self.Q_.from_sequence([])
564
565        u_array_5 = self.Q_.from_list(u_seq)
566        assert all(u_array_5 == u_array_ref)
567
568    @helpers.requires_numpy
569    def test_iter(self):
570        # Verify that iteration gives element as Quantity with same units
571        x = self.Q_([0, 1, 2, 3], "m")
572        helpers.assert_quantity_equal(next(iter(x)), self.Q_(0, "m"))
573
574    def test_notiter(self):
575        # Verify that iter() crashes immediately, without needing to draw any
576        # element from it, if the magnitude isn't iterable
577        x = self.Q_(1, "m")
578        with pytest.raises(TypeError):
579            iter(x)
580
581    @helpers.requires_array_function_protocol()
582    def test_no_longer_array_function_warning_on_creation(self):
583        # Test that warning is no longer raised on first creation
584        with warnings.catch_warnings():
585            warnings.filterwarnings("error")
586            self.Q_([])
587
588    @helpers.requires_not_numpy()
589    def test_no_ndarray_coercion_without_numpy(self):
590        with pytest.raises(ValueError):
591            self.Q_(1, "m").__array__()
592
593    @patch("pint.compat.upcast_types", [FakeWrapper])
594    def test_upcast_type_rejection_on_creation(self):
595        with pytest.raises(TypeError):
596            self.Q_(FakeWrapper(42), "m")
597        assert FakeWrapper(self.Q_(42, "m")).q == self.Q_(42, "m")
598
599
600class TestQuantityToCompact(QuantityTestCase):
601    def assertQuantityAlmostIdentical(self, q1, q2):
602        assert q1.units == q2.units
603        assert round(abs(q1.magnitude - q2.magnitude), 7) == 0
604
605    def compare_quantity_compact(self, q, expected_compact, unit=None):
606        helpers.assert_quantity_almost_equal(q.to_compact(unit=unit), expected_compact)
607
608    def test_dimensionally_simple_units(self):
609        ureg = self.ureg
610        self.compare_quantity_compact(1 * ureg.m, 1 * ureg.m)
611        self.compare_quantity_compact(1e-9 * ureg.m, 1 * ureg.nm)
612
613    def test_power_units(self):
614        ureg = self.ureg
615        self.compare_quantity_compact(900 * ureg.m ** 2, 900 * ureg.m ** 2)
616        self.compare_quantity_compact(1e7 * ureg.m ** 2, 10 * ureg.km ** 2)
617
618    def test_inverse_units(self):
619        ureg = self.ureg
620        self.compare_quantity_compact(1 / ureg.m, 1 / ureg.m)
621        self.compare_quantity_compact(100e9 / ureg.m, 100 / ureg.nm)
622
623    def test_inverse_square_units(self):
624        ureg = self.ureg
625        self.compare_quantity_compact(1 / ureg.m ** 2, 1 / ureg.m ** 2)
626        self.compare_quantity_compact(1e11 / ureg.m ** 2, 1e5 / ureg.mm ** 2)
627
628    def test_fractional_units(self):
629        ureg = self.ureg
630        # Typing denominator first to provoke potential error
631        self.compare_quantity_compact(20e3 * ureg("hr^(-1) m"), 20 * ureg.km / ureg.hr)
632
633    def test_fractional_exponent_units(self):
634        ureg = self.ureg
635        self.compare_quantity_compact(1 * ureg.m ** 0.5, 1 * ureg.m ** 0.5)
636        self.compare_quantity_compact(1e-2 * ureg.m ** 0.5, 10 * ureg.um ** 0.5)
637
638    def test_derived_units(self):
639        ureg = self.ureg
640        self.compare_quantity_compact(0.5 * ureg.megabyte, 500 * ureg.kilobyte)
641        self.compare_quantity_compact(1e-11 * ureg.N, 10 * ureg.pN)
642
643    def test_unit_parameter(self):
644        ureg = self.ureg
645        self.compare_quantity_compact(
646            self.Q_(100e-9, "kg m / s^2"), 100 * ureg.nN, ureg.N
647        )
648        self.compare_quantity_compact(
649            self.Q_(101.3e3, "kg/m/s^2"), 101.3 * ureg.kPa, ureg.Pa
650        )
651
652    def test_limits_magnitudes(self):
653        ureg = self.ureg
654        self.compare_quantity_compact(0 * ureg.m, 0 * ureg.m)
655        self.compare_quantity_compact(float("inf") * ureg.m, float("inf") * ureg.m)
656
657    def test_nonnumeric_magnitudes(self):
658        ureg = self.ureg
659        x = "some string" * ureg.m
660        with pytest.warns(RuntimeWarning):
661            self.compare_quantity_compact(x, x)
662
663    def test_very_large_to_compact(self):
664        # This should not raise an IndexError
665        self.compare_quantity_compact(
666            self.Q_(10000, "yottameter"), self.Q_(10 ** 28, "meter").to_compact()
667        )
668
669
670class TestQuantityBasicMath(QuantityTestCase):
671    def _test_inplace(self, operator, value1, value2, expected_result, unit=None):
672        if isinstance(value1, str):
673            value1 = self.Q_(value1)
674        if isinstance(value2, str):
675            value2 = self.Q_(value2)
676        if isinstance(expected_result, str):
677            expected_result = self.Q_(expected_result)
678
679        if unit is not None:
680            value1 = value1 * unit
681            value2 = value2 * unit
682            expected_result = expected_result * unit
683
684        value1 = copy.copy(value1)
685        value2 = copy.copy(value2)
686        id1 = id(value1)
687        id2 = id(value2)
688        value1 = operator(value1, value2)
689        value2_cpy = copy.copy(value2)
690        helpers.assert_quantity_almost_equal(value1, expected_result)
691        assert id1 == id(value1)
692        helpers.assert_quantity_almost_equal(value2, value2_cpy)
693        assert id2 == id(value2)
694
695    def _test_not_inplace(self, operator, value1, value2, expected_result, unit=None):
696        if isinstance(value1, str):
697            value1 = self.Q_(value1)
698        if isinstance(value2, str):
699            value2 = self.Q_(value2)
700        if isinstance(expected_result, str):
701            expected_result = self.Q_(expected_result)
702
703        if unit is not None:
704            value1 = value1 * unit
705            value2 = value2 * unit
706            expected_result = expected_result * unit
707
708        id1 = id(value1)
709        id2 = id(value2)
710
711        value1_cpy = copy.copy(value1)
712        value2_cpy = copy.copy(value2)
713
714        result = operator(value1, value2)
715
716        helpers.assert_quantity_almost_equal(expected_result, result)
717        helpers.assert_quantity_almost_equal(value1, value1_cpy)
718        helpers.assert_quantity_almost_equal(value2, value2_cpy)
719        assert id(result) != id1
720        assert id(result) != id2
721
722    def _test_quantity_add_sub(self, unit, func):
723        x = self.Q_(unit, "centimeter")
724        y = self.Q_(unit, "inch")
725        z = self.Q_(unit, "second")
726        a = self.Q_(unit, None)
727
728        func(op.add, x, x, self.Q_(unit + unit, "centimeter"))
729        func(op.add, x, y, self.Q_(unit + 2.54 * unit, "centimeter"))
730        func(op.add, y, x, self.Q_(unit + unit / (2.54 * unit), "inch"))
731        func(op.add, a, unit, self.Q_(unit + unit, None))
732        with pytest.raises(DimensionalityError):
733            op.add(10, x)
734        with pytest.raises(DimensionalityError):
735            op.add(x, 10)
736        with pytest.raises(DimensionalityError):
737            op.add(x, z)
738
739        func(op.sub, x, x, self.Q_(unit - unit, "centimeter"))
740        func(op.sub, x, y, self.Q_(unit - 2.54 * unit, "centimeter"))
741        func(op.sub, y, x, self.Q_(unit - unit / (2.54 * unit), "inch"))
742        func(op.sub, a, unit, self.Q_(unit - unit, None))
743        with pytest.raises(DimensionalityError):
744            op.sub(10, x)
745        with pytest.raises(DimensionalityError):
746            op.sub(x, 10)
747        with pytest.raises(DimensionalityError):
748            op.sub(x, z)
749
750    def _test_quantity_iadd_isub(self, unit, func):
751        x = self.Q_(unit, "centimeter")
752        y = self.Q_(unit, "inch")
753        z = self.Q_(unit, "second")
754        a = self.Q_(unit, None)
755
756        func(op.iadd, x, x, self.Q_(unit + unit, "centimeter"))
757        func(op.iadd, x, y, self.Q_(unit + 2.54 * unit, "centimeter"))
758        func(op.iadd, y, x, self.Q_(unit + unit / 2.54, "inch"))
759        func(op.iadd, a, unit, self.Q_(unit + unit, None))
760        with pytest.raises(DimensionalityError):
761            op.iadd(10, x)
762        with pytest.raises(DimensionalityError):
763            op.iadd(x, 10)
764        with pytest.raises(DimensionalityError):
765            op.iadd(x, z)
766
767        func(op.isub, x, x, self.Q_(unit - unit, "centimeter"))
768        func(op.isub, x, y, self.Q_(unit - 2.54, "centimeter"))
769        func(op.isub, y, x, self.Q_(unit - unit / 2.54, "inch"))
770        func(op.isub, a, unit, self.Q_(unit - unit, None))
771        with pytest.raises(DimensionalityError):
772            op.sub(10, x)
773        with pytest.raises(DimensionalityError):
774            op.sub(x, 10)
775        with pytest.raises(DimensionalityError):
776            op.sub(x, z)
777
778    def _test_quantity_mul_div(self, unit, func):
779        func(op.mul, unit * 10.0, "4.2*meter", "42*meter", unit)
780        func(op.mul, "4.2*meter", unit * 10.0, "42*meter", unit)
781        func(op.mul, "4.2*meter", "10*inch", "42*meter*inch", unit)
782        func(op.truediv, unit * 42, "4.2*meter", "10/meter", unit)
783        func(op.truediv, "4.2*meter", unit * 10.0, "0.42*meter", unit)
784        func(op.truediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
785
786    def _test_quantity_imul_idiv(self, unit, func):
787        # func(op.imul, 10.0, '4.2*meter', '42*meter')
788        func(op.imul, "4.2*meter", 10.0, "42*meter", unit)
789        func(op.imul, "4.2*meter", "10*inch", "42*meter*inch", unit)
790        # func(op.truediv, 42, '4.2*meter', '10/meter')
791        func(op.itruediv, "4.2*meter", unit * 10.0, "0.42*meter", unit)
792        func(op.itruediv, "4.2*meter", "10*inch", "0.42*meter/inch", unit)
793
794    def _test_quantity_floordiv(self, unit, func):
795        a = self.Q_("10*meter")
796        b = self.Q_("3*second")
797        with pytest.raises(DimensionalityError):
798            op.floordiv(a, b)
799        with pytest.raises(DimensionalityError):
800            op.floordiv(3, b)
801        with pytest.raises(DimensionalityError):
802            op.floordiv(a, 3)
803        with pytest.raises(DimensionalityError):
804            op.ifloordiv(a, b)
805        with pytest.raises(DimensionalityError):
806            op.ifloordiv(3, b)
807        with pytest.raises(DimensionalityError):
808            op.ifloordiv(a, 3)
809        func(op.floordiv, unit * 10.0, "4.2*meter/meter", 2, unit)
810        func(op.floordiv, "10*meter", "4.2*inch", 93, unit)
811
812    def _test_quantity_mod(self, unit, func):
813        a = self.Q_("10*meter")
814        b = self.Q_("3*second")
815        with pytest.raises(DimensionalityError):
816            op.mod(a, b)
817        with pytest.raises(DimensionalityError):
818            op.mod(3, b)
819        with pytest.raises(DimensionalityError):
820            op.mod(a, 3)
821        with pytest.raises(DimensionalityError):
822            op.imod(a, b)
823        with pytest.raises(DimensionalityError):
824            op.imod(3, b)
825        with pytest.raises(DimensionalityError):
826            op.imod(a, 3)
827        func(op.mod, unit * 10.0, "4.2*meter/meter", 1.6, unit)
828
829    def _test_quantity_ifloordiv(self, unit, func):
830        func(op.ifloordiv, 10.0, "4.2*meter/meter", 2, unit)
831        func(op.ifloordiv, "10*meter", "4.2*inch", 93, unit)
832
833    def _test_quantity_divmod_one(self, a, b):
834        if isinstance(a, str):
835            a = self.Q_(a)
836        if isinstance(b, str):
837            b = self.Q_(b)
838
839        q, r = divmod(a, b)
840        assert q == a // b
841        assert r == a % b
842        assert a == (q * b) + r
843        assert q == math.floor(q)
844        if b > (0 * b):
845            assert (0 * b) <= r < b
846        else:
847            assert (0 * b) >= r > b
848        if isinstance(a, self.Q_):
849            assert r.units == a.units
850        else:
851            assert r.unitless
852        assert q.unitless
853
854        copy_a = copy.copy(a)
855        a %= b
856        assert a == r
857        copy_a //= b
858        assert copy_a == q
859
860    def _test_quantity_divmod(self):
861        self._test_quantity_divmod_one("10*meter", "4.2*inch")
862        self._test_quantity_divmod_one("-10*meter", "4.2*inch")
863        self._test_quantity_divmod_one("-10*meter", "-4.2*inch")
864        self._test_quantity_divmod_one("10*meter", "-4.2*inch")
865
866        self._test_quantity_divmod_one("400*degree", "3")
867        self._test_quantity_divmod_one("4", "180 degree")
868        self._test_quantity_divmod_one(4, "180 degree")
869        self._test_quantity_divmod_one("20", 4)
870        self._test_quantity_divmod_one("300*degree", "100 degree")
871
872        a = self.Q_("10*meter")
873        b = self.Q_("3*second")
874        with pytest.raises(DimensionalityError):
875            divmod(a, b)
876        with pytest.raises(DimensionalityError):
877            divmod(3, b)
878        with pytest.raises(DimensionalityError):
879            divmod(a, 3)
880
881    def _test_numeric(self, unit, ifunc):
882        self._test_quantity_add_sub(unit, self._test_not_inplace)
883        self._test_quantity_iadd_isub(unit, ifunc)
884        self._test_quantity_mul_div(unit, self._test_not_inplace)
885        self._test_quantity_imul_idiv(unit, ifunc)
886        self._test_quantity_floordiv(unit, self._test_not_inplace)
887        self._test_quantity_mod(unit, self._test_not_inplace)
888        self._test_quantity_divmod()
889        # self._test_quantity_ifloordiv(unit, ifunc)
890
891    def test_float(self):
892        self._test_numeric(1.0, self._test_not_inplace)
893
894    def test_fraction(self):
895        import fractions
896
897        self._test_numeric(fractions.Fraction(1, 1), self._test_not_inplace)
898
899    @helpers.requires_numpy
900    def test_nparray(self):
901        self._test_numeric(np.ones((1, 3)), self._test_inplace)
902
903    def test_quantity_abs_round(self):
904
905        x = self.Q_(-4.2, "meter")
906        y = self.Q_(4.2, "meter")
907
908        for fun in (abs, round, op.pos, op.neg):
909            zx = self.Q_(fun(x.magnitude), "meter")
910            zy = self.Q_(fun(y.magnitude), "meter")
911            rx = fun(x)
912            ry = fun(y)
913            assert rx == zx, "while testing {0}".format(fun)
914            assert ry == zy, "while testing {0}".format(fun)
915            assert rx is not zx, "while testing {0}".format(fun)
916            assert ry is not zy, "while testing {0}".format(fun)
917
918    def test_quantity_float_complex(self):
919        x = self.Q_(-4.2, None)
920        y = self.Q_(4.2, None)
921        z = self.Q_(1, "meter")
922        for fun in (float, complex):
923            assert fun(x) == fun(x.magnitude)
924            assert fun(y) == fun(y.magnitude)
925            with pytest.raises(DimensionalityError):
926                fun(z)
927
928
929class TestQuantityNeutralAdd(QuantityTestCase):
930    """Addition to zero or NaN is allowed between a Quantity and a non-Quantity"""
931
932    def test_bare_zero(self):
933        v = self.Q_(2.0, "m")
934        assert v + 0 == v
935        assert v - 0 == v
936        assert 0 + v == v
937        assert 0 - v == -v
938
939    def test_bare_zero_inplace(self):
940        v = self.Q_(2.0, "m")
941        v2 = self.Q_(2.0, "m")
942        v2 += 0
943        assert v2 == v
944        v2 = self.Q_(2.0, "m")
945        v2 -= 0
946        assert v2 == v
947        v2 = 0
948        v2 += v
949        assert v2 == v
950        v2 = 0
951        v2 -= v
952        assert v2 == -v
953
954    def test_bare_nan(self):
955        v = self.Q_(2.0, "m")
956        helpers.assert_quantity_equal(v + math.nan, self.Q_(math.nan, v.units))
957        helpers.assert_quantity_equal(v - math.nan, self.Q_(math.nan, v.units))
958        helpers.assert_quantity_equal(math.nan + v, self.Q_(math.nan, v.units))
959        helpers.assert_quantity_equal(math.nan - v, self.Q_(math.nan, v.units))
960
961    def test_bare_nan_inplace(self):
962        v = self.Q_(2.0, "m")
963        v2 = self.Q_(2.0, "m")
964        v2 += math.nan
965        helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units))
966        v2 = self.Q_(2.0, "m")
967        v2 -= math.nan
968        helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units))
969        v2 = math.nan
970        v2 += v
971        helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units))
972        v2 = math.nan
973        v2 -= v
974        helpers.assert_quantity_equal(v2, self.Q_(math.nan, v.units))
975
976    @helpers.requires_numpy
977    def test_bare_zero_or_nan_numpy(self):
978        z = np.array([0.0, np.nan])
979        v = self.Q_([1.0, 2.0], "m")
980        e = self.Q_([1.0, np.nan], "m")
981        helpers.assert_quantity_equal(z + v, e)
982        helpers.assert_quantity_equal(z - v, -e)
983        helpers.assert_quantity_equal(v + z, e)
984        helpers.assert_quantity_equal(v - z, e)
985
986        # If any element is non-zero and non-NaN, raise DimensionalityError
987        nz = np.array([0.0, 1.0])
988        with pytest.raises(DimensionalityError):
989            nz + v
990        with pytest.raises(DimensionalityError):
991            nz - v
992        with pytest.raises(DimensionalityError):
993            v + nz
994        with pytest.raises(DimensionalityError):
995            v - nz
996
997        # Mismatched shape
998        z = np.array([0.0, np.nan, 0.0])
999        v = self.Q_([1.0, 2.0], "m")
1000        for x, y in ((z, v), (v, z)):
1001            with pytest.raises(ValueError):
1002                x + y
1003            with pytest.raises(ValueError):
1004                x - y
1005
1006    @helpers.requires_numpy
1007    def test_bare_zero_or_nan_numpy_inplace(self):
1008        z = np.array([0.0, np.nan])
1009        v = self.Q_([1.0, 2.0], "m")
1010        e = self.Q_([1.0, np.nan], "m")
1011        v += z
1012        helpers.assert_quantity_equal(v, e)
1013        v = self.Q_([1.0, 2.0], "m")
1014        v -= z
1015        helpers.assert_quantity_equal(v, e)
1016        v = self.Q_([1.0, 2.0], "m")
1017        z = np.array([0.0, np.nan])
1018        z += v
1019        helpers.assert_quantity_equal(z, e)
1020        v = self.Q_([1.0, 2.0], "m")
1021        z = np.array([0.0, np.nan])
1022        z -= v
1023        helpers.assert_quantity_equal(z, -e)
1024
1025
1026class TestDimensions(QuantityTestCase):
1027    def test_get_dimensionality(self):
1028        get = self.ureg.get_dimensionality
1029        assert get("[time]") == UnitsContainer({"[time]": 1})
1030        assert get(UnitsContainer({"[time]": 1})) == UnitsContainer({"[time]": 1})
1031        assert get("seconds") == UnitsContainer({"[time]": 1})
1032        assert get(UnitsContainer({"seconds": 1})) == UnitsContainer({"[time]": 1})
1033        assert get("[speed]") == UnitsContainer({"[length]": 1, "[time]": -1})
1034        assert get("[acceleration]") == UnitsContainer({"[length]": 1, "[time]": -2})
1035
1036    def test_dimensionality(self):
1037        x = self.Q_(42, "centimeter")
1038        x.to_base_units()
1039        x = self.Q_(42, "meter*second")
1040        assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 1.0})
1041        x = self.Q_(42, "meter*second*second")
1042        assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 2.0})
1043        x = self.Q_(42, "inch*second*second")
1044        assert x.dimensionality == UnitsContainer({"[length]": 1.0, "[time]": 2.0})
1045        assert self.Q_(42, None).dimensionless
1046        assert not self.Q_(42, "meter").dimensionless
1047        assert (self.Q_(42, "meter") / self.Q_(1, "meter")).dimensionless
1048        assert not (self.Q_(42, "meter") / self.Q_(1, "second")).dimensionless
1049        assert (self.Q_(42, "meter") / self.Q_(1, "inch")).dimensionless
1050
1051    def test_inclusion(self):
1052        dim = self.Q_(42, "meter").dimensionality
1053        assert "[length]" in dim
1054        assert not ("[time]" in dim)
1055        dim = (self.Q_(42, "meter") / self.Q_(11, "second")).dimensionality
1056        assert "[length]" in dim
1057        assert "[time]" in dim
1058        dim = self.Q_(20.785, "J/(mol)").dimensionality
1059        for dimension in ("[length]", "[mass]", "[substance]", "[time]"):
1060            assert dimension in dim
1061        assert not ("[angle]" in dim)
1062
1063
1064class TestQuantityWithDefaultRegistry(TestDimensions):
1065    @classmethod
1066    def setup_class(cls):
1067        from pint import _DEFAULT_REGISTRY
1068
1069        cls.ureg = _DEFAULT_REGISTRY
1070        cls.Q_ = cls.ureg.Quantity
1071
1072
1073class TestDimensionsWithDefaultRegistry(TestDimensions):
1074    @classmethod
1075    def setup_class(cls):
1076        from pint import _DEFAULT_REGISTRY
1077
1078        cls.ureg = _DEFAULT_REGISTRY
1079        cls.Q_ = cls.ureg.Quantity
1080
1081
1082class TestOffsetUnitMath(QuantityTestCase):
1083    @classmethod
1084    def setup_class(cls):
1085        super().setup_class()
1086        cls.ureg.autoconvert_offset_to_baseunit = False
1087        cls.ureg.default_as_delta = True
1088
1089    additions = [
1090        # --- input tuple -------------------- | -- expected result --
1091        (((100, "kelvin"), (10, "kelvin")), (110, "kelvin")),
1092        (((100, "kelvin"), (10, "degC")), "error"),
1093        (((100, "kelvin"), (10, "degF")), "error"),
1094        (((100, "kelvin"), (10, "degR")), (105.56, "kelvin")),
1095        (((100, "kelvin"), (10, "delta_degC")), (110, "kelvin")),
1096        (((100, "kelvin"), (10, "delta_degF")), (105.56, "kelvin")),
1097        (((100, "degC"), (10, "kelvin")), "error"),
1098        (((100, "degC"), (10, "degC")), "error"),
1099        (((100, "degC"), (10, "degF")), "error"),
1100        (((100, "degC"), (10, "degR")), "error"),
1101        (((100, "degC"), (10, "delta_degC")), (110, "degC")),
1102        (((100, "degC"), (10, "delta_degF")), (105.56, "degC")),
1103        (((100, "degF"), (10, "kelvin")), "error"),
1104        (((100, "degF"), (10, "degC")), "error"),
1105        (((100, "degF"), (10, "degF")), "error"),
1106        (((100, "degF"), (10, "degR")), "error"),
1107        (((100, "degF"), (10, "delta_degC")), (118, "degF")),
1108        (((100, "degF"), (10, "delta_degF")), (110, "degF")),
1109        (((100, "degR"), (10, "kelvin")), (118, "degR")),
1110        (((100, "degR"), (10, "degC")), "error"),
1111        (((100, "degR"), (10, "degF")), "error"),
1112        (((100, "degR"), (10, "degR")), (110, "degR")),
1113        (((100, "degR"), (10, "delta_degC")), (118, "degR")),
1114        (((100, "degR"), (10, "delta_degF")), (110, "degR")),
1115        (((100, "delta_degC"), (10, "kelvin")), (110, "kelvin")),
1116        (((100, "delta_degC"), (10, "degC")), (110, "degC")),
1117        (((100, "delta_degC"), (10, "degF")), (190, "degF")),
1118        (((100, "delta_degC"), (10, "degR")), (190, "degR")),
1119        (((100, "delta_degC"), (10, "delta_degC")), (110, "delta_degC")),
1120        (((100, "delta_degC"), (10, "delta_degF")), (105.56, "delta_degC")),
1121        (((100, "delta_degF"), (10, "kelvin")), (65.56, "kelvin")),
1122        (((100, "delta_degF"), (10, "degC")), (65.56, "degC")),
1123        (((100, "delta_degF"), (10, "degF")), (110, "degF")),
1124        (((100, "delta_degF"), (10, "degR")), (110, "degR")),
1125        (((100, "delta_degF"), (10, "delta_degC")), (118, "delta_degF")),
1126        (((100, "delta_degF"), (10, "delta_degF")), (110, "delta_degF")),
1127    ]
1128
1129    @pytest.mark.parametrize(("input_tuple", "expected"), additions)
1130    def test_addition(self, input_tuple, expected):
1131        self.ureg.autoconvert_offset_to_baseunit = False
1132        qin1, qin2 = input_tuple
1133        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
1134        # update input tuple with new values to have correct values on failure
1135        input_tuple = q1, q2
1136        if expected == "error":
1137            with pytest.raises(OffsetUnitCalculusError):
1138                op.add(q1, q2)
1139        else:
1140            expected = self.Q_(*expected)
1141            assert op.add(q1, q2).units == expected.units
1142            helpers.assert_quantity_almost_equal(op.add(q1, q2), expected, atol=0.01)
1143
1144    @helpers.requires_numpy
1145    @pytest.mark.parametrize(("input_tuple", "expected"), additions)
1146    def test_inplace_addition(self, input_tuple, expected):
1147        self.ureg.autoconvert_offset_to_baseunit = False
1148        (q1v, q1u), (q2v, q2u) = input_tuple
1149        # update input tuple with new values to have correct values on failure
1150        input_tuple = (
1151            (np.array([q1v] * 2, dtype=np.float), q1u),
1152            (np.array([q2v] * 2, dtype=np.float), q2u),
1153        )
1154        Q_ = self.Q_
1155        qin1, qin2 = input_tuple
1156        q1, q2 = Q_(*qin1), Q_(*qin2)
1157        q1_cp = copy.copy(q1)
1158        if expected == "error":
1159            with pytest.raises(OffsetUnitCalculusError):
1160                op.iadd(q1_cp, q2)
1161        else:
1162            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
1163            assert op.iadd(q1_cp, q2).units == Q_(*expected).units
1164            q1_cp = copy.copy(q1)
1165            helpers.assert_quantity_almost_equal(
1166                op.iadd(q1_cp, q2), Q_(*expected), atol=0.01
1167            )
1168
1169    subtractions = [
1170        (((100, "kelvin"), (10, "kelvin")), (90, "kelvin")),
1171        (((100, "kelvin"), (10, "degC")), (-183.15, "kelvin")),
1172        (((100, "kelvin"), (10, "degF")), (-160.93, "kelvin")),
1173        (((100, "kelvin"), (10, "degR")), (94.44, "kelvin")),
1174        (((100, "kelvin"), (10, "delta_degC")), (90, "kelvin")),
1175        (((100, "kelvin"), (10, "delta_degF")), (94.44, "kelvin")),
1176        (((100, "degC"), (10, "kelvin")), (363.15, "delta_degC")),
1177        (((100, "degC"), (10, "degC")), (90, "delta_degC")),
1178        (((100, "degC"), (10, "degF")), (112.22, "delta_degC")),
1179        (((100, "degC"), (10, "degR")), (367.59, "delta_degC")),
1180        (((100, "degC"), (10, "delta_degC")), (90, "degC")),
1181        (((100, "degC"), (10, "delta_degF")), (94.44, "degC")),
1182        (((100, "degF"), (10, "kelvin")), (541.67, "delta_degF")),
1183        (((100, "degF"), (10, "degC")), (50, "delta_degF")),
1184        (((100, "degF"), (10, "degF")), (90, "delta_degF")),
1185        (((100, "degF"), (10, "degR")), (549.67, "delta_degF")),
1186        (((100, "degF"), (10, "delta_degC")), (82, "degF")),
1187        (((100, "degF"), (10, "delta_degF")), (90, "degF")),
1188        (((100, "degR"), (10, "kelvin")), (82, "degR")),
1189        (((100, "degR"), (10, "degC")), (-409.67, "degR")),
1190        (((100, "degR"), (10, "degF")), (-369.67, "degR")),
1191        (((100, "degR"), (10, "degR")), (90, "degR")),
1192        (((100, "degR"), (10, "delta_degC")), (82, "degR")),
1193        (((100, "degR"), (10, "delta_degF")), (90, "degR")),
1194        (((100, "delta_degC"), (10, "kelvin")), (90, "kelvin")),
1195        (((100, "delta_degC"), (10, "degC")), (90, "degC")),
1196        (((100, "delta_degC"), (10, "degF")), (170, "degF")),
1197        (((100, "delta_degC"), (10, "degR")), (170, "degR")),
1198        (((100, "delta_degC"), (10, "delta_degC")), (90, "delta_degC")),
1199        (((100, "delta_degC"), (10, "delta_degF")), (94.44, "delta_degC")),
1200        (((100, "delta_degF"), (10, "kelvin")), (45.56, "kelvin")),
1201        (((100, "delta_degF"), (10, "degC")), (45.56, "degC")),
1202        (((100, "delta_degF"), (10, "degF")), (90, "degF")),
1203        (((100, "delta_degF"), (10, "degR")), (90, "degR")),
1204        (((100, "delta_degF"), (10, "delta_degC")), (82, "delta_degF")),
1205        (((100, "delta_degF"), (10, "delta_degF")), (90, "delta_degF")),
1206    ]
1207
1208    @pytest.mark.parametrize(("input_tuple", "expected"), subtractions)
1209    def test_subtraction(self, input_tuple, expected):
1210        self.ureg.autoconvert_offset_to_baseunit = False
1211        qin1, qin2 = input_tuple
1212        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
1213        input_tuple = q1, q2
1214        if expected == "error":
1215            with pytest.raises(OffsetUnitCalculusError):
1216                op.sub(q1, q2)
1217        else:
1218            expected = self.Q_(*expected)
1219            assert op.sub(q1, q2).units == expected.units
1220            helpers.assert_quantity_almost_equal(op.sub(q1, q2), expected, atol=0.01)
1221
1222    #    @pytest.mark.xfail
1223    @helpers.requires_numpy
1224    @pytest.mark.parametrize(("input_tuple", "expected"), subtractions)
1225    def test_inplace_subtraction(self, input_tuple, expected):
1226        self.ureg.autoconvert_offset_to_baseunit = False
1227        (q1v, q1u), (q2v, q2u) = input_tuple
1228        # update input tuple with new values to have correct values on failure
1229        input_tuple = (
1230            (np.array([q1v] * 2, dtype=np.float), q1u),
1231            (np.array([q2v] * 2, dtype=np.float), q2u),
1232        )
1233        Q_ = self.Q_
1234        qin1, qin2 = input_tuple
1235        q1, q2 = Q_(*qin1), Q_(*qin2)
1236        q1_cp = copy.copy(q1)
1237        if expected == "error":
1238            with pytest.raises(OffsetUnitCalculusError):
1239                op.isub(q1_cp, q2)
1240        else:
1241            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
1242            assert op.isub(q1_cp, q2).units == Q_(*expected).units
1243            q1_cp = copy.copy(q1)
1244            helpers.assert_quantity_almost_equal(
1245                op.isub(q1_cp, q2), Q_(*expected), atol=0.01
1246            )
1247
1248    multiplications = [
1249        (((100, "kelvin"), (10, "kelvin")), (1000, "kelvin**2")),
1250        (((100, "kelvin"), (10, "degC")), "error"),
1251        (((100, "kelvin"), (10, "degF")), "error"),
1252        (((100, "kelvin"), (10, "degR")), (1000, "kelvin*degR")),
1253        (((100, "kelvin"), (10, "delta_degC")), (1000, "kelvin*delta_degC")),
1254        (((100, "kelvin"), (10, "delta_degF")), (1000, "kelvin*delta_degF")),
1255        (((100, "degC"), (10, "kelvin")), "error"),
1256        (((100, "degC"), (10, "degC")), "error"),
1257        (((100, "degC"), (10, "degF")), "error"),
1258        (((100, "degC"), (10, "degR")), "error"),
1259        (((100, "degC"), (10, "delta_degC")), "error"),
1260        (((100, "degC"), (10, "delta_degF")), "error"),
1261        (((100, "degF"), (10, "kelvin")), "error"),
1262        (((100, "degF"), (10, "degC")), "error"),
1263        (((100, "degF"), (10, "degF")), "error"),
1264        (((100, "degF"), (10, "degR")), "error"),
1265        (((100, "degF"), (10, "delta_degC")), "error"),
1266        (((100, "degF"), (10, "delta_degF")), "error"),
1267        (((100, "degR"), (10, "kelvin")), (1000, "degR*kelvin")),
1268        (((100, "degR"), (10, "degC")), "error"),
1269        (((100, "degR"), (10, "degF")), "error"),
1270        (((100, "degR"), (10, "degR")), (1000, "degR**2")),
1271        (((100, "degR"), (10, "delta_degC")), (1000, "degR*delta_degC")),
1272        (((100, "degR"), (10, "delta_degF")), (1000, "degR*delta_degF")),
1273        (((100, "delta_degC"), (10, "kelvin")), (1000, "delta_degC*kelvin")),
1274        (((100, "delta_degC"), (10, "degC")), "error"),
1275        (((100, "delta_degC"), (10, "degF")), "error"),
1276        (((100, "delta_degC"), (10, "degR")), (1000, "delta_degC*degR")),
1277        (((100, "delta_degC"), (10, "delta_degC")), (1000, "delta_degC**2")),
1278        (((100, "delta_degC"), (10, "delta_degF")), (1000, "delta_degC*delta_degF")),
1279        (((100, "delta_degF"), (10, "kelvin")), (1000, "delta_degF*kelvin")),
1280        (((100, "delta_degF"), (10, "degC")), "error"),
1281        (((100, "delta_degF"), (10, "degF")), "error"),
1282        (((100, "delta_degF"), (10, "degR")), (1000, "delta_degF*degR")),
1283        (((100, "delta_degF"), (10, "delta_degC")), (1000, "delta_degF*delta_degC")),
1284        (((100, "delta_degF"), (10, "delta_degF")), (1000, "delta_degF**2")),
1285    ]
1286
1287    @pytest.mark.parametrize(("input_tuple", "expected"), multiplications)
1288    def test_multiplication(self, input_tuple, expected):
1289        self.ureg.autoconvert_offset_to_baseunit = False
1290        qin1, qin2 = input_tuple
1291        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
1292        input_tuple = q1, q2
1293        if expected == "error":
1294            with pytest.raises(OffsetUnitCalculusError):
1295                op.mul(q1, q2)
1296        else:
1297            expected = self.Q_(*expected)
1298            assert op.mul(q1, q2).units == expected.units
1299            helpers.assert_quantity_almost_equal(op.mul(q1, q2), expected, atol=0.01)
1300
1301    @helpers.requires_numpy
1302    @pytest.mark.parametrize(("input_tuple", "expected"), multiplications)
1303    def test_inplace_multiplication(self, input_tuple, expected):
1304        self.ureg.autoconvert_offset_to_baseunit = False
1305        (q1v, q1u), (q2v, q2u) = input_tuple
1306        # update input tuple with new values to have correct values on failure
1307        input_tuple = (
1308            (np.array([q1v] * 2, dtype=np.float), q1u),
1309            (np.array([q2v] * 2, dtype=np.float), q2u),
1310        )
1311        Q_ = self.Q_
1312        qin1, qin2 = input_tuple
1313        q1, q2 = Q_(*qin1), Q_(*qin2)
1314        q1_cp = copy.copy(q1)
1315        if expected == "error":
1316            with pytest.raises(OffsetUnitCalculusError):
1317                op.imul(q1_cp, q2)
1318        else:
1319            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
1320            assert op.imul(q1_cp, q2).units == Q_(*expected).units
1321            q1_cp = copy.copy(q1)
1322            helpers.assert_quantity_almost_equal(
1323                op.imul(q1_cp, q2), Q_(*expected), atol=0.01
1324            )
1325
1326    divisions = [
1327        (((100, "kelvin"), (10, "kelvin")), (10, "")),
1328        (((100, "kelvin"), (10, "degC")), "error"),
1329        (((100, "kelvin"), (10, "degF")), "error"),
1330        (((100, "kelvin"), (10, "degR")), (10, "kelvin/degR")),
1331        (((100, "kelvin"), (10, "delta_degC")), (10, "kelvin/delta_degC")),
1332        (((100, "kelvin"), (10, "delta_degF")), (10, "kelvin/delta_degF")),
1333        (((100, "degC"), (10, "kelvin")), "error"),
1334        (((100, "degC"), (10, "degC")), "error"),
1335        (((100, "degC"), (10, "degF")), "error"),
1336        (((100, "degC"), (10, "degR")), "error"),
1337        (((100, "degC"), (10, "delta_degC")), "error"),
1338        (((100, "degC"), (10, "delta_degF")), "error"),
1339        (((100, "degF"), (10, "kelvin")), "error"),
1340        (((100, "degF"), (10, "degC")), "error"),
1341        (((100, "degF"), (10, "degF")), "error"),
1342        (((100, "degF"), (10, "degR")), "error"),
1343        (((100, "degF"), (10, "delta_degC")), "error"),
1344        (((100, "degF"), (10, "delta_degF")), "error"),
1345        (((100, "degR"), (10, "kelvin")), (10, "degR/kelvin")),
1346        (((100, "degR"), (10, "degC")), "error"),
1347        (((100, "degR"), (10, "degF")), "error"),
1348        (((100, "degR"), (10, "degR")), (10, "")),
1349        (((100, "degR"), (10, "delta_degC")), (10, "degR/delta_degC")),
1350        (((100, "degR"), (10, "delta_degF")), (10, "degR/delta_degF")),
1351        (((100, "delta_degC"), (10, "kelvin")), (10, "delta_degC/kelvin")),
1352        (((100, "delta_degC"), (10, "degC")), "error"),
1353        (((100, "delta_degC"), (10, "degF")), "error"),
1354        (((100, "delta_degC"), (10, "degR")), (10, "delta_degC/degR")),
1355        (((100, "delta_degC"), (10, "delta_degC")), (10, "")),
1356        (((100, "delta_degC"), (10, "delta_degF")), (10, "delta_degC/delta_degF")),
1357        (((100, "delta_degF"), (10, "kelvin")), (10, "delta_degF/kelvin")),
1358        (((100, "delta_degF"), (10, "degC")), "error"),
1359        (((100, "delta_degF"), (10, "degF")), "error"),
1360        (((100, "delta_degF"), (10, "degR")), (10, "delta_degF/degR")),
1361        (((100, "delta_degF"), (10, "delta_degC")), (10, "delta_degF/delta_degC")),
1362        (((100, "delta_degF"), (10, "delta_degF")), (10, "")),
1363    ]
1364
1365    @pytest.mark.parametrize(("input_tuple", "expected"), divisions)
1366    def test_truedivision(self, input_tuple, expected):
1367        self.ureg.autoconvert_offset_to_baseunit = False
1368        qin1, qin2 = input_tuple
1369        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
1370        input_tuple = q1, q2
1371        if expected == "error":
1372            with pytest.raises(OffsetUnitCalculusError):
1373                op.truediv(q1, q2)
1374        else:
1375            expected = self.Q_(*expected)
1376            assert op.truediv(q1, q2).units == expected.units
1377            helpers.assert_quantity_almost_equal(
1378                op.truediv(q1, q2), expected, atol=0.01
1379            )
1380
1381    @helpers.requires_numpy
1382    @pytest.mark.parametrize(("input_tuple", "expected"), divisions)
1383    def test_inplace_truedivision(self, input_tuple, expected):
1384        self.ureg.autoconvert_offset_to_baseunit = False
1385        (q1v, q1u), (q2v, q2u) = input_tuple
1386        # update input tuple with new values to have correct values on failure
1387        input_tuple = (
1388            (np.array([q1v] * 2, dtype=np.float), q1u),
1389            (np.array([q2v] * 2, dtype=np.float), q2u),
1390        )
1391        Q_ = self.Q_
1392        qin1, qin2 = input_tuple
1393        q1, q2 = Q_(*qin1), Q_(*qin2)
1394        q1_cp = copy.copy(q1)
1395        if expected == "error":
1396            with pytest.raises(OffsetUnitCalculusError):
1397                op.itruediv(q1_cp, q2)
1398        else:
1399            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
1400            assert op.itruediv(q1_cp, q2).units == Q_(*expected).units
1401            q1_cp = copy.copy(q1)
1402            helpers.assert_quantity_almost_equal(
1403                op.itruediv(q1_cp, q2), Q_(*expected), atol=0.01
1404            )
1405
1406    multiplications_with_autoconvert_to_baseunit = [
1407        (((100, "kelvin"), (10, "degC")), (28315.0, "kelvin**2")),
1408        (((100, "kelvin"), (10, "degF")), (26092.78, "kelvin**2")),
1409        (((100, "degC"), (10, "kelvin")), (3731.5, "kelvin**2")),
1410        (((100, "degC"), (10, "degC")), (105657.42, "kelvin**2")),
1411        (((100, "degC"), (10, "degF")), (97365.20, "kelvin**2")),
1412        (((100, "degC"), (10, "degR")), (3731.5, "kelvin*degR")),
1413        (((100, "degC"), (10, "delta_degC")), (3731.5, "kelvin*delta_degC")),
1414        (((100, "degC"), (10, "delta_degF")), (3731.5, "kelvin*delta_degF")),
1415        (((100, "degF"), (10, "kelvin")), (3109.28, "kelvin**2")),
1416        (((100, "degF"), (10, "degC")), (88039.20, "kelvin**2")),
1417        (((100, "degF"), (10, "degF")), (81129.69, "kelvin**2")),
1418        (((100, "degF"), (10, "degR")), (3109.28, "kelvin*degR")),
1419        (((100, "degF"), (10, "delta_degC")), (3109.28, "kelvin*delta_degC")),
1420        (((100, "degF"), (10, "delta_degF")), (3109.28, "kelvin*delta_degF")),
1421        (((100, "degR"), (10, "degC")), (28315.0, "degR*kelvin")),
1422        (((100, "degR"), (10, "degF")), (26092.78, "degR*kelvin")),
1423        (((100, "delta_degC"), (10, "degC")), (28315.0, "delta_degC*kelvin")),
1424        (((100, "delta_degC"), (10, "degF")), (26092.78, "delta_degC*kelvin")),
1425        (((100, "delta_degF"), (10, "degC")), (28315.0, "delta_degF*kelvin")),
1426        (((100, "delta_degF"), (10, "degF")), (26092.78, "delta_degF*kelvin")),
1427    ]
1428
1429    @pytest.mark.parametrize(
1430        ("input_tuple", "expected"), multiplications_with_autoconvert_to_baseunit
1431    )
1432    def test_multiplication_with_autoconvert(self, input_tuple, expected):
1433        self.ureg.autoconvert_offset_to_baseunit = True
1434        qin1, qin2 = input_tuple
1435        q1, q2 = self.Q_(*qin1), self.Q_(*qin2)
1436        input_tuple = q1, q2
1437        if expected == "error":
1438            with pytest.raises(OffsetUnitCalculusError):
1439                op.mul(q1, q2)
1440        else:
1441            expected = self.Q_(*expected)
1442            assert op.mul(q1, q2).units == expected.units
1443            helpers.assert_quantity_almost_equal(op.mul(q1, q2), expected, atol=0.01)
1444
1445    @helpers.requires_numpy
1446    @pytest.mark.parametrize(
1447        ("input_tuple", "expected"), multiplications_with_autoconvert_to_baseunit
1448    )
1449    def test_inplace_multiplication_with_autoconvert(self, input_tuple, expected):
1450        self.ureg.autoconvert_offset_to_baseunit = True
1451        (q1v, q1u), (q2v, q2u) = input_tuple
1452        # update input tuple with new values to have correct values on failure
1453        input_tuple = (
1454            (np.array([q1v] * 2, dtype=np.float), q1u),
1455            (np.array([q2v] * 2, dtype=np.float), q2u),
1456        )
1457        Q_ = self.Q_
1458        qin1, qin2 = input_tuple
1459        q1, q2 = Q_(*qin1), Q_(*qin2)
1460        q1_cp = copy.copy(q1)
1461        if expected == "error":
1462            with pytest.raises(OffsetUnitCalculusError):
1463                op.imul(q1_cp, q2)
1464        else:
1465            expected = np.array([expected[0]] * 2, dtype=np.float), expected[1]
1466            assert op.imul(q1_cp, q2).units == Q_(*expected).units
1467            q1_cp = copy.copy(q1)
1468            helpers.assert_quantity_almost_equal(
1469                op.imul(q1_cp, q2), Q_(*expected), atol=0.01
1470            )
1471
1472    multiplications_with_scalar = [
1473        (((10, "kelvin"), 2), (20.0, "kelvin")),
1474        (((10, "kelvin**2"), 2), (20.0, "kelvin**2")),
1475        (((10, "degC"), 2), (20.0, "degC")),
1476        (((10, "1/degC"), 2), "error"),
1477        (((10, "degC**0.5"), 2), "error"),
1478        (((10, "degC**2"), 2), "error"),
1479        (((10, "degC**-2"), 2), "error"),
1480    ]
1481
1482    @pytest.mark.parametrize(("input_tuple", "expected"), multiplications_with_scalar)
1483    def test_multiplication_with_scalar(self, input_tuple, expected):
1484        self.ureg.default_as_delta = False
1485        in1, in2 = input_tuple
1486        if type(in1) is tuple:
1487            in1, in2 = self.Q_(*in1), in2
1488        else:
1489            in1, in2 = in1, self.Q_(*in2)
1490        input_tuple = in1, in2  # update input_tuple for better tracebacks
1491        if expected == "error":
1492            with pytest.raises(OffsetUnitCalculusError):
1493                op.mul(in1, in2)
1494        else:
1495            expected = self.Q_(*expected)
1496            assert op.mul(in1, in2).units == expected.units
1497            helpers.assert_quantity_almost_equal(op.mul(in1, in2), expected, atol=0.01)
1498
1499    divisions_with_scalar = [  # without / with autoconvert to base unit
1500        (((10, "kelvin"), 2), [(5.0, "kelvin"), (5.0, "kelvin")]),
1501        (((10, "kelvin**2"), 2), [(5.0, "kelvin**2"), (5.0, "kelvin**2")]),
1502        (((10, "degC"), 2), ["error", "error"]),
1503        (((10, "degC**2"), 2), ["error", "error"]),
1504        (((10, "degC**-2"), 2), ["error", "error"]),
1505        ((2, (10, "kelvin")), [(0.2, "1/kelvin"), (0.2, "1/kelvin")]),
1506        ((2, (10, "degC")), ["error", (2 / 283.15, "1/kelvin")]),
1507        ((2, (10, "degC**2")), ["error", "error"]),
1508        ((2, (10, "degC**-2")), ["error", "error"]),
1509    ]
1510
1511    @pytest.mark.parametrize(("input_tuple", "expected"), divisions_with_scalar)
1512    def test_division_with_scalar(self, input_tuple, expected):
1513        self.ureg.default_as_delta = False
1514        in1, in2 = input_tuple
1515        if type(in1) is tuple:
1516            in1, in2 = self.Q_(*in1), in2
1517        else:
1518            in1, in2 = in1, self.Q_(*in2)
1519        input_tuple = in1, in2  # update input_tuple for better tracebacks
1520        expected_copy = expected[:]
1521        for i, mode in enumerate([False, True]):
1522            self.ureg.autoconvert_offset_to_baseunit = mode
1523            if expected_copy[i] == "error":
1524                with pytest.raises(OffsetUnitCalculusError):
1525                    op.truediv(in1, in2)
1526            else:
1527                expected = self.Q_(*expected_copy[i])
1528                assert op.truediv(in1, in2).units == expected.units
1529                helpers.assert_quantity_almost_equal(op.truediv(in1, in2), expected)
1530
1531    exponentiation = [  # results without / with autoconvert
1532        (((10, "degC"), 1), [(10, "degC"), (10, "degC")]),
1533        (((10, "degC"), 0.5), ["error", (283.15 ** 0.5, "kelvin**0.5")]),
1534        (((10, "degC"), 0), [(1.0, ""), (1.0, "")]),
1535        (((10, "degC"), -1), ["error", (1 / (10 + 273.15), "kelvin**-1")]),
1536        (((10, "degC"), -2), ["error", (1 / (10 + 273.15) ** 2.0, "kelvin**-2")]),
1537        (((0, "degC"), -2), ["error", (1 / 273.15 ** 2, "kelvin**-2")]),
1538        (((10, "degC"), (2, "")), ["error", (283.15 ** 2, "kelvin**2")]),
1539        (((10, "degC"), (10, "degK")), ["error", "error"]),
1540        (((10, "kelvin"), (2, "")), [(100.0, "kelvin**2"), (100.0, "kelvin**2")]),
1541        ((2, (2, "kelvin")), ["error", "error"]),
1542        ((2, (500.0, "millikelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
1543        ((2, (0.5, "kelvin/kelvin")), [2 ** 0.5, 2 ** 0.5]),
1544        (
1545            ((10, "degC"), (500.0, "millikelvin/kelvin")),
1546            ["error", (283.15 ** 0.5, "kelvin**0.5")],
1547        ),
1548    ]
1549
1550    @pytest.mark.parametrize(("input_tuple", "expected"), exponentiation)
1551    def test_exponentiation(self, input_tuple, expected):
1552        self.ureg.default_as_delta = False
1553        in1, in2 = input_tuple
1554        if type(in1) is tuple and type(in2) is tuple:
1555            in1, in2 = self.Q_(*in1), self.Q_(*in2)
1556        elif not type(in1) is tuple and type(in2) is tuple:
1557            in2 = self.Q_(*in2)
1558        else:
1559            in1 = self.Q_(*in1)
1560        input_tuple = in1, in2
1561        expected_copy = expected[:]
1562        for i, mode in enumerate([False, True]):
1563            self.ureg.autoconvert_offset_to_baseunit = mode
1564            if expected_copy[i] == "error":
1565                with pytest.raises((OffsetUnitCalculusError, DimensionalityError)):
1566                    op.pow(in1, in2)
1567            else:
1568                if type(expected_copy[i]) is tuple:
1569                    expected = self.Q_(*expected_copy[i])
1570                    assert op.pow(in1, in2).units == expected.units
1571                else:
1572                    expected = expected_copy[i]
1573                helpers.assert_quantity_almost_equal(op.pow(in1, in2), expected)
1574
1575    @helpers.requires_numpy
1576    def test_exponentiation_force_ndarray(self):
1577        ureg = UnitRegistry(force_ndarray_like=True)
1578        q = ureg.Quantity(1, "1 / hours")
1579
1580        q1 = q ** 2
1581        assert all(isinstance(v, int) for v in q1._units.values())
1582
1583        q2 = q.copy()
1584        q2 **= 2
1585        assert all(isinstance(v, int) for v in q2._units.values())
1586
1587    @helpers.requires_numpy
1588    @pytest.mark.parametrize(("input_tuple", "expected"), exponentiation)
1589    def test_inplace_exponentiation(self, input_tuple, expected):
1590        self.ureg.default_as_delta = False
1591        in1, in2 = input_tuple
1592        if type(in1) is tuple and type(in2) is tuple:
1593            (q1v, q1u), (q2v, q2u) = in1, in2
1594            in1 = self.Q_(*(np.array([q1v] * 2, dtype=np.float), q1u))
1595            in2 = self.Q_(q2v, q2u)
1596        elif not type(in1) is tuple and type(in2) is tuple:
1597            in2 = self.Q_(*in2)
1598        else:
1599            in1 = self.Q_(*in1)
1600
1601        input_tuple = in1, in2
1602
1603        expected_copy = expected[:]
1604        for i, mode in enumerate([False, True]):
1605            self.ureg.autoconvert_offset_to_baseunit = mode
1606            in1_cp = copy.copy(in1)
1607            if expected_copy[i] == "error":
1608                with pytest.raises((OffsetUnitCalculusError, DimensionalityError)):
1609                    op.ipow(in1_cp, in2)
1610            else:
1611                if type(expected_copy[i]) is tuple:
1612                    expected = self.Q_(
1613                        np.array([expected_copy[i][0]] * 2, dtype=np.float),
1614                        expected_copy[i][1],
1615                    )
1616                    assert op.ipow(in1_cp, in2).units == expected.units
1617                else:
1618                    expected = np.array([expected_copy[i]] * 2, dtype=np.float)
1619
1620                in1_cp = copy.copy(in1)
1621                helpers.assert_quantity_almost_equal(op.ipow(in1_cp, in2), expected)
1622
1623    # matmul is only a ufunc since 1.16
1624    @helpers.requires_numpy_at_least("1.16")
1625    def test_matmul_with_numpy(self):
1626        A = [[1, 2], [3, 4]] * self.ureg.m
1627        B = np.array([[0, -1], [-1, 0]])
1628        b = [[1], [0]] * self.ureg.m
1629        helpers.assert_quantity_equal(A @ B, [[-2, -1], [-4, -3]] * self.ureg.m)
1630        helpers.assert_quantity_equal(A @ b, [[1], [3]] * self.ureg.m ** 2)
1631        helpers.assert_quantity_equal(B @ b, [[0], [-1]] * self.ureg.m)
1632
1633
1634class TestDimensionReduction:
1635    def _calc_mass(self, ureg):
1636        density = 3 * ureg.g / ureg.L
1637        volume = 32 * ureg.milliliter
1638        return density * volume
1639
1640    def _icalc_mass(self, ureg):
1641        res = ureg.Quantity(3.0, "gram/liter")
1642        res *= ureg.Quantity(32.0, "milliliter")
1643        return res
1644
1645    def test_mul_and_div_reduction(self):
1646        ureg = UnitRegistry(auto_reduce_dimensions=True)
1647        mass = self._calc_mass(ureg)
1648        assert mass.units == ureg.g
1649        ureg = UnitRegistry(auto_reduce_dimensions=False)
1650        mass = self._calc_mass(ureg)
1651        assert mass.units == ureg.g / ureg.L * ureg.milliliter
1652
1653    @helpers.requires_numpy
1654    def test_imul_and_div_reduction(self):
1655        ureg = UnitRegistry(auto_reduce_dimensions=True, force_ndarray=True)
1656        mass = self._icalc_mass(ureg)
1657        assert mass.units == ureg.g
1658        ureg = UnitRegistry(auto_reduce_dimensions=False, force_ndarray=True)
1659        mass = self._icalc_mass(ureg)
1660        assert mass.units == ureg.g / ureg.L * ureg.milliliter
1661
1662    def test_reduction_to_dimensionless(self):
1663        ureg = UnitRegistry(auto_reduce_dimensions=True)
1664        x = (10 * ureg.feet) / (3 * ureg.inches)
1665        assert x.units == UnitsContainer({})
1666        ureg = UnitRegistry(auto_reduce_dimensions=False)
1667        x = (10 * ureg.feet) / (3 * ureg.inches)
1668        assert x.units == ureg.feet / ureg.inches
1669
1670    def test_nocoerce_creation(self):
1671        ureg = UnitRegistry(auto_reduce_dimensions=True)
1672        x = 1 * ureg.foot
1673        assert x.units == ureg.foot
1674
1675
1676class TestTimedelta(QuantityTestCase):
1677    def test_add_sub(self):
1678        d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24)
1679        after = d + 3 * self.ureg.second
1680        assert d + datetime.timedelta(seconds=3) == after
1681        after = 3 * self.ureg.second + d
1682        assert d + datetime.timedelta(seconds=3) == after
1683        after = d - 3 * self.ureg.second
1684        assert d - datetime.timedelta(seconds=3) == after
1685        with pytest.raises(DimensionalityError):
1686            3 * self.ureg.second - d
1687
1688    def test_iadd_isub(self):
1689        d = datetime.datetime(year=1968, month=1, day=10, hour=3, minute=42, second=24)
1690        after = copy.copy(d)
1691        after += 3 * self.ureg.second
1692        assert d + datetime.timedelta(seconds=3) == after
1693        after = 3 * self.ureg.second
1694        after += d
1695        assert d + datetime.timedelta(seconds=3) == after
1696        after = copy.copy(d)
1697        after -= 3 * self.ureg.second
1698        assert d - datetime.timedelta(seconds=3) == after
1699        after = 3 * self.ureg.second
1700        with pytest.raises(DimensionalityError):
1701            after -= d
1702
1703
1704class TestCompareNeutral(QuantityTestCase):
1705    """Test comparisons against non-Quantity zero or NaN values for for
1706    non-dimensionless quantities
1707    """
1708
1709    def test_equal_zero(self):
1710        self.ureg.autoconvert_offset_to_baseunit = False
1711        assert self.Q_(0, "J") == 0
1712        assert not (self.Q_(0, "J") == self.Q_(0, ""))
1713        assert not (self.Q_(5, "J") == 0)
1714
1715    def test_equal_nan(self):
1716        # nan == nan returns False
1717        self.ureg.autoconvert_offset_to_baseunit = False
1718        assert not (self.Q_(math.nan, "J") == 0)
1719        assert not (self.Q_(math.nan, "J") == math.nan)
1720        assert not (self.Q_(math.nan, "J") == self.Q_(math.nan, ""))
1721        assert not (self.Q_(5, "J") == math.nan)
1722
1723    @helpers.requires_numpy
1724    def test_equal_zero_nan_NP(self):
1725        self.ureg.autoconvert_offset_to_baseunit = False
1726        aeq = np.testing.assert_array_equal
1727        aeq(self.Q_(0, "J") == np.array([0, np.nan]), np.array([True, False]))
1728        aeq(self.Q_(5, "J") == np.array([0, np.nan]), np.array([False, False]))
1729        aeq(
1730            self.Q_([0, 1, 2], "J") == np.array([0, 0, np.nan]),
1731            np.asarray([True, False, False]),
1732        )
1733        assert not (self.Q_(np.arange(4), "J") == np.zeros(3))
1734
1735    def test_offset_equal_zero(self):
1736        ureg = self.ureg
1737        ureg.autoconvert_offset_to_baseunit = False
1738        q0 = ureg.Quantity(-273.15, "degC")
1739        q1 = ureg.Quantity(0, "degC")
1740        q2 = ureg.Quantity(5, "degC")
1741        with pytest.raises(OffsetUnitCalculusError):
1742            q0.__eq__(0)
1743        with pytest.raises(OffsetUnitCalculusError):
1744            q1.__eq__(0)
1745        with pytest.raises(OffsetUnitCalculusError):
1746            q2.__eq__(0)
1747        assert not (q0 == ureg.Quantity(0, ""))
1748
1749    def test_offset_autoconvert_equal_zero(self):
1750        ureg = self.ureg
1751        ureg.autoconvert_offset_to_baseunit = True
1752        q0 = ureg.Quantity(-273.15, "degC")
1753        q1 = ureg.Quantity(0, "degC")
1754        q2 = ureg.Quantity(5, "degC")
1755        assert q0 == 0
1756        assert not (q1 == 0)
1757        assert not (q2 == 0)
1758        assert not (q0 == ureg.Quantity(0, ""))
1759
1760    def test_gt_zero(self):
1761        self.ureg.autoconvert_offset_to_baseunit = False
1762        q0 = self.Q_(0, "J")
1763        q0m = self.Q_(0, "m")
1764        q0less = self.Q_(0, "")
1765        qpos = self.Q_(5, "J")
1766        qneg = self.Q_(-5, "J")
1767        assert qpos > q0
1768        assert qpos > 0
1769        assert not (qneg > 0)
1770        with pytest.raises(DimensionalityError):
1771            qpos > q0less
1772        with pytest.raises(DimensionalityError):
1773            qpos > q0m
1774
1775    def test_gt_nan(self):
1776        self.ureg.autoconvert_offset_to_baseunit = False
1777        qn = self.Q_(math.nan, "J")
1778        qnm = self.Q_(math.nan, "m")
1779        qnless = self.Q_(math.nan, "")
1780        qpos = self.Q_(5, "J")
1781        assert not (qpos > qn)
1782        assert not (qpos > math.nan)
1783        with pytest.raises(DimensionalityError):
1784            qpos > qnless
1785        with pytest.raises(DimensionalityError):
1786            qpos > qnm
1787
1788    @helpers.requires_numpy
1789    def test_gt_zero_nan_NP(self):
1790        self.ureg.autoconvert_offset_to_baseunit = False
1791        qpos = self.Q_(5, "J")
1792        qneg = self.Q_(-5, "J")
1793        aeq = np.testing.assert_array_equal
1794        aeq(qpos > np.array([0, np.nan]), np.asarray([True, False]))
1795        aeq(qneg > np.array([0, np.nan]), np.asarray([False, False]))
1796        aeq(
1797            self.Q_(np.arange(-2, 3), "J") > np.array([np.nan, 0, 0, 0, np.nan]),
1798            np.asarray([False, False, False, True, False]),
1799        )
1800        with pytest.raises(ValueError):
1801            self.Q_(np.arange(-1, 2), "J") > np.zeros(4)
1802
1803    def test_offset_gt_zero(self):
1804        ureg = self.ureg
1805        ureg.autoconvert_offset_to_baseunit = False
1806        q0 = ureg.Quantity(-273.15, "degC")
1807        q1 = ureg.Quantity(0, "degC")
1808        q2 = ureg.Quantity(5, "degC")
1809        with pytest.raises(OffsetUnitCalculusError):
1810            q0.__gt__(0)
1811        with pytest.raises(OffsetUnitCalculusError):
1812            q1.__gt__(0)
1813        with pytest.raises(OffsetUnitCalculusError):
1814            q2.__gt__(0)
1815        with pytest.raises(DimensionalityError):
1816            q1.__gt__(ureg.Quantity(0, ""))
1817
1818    def test_offset_autoconvert_gt_zero(self):
1819        ureg = self.ureg
1820        ureg.autoconvert_offset_to_baseunit = True
1821        q0 = ureg.Quantity(-273.15, "degC")
1822        q1 = ureg.Quantity(0, "degC")
1823        q2 = ureg.Quantity(5, "degC")
1824        assert not (q0 > 0)
1825        assert q1 > 0
1826        assert q2 > 0
1827        with pytest.raises(DimensionalityError):
1828            q1.__gt__(ureg.Quantity(0, ""))
1829