Skip to content
Snippets Groups Projects
Commit b0709085 authored by Taddeus Kroes's avatar Taddeus Kroes
Browse files

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
No related branches found
No related tags found
No related merge requests found
......@@ -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],
}
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))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment