Commit 08555af7 authored by Taddeus Kroes's avatar Taddeus Kroes

Applied most of the new fraction rules.

parent 7a9605a7
...@@ -11,7 +11,7 @@ from .numerics import match_add_numerics, match_divide_numerics, \ ...@@ -11,7 +11,7 @@ from .numerics import match_add_numerics, match_divide_numerics, \
match_raise_numerics match_raise_numerics
from .fractions import match_constant_division, match_add_fractions, \ from .fractions import match_constant_division, match_add_fractions, \
match_multiply_fractions, match_divide_fractions, \ match_multiply_fractions, match_divide_fractions, \
match_equal_fraction_parts match_extract_fraction_terms
from .negation import match_negated_factor, match_negate_polynome, \ from .negation import match_negated_factor, match_negate_polynome, \
match_negated_division match_negated_division
from .sort import match_sort_multiplicants from .sort import match_sort_multiplicants
...@@ -39,7 +39,7 @@ RULES = { ...@@ -39,7 +39,7 @@ RULES = {
match_factor_in_multiplicant], match_factor_in_multiplicant],
OP_DIV: [match_subtract_exponents, match_divide_numerics, OP_DIV: [match_subtract_exponents, match_divide_numerics,
match_constant_division, match_divide_fractions, match_constant_division, match_divide_fractions,
match_negated_division, match_equal_fraction_parts], match_negated_division, match_extract_fraction_terms],
OP_POW: [match_multiply_exponents, match_duplicate_exponent, OP_POW: [match_multiply_exponents, match_duplicate_exponent,
match_raised_fraction, match_remove_negative_exponent, match_raised_fraction, match_remove_negative_exponent,
match_exponent_to_root, match_extend_exponent, match_exponent_to_root, match_extend_exponent,
......
...@@ -180,7 +180,7 @@ MESSAGES[constant_to_fraction] = \ ...@@ -180,7 +180,7 @@ MESSAGES[constant_to_fraction] = \
def match_multiply_fractions(node): def match_multiply_fractions(node):
""" """
a / b * (c / d) -> ac / (bd) a / b * (c / d) -> ac / (bd)
a / b * c and a, c in Z or (a = 1 and eval(b) not in Z) -> ac / b a / b * c and (c in Z or eval(a / b) not in Z) -> ac / b
""" """
assert node.is_op(OP_MUL) assert node.is_op(OP_MUL)
...@@ -195,8 +195,9 @@ def match_multiply_fractions(node): ...@@ -195,8 +195,9 @@ def match_multiply_fractions(node):
for ab, c in product(fractions, others): for ab, c in product(fractions, others):
a, b = ab a, b = ab
if (a.is_numeric() and c.is_numeric()) or \ #if (a.is_numeric() and c.is_numeric()) or \
(a == 1 and evals_to_numeric(b)): # (a == 1 and evals_to_numeric(b)):
if c.is_numeric() or not evals_to_numeric(ab):
p.append(P(node, multiply_with_fraction, (scope, ab, c))) p.append(P(node, multiply_with_fraction, (scope, ab, c)))
return p return p
...@@ -243,6 +244,9 @@ def match_divide_fractions(node): ...@@ -243,6 +244,9 @@ def match_divide_fractions(node):
Examples: Examples:
a / b / c -> a / (bc) a / b / c -> a / (bc)
a / (b / c) -> ac / b a / (b / c) -> ac / b
Note that:
a / b / (c / d) ->* ad / bd # chain test!
""" """
assert node.is_op(OP_DIV) assert node.is_op(OP_DIV)
...@@ -260,11 +264,11 @@ def match_divide_fractions(node): ...@@ -260,11 +264,11 @@ def match_divide_fractions(node):
def divide_fraction(root, args): def divide_fraction(root, args):
""" """
a / b / c -> a / (bc) a / b / c -> a / (bc)
""" """
a, b, c = args (a, b), c = root
return a / (b * c) return (a / (b * c)).negate(root.negated)
MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.') MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.')
...@@ -272,38 +276,18 @@ MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.') ...@@ -272,38 +276,18 @@ MESSAGES[divide_fraction] = _('Move {3} to denominator of fraction {1} / {2}.')
def divide_by_fraction(root, args): def divide_by_fraction(root, args):
""" """
a / (b / c) -> ac / b a / (b / c) -> ac / b
""" """
a, b, c = args a, bc = root
b, c = bc
return a * c / b return (a * c / b).negate(root.negated + bc.negated)
MESSAGES[divide_by_fraction] = \ MESSAGES[divide_by_fraction] = \
_('Move {3} to nominator of fraction {1} / {2}.') _('Move {3} to nominator of fraction {1} / {2}.')
#def match_extract_divided_fractions(node):
# """
# Reduce divisions of fractions to a single fraction.
#
# Examples:
# a / b / c -> a / bc
# a / (b / c) -> ac / b
# # IMPLICIT: a / b / (c / d) ->* ad / bd -> validation test!
# """
# assert node.is_op(OP_DIV)
#
# nom, denom = node
# n_scope, d_scope = fraction_scopes(node)
# is_division = lambda n: n.is_op(OP_DIV)
# n_fractions, n_others = partition(is_division, n_scope)
# d_fractions, d_others = partition(is_division, d_scope)
#
#
# return []
def fraction_scopes(node): def fraction_scopes(node):
""" """
Get the multiplication scopes of the nominator and denominator of a Get the multiplication scopes of the nominator and denominator of a
...@@ -344,7 +328,7 @@ def is_power_combination(a, b): ...@@ -344,7 +328,7 @@ def is_power_combination(a, b):
return a == b return a == b
def match_equal_fraction_parts(node): def match_extract_fraction_terms(node):
""" """
Divide nominator and denominator by the same part. Divide nominator and denominator by the same part.
...@@ -357,6 +341,7 @@ def match_equal_fraction_parts(node): ...@@ -357,6 +341,7 @@ def match_equal_fraction_parts(node):
#a ^ p * b / a -> a ^ p / a * b / 1 #a ^ p * b / a -> a ^ p / a * b / 1
#a * b / a ^ q -> a / a ^ q * b / 1 #a * b / a ^ q -> a / a ^ q * b / 1
""" """
# TODO: ac / b -> a / b * c
assert node.is_op(OP_DIV) assert node.is_op(OP_DIV)
nominator, denominator = node nominator, denominator = node
...@@ -369,31 +354,16 @@ def match_equal_fraction_parts(node): ...@@ -369,31 +354,16 @@ def match_equal_fraction_parts(node):
# Look for matching parts in scopes # Look for matching parts in scopes
for n, d in product(n_scope, d_scope): for n, d in product(n_scope, d_scope):
if is_power_combination(n, d): if is_power_combination(n, d):
p.append(P(N, extract_fraction_terms, (n_scope, d_scope, n, d))) p.append(P(node, extract_fraction_terms, (n_scope, d_scope, n, d)))
#for i, n in enumerate(n_scope):
# for j, d in enumerate(d_scope):
# if n.equals(d, ignore_negation=True):
# p.append(P(node, divide_fraction_parts,
# (negate(n, 0), n_scope, d_scope, i, j)))
# if n.is_op(OP_POW):
# a = n[0]
# if d == a or (d.is_op(OP_POW) and d[0] == a):
# # a ^ p * b / a -> a ^ p / a * b
# p.append(P(node, extract_divided_roots,
# (a, n_scope, d_scope, i, j)))
# elif d.is_op(OP_POW) and n == d[0]:
# # a * b / a ^ q -> a / a ^ q * b
# p.append(P(node, extract_divided_roots,
# (d[0], n_scope, d_scope, i, j)))
return p return p
def extract_fraction_terms(root, args): def extract_fraction_terms(root, args):
""" """
ab / a -> a / a * (b / 1)
a / (ba) -> a / a * (1 / b)
a * c / (ae) -> a / a * (c / e)
a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e) a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e)
""" """
n_scope, d_scope, n, d = args n_scope, d_scope, n, d = args
...@@ -406,72 +376,9 @@ def extract_fraction_terms(root, args): ...@@ -406,72 +376,9 @@ def extract_fraction_terms(root, args):
if len(d_scope) == 1: if len(d_scope) == 1:
d_scope.replace(d, L(1)) d_scope.replace(d, L(1))
else: else:
d_scope.remove(n) d_scope.remove(d)
return n / d * (n_scope.as_nary_node() / d_scope.as_nary_node()) return n / d * (n_scope.as_nary_node() / d_scope.as_nary_node())
#def remove_from_scopes(n_scope, d_scope, i, j): MESSAGES[extract_fraction_terms] = _('Extract {3} / {4} from fraction {0}.')
# a_n, a_d = n_scope[i], d_scope[j]
#
# del n_scope[i]
# del d_scope[j]
#
# if not n_scope:
# # Last element of nominator scope, replace by 1
# nom = L(1)
# elif len(n_scope) == 1:
# # Only one element left, no multiplication
# nom = n_scope[0]
# else:
# # Still a multiplication
# nom = nary_node('*', n_scope)
#
# if not d_scope:
# denom = L(1)
# elif len(n_scope) == 1:
# denom = d_scope[0]
# else:
# denom = nary_node('*', d_scope)
#
# return a_n, a_d, nom, denom
#
#
#def divide_fraction_parts(root, args):
# """
# Divide nominator and denominator by the same part.
#
# Examples:
# ab / (ac) -> b / c
# ab / a -> b / 1
# a / (ab) -> 1 / b
# -ab / a -> -b / 1
# """
# a, n_scope, d_scope, i, j = args
# n, d = root
# a_n, a_d, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
#
# # Move negation of removed part to nominator and denominator
# return nom.negate(n.negated + a_n.negated) \
# / denom.negate(d.negated + a_d.negated)
#
#
#MESSAGES[divide_fraction_parts] = \
# _('Divide nominator and denominator in {0} by {1}.')
#
#
#def extract_divided_roots(root, args):
# """
# a ^ p * b / a ^ q -> a ^ p / a ^ q * b / 1
# a ^ p * b / a -> a ^ p / a * b / 1
# a * b / a ^ q -> a / a ^ q * b / 1
# """
# a, n_scope, d_scope, i, j = args
# n, d = root
# ap, aq, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
#
# return ap / aq * nom.negate(n.negated) / denom.negate(d.negated)
#
#
#MESSAGES[extract_divided_roots] = \
# _('Extract the root {1} from nominator and denominator in {0}.')
from itertools import combinations from itertools import combinations
from .utils import evals_to_numeric
from ..node import ExpressionLeaf as Leaf, Scope, OP_ADD, OP_MUL, nary_node, \ from ..node import ExpressionLeaf as Leaf, Scope, OP_ADD, OP_MUL, nary_node, \
negate negate
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
...@@ -35,9 +36,7 @@ def match_combine_groups(node): ...@@ -35,9 +36,7 @@ def match_combine_groups(node):
l = len(n_scope) l = len(n_scope)
for i, sub_node in enumerate(n_scope): for i, sub_node in enumerate(n_scope):
# TODO: use utitlity function evals_to_numeric if evals_to_numeric(sub_node):
#if evals_to_numeric(sub_node):
if sub_node.is_numeric():
others = [n_scope[j] for j in range(i) + range(i + 1, l)] others = [n_scope[j] for j in range(i) + range(i + 1, l)]
if len(others) == 1: if len(others) == 1:
......
...@@ -2,9 +2,9 @@ from src.rules.fractions import match_constant_division, division_by_one, \ ...@@ -2,9 +2,9 @@ from src.rules.fractions import match_constant_division, division_by_one, \
division_of_zero, division_by_self, match_add_fractions, \ division_of_zero, division_by_self, match_add_fractions, \
equalize_denominators, add_nominators, match_multiply_fractions, \ equalize_denominators, add_nominators, match_multiply_fractions, \
multiply_fractions, multiply_with_fraction, match_divide_fractions, \ multiply_fractions, multiply_with_fraction, match_divide_fractions, \
divide_fraction, divide_by_fraction, match_equal_fraction_parts, \ divide_fraction, divide_by_fraction, match_extract_fraction_terms, \
constant_to_fraction, extract_fraction_terms constant_to_fraction, extract_fraction_terms
from src.node import Scope from src.node import ExpressionNode as N, Scope, OP_MUL
from src.possibilities import Possibility as P from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree from tests.rulestestcase import RulesTestCase, tree
...@@ -181,119 +181,82 @@ class TestRulesFractions(RulesTestCase): ...@@ -181,119 +181,82 @@ class TestRulesFractions(RulesTestCase):
(a, b), c = root = tree('a / b / c') (a, b), c = root = tree('a / b / c')
self.assertEqual(divide_fraction(root, (a, b, c)), a / (b * c)) self.assertEqual(divide_fraction(root, (a, b, c)), a / (b * c))
(a, b), c = root = tree('-a / b / c')
self.assertEqual(divide_fraction(root, (a, b, c)), -(a / (b * c)))
root = tree('a / b / -c')
self.assertEqual(divide_fraction(root, (a, b, c)), a / (b * -c))
def test_divide_by_fraction(self): def test_divide_by_fraction(self):
a, (b, c) = root = tree('a / (b / c)') a, (b, c) = root = tree('a / (b / c)')
self.assertEqual(divide_by_fraction(root, (a, b, c)), a * c / b) self.assertEqual(divide_by_fraction(root, (a, b, c)), a * c / b)
def test_match_equal_fraction_parts(self): a, (b, c) = root = tree('-a / (b / c)')
self.assertEqual(divide_by_fraction(root, (a, b, c)), -(a * c / b))
root = tree('a / -(b / c)')
self.assertEqual(divide_by_fraction(root, (a, b, c)), -(a * c / b))
def test_match_extract_fraction_terms(self):
root, a, b, c = tree('ab / (ca), a, b, c') root, a, b, c = tree('ab / (ca), a, b, c')
n, d = root n, d = root
self.assertEqualPos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))]) [P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
lscp = lambda l: Scope(N(OP_MUL, l))
n, d = root = tree('ab / a') n, d = root = tree('ab / a')
self.assertEqualPos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))]) [P(root, extract_fraction_terms, (Scope(n), lscp(d), a, a))])
n, d = root = tree('a / (ab)') n, d = root = tree('a / (ab)')
self.assertEqualPos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))]) [P(root, extract_fraction_terms, (lscp(n), Scope(d), a, a))])
n, d = root = tree('abc / (cba)') n, d = root = tree('abc / (cba)')
self.assertEqualPos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), scope(d), a, a)), [P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a)),
P(root, extract_fraction_terms, (Scope(n), scope(d), b, b)), P(root, extract_fraction_terms, (Scope(n), Scope(d), b, b)),
P(root, extract_fraction_terms, (Scope(n), scope(d), c, c))]) P(root, extract_fraction_terms, (Scope(n), Scope(d), c, c))])
root = tree('a / a') root = tree('a / a')
self.assertEqualPos(match_equal_fraction_parts(root), []) self.assertEqualPos(match_extract_fraction_terms(root), [])
(ap, b), aq = root = tree('a ^ p * b / a ^ q') (ap, b), aq = n, d = root = tree('a ^ p * b / a ^ q')
self.assertequalpos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[p(root, extract_fraction_terms, (a, [ap, b], [aq], 0, 0))]) [P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, aq))])
(a, b), aq = root = tree('a * b / a ^ q') (a, b), aq = n, d = root = tree('a * b / a ^ q')
self.assertequalpos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[p(root, extract_fraction_terms, (a, [a, b], [aq], 0, 0))]) [P(root, extract_fraction_terms, (Scope(n), lscp(d), a, aq))])
(ap, b), a = root = tree('a ^ p * b / a') (ap, b), a = n, d = root = tree('a ^ p * b / a')
self.assertequalpos(match_equal_fraction_parts(root), self.assertEqualPos(match_extract_fraction_terms(root),
[p(root, extract_fraction_terms, (a, [ap, b], [a], 0, 0))]) [P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, a))])
#def test_match_equal_fraction_parts(self): def test_extract_fraction_terms_basic(self):
# (a, b), (c, a) = root = tree('ab / (ca)') root, expect = tree('ab / (ca), a / a * (b / c)')
# self.assertEqualPos(match_equal_fraction_parts(root), n, d = root
# [P(root, divide_fraction_parts, (a, [a, b], [c, a], 0, 1))]) self.assertEqual(extract_fraction_terms(root,
(Scope(n), Scope(d), n[0], d[1])), expect)
# (a, b), a = root = tree('ab / a')
# self.assertEqualPos(match_equal_fraction_parts(root), def test_extract_fraction_terms_leaf(self):
# [P(root, divide_fraction_parts, (a, [a, b], [a], 0, 0))]) root, expect = tree('ba / a, a / a * (b / 1)')
n, d = root
# a, (a, b) = root = tree('a / (ab)') self.assertEqual(extract_fraction_terms(root,
# self.assertEqualPos(match_equal_fraction_parts(root), (Scope(n), Scope(N(OP_MUL, d)), n[1], d)), expect)
# [P(root, divide_fraction_parts, (a, [a], [a, b], 0, 0))])
root, expect = tree('a / (ab), a / a * (1 / b)')
# root = tree('abc / (cba)') n, d = root
# ((a, b), c) = root[0] self.assertEqual(extract_fraction_terms(root,
# s0, s1 = [a, b, c], [c, b, a] (Scope(N(OP_MUL, n)), Scope(d), n, d[0])), expect)
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, s0, s1, 0, 2)), def test_extract_fraction_terms_chain(self):
# P(root, divide_fraction_parts, (b, s0, s1, 1, 1)), self.assertRewrite([
# P(root, divide_fraction_parts, (c, s0, s1, 2, 0))]) 'a ^ 3 * 4 / (a ^ 2 * 5)',
'a ^ 3 / a ^ 2 * (4 / 5)',
# root = tree('-a / a') 'a ^ (3 - 2)(4 / 5)',
# self.assertEqualPos(match_equal_fraction_parts(root), 'a ^ 1 * (4 / 5)',
# [P(root, divide_fraction_parts, (a, [-a], [a], 0, 0))]) 'a(4 / 5)',
# FIXME: '4 / 5 * a',
# (ap, b), aq = root = tree('a ^ p * b / a ^ q') ])
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [ap, b], [aq], 0, 0))])
# (a, b), aq = root = tree('a * b / a ^ q')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [a, b], [aq], 0, 0))])
# (ap, b), a = root = tree('a ^ p * b / a')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [ap, b], [a], 0, 0))])
#def test_divide_fraction_parts(self):
# (a, b), (c, a) = root = tree('ab / (ca)')
# result = divide_fraction_parts(root, (a, [a, b], [c, a], 0, 1))
# self.assertEqual(result, b / c)
# (a, b), a = root = tree('ab / a')
# result = divide_fraction_parts(root, (a, [a, b], [a], 0, 0))
# self.assertEqual(result, b / 1)
# root, l1 = tree('a / (ab), 1')
# a, (a, b) = root
# result = divide_fraction_parts(root, (a, [a], [a, b], 0, 0))
# self.assertEqual(result, l1 / b)
# root = tree('abc / (cba)')
# ((a, b), c) = root[0]
# result = divide_fraction_parts(root, (a, [a, b, c], [c, b, a], 0, 2))
# self.assertEqual(result, b * c / (c * b))
# result = divide_fraction_parts(root, (b, [a, b, c], [c, b, a], 1, 1))
# self.assertEqual(result, a * c / (c * a))
# result = divide_fraction_parts(root, (c, [a, b, c], [c, b, a], 2, 0))
# self.assertEqual(result, a * b / (b * a))
# (a, b), a = root = tree('-ab / a')
# result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
# self.assertEqual(result, -b / 1)
#def test_extract_divided_roots(self):
# r, a = tree('a ^ p * b / a ^ q, a')
# ((a, p), b), (a, q) = (ap, b), aq = r
# self.assertEqual(extract_divided_roots(r, (a, [ap, b], [aq], 0, 0)),
# a ** p / a ** q * b / 1)
# r = tree('a * b / a ^ q, a')
# self.assertEqual(extract_divided_roots(r, (a, [a, b], [aq], 0, 0)),
# a / a ** q * b / 1)
# r = tree('a ^ p * b / a, a')
# self.assertEqual(extract_divided_roots(r, (a, [ap, b], [a], 0, 0)),
# a ** p / a * b / 1)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment