Parcourir la source

Improved elimination of terms in fraction (de)nominators.

Taddeus Kroes il y a 13 ans
Parent
commit
3d3792c372

+ 31 - 8
src/rules/fractions.py

@@ -288,7 +288,7 @@ MESSAGES[divide_by_fraction] = \
         _('Move {3} to nominator of fraction {1} / {2}.')
 
 
-def is_power_combination(pair):
+def is_power_combination(a, b):
     """
     Check if two nodes are powers that can be combined in a fraction, for
     example:
@@ -297,8 +297,6 @@ def is_power_combination(pair):
     a^2 and a^2
     a^2 and a
     """
-    a, b = pair
-
     if a.is_power():
         a = a[0]
 
@@ -358,8 +356,15 @@ def match_extract_fraction_terms(node):
         return p
 
     # a ^ b * c / (a ^ d * e)
-    for n, d in filter(is_power_combination, product(n_scope, d_scope)):
-        p.append(P(node, extract_fraction_terms, (n_scope, d_scope, n, d)))
+    for n, d in product(n_scope, d_scope):
+        if n == d:
+            handler = divide_fraction_by_term
+        elif is_power_combination(n, d):
+            handler = extract_fraction_terms
+        else:
+            continue
+
+        p.append(P(node, handler, (n_scope, d_scope, n, d)))
 
     return p
 
@@ -379,9 +384,6 @@ MESSAGES[extract_nominator_term] = \
 
 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)
     """
     n_scope, d_scope, n, d = args
@@ -394,6 +396,27 @@ def extract_fraction_terms(root, args):
 MESSAGES[extract_fraction_terms] = _('Extract {3} / {4} from fraction {0}.')
 
 
+def divide_fraction_by_term(root, args):
+    """
+    ab / a                   ->  b
+    a / (ba)                 ->  1 / b
+    a * c / (ae)             ->  c / e
+    """
+    n_scope, d_scope, n, d = args
+
+    nom = remove_from_mult_scope(n_scope, n)
+    d_scope.remove(d)
+
+    if not len(d_scope):
+        return negate(nom, root.negated)
+
+    return negate(nom / d_scope.as_nary_node(), root.negated)
+
+
+MESSAGES[divide_fraction_by_term] = \
+        _('Divide nominator and denominator od {0} by {2}.')
+
+
 def match_division_in_denominator(node):
     """
     a / (b / c + d)  ->  (ca) / (c(b / c + d))

+ 2 - 2
src/rules/precedences.py

@@ -8,7 +8,7 @@ from .derivatives import chain_rule
 from .negation import double_negation, negated_factor, negated_nominator, \
         negated_denominator, negated_zero
 from .factors import expand_double, expand_single
-from .fractions import multiply_with_fraction, extract_fraction_terms, \
+from .fractions import multiply_with_fraction, divide_fraction_by_term, \
         add_nominators
 from .integrals import factor_out_constant, integrate_variable_root
 from .powers import remove_power_of_one
@@ -68,7 +68,7 @@ RELATIVE = [
 
         # Prevent cycles that are caused by multiplication reductions when
         # splitting up fractions
-        (extract_fraction_terms, multiply_numerics),
+        (divide_fraction_by_term, multiply_numerics),
 
         # Prevent useless swapping when solving multiple equations
         (substitute_variable, swap_sides),

+ 0 - 2
tests/test_leiden_oefenopgave.py

@@ -173,8 +173,6 @@ class TestLeidenOefenopgave(TestCase):
         self.assertRewrite([
             '(7/3)(3/5)',
             '(7 * 3) / (3 * 5)',
-            '3 / 3 * 7 / 5',
-            '1 * 7 / 5',
             '7 / 5',
         ])
 

+ 14 - 11
tests/test_rules_fractions.py

@@ -4,7 +4,8 @@ from src.rules.fractions import match_constant_division, division_by_one, \
         multiply_fractions, multiply_with_fraction, match_divide_fractions, \
         divide_fraction, divide_by_fraction, match_extract_fraction_terms, \
         constant_to_fraction, extract_nominator_term, extract_fraction_terms, \
-        match_division_in_denominator, multiply_with_term
+        match_division_in_denominator, multiply_with_term, \
+        divide_fraction_by_term
 from src.node import ExpressionNode as N, Scope, OP_MUL
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -202,23 +203,23 @@ class TestRulesFractions(RulesTestCase):
         root, a, b, c = tree('(ab) / (ca), a, b, c')
         n, d = root
         self.assertEqualPos(match_extract_fraction_terms(root),
-                [P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
+                [P(root, divide_fraction_by_term, (Scope(n), Scope(d), a, a))])
 
         lscp = lambda l: Scope(N(OP_MUL, l))
 
         n, d = root = tree('(ab) / a')
         self.assertEqualPos(match_extract_fraction_terms(root),
-                [P(root, extract_fraction_terms, (Scope(n), lscp(d), a, a))])
+                [P(root, divide_fraction_by_term, (Scope(n), lscp(d), a, a))])
 
         n, d = root = tree('a / (ab)')
         self.assertEqualPos(match_extract_fraction_terms(root),
-                [P(root, extract_fraction_terms, (lscp(n), Scope(d), a, a))])
+                [P(root, divide_fraction_by_term, (lscp(n), Scope(d), a, a))])
 
         n, d = root = tree('(abc) / (cba)')
         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))])
+                [P(root, divide_fraction_by_term, (Scope(n), Scope(d), a, a)),
+                 P(root, divide_fraction_by_term, (Scope(n), Scope(d), b, b)),
+                 P(root, divide_fraction_by_term, (Scope(n), Scope(d), c, c))])
 
         root = tree('a / a')
         self.assertEqualPos(match_extract_fraction_terms(root), [])
@@ -249,7 +250,7 @@ class TestRulesFractions(RulesTestCase):
         n, d = root = tree('(2a) / 2')
         self.assertEqualPos(match_extract_fraction_terms(root),
                 [P(root, extract_nominator_term, (2, a)),
-                 P(root, extract_fraction_terms, (Scope(n), lscp(d), 2, 2))])
+                 P(root, divide_fraction_by_term, (Scope(n), lscp(d), 2, 2))])
 
     def test_extract_nominator_term(self):
         root, expect = tree('(2a) / 3, 2 / 3 * a')
@@ -286,6 +287,11 @@ class TestRulesFractions(RulesTestCase):
             # FIXME: '4 / 5 * a',
         ])
 
+    def test_divide_fraction_by_term(self):
+        (ab, a), expect = root = tree('(ab) / a, b')
+        args = Scope(ab), Scope(N(OP_MUL, a)), ab[0], a
+        self.assertEqual(divide_fraction_by_term(root, args), expect)
+
     def test_match_division_in_denominator(self):
         a, ((b, c), d) = root = tree('a / (b / c + d)')
         self.assertEqualPos(match_division_in_denominator(root),
@@ -312,8 +318,5 @@ class TestRulesFractions(RulesTestCase):
             '(ab) / (a + a(-b / a))',
             '(ab) / (a - ab / a)',
             '(ab) / (a - (ab) / a)',
-            '(ab) / (a - a / a * b / 1)',
-            '(ab) / (a - 1b / 1)',
-            '(ab) / (a - 1b)',
             '(ab) / (a - b)',
         ])