Commit b0709085 authored by Taddeus Kroes's avatar Taddeus Kroes

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.
parent af621c8b
...@@ -5,4 +5,8 @@ from .poly import match_combine_polynomes, match_expand ...@@ -5,4 +5,8 @@ from .poly import match_combine_polynomes, match_expand
RULES = { RULES = {
OP_ADD: [match_combine_polynomes], OP_ADD: [match_combine_polynomes],
OP_MUL: [match_expand], 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],
} }
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)
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))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment