fractions.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. from itertools import combinations, product
  2. from .utils import least_common_multiple, partition
  3. from ..node import ExpressionLeaf as L, Scope, OP_DIV, OP_ADD, OP_MUL, \
  4. OP_POW, nary_node, negate
  5. from ..possibilities import Possibility as P, MESSAGES
  6. from ..translate import _
  7. def match_constant_division(node):
  8. """
  9. a / 0 -> Division by zero
  10. a / 1 -> a
  11. 0 / a -> 0
  12. a / a -> 1
  13. """
  14. assert node.is_op(OP_DIV)
  15. p = []
  16. nominator, denominator = node
  17. # a / 0
  18. if denominator == 0:
  19. raise ZeroDivisionError('Division by zero: %s.' % node)
  20. # a / 1
  21. if denominator == 1:
  22. p.append(P(node, division_by_one, (nominator,)))
  23. # 0 / a
  24. if nominator == 0:
  25. p.append(P(node, division_of_zero, (denominator,)))
  26. # a / a
  27. if nominator == denominator:
  28. p.append(P(node, division_by_self, (nominator,)))
  29. return p
  30. def division_by_one(root, args):
  31. """
  32. a / 1 -> a
  33. """
  34. return args[0]
  35. MESSAGES[division_by_one] = _('Division by 1 yields the nominator.')
  36. def division_of_zero(root, args):
  37. """
  38. 0 / a -> 0
  39. """
  40. return L(0)
  41. MESSAGES[division_of_zero] = _('Division of 0 by {1} reduces to 0.')
  42. def division_by_self(root, args):
  43. """
  44. a / a -> 1
  45. """
  46. return L(1)
  47. MESSAGES[division_by_self] = _('Division of {1} by itself reduces to 1.')
  48. def match_add_constant_fractions(node):
  49. """
  50. 1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4 # Equalize denominators
  51. 2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4
  52. 2 / 4 + 3 / 4 -> 5 / 4 # Equal denominators, so nominators can
  53. # be added
  54. 2 / 4 - 3 / 4 -> -1 / 4
  55. 1 / 2 + 3 / 4 -> 4 / 8 + 6 / 8 # Equalize denominators by multiplying
  56. # them with eachother
  57. """
  58. assert node.is_op(OP_ADD)
  59. p = []
  60. scope = Scope(node)
  61. fractions = filter(lambda node: node.is_op(OP_DIV), scope)
  62. for a, b in combinations(fractions, 2):
  63. na, da = a
  64. nb, db = b
  65. if da == db:
  66. # Equal denominators, add nominators to create a single fraction
  67. p.append(P(node, add_nominators, (a, b)))
  68. elif da.is_numeric() and db.is_numeric():
  69. # Denominators are both numeric, rewrite both fractions to the
  70. # least common multiple of their denominators. Later, the
  71. # nominators will be added
  72. denom = least_common_multiple(da.value, db.value)
  73. p.append(P(node, equalize_denominators, (scope, a, b, denom)))
  74. # Also, add the (non-recommended) possibility to multiply the
  75. # denominators
  76. p.append(P(node, equalize_denominators, (scope, a, b,
  77. da.value * db.value)))
  78. return p
  79. def equalize_denominators(root, args):
  80. """
  81. 1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4
  82. 1 / 2 - 3 / 4 -> 2 / 4 - 3 / 4
  83. a / 2 + b / 4 -> 2a / 4 + b / 4
  84. """
  85. scope, denom = args[::3]
  86. for fraction in args[1:3]:
  87. n, d = fraction
  88. mult = denom / d.value
  89. if mult != 1:
  90. if n.is_numeric():
  91. nom = L(n.value * mult)
  92. else:
  93. nom = L(mult) * n
  94. scope.replace(fraction, negate(nom / L(d.value * mult),
  95. fraction.negated))
  96. return scope.as_nary_node()
  97. MESSAGES[equalize_denominators] = _('Equalize the denominators of divisions'
  98. ' {2} and {3} to {4}.')
  99. def add_nominators(root, args):
  100. """
  101. a / b + c / b -> (a + c) / b
  102. a / b - c / b -> (a - c) / b
  103. -(a / b) + c / b -> -((a + c) / b)
  104. -(a / b) - c / b -> (c - a) / -b
  105. """
  106. # TODO: is 'add' Appropriate when rewriting to "(a + (-c)) / b"?
  107. ab, cb = args
  108. a, b = ab
  109. scope = Scope(root)
  110. # Replace the left node with the new expression
  111. scope.replace(ab, (a + cb[0].negate(cb.negated)) / b)
  112. # Remove the right node
  113. scope.remove(cb)
  114. return scope.as_nary_node()
  115. # TODO: convert this to a lambda. Example: 22 / 77 - 28 / 77. the "-" is above
  116. # the "28/77" division.
  117. MESSAGES[add_nominators] = _('Add the nominators of {1} and {2}.')
  118. def match_expand_and_add_fractions(node):
  119. """
  120. a * b / c + d * b / c -> (a + d) * (b / c)
  121. a * b / c + (- d * b / c) -> (a + (-d)) * (b / c)
  122. """
  123. # TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
  124. assert node.is_op(OP_MUL)
  125. p = []
  126. return p
  127. def match_multiply_fractions(node):
  128. """
  129. a / b * (c / d) -> ac / (bd)
  130. a * (b / c) -> ab / c
  131. """
  132. assert node.is_op(OP_MUL)
  133. p = []
  134. scope = Scope(node)
  135. fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
  136. for ab, cd in combinations(fractions, 2):
  137. p.append(P(node, multiply_fractions, (scope, ab, cd)))
  138. for a, bc in product(others, fractions):
  139. p.append(P(node, multiply_with_fraction, (scope, a, bc)))
  140. return p
  141. def multiply_fractions(root, args):
  142. """
  143. a / b * (c / d) -> ac / (bd)
  144. """
  145. scope, ab, cd = args
  146. a, b = ab
  147. c, d = cd
  148. scope.replace(ab, a * c / (b * d))
  149. scope.remove(cd)
  150. return scope.as_nary_node()
  151. MESSAGES[multiply_fractions] = _('Multiply fractions {2} and {3}.')
  152. def multiply_with_fraction(root, args):
  153. """
  154. a * (b / c) -> ab / c
  155. """
  156. scope, a, bc = args
  157. b, c = bc
  158. scope.replace(a, a * b / c)
  159. scope.remove(bc)
  160. return scope.as_nary_node()
  161. MESSAGES[multiply_with_fraction] = _('Multiply {2} with fraction {3}.')
  162. def match_divide_fractions(node):
  163. """
  164. Reduce divisions of fractions to a single fraction.
  165. Examples:
  166. a / b / c -> a / (bc)
  167. a / (b / c) -> ac / b
  168. """
  169. assert node.is_op(OP_DIV)
  170. nom, denom = node
  171. p = []
  172. if nom.is_op(OP_DIV):
  173. p.append(P(node, divide_fraction, tuple(nom) + (denom,)))
  174. if denom.is_op(OP_DIV):
  175. p.append(P(node, divide_by_fraction, (nom,) + tuple(denom)))
  176. return p
  177. def divide_fraction(root, args):
  178. """
  179. a / b / c -> a / (bc)
  180. """
  181. a, b, c = args
  182. return a / (b * c)
  183. MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.')
  184. def divide_by_fraction(root, args):
  185. """
  186. a / (b / c) -> ac / b
  187. """
  188. a, b, c = args
  189. return a * c / b
  190. MESSAGES[divide_by_fraction] = \
  191. _('Move {3} to nominator of fraction {1} / {2}.')
  192. #def match_extract_divided_fractions(node):
  193. # """
  194. # Reduce divisions of fractions to a single fraction.
  195. #
  196. # Examples:
  197. # a / b / c -> a / bc
  198. # a / (b / c) -> ac / b
  199. # # IMPLICIT: a / b / (c / d) ->* ad / bd -> validation test!
  200. # """
  201. # assert node.is_op(OP_DIV)
  202. #
  203. # nom, denom = node
  204. # n_scope, d_scope = fraction_scopes(node)
  205. # is_division = lambda n: n.is_op(OP_DIV)
  206. # n_fractions, n_others = partition(is_division, n_scope)
  207. # d_fractions, d_others = partition(is_division, d_scope)
  208. #
  209. #
  210. # return []
  211. def fraction_scopes(node):
  212. """
  213. Get the multiplication scopes of the nominator and denominator of a
  214. fraction.
  215. """
  216. assert node.is_op(OP_DIV)
  217. nominator, denominator = node
  218. if nominator.is_op(OP_MUL):
  219. n_scope = list(Scope(nominator))
  220. else:
  221. n_scope = [nominator]
  222. if denominator.is_op(OP_MUL):
  223. d_scope = list(Scope(denominator))
  224. else:
  225. d_scope = [denominator]
  226. return n_scope, d_scope
  227. def match_equal_fraction_parts(node):
  228. """
  229. Divide nominator and denominator by the same part.
  230. Examples:
  231. ab / (ac) -> b / c
  232. ab / a -> b / 1
  233. a / (ab) -> 1 / b
  234. If the same root appears in both nominator and denominator, extrct it so
  235. that it can be reduced to a single power by power division rules.
  236. a ^ p * b / a ^ q -> a ^ p / a ^ q * b / 1
  237. a ^ p * b / a -> a ^ p / a * b / 1
  238. a * b / a ^ q -> a / a ^ q * b / 1
  239. """
  240. assert node.is_op(OP_DIV)
  241. nominator, denominator = node
  242. n_scope, d_scope = fraction_scopes(node)
  243. p = []
  244. if len(n_scope) == 1 and len(d_scope) == 1:
  245. return p
  246. # Look for matching parts in scopes
  247. for i, n in enumerate(n_scope):
  248. for j, d in enumerate(d_scope):
  249. if n.equals(d, ignore_negation=True):
  250. p.append(P(node, divide_fraction_parts,
  251. (negate(n, 0), n_scope, d_scope, i, j)))
  252. if n.is_op(OP_POW):
  253. a = n[0]
  254. if d == a or (d.is_op(OP_POW) and d[0] == a):
  255. # a ^ p * b / a -> a ^ p / a * b
  256. p.append(P(node, extract_divided_roots,
  257. (a, n_scope, d_scope, i, j)))
  258. elif d.is_op(OP_POW) and n == d[0]:
  259. # a * b / a ^ q -> a / a ^ q * b
  260. p.append(P(node, extract_divided_roots,
  261. (d[0], n_scope, d_scope, i, j)))
  262. return p
  263. def remove_from_scopes(n_scope, d_scope, i, j):
  264. a_n, a_d = n_scope[i], d_scope[j]
  265. del n_scope[i]
  266. del d_scope[j]
  267. if not n_scope:
  268. # Last element of nominator scope, replace by 1
  269. nom = L(1)
  270. elif len(n_scope) == 1:
  271. # Only one element left, no multiplication
  272. nom = n_scope[0]
  273. else:
  274. # Still a multiplication
  275. nom = nary_node('*', n_scope)
  276. if not d_scope:
  277. denom = L(1)
  278. elif len(n_scope) == 1:
  279. denom = d_scope[0]
  280. else:
  281. denom = nary_node('*', d_scope)
  282. return a_n, a_d, nom, denom
  283. def divide_fraction_parts(root, args):
  284. """
  285. Divide nominator and denominator by the same part.
  286. Examples:
  287. ab / (ac) -> b / c
  288. ab / a -> b / 1
  289. a / (ab) -> 1 / b
  290. -ab / a -> -b / 1
  291. """
  292. a, n_scope, d_scope, i, j = args
  293. n, d = root
  294. a_n, a_d, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
  295. # Move negation of removed part to nominator and denominator
  296. return nom.negate(n.negated + a_n.negated) \
  297. / denom.negate(d.negated + a_d.negated)
  298. MESSAGES[divide_fraction_parts] = \
  299. _('Divide nominator and denominator in {0} by {1}.')
  300. def extract_divided_roots(root, args):
  301. """
  302. a ^ p * b / a ^ q -> a ^ p / a ^ q * b / 1
  303. a ^ p * b / a -> a ^ p / a * b / 1
  304. a * b / a ^ q -> a / a ^ q * b / 1
  305. """
  306. a, n_scope, d_scope, i, j = args
  307. n, d = root
  308. ap, aq, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
  309. return ap / aq * nom.negate(n.negated) / denom.negate(d.negated)
  310. MESSAGES[extract_divided_roots] = \
  311. _('Extract the root {1} from nominator and denominator in {0}.')