Przeglądaj źródła

Applied most of the new fraction rules.

Taddeus Kroes 14 lat temu
rodzic
commit
08555af743
4 zmienionych plików z 92 dodań i 223 usunięć
  1. 2 2
      src/rules/__init__.py
  2. 22 115
      src/rules/fractions.py
  3. 2 3
      src/rules/groups.py
  4. 66 103
      tests/test_rules_fractions.py

+ 2 - 2
src/rules/__init__.py

@@ -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 = {
                  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,

+ 22 - 115
src/rules/fractions.py

@@ -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):
     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 \
-                (a == 1 and evals_to_numeric(b)):
+        #if (a.is_numeric() and c.is_numeric()) or \
+        #        (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):
     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):
 
 
 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}.')
 
 
 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):
     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):
     #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):
     # 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)))
-
-    #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)))
+            p.append(P(node, extract_fraction_terms, (n_scope, d_scope, n, d)))
 
 
     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):
     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):
-#    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}.')
+MESSAGES[extract_fraction_terms] = _('Extract {3} / {4} from fraction {0}.')

+ 2 - 3
src/rules/groups.py

@@ -1,5 +1,6 @@
 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):
             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 sub_node.is_numeric():
+                if evals_to_numeric(sub_node):
                     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:

+ 66 - 103
tests/test_rules_fractions.py

@@ -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):
         (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),
-                [P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
+        self.assertEqualPos(match_extract_fraction_terms(root),
+                [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),
-                [P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
+        self.assertEqualPos(match_extract_fraction_terms(root),
+                [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),
-                [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), c, c))])
+        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), b, b)),
+                 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), [])
-
-        (ap, b), aq = root = tree('a ^ p * b / a ^ q')
-        self.assertequalpos(match_equal_fraction_parts(root),
-                [p(root, extract_fraction_terms, (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_fraction_terms, (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_fraction_terms, (a, [ap, b], [a], 0, 0))])
-
-    #def test_match_equal_fraction_parts(self):
-    #    (a, b), (c, a) = root = tree('ab / (ca)')
-    #    self.assertEqualPos(match_equal_fraction_parts(root),
-    #            [P(root, divide_fraction_parts, (a, [a, b], [c, a], 0, 1))])
-
-    #    (a, b), a = root = tree('ab / a')
-    #    self.assertEqualPos(match_equal_fraction_parts(root),
-    #            [P(root, divide_fraction_parts, (a, [a, b], [a], 0, 0))])
-
-    #    a, (a, b) = root = tree('a / (ab)')
-    #    self.assertEqualPos(match_equal_fraction_parts(root),
-    #            [P(root, divide_fraction_parts, (a, [a], [a, b], 0, 0))])
-
-    #    root = tree('abc / (cba)')
-    #    ((a, b), c) = root[0]
-    #    s0, s1 = [a, b, c], [c, b, a]
-    #    self.assertEqualPos(match_equal_fraction_parts(root),
-    #            [P(root, divide_fraction_parts, (a, s0, s1, 0, 2)),
-    #             P(root, divide_fraction_parts, (b, s0, s1, 1, 1)),
-    #             P(root, divide_fraction_parts, (c, s0, s1, 2, 0))])
-
-    #    root = tree('-a / a')
-    #    self.assertEqualPos(match_equal_fraction_parts(root),
-    #            [P(root, divide_fraction_parts, (a, [-a], [a], 0, 0))])
-
-    #    (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)
+        self.assertEqualPos(match_extract_fraction_terms(root), [])
+
+        (ap, b), aq = n, d = root = tree('a ^ p * b / a ^ q')
+        self.assertEqualPos(match_extract_fraction_terms(root),
+                [P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, aq))])
+
+        (a, b), aq = n, d = root = tree('a * b / a ^ q')
+        self.assertEqualPos(match_extract_fraction_terms(root),
+                [P(root, extract_fraction_terms, (Scope(n), lscp(d), a, aq))])
+
+        (ap, b), a = n, d = root = tree('a ^ p * b / a')
+        self.assertEqualPos(match_extract_fraction_terms(root),
+                [P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, a))])
+
+    def test_extract_fraction_terms_basic(self):
+        root, expect = tree('ab / (ca), a / a * (b / c)')
+        n, d = root
+        self.assertEqual(extract_fraction_terms(root,
+                (Scope(n), Scope(d), n[0], d[1])), expect)
+
+    def test_extract_fraction_terms_leaf(self):
+        root, expect = tree('ba / a, a / a * (b / 1)')
+        n, d = root
+        self.assertEqual(extract_fraction_terms(root,
+                (Scope(n), Scope(N(OP_MUL, d)), n[1], d)), expect)
+
+        root, expect = tree('a / (ab), a / a * (1 / b)')
+        n, d = root
+        self.assertEqual(extract_fraction_terms(root,
+                (Scope(N(OP_MUL, n)), Scope(d), n, d[0])), expect)
+
+    def test_extract_fraction_terms_chain(self):
+        self.assertRewrite([
+            'a ^ 3 * 4 / (a ^ 2 * 5)',
+            'a ^ 3 / a ^ 2 * (4 / 5)',
+            'a ^ (3 - 2)(4 / 5)',
+            'a ^ 1 * (4 / 5)',
+            'a(4 / 5)',
+            # FIXME: '4 / 5 * a',
+        ])