فهرست منبع

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 سال پیش
والد
کامیت
b0709085ac
3فایلهای تغییر یافته به همراه332 افزوده شده و 0 حذف شده
  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))