numerics.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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_divide_numerics(node):
  53. """
  54. Combine two constants to a single constant in a division, if it does not
  55. lead to a decrease in precision.
  56. Example:
  57. 6 / 2 -> 3
  58. 3 / 2 -> 3 / 2 # 1.5 would mean a decrease in precision
  59. 3.0 / 2 -> 1.5
  60. 3 / 2.0 -> 1.5
  61. 3.0 / 2.0 -> 1.5
  62. 3 / 1.0 -> 3 # Exceptional case: division of integer by 1.0
  63. # keeps integer precision
  64. 2 / 4 -> 1 / 2 # 1 < greatest common divisor <= nominator
  65. 4 / 3 -> 1 + 1 / 3 # nominator > denominator
  66. """
  67. assert node.is_op(OP_DIV)
  68. n, d = node
  69. if n.negated or d.negated:
  70. return []
  71. nv, dv = n.value, d.value
  72. if n.is_int() and d.is_int():
  73. mod = nv % dv
  74. if not mod:
  75. # 6 / 2 -> 3
  76. # 3 / 2 -> 3 / 2
  77. return [P(node, divide_numerics)]
  78. gcd = greatest_common_divisor(nv, dv)
  79. if 1 < gcd <= nv:
  80. # 2 / 4 -> 1 / 2
  81. return [P(node, reduce_fraction_constants, (gcd,))]
  82. #if nv > dv:
  83. # # 4 / 3 -> 1 + 1 / 3
  84. # return [P(node, fraction_to_int_fraction,
  85. # ((nv - mod) / dv, mod, dv))]
  86. elif n.is_numeric() and d.is_numeric():
  87. if d == 1.0:
  88. # 3 / 1.0 -> 3
  89. dv = 1
  90. # 3.0 / 2 -> 1.5
  91. # 3 / 2.0 -> 1.5
  92. # 3.0 / 2.0 -> 1.5
  93. return [P(node, divide_numerics)]
  94. return []
  95. def divide_numerics(root, args):
  96. """
  97. Combine two divided constants into a single constant.
  98. Examples:
  99. 6 / 2 -> 3
  100. 3.0 / 2 -> 1.5
  101. 3 / 2.0 -> 1.5
  102. 3.0 / 2.0 -> 1.5
  103. 3 / 1.0 -> 3
  104. """
  105. n, d = root
  106. return Leaf(n.value / d.value).negate(root.negated)
  107. MESSAGES[divide_numerics] = _('Constant division {0} reduces to a number.')
  108. def reduce_fraction_constants(root, args):
  109. """
  110. Reduce the nominator and denominator of a fraction with a given greatest
  111. common divisor.
  112. Example:
  113. 2 / 4 -> 1 / 2
  114. """
  115. gcd = args[0]
  116. a, b = root
  117. return Leaf(a.value / gcd) / Leaf(b.value / gcd)
  118. MESSAGES[reduce_fraction_constants] = \
  119. _('Divide the nominator and denominator of fraction {0} by {1}.')
  120. def match_multiply_zero(node):
  121. """
  122. a * 0 -> 0
  123. 0 * a -> 0
  124. -0 * a -> -0
  125. 0 * -a -> -0
  126. -0 * -a -> 0
  127. """
  128. assert node.is_op(OP_MUL)
  129. left, right = node
  130. if (left.is_leaf and left.value == 0) \
  131. or (right.is_leaf and right.value == 0):
  132. return [P(node, multiply_zero, (left.negated + right.negated,))]
  133. return []
  134. def match_multiply_numerics(node):
  135. """
  136. 3 * 2 -> 6
  137. 3.0 * 2 -> 6.0
  138. 3 * 2.0 -> 6.0
  139. 3.0 * 2.0 -> 6.0
  140. """
  141. assert node.is_op(OP_MUL)
  142. p = []
  143. scope = Scope(node)
  144. numerics = filter(lambda n: n.is_numeric(), scope)
  145. for n in numerics:
  146. if n.negated:
  147. continue
  148. if n.value == 0:
  149. p.append(P(node, multiply_zero, (n,)))
  150. if n.value == 1:
  151. p.append(P(node, multiply_one, (scope, n)))
  152. for c0, c1 in combinations(numerics, 2):
  153. p.append(P(node, multiply_numerics, (scope, c0, c1)))
  154. return p
  155. def multiply_zero(root, args):
  156. """
  157. 0 * a -> 0
  158. -0 * a -> -0
  159. """
  160. return args[0].negate(root.negated)
  161. MESSAGES[multiply_zero] = _('Multiplication with zero yields zero.')
  162. def multiply_one(root, args):
  163. """
  164. 1 * a -> a
  165. -1 * a -> -a
  166. """
  167. scope, one = args
  168. scope.remove(one)
  169. return scope.as_nary_node().negate(one.negated)
  170. MESSAGES[multiply_one] = _('Multiplication with one yields the multiplicant.')
  171. def multiply_numerics(root, args):
  172. """
  173. Combine two constants to a single constant in an n-ary multiplication.
  174. Example:
  175. 2 * 3 -> 6
  176. """
  177. scope, c0, c1 = args
  178. # Replace the left node with the new expression
  179. substitution = Leaf(c0.value * c1.value).negate(c0.negated + c1.negated)
  180. scope.replace(c0, substitution)
  181. # Remove the right node
  182. scope.remove(c1)
  183. return scope.as_nary_node()
  184. MESSAGES[multiply_numerics] = _('Multiply constant {2} with {3}.')
  185. def match_raise_numerics(node):
  186. """
  187. 2 ^ 3 -> 8
  188. (-2) ^ 3 -> -8
  189. (-2) ^ 2 -> 4
  190. """
  191. assert node.is_op(OP_POW)
  192. r, e = node
  193. if r.is_numeric() and e.is_numeric() and not e.negated:
  194. return [P(node, raise_numerics, (r, e))]
  195. return []
  196. def raise_numerics(root, args):
  197. """
  198. 2 ^ 3 -> 8
  199. (-2) ^ 3 -> -8
  200. (-2) ^ 2 -> 4
  201. """
  202. r, e = args
  203. return Leaf(r.value ** e.value).negate(r.negated * e.value)
  204. MESSAGES[raise_numerics] = _('Raise constant {1} with {2}.')