numerics.py 5.8 KB

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