Parcourir la source

Added rule 'a/b + c/d -> ad/(bd) + bc/(bd) -> (ad + bc)/(bd)' and some helper rules.

Taddeus Kroes il y a 13 ans
Parent
commit
5ac16510ff
5 fichiers modifiés avec 118 ajouts et 23 suppressions
  1. 0 3
      TODO
  2. 1 1
      src/node.py
  3. 6 3
      src/rules/__init__.py
  4. 71 1
      src/rules/fractions.py
  5. 40 15
      tests/test_rules_fractions.py

+ 0 - 3
TODO

@@ -26,9 +26,6 @@
 
 - "sin^2 x" is supported by parser, but not yet by line printer.
 
-- 1/x + 2/(x+1) -> (3x + 1) / (x(x + 1)):
-  a/b + c/d  ->  ad/(bd) + bc/(bd)  ->  (ad + bc)/(bd)
-
 #- To work 100% correctly, POW and SUB (^ and _) should have a precedence equal
 #  to INTEGRAL when used for integral bounds.
 

+ 1 - 1
src/node.py

@@ -659,7 +659,7 @@ def negate(node, n=1, clone=False):
     Negate the given node n times. If clone is set to true, return a new node
     so that the original node is not altered.
     """
-    assert n >= 0
+    #assert n >= 0
 
     if clone:
         node = node.clone()

+ 6 - 3
src/rules/__init__.py

@@ -25,7 +25,8 @@ from .numerics import match_add_numerics, match_divide_numerics, \
         match_multiply_numerics, match_raise_numerics
 from .fractions import match_constant_division, match_add_fractions, \
         match_multiply_fractions, match_divide_fractions, \
-        match_extract_fraction_terms, match_division_in_denominator
+        match_extract_fraction_terms, match_division_in_denominator, \
+        match_combine_fractions, match_remove_division_negation
 from .negation import match_negated_factor, match_negate_polynome, \
         match_negated_division
 from .sort import match_sort_polynome, match_sort_monomial
@@ -50,14 +51,16 @@ from .sqrt import match_reduce_sqrt
 RULES = {
         OP_ADD: [match_add_numerics, match_add_fractions,
                  match_combine_groups, match_add_quadrants,
-                 match_add_logarithms, match_sort_polynome],
+                 match_add_logarithms, match_sort_polynome,
+                 match_combine_fractions],
         OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
                  match_negated_factor, match_multiply_fractions,
                  match_factor_in_multiplicant, match_sort_monomial],
         OP_DIV: [match_subtract_exponents, match_divide_numerics,
                  match_constant_division, match_divide_fractions,
                  match_negated_division, match_extract_fraction_terms,
-                 match_division_in_denominator],
+                 match_division_in_denominator,
+                 match_remove_division_negation],
         OP_POW: [match_multiply_exponents, match_duplicate_exponent,
                  match_raised_fraction, match_remove_negative_child,
                  match_exponent_to_root, match_extend_exponent,

+ 71 - 1
src/rules/fractions.py

@@ -20,6 +20,7 @@ from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DIV, \
         OP_ADD, OP_MUL, negate
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
+from .negation import negate_polynome
 
 
 def match_constant_division(node):
@@ -300,7 +301,7 @@ def divide_by_fraction(root, args):
 
 
 MESSAGES[divide_by_fraction] = \
-        _('Move {3} to nominator of fraction `{1} / {2}`.')
+        _('Move {3} to the nominator of fraction `{1} / {2}`.')
 
 
 def is_power_combination(a, b):
@@ -464,3 +465,72 @@ def multiply_with_term(root, args):
 
 MESSAGES[multiply_with_term] = \
         _('Multiply nominator and denominator of {0} with {1}.')
+
+
+def match_combine_fractions(node):
+    """
+    a/b + c/d  ->  ad/(bd) + bc/(bd)  # ->  (ad + bc)/(bd)
+    """
+    assert node.is_op(OP_ADD)
+
+    scope = Scope(node)
+    fractions = [n for n in scope if n.is_op(OP_DIV)]
+    p = []
+
+    for left, right in combinations(fractions, 2):
+        p.append(P(node, combine_fractions, (scope, left, right)))
+
+    return p
+
+
+def combine_fractions(root, args):
+    """
+    a/b + c/d  ->  ad/(bd) + bc/(bd)
+    """
+    scope, ab, cd = args
+    (a, b), (c, d) = ab, cd
+    a = negate(a, ab.negated)
+    d = negate(d, cd.negated)
+
+    scope.replace(ab, a * d / (b * d) + b * c / (b * d))
+    scope.remove(cd)
+
+    return scope.as_nary_node()
+
+
+MESSAGES[combine_fractions] = _('Combine fraction {2} and {3}.')
+
+
+def match_remove_division_negation(node):
+    """
+    -a / (-b + c)  -> a / (--b - c)
+    """
+    assert node.is_op(OP_DIV)
+    nom, denom = node
+
+    if node.negated:
+        if nom.is_op(OP_ADD) and any([n.negated for n in Scope(nom)]):
+            return [P(node, remove_division_negation, (True, nom))]
+
+        if denom.is_op(OP_ADD) and any([n.negated for n in Scope(denom)]):
+            return [P(node, remove_division_negation, (False, denom))]
+
+    return []
+
+
+def remove_division_negation(root, args):
+    """
+    -a / (-b + c)  -> a / (--b - c)
+    """
+    nom, denom = root
+
+    if args[0]:
+        nom = negate_polynome(nom, ())
+    else:
+        denom = negate_polynome(denom, ())
+
+    return negate(nom / denom, root.negated - 1)
+
+
+MESSAGES[remove_division_negation] = \
+        _('Move negation from fraction {0} to polynome {2}.')

+ 40 - 15
tests/test_rules_fractions.py

@@ -19,7 +19,8 @@ from src.rules.fractions import match_constant_division, division_by_one, \
         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, \
-        divide_fraction_by_term
+        divide_fraction_by_term, match_combine_fractions, combine_fractions, \
+        match_remove_division_negation, remove_division_negation
 from src.node import ExpressionNode as N, Scope, OP_MUL
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -318,19 +319,43 @@ class TestRulesFractions(RulesTestCase):
     def test_multiply_with_term_chain(self):
         self.assertRewrite([
             '1 / (1 / b - 1 / a)',
-            '(b * 1) / (b(1 / b - 1 / a))',
-            'b / (b(1 / b - 1 / a))',
-            'b / (b * 1 / b + b * -1 / a)',
-            'b / (b * 1 / b - b * 1 / a)',
-            'b / ((b * 1) / b - b * 1 / a)',
-            'b / (b / b - b * 1 / a)',
-            'b / (1 - b * 1 / a)',
-            'b / (1 - (b * 1) / a)',
-            'b / (1 - b / a)',
-            '(ab) / (a(1 - b / a))',
-            '(ab) / (a * 1 + a * -b / a)',
-            '(ab) / (a + a * -b / a)',
-            '(ab) / (a - a b / a)',
-            '(ab) / (a - (ab) / a)',
+            '1 / ((1 * -a) / (b * -a) + (b * 1) / (b * -a))',
+            '1 / ((1 * -a + b * 1) / (b * -a))',
+            '1 / ((-a + b * 1) / (b * -a))',
+            '1 / ((-a + b) / (b * -a))',
+            '1 / ((-a + b) / (-ba))',
+            '1 / (-(-a + b) / (ba))',
+            '1 / ((--a - b) / (ba))',
+            '1 / ((a - b) / (ba))',
+            '(1ba) / (a - b)',
+            '(ba) / (a - b)',
             '(ab) / (a - b)',
         ])
+
+    def test_match_combine_fractions(self):
+        ab, cd = root = tree('a / b + c / d')
+        self.assertEqualPos(match_combine_fractions(root),
+                [P(root, combine_fractions, (Scope(root), ab, cd))])
+
+    def test_combine_fractions(self):
+        (a, b), (c, d) = ab, cd = root = tree('a / b + c / d')
+        self.assertEqual(combine_fractions(root, (Scope(root), ab, cd)),
+                         a * d / (b * d) + b * c / (b * d))
+
+    def test_match_remove_division_negation(self):
+        root = tree('-(-a + b) / c')
+        self.assertEqualPos(match_remove_division_negation(root),
+                [P(root, remove_division_negation, (True, root[0]))])
+
+        root = tree('-a / (-b + c)')
+        self.assertEqualPos(match_remove_division_negation(root),
+                [P(root, remove_division_negation, (False, root[1]))])
+
+    def test_remove_division_negation(self):
+        (a, b), c = root = tree('-(-a + b) / c')
+        self.assertEqual(remove_division_negation(root, (True, root[0])),
+                         (-a - b) / c)
+
+        a, (b, c) = root = tree('-a / (-b + c)')
+        self.assertEqual(remove_division_negation(root, (False, root[1])),
+                         +a / (-b - c))