UnitDbl.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. """UnitDbl module."""
  2. import functools
  3. import operator
  4. from matplotlib import _api
  5. class UnitDbl:
  6. """Class UnitDbl in development."""
  7. # Unit conversion table. Small subset of the full one but enough
  8. # to test the required functions. First field is a scale factor to
  9. # convert the input units to the units of the second field. Only
  10. # units in this table are allowed.
  11. allowed = {
  12. "m": (0.001, "km"),
  13. "km": (1, "km"),
  14. "mile": (1.609344, "km"),
  15. "rad": (1, "rad"),
  16. "deg": (1.745329251994330e-02, "rad"),
  17. "sec": (1, "sec"),
  18. "min": (60.0, "sec"),
  19. "hour": (3600, "sec"),
  20. }
  21. _types = {
  22. "km": "distance",
  23. "rad": "angle",
  24. "sec": "time",
  25. }
  26. def __init__(self, value, units):
  27. """
  28. Create a new UnitDbl object.
  29. Units are internally converted to km, rad, and sec. The only
  30. valid inputs for units are [m, km, mile, rad, deg, sec, min, hour].
  31. The field UnitDbl.value will contain the converted value. Use
  32. the convert() method to get a specific type of units back.
  33. = ERROR CONDITIONS
  34. - If the input units are not in the allowed list, an error is thrown.
  35. = INPUT VARIABLES
  36. - value The numeric value of the UnitDbl.
  37. - units The string name of the units the value is in.
  38. """
  39. data = _api.check_getitem(self.allowed, units=units)
  40. self._value = float(value * data[0])
  41. self._units = data[1]
  42. def convert(self, units):
  43. """
  44. Convert the UnitDbl to a specific set of units.
  45. = ERROR CONDITIONS
  46. - If the input units are not in the allowed list, an error is thrown.
  47. = INPUT VARIABLES
  48. - units The string name of the units to convert to.
  49. = RETURN VALUE
  50. - Returns the value of the UnitDbl in the requested units as a floating
  51. point number.
  52. """
  53. if self._units == units:
  54. return self._value
  55. data = _api.check_getitem(self.allowed, units=units)
  56. if self._units != data[1]:
  57. raise ValueError(f"Error trying to convert to different units.\n"
  58. f" Invalid conversion requested.\n"
  59. f" UnitDbl: {self}\n"
  60. f" Units: {units}\n")
  61. return self._value / data[0]
  62. def __abs__(self):
  63. """Return the absolute value of this UnitDbl."""
  64. return UnitDbl(abs(self._value), self._units)
  65. def __neg__(self):
  66. """Return the negative value of this UnitDbl."""
  67. return UnitDbl(-self._value, self._units)
  68. def __bool__(self):
  69. """Return the truth value of a UnitDbl."""
  70. return bool(self._value)
  71. def _cmp(self, op, rhs):
  72. """Check that *self* and *rhs* share units; compare them using *op*."""
  73. self.checkSameUnits(rhs, "compare")
  74. return op(self._value, rhs._value)
  75. __eq__ = functools.partialmethod(_cmp, operator.eq)
  76. __ne__ = functools.partialmethod(_cmp, operator.ne)
  77. __lt__ = functools.partialmethod(_cmp, operator.lt)
  78. __le__ = functools.partialmethod(_cmp, operator.le)
  79. __gt__ = functools.partialmethod(_cmp, operator.gt)
  80. __ge__ = functools.partialmethod(_cmp, operator.ge)
  81. def _binop_unit_unit(self, op, rhs):
  82. """Check that *self* and *rhs* share units; combine them using *op*."""
  83. self.checkSameUnits(rhs, op.__name__)
  84. return UnitDbl(op(self._value, rhs._value), self._units)
  85. __add__ = functools.partialmethod(_binop_unit_unit, operator.add)
  86. __sub__ = functools.partialmethod(_binop_unit_unit, operator.sub)
  87. def _binop_unit_scalar(self, op, scalar):
  88. """Combine *self* and *scalar* using *op*."""
  89. return UnitDbl(op(self._value, scalar), self._units)
  90. __mul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
  91. __rmul__ = functools.partialmethod(_binop_unit_scalar, operator.mul)
  92. def __str__(self):
  93. """Print the UnitDbl."""
  94. return f"{self._value:g} *{self._units}"
  95. def __repr__(self):
  96. """Print the UnitDbl."""
  97. return f"UnitDbl({self._value:g}, '{self._units}')"
  98. def type(self):
  99. """Return the type of UnitDbl data."""
  100. return self._types[self._units]
  101. @staticmethod
  102. def range(start, stop, step=None):
  103. """
  104. Generate a range of UnitDbl objects.
  105. Similar to the Python range() method. Returns the range [
  106. start, stop) at the requested step. Each element will be a
  107. UnitDbl object.
  108. = INPUT VARIABLES
  109. - start The starting value of the range.
  110. - stop The stop value of the range.
  111. - step Optional step to use. If set to None, then a UnitDbl of
  112. value 1 w/ the units of the start is used.
  113. = RETURN VALUE
  114. - Returns a list containing the requested UnitDbl values.
  115. """
  116. if step is None:
  117. step = UnitDbl(1, start._units)
  118. elems = []
  119. i = 0
  120. while True:
  121. d = start + i * step
  122. if d >= stop:
  123. break
  124. elems.append(d)
  125. i += 1
  126. return elems
  127. def checkSameUnits(self, rhs, func):
  128. """
  129. Check to see if units are the same.
  130. = ERROR CONDITIONS
  131. - If the units of the rhs UnitDbl are not the same as our units,
  132. an error is thrown.
  133. = INPUT VARIABLES
  134. - rhs The UnitDbl to check for the same units
  135. - func The name of the function doing the check.
  136. """
  137. if self._units != rhs._units:
  138. raise ValueError(f"Cannot {func} units of different types.\n"
  139. f"LHS: {self._units}\n"
  140. f"RHS: {rhs._units}")