Commit 42662f45 authored by Taddeus Kroes's avatar Taddeus Kroes

Changed negation precedence to that of subtraction and modified rules+tests correspondingly.

parent a9858f37
graph_drawing @ ade1a950
Subproject commit 70714ec0339bb043dcd92a9f233d83347ac0ae95
Subproject commit ade1a95046e5539a4f11535892061e05b7c23b95
......@@ -359,7 +359,7 @@ class ExpressionNode(Node, ExpressionBase):
self.nodes[self.nodes.index(old_child)] = new_child
def graph(self): # pragma: nocover
return generate_graph(self)
return generate_graph(negation_to_node(self))
def extract_polynome_properties(self):
"""
......@@ -602,7 +602,7 @@ def get_scope(node):
scope = []
for child in node:
if child.is_op(node.op):
if child.is_op(node.op) and not child.negated:
scope += get_scope(child)
else:
scope.append(child)
......@@ -698,3 +698,21 @@ def eq(left, right):
Create an equality operator node.
"""
return ExpressionNode(OP_EQ, left, right)
def negation_to_node(node):
"""
Recursively replace negation flags inside a node by explicit unary negation
nodes.
"""
if node.negated:
negations = node.negated
node = negate(node, 0)
for i in range(negations):
node = ExpressionNode('-', node)
if node.is_leaf:
return node
return ExpressionNode(node.op, *map(negation_to_node, node))
......@@ -18,7 +18,7 @@ from node import ExpressionBase, ExpressionNode as Node, \
ExpressionLeaf as Leaf, OP_MAP, OP_DER, TOKEN_MAP, TYPE_OPERATOR, \
OP_COMMA, OP_NEG, OP_MUL, OP_DIV, OP_POW, OP_LOG, OP_ADD, Scope, E, \
DEFAULT_LOGARITHM_BASE, OP_VALUE_MAP, SPECIAL_TOKENS, OP_INT, \
OP_INT_INDEF, OP_ABS
OP_INT_INDEF, OP_ABS, OP_NEG, negation_to_node
from rules import RULES
from rules.utils import find_variable
from strategy import pick_suggestion
......@@ -90,10 +90,10 @@ class Parser(BisonParser):
('left', ('OR', )),
('left', ('AND', )),
('left', ('EQ', )),
('left', ('MINUS', 'PLUS')),
('left', ('MINUS', 'PLUS', 'NEG')),
('left', ('TIMES', 'DIVIDE')),
('right', ('FUNCTION', )),
('left', ('NEG', )),
#('left', ('NEG', )),
('right', ('POW', )),
('left', ('SUB', )),
('right', ('FUNCTION_LPAREN', )),
......@@ -245,13 +245,12 @@ class Parser(BisonParser):
if not retval.negated and retval.type != TYPE_OPERATOR:
return retval
if retval.type == TYPE_OPERATOR and retval.op in RULES:
handlers = RULES[retval.op]
else:
handlers = []
if retval.negated:
handlers = RULES[OP_NEG]
elif retval.type == TYPE_OPERATOR and retval.op in RULES:
handlers = RULES[retval.op]
else:
return retval
for handler in handlers:
possibilities = handler(retval)
......@@ -360,7 +359,7 @@ class Parser(BisonParser):
"""
if option == 0:
print generate_graph(values[1])
print generate_graph(negation_to_node(values[1]))
return values[1]
raise BisonSyntaxError('Unsupported option %d in target "%s".'
......@@ -396,7 +395,7 @@ class Parser(BisonParser):
def on_unary(self, target, option, names, values):
"""
unary : MINUS exp %prec NEG
unary : MINUS exp
| FUNCTION_LPAREN exp RPAREN
| FUNCTION exp
| DERIVATIVE exp
......@@ -408,16 +407,9 @@ class Parser(BisonParser):
"""
if option == 0: # rule: NEG exp
node = values[1]
# Add negation to the left-most child
if node.is_leaf or (node.op != OP_MUL and node.op != OP_DIV):
node.negated += 1
else:
child = Scope(node)[0]
child.negated += 1
values[1].negated += 1
return node
return values[1]
if option in (1, 2): # rule: FUNCTION_LPAREN exp RPAREN | FUNCTION exp
op = values[0].split(' ', 1)[0]
......@@ -542,19 +534,13 @@ class Parser(BisonParser):
return Node(values[1], values[0], values[2])
if option == 6: # rule: exp MINUS exp
node = values[2]
# Add negation to the left-most child
if node.is_leaf or (node.op != OP_MUL and node.op != OP_DIV):
node.negated += 1
else:
node = Scope(node)[0]
node.negated += 1
right = values[2]
right.negated += 1
# Explicit call the hook handler on the created unary negation.
self.hook_handler('binary', 3, names, values, node)
self.hook_handler('unary', 0, names, values, right)
return Node(OP_ADD, values[0], values[2])
return Node(OP_ADD, values[0], right)
if option == 7: # rule: power
return Node(OP_POW, *values[0])
......
......@@ -7,17 +7,18 @@ from ..translate import _
def match_expand(node):
"""
a * (b + c) -> ab + ac
(b + c) * a -> ab + ac
(a + b) * (c + d) -> ac + ad + bc + bd
a(b + c) -> ab + ac
(b + c)a -> ab + ac
(a + b)(c + d) -> ac + ad + bc + bd
"""
assert node.is_op(OP_MUL)
p = []
leaves = []
additions = []
scope = Scope(node)
for n in Scope(node):
for n in scope:
if n.is_leaf:
leaves.append(n)
elif n.op == OP_ADD:
......@@ -27,11 +28,11 @@ def match_expand(node):
additions.append(n)
for args in product(leaves, additions):
p.append(P(node, expand_single, args))
for l, a in product(leaves, additions):
p.append(P(node, expand_single, (scope, l, a)))
for args in combinations(additions, 2):
p.append(P(node, expand_double, args))
for a0, a1 in combinations(additions, 2):
p.append(P(node, expand_double, (scope, a0, a1)))
return p
......@@ -41,12 +42,11 @@ def expand_single(root, args):
Combine a leaf (a) multiplied with an addition of two expressions
(b + c) to an addition of two multiplications.
a * (b + c) -> ab + ac
(b + c) * a -> ab + ac
a(b + c) -> ab + ac
(b + c)a -> ab + ac
"""
a, bc = args
scope, a, bc = args
b, c = bc
scope = Scope(root)
# Replace 'a' with the new expression
scope.replace(a, a * b + a * c)
......@@ -64,10 +64,10 @@ def expand_double(root, args):
"""
Rewrite two multiplied additions to an addition of four multiplications.
(a + b) * (c + d) -> ac + ad + bc + bd
(a + b)(c + d) -> ac + ad + bc + bd
"""
(a, b), (c, d) = ab, cd = args
scope = Scope(root)
scope, ab, cd = args
(a, b), (c, d) = ab, cd
# Replace 'a + b' with the new expression
scope.replace(ab, a * c + a * d + b * c + b * d)
......
......@@ -126,7 +126,8 @@ def equalize_denominators(root, args):
else:
nom = L(mult) * n
scope.replace(fraction, negate(nom / L(d.value * mult), n.negated))
scope.replace(fraction, negate(nom / L(d.value * mult),
fraction.negated))
return scope.as_nary_node()
......@@ -338,6 +339,9 @@ def match_equal_fraction_parts(node):
n_scope, d_scope = fraction_scopes(node)
p = []
if len(n_scope) == 1 and len(d_scope) == 1:
return p
# Look for matching parts in scopes
for i, n in enumerate(n_scope):
for j, d in enumerate(d_scope):
......
......@@ -26,7 +26,7 @@ def match_combine_groups(node):
for n in scope:
if not n.is_numeric():
groups.append((Leaf(1), n, n))
groups.append((Leaf(1), n, n, True))
# Each number multiplication yields a group, multiple occurences of
# the same group can be replaced by a single one
......@@ -35,17 +35,25 @@ def match_combine_groups(node):
l = len(n_scope)
for i, sub_node in enumerate(n_scope):
# TODO: use utitlity function evals_to_numeric
#if evals_to_numeric(sub_node):
if sub_node.is_numeric():
others = [n_scope[j] for j in range(i) + range(i + 1, l)]
if len(others) == 1:
g = others[0]
else:
g = nary_node('*', others)
g = nary_node(OP_MUL, others)
groups.append((sub_node, g, n))
groups.append((sub_node, g, n, False))
for (c0, g0, n0, root0), (c1, g1, n1, root1) in combinations(groups, 2):
if not root0:
c0 = c0.negate(n0.negated)
if not root1:
c1 = c1.negate(n1.negated)
for (c0, g0, n0), (c1, g1, n1) in combinations(groups, 2):
if g0.equals(g1):
p.append(P(node, combine_groups, (scope, c0, g0, n0, c1, g1, n1)))
elif g0.equals(g1, ignore_negation=True):
......
......@@ -23,7 +23,7 @@ def match_move_term(node):
# Multiplication
x / a = b -> x / a * a = b * a # =>* x = a * b
a / x = b -> a / x * x = b * x # =>* x = a / b
-x = b -> -x * -1 = b * -1 # =>* x = -b
-x = b -> -x * -1 = b * -1 # =>* x = -b
"""
assert node.is_op(OP_EQ)
......
......@@ -6,9 +6,10 @@ from ..translate import _
def match_negated_factor(node):
"""
This rule assures that negations in the scope of a multiplication are
brought to the most left node in the multiplication's scope.
brought to the multiplication itself.
Example:
Examples:
(-a)b -> -ab
a * -b -> -ab
"""
assert node.is_op(OP_MUL)
......@@ -16,10 +17,7 @@ def match_negated_factor(node):
p = []
scope = Scope(node)
# FIXME: The negation that is brought outside is assigned to the first
# element in the scope during the next parsing step:
# -ab -> -(ab), but -(ab) is printed as -ab
for factor in scope[1:]:
for factor in scope:
if factor.negated:
p.append(P(node, negated_factor, (scope, factor)))
......@@ -28,13 +26,13 @@ def match_negated_factor(node):
def negated_factor(root, args):
"""
(-a)b -> -ab
a * -b -> -ab
"""
scope, factor = args
scope[0] = -scope[0]
scope.replace(factor, +factor)
return scope.as_nary_node()
return -scope.as_nary_node()
MESSAGES[negated_factor] = \
......@@ -44,27 +42,33 @@ MESSAGES[negated_factor] = \
def match_negate_polynome(node):
"""
--a -> a
----a -> --a
-(a + b) -> -a - b
-0 -> 0
"""
#print 'match_negate_polynome:', node, node.negated
assert node.negated, str(node.negated) + '; ' + str(node)
p = []
if node.negated == 2:
# --a
p.append(P(node, double_negation, ()))
if not (node.negated & 1):
# --a, ----a
p.append(P(node, double_negation))
if node.is_op(OP_ADD):
if node == 0 or node == 0.0:
# -0
p.append(P(node, negated_zero))
elif node.is_op(OP_ADD):
# -(a + b) -> -a - b
p.append(P(node, negate_polynome, ()))
p.append(P(node, negate_polynome))
return p
def double_negation(root, args):
"""
--a -> a
--a -> a
----a -> --a
...
"""
return root.reduce_negation(2)
......@@ -72,6 +76,16 @@ def double_negation(root, args):
MESSAGES[double_negation] = _('Remove double negation in {0}.')
def negated_zero(root, args):
"""
-0 -> 0
"""
return root.reduce_negation()
MESSAGES[negated_zero] = _('Remove negation from zero.')
def negate_polynome(root, args):
"""
-(a + b) -> -a - b
......@@ -88,65 +102,49 @@ def negate_polynome(root, args):
MESSAGES[negate_polynome] = _('Apply negation to the polynome {0}.')
#def negate_group(root, args):
# """
# -(a * -3c) -> a * 3c
# -(a * ... * -b) -> ab
# """
# node, scope = args
#
# for i, n in enumerate(scope):
# if n.negated:
# scope[i] = n.reduce_negation()
#
# return nary_node('*', scope).reduce_negation()
#
#
#MESSAGES[negate_polynome] = _('Apply negation to the subexpression {1[0]}.')
def match_negated_division(node):
"""
-a / -b -> a / b
(-a) / b -> -a / b
a / (-b) -> -a / b
Note that:
(-a) / (-b) -> -a / (-b) -> --a / b -> a / b
"""
assert node.is_op(OP_DIV)
a, b = node
p = []
if a.negated and b.negated:
return [P(node, double_negated_division, ())]
elif b.negated:
return [P(node, single_negated_division, (a, +b))]
if a.negated:
p.append(P(node, negated_nominator))
return []
if b.negated:
p.append(P(node, negated_denominator))
return p
def single_negated_division(root, args):
def negated_nominator(root, args):
"""
a / -b -> -a / b
(-a) / b -> -a / b
"""
a, b = args
# FIXME: "-a/b" results in "-(a/b)", which will cause a loop.
a, b = root
return -a / b
return -(+a / b).negate(root.negated)
MESSAGES[single_negated_division] = \
_('Bring negation outside of the division: -{1} / {2}.')
MESSAGES[negated_nominator] = \
_('Bring nominator negation in {0} outside to the fraction itself.')
def double_negated_division(root, args):
def negated_denominator(root, args):
"""
-a / -b -> a / b
a / (-b) -> -a / b
"""
a, b = root
return +a / +b
MESSAGES[double_negated_division] = \
_('Eliminate top and bottom negation in {0}.')
return -(a / +b).negate(root.negated)
# TODO: negated multiplication: -a * -b = ab
MESSAGES[negated_denominator] = \
_('Bring denominator negation in {0} outside to the fraction itself.')
......@@ -99,6 +99,10 @@ def match_divide_numerics(node):
assert node.is_op(OP_DIV)
n, d = node
if n.negated or d.negated:
return []
nv, dv = n.value, d.value
if n.is_int() and d.is_int():
......@@ -107,20 +111,18 @@ def match_divide_numerics(node):
if not mod:
# 6 / 2 -> 3
# 3 / 2 -> 3 / 2
return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
return [P(node, divide_numerics)]
gcd = greatest_common_divisor(nv, dv)
if 1 < gcd <= nv:
# 2 / 4 -> 1 / 2
# TODO: Test with negations!
return [P(node, reduce_fraction_constants, (gcd,))]
if nv > dv:
# 4 / 3 -> 1 + 1 / 3
# TODO: Test with negations!
return [P(node, fraction_to_int_fraction,
((nv - mod) / dv, mod, dv))]
#if nv > dv:
# # 4 / 3 -> 1 + 1 / 3
# return [P(node, fraction_to_int_fraction,
# ((nv - mod) / dv, mod, dv))]
elif n.is_numeric() and d.is_numeric():
if d == 1.0:
# 3 / 1.0 -> 3
......@@ -129,7 +131,7 @@ def match_divide_numerics(node):
# 3.0 / 2 -> 1.5
# 3 / 2.0 -> 1.5
# 3.0 / 2.0 -> 1.5
return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
return [P(node, divide_numerics)]
return []
......@@ -145,9 +147,9 @@ def divide_numerics(root, args):
3.0 / 2.0 -> 1.5
3 / 1.0 -> 3
"""
n, d, negated = args
n, d = root
return Leaf(n / d).negate(negated)
return Leaf(n.value / d.value).negate(root.negated)
MESSAGES[divide_numerics] = _('Divide constant {1} by constant {2}.')
......@@ -164,29 +166,12 @@ def reduce_fraction_constants(root, args):
gcd = args[0]
a, b = root
return Leaf(a.value / gcd).negate(a.negated) \
/ Leaf(b.value / gcd).negate(b.negated)
return Leaf(a.value / gcd) / Leaf(b.value / gcd)
MESSAGES[reduce_fraction_constants] = _('Simplify fraction {0}.')
def fraction_to_int_fraction(root, args):
"""
Combine two divided integer into an integer with a fraction.
Examples:
4 / 3 -> 1 + 1 / 3
"""
integer, nominator, denominator = map(Leaf, args)
return integer + nominator / denominator
MESSAGES[fraction_to_int_fraction] = _('Expand fraction with nominator greater'
' than denominator {0} to an integer plus a fraction.')
def match_multiply_zero(node):
"""
a * 0 -> 0
......
from rules.sort import move_constant
from rules.numerics import reduce_fraction_constants, fraction_to_int_fraction
from rules.numerics import reduce_fraction_constants
from rules.logarithmic import factor_in_exponent_multiplicant
......@@ -9,8 +9,8 @@ def pick_suggestion(possibilities):
# TODO: pick the best suggestion.
for suggestion, p in enumerate(possibilities + [None]):
if p and p.handler not in [move_constant, fraction_to_int_fraction,
reduce_fraction_constants, factor_in_exponent_multiplicant]:
if p and p.handler not in [move_constant, reduce_fraction_constants,
factor_in_exponent_multiplicant]:
break
if not p:
......
......@@ -2,6 +2,11 @@ import sys
from external.graph_drawing.graph import generate_graph
from external.graph_drawing.line import generate_line
from src.node import negation_to_node
def create_graph(node):
return generate_graph(negation_to_node(node))
class ParserWrapper(object):
......@@ -90,9 +95,9 @@ def run_expressions(base_class, expressions, fail=True, silent=False,
if not silent and hasattr(res, 'nodes'):
print >>sys.stderr, 'result graph:'
print >>sys.stderr, generate_graph(res)
print >>sys.stderr, create_graph(res)
print >>sys.stderr, 'expected graph:'
print >>sys.stderr, generate_graph(out)
print >>sys.stderr, create_graph(out)
if fail:
raise
......@@ -114,16 +119,16 @@ def apply_expressions(base_class, expressions, fail=True, silent=False,
if not silent and hasattr(res, 'nodes'):
print >>sys.stderr, 'result graph:'
print >>sys.stderr, generate_graph(res)
print >>sys.stderr, create_graph(res)
print >>sys.stderr, 'expected graph:'
print >>sys.stderr, generate_graph(out)
print >>sys.stderr, create_graph(out)
if fail:
raise
def graph(parser, *exp, **kwargs):
return generate_graph(ParserWrapper(parser, **kwargs).run(exp))
return create_graph(ParserWrapper(parser, **kwargs).run(exp))
def line(parser, *exp, **kwargs):
......
......@@ -10,10 +10,10 @@ class TestB1Ch08(unittest.TestCase):
def test_diagnostic_test_parser(self):
run_expressions(Parser, [
('6*5^2', L(6) * L(5) ** 2),
('-5*(-3)^2', (-L(5)) * (-L(3)) ** 2),
('7p-3p', L(7) * 'p' + (-L(3) * 'p')),
('-5a*-6', (-L(5)) * 'a' * (-L(6))),
('3a-8--5-2a', L(3) * 'a' + -L(8) + (--L(5)) + (-L(2) * 'a')),
('-5*(-3)^2', -(L(5) * (-L(3)) ** 2)),
('7p-3p', L(7) * 'p' + -(L(3) * 'p')),
('-5a*-6', -(L(5) * 'a' * -L(6))),
('3a-8--5-2a', L(3) * 'a' + -L(8) + --(L(5)) + -(L(2) * 'a')),
])
def test_diagnostic_test_application(self):
......
......@@ -9,12 +9,12 @@ class TestB1Ch10(unittest.TestCase):
def test_diagnostic_test(self):
run_expressions(Parser, [
('5(a-2b)', L(5) * (L('a') + (-L(2) * 'b'))),
('5(a-2b)', L(5) * (L('a') + -(L(2) * 'b'))),
('-(3a+6b)', -(L(3) * L('a') + L(6) * 'b')),
('18-(a-12)', L(18) + -(L('a') + -L(12))),
('-p-q+5(p-q)-3q-2(p-q)',
-L('p') + -L('q') + L(5) * (L('p') + -L('q')) + (-L(3) * 'q') \
+ (-L(2) * (L('p') + -L('q')))
-L('p') + -L('q') + L(5) * (L('p') + -L('q')) + -(L(3) * 'q') \
+ -(L(2) * (L('p') + -L('q')))
),
('(2+3/7)^4',
N('^', N('+', L(2), N('/', L(3), L(7))), L(4))
......@@ -28,7 +28,7 @@ class TestB1Ch10(unittest.TestCase):
)
),
('-x3*-2x5',
-(L('x') ** L(3)) * - L(2) * L('x') ** L(5)
-(L('x') ** L(3) * -(L(2) * L('x') ** L(5)))
),
('(7x2y3)^2/(7x2y3)',
N('/',
......
......@@ -3,15 +3,16 @@ from tests.rulestestcase import RulesTestCase as TestCase, rewrite
class TestLeidenOefenopgave(TestCase):
def test_1_1(self):
for chain in [['-5(x2 - 3x + 6)', '-5(x ^ 2 - 3x) - 5 * 6',
'-5x ^ 2 - 5 * -3x - 5 * 6',
'-5x ^ 2 - -15x - 5 * 6',
'-5x ^ 2 + 15x - 5 * 6',
'-5x ^ 2 + 15x - 30',
],
]:
self.assertRewrite(chain)
self.assertRewrite([
'-5(x2 - 3x + 6)',
'-(5(x ^ 2 - 3x) + 5 * 6)',
'-(5x ^ 2 + 5(-3x) + 5 * 6)',
'-(5x ^ 2 - 5 * 3x + 5 * 6)',
'-(5x ^ 2 - 15x + 5 * 6)',
'-(5x ^ 2 - 15x + 30)',
'-5x ^ 2 - -15x - 30',
'-5x ^ 2 + 15x - 30',
])
return
for exp, solution in [
......@@ -70,16 +71,18 @@ class TestLeidenOefenopgave(TestCase):
def test_1_4(self):
# (x-1)^2 -> x^2 - 2x + 1
self.assertRewrite([
'(x-1)^2', '(x - 1)(x - 1)',
'xx + x * -1 - 1x - 1 * -1',
'x ^ (1 + 1) + x * -1 - 1x - 1 * -1',
'x ^ 2 + x * -1 - 1x - 1 * -1',
'x ^ 2 - x * 1 - 1x - 1 * -1',
'x ^ 2 - x - 1x - 1 * -1',
'x ^ 2 - x - x - 1 * -1',
'x ^ 2 + (1 + 1) * -x - 1 * -1',
'x ^ 2 + 2 * -x - 1 * -1',
'x ^ 2 - 2x - 1 * -1',
'(x - 1) ^ 2',
'(x - 1)(x - 1)',
'xx + x(-1) + (-1)x + (-1)(-1)',
'x ^ (1 + 1) + x(-1) + (-1)x + (-1)(-1)',
'x ^ 2 + x(-1) + (-1)x + (-1)(-1)',
'x ^ 2 - x * 1 + (-1)x + (-1)(-1)',
'x ^ 2 - x + (-1)x + (-1)(-1)',
'x ^ 2 - x - 1x + (-1)(-1)',
'x ^ 2 - x - x + (-1)(-1)',
'x ^ 2 + (1 + 1)(-x) + (-1)(-1)',
'x ^ 2 + 2(-x) + (-1)(-1)',
'x ^ 2 - 2x + (-1)(-1)',
'x ^ 2 - 2x - -1',
'x ^ 2 - 2x + 1',
])
......@@ -101,20 +104,20 @@ class TestLeidenOefenopgave(TestCase):
'-x * 1 - 1x',
'-x - 1x',
'-x - x',
'(1 + 1) * -x',
'2 * -x',
'(1 + 1)(-x)',
'2(-x)',
'-2x',
])
def test_1_4_3(self):
self.assertRewrite([
'x * -1 + x * -1',
'-x * 1 + x * -1',
'-x + x * -1',
'-x * 1 + x(-1)',
'-x + x(-1)',
'-x - x * 1',
'-x - x',
'(1 + 1) * -x',
'2 * -x',
'(1 + 1)(-x)',
'2(-x)',
'-2x',
])
......@@ -132,14 +135,16 @@ class TestLeidenOefenopgave(TestCase):
'(4x + 5) * -(5 - 4x)',
'(4x + 5)(-5 - -4x)',
'(4x + 5)(-5 + 4x)',
'4x * -5 + 4x * 4x + 5 * -5 + 5 * 4x',
'-20x + 4x * 4x + 5 * -5 + 5 * 4x',
'-20x + 16xx + 5 * -5 + 5 * 4x',
'-20x + 16x ^ (1 + 1) + 5 * -5 + 5 * 4x',
'-20x + 16x ^ 2 + 5 * -5 + 5 * 4x',
'4x(-5) + 4x * 4x + 5(-5) + 5 * 4x',
'(-20)x + 4x * 4x + 5(-5) + 5 * 4x',
'-20x + 4x * 4x + 5(-5) + 5 * 4x',
'-20x + 16xx + 5(-5) + 5 * 4x',
'-20x + 16x ^ (1 + 1) + 5(-5) + 5 * 4x',
'-20x + 16x ^ 2 + 5(-5) + 5 * 4x',
'-20x + 16x ^ 2 - 25 + 5 * 4x',
'-20x + 16x ^ 2 - 25 + 20x',
'(-20 + 20)x + 16x ^ 2 - 25',
'(-1 + 1)20x + 16x ^ 2 - 25',
'0 * 20x + 16x ^ 2 - 25',
'0x + 16x ^ 2 - 25',
'0 + 16x ^ 2 - 25',
'16x ^ 2 - 25',
......@@ -164,6 +169,7 @@ class TestLeidenOefenopgave(TestCase):
'2/7 - 4/11',
'22 / 77 - 28 / 77',
'(22 - 28) / 77',
'(-6) / 77',
'-6 / 77',
])
......
......@@ -3,51 +3,58 @@ from tests.rulestestcase import RulesTestCase as TestCase
class TestLeidenOefenopgaveV12(TestCase):
def test_1_a(self):
self.assertRewrite(['-5(x2 - 3x + 6)',
'-5(x ^ 2 - 3x) - 5 * 6',
'-5x ^ 2 - 5 * -3x - 5 * 6',
'-5x ^ 2 - -15x - 5 * 6',
'-5x ^ 2 + 15x - 5 * 6',
'-5x ^ 2 + 15x - 30'])
self.assertRewrite([
'-5(x2 - 3x + 6)',
'-(5(x ^ 2 - 3x) + 5 * 6)',
'-(5x ^ 2 + 5(-3x) + 5 * 6)',
'-(5x ^ 2 - 5 * 3x + 5 * 6)',
'-(5x ^ 2 - 15x + 5 * 6)',
'-(5x ^ 2 - 15x + 30)',
'-5x ^ 2 - -15x - 30',
'-5x ^ 2 + 15x - 30',
])
def test_1_d(self):
self.assertRewrite(['(2x + x)x',
'(2 + 1)xx',
'3xx',
'3x ^ (1 + 1)',
'3x ^ 2'])
self.assertRewrite([
'(2x + x)x',
'(2 + 1)xx',
'3xx',
'3x ^ (1 + 1)',
'3x ^ 2',
])
def test_1_e(self):
self.assertRewrite([
'-2(6x - 4) ^ 2x',
'-2(6x - 4)(6x - 4)x',
'(-2 * 6x - 2 * -4)(6x - 4)x',
'(-12x - 2 * -4)(6x - 4)x',
'(-12x - -8)(6x - 4)x',
'(-12x + 8)(6x - 4)x',
'(-12x * 6x - 12x * -4 + 8 * 6x + 8 * -4)x',
'(-72xx - 12x * -4 + 8 * 6x + 8 * -4)x',
'(-72x ^ (1 + 1) - 12x * -4 + 8 * 6x + 8 * -4)x',
'(-72x ^ 2 - 12x * -4 + 8 * 6x + 8 * -4)x',
'(-72x ^ 2 - -48x + 8 * 6x + 8 * -4)x',
'(-72x ^ 2 + 48x + 8 * 6x + 8 * -4)x',
'(-72x ^ 2 + 48x + 48x + 8 * -4)x',
'(-72x ^ 2 + (1 + 1)48x + 8 * -4)x',
'(-72x ^ 2 + 2 * 48x + 8 * -4)x',
'(-72x ^ 2 + 96x + 8 * -4)x',
'(-72x ^ 2 + 96x - 32)x',
'x(-72x ^ 2 + 96x) + x * -32',
'x * -72x ^ 2 + x * 96x + x * -32',
'-x * 72x ^ 2 + x * 96x + x * -32',
'-x ^ (1 + 2)72 + x * 96x + x * -32',
'-x ^ 3 * 72 + x * 96x + x * -32',
'-x ^ 3 * 72 + x ^ (1 + 1)96 + x * -32',
'-x ^ 3 * 72 + x ^ 2 * 96 + x * -32',
'-(2 * 6x + 2(-4))(6x - 4)x',
'-(12x + 2(-4))(6x - 4)x',
'-(12x - 8)(6x - 4)x',
'-(12x * 6x + 12x(-4) + (-8)6x + (-8)(-4))x',
'-(72xx + 12x(-4) + (-8)6x + (-8)(-4))x',
'-(72x ^ (1 + 1) + 12x(-4) + (-8)6x + (-8)(-4))x',
'-(72x ^ 2 + 12x(-4) + (-8)6x + (-8)(-4))x',
'-(72x ^ 2 + (-48)x + (-8)6x + (-8)(-4))x',
'-(72x ^ 2 - 48x + (-8)6x + (-8)(-4))x',
'-(72x ^ 2 - 48x + (-48)x + (-8)(-4))x',
'-(72x ^ 2 - 48x - 48x + (-8)(-4))x',
'-(72x ^ 2 + (1 + 1)(-48x) + (-8)(-4))x',
'-(72x ^ 2 + 2(-48x) + (-8)(-4))x',
'-(72x ^ 2 - 2 * 48x + (-8)(-4))x',
'-(72x ^ 2 - 96x + (-8)(-4))x',
'-(72x ^ 2 - 96x - -32)x',
'-(72x ^ 2 - 96x + 32)x',
'-(x(72x ^ 2 - 96x) + x * 32)',
'-(x * 72x ^ 2 + x(-96x) + x * 32)',
'-(x ^ (1 + 2)72 + x(-96x) + x * 32)',
'-(x ^ 3 * 72 + x(-96x) + x * 32)',
'-(x ^ 3 * 72 - x * 96x + x * 32)',
'-(x ^ 3 * 72 - x ^ (1 + 1)96 + x * 32)',
'-(x ^ 3 * 72 - x ^ 2 * 96 + x * 32)',
'-x ^ 3 * 72 - -x ^ 2 * 96 - x * 32',
'-x ^ 3 * 72 + x ^ 2 * 96 - x * 32',
'72 * -x ^ 3 + x ^ 2 * 96 - x * 32',
'-72x ^ 3 + x ^ 2 * 96 - x * 32',
'-72x ^ 3 + 96x ^ 2 - x * 32',
'-72x ^ 3 + 96x ^ 2 + 32 * -x',
'-72x ^ 3 + 96x ^ 2 - 32x',
])
......@@ -67,7 +74,7 @@ class TestLeidenOefenopgaveV12(TestCase):
'b ^ 2 * a ^ 7 / b ^ 3',
'b ^ 2 / b ^ 3 * a ^ 7 / 1',
'b ^ (2 - 3)a ^ 7 / 1',
'b ^ -1 * a ^ 7 / 1',
'b ^ (-1)a ^ 7 / 1',
'1 / b ^ 1 * a ^ 7 / 1',
'1 / b * a ^ 7 / 1',
'a ^ 7 * 1 / b / 1',
......@@ -105,9 +112,9 @@ class TestLeidenOefenopgaveV12(TestCase):
def test_2_f(self):
self.assertRewrite([
'(4b) ^ -2',
'4 ^ -2 * b ^ -2',
'1 / 4 ^ 2 * b ^ -2',
'1 / 16 * b ^ -2',
'4 ^ (-2)b ^ (-2)',
'1 / 4 ^ 2 * b ^ (-2)',
'1 / 16 * b ^ (-2)',
'1 / 16 * (1 / b ^ 2)',
'1 * 1 / (16b ^ 2)',
'1 / (16b ^ 2)',
......
from src.node import ExpressionNode as N, ExpressionLeaf as L, Scope, \
nary_node, get_scope, OP_ADD, infinity, absolute, sin, cos, tan, log, \
ln, der, integral, indef, eq
ln, der, integral, indef, eq, negation_to_node
from tests.rulestestcase import RulesTestCase, tree
......@@ -106,6 +106,10 @@ class TestNode(RulesTestCase):
plus = N('+', N('+', N('+', *self.l[:2]), self.l[2]), self.l[3])
self.assertEqual(get_scope(plus), self.l)
def test_get_scope_negation(self):
root, a, b, cd = tree('a * b * -cd, a, b, -cd')
self.assertEqual(get_scope(root), [a, b, cd])
def test_equals_node_leaf(self):
a, b = plus = tree('a + b')
......@@ -296,3 +300,9 @@ class TestNode(RulesTestCase):
def test_eq(self):
x, a, b, expect = tree('x, a, b, x + a = b')
self.assertEqual(eq(x + a, b), expect)
def test_negation_to_node(self):
a = tree('a')
self.assertEqual(negation_to_node(-a), N('-', a))
self.assertEqual(negation_to_node(-(a + 1)), N('-', a + 1))
self.assertEqual(negation_to_node(-(a - 1)), N('-', a + N('-', 1)))
......@@ -63,14 +63,6 @@ class TestParser(unittest.TestCase):
# FIXME: self.assertEqual(tree('a|b|'), tree('a * |b|'))
# FIXME: self.assertEqual(tree('|a|b'), tree('|a| * b'))
def test_moved_negation(self):
a, b = tree('a,b')
self.assertEqual(tree('-ab'), (-a) * b)
self.assertEqual(tree('-(ab)'), (-a) * b)
self.assertEqual(tree('-a / b'), (-a) / b)
self.assertEqual(tree('-(a / b)'), (-a) / b)
def test_functions(self):
x = tree('x')
......
......@@ -4,11 +4,23 @@ from tests.rulestestcase import RulesTestCase as TestCase
class TestRewrite(TestCase):
def test_addition_rewrite(self):
self.assertRewrite(['2 + 3 + 4', '5 + 4', '9'])
self.assertRewrite([
'2 + 3 + 4',
'5 + 4',
'9',
])
def test_addition_identifiers_rewrite(self):
self.assertRewrite(['2 + 3a + 4', '6 + 3a'])
self.assertRewrite([
'2 + 3a + 4',
'6 + 3a',
])
def test_division_rewrite(self):
self.assertRewrite(['2/7 - 4/11', '22 / 77 - 28 / 77',
'(22 - 28) / 77', '-6 / 77'])
self.assertRewrite([
'2/7 - 4/11',
'22 / 77 - 28 / 77',
'(22 - 28) / 77',
'(-6) / 77',
'-6 / 77',
])
from src.rules.factors import match_expand, expand_single, expand_double
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -10,45 +11,41 @@ class TestRulesFactors(RulesTestCase):
b, c = bc
root = a * bc
possibilities = match_expand(root)
self.assertEqualPos(possibilities,
[P(root, expand_single, (a, bc))])
self.assertEqualPos(match_expand(root),
[P(root, expand_single, (Scope(root), a, bc))])
root = bc * a
possibilities = match_expand(root)
self.assertEqualPos(possibilities,
[P(root, expand_single, (a, bc))])
self.assertEqualPos(match_expand(root),
[P(root, expand_single, (Scope(root), a, bc))])
root = a * d * bc
possibilities = match_expand(root)
self.assertEqualPos(possibilities,
[P(root, expand_single, (a, bc)),
P(root, expand_single, (d, bc))])
self.assertEqualPos(match_expand(root),
[P(root, expand_single, (Scope(root), a, bc)),
P(root, expand_single, (Scope(root), d, bc))])
ab, cd = root = (a + b) * (c + d)
possibilities = match_expand(root)
self.assertEqualPos(possibilities,
[P(root, expand_double, (ab, cd))])
self.assertEqualPos(match_expand(root),
[P(root, expand_double, (Scope(root), ab, cd))])
def test_expand_single(self):
a, b, c, d = tree('a,b,c,d')
bc = b + c
root = a * bc
self.assertEqualNodes(expand_single(root, (a, bc)),
self.assertEqualNodes(expand_single(root, (Scope(root), a, bc)),
a * b + a * c)
root = a * d * bc
self.assertEqualNodes(expand_single(root, (a, bc)),
self.assertEqualNodes(expand_single(root, (Scope(root), a, bc)),
(a * b + a * c) * d)
def test_expand_double(self):
(a, b), (c, d) = ab, cd = tree('a + b,c + d')
root = ab * cd
self.assertEqualNodes(expand_double(root, (ab, cd)),
self.assertEqualNodes(expand_double(root, (Scope(root), ab, cd)),
a * c + a * d + b * c + b * d)
root = a * ab * b * cd * c
self.assertEqualNodes(expand_double(root, (ab, cd)),
self.assertEqualNodes(expand_double(root, (Scope(root), ab, cd)),
a * (a * c + a * d + b * c + b * d) * b * c)
......@@ -185,9 +185,8 @@ class TestRulesFractions(RulesTestCase):
P(root, divide_fraction_parts, (b, s0, s1, 1, 1)),
P(root, divide_fraction_parts, (c, s0, s1, 2, 0))])
root = tree('-a / a')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [-a], [a], 0, 0))])
root = tree('a / a')
self.assertEqualPos(match_equal_fraction_parts(root), [])
(ap, b), aq = root = tree('a ^ p * b / a ^ q')
self.assertEqualPos(match_equal_fraction_parts(root),
......@@ -224,7 +223,7 @@ class TestRulesFractions(RulesTestCase):
result = divide_fraction_parts(root, (c, [a, b, c], [c, b, a], 2, 0))
self.assertEqual(result, a * b / (b * a))
(a, b), a = root = tree('-ab / a')
(a, b), a = root = tree('(-a)b / a')
result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
self.assertEqual(result, -b / 1)
......
......@@ -95,7 +95,13 @@ class TestRulesGroups(RulesTestCase):
root, l1 = tree('ab + b + ba,1')
abb, ba = root
ab, b = abb
self.assertEqualNodes(combine_groups(root,
(Scope(root), l1, ab, ab, l1, ba, ba)),
(l1 + 1) * ab + b)
def test_combine_groups_negation(self):
root, expect = tree('3a - 2a, -(3 + 2)a')
(l3, a0), (l2, a1) = n0, n1 = root
self.assertEqualNodes(combine_groups(root,
(Scope(root), l3, a0, n0, l2, a1, n1)),
expect)
......@@ -98,7 +98,7 @@ class TestRulesIntegrals(RulesTestCase):
[P(root, split_negation_to_constant)])
def test_split_negation_to_constant(self):
root, expect = tree('int -x2 dx, int -1x2 dx')
root, expect = tree('int -x2 dx, int (-1)x2 dx')
self.assertEqual(split_negation_to_constant(root, ()), expect)
def test_factor_out_constant(self):
......
......@@ -69,12 +69,14 @@ class TestRulesLineq(RulesTestCase):
'(2 + 3)x = -3x - 5 - -3x',
'5x = -3x - 5 - -3x',
'5x = -3x - 5 + 3x',
'5x = (-3 + 3)x - 5',
'5x = (-1 + 1)3x - 5',
'5x = 0 * 3x - 5',
'5x = 0x - 5',
'5x = 0 - 5',
'5x = -5',
'5x / 5 = -5 / 5',
'x / 1 = -5 / 5',
'5x / 5 = (-5) / 5',
'x / 1 = (-5) / 5',
'x = (-5) / 5',
'x = -5 / 5',
'x = -1',
])
......@@ -82,10 +84,11 @@ class TestRulesLineq(RulesTestCase):
def test_match_move_term_chain_advanced(self):
self.assertRewrite([
'-x = a',
'-x * -1 = a * -1',
'--x * 1 = a * -1',
'x * 1 = a * -1',
'x = a * -1',
'(-x)(-1) = a(-1)',
'-x(-1) = a(-1)',
'--x * 1 = a(-1)',
'--x = a(-1)',
'x = a(-1)',
'x = -a * 1',
'x = -a',
])
from src.rules.negation import match_negated_factor, negated_factor, \
match_negate_polynome, negate_polynome, double_negation, \
match_negated_division, single_negated_division, \
double_negated_division
match_negate_polynome, negate_polynome, negated_zero, \
double_negation, match_negated_division, negated_nominator, \
negated_denominator
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -14,7 +14,7 @@ class TestRulesNegation(RulesTestCase):
self.assertEqualPos(match_negated_factor(root),
[P(root, negated_factor, (Scope(root), b))])
(a, b), c = root = tree('a * -b * -c')
(a, b), c = root = tree('a * (-b) * -c')
scope = Scope(root)
self.assertEqualPos(match_negated_factor(root),
[P(root, negated_factor, (scope, b)),
......@@ -22,34 +22,44 @@ class TestRulesNegation(RulesTestCase):
def test_negated_factor(self):
a, b = root = tree('a * -b')
self.assertEqualNodes(negated_factor(root, (Scope(root), b)),
-a * +b)
self.assertEqual(negated_factor(root, (Scope(root), b)), -(a * +b))
(a, b), c = root = tree('a * -b * -c')
self.assertEqualNodes(negated_factor(root, (Scope(root), b)),
-a * +b * c)
self.assertEqualNodes(negated_factor(root, (Scope(root), c)),
-a * b * +c)
(a, b), c = root = tree('a * (-b) * -c')
self.assertEqual(negated_factor(root, (Scope(root), b)), -(a * +b * c))
self.assertEqual(negated_factor(root, (Scope(root), c)), -(a * b * +c))
def test_match_negate_polynome(self):
root = tree('--a')
self.assertEqualPos(match_negate_polynome(root),
[P(root, double_negation, ())])
[P(root, double_negation)])
root = tree('-0')
self.assertEqualPos(match_negate_polynome(root),
[P(root, negated_zero)])
root = tree('--0')
self.assertEqualPos(match_negate_polynome(root),
[P(root, double_negation),
P(root, negated_zero)])
root = tree('-(a + b)')
self.assertEqualPos(match_negate_polynome(root),
[P(root, negate_polynome, ())])
[P(root, negate_polynome)])
def test_double_negation(self):
root = tree('--a')
self.assertEqualNodes(double_negation(root, ()), ++root)
self.assertEqual(double_negation(root, ()), ++root)
def test_negated_zero(self):
root = tree('-0')
self.assertEqual(negated_zero(root, ()), 0)
def test_negate_polynome(self):
a, b = root = tree('-(a + b)')
self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
self.assertEqual(negate_polynome(root, ()), -a + -b)
a, b = root = tree('-(a - b)')
self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
self.assertEqual(negate_polynome(root, ()), -a + -b)
def test_match_negated_division_none(self):
self.assertEqual(match_negated_division(tree('1 / 2')), [])
......@@ -58,25 +68,32 @@ class TestRulesNegation(RulesTestCase):
l1, l2 = root = tree('-1 / 2')
self.assertEqualPos(match_negated_division(root), [])
l1, l2 = root = tree('(-1) / 2')
self.assertEqualPos(match_negated_division(root),
[P(root, negated_nominator)])
l1, l2 = root = tree('1 / -2')
possibilities = match_negated_division(root)
self.assertEqualPos(possibilities,
[P(root, single_negated_division, (l1, +l2))])
self.assertEqualPos(match_negated_division(root),
[P(root, negated_denominator)])
def test_match_negated_division_double(self):
root = tree('-1 / -2')
root = tree('(-1) / -2')
self.assertEqualPos(match_negated_division(root),
[P(root, negated_nominator),
P(root, negated_denominator)])
possibilities = match_negated_division(root)
self.assertEqualPos(possibilities,
[P(root, double_negated_division, ())])
def test_negated_nominator(self):
l1, l2 = root = tree('(-1) / 2')
self.assertEqual(negated_nominator(root, ()), -(+l1 / l2))
def test_single_negated_division(self):
def test_negated_denominator(self):
l1, l2 = root = tree('1 / -2')
self.assertEqualNodes(single_negated_division(root, (l1, +l2)),
-l1 / +l2)
self.assertEqual(negated_denominator(root, ()), -(l1 / +l2))
def test_double_negated_division(self):
l1, l2 = root = tree('-1 / -2')
self.assertEqualNodes(double_negated_division(root, ()),
+l1 / +l2)
self.assertRewrite([
'(-a) / (-b)',
'-a / (-b)',
'--a / b',
'a / b',
])
from src.rules.numerics import match_add_numerics, add_numerics, \
match_divide_numerics, divide_numerics, reduce_fraction_constants, \
fraction_to_int_fraction, match_multiply_numerics, multiply_numerics, \
match_multiply_numerics, multiply_numerics, \
raise_numerics
from src.node import ExpressionLeaf as L, Scope
from src.possibilities import Possibility as P
......@@ -42,67 +42,54 @@ class TestRulesNumerics(RulesTestCase):
root = i6 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (6, 2, 0))])
self.assertEqualPos(possibilities, [P(root, divide_numerics)])
root = -i6 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (6, 2, 1))])
self.assertEqualPos(match_divide_numerics(root), [])
root = i3 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, fraction_to_int_fraction, (1, 1, 2))])
root = i6 / -i2
self.assertEqualPos(match_divide_numerics(root), [])
root = i2 / i4
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
self.assertEqualPos(match_divide_numerics(root),
[P(root, reduce_fraction_constants, (2,))])
root = f3 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3.0, 2, 0))])
self.assertEqualPos(match_divide_numerics(root),
[P(root, divide_numerics)])
root = i3 / f2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3, 2.0, 0))])
self.assertEqualPos(match_divide_numerics(root),
[P(root, divide_numerics)])
root = f3 / f2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3.0, 2.0, 0))])
self.assertEqualPos(match_divide_numerics(root),
[P(root, divide_numerics)])
root = i3 / f1
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3, 1, 0))])
self.assertEqualPos(match_divide_numerics(root),
[P(root, divide_numerics)])
root = a / b
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities, [])
self.assertEqualPos(match_divide_numerics(root), [])
def test_divide_numerics(self):
i2, i3, i6, f2, f3 = tree('2,3,6,2.0,3.0')
self.assertEqual(divide_numerics(i6 / i2, (6, 2, 0)), 3)
self.assertEqual(divide_numerics(f3 / i2, (3.0, 2, 0)), 1.5)
self.assertEqual(divide_numerics(i3 / f2, (3, 2.0, 0)), 1.5)
self.assertEqual(divide_numerics(f3 / f2, (3.0, 2.0, 0)), 1.5)
self.assertEqual(divide_numerics(i6 / i2, (6, 2, 1)), -3)
self.assertEqual(divide_numerics(i6 / i2, (6, 2, 2)), --i3)
self.assertEqual(divide_numerics(i6 / i2, ()), 3)
self.assertEqual(divide_numerics(f3 / i2, ()), 1.5)
self.assertEqual(divide_numerics(i3 / f2, ()), 1.5)
self.assertEqual(divide_numerics(f3 / f2, ()), 1.5)
self.assertEqual(divide_numerics(-(i6 / i2), ()), -i3)
def test_reduce_fraction_constants(self):
l1, l2 = tree('1,2')
self.assertEqual(reduce_fraction_constants(l2 / 4, (2,)), l1 / l2)
def test_fraction_to_int_fraction(self):
l1, l4 = tree('1,4')
self.assertEqual(fraction_to_int_fraction(l4 / 3, (1, 1, 3)),
l1 + l1 / 3)
#def test_fraction_to_int_fraction(self):
# l1, l4 = tree('1,4')
# self.assertEqual(fraction_to_int_fraction(l4 / 3, (1, 1, 3)),
# l1 + l1 / 3)
def test_match_multiply_numerics(self):
i2, i3, i6, f2, f3, f6 = tree('2,3,6,2.0,3.0,6.0')
......
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