mathutils.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2013, Mahmoud Hashemi
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. #
  11. # * Redistributions in binary form must reproduce the above
  12. # copyright notice, this list of conditions and the following
  13. # disclaimer in the documentation and/or other materials provided
  14. # with the distribution.
  15. #
  16. # * The names of the contributors may not be used to endorse or
  17. # promote products derived from this software without specific
  18. # prior written permission.
  19. #
  20. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. """This module provides useful math functions on top of Python's
  32. built-in :mod:`math` module.
  33. """
  34. from __future__ import division
  35. from math import ceil as _ceil, floor as _floor
  36. import bisect
  37. import binascii
  38. def clamp(x, lower=float('-inf'), upper=float('inf')):
  39. """Limit a value to a given range.
  40. Args:
  41. x (int or float): Number to be clamped.
  42. lower (int or float): Minimum value for x.
  43. upper (int or float): Maximum value for x.
  44. The returned value is guaranteed to be between *lower* and
  45. *upper*. Integers, floats, and other comparable types can be
  46. mixed.
  47. >>> clamp(1.0, 0, 5)
  48. 1.0
  49. >>> clamp(-1.0, 0, 5)
  50. 0
  51. >>> clamp(101.0, 0, 5)
  52. 5
  53. >>> clamp(123, upper=5)
  54. 5
  55. Similar to `numpy's clip`_ function.
  56. .. _numpy's clip: http://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html
  57. """
  58. if upper < lower:
  59. raise ValueError('expected upper bound (%r) >= lower bound (%r)'
  60. % (upper, lower))
  61. return min(max(x, lower), upper)
  62. def ceil(x, options=None):
  63. """Return the ceiling of *x*. If *options* is set, return the smallest
  64. integer or float from *options* that is greater than or equal to
  65. *x*.
  66. Args:
  67. x (int or float): Number to be tested.
  68. options (iterable): Optional iterable of arbitrary numbers
  69. (ints or floats).
  70. >>> VALID_CABLE_CSA = [1.5, 2.5, 4, 6, 10, 25, 35, 50]
  71. >>> ceil(3.5, options=VALID_CABLE_CSA)
  72. 4
  73. >>> ceil(4, options=VALID_CABLE_CSA)
  74. 4
  75. """
  76. if options is None:
  77. return _ceil(x)
  78. options = sorted(options)
  79. i = bisect.bisect_left(options, x)
  80. if i == len(options):
  81. raise ValueError("no ceil options greater than or equal to: %r" % x)
  82. return options[i]
  83. def floor(x, options=None):
  84. """Return the floor of *x*. If *options* is set, return the largest
  85. integer or float from *options* that is less than or equal to
  86. *x*.
  87. Args:
  88. x (int or float): Number to be tested.
  89. options (iterable): Optional iterable of arbitrary numbers
  90. (ints or floats).
  91. >>> VALID_CABLE_CSA = [1.5, 2.5, 4, 6, 10, 25, 35, 50]
  92. >>> floor(3.5, options=VALID_CABLE_CSA)
  93. 2.5
  94. >>> floor(2.5, options=VALID_CABLE_CSA)
  95. 2.5
  96. """
  97. if options is None:
  98. return _floor(x)
  99. options = sorted(options)
  100. i = bisect.bisect_right(options, x)
  101. if not i:
  102. raise ValueError("no floor options less than or equal to: %r" % x)
  103. return options[i - 1]
  104. try:
  105. _int_types = (int, long)
  106. bytes = str
  107. except NameError:
  108. # py3 has no long
  109. _int_types = (int,)
  110. unicode = str
  111. class Bits(object):
  112. '''
  113. An immutable bit-string or bit-array object.
  114. Provides list-like access to bits as bools,
  115. as well as bitwise masking and shifting operators.
  116. Bits also make it easy to convert between many
  117. different useful representations:
  118. * bytes -- good for serializing raw binary data
  119. * int -- good for incrementing (e.g. to try all possible values)
  120. * list of bools -- good for iterating over or treating as flags
  121. * hex/bin string -- good for human readability
  122. '''
  123. __slots__ = ('val', 'len')
  124. def __init__(self, val=0, len_=None):
  125. if type(val) not in _int_types:
  126. if type(val) is list:
  127. val = ''.join(['1' if e else '0' for e in val])
  128. if type(val) is bytes:
  129. val = val.decode('ascii')
  130. if type(val) is unicode:
  131. if len_ is None:
  132. len_ = len(val)
  133. if val.startswith('0x'):
  134. len_ = (len_ - 2) * 4
  135. if val.startswith('0x'):
  136. val = int(val, 16)
  137. else:
  138. if val:
  139. val = int(val, 2)
  140. else:
  141. val = 0
  142. if type(val) not in _int_types:
  143. raise TypeError('initialized with bad type: {0}'.format(type(val).__name__))
  144. if val < 0:
  145. raise ValueError('Bits cannot represent negative values')
  146. if len_ is None:
  147. len_ = len('{0:b}'.format(val))
  148. if val > 2 ** len_:
  149. raise ValueError('value {0} cannot be represented with {1} bits'.format(val, len_))
  150. self.val = val # data is stored internally as integer
  151. self.len = len_
  152. def __getitem__(self, k):
  153. if type(k) is slice:
  154. return Bits(self.as_bin()[k])
  155. if type(k) is int:
  156. if k >= self.len:
  157. raise IndexError(k)
  158. return bool((1 << (self.len - k - 1)) & self.val)
  159. raise TypeError(type(k))
  160. def __len__(self):
  161. return self.len
  162. def __eq__(self, other):
  163. if type(self) is not type(other):
  164. return NotImplemented
  165. return self.val == other.val and self.len == other.len
  166. def __or__(self, other):
  167. if type(self) is not type(other):
  168. return NotImplemented
  169. return Bits(self.val | other.val, max(self.len, other.len))
  170. def __and__(self, other):
  171. if type(self) is not type(other):
  172. return NotImplemented
  173. return Bits(self.val & other.val, max(self.len, other.len))
  174. def __lshift__(self, other):
  175. return Bits(self.val << other, self.len + other)
  176. def __rshift__(self, other):
  177. return Bits(self.val >> other, self.len - other)
  178. def __hash__(self):
  179. return hash(self.val)
  180. def as_list(self):
  181. return [c == '1' for c in self.as_bin()]
  182. def as_bin(self):
  183. return '{{0:0{0}b}}'.format(self.len).format(self.val)
  184. def as_hex(self):
  185. # make template to pad out to number of bytes necessary to represent bits
  186. tmpl = '%0{0}X'.format(2 * (self.len // 8 + ((self.len % 8) != 0)))
  187. ret = tmpl % self.val
  188. return ret
  189. def as_int(self):
  190. return self.val
  191. def as_bytes(self):
  192. return binascii.unhexlify(self.as_hex())
  193. @classmethod
  194. def from_list(cls, list_):
  195. return cls(list_)
  196. @classmethod
  197. def from_bin(cls, bin):
  198. return cls(bin)
  199. @classmethod
  200. def from_hex(cls, hex):
  201. if isinstance(hex, bytes):
  202. hex = hex.decode('ascii')
  203. if not hex.startswith('0x'):
  204. hex = '0x' + hex
  205. return cls(hex)
  206. @classmethod
  207. def from_int(cls, int_, len_=None):
  208. return cls(int_, len_)
  209. @classmethod
  210. def from_bytes(cls, bytes_):
  211. return cls.from_hex(binascii.hexlify(bytes_))
  212. def __repr__(self):
  213. cn = self.__class__.__name__
  214. return "{0}('{1}')".format(cn, self.as_bin())