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