fractions.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. from itertools import combinations, product, ifilterfalse
  2. from .utils import least_common_multiple, partition, is_numeric_node, \
  3. evals_to_numeric
  4. from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DIV, \
  5. OP_ADD, OP_MUL, negate
  6. from ..possibilities import Possibility as P, MESSAGES
  7. from ..translate import _
  8. def match_constant_division(node):
  9. """
  10. a / 0 -> Division by zero
  11. a / 1 -> a
  12. 0 / a -> 0
  13. a / a -> 1
  14. """
  15. assert node.is_op(OP_DIV)
  16. p = []
  17. nominator, denominator = node
  18. # a / 0
  19. if denominator == 0:
  20. raise ZeroDivisionError('Division by zero: %s.' % node)
  21. # a / 1
  22. if denominator == 1:
  23. p.append(P(node, division_by_one, (nominator,)))
  24. # 0 / a
  25. if nominator == 0:
  26. p.append(P(node, division_of_zero, (denominator,)))
  27. # a / a
  28. if nominator == denominator:
  29. p.append(P(node, division_by_self, (nominator,)))
  30. return p
  31. def division_by_one(root, args):
  32. """
  33. a / 1 -> a
  34. """
  35. return args[0]
  36. MESSAGES[division_by_one] = _('Division by 1 yields the nominator.')
  37. def division_of_zero(root, args):
  38. """
  39. 0 / a -> 0
  40. """
  41. return L(0)
  42. MESSAGES[division_of_zero] = _('Division of 0 by {1} reduces to 0.')
  43. def division_by_self(root, args):
  44. """
  45. a / a -> 1
  46. """
  47. return L(1)
  48. MESSAGES[division_by_self] = _('Division of {1} by itself reduces to 1.')
  49. def match_add_fractions(node):
  50. """
  51. a / b + c / b and a, c in Z -> (a + c) / b
  52. a / b + c / d and a, b, c, d in Z -> a' / e + c' / e # e = lcm(b, d)
  53. # | e = b * d
  54. a / b + c and a, b, c in Z -> a / b + (bc) / b # =>* (a + bc) / b
  55. """
  56. assert node.is_op(OP_ADD)
  57. p = []
  58. scope = Scope(node)
  59. fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
  60. numerics = filter(is_numeric_node, others)
  61. for ab, cd in combinations(fractions, 2):
  62. a, b = ab
  63. c, d = cd
  64. if b == d:
  65. # Equal denominators, add nominators to create a single fraction
  66. p.append(P(node, add_nominators, (scope, ab, cd)))
  67. elif all(map(is_numeric_node, (a, b, c, d))):
  68. # Denominators are both numeric, rewrite both fractions to the
  69. # least common multiple of their denominators. Later, the
  70. # nominators will be added
  71. lcm = least_common_multiple(b.value, d.value)
  72. p.append(P(node, equalize_denominators, (scope, ab, cd, lcm)))
  73. # Also, add the (non-recommended) possibility to multiply the
  74. # denominators. Do this only if the multiplication is not equal to
  75. # the least common multiple, to avoid duplicate possibilities
  76. mult = b.value * d.value
  77. if mult != lcm:
  78. p.append(P(node, equalize_denominators, (scope, ab, cd, mult)))
  79. for ab, c in product(fractions, numerics):
  80. a, b = ab
  81. if a.is_numeric() and b.is_numeric():
  82. # Fraction of constants added to a constant -> create a single
  83. # constant fraction
  84. p.append(P(node, constant_to_fraction, (scope, ab, c)))
  85. return p
  86. def add_nominators(root, args):
  87. """
  88. a / b + c / b and a, c in Z -> (a + c) / b
  89. """
  90. scope, ab, cb = args
  91. a, b = ab
  92. c = cb[0]
  93. # Replace the left node with the new expression, transfer fraction
  94. # negations to nominators
  95. scope.replace(ab, (a.negate(ab.negated) + c.negate(cb.negated)) / b)
  96. scope.remove(cb)
  97. return scope.as_nary_node()
  98. MESSAGES[add_nominators] = \
  99. _('Add the nominators of {2} and {3} to create a single fraction.')
  100. def equalize_denominators(root, args):
  101. """
  102. a / b + c / d and a, b, c, d in Z -> a' / e + c' / e
  103. """
  104. scope, denom = args[::3]
  105. for fraction in args[1:3]:
  106. n, d = fraction
  107. mult = denom / d.value
  108. if mult != 1:
  109. if n.is_numeric():
  110. nom = L(n.value * mult)
  111. else:
  112. nom = L(mult) * n
  113. scope.replace(fraction, negate(nom / L(d.value * mult),
  114. fraction.negated))
  115. return scope.as_nary_node()
  116. MESSAGES[equalize_denominators] = \
  117. _('Equalize the denominators of divisions' ' {2} and {3} to {4}.')
  118. def constant_to_fraction(root, args):
  119. """
  120. a / b + c and a, b, c in Z -> a / b + (bc) / b # =>* (a + bc) / b
  121. """
  122. scope, ab, c = args
  123. b = ab[1]
  124. scope.replace(c, b * c / b)
  125. return scope.as_nary_node()
  126. MESSAGES[constant_to_fraction] = \
  127. _('Rewrite constant {3} to a fraction to be able to add it to {2}.')
  128. def match_multiply_fractions(node):
  129. """
  130. a / b * c / d -> (ac) / (bd)
  131. a / b * c and (eval(c) in Z or eval(a / b) not in Z) -> (ac) / b
  132. """
  133. assert node.is_op(OP_MUL)
  134. p = []
  135. scope = Scope(node)
  136. fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
  137. for ab, cd in combinations(fractions, 2):
  138. p.append(P(node, multiply_fractions, (scope, ab, cd)))
  139. for ab, c in product(fractions, others):
  140. if evals_to_numeric(c) or not evals_to_numeric(ab):
  141. p.append(P(node, multiply_with_fraction, (scope, ab, c)))
  142. return p
  143. def multiply_fractions(root, args):
  144. """
  145. a / b * (c / d) -> ac / (bd)
  146. """
  147. scope, ab, cd = args
  148. a, b = ab
  149. c, d = cd
  150. scope.replace(ab, (a * c / (b * d)).negate(ab.negated + cd.negated))
  151. scope.remove(cd)
  152. return scope.as_nary_node()
  153. MESSAGES[multiply_fractions] = _('Multiply fractions {2} and {3}.')
  154. def multiply_with_fraction(root, args):
  155. """
  156. a / b * c and (eval(c) in Z or eval(a / b) not in Z) -> (ac) / b
  157. """
  158. scope, ab, c = args
  159. a, b = ab
  160. if scope.index(ab) - scope.index(c) < 0:
  161. replacement = a * c / b
  162. else:
  163. replacement = c * a / b
  164. scope.replace(ab, replacement.negate(ab.negated))
  165. scope.remove(c)
  166. return scope.as_nary_node()
  167. MESSAGES[multiply_with_fraction] = \
  168. _('Multiply {3} with the nominator of fraction {2}.')
  169. def match_divide_fractions(node):
  170. """
  171. Reduce divisions of fractions to a single fraction.
  172. Examples:
  173. a / b / c -> a / (bc)
  174. a / (b / c) -> ac / b
  175. Note that:
  176. a / b / (c / d) ->* ad / bd # chain test!
  177. """
  178. assert node.is_op(OP_DIV)
  179. nom, denom = node
  180. p = []
  181. if nom.is_op(OP_DIV):
  182. p.append(P(node, divide_fraction, tuple(nom) + (denom,)))
  183. if denom.is_op(OP_DIV):
  184. p.append(P(node, divide_by_fraction, (nom,) + tuple(denom)))
  185. return p
  186. def divide_fraction(root, args):
  187. """
  188. a / b / c -> a / (bc)
  189. """
  190. (a, b), c = root
  191. return (a / (b * c)).negate(root.negated)
  192. MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.')
  193. def divide_by_fraction(root, args):
  194. """
  195. a / (b / c) -> ac / b
  196. """
  197. a, bc = root
  198. b, c = bc
  199. return (a * c / b).negate(root.negated + bc.negated)
  200. MESSAGES[divide_by_fraction] = \
  201. _('Move {3} to nominator of fraction {1} / {2}.')
  202. def is_power_combination(pair):
  203. """
  204. Check if two nodes are powers that can be combined in a fraction, for
  205. example:
  206. a and a^2
  207. a^2 and a^2
  208. a^2 and a
  209. """
  210. a, b = pair
  211. if a.is_power():
  212. a = a[0]
  213. if b.is_power():
  214. b = b[0]
  215. return a == b
  216. def mult_scope(node):
  217. """
  218. Get the multiplication scope of a node that may or may no be a
  219. multiplication itself.
  220. """
  221. if node.is_op(OP_MUL):
  222. return Scope(node)
  223. return Scope(N(OP_MUL, node))
  224. def remove_from_mult_scope(scope, node):
  225. if len(scope) == 1:
  226. scope.replace(node, L(1))
  227. else:
  228. scope.remove(node)
  229. return scope.as_nary_node()
  230. def match_extract_fraction_terms(node):
  231. """
  232. Divide nominator and denominator by the same part. If the same root of a
  233. power appears in both nominator and denominator, also extract it so that it
  234. can be reduced to a single power by power division rules.
  235. Examples:
  236. ab / (ac) -> a / a * (c / e) # =>* c / e
  237. a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e) # -> a^(b - d)(c / e)
  238. ac / b and eval(c) not in Z and eval(a / b) in Z -> a / b * c
  239. """
  240. assert node.is_op(OP_DIV)
  241. n_scope, d_scope = map(mult_scope, node)
  242. p = []
  243. nominator, denominator = node
  244. # ac / b
  245. for n in ifilterfalse(evals_to_numeric, n_scope):
  246. a_scope = mult_scope(nominator)
  247. a = remove_from_mult_scope(a_scope, n)
  248. if evals_to_numeric(a / denominator):
  249. p.append(P(node, extract_nominator_term, (a, n)))
  250. if len(n_scope) == 1 and len(d_scope) == 1:
  251. return p
  252. # a ^ b * c / (a ^ d * e)
  253. for n, d in filter(is_power_combination, product(n_scope, d_scope)):
  254. p.append(P(node, extract_fraction_terms, (n_scope, d_scope, n, d)))
  255. return p
  256. def extract_nominator_term(root, args):
  257. """
  258. ac / b and eval(c) not in Z and eval(a / b) in Z -> a / b * c
  259. """
  260. a, c = args
  261. return a / root[1] * c
  262. MESSAGES[extract_nominator_term] = \
  263. _('Extract {2} from the nominator of fraction {0}.')
  264. def extract_fraction_terms(root, args):
  265. """
  266. ab / a -> a / a * (b / 1)
  267. a / (ba) -> a / a * (1 / b)
  268. a * c / (ae) -> a / a * (c / e)
  269. a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e)
  270. """
  271. n_scope, d_scope, n, d = args
  272. return n / d * (remove_from_mult_scope(n_scope, n) \
  273. / remove_from_mult_scope(d_scope, d))
  274. MESSAGES[extract_fraction_terms] = _('Extract {3} / {4} from fraction {0}.')