Bladeren bron

Added basic rewrite rules for powers.

- Addition, subtraction, multiplication of powers are covered, see
  src/rules/powers for more details.
- All cases have been unit tested.
- Match functions have been added to the rules list in comments, to be
  uncommented when verified.
Taddeus Kroes 14 jaren geleden
bovenliggende
commit
b0709085ac
3 gewijzigde bestanden met toevoegingen van 332 en 0 verwijderingen
  1. 4 0
      src/rules/__init__.py
  2. 183 0
      src/rules/powers.py
  3. 145 0
      tests/test_rules_powers.py

+ 4 - 0
src/rules/__init__.py

@@ -5,4 +5,8 @@ from .poly import match_combine_polynomes, match_expand
 RULES = {
         OP_ADD: [match_combine_polynomes],
         OP_MUL: [match_expand],
+        #OP_MUL: [match_expand, match_add_exponents],
+        #OP_DIV: [match_subtract_exponents],
+        #OP_POW: [match_multiply_exponents, match_duplicate_exponent, \
+        #         match_remove_negative_exponent, match_exponent_to_root],
         }

+ 183 - 0
src/rules/powers.py

@@ -0,0 +1,183 @@
+from itertools import combinations
+
+from ..node import ExpressionNode as N, ExpressionLeaf as L, \
+                   OP_NEG, OP_MUL, OP_DIV, OP_POW
+from ..possibilities import Possibility as P
+from .utils import nary_node
+
+
+def match_add_exponents(node):
+    """
+    a^p * a^q  ->  a^(p + q)
+    """
+    assert node.is_op(OP_MUL)
+
+    p = []
+    powers = {}
+
+    for n in node.get_scope():
+        if n.is_op(OP_POW):
+            # Order powers by their roots, e.g. a^p and a^q are put in the same
+            # list because of the mutual 'a'
+            s = str(n[0])
+
+            if s in powers:
+                powers[s].append(n)
+            else:
+                powers[s] = [n]
+
+    for root, occurrences in powers.iteritems():
+        # If a root has multiple occurences, their exponents can be added to
+        # create a single power with that root
+        if len(occurrences) > 1:
+            for pair in combinations(occurrences, 2):
+                p.append(P(node, add_exponents, pair))
+
+    return p
+
+
+def match_subtract_exponents(node):
+    """
+    a^p / a^q  ->  a^(p - q)
+    a^p / a    ->  a^(p - 1)
+    a / a^q    ->  a^(1 - q)
+    """
+    assert node.is_op(OP_DIV)
+
+    left, right = node
+    left_pow, right_pow = left.is_op(OP_POW), right.is_op(OP_POW)
+
+    if left_pow and right_pow and left[0] == right[0]:
+        # A power is divided by a power with the same root
+        return [P(node, subtract_exponents, tuple(left) + (right[1],))]
+
+    if left_pow and left[0] == right:
+        # A power is divided by a its root
+        return [P(node, subtract_exponents, tuple(left) + (1,))]
+
+    if right_pow and left == right[0]:
+        # An identifier is divided by a power of itself
+        return [P(node, subtract_exponents, (left, 1, right[1]))]
+
+    return []
+
+
+def match_multiply_exponents(node):
+    """
+    (a^p)^q  ->  a^(pq)
+    """
+    assert node.is_op(OP_POW)
+
+    left, right = node
+
+    if left.is_op(OP_POW):
+        return [P(node, multiply_exponents, tuple(left) + (right,))]
+
+    return []
+
+
+def match_duplicate_exponent(node):
+    """
+    (ab)^p  ->  a^p * b^p
+    """
+    assert node.is_op(OP_POW)
+
+    left, right = node
+
+    if left.is_op(OP_MUL):
+        return [P(node, duplicate_exponent, tuple(left) + (right,))]
+
+    return []
+
+
+def match_remove_negative_exponent(node):
+    """
+    a^-p  ->  1 / a^p
+    """
+    assert node.is_op(OP_POW)
+
+    left, right = node
+
+    if right.is_op(OP_NEG):
+        return [P(node, remove_negative_exponent, (left, right[0]))]
+
+    return []
+
+
+def match_exponent_to_root(node):
+    """
+    a^(1 / m)  ->  sqrt(a, m)
+    a^(n / m)  ->  sqrt(a^n, m)
+    """
+    assert node.is_op(OP_POW)
+
+    left, right = node
+
+    if right.is_op(OP_DIV):
+        return [P(node, exponent_to_root, (left,) + tuple(right))]
+
+    return []
+
+
+def add_exponents(root, args):
+    """
+    a^p * a^q  ->  a^(p + q)
+    """
+    n0, n1 = args
+    a, p = n0
+    q = n1[1]
+    scope = root.get_scope()
+
+    # Replace the left node with the new expression
+    scope[scope.index(n0)] = a ** (p + q)
+
+    # Remove the right node
+    scope.remove(n1)
+
+    return nary_node('*', scope)
+
+
+def subtract_exponents(root, args):
+    """
+    a^p / a^q  ->  a^(p - q)
+    """
+    a, p, q = args
+
+    return a ** (p - q)
+
+
+def multiply_exponents(root, args):
+    """
+    (a^p)^q  ->  a^(pq)
+    """
+    a, p, q = args
+
+    return a ** (p * q)
+
+
+def duplicate_exponent(root, args):
+    """
+    (ab)^p  ->  a^p * b^p
+    """
+    a, b, p = args
+
+    return a ** p * b ** p
+
+
+def remove_negative_exponent(root, args):
+    """
+    a^-p  ->  1 / a^p
+    """
+    a, p = args
+
+    return L(1) / a ** p
+
+
+def exponent_to_root(root, args):
+    """
+    a^(1 / m)  ->  sqrt(a, m)
+    a^(n / m)  ->  sqrt(a^n, m)
+    """
+    a, n, m = args
+
+    return N('sqrt', a if n == 1 else a ** n, m)

+ 145 - 0
tests/test_rules_powers.py

@@ -0,0 +1,145 @@
+from src.rules.powers import match_add_exponents, add_exponents, \
+        match_subtract_exponents, subtract_exponents, \
+        match_multiply_exponents, multiply_exponents, \
+        match_duplicate_exponent, duplicate_exponent, \
+        match_remove_negative_exponent, remove_negative_exponent, \
+        match_exponent_to_root, exponent_to_root
+from src.possibilities import Possibility as P
+from src.node import ExpressionNode as N
+from tests.test_rules_poly import tree
+from tests.rulestestcase import RulesTestCase
+
+
+class TestRulesPowers(RulesTestCase):
+
+    def test_match_add_exponents_binary(self):
+        a, p, q = tree('a,p,q')
+        n0, n1 = root = a ** p * a ** q
+
+        possibilities = match_add_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, add_exponents, (n0, n1))])
+
+    def test_match_add_exponents_ternary(self):
+        a, p, q, r = tree('a,p,q,r')
+        (n0, n1), n2 = root = a ** p * a ** q * a ** r
+
+        possibilities = match_add_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, add_exponents, (n0, n1)),
+                 P(root, add_exponents, (n0, n2)),
+                 P(root, add_exponents, (n1, n2))])
+
+    def test_match_add_exponents_multiple_identifiers(self):
+        a, b, p, q = tree('a,b,p,q')
+        ((a0, b0), a1), b1 = root = a ** p * b ** p * a ** q * b ** q
+
+        possibilities = match_add_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, add_exponents, (a0, a1)),
+                 P(root, add_exponents, (b0, b1))])
+
+    def test_match_subtract_exponents_powers(self):
+        a, p, q = tree('a,p,q')
+        root = a ** p / a ** q
+
+        possibilities = match_subtract_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, subtract_exponents, (a, p, q))])
+
+    def test_match_subtract_power_id(self):
+        a, p = tree('a,p')
+        root = a ** p / a
+
+        possibilities = match_subtract_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, subtract_exponents, (a, p, 1))])
+
+    def test_match_subtract_id_power(self):
+        a, q = tree('a,q')
+        root = a / a ** q
+
+        possibilities = match_subtract_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, subtract_exponents, (a, 1, q))])
+
+    def test_match_multiply_exponents(self):
+        a, p, q = tree('a,p,q')
+        root = (a ** p) ** q
+
+        possibilities = match_multiply_exponents(root)
+        self.assertEqualPos(possibilities,
+                [P(root, multiply_exponents, (a, p, q))])
+
+    def test_match_duplicate_exponent(self):
+        a, b, p = tree('a,b,p')
+        root = (a * b) ** p
+
+        possibilities = match_duplicate_exponent(root)
+        self.assertEqualPos(possibilities,
+                [P(root, duplicate_exponent, (a, b, p))])
+
+    def test_match_remove_negative_exponent(self):
+        a, p = tree('a,p')
+        root = a ** -p
+
+        possibilities = match_remove_negative_exponent(root)
+        self.assertEqualPos(possibilities,
+                [P(root, remove_negative_exponent, (a, p))])
+
+    def test_match_exponent_to_root(self):
+        a, n, m = tree('a,n,m')
+        root = a ** (n / m)
+
+        possibilities = match_exponent_to_root(root)
+        self.assertEqualPos(possibilities,
+                [P(root, exponent_to_root, (a, n, m))])
+
+        n.value = 1
+        possibilities = match_exponent_to_root(root)
+        self.assertEqualPos(possibilities,
+                [P(root, exponent_to_root, (a, 1, m))])
+
+    def test_add_exponents(self):
+        a, p, q = tree('a,p,q')
+        n0, n1 = root = a ** p * a ** q
+
+        self.assertEqualNodes(add_exponents(root, (n0, n1)), a ** (p + q))
+
+    def test_subtract_exponents(self):
+        a, p, q = tree('a,p,q')
+        root = a ** p / a ** q
+
+        self.assertEqualNodes(subtract_exponents(root, (a, p, q)),
+                              a ** (p - q))
+
+    def test_multiply_exponents(self):
+        a, p, q = tree('a,p,q')
+        root = (a ** p) ** q
+
+        self.assertEqualNodes(multiply_exponents(root, (a, p, q)),
+                              a ** (p * q))
+
+    def test_duplicate_exponent(self):
+        a, b, p = tree('a,b,p')
+        root = (a * b) ** p
+
+        self.assertEqualNodes(duplicate_exponent(root, (a, b, p)),
+                              a ** p * b ** p)
+
+    def test_remove_negative_exponent(self):
+        a, p, l1 = tree('a,p,1')
+        root = a ** -p
+
+        self.assertEqualNodes(remove_negative_exponent(root, (a, p)),
+                              l1 / a ** p)
+
+    def test_exponent_to_root(self):
+        a, n, m, l1 = tree('a,n,m,1')
+        root = a ** (n / m)
+
+        self.assertEqualNodes(exponent_to_root(root, (a, n, m)),
+                              N('sqrt', a ** n, m))
+
+        self.assertEqualNodes(exponent_to_root(root, (a, l1, m)),
+                              N('sqrt', a, m))