Commit 2bc65876 authored by Sander Mathijs van Veen's avatar Sander Mathijs van Veen

Merge branch 'master' of kompiler.org:trs

parents 073810b9 2304214e
# vim: set fileencoding=utf-8 :
- Fix BisonSyntaxError location tracking.
- Sort polynom by its exponents?
- No possibilities found for:
>>> a2b3 + a2b3
a ^ 2 * b ^ 3 + a ^ 2 * b ^ 3
- 2 + 3 + 4 rewrites to 5 instead of 5 + 4
-> the problem is that the 'root' of the application is actually a subtree
of the entire expression. This means that the parent of each possibility
root (or 'subtree') must me stored to be able to replace the subtree.
- MESSAGES needs to be expanded.
- rewrite match_combine_polynomes to an even more generic form:
match_combine_factors.
- "--ab + c" has no rewrite possibility. The graph of "--ab + c" is also
not valid:
-
+
╭─┴╮
* c
╭┴╮
- b
a
- The following expression gives a cycle in the possibilities:
>>> ab + ba
possibilities:
Group "ab" is multiplied by 1 and 1, combine them.
>>> (1 + 1) * ab
(1 + 1)ab
possibilities:
Combine the constants 1 and 1.
Group "1" is multiplied by 1 and 1, combine them.
Expand a(1 + 1).
Expand b(1 + 1).
- Fix division by zero caused by "0/0".
- Fix division by zero caused by "0/0": Catch exception in front-end
smvv@multivac ~/work/trs $ printf "a/0\n??" | ./main.py
Traceback (most recent call last):
......@@ -84,3 +48,56 @@ smvv@multivac ~/work/trs $ printf "0/1\n??" | ./main.py
<Possibility root="0 / 1" handler=divide_numerics args=(0, 1)>
Division of 0 by 1 reduces to 0.
Division of 0 by 1 reduces to 0.
- Fractions constant rewrite rules.
- >>> (sin x) ^ 2 + (cos x) ^ 2
sin(x) ^ 2 + cos(x) ^ 2
>>> sin(x) ^ 2 + cos(x) ^ 2
sin(x ^ 2) + cos(x ^ 2)
- ExpressionNode.equals() werkend maken voor alle cases (negatie).
- validation: preorder traversal implementatie vergelijken met andere
implementaties.
- Fix the following loop using strategy (reduce_fraction_constants):
>>> 2 / 7 - 4 / 11
2 / 7 - 4 / 11
>>> @
22 / 77 - 28 / 77
>>> @
2 / 7 - 28 / 77
>>> @
2 / 7 + 4 / 11
- Cancel terms before multiplying constants: (3 * ...) / (3 * ...) -> ... / ...
>>> (7/3)*(3/5)
7 / 3 * (3 / 5)
>>> ??
Expand fraction with nominator greater than denominator 7 / 3 to an integer
plus a fraction.
Multiply fractions 7 / 3 and 3 / 5.
>>> @
7 * 3 / (3 * 5)
>>> ?
Multiply constant 7 with 3.
>>> @
21 / (3 * 5)
>>> @
21 / 15
>>> @
7 / 5
- filter_duplicates does not seem to work anymore...
- Fix error while parsing unicode PI:
>>> sin(1/2 * pi)
sin(1 / 2 * π)
>>> @
unknown char � ignored.
unknown char � ignored.
ERROR: 41.7-41.8: "syntax error, unexpected TIMES" near "*".
ERROR: 41.14-41.15: "syntax error, unexpected RPAREN" near ")".
- No matches for sin(pi), sin(2pi), sin(4pi), etc...
graph_drawing @ bb3c5d23
Subproject commit 11940973bdfef9432438b054c65b28af2eb97d0c
Subproject commit bb3c5d23dc2a15e0e634f72b1ca48e5b22817642
This diff is collapsed.
This diff is collapsed.
from node import TYPE_OPERATOR
# Each rule will append its hint message to the following dictionary. The
# function pointer to the apply function of the rule is used as key. The
# corresponding value is a string, which will be used to produce the hint
......@@ -51,16 +54,27 @@ def filter_duplicates(possibilities):
return unique
def pick_suggestion(possibilities):
if not possibilities:
return
def find_parent_node(root, child):
nodes = [root]
while nodes:
node = nodes.pop()
while node:
# TODO: pick the best suggestion.
suggestion = 0
return possibilities[suggestion]
if node.type != TYPE_OPERATOR:
break
if child in node:
return node
def apply_suggestion(root, subtree_map, suggestion):
if len(node) > 1:
nodes.append(node[1])
node = node[0]
def apply_suggestion(root, suggestion):
# TODO: clone the root node before modifying. After deep copying the root
# node, the subtree_map cannot be used since the hash() of each node in the
# deep copied root node has changed.
......@@ -68,10 +82,7 @@ def apply_suggestion(root, subtree_map, suggestion):
subtree = suggestion.handler(suggestion.root, suggestion.args)
if suggestion.root in subtree_map:
parent_node = subtree_map[suggestion.root]
else:
parent_node = None
parent_node = find_parent_node(root, suggestion.root)
# There is either a parent node or the subtree is the root node.
# FIXME: FAIL: test_diagnostic_test_application in tests/test_b1_ch08.py
......@@ -85,4 +96,5 @@ def apply_suggestion(root, subtree_map, suggestion):
if parent_node:
parent_node.substitute(suggestion.root, subtree)
return root
return subtree
from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG
from .poly import match_combine_polynomes
from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS, \
OP_TAN
from .groups import match_combine_groups
from .factors import match_expand
from .powers import match_add_exponents, match_subtract_exponents, \
match_multiply_exponents, match_duplicate_exponent, \
match_remove_negative_exponent, match_exponent_to_root, \
match_extend_exponent
from .numerics import match_divide_numerics, match_multiply_numerics, \
match_multiply_zero
match_raised_fraction, match_remove_negative_exponent, \
match_exponent_to_root, match_extend_exponent, match_constant_exponent
from .numerics import match_add_numerics, match_divide_numerics, \
match_multiply_numerics, match_multiply_zero, match_multiply_one, \
match_raise_numerics
from .fractions import match_constant_division, match_add_constant_fractions, \
match_expand_and_add_fractions
from .negation import match_negate_group, match_negated_division
match_expand_and_add_fractions, match_multiply_fractions, \
match_divide_fractions, match_equal_fraction_parts
from .negation import match_negated_factor, match_negate_polynome, \
match_negated_division
from .sort import match_sort_multiplicants
from .goniometry import match_add_quadrants, match_negated_parameter, \
match_half_pi_subtraction, match_standard_radian
RULES = {
OP_ADD: [match_add_constant_fractions, match_combine_polynomes, \
match_combine_groups],
OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents, \
match_expand_and_add_fractions, match_multiply_zero],
OP_DIV: [match_subtract_exponents, match_divide_numerics, \
match_constant_division, match_negated_division],
OP_POW: [match_multiply_exponents, match_duplicate_exponent, \
match_remove_negative_exponent, match_exponent_to_root, \
match_extend_exponent],
OP_NEG: [match_negate_group],
OP_ADD: [match_add_numerics, match_add_constant_fractions,
match_combine_groups, match_add_quadrants],
OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
match_expand_and_add_fractions, match_multiply_zero,
match_negated_factor, match_multiply_one,
match_sort_multiplicants, match_multiply_fractions],
OP_DIV: [match_subtract_exponents, match_divide_numerics,
match_constant_division, match_divide_fractions, \
match_negated_division, match_equal_fraction_parts],
OP_POW: [match_multiply_exponents, match_duplicate_exponent,
match_raised_fraction, match_remove_negative_exponent,
match_exponent_to_root, match_extend_exponent,
match_constant_exponent, match_raise_numerics],
OP_NEG: [match_negate_polynome],
OP_SIN: [match_negated_parameter, match_half_pi_subtraction,
match_standard_radian],
OP_COS: [match_negated_parameter, match_half_pi_subtraction,
match_standard_radian],
OP_TAN: [match_standard_radian],
}
from itertools import product, combinations
from ..node import Scope, OP_ADD, OP_MUL, OP_NEG
from ..node import Scope, OP_ADD, OP_MUL
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -18,9 +18,13 @@ def match_expand(node):
additions = []
for n in Scope(node):
if n.is_leaf or n.is_op(OP_NEG) and n[0].is_leaf:
if n.is_leaf:
leaves.append(n)
elif n.op == OP_ADD:
# If the addition only contains numerics, do not expand
if not filter(lambda n: not n.is_numeric(), Scope(n)):
continue
additions.append(n)
for args in product(leaves, additions):
......@@ -45,7 +49,7 @@ def expand_single(root, args):
scope = Scope(root)
# Replace 'a' with the new expression
scope.remove(a, a * b + a * c)
scope.replace(a, a * b + a * c)
# Remove the addition
scope.remove(bc)
......@@ -66,7 +70,7 @@ def expand_double(root, args):
scope = Scope(root)
# Replace 'a + b' with the new expression
scope.remove(ab, a * c + a * d + b * c + b * d)
scope.replace(ab, a * c + a * d + b * c + b * d)
# Remove the right addition
scope.remove(cd)
......
This diff is collapsed.
from .utils import is_fraction
from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_ADD, \
OP_POW, OP_MUL, OP_DIV, OP_SIN, OP_COS, OP_TAN, PI, TYPE_OPERATOR
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
def sin(*args):
return N('sin', *args)
def cos(*args):
return N('cos', *args)
def tan(*args):
return N('tan', *args)
def match_add_quadrants(node):
"""
sin(t) ^ 2 + cos(t) ^ 2 -> 1
"""
assert node.is_op(OP_ADD)
p = []
sin_q, cos_q = node
if sin_q.is_power(2) and cos_q.is_power(2):
sin, cos = sin_q[0], cos_q[0]
if sin.is_op(OP_SIN) and cos.is_op(OP_COS):
p.append(P(node, add_quadrants, ()))
return p
def add_quadrants(root, args):
"""
sin(t) ^ 2 + cos(t) ^ 2 -> 1
"""
return L(1)
MESSAGES[add_quadrants] = _('Add the sinus and cosinus quadrants to 1.')
def match_negated_parameter(node):
"""
sin(-t) -> -sin(t)
cos(-t) -> cos(t)
"""
assert node.is_op(OP_SIN) or node.is_op(OP_COS)
t = node[0]
if t.negated:
if node.op == OP_SIN:
return [P(node, negated_sinus_parameter, (t,))]
return [P(node, negated_cosinus_parameter, (t,))]
return []
def negated_sinus_parameter(root, args):
"""
sin(-t) -> -sin(t)
"""
return -sin(+args[0])
MESSAGES[negated_sinus_parameter] = \
_('Bring the negation from the sinus parameter {1} to the outside.')
def negated_cosinus_parameter(root, args):
"""
cos(-t) -> cos(t)
"""
return cos(+args[0])
MESSAGES[negated_cosinus_parameter] = \
_('Remove the negation from the cosinus parameter {1}.')
def match_half_pi_subtraction(node):
"""
sin(pi / 2 - t) -> cos(t)
cos(pi / 2 - t) -> sin(t)
"""
assert node.is_op(OP_SIN) or node.is_op(OP_COS)
if node[0].is_op(OP_ADD):
half_pi, t = node[0]
if half_pi == L(PI) / 2:
if node.op == OP_SIN:
return [P(node, half_pi_subtraction_sinus, (t,))]
return []
def is_pi_frac(node, denominator):
"""
Check if a node is a fraction of 1 multiplied with PI.
Example:
>>> print is_pi_frac(L(1) / 2 * L(PI), 2)
True
"""
if not node.is_op(OP_MUL):
return False
frac, pi = node
if not frac.is_op(OP_DIV) or not pi.is_leaf or pi.value != PI:
return False
n, d = frac
return n == 1 and d == denominator
def sqrt(value):
return N('sqrt', L(value))
l0, l1, sq2, sq3 = L(0), L(1), sqrt(2), sqrt(3)
half = l1 / 2
CONSTANTS = {
OP_SIN: [l0, half, half * sq2, half * sq3, l1],
OP_COS: [l1, half * sq3, half * sq2, half, l0],
OP_TAN: [l0, l1 / 3 * sq3, l1, sq3]
}
def match_standard_radian(node):
"""
Apply a direct constant calculation from the constants table.
| 0 | pi / 6 | pi / 4 | pi / 3 | pi / 2
----+---+-----------+-----------+-----------+-------
sin | 0 | 1/2 | sqrt(2)/2 | sqrt(3)/2 | 1
cos | 1 | sqrt(3)/2 | sqrt(2)/2 | 1/2 | 0
tan | 0 | sqrt(3)/3 | 1 | sqrt(3) | -
"""
assert node.type == TYPE_OPERATOR and node.op in (OP_SIN, OP_COS, OP_TAN)
t = node[0]
if t == 0:
return [P(node, standard_radian, (node.op, 0))]
denoms = [6, 4, 3]
if node.op != OP_TAN:
denoms.append(2)
for i, denominator in enumerate(denoms):
if is_pi_frac(t, denominator):
return [P(node, standard_radian, (node.op, i + 1))]
return []
def standard_radian(root, args):
op, column = args
return CONSTANTS[op][column].clone()
MESSAGES[standard_radian] = _('Replace standard radian {0}.')
from itertools import combinations
from ..node import ExpressionNode as Node, ExpressionLeaf as Leaf, Scope, \
OP_ADD, OP_MUL, OP_NEG
from ..node import ExpressionLeaf as Leaf, Scope, OP_ADD, OP_MUL, nary_node, \
negate
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -18,50 +18,53 @@ def match_combine_groups(node):
ab + 2ab -> 3ab
ab + ba -> 2ab
"""
# TODO: handle OP_NEG nodes
assert node.is_op(OP_ADD)
p = []
groups = []
scope = Scope(node)
for n in Scope(node):
groups.append((1, n, n))
for n in scope:
if not n.is_numeric():
groups.append((Leaf(1), n, n))
# Each number multiplication yields a group, multiple occurences of
# the same group can be replaced by a single one
if n.is_op(OP_MUL):
scope = Scope(n)
l = len(scope)
n_scope = Scope(n)
l = len(n_scope)
for i, sub_node in enumerate(scope):
if sub_node.is_numeric() or (sub_node.is_op(OP_NEG)
and sub_node[0].is_numeric()):
others = [scope[j] for j in range(i) + range(i + 1, l)]
for i, sub_node in enumerate(n_scope):
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 = Node('*', *others)
g = nary_node('*', others)
groups.append((sub_node, g, n))
for g0, g1 in combinations(groups, 2):
if g0[1].equals(g1[1]):
p.append(P(node, combine_groups, g0 + g1))
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):
# Move negations to constants
c0 = c0.negate(g0.negated)
c1 = c1.negate(g1.negated)
g0 = negate(g0, 0)
g1 = negate(g1, 0)
p.append(P(node, combine_groups, (scope, c0, g0, n0, c1, g1, n1)))
return p
def combine_groups(root, args):
c0, g0, n0, c1, g1, n1 = args
scope = Scope(root)
if not isinstance(c0, Leaf) and not isinstance(c0, Node):
c0 = Leaf(c0)
scope, c0, g0, n0, c1, g1, n1 = args
# Replace the left node with the new expression
scope.remove(n0, (c0 + c1) * g0)
scope.replace(n0, (c0 + c1) * g0)
# Remove the right node
scope.remove(n1)
......@@ -70,4 +73,4 @@ def combine_groups(root, args):
MESSAGES[combine_groups] = \
_('Group "{2}" is multiplied by {1} and {4}, combine them.')
_('Group "{3}" is multiplied by {2} and {5}, combine them.')
from ..node import get_scope, nary_node, OP_NEG, OP_ADD, OP_MUL, OP_DIV
from ..node import Scope, OP_ADD, OP_MUL, OP_DIV
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
def match_negate_group(node):
def match_negated_factor(node):
"""
--a -> a
--ab -> ab
-(-ab + c) -> --ab - c
-(a + b + ... + z) -> -a + -b + ... + -z
This rule assures that negations in the scope of a multiplication are
brought to the most left node in the multiplication's scope.
Example:
a * -b -> -ab
"""
assert node.is_op(OP_NEG)
assert node.is_op(OP_MUL)
val = node[0]
p = []
scope = Scope(node)
if val.is_op(OP_NEG):
# --a
return [P(node, double_negation, (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:]:
if factor.negated:
p.append(P(node, negated_factor, (scope, factor)))
if not val.is_leaf:
scope = get_scope(val)
return p
if not any(map(lambda n: n.is_op(OP_NEG), scope)):
return []
if val.is_op(OP_MUL):
# --ab
return [P(node, negate_polynome, (node, scope))]
def negated_factor(root, args):
"""
a * -b -> -ab
"""
scope, factor = args
scope[0] = -scope[0]
scope.replace(factor, +factor)
elif val.is_op(OP_ADD):
# -(ab + c) -> -ab - c
# -(-ab + c) -> ab - c
return [P(node, negate_group, (node, scope))]
return scope.as_nary_node()
return []
MESSAGES[negated_factor] = \
_('Bring negation of {2} to the outside of the multiplication.')
def negate_polynome(root, args):
def match_negate_polynome(node):
"""
# -a * -3c -> a * 3c
--a * 3c -> a * 3c
--ab -> ab
--abc -> abc
--a -> a
-(a + b) -> -a - b
"""
node, scope = args
#print 'match_negate_polynome:', node, node.negated
assert node.negated, str(node.negated) + '; ' + str(node)
for i, n in enumerate(scope):
# XXX: validate this property!
if n.is_op(OP_NEG):
scope[i] = n[0]
return nary_node('*', scope)
p = []
raise RuntimeError('No negation node found in scope.')
if node.negated == 2:
# --a
p.append(P(node, double_negation, ()))
if node.is_op(OP_ADD):
# -(a + b) -> -a - b
p.append(P(node, negate_polynome, ()))
MESSAGES[negate_polynome] = _('Apply negation to the polynome {1[0]}.')
return p
def negate_group(root, args):
def double_negation(root, args):
"""
-(-ab + ... + c) -> --ab + ... + -c
--a -> a
"""
node, scope = args
return root.reduce_negation(2)
# Negate each group
for i, n in enumerate(scope):
scope[i] = -n
return nary_node('+', scope)
MESSAGES[double_negation] = _('Remove double negation in {0}.')
MESSAGES[negate_group] = _('Apply negation to the subexpression {1[0]}.')
def double_negation(root, args):
def negate_polynome(root, args):
"""
--a -> a
-(a + b) -> -a - b
"""
node = args[0]
scope = Scope(root)
# Negate each group
for i, n in enumerate(scope):
scope[i] = -n
return +scope.as_nary_node()
return node[0][0]
MESSAGES[negate_polynome] = _('Apply negation to the polynome {0}.')
MESSAGES[double_negation] = _('Remove double negation in {1}.')
#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):
......@@ -92,33 +112,28 @@ def match_negated_division(node):
assert node.is_op(OP_DIV)
a, b = node
a_neg = a.is_op(OP_NEG)
b_neg = b.is_op(OP_NEG)
if a_neg and b_neg:
return [P(node, double_negated_division, (node,))]
elif a_neg:
return [P(node, single_negated_division, (a[0], b))]
elif b_neg:
return [P(node, single_negated_division, (a, b[0]))]
if a.negated and b.negated:
return [P(node, double_negated_division, ())]
elif b.negated:
return [P(node, single_negated_division, (a, +b))]
return []
def single_negated_division(root, args):
"""
-a / b -> -(a / b)
a / -b -> -(a / b)
a / -b -> -a / b
"""
a, b = args
# FIXME: "-a/b" results in "-(a/b)", which will cause a loop.
return -(a / b)
return -a / b
MESSAGES[single_negated_division] = \
_('Bring negation outside of the division: -({1} / {2}).')
_('Bring negation outside of the division: -{1} / {2}.')
def double_negated_division(root, args):
......@@ -127,8 +142,11 @@ def double_negated_division(root, args):
"""
a, b = root
return a[0] / b[0]
return +a / +b
MESSAGES[double_negated_division] = \
_('Eliminate top and bottom negation in {1}.')
_('Eliminate top and bottom negation in {0}.')
# TODO: negated multiplication: -a * -b = ab
from itertools import combinations
from ..node import ExpressionLeaf as Leaf, Scope, OP_DIV, OP_MUL, OP_NEG
from .utils import greatest_common_divisor
from ..node import ExpressionLeaf as Leaf, Scope, negate, OP_ADD, OP_DIV, \
OP_MUL, OP_POW
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
def add_numerics(root, args):
def match_add_numerics(node):
"""
Combine two constants to a single constant in an n-ary addition.
......@@ -14,31 +16,57 @@ def add_numerics(root, args):
2 + -3 -> -1
-2 + 3 -> 1
-2 + -3 -> -5
0 + 3 -> 3
0 + -3 -> -3
"""
assert node.is_op(OP_ADD)
p = []
scope = Scope(node)
numerics = []
for n in scope:
if n == 0:
p.append(P(node, remove_zero, (scope, n)))
elif n.is_numeric():
numerics.append(n)
for c0, c1 in combinations(numerics, 2):
p.append(P(node, add_numerics, (scope, c0, c1)))
return p
def remove_zero(root, args):
"""
0 + a -> a
"""
n0, n1, c0, c1 = args
scope, n = args
scope.remove(n)
if c0.is_op(OP_NEG):
c0 = -c0[0].value
else:
c0 = c0.value
return scope.as_nary_node()
if c1.is_op(OP_NEG):
c1 = (-c1[0].value)
else:
c1 = c1.value
scope = Scope(root)
def add_numerics(root, args):
"""
2 + 3 -> 5
2 + -3 -> -1
-2 + 3 -> 1
-2 + -3 -> -5
"""
scope, c0, c1 = args
value = c0.actual_value() + c1.actual_value()
# Replace the left node with the new expression
scope.remove(n0, Leaf(c0 + c1))
scope.replace(c0, Leaf(abs(value)).negate(int(value < 0)))
# Remove the right node
scope.remove(n1)
scope.remove(c1)
return scope.as_nary_node()
MESSAGES[add_numerics] = _('Combine the constants {1} and {2}.')
MESSAGES[add_numerics] = _('Add the constants {2} and {3}.')
#def match_subtract_numerics(node):
......@@ -59,23 +87,41 @@ def match_divide_numerics(node):
Example:
6 / 2 -> 3
3 / 2 -> 3 / 2 # 1.5 would mean a decrease in precision
3 / 2 -> 3 / 2 # 1.5 would mean a decrease in precision
3.0 / 2 -> 1.5
3 / 2.0 -> 1.5
3.0 / 2.0 -> 1.5
3 / 1.0 -> 3 # Exceptional case: division of integer by 1.0 keeps
# integer precision
3 / 1.0 -> 3 # Exceptional case: division of integer by 1.0
# keeps integer precision
2 / 4 -> 1 / 2 # 1 < greatest common divisor <= nominator
4 / 3 -> 1 + 1 / 3 # nominator > denominator
"""
assert node.is_op(OP_DIV)
n, d = node
divide = False
dv = d.value
nv, dv = n.value, d.value
if n.is_int() and d.is_int():
# 6 / 2 -> 3
# 3 / 2 -> 3 / 2
divide = not divmod(n.value, dv)[1]
mod = nv % dv
if not mod:
# 6 / 2 -> 3
# 3 / 2 -> 3 / 2
return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
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))]
elif n.is_numeric() and d.is_numeric():
if d == 1.0:
# 3 / 1.0 -> 3
......@@ -84,14 +130,14 @@ def match_divide_numerics(node):
# 3.0 / 2 -> 1.5
# 3 / 2.0 -> 1.5
# 3.0 / 2.0 -> 1.5
divide = True
return [P(node, divide_numerics, (nv, dv, n.negated + d.negated))]
return [P(node, divide_numerics, (n.value, dv))] if divide else []
return []
def divide_numerics(root, args):
"""
Combine two constants to a single constant in a division.
Combine two divided constants into a single constant.
Examples:
6 / 2 -> 3
......@@ -100,14 +146,48 @@ def divide_numerics(root, args):
3.0 / 2.0 -> 1.5
3 / 1.0 -> 3
"""
n, d = args
n, d, negated = args
return Leaf(n / d)
return Leaf(n / d).negate(negated)
MESSAGES[divide_numerics] = _('Divide constant {1} by constant {2}.')
def reduce_fraction_constants(root, args):
"""
Reduce the nominator and denominator of a fraction with a given greatest
common divisor.
Example:
2 / 4 -> 1 / 2
"""
gcd = args[0]
a, b = root
return Leaf(a.value / gcd).negate(a.negated) \
/ Leaf(b.value / gcd).negate(b.negated)
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
......@@ -119,20 +199,12 @@ def match_multiply_zero(node):
assert node.is_op(OP_MUL)
left, right = node
is_zero = lambda n: n.is_leaf and n.value == 0
if is_zero(left):
negated = right.is_op(OP_NEG)
elif is_zero(right):
negated = left.is_op(OP_NEG)
elif left.is_op(OP_NEG) and is_zero(left[0]):
negated = not right.is_op(OP_NEG)
elif right.is_op(OP_NEG) and is_zero(right[0]):
negated = not left.is_op(OP_NEG)
else:
return []
if (left.is_leaf and left.value == 0) \
or (right.is_leaf and right.value == 0):
return [P(node, multiply_zero, (left.negated + right.negated,))]
return [P(node, multiply_zero, (negated,))]
return []
def multiply_zero(root, args):
......@@ -143,17 +215,48 @@ def multiply_zero(root, args):
0 * -a -> -0
-0 * -a -> 0
"""
negated = args[0]
if negated:
return -Leaf(0)
else:
return Leaf(0)
return negate(Leaf(0), args[0])
MESSAGES[multiply_zero] = _('Multiplication with zero yields zero.')
def match_multiply_one(node):
"""
a * 1 -> a
1 * a -> a
-1 * a -> -a
1 * -a -> -a
-1 * -a -> a
"""
assert node.is_op(OP_MUL)
left, right = node
if left.value == 1:
return [P(node, multiply_one, (right, left))]
if right.value == 1:
return [P(node, multiply_one, (left, right))]
return []
def multiply_one(root, args):
"""
a * 1 -> a
1 * a -> a
-1 * a -> -a
1 * -a -> -a
-1 * -a -> a
"""
a, one = args
return a.negate(one.negated + root.negated)
MESSAGES[multiply_one] = _('Multiplication with one yields the multiplicant.')
def match_multiply_numerics(node):
"""
3 * 2 -> 6
......@@ -164,16 +267,11 @@ def match_multiply_numerics(node):
assert node.is_op(OP_MUL)
p = []
numerics = []
for n in Scope(node):
if n.is_numeric():
numerics.append((n, n.value))
elif n.is_op(OP_NEG) and n[0].is_numeric():
numerics.append((n, -n[0].value))
scope = Scope(node)
numerics = filter(lambda n: n.is_numeric(), scope)
for (n0, v0), (n1, v1) in combinations(numerics, 2):
p.append(P(node, multiply_numerics, (n0, n1, v0, v1)))
for c0, c1 in combinations(numerics, 2):
p.append(P(node, multiply_numerics, (scope, c0, c1)))
return p
......@@ -185,24 +283,46 @@ def multiply_numerics(root, args):
Example:
2 * 3 -> 6
"""
n0, n1, v0, v1 = args
scope = []
value = v0 * v1
if value > 0:
substitution = Leaf(value)
else:
substitution = -Leaf(-value)
scope = Scope(root)
scope, c0, c1 = args
# Replace the left node with the new expression
scope.remove(n0, substitution)
substitution = Leaf(c0.value * c1.value).negate(c0.negated + c1.negated)
scope.replace(c0, substitution)
# Remove the right node
scope.remove(n1)
scope.remove(c1)
return scope.as_nary_node()
MESSAGES[multiply_numerics] = _('Multiply constant {1} with {2}.')
MESSAGES[multiply_numerics] = _('Multiply constant {2} with {3}.')
def match_raise_numerics(node):
"""
2 ^ 3 -> 8
(-2) ^ 3 -> -8
(-2) ^ 2 -> 4
"""
assert node.is_op(OP_POW)
r, e = node
if r.is_numeric() and e.is_numeric() and not e.negated:
return [P(node, raise_numerics, (r, e))]
return []
def raise_numerics(root, args):
"""
2 ^ 3 -> 8
(-2) ^ 3 -> -8
(-2) ^ 2 -> 4
"""
r, e = args
return Leaf(r.value ** e.value).negate(r.negated * e.value)
MESSAGES[raise_numerics] = _('Raise constant {1} with {2}.')
from itertools import combinations
from ..node import Scope, OP_ADD, OP_NEG
from ..possibilities import Possibility as P, MESSAGES
from .numerics import add_numerics
def is_numeric_or_negated_numeric(n):
return n.is_numeric() or (n.is_op(OP_NEG) and n[0].is_numeric())
def match_combine_polynomes(node, verbose=False):
"""
n + exp + m -> exp + (n + m)
k0 * v ^ n + exp + k1 * v ^ n -> exp + (k0 + k1) * v ^ n
"""
assert node.is_op(OP_ADD)
p = []
# Collect all nodes that can be combined:
# a ^ e = 1 * a ^ e
# c * a = c * a ^ 1
# c * a ^ e
# a = 1 * a ^ 1
#
# Identifier nodes of all polynomes, tuple format is:
# (root, exponent, coefficient, literal_coefficient)
polys = []
if verbose: # pragma: nocover
print 'match combine factors:', node
for n in Scope(node):
polynome = n.extract_polynome_properties()
if verbose: # pragma: nocover
print 'n:', n, 'polynome:', polynome
if polynome:
polys.append((n, polynome))
# Each combination of powers of the same value and polynome can be added
if len(polys) >= 2:
for left, right in combinations(polys, 2):
n0, p0 = left
n1, p1 = right
c0, r0, e0 = p0
c1, r1, e1 = p1
# Both numeric root and same exponent -> combine coefficients and
# roots, or: same root and exponent -> combine coefficients.
# TODO: Addition with zero, e.g. a + 0 -> a
if c0 == 1 and c1 == 1 and e0 == 1 and e1 == 1 \
and all(map(is_numeric_or_negated_numeric, [r0, r1])):
# 2 + 3 -> 5
# 2 + -3 -> -1
# -2 + 3 -> 1
# -2 + -3 -> -5
p.append(P(node, add_numerics, (n0, n1, r0, r1)))
elif c0.is_numeric() and c1.is_numeric() and r0 == r1 and e0 == e1:
# 2a + 2a -> 4a
# a + 2a -> 3a
# 2a + a -> 3a
# a + a -> 2a
p.append(P(node, combine_polynomes, (n0, n1, c0, c1, r0, e0)))
return p
def combine_polynomes(root, args):
"""
Combine two multiplications of any polynome in an n-ary plus.
Synopsis:
c0 * a ^ b + c1 * a ^ b -> (c0 + c1) * a ^ b
"""
n0, n1, c0, c1, r, e = args
# a ^ 1 -> a
if e == 1:
power = r
else:
power = r ** e
scope = Scope(root)
# Replace the left node with the new expression:
# (c0 + c1) * a ^ b
# a, b and c are from 'left', d is from 'right'.
scope.remove(n0, (c0 + c1) * power)
# Remove the right node
scope.remove(n1)
return scope.as_nary_node()
from itertools import combinations
from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, \
OP_NEG, OP_MUL, OP_DIV, OP_POW, OP_ADD
OP_MUL, OP_DIV, OP_POW, OP_ADD, negate
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -12,21 +12,23 @@ def match_add_exponents(node):
a * a^q -> a^(1 + q)
a^p * a -> a^(p + 1)
a * a -> a^(1 + 1)
-a * a^q -> -a^(1 + q)
"""
assert node.is_op(OP_MUL)
p = []
powers = {}
scope = Scope(node)
for n in Scope(node):
for n in scope:
# Order powers by their roots, e.g. a^p and a^q are put in the same
# list because of the mutual 'a'
if n.is_identifier():
s = n
s = negate(n, 0)
exponent = L(1)
elif 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, exponent = n
else:
else: # pragma: nocover
continue
s_str = str(s)
......@@ -41,7 +43,7 @@ def match_add_exponents(node):
# create a single power with that root
if len(occurrences) > 1:
for (n0, e1, a0), (n1, e2, a1) in combinations(occurrences, 2):
p.append(P(node, add_exponents, (n0, n1, a0, e1, e2)))
p.append(P(node, add_exponents, (scope, n0, n1, a0, e1, e2)))
return p
......@@ -50,11 +52,12 @@ def add_exponents(root, args):
"""
a^p * a^q -> a^(p + q)
"""
n0, n1, a, p, q = args
scope = Scope(root)
scope, n0, n1, a, p, q = args
# TODO: combine exponent negations
# Replace the left node with the new expression
scope.remove(n0, a ** (p + q))
scope.replace(n0, (a ** (p + q)).negate(n0.negated + n1.negated))
# Remove the right node
scope.remove(n1)
......@@ -62,7 +65,7 @@ def add_exponents(root, args):
return scope.as_nary_node()
MESSAGES[add_exponents] = _('Add the exponents of {1} and {2}.')
MESSAGES[add_exponents] = _('Add the exponents of {2} and {3}.')
def match_subtract_exponents(node):
......@@ -91,6 +94,18 @@ def match_subtract_exponents(node):
return []
def subtract_exponents(root, args):
"""
a^p / a^q -> a^(p - q)
"""
a, p, q = args
return a ** (p - q)
MESSAGES[subtract_exponents] = _('Substract the exponents {2} and {3}.')
def match_multiply_exponents(node):
"""
(a^p)^q -> a^(pq)
......@@ -105,34 +120,102 @@ def match_multiply_exponents(node):
return []
def multiply_exponents(root, args):
"""
(a^p)^q -> a^(pq)
"""
a, p, q = args
return a ** (p * q)
MESSAGES[multiply_exponents] = _('Multiply the exponents {2} and {3}.')
def match_duplicate_exponent(node):
"""
(ab)^p -> a^p * b^p
"""
assert node.is_op(OP_POW)
left, right = node
root, exponent = node
if root.is_op(OP_MUL):
return [P(node, duplicate_exponent, (list(Scope(root)), exponent))]
return []
def duplicate_exponent(root, args):
"""
(ab)^p -> a^p * b^p
(abc)^p -> a^p * b^p * c^p
"""
ab, p = args
result = ab[0] ** p
for b in ab[1:]:
result *= b ** p
return result
MESSAGES[duplicate_exponent] = _('Duplicate the exponent {2}.')
if left.is_op(OP_MUL):
return [P(node, duplicate_exponent, (list(Scope(left)), right))]
def match_raised_fraction(node):
"""
(a / b) ^ p -> a^p / b^p
"""
assert node.is_op(OP_POW)
root, exponent = node
if root.is_op(OP_DIV):
return [P(node, raised_fraction, (root, exponent))]
return []
def raised_fraction(root, args):
"""
(a / b) ^ p -> a^p / b^p
"""
(a, b), p = args
return a ** p / b ** p
MESSAGES[raised_fraction] = _('Apply the exponent {2} to the nominator and'
' denominator of fraction {1}.')
def match_remove_negative_exponent(node):
"""
a^-p -> 1 / a^p
a ^ -p -> 1 / a ^ p
"""
assert node.is_op(OP_POW)
left, right = node
a, p = node
if right.is_op(OP_NEG):
return [P(node, remove_negative_exponent, (left, right[0]))]
if p.negated:
return [P(node, remove_negative_exponent, (a, p))]
return []
def remove_negative_exponent(root, args):
"""
a^-p -> 1 / a^p
"""
a, p = args
return L(1) / a ** p.reduce_negation()
MESSAGES[remove_negative_exponent] = _('Remove negative exponent {2}.')
def match_exponent_to_root(node):
"""
a^(1 / m) -> sqrt(a, m)
......@@ -148,6 +231,16 @@ def match_exponent_to_root(node):
return []
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)
def match_extend_exponent(node):
"""
(a + ... + z)^n -> (a + ... + z)(a + ... + z)^(n - 1) # n > 1
......@@ -176,64 +269,38 @@ def extend_exponent(root, args):
return left * left
def subtract_exponents(root, args):
def match_constant_exponent(node):
"""
a^p / a^q -> a^(p - q)
(a + ... + z)^n -> (a + ... + z)(a + ... + z)^(n - 1) # n > 1
"""
a, p, q = args
return a ** (p - q)
MESSAGES[subtract_exponents] = _('Substract the exponents {2} and {3}.')
assert node.is_op(OP_POW)
def multiply_exponents(root, args):
"""
(a^p)^q -> a^(pq)
"""
a, p, q = args
exponent = node[1]
return a ** (p * q)
if exponent == 0:
return [P(node, remove_power_of_zero, ())]
if exponent == 1:
return [P(node, remove_power_of_one, ())]
MESSAGES[multiply_exponents] = _('Multiply the exponents {2} and {3}.')
return []
def duplicate_exponent(root, args):
def remove_power_of_zero(root, args):
"""
(ab)^p -> a^p * b^p
(abc)^p -> a^p * b^p * c^p
a ^ 0 -> 1
"""
ab, p = args
result = ab[0] ** p
for b in ab[1:]:
result *= b ** p
return result
return L(1)
MESSAGES[duplicate_exponent] = _('Duplicate the exponent {2}.')
MESSAGES[remove_power_of_zero] = _('Power of zero {0} rewrites to 1.')
def remove_negative_exponent(root, args):
def remove_power_of_one(root, args):
"""
a^-p -> 1 / a^p
a ^ 1 -> a
"""
a, p = args
return L(1) / a ** p
MESSAGES[remove_negative_exponent] = _('Remove negative exponent {2}.')
return root[0]
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)
MESSAGES[remove_power_of_one] = _('Remove the power of one in {0}.')
from itertools import product, combinations
from ..node import Scope, OP_ADD, OP_MUL
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
def match_sort_multiplicants(node):
"""
Sort multiplicant factors by swapping
x * 2 -> 2x
"""
assert node.is_op(OP_MUL)
p = []
scope = Scope(node)
for i, n in enumerate(scope[1:]):
left_nb = scope[i]
if n.is_numeric() and not left_nb.is_numeric():
p.append(P(node, move_constant, (scope, n, left_nb)))
return p
def move_constant(root, args):
scope, constant, destination = args
scope.replace(destination, constant * destination)
scope.remove(constant)
return scope.as_nary_node()
MESSAGES[move_constant] = \
_('Move constant {2} to the left of the multiplication {0}.')
def gcd(a, b):
from ..node import ExpressionLeaf as L, OP_MUL, OP_DIV
def greatest_common_divisor(a, b):
"""
Return greatest common divisor using Euclid's Algorithm.
Return greatest common divisor of a and b using Euclid's Algorithm.
"""
while b:
a, b = b, a % b
......@@ -12,7 +15,7 @@ def lcm(a, b):
"""
Return least common multiple of a and b.
"""
return a * b // gcd(a, b)
return a * b // greatest_common_divisor(a, b)
def least_common_multiple(*args):
......@@ -20,3 +23,51 @@ def least_common_multiple(*args):
Return lcm of args.
"""
return reduce(lcm, args)
def is_fraction(node, nominator, denominator):
"""
Check if a node represents the fraction of a given nominator and
denominator.
>>> from ..node import ExpressionLeaf as L
>>> l1, l2, a = L('a'), L(1), L(2)
>>> is_fraction(a / l2, a, 2)
True
>>> is_fraction(l1 / l2 * a, a, 2)
True
>>> is_fraction(l2 / l1 * a, a, 2)
False
"""
if node.is_op(OP_DIV):
nom, denom = node
return nom == nominator and denom == denominator
if node.is_op(OP_MUL):
# 1 / denominator * nominator
# nominator * 1 / denominator
left, right = node
fraction = L(1) / denominator
return (left == nominator and right == fraction) \
or (right == nominator and left == fraction)
return False
def partition(callback, iterable):
"""
Partition an iterable into two parts using a callback that returns a
boolean.
Example:
>>> partition(lambda x: x & 1, range(6))
([1, 3, 5], [0, 2, 4])
"""
a, b = [], []
for item in iterable:
(a if callback(item) else b).append(item)
return a, b
from rules.sort import move_constant
from rules.numerics import reduce_fraction_constants, fraction_to_int_fraction
def pick_suggestion(possibilities):
if not possibilities:
return
# 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]:
break
if not p:
return possibilities[0]
return possibilities[suggestion]
# vim: set fileencoding=utf-8 :
SQRT = '√'
CBRT = '∛'
FORT = '∜'
PI = 'π'
INFINITY = '∞'
SUP = {
'0': '⁰',
'1': '¹',
'2': '²',
'3': '³',
'3': '⁴',
'5': '⁵',
'6': '⁶',
'7': '⁷',
'8': '⁸',
'9': '⁹',
}
DOT = '⋅'
from src.parser import Parser
from tests.parser import ParserWrapper
from src.possibilities import apply_suggestion
def validate(exp, result):
"""
Validate that exp =>* result.
"""
parser = ParserWrapper(Parser)
result = parser.run([result])
return traverse_preorder(parser, exp, result)
def traverse_preorder(parser, exp, result):
"""
Traverse the possibility tree using pre-order traversal.
"""
root = parser.run([exp])
if root.equals(result):
return root
possibilities = parser.parser.possibilities
for p in possibilities:
child = apply_suggestion(root, p)
next_root = traverse_preorder(parser, str(child), result)
if next_root:
return next_root
......@@ -44,16 +44,26 @@ class RulesTestCase(unittest.TestCase):
try:
for i, exp in enumerate(rewrite_chain[:-1]):
self.assertMultiLineEqual(str(rewrite(exp)),
str(rewrite_chain[i+1]))
except AssertionError: # pragma: nocover
print 'rewrite failed: "%s" -> "%s"' \
% (str(exp), str(rewrite_chain[i+1]))
print 'rewrite chain index: %d' % i
print 'rewrite chain: ---'
str(rewrite_chain[i + 1]))
except AssertionError as e: # pragma: nocover
msg = e.args[0]
for i, c in enumerate(rewrite_chain):
print '%2d %s' % (i, str(c))
msg += '-' * 30 + '\n'
print '-' * 30
msg += 'rewrite failed: "%s" -> "%s"\n' \
% (str(exp), str(rewrite_chain[i + 1]))
msg += 'rewrite chain: ---\n'
chain = []
for j, c in enumerate(rewrite_chain):
if i == j:
chain.append('%2d %s <-- error' % (j, str(c)))
else:
chain.append('%2d %s' % (j, str(c)))
e.message = msg + '\n'.join(chain)
e.args = (e.message,) + e.args[1:]
raise
......@@ -11,13 +11,13 @@ class TestB1Ch08(unittest.TestCase):
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')),
('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')),
('3a-8--5-2a', L(3) * 'a' + -L(8) + (--L(5)) + (-L(2) * 'a')),
])
def test_diagnostic_test_application(self):
apply_expressions(Parser, [
('7p+2p', 1, (L(7) + 2) * 'p'),
#('7p-3p', 1, (L(7) - 3) * 'p'),
('7p-3p', 1, (L(7) + -L(3)) * 'p'),
])
......@@ -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))
......
......@@ -12,11 +12,11 @@ class TestCalc(unittest.TestCase):
== N('+', L(1), L(4))
def test_basic_on_exp(self):
expressions = [('4', L(4)),
expressions = [('4', L(4)),
('3+4', L(3) + L(4)),
('3-4', L(3) + -L(4)),
('3/4', L(3) / L(4)),
('-4', -L(4)),
('-4', -L(4)),
('3^4', N('^', L(3), L(4))),
('(2)', L(2))]
......
......@@ -7,9 +7,4 @@ from tests.parser import ParserWrapper
class TestException(unittest.TestCase):
def test_raise(self):
try:
ParserWrapper(Parser).run(['raise'])
except RuntimeError:
return
raise AssertionError('Expected raised RuntimeError!') # pragma: nocover
self.assertRaises(RuntimeError, ParserWrapper(Parser).run, ['raise'])
This diff is collapsed.
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'])
def test_1_d(self):
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',
'-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',
])
def test_2_a(self):
self.assertRewrite([
'(a2b^-1)^3(ab2)',
'(a ^ 2 * (1 / b ^ 1)) ^ 3 * ab ^ 2',
'(a ^ 2 * (1 / b)) ^ 3 * ab ^ 2',
'(a ^ 2 * 1 / b) ^ 3 * ab ^ 2',
'(a ^ 2 / b) ^ 3 * ab ^ 2',
'(a ^ 2) ^ 3 / b ^ 3 * ab ^ 2',
'a ^ (2 * 3) / b ^ 3 * ab ^ 2',
'a ^ 6 / b ^ 3 * ab ^ 2',
'aa ^ 6 / b ^ 3 * b ^ 2',
'a ^ (1 + 6) / b ^ 3 * b ^ 2',
'a ^ 7 / b ^ 3 * b ^ 2',
'b ^ 2 * a ^ 7 / b ^ 3',
'b ^ 2 / b ^ 3 * a ^ 7 / 1',
'b ^ (2 - 3)a ^ 7 / 1',
'b ^ -1 * a ^ 7 / 1',
'1 / b ^ 1 * a ^ 7 / 1',
'1 / b * a ^ 7 / 1',
'a ^ 7 * 1 / b / 1',
'a ^ 7 / b / 1',
'a ^ 7 / b',
])
def test_2_b(self):
self.assertRewrite([
'a3b2a3',
'a ^ (3 + 3)b ^ 2',
'a ^ 6 * b ^ 2',
])
def test_2_c(self):
self.assertRewrite([
'a5+a3',
'a ^ 5 + a ^ 3',
])
def test_2_d(self):
self.assertRewrite([
'a2+a2',
'(1 + 1)a ^ 2',
'2a ^ 2',
])
def test_2_e(self):
self.assertRewrite([
'4b^-2',
'4(1 / b ^ 2)',
'4 * 1 / b ^ 2',
])
def test_2_f(self):
self.assertRewrite([
'(4b) ^ -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)',
])
......@@ -30,25 +30,17 @@ class TestNode(RulesTestCase):
self.assertTrue(N('+', *self.l[:2]).is_op(OP_ADD))
self.assertFalse(N('-', *self.l[:2]).is_op(OP_ADD))
def test_is_op_or_negated(self):
self.assertTrue(N('+', *self.l[:2]).is_op_or_negated(OP_ADD))
self.assertTrue(N('-', N('+', *self.l[:2])).is_op_or_negated(OP_ADD))
self.assertFalse(N('-', *self.l[:2]).is_op_or_negated(OP_ADD))
self.assertFalse(self.l[0].is_op_or_negated(OP_ADD))
def test_is_leaf(self):
self.assertTrue(L(2).is_leaf)
self.assertFalse(N('+', *self.l[:2]).is_leaf)
def test_is_leaf_or_negated(self):
self.assertTrue(L(2).is_leaf_or_negated())
self.assertTrue(N('-', L(2)).is_leaf_or_negated())
self.assertFalse(N('+', *self.l[:2]).is_leaf_or_negated())
self.assertFalse(N('-', N('+', *self.l[:2])).is_leaf_or_negated())
def test_is_power(self):
self.assertTrue(N('^', *self.l[:2]).is_power())
self.assertFalse(N('+', *self.l[:2]).is_power())
self.assertTrue(N('^', *self.l[2:]).is_power())
self.assertFalse(N('+', *self.l[2:]).is_power())
def test_is_power_exponent(self):
self.assertTrue(N('^', *self.l[2:]).is_power(5))
self.assertFalse(N('^', *self.l[2:]).is_power(2))
def test_is_nary(self):
self.assertTrue(N('+', *self.l[:2]).is_nary())
......@@ -173,6 +165,13 @@ class TestNode(RulesTestCase):
m0, m1 = tree('-5 * -3,-5 * 6')
self.assertFalse(m0.equals(m1))
def test_equals_ignore_negation(self):
p0, p1 = tree('-(a + b), a + b')
self.assertTrue(p0.equals(p1, ignore_negation=True))
a0, a1 = tree('-a,a')
self.assertTrue(a0.equals(a1, ignore_negation=True))
def test_scope___init__(self):
self.assertEqual(self.scope.node, self.n)
self.assertEqual(self.scope.nodes, [self.a, self.b, self.cd])
......@@ -185,14 +184,14 @@ class TestNode(RulesTestCase):
self.scope.remove(self.cd)
self.assertEqual(self.scope.nodes, [self.a, self.b])
def test_scope_remove_replace(self):
self.scope.remove(self.cd, self.f)
self.assertEqual(self.scope.nodes, [self.a, self.b, self.f])
def test_scope_remove_error(self):
with self.assertRaises(ValueError):
self.scope.remove(self.f)
def test_scope_replace(self):
self.scope.replace(self.cd, self.f)
self.assertEqual(self.scope.nodes, [self.a, self.b, self.f])
def test_nary_node(self):
a, b, c, d = tree('a,b,c,d')
......@@ -205,3 +204,8 @@ class TestNode(RulesTestCase):
def test_scope_as_nary_node(self):
self.assertEqualNodes(self.scope.as_nary_node(), self.n)
def test_scope_as_nary_node_negated(self):
n = tree('-(a + b)')
self.assertEqualNodes(Scope(n).as_nary_node(), n)
self.assertEqualNodes(Scope(-n).as_nary_node(), -n)
......@@ -4,6 +4,8 @@ import unittest
from src.parser import Parser
from src.node import ExpressionNode as Node, ExpressionLeaf as Leaf
from tests.parser import ParserWrapper, run_expressions, line, graph
from tests.rulestestcase import tree
from src.rules.goniometry import sin, cos
class TestParser(unittest.TestCase):
......@@ -15,11 +17,42 @@ class TestParser(unittest.TestCase):
run_expressions(Parser, [('a', Leaf('a'))])
def test_graph(self):
assert graph(Parser, '4a') == ("""
self.assertEqual(graph(Parser, '4a'), ("""
*
╭┴╮
4 a
""").replace('\n ', '\n')[1:-1]
""").replace('\n ', '\n')[1:-1])
def test_line(self):
self.assertEqual(line(Parser, '4-a'), '4 - a')
def test_reset_after_failure(self):
parser = ParserWrapper(Parser)
parser.run(['-(3a+6b)'])
possibilities1 = parser.parser.possibilities
self.assertNotEqual(possibilities1, [])
parser.run(['5+2*6'])
possibilities2 = parser.parser.possibilities
self.assertNotEqual(possibilities2, [])
self.assertNotEqual(possibilities1, possibilities2)
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):
root, x = tree('sin x, x')
self.assertEqual(root, sin(x))
self.assertEqual(tree('sin x ^ 2'), sin(x) ** 2)
self.assertEqual(tree('sin(x) ^ 2'), sin(x) ** 2)
self.assertEqual(tree('sin (x) ^ 2'), sin(x) ** 2)
self.assertEqual(tree('sin(x ^ 2)'), sin(x ** 2))
self.assertEqual(tree('sin cos x'), sin(cos(x)))
self.assertEqual(tree('sin cos x ^ 2'), sin(cos(x)) ** 2)
......@@ -45,7 +45,7 @@ class TestPossibilities(unittest.TestCase):
possibilities = parser.parser.possibilities
self.assertEqual('\n'.join([repr(pos) for pos in possibilities]),
'<Possibility root="3 + 4" handler=add_numerics' \
' args=(3, 4, 3, 4)>')
' args=(<Scope of "3 + 4">, 3, 4)>')
def test_multiple_runs(self):
parser = ParserWrapper(Parser)
......@@ -53,21 +53,19 @@ class TestPossibilities(unittest.TestCase):
possibilities = parser.parser.possibilities
self.assertEqual('\n'.join([repr(pos) for pos in possibilities]),
'<Possibility root="1 + 2" handler=add_numerics' \
' args=(1, 2, 1, 2)>')
' args=(<Scope of "1 + 2">, 1, 2)>')
# Keep previous possibilities (skip whitespace lines)
# Remove previous possibilities after second run() call.
parser.run(['', ' '])
possibilities = parser.parser.possibilities
self.assertEqual('\n'.join([repr(pos) for pos in possibilities]),
'<Possibility root="1 + 2" handler=add_numerics' \
' args=(1, 2, 1, 2)>')
self.assertEqual(possibilities, [])
# Overwrite previous possibilities with new ones
parser.run(['3+4'])
possibilities = parser.parser.possibilities
self.assertEqual('\n'.join([repr(pos) for pos in possibilities]),
'<Possibility root="3 + 4" handler=add_numerics' \
' args=(3, 4, 3, 4)>')
' args=(<Scope of "3 + 4">, 3, 4)>')
def test_filter_duplicates(self):
a, b = ab = tree('a + b')
......
from src.rules.fractions import match_constant_division, division_by_one, \
division_of_zero, division_by_self, match_add_constant_fractions, \
equalize_denominators, add_nominators
equalize_denominators, add_nominators, match_multiply_fractions, \
multiply_fractions, multiply_with_fraction, match_divide_fractions, \
divide_fraction, divide_by_fraction, match_equal_fraction_parts, \
divide_fraction_parts, extract_divided_roots
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -51,12 +55,14 @@ class TestRulesFractions(RulesTestCase):
n0, n1 = root = l1 / l2 + l3 / l4
possibilities = match_add_constant_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (n0, n1, 4))])
[P(root, equalize_denominators, (Scope(root), n0, n1, 4)),
P(root, equalize_denominators, (Scope(root), n0, n1, 8))])
(((n0, n1), n2), n3), n4 = root = a + l1 / l2 + b + l3 / l4 + c
possibilities = match_add_constant_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (n1, n3, 4))])
[P(root, equalize_denominators, (Scope(root), n1, n3, 4)),
P(root, equalize_denominators, (Scope(root), n1, n3, 8))])
n0, n1 = root = l2 / l4 + l3 / l4
possibilities = match_add_constant_fractions(root)
......@@ -74,7 +80,8 @@ class TestRulesFractions(RulesTestCase):
(((n0, n1), n2), n3), n4 = root = a + l2 / l2 + b + (-l3 / l4) + c
possibilities = match_add_constant_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (n1, n3, 4))])
[P(root, equalize_denominators, (Scope(root), n1, n3, 4)),
P(root, equalize_denominators, (Scope(root), n1, n3, 8))])
(((n0, n1), n2), n3), n4 = root = a + l2 / l4 + b + (-l3 / l4) + c
possibilities = match_add_constant_fractions(root)
......@@ -85,22 +92,23 @@ class TestRulesFractions(RulesTestCase):
a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
n0, n1 = root = l1 / l2 + l3 / l4
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
l2 / l4 + l3 / l4)
self.assertEqualNodes(equalize_denominators(root,
(Scope(root), n0, n1, 4)), l2 / l4 + l3 / l4)
n0, n1 = root = a / l2 + b / l4
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
(l2 * a) / l4 + b / l4)
self.assertEqualNodes(equalize_denominators(root,
(Scope(root), n0, n1, 4)), (l2 * a) / l4 + b /
l4)
#2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
n0, n1 = root = l1 / l2 + (-l3 / l4)
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
l2 / l4 + (-l3 / l4))
self.assertEqualNodes(equalize_denominators(root,
(Scope(root), n0, n1, 4)), l2 / l4 + (-l3 / l4))
#2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
n0, n1 = root = a / l2 + (-b / l4)
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
(l2 * a) / l4 + (-b / l4))
self.assertEqualNodes(equalize_denominators(root,
(Scope(root), n0, n1, 4)), (l2 * a) / l4 + (-b / l4))
def test_add_nominators(self):
a, b, c = tree('a,b,c')
......@@ -118,3 +126,118 @@ class TestRulesFractions(RulesTestCase):
n0, n1 = root = a / -b + -c / -b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + -c) / -b)
def test_match_multiply_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b * (c / d)')
self.assertEqualPos(match_multiply_fractions(root),
[P(root, multiply_fractions, (Scope(root), ab, cd))])
(ab, e), cd = root = tree('a / b * e * (c / d)')
self.assertEqualPos(match_multiply_fractions(root),
[P(root, multiply_fractions, (Scope(root), ab, cd)),
P(root, multiply_with_fraction, (Scope(root), e, ab)),
P(root, multiply_with_fraction, (Scope(root), e, cd))])
def test_multiply_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b * (c / d)')
self.assertEqual(multiply_fractions(root, (Scope(root), ab, cd)),
a * c / (b * d))
(ab, e), cd = root = tree('a / b * e * (c / d)')
self.assertEqual(multiply_fractions(root, (Scope(root), ab, cd)),
a * c / (b * d) * e)
def test_match_divide_fractions(self):
(a, b), c = root = tree('a / b / c')
self.assertEqualPos(match_divide_fractions(root),
[P(root, divide_fraction, (a, b, c))])
root = tree('a / (b / c)')
self.assertEqualPos(match_divide_fractions(root),
[P(root, divide_by_fraction, (a, b, c))])
def test_divide_fraction(self):
(a, b), c = root = tree('a / b / c')
self.assertEqual(divide_fraction(root, (a, b, c)), a / (b * c))
def test_divide_by_fraction(self):
a, (b, c) = root = tree('a / (b / c)')
self.assertEqual(divide_by_fraction(root, (a, b, c)), a * c / b)
def test_match_equal_fraction_parts(self):
(a, b), (c, a) = root = tree('ab / (ca)')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a, b], [c, a], 0, 1))])
(a, b), a = root = tree('ab / a')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a, b], [a], 0, 0))])
a, (a, b) = root = tree('a / (ab)')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a], [a, b], 0, 0))])
root = tree('abc / (cba)')
((a, b), c) = root[0]
s0, s1 = [a, b, c], [c, b, a]
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, s0, s1, 0, 2)),
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))])
(ap, b), aq = root = tree('a ^ p * b / a ^ q')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [ap, b], [aq], 0, 0))])
(a, b), aq = root = tree('a * b / a ^ q')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [a, b], [aq], 0, 0))])
(ap, b), a = root = tree('a ^ p * b / a')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [ap, b], [a], 0, 0))])
def test_divide_fraction_parts(self):
(a, b), (c, a) = root = tree('ab / (ca)')
result = divide_fraction_parts(root, (a, [a, b], [c, a], 0, 1))
self.assertEqual(result, b / c)
(a, b), a = root = tree('ab / a')
result = divide_fraction_parts(root, (a, [a, b], [a], 0, 0))
self.assertEqual(result, b / 1)
root, l1 = tree('a / (ab), 1')
a, (a, b) = root
result = divide_fraction_parts(root, (a, [a], [a, b], 0, 0))
self.assertEqual(result, l1 / b)
root = tree('abc / (cba)')
((a, b), c) = root[0]
result = divide_fraction_parts(root, (a, [a, b, c], [c, b, a], 0, 2))
self.assertEqual(result, b * c / (c * b))
result = divide_fraction_parts(root, (b, [a, b, c], [c, b, a], 1, 1))
self.assertEqual(result, a * c / (c * a))
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')
result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
self.assertEqual(result, -b / 1)
def test_extract_divided_roots(self):
r, a = tree('a ^ p * b / a ^ q, a')
((a, p), b), (a, q) = (ap, b), aq = r
self.assertEqual(extract_divided_roots(r, (a, [ap, b], [aq], 0, 0)),
a ** p / a ** q * b / 1)
r = tree('a * b / a ^ q, a')
self.assertEqual(extract_divided_roots(r, (a, [a, b], [aq], 0, 0)),
a / a ** q * b / 1)
r = tree('a ^ p * b / a, a')
self.assertEqual(extract_divided_roots(r, (a, [ap, b], [a], 0, 0)),
a ** p / a * b / 1)
# vim: set fileencoding=utf-8 :
from src.rules.goniometry import sin, cos, tan, match_add_quadrants, \
add_quadrants, match_negated_parameter, negated_sinus_parameter, \
negated_cosinus_parameter, match_standard_radian, standard_radian, \
is_pi_frac
from src.node import PI, OP_SIN, OP_COS, OP_TAN
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
from src.rules import goniometry
import doctest
class TestRulesGoniometry(RulesTestCase):
def test_doctest(self):
self.assertEqual(doctest.testmod(m=goniometry)[0], 0)
def test_match_add_quadrants(self):
root = tree('sin t ^ 2 + cos t ^ 2')
possibilities = match_add_quadrants(root)
self.assertEqualPos(possibilities, [P(root, add_quadrants, ())])
def test_add_quadrants(self):
self.assertEqual(add_quadrants(None, ()), 1)
def test_match_negated_parameter(self):
s, c = tree('sin -t, cos -t')
t = s[0]
self.assertEqualPos(match_negated_parameter(s), \
[P(s, negated_sinus_parameter, (t,))])
self.assertEqualPos(match_negated_parameter(c), \
[P(c, negated_cosinus_parameter, (t,))])
def test_negated_sinus_parameter(self):
s = tree('sin -t')
t = s[0]
self.assertEqual(negated_sinus_parameter(s, (t,)), -sin(+t))
def test_negated_cosinus_parameter(self):
c = tree('cos -t')
t = c[0]
self.assertEqual(negated_cosinus_parameter(c, (t,)), cos(+t))
def test_is_pi_frac(self):
l1, pi = tree('1,' + PI)
self.assertTrue(is_pi_frac(l1 / 2 * pi, 2))
self.assertFalse(is_pi_frac(l1 / 2 * pi, 3))
self.assertFalse(is_pi_frac(l1 * pi, 3))
def test_match_standard_radian(self):
s, c, t = tree('sin(1 / 6 * pi), cos(1 / 2 * pi), tan(0)')
self.assertEqualPos(match_standard_radian(s), \
[P(s, standard_radian, (OP_SIN, 1))])
self.assertEqualPos(match_standard_radian(c), \
[P(c, standard_radian, (OP_COS, 4))])
self.assertEqualPos(match_standard_radian(t), \
[P(t, standard_radian, (OP_TAN, 0))])
def test_standard_radian(self):
l0, l1, sq3, pi6, pi4, pi2 = tree('0,1,sqrt(3),1/6*pi,1/4*pi,1/2*pi')
self.assertEqual(standard_radian(sin(pi6), (OP_SIN, 1)), l1 / 2)
self.assertEqual(standard_radian(sin(pi2), (OP_SIN, 4)), 1)
self.assertEqual(standard_radian(cos(l0), (OP_COS, 0)), 1)
self.assertEqual(standard_radian(tan(pi4), (OP_TAN, 3)), sq3)
from src.rules.groups import match_combine_groups, combine_groups
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -6,64 +7,88 @@ from tests.rulestestcase import RulesTestCase, tree
class TestRulesGroups(RulesTestCase):
def test_match_combine_groups_no_const(self):
a0, a1 = root = tree('a + a')
root, l1 = tree('a + a,1')
a0, a1 = root
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (Scope(root), l1, a0, a0,
l1, a1, a1))])
def test_match_combine_groups_negation(self):
root, l1 = tree('-a + a,1')
a0, a1 = root
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (1, a0, a0, 1, a1, a1))])
[P(root, combine_groups, (Scope(root), -l1, +a0, a0,
l1, a1, a1))])
def test_match_combine_groups_single_const(self):
a0, mul = root = tree('a + 2a')
root, l1 = tree('a + 2a,1')
a0, mul = root
l2, a1 = mul
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (1, a0, a0, l2, a1, mul))])
[P(root, combine_groups, (Scope(root), l1, a0, a0,
l2, a1, mul))])
def test_match_combine_groups_two_const(self):
((l2, a0), b), (l3, a1) = (m0, b), m1 = root = tree('2a + b + 3a')
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (l2, a0, m0, l3, a1, m1))])
[P(root, combine_groups, (Scope(root), l2, a0, m0,
l3, a1, m1))])
def test_match_combine_groups_n_const(self):
((l2, a0), (l3, a1)), (l4, a2) = (m0, m1), m2 = root = tree('2a+3a+4a')
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (l2, a0, m0, l3, a1, m1)),
P(root, combine_groups, (l2, a0, m0, l4, a2, m2)),
P(root, combine_groups, (l3, a1, m1, l4, a2, m2))])
[P(root, combine_groups, (Scope(root), l2, a0, m0,
l3, a1, m1)),
P(root, combine_groups, (Scope(root), l2, a0, m0,
l4, a2, m2)),
P(root, combine_groups, (Scope(root), l3, a1, m1,
l4, a2, m2))])
def test_match_combine_groups_identifier_group_no_const(self):
ab0, ab1 = root = tree('ab + ab')
root, l1 = tree('ab + ab,1')
ab0, ab1 = root
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (1, ab0, ab0, 1, ab1, ab1))])
[P(root, combine_groups, (Scope(root), l1, ab0, ab0,
l1, ab1, ab1))])
def test_match_combine_groups_identifier_group_single_const(self):
m0, m1 = root = tree('ab + 2ab')
root, l1 = tree('ab + 2ab,1')
m0, m1 = root
(l2, a), b = m1
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (1, m0, m0, l2, a * b, m1))])
[P(root, combine_groups, (Scope(root), l1, m0, m0,
l2, a * b, m1))])
def test_match_combine_groups_identifier_group_unordered(self):
m0, m1 = root = tree('ab + ba')
root, l1 = tree('ab + ba,1')
m0, m1 = root
b, a = m1
possibilities = match_combine_groups(root)
self.assertEqualPos(possibilities,
[P(root, combine_groups, (1, m0, m0, 1, b * a, m1))])
[P(root, combine_groups, (Scope(root), l1, m0, m0,
l1, b * a, m1))])
def test_combine_groups_simple(self):
root, l1 = tree('a + a,1')
a0, a1 = root
self.assertEqualNodes(combine_groups(root, (1, a0, a0, 1, a1, a1)),
self.assertEqualNodes(combine_groups(root,
(Scope(root), l1, a0, a0, l1, a1, a1)),
(l1 + 1) * a0)
def test_combine_groups_nary(self):
......@@ -71,5 +96,6 @@ class TestRulesGroups(RulesTestCase):
abb, ba = root
ab, b = abb
self.assertEqualNodes(combine_groups(root, (1, ab, ab, 1, ba, ba)),
self.assertEqualNodes(combine_groups(root,
(Scope(root), l1, ab, ab, l1, ba, ba)),
(l1 + 1) * ab + b)
from src.rules.negation import match_negated_division, \
single_negated_division, double_negated_division
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
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
class TestRulesNegation(RulesTestCase):
def test_match_negated_factor(self):
a, b = root = tree('a * -b')
self.assertEqualPos(match_negated_factor(root),
[P(root, negated_factor, (Scope(root), b))])
(a, b), c = root = tree('a * -b * -c')
scope = Scope(root)
self.assertEqualPos(match_negated_factor(root),
[P(root, negated_factor, (scope, b)),
P(root, negated_factor, (scope, c))])
def test_negated_factor(self):
a, b = root = tree('a * -b')
self.assertEqualNodes(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)
def test_match_negate_polynome(self):
root = tree('--a')
self.assertEqualPos(match_negate_polynome(root),
[P(root, double_negation, ())])
root = tree('-(a + b)')
self.assertEqualPos(match_negate_polynome(root),
[P(root, negate_polynome, ())])
def test_double_negation(self):
root = tree('--a')
self.assertEqualNodes(double_negation(root, ()), ++root)
def test_negate_polynome(self):
a, b = root = tree('-(a + b)')
self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
a, b = root = tree('-(a - b)')
self.assertEqualNodes(negate_polynome(root, ()), -a + -b)
def test_match_negated_division_none(self):
self.assertEqual(match_negated_division(tree('1 / 2')), [])
def test_match_negated_division_single(self):
l1, l2 = root = tree('-1 / 2')
possibilities = match_negated_division(root)
self.assertEqualPos(possibilities,
[P(root, single_negated_division, (l1[0], l2))])
self.assertEqualPos(match_negated_division(root), [])
l1, l2 = root = tree('1 / -2')
possibilities = match_negated_division(root)
self.assertEqualPos(possibilities,
[P(root, single_negated_division, (l1, l2[0]))])
[P(root, single_negated_division, (l1, +l2))])
def test_match_negated_division_double(self):
root = tree('-1 / -2')
possibilities = match_negated_division(root)
self.assertEqualPos(possibilities,
[P(root, double_negated_division, (root,))])
[P(root, double_negated_division, ())])
def test_single_negated_division(self):
l1, l2 = root = tree('-1 / 2')
self.assertEqualNodes(single_negated_division(root, (l1[0], l2)),
-(l1[0] / l2))
l1, l2 = root = tree('1 / -2')
self.assertEqualNodes(single_negated_division(root, (l1, l2[0])),
-(l1 / l2[0]))
self.assertEqualNodes(single_negated_division(root, (l1, +l2)),
-l1 / +l2)
def test_double_negated_division(self):
l1, l2 = root = tree('-1 / -2')
self.assertEqualNodes(double_negated_division(root, (root,)),
l1[0] / l2[0])
self.assertEqualNodes(double_negated_division(root, ()),
+l1 / +l2)
from src.rules.numerics import add_numerics, match_divide_numerics, \
divide_numerics, match_multiply_numerics, multiply_numerics
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, \
raise_numerics
from src.node import ExpressionLeaf as L, Scope
from src.possibilities import Possibility as P
from src.node import ExpressionLeaf as L
from tests.rulestestcase import RulesTestCase, tree
class TestRulesNumerics(RulesTestCase):
def test_match_add_numerics(self):
l1, l2 = root = tree('1 + 2')
possibilities = match_add_numerics(root)
self.assertEqualPos(possibilities,
[P(root, add_numerics, (Scope(root), l1, l2))])
(l1, b), l2 = root = tree('1 + b + 2')
possibilities = match_add_numerics(root)
self.assertEqualPos(possibilities,
[P(root, add_numerics, (Scope(root), l1, l2))])
def test_add_numerics(self):
l0, a, l1 = tree('1,a,2')
self.assertEqual(add_numerics(l0 + l1, (l0, l1, L(1), L(2))), 3)
self.assertEqual(add_numerics(l0 + a + l1, (l0, l1, L(1), L(2))),
L(3) + a)
root = l0 + l1
self.assertEqual(add_numerics(root, (Scope(root), l0, l1)), 3)
root = l0 + a + l1
self.assertEqual(add_numerics(root, (Scope(root), l0, l1)), L(3) + a)
def test_add_numerics_negations(self):
l0, a, l1 = tree('1,a,2')
l1, a, l2 = tree('1,a,2')
ml1, ml2 = -l1, -l2
self.assertEqual(add_numerics(-l0 + l1, (-l0, l1, -L(1), L(2))), 1)
self.assertEqual(add_numerics(l0 + -l1, (l0, -l1, L(1), -L(2))), -1)
self.assertEqual(add_numerics(l0 + a + -l1, (l0, -l1, L(1), -L(2))),
L(-1) + a)
r = ml1 + l2
self.assertEqual(add_numerics(r, (Scope(r), ml1, l2)), 1)
r = l1 + ml2
self.assertEqual(add_numerics(r, (Scope(r), l1, ml2)), -1)
def test_match_divide_numerics(self):
a, b, i2, i3, i6, f1, f2, f3 = tree('a,b,2,3,6,1.0,2.0,3.0')
a, b, i2, i3, i4, i6, f1, f2, f3 = tree('a,b,2,3,4,6,1.0,2.0,3.0')
root = i6 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (6, 2))])
[P(root, divide_numerics, (6, 2, 0))])
root = -i6 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (6, 2, 1))])
root = i3 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities, [])
self.assertEqualPos(possibilities,
[P(root, fraction_to_int_fraction, (1, 1, 2))])
root = i2 / i4
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, reduce_fraction_constants, (2,))])
root = f3 / i2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3.0, 2))])
[P(root, divide_numerics, (3.0, 2, 0))])
root = i3 / f2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3, 2.0))])
[P(root, divide_numerics, (3, 2.0, 0))])
root = f3 / f2
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3.0, 2.0))])
[P(root, divide_numerics, (3.0, 2.0, 0))])
root = i3 / f1
possibilities = match_divide_numerics(root)
self.assertEqualPos(possibilities,
[P(root, divide_numerics, (3, 1))])
[P(root, divide_numerics, (3, 1, 0))])
root = a / b
possibilities = match_divide_numerics(root)
......@@ -61,55 +87,81 @@ class TestRulesNumerics(RulesTestCase):
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)), 3)
self.assertEqual(divide_numerics(f3 / i2, (3.0, 2)), 1.5)
self.assertEqual(divide_numerics(i3 / f2, (3, 2.0)), 1.5)
self.assertEqual(divide_numerics(f3 / f2, (3.0, 2.0)), 1.5)
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)
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_match_multiply_numerics(self):
i2, i3, i6, f2, f3, f6 = tree('2,3,6,2.0,3.0,6.0')
root = i3 * i2
self.assertEqual(match_multiply_numerics(root),
[P(root, multiply_numerics, (i3, i2, 3, 2))])
[P(root, multiply_numerics, (Scope(root), i3, i2))])
root = f3 * i2
self.assertEqual(match_multiply_numerics(root),
[P(root, multiply_numerics, (f3, i2, 3.0, 2))])
[P(root, multiply_numerics, (Scope(root), f3, i2))])
root = i3 * f2
self.assertEqual(match_multiply_numerics(root),
[P(root, multiply_numerics, (i3, f2, 3, 2.0))])
[P(root, multiply_numerics, (Scope(root), i3, f2))])
root = f3 * f2
self.assertEqual(match_multiply_numerics(root),
[P(root, multiply_numerics, (f3, f2, 3.0, 2.0))])
[P(root, multiply_numerics, (Scope(root), f3, f2))])
def test_multiply_numerics(self):
a, b, i2, i3, i6, f2, f3, f6 = tree('a,b,2,3,6,2.0,3.0,6.0')
self.assertEqual(multiply_numerics(i3 * i2, (i3, i2, 3, 2)), 6)
self.assertEqual(multiply_numerics(f3 * i2, (f3, i2, 3.0, 2)), 6.0)
self.assertEqual(multiply_numerics(i3 * f2, (i3, f2, 3, 2.0)), 6.0)
self.assertEqual(multiply_numerics(f3 * f2, (f3, f2, 3.0, 2.0)), 6.0)
root = i3 * i2
self.assertEqual(multiply_numerics(root, (Scope(root), i3, i2)), 6)
root = f3 * i2
self.assertEqual(multiply_numerics(root, (Scope(root), f3, i2)), 6.0)
root = i3 * f2
self.assertEqual(multiply_numerics(root, (Scope(root), i3, f2)), 6.0)
root = f3 * f2
self.assertEqual(multiply_numerics(root, (Scope(root), f3, f2)), 6.0)
self.assertEqualNodes(multiply_numerics(a * i3 * i2 * b, (i3, i2, 3, 2)),
a * 6 * b)
root = a * i3 * i2 * b
self.assertEqualNodes(multiply_numerics(root,
(Scope(root), i3, i2)), a * 6 * b)
def test_multiply_numerics_negation(self):
l1_neg, l2 = root = tree('-1 * 2')
self.assertEqualNodes(multiply_numerics(root, (l1_neg, l2, -1, 2)), -l2)
root, l6 = tree('1 - 2 * 3,6')
l1, neg = root
l2, l3 = mul = neg[0]
self.assertEqualNodes(multiply_numerics(mul, (l2, l3, 2, 3)), l6)
self.assertEqualNodes(multiply_numerics(root, (Scope(root), l1_neg,
l2)), -l2)
l1, mul = root = tree('1 + -2 * 3')
root, l6 = tree('1 + -2 * 3,6')
l1, mul = root
l2_neg, l3 = mul
self.assertEqualNodes(multiply_numerics(mul, (l2_neg, l3, -2, 3)), -l6)
self.assertEqualNodes(multiply_numerics(mul, (Scope(mul),
l2_neg, l3)), -l6)
root, l30 = tree('-5 * x ^ 2 - -15x - 5 * 6,30')
rest, mul_neg = root
l5_neg, l6 = mul = mul_neg[0]
self.assertEqualNodes(multiply_numerics(mul, (l5_neg, l6, 5, 6)), l30)
rest, mul = root
l5_neg, l6 = mul
self.assertEqualNodes(multiply_numerics(mul, (Scope(mul),
l5_neg, l6)), -l30)
def test_raise_numerics(self):
l1, l2 = root = tree('2 ^ 3')
self.assertEqualNodes(raise_numerics(root, (l1, l2)), L(8))
l1_neg, l2 = root = tree('(-2) ^ 2')
self.assertEqualNodes(raise_numerics(root, (l1_neg, l2)), --L(4))
l1_neg, l2 = root = tree('(-2) ^ 3')
self.assertEqualNodes(raise_numerics(root, (l1_neg, l2)), ---L(8))
from src.rules.poly import match_combine_polynomes, combine_polynomes
from src.rules.numerics import add_numerics
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
class TestRulesPoly(RulesTestCase):
def test_identifiers_basic(self):
a1, a2 = root = tree('a+a')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 1, 1, 'a', 1))])
def test_identifiers_normal(self):
a1, a2 = root = tree('a+2a')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 1, 2, 'a', 1))])
def test_identifiers_reverse(self):
a1, a2 = root = tree('a+a*2')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 1, 2, a1, 1))])
def test_identifiers_exponent(self):
a1, a2 = root = tree('a2+a2')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 1, 1, 'a', 2))])
def test_identifiers_coeff_exponent_left(self):
a1, a2 = root = tree('2a3+a3')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 2, 1, 'a', 3))])
def test_identifiers_coeff_exponent_both(self):
a1, a2 = root = tree('2a3+2a3')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 2, 2, 'a', 3))])
def test_basic_subexpressions(self):
a_b, c, d = tree('a+b,c,d')
left, right = root = tree('(a+b)^d + (a+b)^d')
self.assertEqual(left, right)
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (left, right, 1, 1, a_b, d))])
left, right = root = tree('5(a+b)^d + 7(a+b)^d')
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (left, right, 5, 7, a_b, d))])
# TODO: Move to other strategy
#left, right = root = tree('c(a+b)^d + c(a+b)^d')
#self.assertEqual(left, right)
#possibilities = match_combine_polynomes(root)
#self.assertEqualPos(possibilities,
# [P(root, combine_polynomes, (left, right, c, c, a_b, d))])
def test_match_add_numerics(self):
l0, l1, l2 = tree('0,1,2')
root = l0 + l1 + l2
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, add_numerics, (l0, l1, l0, l1)),
P(root, add_numerics, (l0, l2, l0, l2)),
P(root, add_numerics, (l1, l2, l1, l2))])
def test_match_add_numerics_explicit_powers(self):
l0, l1, l2 = tree('0^1,1*1,1*2^1')
root = l0 + l1 + l2
possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities,
[P(root, add_numerics, (l0, l1, l0[0], l1[1])),
P(root, add_numerics, (l0, l2, l0[0], l2[1][0])),
P(root, add_numerics, (l1, l2, l1[1], l2[1][0]))])
def test_combine_polynomes(self):
# 2a + 3a -> (2 + 3) * a
l0, a, l1, l2 = tree('2,a,3,1')
root = l0 * a + l1 * a
left, right = root
replacement = combine_polynomes(root, (left, right, l0, l1, a, 1))
self.assertEqualNodes(replacement, (l0 + l1) * a)
# a + 3a -> (1 + 3) * a
root = a + l1 * a
left, right = root
replacement = combine_polynomes(root, (left, right, l2, l1, a, 1))
self.assertEqualNodes(replacement, (l2 + l1) * a)
# 2a + a -> (2 + 1) * a
root = l0 * a + a
left, right = root
replacement = combine_polynomes(root, (left, right, l0, l2, a, 1))
self.assertEqualNodes(replacement, (l0 + 1) * a)
# a + a -> (1 + 1) * a
root = a + a
left, right = root
replacement = combine_polynomes(root, (left, right, l2, l2, a, 1))
self.assertEqualNodes(replacement, (l2 + 1) * a)
......@@ -2,10 +2,12 @@ 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_raised_fraction, raised_fraction, \
match_remove_negative_exponent, remove_negative_exponent, \
match_exponent_to_root, exponent_to_root
match_exponent_to_root, exponent_to_root, \
match_constant_exponent, remove_power_of_zero, remove_power_of_one
from src.node import Scope, ExpressionNode as N
from src.possibilities import Possibility as P
from src.node import ExpressionNode as N
from tests.rulestestcase import RulesTestCase, tree
......@@ -17,7 +19,7 @@ class TestRulesPowers(RulesTestCase):
possibilities = match_add_exponents(root)
self.assertEqualPos(possibilities,
[P(root, add_exponents, (n0, n1, a, p, q))])
[P(root, add_exponents, (Scope(root), n0, n1, a, p, q))])
def test_match_add_exponents_ternary(self):
a, p, q, r = tree('a,p,q,r')
......@@ -25,9 +27,9 @@ class TestRulesPowers(RulesTestCase):
possibilities = match_add_exponents(root)
self.assertEqualPos(possibilities,
[P(root, add_exponents, (n0, n1, a, p, q)),
P(root, add_exponents, (n0, n2, a, p, r)),
P(root, add_exponents, (n1, n2, a, q, r))])
[P(root, add_exponents, (Scope(root), n0, n1, a, p, q)),
P(root, add_exponents, (Scope(root), n0, n2, a, p, r)),
P(root, add_exponents, (Scope(root), n1, n2, a, q, r))])
def test_match_add_exponents_multiple_identifiers(self):
a, b, p, q = tree('a,b,p,q')
......@@ -35,8 +37,24 @@ class TestRulesPowers(RulesTestCase):
possibilities = match_add_exponents(root)
self.assertEqualPos(possibilities,
[P(root, add_exponents, (a0, a1, a, p, q)),
P(root, add_exponents, (b0, b1, b, p, q))])
[P(root, add_exponents, (Scope(root), a0, a1, a, p, q)),
P(root, add_exponents, (Scope(root), b0, b1, b, p, q))])
def test_match_add_exponents_nary_multiplication(self):
a, p, q = tree('a,p,q')
(n0, l1), n1 = root = a ** p * 2 * a ** q
possibilities = match_add_exponents(root)
self.assertEqualPos(possibilities,
[P(root, add_exponents, (Scope(root), n0, n1, a, p, q))])
def test_match_add_exponents_negated(self):
a, q = tree('a,q')
n0, n1 = root = (-a) * a ** q
possibilities = match_add_exponents(root)
self.assertEqualPos(possibilities,
[P(root, add_exponents, (Scope(root), n0, n1, a, 1, q))])
def test_match_subtract_exponents_powers(self):
a, p, q = tree('a,p,q')
......@@ -78,13 +96,25 @@ class TestRulesPowers(RulesTestCase):
self.assertEqualPos(possibilities,
[P(root, duplicate_exponent, ([a, b], p))])
def test_match_raised_fraction(self):
ab, p = root = tree('(a / b) ^ p')
self.assertEqualPos(match_raised_fraction(root),
[P(root, raised_fraction, (ab, p))])
def test_raised_fraction(self):
ab, p = root = tree('(a / b) ^ p')
a, b = ab
self.assertEqual(raised_fraction(root, (ab, p)), a ** p / 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))])
[P(root, remove_negative_exponent, (a, -p))])
def test_match_exponent_to_root(self):
a, n, m, l1 = tree('a,n,m,1')
......@@ -103,7 +133,8 @@ class TestRulesPowers(RulesTestCase):
a, p, q = tree('a,p,q')
n0, n1 = root = a ** p * a ** q
self.assertEqualNodes(add_exponents(root, (n0, n1, a, p, q)), a ** (p + q))
self.assertEqualNodes(add_exponents(root,
(Scope(root), n0, n1, a, p, q)), a ** (p + q))
def test_subtract_exponents(self):
a, p, q = tree('a,p,q')
......@@ -131,11 +162,11 @@ class TestRulesPowers(RulesTestCase):
a ** p * b ** p * c ** p)
def test_remove_negative_exponent(self):
a, p, l1 = tree('a,p,1')
root = a ** -p
a, p, l1 = tree('a,-p,1')
root = a ** p
self.assertEqualNodes(remove_negative_exponent(root, (a, p)),
l1 / a ** p)
l1 / a ** +p)
def test_exponent_to_root(self):
a, n, m, l1 = tree('a,n,m,1')
......@@ -146,3 +177,21 @@ class TestRulesPowers(RulesTestCase):
self.assertEqualNodes(exponent_to_root(root, (a, l1, m)),
N('sqrt', a, m))
def test_match_constant_exponent(self):
a0, a1, a2 = tree('a0,a1,a2')
self.assertEqualPos(match_constant_exponent(a0),
[P(a0, remove_power_of_zero, ())])
self.assertEqualPos(match_constant_exponent(a1),
[P(a1, remove_power_of_one, ())])
self.assertEqualPos(match_constant_exponent(a2), [])
def test_remove_power_of_zero(self):
self.assertEqual(remove_power_of_zero(tree('a0'), ()), 1)
def test_remove_power_of_one(self):
a1 = tree('a1')
self.assertEqual(remove_power_of_one(a1, ()), a1[0])
from src.rules.sort import match_sort_multiplicants, move_constant
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
class TestRulesSort(RulesTestCase):
def test_match_sort_multiplicants(self):
x, l2 = root = tree('x * 2')
possibilities = match_sort_multiplicants(root)
self.assertEqualPos(possibilities,
[P(root, move_constant, (Scope(root), l2, x))])
def test_move_constant(self):
x, l2 = root = tree('x * 2')
self.assertEqualNodes(move_constant(root, (Scope(root), l2, x)),
l2 * x)
import unittest
from src.rules.utils import least_common_multiple
from src.rules.utils import least_common_multiple, is_fraction, partition
from tests.rulestestcase import tree
class TestRulesUtils(unittest.TestCase):
......@@ -9,3 +10,15 @@ class TestRulesUtils(unittest.TestCase):
self.assertEqual(least_common_multiple(5, 6), 30)
self.assertEqual(least_common_multiple(5, 6, 15), 30)
self.assertEqual(least_common_multiple(2, 4), 4)
def test_is_fraction(self):
l1, a = tree('1, a')
self.assertTrue(is_fraction(a / 2, a, 2))
self.assertTrue(is_fraction(l1 / 2 * a, a, 2))
self.assertTrue(is_fraction(a * (l1 / 2), a, 2))
self.assertFalse(is_fraction(l1 / 3 * a, a, 2))
def test_partition(self):
self.assertEqual(partition(lambda x: x & 1, range(6)),
([1, 3, 5], [0, 2, 4]))
from unittest import TestCase
from src.validation import validate
class TestValidation(TestCase):
def test_simple_success(self):
self.assertTrue(validate('3a+a', '4a'))
def test_simple_failure(self):
self.assertFalse(validate('3a+a', '4a+1'))
def test_intermediate_success(self):
self.assertTrue(validate('3a+a+b+2b', '4a+3b'))
self.assertTrue(validate('a/b/(c/d)', 'ad/(bc)'))
def test_intermediate_failure(self):
self.assertFalse(validate('3a+a+b+2b', '4a+4b'))
#def test_advanced_failure(self):
# self.assertFalse(validate('(x-1)^3+(x-1)^3', '4a+4b'))
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