numerics.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. from itertools import combinations
  2. from .utils import greatest_common_divisor
  3. from ..node import ExpressionLeaf as Leaf, Scope, negate, OP_ADD, OP_DIV, \
  4. OP_MUL, OP_POW
  5. from ..possibilities import Possibility as P, MESSAGES
  6. from ..translate import _
  7. def match_add_numerics(node):
  8. """
  9. Combine two constants to a single constant in an n-ary addition.
  10. Example:
  11. 2 + 3 -> 5
  12. 2 + -3 -> -1
  13. -2 + 3 -> 1
  14. -2 + -3 -> -5
  15. 0 + 3 -> 3
  16. 0 + -3 -> -3
  17. """
  18. assert node.is_op(OP_ADD)
  19. p = []
  20. scope = Scope(node)
  21. numerics = []
  22. for n in scope:
  23. if n == 0:
  24. p.append(P(node, remove_zero, (scope, n)))
  25. elif n.is_numeric():
  26. numerics.append(n)
  27. for c0, c1 in combinations(numerics, 2):
  28. p.append(P(node, add_numerics, (scope, c0, c1)))
  29. return p
  30. def remove_zero(root, args):
  31. """
  32. 0 + a -> a
  33. """
  34. scope, n = args
  35. scope.remove(n)
  36. return scope.as_nary_node()
  37. def add_numerics(root, args):
  38. """
  39. 2 + 3 -> 5
  40. 2 + -3 -> -1
  41. -2 + 3 -> 1
  42. -2 + -3 -> -5
  43. """
  44. scope, c0, c1 = args
  45. value = c0.actual_value() + c1.actual_value()
  46. # Replace the left node with the new expression
  47. scope.replace(c0, Leaf(abs(value)).negate(int(value < 0)))
  48. # Remove the right node
  49. scope.remove(c1)
  50. return scope.as_nary_node()
  51. MESSAGES[add_numerics] = _('Add the constants {2} and {3}.')
  52. #def match_subtract_numerics(node):
  53. # """
  54. # 3 - 2 -> 2.0
  55. # 3.0 - 2 -> 1.0
  56. # 3 - 2.0 -> 1.0
  57. # 3.0 - 2.0 -> 1.0
  58. # """
  59. # # TODO: This should be handled by match_combine_polynomes
  60. # assert node.is_op(OP_MUL)
  61. def match_divide_numerics(node):
  62. """
  63. Combine two constants to a single constant in a division, if it does not
  64. lead to a decrease in precision.
  65. Example:
  66. 6 / 2 -> 3
  67. 3 / 2 -> 3 / 2 # 1.5 would mean a decrease in precision
  68. 3.0 / 2 -> 1.5
  69. 3 / 2.0 -> 1.5
  70. 3.0 / 2.0 -> 1.5
  71. 3 / 1.0 -> 3 # Exceptional case: division of integer by 1.0
  72. # keeps integer precision
  73. 2 / 4 -> 1 / 2 # 1 < greatest common divisor <= nominator
  74. 4 / 3 -> 1 + 1 / 3 # nominator > denominator
  75. """
  76. assert node.is_op(OP_DIV)
  77. n, d = node
  78. divide = False
  79. nv, dv = n.value, d.value
  80. if n.is_int() and d.is_int():
  81. mod = nv % dv
  82. if not mod:
  83. # 6 / 2 -> 3
  84. # 3 / 2 -> 3 / 2
  85. return [P(node, divide_numerics, (nv, dv))]
  86. gcd = greatest_common_divisor(nv, dv)
  87. if 1 < gcd <= nv:
  88. # 2 / 4 -> 1 / 2
  89. return [P(node, reduce_fraction_constants, (gcd,))]
  90. if nv > dv:
  91. # 4 / 3 -> 1 + 1 / 3
  92. return [P(node, fraction_to_int_fraction,
  93. ((nv - mod) / dv, mod, dv))]
  94. elif n.is_numeric() and d.is_numeric():
  95. if d == 1.0:
  96. # 3 / 1.0 -> 3
  97. dv = 1
  98. # 3.0 / 2 -> 1.5
  99. # 3 / 2.0 -> 1.5
  100. # 3.0 / 2.0 -> 1.5
  101. return [P(node, divide_numerics, (nv, dv))]
  102. return []
  103. def divide_numerics(root, args):
  104. """
  105. Combine two divided constants into a single constant.
  106. Examples:
  107. 6 / 2 -> 3
  108. 3.0 / 2 -> 1.5
  109. 3 / 2.0 -> 1.5
  110. 3.0 / 2.0 -> 1.5
  111. 3 / 1.0 -> 3
  112. """
  113. n, d = args
  114. return Leaf(n / d)
  115. MESSAGES[divide_numerics] = _('Divide constant {1} by constant {2}.')
  116. def reduce_fraction_constants(root, args):
  117. """
  118. Reduce the nominator and denominator of a fraction with a given greatest
  119. common divisor.
  120. Example:
  121. 2 / 4 -> 1 / 2
  122. """
  123. gcd = args[0]
  124. a, b = root
  125. return Leaf(a.value / gcd).negate(a.negated) \
  126. / Leaf(b.value / gcd).negate(b.negated)
  127. MESSAGES[reduce_fraction_constants] = _('Simplify fraction {0}.')
  128. def fraction_to_int_fraction(root, args):
  129. """
  130. Combine two divided integer into an integer with a fraction.
  131. Examples:
  132. 4 / 3 -> 1 + 1 / 3
  133. """
  134. integer, nominator, denominator = map(Leaf, args)
  135. return integer + nominator / denominator
  136. MESSAGES[fraction_to_int_fraction] = _('Expand fraction with nominator greater'
  137. ' than denominator {0} to an integer plus a fraction.')
  138. def match_multiply_zero(node):
  139. """
  140. a * 0 -> 0
  141. 0 * a -> 0
  142. -0 * a -> -0
  143. 0 * -a -> -0
  144. -0 * -a -> 0
  145. """
  146. assert node.is_op(OP_MUL)
  147. left, right = node
  148. if (left.is_leaf and left.value == 0) \
  149. or (right.is_leaf and right.value == 0):
  150. return [P(node, multiply_zero, (left.negated + right.negated,))]
  151. return []
  152. def multiply_zero(root, args):
  153. """
  154. a * 0 -> 0
  155. 0 * a -> 0
  156. -0 * a -> -0
  157. 0 * -a -> -0
  158. -0 * -a -> 0
  159. """
  160. return negate(Leaf(0), args[0])
  161. MESSAGES[multiply_zero] = _('Multiplication with zero yields zero.')
  162. def match_multiply_one(node):
  163. """
  164. a * 1 -> a
  165. 1 * a -> a
  166. -1 * a -> -a
  167. 1 * -a -> -a
  168. -1 * -a -> a
  169. """
  170. assert node.is_op(OP_MUL)
  171. left, right = node
  172. if left.value == 1:
  173. return [P(node, multiply_one, (right, left))]
  174. if right.value == 1:
  175. return [P(node, multiply_one, (left, right))]
  176. return []
  177. def multiply_one(root, args):
  178. """
  179. a * 1 -> a
  180. 1 * a -> a
  181. -1 * a -> -a
  182. 1 * -a -> -a
  183. -1 * -a -> a
  184. """
  185. a, one = args
  186. return a.negate(one.negated + root.negated)
  187. MESSAGES[multiply_one] = _('Multiplication with one yields the multiplicant.')
  188. def match_multiply_numerics(node):
  189. """
  190. 3 * 2 -> 6
  191. 3.0 * 2 -> 6.0
  192. 3 * 2.0 -> 6.0
  193. 3.0 * 2.0 -> 6.0
  194. """
  195. assert node.is_op(OP_MUL)
  196. p = []
  197. scope = Scope(node)
  198. numerics = filter(lambda n: n.is_numeric(), scope)
  199. for c0, c1 in combinations(numerics, 2):
  200. p.append(P(node, multiply_numerics, (scope, c0, c1)))
  201. return p
  202. def multiply_numerics(root, args):
  203. """
  204. Combine two constants to a single constant in an n-ary multiplication.
  205. Example:
  206. 2 * 3 -> 6
  207. """
  208. scope, c0, c1 = args
  209. # Replace the left node with the new expression
  210. substitution = Leaf(c0.value * c1.value).negate(c0.negated + c1.negated)
  211. scope.replace(c0, substitution)
  212. # Remove the right node
  213. scope.remove(c1)
  214. return scope.as_nary_node()
  215. MESSAGES[multiply_numerics] = _('Multiply constant {2} with {3}.')
  216. def match_raise_numerics(node):
  217. """
  218. 2 ^ 3 -> 8
  219. (-2) ^ 3 -> -8
  220. (-2) ^ 2 -> 4
  221. """
  222. assert node.is_op(OP_POW)
  223. r, e = node
  224. if r.is_numeric() and e.is_numeric() and not e.negated:
  225. return [P(node, raise_numerics, (r, e))]
  226. return []
  227. def raise_numerics(root, args):
  228. """
  229. 2 ^ 3 -> 8
  230. (-2) ^ 3 -> -8
  231. (-2) ^ 2 -> 4
  232. """
  233. r, e = args
  234. return Leaf(r.value ** e.value).negate(r.negated * e.value)
  235. MESSAGES[raise_numerics] = _('Raise constant {1} with {2}.')