numerics.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. # This file is part of TRS (http://math.kompiler.org)
  2. #
  3. # TRS is free software: you can redistribute it and/or modify it under the
  4. # terms of the GNU Affero General Public License as published by the Free
  5. # Software Foundation, either version 3 of the License, or (at your option) any
  6. # later version.
  7. #
  8. # TRS is distributed in the hope that it will be useful, but WITHOUT ANY
  9. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  10. # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  11. # details.
  12. #
  13. # You should have received a copy of the GNU Affero General Public License
  14. # along with TRS. If not, see <http://www.gnu.org/licenses/>.
  15. from itertools import combinations
  16. from .utils import greatest_common_divisor, is_numeric_node
  17. from ..node import ExpressionLeaf as Leaf, Scope, OP_ADD, OP_DIV, OP_MUL, \
  18. OP_POW
  19. from ..possibilities import Possibility as P, MESSAGES
  20. from ..translate import _
  21. def match_add_numerics(node):
  22. """
  23. Combine two constants to a single constant in an n-ary addition.
  24. Example:
  25. 2 + 3 -> 5
  26. 2 + -3 -> -1
  27. -2 + 3 -> 1
  28. -2 + -3 -> -5
  29. 0 + 3 -> 3
  30. 0 + -3 -> -3
  31. """
  32. assert node.is_op(OP_ADD)
  33. p = []
  34. scope = Scope(node)
  35. numerics = []
  36. for n in scope:
  37. if n == 0:
  38. p.append(P(node, remove_zero, (scope, n)))
  39. elif n.is_numeric():
  40. numerics.append(n)
  41. for c0, c1 in combinations(numerics, 2):
  42. p.append(P(node, add_numerics, (scope, c0, c1)))
  43. return p
  44. def remove_zero(root, args):
  45. """
  46. 0 + a -> a
  47. """
  48. scope, n = args
  49. scope.remove(n)
  50. return scope.as_nary_node()
  51. MESSAGES[remove_zero] = _('Remove addition of zero.')
  52. def add_numerics(root, args):
  53. """
  54. 2 + 3 -> 5
  55. 2 + -3 -> -1
  56. -2 + 3 -> 1
  57. -2 + -3 -> -5
  58. """
  59. scope, c0, c1 = args
  60. value = c0.actual_value() + c1.actual_value()
  61. # Replace the left node with the new expression
  62. scope.replace(c0, Leaf(abs(value), negated=int(value < 0)))
  63. # Remove the right node
  64. scope.remove(c1)
  65. return scope.as_nary_node()
  66. MESSAGES[add_numerics] = _('Add the constants {2} and {3}.')
  67. def match_divide_numerics(node):
  68. """
  69. Combine two constants to a single constant in a division, if it does not
  70. lead to a decrease in precision.
  71. Example:
  72. 6 / 2 -> 3
  73. 3 / 2 -> 3 / 2 # 1.5 would mean a decrease in precision
  74. 3.0 / 2 -> 1.5
  75. 3 / 2.0 -> 1.5
  76. 3.0 / 2.0 -> 1.5
  77. 3 / 1.0 -> 3 # Exceptional case: division of integer by 1.0
  78. # keeps integer precision
  79. 2 / 4 -> 1 / 2 # 1 < greatest common divisor <= nominator
  80. 4 / 3 -> 1 + 1 / 3 # nominator > denominator
  81. """
  82. assert node.is_op(OP_DIV)
  83. n, d = node
  84. if n.negated or d.negated:
  85. return []
  86. nv, dv = n.value, d.value
  87. if n.is_int() and d.is_int():
  88. mod = nv % dv
  89. if not mod:
  90. # 6 / 2 -> 3
  91. # 3 / 2 -> 3 / 2
  92. return [P(node, divide_numerics)]
  93. gcd = greatest_common_divisor(nv, dv)
  94. if 1 < gcd <= nv:
  95. # 2 / 4 -> 1 / 2
  96. return [P(node, reduce_fraction_constants, (gcd,))]
  97. #if nv > dv:
  98. # # 4 / 3 -> 1 + 1 / 3
  99. # return [P(node, fraction_to_int_fraction,
  100. # ((nv - mod) / dv, mod, dv))]
  101. elif n.is_numeric() and d.is_numeric():
  102. if d == 1.0:
  103. # 3 / 1.0 -> 3
  104. dv = 1
  105. # 3.0 / 2 -> 1.5
  106. # 3 / 2.0 -> 1.5
  107. # 3.0 / 2.0 -> 1.5
  108. return [P(node, divide_numerics)]
  109. return []
  110. def divide_numerics(root, args):
  111. """
  112. Combine two divided constants into a single constant.
  113. Examples:
  114. 6 / 2 -> 3
  115. 3.0 / 2 -> 1.5
  116. 3 / 2.0 -> 1.5
  117. 3.0 / 2.0 -> 1.5
  118. 3 / 1.0 -> 3
  119. """
  120. n, d = root
  121. return Leaf(n.value / d.value, negated=root.negated)
  122. MESSAGES[divide_numerics] = _('Constant division {0} reduces to a number.')
  123. def reduce_fraction_constants(root, args):
  124. """
  125. Reduce the nominator and denominator of a fraction with a given greatest
  126. common divisor.
  127. Example:
  128. 2 / 4 -> 1 / 2
  129. """
  130. gcd = args[0]
  131. a, b = root
  132. return Leaf(a.value / gcd) / Leaf(b.value / gcd)
  133. MESSAGES[reduce_fraction_constants] = \
  134. _('Divide the nominator and denominator of fraction {0} by {1}.')
  135. def match_multiply_numerics(node):
  136. """
  137. 3 * 2 -> 6
  138. 3.0 * 2 -> 6.0
  139. 3 * 2.0 -> 6.0
  140. 3.0 * 2.0 -> 6.0
  141. """
  142. assert node.is_op(OP_MUL)
  143. p = []
  144. scope = Scope(node)
  145. numerics = filter(is_numeric_node, scope)
  146. for n in numerics:
  147. if n.negated:
  148. continue
  149. if n.value == 0:
  150. p.append(P(node, multiply_zero, (n,)))
  151. if n.value == 1:
  152. p.append(P(node, multiply_one, (scope, n)))
  153. for c0, c1 in combinations(numerics, 2):
  154. p.append(P(node, multiply_numerics, (scope, c0, c1)))
  155. return p
  156. def multiply_zero(root, args):
  157. """
  158. 0 * a -> 0
  159. -0 * a -> -0
  160. """
  161. return args[0].negate(root.negated)
  162. MESSAGES[multiply_zero] = _('Multiplication with zero yields zero.')
  163. def multiply_one(root, args):
  164. """
  165. 1 * a -> a
  166. -1 * a -> -a
  167. """
  168. scope, one = args
  169. scope.remove(one)
  170. return scope.as_nary_node().negate(one.negated)
  171. MESSAGES[multiply_one] = _('Multiplication with one yields the multiplicant.')
  172. def multiply_numerics(root, args):
  173. """
  174. Combine two constants to a single constant in an n-ary multiplication.
  175. Example:
  176. 2 * 3 -> 6
  177. """
  178. scope, c0, c1 = args
  179. # Replace the left node with the new expression
  180. substitution = Leaf(c0.value * c1.value, negated=c0.negated + c1.negated)
  181. scope.replace(c0, substitution)
  182. # Remove the right node
  183. scope.remove(c1)
  184. return scope.as_nary_node()
  185. MESSAGES[multiply_numerics] = _('Multiply constant {2} with {3}.')
  186. def match_raise_numerics(node):
  187. """
  188. 2 ^ 3 -> 8
  189. (-2) ^ 3 -> -8
  190. (-2) ^ 2 -> 4
  191. """
  192. assert node.is_op(OP_POW)
  193. r, e = node
  194. if r.is_numeric() and e.is_numeric() and not e.negated:
  195. return [P(node, raise_numerics, (r, e, node.negated))]
  196. return []
  197. def raise_numerics(root, args):
  198. """
  199. 2 ^ 3 -> 8
  200. (-2) ^ 3 -> -8
  201. (-2) ^ 2 -> 4
  202. """
  203. r, e, negated = args
  204. return Leaf(r.value ** e.value, negated=r.negated * e.value + negated)
  205. MESSAGES[raise_numerics] = _('Raise constant {1} with {2}.')