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