Commit 72f94a0f authored by Taddeüs Kroes's avatar Taddeüs Kroes

Significantly improved the parser.

- Thought over and reconfigured precedences. These are now also synchronized
  with precedences in the line printer.
- The subscript operator ("_") has been added to the grammar similarly to
  exponentiation ("^"). This resolved some issues with the use of operators in
  integral bounds.
- Unary negation now has a higher precedence, but the parser moves negations up
  in the tree as far as possible (within the bounds of correctness and without
  causing cycles during reduction).
- All unit tests have been updated using the new syntax.
- graph_drawing has been updated to include the new syntax of the line printer.
parent 9cbf75f1
graph_drawing @ e17779f2
Subproject commit 107e93a1697600860f4e02175f47cf8650affd0f
Subproject commit e17779f211ca67336c3a6ac9038f552f91ff2f79
......@@ -21,7 +21,7 @@ import re
sys.path.insert(0, os.path.realpath('external'))
from graph_drawing.graph import generate_graph
from graph_drawing.line import generate_line
from graph_drawing.line import generate_line, preprocess_node
from graph_drawing.node import Node, Leaf
......@@ -70,6 +70,16 @@ OP_REWRITE_ALL = 24
OP_REWRITE_ALL_VERBOSE = 25
OP_REWRITE = 26
# Different types of derivative
OP_PRIME = 27
OP_DXDER = 28
OP_PARENS = 29
OP_BRACKETS = 30
OP_CBRACKETS = 31
UNARY_FUNCTIONS = [OP_INT, OP_DXDER, OP_LOG]
# Special identifiers
PI = 'pi'
E = 'e'
......@@ -102,7 +112,7 @@ OP_MAP = {
'tan': OP_TAN,
'sqrt': OP_SQRT,
'int': OP_INT,
'der': OP_DER,
'\'': OP_PRIME,
'solve': OP_SOLVE,
'log': OP_LOG,
'=': OP_EQ,
......@@ -114,9 +124,13 @@ OP_MAP = {
}
OP_VALUE_MAP = dict([(v, k) for k, v in OP_MAP.iteritems()])
OP_MAP['ln'] = OP_LOG
OP_VALUE_MAP[OP_INT_INDEF] = 'indef'
OP_VALUE_MAP[OP_ABS] = 'abs'
OP_VALUE_MAP[OP_ABS] = '||'
OP_VALUE_MAP[OP_DXDER] = 'd/d'
OP_VALUE_MAP[OP_PARENS] = '()'
OP_VALUE_MAP[OP_BRACKETS] = '[]'
OP_VALUE_MAP[OP_CBRACKETS] = '{}'
OP_MAP['ln'] = OP_LOG
TOKEN_MAP = {
OP_COMMA: 'COMMA',
......@@ -133,9 +147,10 @@ TOKEN_MAP = {
OP_COS: 'FUNCTION',
OP_TAN: 'FUNCTION',
OP_INT: 'INTEGRAL',
OP_DER: 'FUNCTION',
OP_DXDER: 'DERIVATIVE',
OP_PRIME: 'PRIME',
OP_SOLVE: 'FUNCTION',
OP_LOG: 'FUNCTION',
OP_LOG: 'LOGARITHM',
OP_EQ: 'EQ',
OP_POSSIBILITIES: 'POSSIBILITIES',
OP_HINT: 'HINT',
......@@ -152,6 +167,11 @@ def to_expression(obj):
return ExpressionLeaf(obj)
def bounds_str(f, a, b):
left = str(ExpressionNode(OP_SUBSCRIPT, f, a, no_spacing=True))
return left + str(ExpressionNode(OP_POW, Leaf(1), b, no_spacing=True))[1:]
class ExpressionBase(object):
def __lt__(self, other):
"""
......@@ -208,7 +228,8 @@ class ExpressionBase(object):
return copy.deepcopy(self)
def is_op(self, *ops):
return not self.is_leaf and self.op in ops
return not self.is_leaf and (self.op in ops or
(self.op in (OP_DXDER, OP_PRIME) and OP_DER in ops))
def is_power(self, exponent=None):
if self.is_leaf or self.op != OP_POW:
......@@ -266,9 +287,9 @@ class ExpressionBase(object):
return self.negate(-n)
def negate(self, n=1):
def negate(self, n=1, clone=True):
"""Negate the node n times."""
return negate(self, self.negated + n, clone=True)
return negate(self, self.negated + n, clone=clone)
def contains(self, node, include_self=True):
"""
......@@ -290,6 +311,7 @@ class ExpressionNode(Node, ExpressionBase):
super(ExpressionNode, self).__init__(*args, **kwargs)
self.type = TYPE_OPERATOR
op = args[0]
self.parens = False
if isinstance(op, str):
self.value = op
......@@ -298,38 +320,6 @@ class ExpressionNode(Node, ExpressionBase):
self.value = OP_VALUE_MAP[op]
self.op = op
def construct_derivative(self, children):
f = children[0]
if len(children) < 2:
# der(der(x ^ 2)) -> [x ^ 2]''
if self[0].is_op(OP_DER) and len(self[0]) < 2:
return f + '\''
# der(x ^ 2) -> [x ^ 2]'
return '[' + f + ']\''
# der(x ^ 2, x) -> d/dx (x ^ 2)
return 'd/d%s (%s)' % (children[1], f)
def construct_logarithm(self, children):
if self[0].is_op(OP_ABS):
content = children[0]
else:
content = '(' + children[0] + ')'
# log(a, e) -> ln(a)
if self[1].is_identifier(E):
return 'ln%s' % content
# log(a, 10) -> log(a)
if self[1] == 10:
return 'log%s' % content
# log(a, 2) -> log_2(a)
if children[1].isdigit():
return 'log_%s%s' % (children[1], content)
def construct_integral(self, children):
# Make sure that any needed parentheses around f(x) are generated,
# and append ' dx' to it (result 'f(x) dx')
......@@ -375,8 +365,8 @@ class ExpressionNode(Node, ExpressionBase):
return '|%s|' % children[0]
constructors = {
OP_DER: self.construct_derivative,
OP_LOG: self.construct_logarithm,
#OP_DER: self.construct_derivative,
#OP_LOG: self.construct_logarithm,
OP_INT: self.construct_integral,
OP_INT_INDEF: self.construct_indef_integral
}
......@@ -393,9 +383,56 @@ class ExpressionNode(Node, ExpressionBase):
and len(self) == 1 and self[0].is_op(OP_ABS):
return self.title() + children[0]
def arity(self):
if self.op in UNARY_FUNCTIONS:
return 1
if self.op == OP_LOG and self[1].value in (E, DEFAULT_LOGARITHM_BASE):
return 1
return len(self)
def operator(self):
if self.op == OP_LOG:
base = self[1].value
if base == DEFAULT_LOGARITHM_BASE:
return self.value
if base == E:
return 'ln'
return '%s_%s' % (self.value, str(self[1]))
if self.op == OP_DXDER:
return self.value + str(self[1])
if self.op == OP_INT and len(self) == 4:
return bounds_str(Leaf('int'), self[2], self[3])
return self.value
def is_postfix(self):
return self.op in (OP_PRIME, OP_INT_INDEF)
def __str__(self): # pragma: nocover
return generate_line(self)
def custom_line(self):
if self.op == OP_INT_INDEF:
Fx, a, b = self
return bounds_str(ExpressionNode(OP_BRACKETS, Fx), a, b)
def preprocess_str_exp(self):
if self.op == OP_PRIME and not self[0].is_op(OP_PRIME):
self[0] = ExpressionNode(OP_BRACKETS, self[0])
def postprocess_str(self, s):
if self.op == OP_INT:
return '%s d%s' % (s, self[1])
return s
def __eq__(self, other):
"""
Check strict equivalence.
......@@ -407,7 +444,7 @@ class ExpressionNode(Node, ExpressionBase):
self.nodes[self.nodes.index(old_child)] = new_child
def graph(self): # pragma: nocover
return generate_graph(negation_to_node(self))
return generate_graph(preprocess_node(self))
def extract_polynome_properties(self):
"""
......@@ -525,6 +562,7 @@ class ExpressionLeaf(Leaf, ExpressionBase):
def __init__(self, *args, **kwargs):
super(ExpressionLeaf, self).__init__(*args, **kwargs)
self.type = TYPE_MAP[type(args[0])]
self.parens = False
def __eq__(self, other):
"""
......@@ -628,16 +666,22 @@ class Scope(object):
def replace(self, node, replacement):
self.remove(node, replacement=replacement)
#def as_nary_node(self):
def as_real_nary_node(self):
return ExpressionNode(self.node.op, *self.nodes) \
.negate(self.node.negated, clone=False)
#def as_binary_node(self):
def as_nary_node(self):
return nary_node(self.node.op, self.nodes).negate(self.node.negated)
#return negate(nary_node(self.node.op, self.nodes), self.node.negated)
return nary_node(self.node.op, self.nodes) \
.negate(self.node.negated, clone=False)
def all_except(self, node):
before = range(0, node.scope_index)
after = range(node.scope_index + 1, len(self))
nodes = [self[i] for i in before + after]
return nary_node(self.node.op, nodes).negate(self.node.negated)
return negate(nary_node(self.node.op, nodes), self.node.negated)
def nary_node(operator, scope):
......@@ -663,6 +707,14 @@ def get_scope(node):
else:
scope.append(child)
#for child in node:
# if child.is_op(node.op) and (not child.negated or node.op == OP_MUL):
# sub_scope = get_scope(child)
# sub_scope[0] = sub_scope[0].negate(child.negated)
# scope += sub_scope
# else:
# scope.append(child)
return scope
......@@ -716,10 +768,13 @@ def tan(*args):
return ExpressionNode(OP_TAN, *args)
def log(exponent, base=10):
def log(exponent, base=None):
"""
Create a logarithm function node (default base is 10).
"""
if base is None:
base = DEFAULT_LOGARITHM_BASE
if not isinstance(base, ExpressionLeaf):
base = ExpressionLeaf(base)
......@@ -737,7 +792,7 @@ def der(f, x=None):
"""
Create a derivative node.
"""
return ExpressionNode(OP_DER, f, x) if x else ExpressionNode(OP_DER, f)
return ExpressionNode(OP_DXDER, f, x) if x else ExpressionNode(OP_PRIME, f)
def integral(*args):
......@@ -766,21 +821,3 @@ def sqrt(exp):
Create a square root node.
"""
return ExpressionNode(OP_SQRT, exp)
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))
......@@ -27,12 +27,13 @@ sys.path.insert(1, EXTERNAL_MODS)
from pybison import BisonParser, BisonSyntaxError
from graph_drawing.graph import generate_graph
from graph_drawing.line import pred
from node import ExpressionNode as Node, \
ExpressionLeaf as Leaf, OP_MAP, OP_DER, TOKEN_MAP, TYPE_OPERATOR, \
ExpressionLeaf as Leaf, OP_MAP, OP_DXDER, TOKEN_MAP, TYPE_OPERATOR, \
OP_COMMA, OP_MUL, OP_POW, OP_LOG, OP_ADD, Scope, E, OP_ABS, \
DEFAULT_LOGARITHM_BASE, OP_VALUE_MAP, SPECIAL_TOKENS, OP_INT, \
OP_INT_INDEF, negation_to_node
OP_INT_INDEF, INFINITY, OP_PRIME, OP_DIV
from rules.utils import find_variable
from rules.precedences import IMPLICIT_RULES
from strategy import find_possibilities
......@@ -45,6 +46,9 @@ import re
# Rewriting an expression is stopped after this number of steps is passed.
MAXIMUM_REWRITE_STEPS = 30
# Precedence of the TIMES operator ("*")
TIMES_PRED = pred(Node(OP_MUL))
# Check for n-ary operator in child nodes
def combine(op, op_type, *nodes):
......@@ -70,12 +74,15 @@ def find_integration_variable(exp):
if len(scope) > 2 and scope[-2] == 'd' and scope[-1].is_identifier():
x = scope[-1]
scope.nodes = scope[:-2]
return scope.as_nary_node(), x
return exp, find_variable(exp)
def apply_operator_negation(op, exp):
exp.negated += len(op) - 1
class Parser(BisonParser):
"""
Implements the calculator parser. Grammar rules are defined in the method
......@@ -95,9 +102,8 @@ class Parser(BisonParser):
# TODO: add a runtime check to verify that this token list match the list
# of tokens of the lex script.
tokens = ['NUMBER', 'IDENTIFIER', 'NEWLINE', 'QUIT', 'RAISE', 'GRAPH',
'LPAREN', 'RPAREN', 'FUNCTION', 'FUNCTION_LPAREN', 'LBRACKET',
'RBRACKET', 'LCBRACKET', 'RCBRACKET', 'PIPE', 'PRIME',
'DERIVATIVE'] \
'LPAREN', 'RPAREN', 'FUNCTION', 'LBRACKET',
'RBRACKET', 'LCBRACKET', 'RCBRACKET', 'PIPE'] \
+ filter(lambda t: t != 'FUNCTION', TOKEN_MAP.values())
# ------------------------------
......@@ -108,19 +114,16 @@ class Parser(BisonParser):
('left', ('OR', )),
('left', ('AND', )),
('left', ('EQ', )),
('left', ('MINUS', 'PLUS', 'NEG')),
('left', ('INTEGRAL', 'DERIVATIVE')),
('left', ('MINUS', 'PLUS')),
('nonassoc', ('INTEGRAL', 'DERIVATIVE')),
('left', ('TIMES', )),
('left', ('PRIME', )),
('left', ('DIVIDE', )),
('right', ('FUNCTION', )),
('right', ('POW', )),
('left', ('SUB', )),
('right', ('FUNCTION_LPAREN', )),
('nonassoc', ('PRIME', )),
('nonassoc', ('NEG', )),
('nonassoc', ('FUNCTION', 'LOGARITHM')),
('right', ('POW', 'SUB')),
)
interactive = 0
def __init__(self, **kwargs):
BisonParser.__init__(self, **kwargs)
self.interactive = kwargs.get('interactive', 0)
......@@ -199,6 +202,7 @@ class Parser(BisonParser):
+ '|([0-9])\s*([' + rsv + 'a-z])' # 4a -> 4 * a
+ '|([' + rsv + 'a-z])([0-9])' # a4 -> a ^ 4
+ '|([' + rsv + '0-9])(\s+[0-9]))' # 4 4 -> 4 * 4
# FIXME: Last line is a bit useless
)
def preprocess_data(match):
......@@ -241,8 +245,8 @@ class Parser(BisonParser):
# Add parentheses to integrals with matching 'dx' so that the 'dx' acts
# as a right parenthesis for the integral function
data = re.sub(r'(int(?:_.+\^.+\*)?)(.+?)(\*d\*[a-z])',
'\\1(\\2)\\3', data)
#data = re.sub(r'(int(?:_.+(?:\^.+)?\*)?)(.+?)(\*d\*[a-z])',
# '\\1(\\2)\\3', data)
if self.verbose and data_before != data: # pragma: nocover
print 'hook_read_after() modified the input data:'
......@@ -265,7 +269,6 @@ class Parser(BisonParser):
if self.possibilities is not None:
if self.verbose:
print 'Expression has not changed, not updating possibilities'
return
self.possibilities = find_possibilities(self.root_node)
......@@ -455,9 +458,8 @@ class Parser(BisonParser):
"""
debug : GRAPH exp
"""
if option == 0:
print generate_graph(negation_to_node(values[1]))
print values[1].graph()
return values[1]
raise BisonSyntaxError('Unsupported option %d in target "%s".'
......@@ -474,8 +476,6 @@ class Parser(BisonParser):
| binary
| nary
"""
# | concat
if option == 0: # rule: NUMBER
# TODO: A bit hacky, this achieves long integers and floats.
value = float(values[0]) if '.' in values[0] else int(values[0])
......@@ -486,6 +486,7 @@ class Parser(BisonParser):
if 2 <= option <= 4: # rule: LPAREN exp RPAREN | LBRACKET exp RBRACKET
# | LCBRACKET exp RCBRACKET
values[1].parens = pred(values[1]) > TIMES_PRED
return values[1]
if 5 <= option <= 7: # rule: unary | binary | nary
......@@ -496,129 +497,159 @@ class Parser(BisonParser):
def on_unary(self, target, option, names, values):
"""
unary : MINUS exp
| FUNCTION_LPAREN exp RPAREN
unary : MINUS exp %prec NEG
| FUNCTION exp
| raised_function exp %prec FUNCTION
| DERIVATIVE exp
| exp PRIME
| INTEGRAL exp
| integral_bounds TIMES exp %prec INTEGRAL
| LBRACKET exp RBRACKET lbnd ubnd
| integral_bounds exp %prec INTEGRAL
| PIPE exp PIPE
| LOGARITHM exp
| logarithm_subscript exp %prec LOGARITHM
| TIMES exp
"""
if option == 0: # rule: NEG exp
if option == 0: # rule: MINUS exp
values[1].negated += 1
return values[1]
if option in (1, 2): # rule: FUNCTION_LPAREN exp RPAREN | FUNCTION exp
op = values[0].split(' ', 1)[0]
if op == 'ln':
return Node(OP_LOG, values[1], Leaf(E))
if option == 1: # rule: FUNCTION exp
if values[1].is_op(OP_COMMA):
return Node(op, *values[1])
if op == OP_VALUE_MAP[OP_LOG]:
return Node(OP_LOG, values[1], Leaf(DEFAULT_LOGARITHM_BASE))
m = re.match(r'^log_([0-9]+|[a-zA-Z])', op)
return Node(values[0], *values[1])
if m:
value = m.group(1)
return Node(values[0], values[1])
if value.isdigit():
value = int(value)
if option == 2: # rule: raised_function exp
func, exponent = values[0]
return Node(OP_LOG, values[1], Leaf(value))
if values[1].is_op(OP_COMMA):
return Node(OP_POW, Node(func, *values[1]), exponent)
return Node(op, values[1])
return Node(OP_POW, Node(func, values[1]), exponent)
if option == 3: # rule: DERIVATIVE exp
# DERIVATIVE looks like 'd/d*x*' -> extract the 'x'
return Node(OP_DER, values[1], Leaf(values[0][-2]))
# DERIVATIVE looks like 'd/d*x' -> extract the 'x'
return Node(OP_DXDER, values[1], Leaf(values[0][-1]))
if option == 4: # rule: exp PRIME
return Node(OP_DER, values[0])
return Node(OP_PRIME, values[0])
if option == 5: # rule: INTEGRAL exp
fx, x = find_integration_variable(values[1])
return Node(OP_INT, fx, x)
if option == 6: # rule: integral_bounds TIMES exp
if option == 6: # rule: integral_bounds exp
lbnd, ubnd = values[0]
fx, x = find_integration_variable(values[2])
fx, x = find_integration_variable(values[1])
return Node(OP_INT, fx, x, lbnd, ubnd)
if option == 7: # rule: LBRACKET exp RBRACKET lbnd ubnd
return Node(OP_INT_INDEF, values[1], values[3], values[4])
if option == 8: # rule: PIPE exp PIPE
if option == 7: # rule: PIPE exp PIPE
return Node(OP_ABS, values[1])
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
if option == 8: # rule: LOGARITHM exp
if values[1].is_op(OP_COMMA):
return Node(OP_LOG, *values[1])
def on_integral_bounds(self, target, option, names, values):
"""
integral_bounds : INTEGRAL lbnd ubnd
"""
if option == 0: # rule: INTEGRAL lbnd ubnd
return values[1], values[2]
if values[0] == 'ln':
base = E
else:
base = DEFAULT_LOGARITHM_BASE
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
return Node(OP_LOG, values[1], Leaf(base))
def on_lbnd(self, target, option, names, values):
"""
lbnd : SUB exp
"""
if option == 0: # rule: SUB exp
if option == 9: # rule: logarithm_subscript exp
if values[1].is_op(OP_COMMA):
raise BisonSyntaxError('Shortcut logarithm base "log_%s" does '
'not support additional arguments.' % (values[0]))
return Node(OP_LOG, values[1], values[0])
if option == 10: # rule: TIMES exp
return values[1]
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
def on_ubnd(self, target, option, names, values):
def on_raised_function(self, target, option, names, values):
"""
ubnd : POW exp
raised_function : FUNCTION POW exp
| LOGARITHM POW exp
"""
if option == 0: # rule: POW exp
return values[1]
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
# | logarithm_subscript POW exp
if option in (0, 1): # rule: {FUNCTION,LOGARITHM} POW exp
apply_operator_negation(values[1], values[2])
return values[0], values[2]
def on_power(self, target, option, names, values):
def on_logarithm_subscript(self, target, option, names, values):
"""
power : exp POW exp
logarithm_subscript : LOGARITHM SUB exp
"""
if option == 0: # rule: LOGARITHM SUB exp
apply_operator_negation(values[1], values[2])
return values[2]
if option == 0: # rule: exp POW exp
return values[0], values[2]
def on_integral_bounds(self, target, option, names, values):
"""
integral_bounds : INTEGRAL SUB exp
"""
if option == 0: # rule: INTEGRAL SUB exp
if values[2].is_op(OP_POW):
lbnd, ubnd = values[2]
else:
lbnd = values[2]
ubnd = Leaf(INFINITY)
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
apply_operator_negation(values[1], lbnd)
return lbnd, ubnd
def on_binary(self, target, option, names, values):
"""
binary : exp PLUS exp
| exp TIMES exp
| exp DIVIDE exp
binary : exp TIMES exp
| exp PLUS exp
| exp EQ exp
| exp AND exp
| exp OR exp
| exp DIVIDE exp
| exp MINUS exp
| power
| exp POW exp
| exp SUB exp
"""
if 0 <= option <= 5: # rule: exp {PLUS,TIMES,DIVIDE,EQ,AND,OR} exp
if option == 0: # rule: exp TIMES exp
first = values[0]
node = Node(values[1], first, values[2])
if first.negated and not first.parens:
node.negated += first.negated
first.negated = 0
return node
if 1 <= option <= 4: # rule: exp {PLUS,EQ,AND,OR} exp
return Node(values[1], values[0], values[2])
if option == 5: # rule: exp DIVIDE exp
top = values[0]
bottom = values[2]
negated = 0
if top.negated and not top.parens:
negated = top.negated
top.negated = 0
if top.is_op(OP_MUL) and bottom.is_op(OP_MUL):
dtop, fx = top
dbot, x = bottom
if dtop.is_identifier('d') and dbot.is_identifier('d') \
and x.is_identifier():
# (d (fx)) / (dx)
return Node(OP_DXDER, fx, x, negated=negated)
return Node(OP_DIV, top, bottom, negated=negated)
if option == 6: # rule: exp MINUS exp
right = values[2]
right.negated += 1
......@@ -628,8 +659,22 @@ class Parser(BisonParser):
return Node(OP_ADD, values[0], right)
if option == 7: # rule: power
return Node(OP_POW, *values[0])
if option == 7: # rule: exp POW exp
apply_operator_negation(values[1], values[2])
return Node(OP_POW, values[0], values[2])
if option == 8: # rule: exp SUB exp
bounds = values[2]
if bounds.is_op(OP_POW):
lbnd, ubnd = bounds
else:
lbnd = bounds
ubnd = Leaf(INFINITY)
lbnd.negated += len(values[1]) - 1
return Node(OP_INT_INDEF, values[0], lbnd, ubnd)
raise BisonSyntaxError('Unsupported option %d in target "%s".'
% (option, target)) # pragma: nocover
......@@ -665,8 +710,6 @@ class Parser(BisonParser):
# Put all functions in a single regex
if functions:
operators += '("%s")[ ]*"(" { returntoken(FUNCTION_LPAREN); }\n' \
% '"|"'.join(functions)
operators += '("%s") { returntoken(FUNCTION); }\n' \
% '"|"'.join(functions)
......@@ -710,7 +753,7 @@ class Parser(BisonParser):
%%
d[ ]*"/"[ ]*"d*"[a-z]"*" { returntoken(DERIVATIVE); }
d[ ]*"/"[ ]*"d*"[a-z] { returntoken(DERIVATIVE); }
[0-9]+"."?[0-9]* { returntoken(NUMBER); }
[a-zA-Z] { returntoken(IDENTIFIER); }
"(" { returntoken(LPAREN); }
......@@ -719,10 +762,7 @@ class Parser(BisonParser):
"]" { returntoken(RBRACKET); }
"{" { returntoken(LCBRACKET); }
"}" { returntoken(RCBRACKET); }
"'" { returntoken(PRIME); }
"|" { returntoken(PIPE); }
log_([0-9]+|[a-zA-Z])"*(" { returntoken(FUNCTION_LPAREN); }
log_([0-9]+|[a-zA-Z])"*" { returntoken(FUNCTION); }
""" + operators + r"""
"raise" { returntoken(RAISE); }
"graph" { returntoken(GRAPH); }
......@@ -736,4 +776,5 @@ class Parser(BisonParser):
yywrap() { return(1); }
"""
#int[ ]*"(" { returntoken(FUNCTION_LPAREN); }
#_-+ { returntoken(SUB); }
#"^"-+ { returntoken(POW); }
......@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with TRS. If not, see <http://www.gnu.org/licenses/>.
from node import TYPE_OPERATOR
from node import TYPE_OPERATOR, OP_MUL, Scope
import re
......@@ -80,6 +80,20 @@ def find_parent_node(root, child):
node = node[0]
def flatten_mult(node):
if node.is_leaf:
return node
if node.is_op(OP_MUL):
scope = Scope(node)
scope.nodes = map(flatten_mult, scope)
return scope.as_nary_node()
node.nodes = map(flatten_mult, node)
return node
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
......@@ -100,6 +114,7 @@ def apply_suggestion(root, suggestion):
if parent_node:
parent_node.substitute(suggestion.root, subtree)
return root
else:
root = subtree
return subtree
return flatten_mult(root)
......@@ -14,7 +14,7 @@
# along with TRS. If not, see <http://www.gnu.org/licenses/>.
from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS, \
OP_TAN, OP_DER, OP_LOG, OP_INT, OP_INT_INDEF, OP_EQ, OP_ABS, OP_SQRT, \
OP_AND, OP_OR
OP_AND, OP_OR, OP_DXDER, OP_PRIME
from .groups import match_combine_groups
from .factors import match_expand
from .powers import match_add_exponents, match_subtract_exponents, \
......@@ -85,3 +85,4 @@ RULES = {
OP_AND: [match_multiple_equations, match_double_case],
OP_OR: [match_double_case],
}
RULES[OP_DXDER] = RULES[OP_PRIME] = RULES[OP_DER]
......@@ -188,9 +188,7 @@ def power_rule(root, args):
"""
[f(x) ^ g(x)]' -> [e ^ ln(f(x) ^ g(x))]'
"""
x = second_arg(root)
return der(L(E) ** ln(root[0]), x)
return der(L(E) ** ln(root[0]), second_arg(root))
MESSAGES[power_rule] = \
......
......@@ -60,9 +60,9 @@ def match_expand(node):
def expand(root, args):
"""
(a + b)(c + d) -> ac + ad + bc + bd
(a + b)c -> ac + bc
a(b + c) -> ab + ac
(a + b)c -> ac + bc
(a + b)(c + d) -> ac + ad + bc + bd
etc..
"""
scope, left, right = args
......
......@@ -226,7 +226,7 @@ def raised_base(root, args):
return args[0]
MESSAGES[raised_base] = _('Apply `g ^ (log_(g)(a)) = a` on {0}.')
MESSAGES[raised_base] = _('Apply `g ^ log_g(a) = a` to {0}.')
def match_factor_out_exponent(node):
......
......@@ -72,12 +72,7 @@ def add_numerics(root, args):
-2 + -3 -> -5
"""
scope, c0, c1 = args
value = c0.actual_value() + c1.actual_value()
# Replace the left node with the new expression
scope.replace(c0, Leaf(abs(value), negated=int(value < 0)))
# Remove the right node
scope.replace(c0, Leaf(c0.actual_value() + c1.actual_value()))
scope.remove(c1)
return scope.as_nary_node()
......@@ -193,13 +188,10 @@ def match_multiply_numerics(node):
numerics = filter(is_numeric_node, scope)
for n in numerics:
if n.negated:
continue
if n.value == 0:
p.append(P(node, multiply_zero, (n,)))
if n.value == 1:
if not n.negated and n.value == 1:
p.append(P(node, multiply_one, (scope, n)))
for c0, c1 in combinations(numerics, 2):
......
......@@ -22,7 +22,7 @@ from .derivatives import chain_rule
from .negation import double_negation, negated_factor, negated_nominator, \
negated_denominator, negated_zero
from .fractions import multiply_with_fraction, divide_fraction_by_term, \
add_nominators
add_nominators, division_by_one
from .integrals import factor_out_constant, integrate_variable_root
from .powers import remove_power_of_one
from .sqrt import quadrant_sqrt, extract_sqrt_mult_priority
......@@ -37,6 +37,16 @@ HIGH = [
# 4 / 4 + 1 / 4 -> 5 / 4 instead of 1 + 1/4
add_nominators,
# Some operations are obvious, they are mostly done on-the-fly
multiply_zero,
multiply_one,
remove_zero,
double_negation,
division_by_one,
add_numerics,
multiply_numerics,
negated_factor,
]
......@@ -104,12 +114,12 @@ IMPLICIT_RULES = [
double_negation,
negated_nominator,
negated_denominator,
multiply_one,
multiply_zero,
multiply_one,
division_by_one,
negated_zero,
remove_zero,
remove_power_of_one,
negated_factor,
add_numerics,
swap_factors,
]
......@@ -16,11 +16,10 @@ import sys
from external.graph_drawing.graph import generate_graph
from external.graph_drawing.line import generate_line
from src.node import negation_to_node
def create_graph(node):
return generate_graph(negation_to_node(node))
return node.graph() if node else None
class ParserWrapper(object):
......
......@@ -42,7 +42,7 @@ class TestB1Ch10(unittest.TestCase):
)
),
('-x^3*-2x^5',
-(L('x') ** L(3) * -(L(2) * L('x') ** L(5)))
-(L('x') ** L(3) * -L(2) * L('x') ** L(5))
),
('(7x^2y^3)^2/(7x^2y^3)',
N('/',
......
......@@ -19,45 +19,45 @@ class TestLeidenOefenopgave(TestCase):
def test_1_1(self):
self.assertRewrite([
'-5(x ^ 2 - 3x + 6)',
'-(5x ^ 2 + 5(-3x) + 5 * 6)',
'-(5x ^ 2 - 5 * 3x + 5 * 6)',
'-(5x ^ 2 - 15x + 5 * 6)',
'-(5x ^ 2 + 5 * -3x + 5 * 6)',
'-(5x ^ 2 + (-15)x + 5 * 6)',
'-(5x ^ 2 + (-15)x + 30)',
'-(5x ^ 2 - 15x + 30)',
'-5x ^ 2 - -15x - 30',
'-5x ^ 2 + 15x - 30',
])
return
for exp, solution in [
('-5(x^2 - 3x + 6)', '-30 + 15x - 5x ^ 2'),
('(x+1)^2', 'x ^ 2 + 2x + 1'),
('(x-1)^2', 'x ^ 2 - 2x + 1'),
('(2x+x)*x', '3x ^ 2'),
('-2(6x-4)^2*x', '-72x ^ 3 + 96x ^ 2 + 32x'),
('(4x + 5) * -(5 - 4x)', '16x^2 - 25'),
]:
self.assertEqual(str(rewrite(exp)), solution)
#for exp, solution in [
# ('-5(x^2 - 3x + 6)', '-30 + 15x - 5x ^ 2'),
# ('(x+1)^2', 'x ^ 2 + 2x + 1'),
# ('(x-1)^2', 'x ^ 2 - 2x + 1'),
# ('(2x+x)*x', '3x ^ 2'),
# ('-2(6x-4)^2*x', '-72x ^ 3 + 96x ^ 2 + 32x'),
# ('(4x + 5) * -(5 - 4x)', '16x^2 - 25'),
# ]:
# self.assertEqual(str(rewrite(exp)), solution)
def test_1_2(self):
self.assertRewrite([
'(x+1)^3', '(x + 1)(x + 1) ^ 2',
'(x + 1) ^ 3',
'(x + 1)(x + 1) ^ 2',
'(x + 1)(x + 1)(x + 1)',
'(xx + x * 1 + 1x + 1 * 1)(x + 1)',
'(x ^ (1 + 1) + x * 1 + 1x + 1 * 1)(x + 1)',
'(x ^ 2 + x * 1 + 1x + 1 * 1)(x + 1)',
'(x ^ 2 + x + 1x + 1 * 1)(x + 1)',
'(x ^ 2 + x + x + 1 * 1)(x + 1)',
'(xx + x + 1x + 1 * 1)(x + 1)',
'(xx + x + x + 1 * 1)(x + 1)',
'(xx + x + x + 1)(x + 1)',
'(x ^ (1 + 1) + x + x + 1)(x + 1)',
'(x ^ 2 + x + x + 1)(x + 1)',
'(x ^ 2 + (1 + 1)x + 1)(x + 1)',
'(x ^ 2 + 2x + 1)(x + 1)',
'x ^ 2 * x + x ^ 2 * 1 + 2xx + 2x * 1 + 1x + 1 * 1',
'x ^ (2 + 1) + x ^ 2 * 1 + 2xx + 2x * 1 + 1x + 1 * 1',
'x ^ 3 + x ^ 2 * 1 + 2xx + 2x * 1 + 1x + 1 * 1',
'x ^ 3 + x ^ 2 + 2xx + 2x * 1 + 1x + 1 * 1',
'x ^ 3 + x ^ 2 + 2x ^ (1 + 1) + 2x * 1 + 1x + 1 * 1',
'x ^ 3 + x ^ 2 + 2x ^ 2 + 2x * 1 + 1x + 1 * 1',
'x ^ 3 + x ^ 2 + 2x ^ 2 + 2x + 1x + 1 * 1',
'x ^ 3 + x ^ 2 + 2x ^ 2 + 2x + x + 1 * 1',
'x ^ 2 * x + x ^ 2 + 2xx + 2x * 1 + 1x + 1 * 1',
'x ^ 2 * x + x ^ 2 + 2xx + 2x + 1x + 1 * 1',
'x ^ 2 * x + x ^ 2 + 2xx + 2x + x + 1 * 1',
'x ^ 2 * x + x ^ 2 + 2xx + 2x + x + 1',
'x ^ (2 + 1) + x ^ 2 + 2xx + 2x + x + 1',
'x ^ 3 + x ^ 2 + 2xx + 2x + x + 1',
'x ^ 3 + x ^ 2 + 2x ^ (1 + 1) + 2x + x + 1',
'x ^ 3 + x ^ 2 + 2x ^ 2 + 2x + x + 1',
'x ^ 3 + (1 + 2)x ^ 2 + 2x + x + 1',
'x ^ 3 + 3x ^ 2 + 2x + x + 1',
......@@ -68,12 +68,13 @@ class TestLeidenOefenopgave(TestCase):
def test_1_3(self):
# (x+1)^2 -> x^2 + 2x + 1
self.assertRewrite([
'(x+1)^2', '(x + 1)(x + 1)',
'(x + 1) ^ 2',
'(x + 1)(x + 1)',
'xx + x * 1 + 1x + 1 * 1',
'x ^ (1 + 1) + x * 1 + 1x + 1 * 1',
'x ^ 2 + x * 1 + 1x + 1 * 1',
'x ^ 2 + x + 1x + 1 * 1',
'x ^ 2 + x + x + 1 * 1',
'xx + x + 1x + 1 * 1',
'xx + x + x + 1 * 1',
'xx + x + x + 1',
'x ^ (1 + 1) + x + x + 1',
'x ^ 2 + x + x + 1',
'x ^ 2 + (1 + 1)x + 1',
'x ^ 2 + 2x + 1',
......@@ -84,25 +85,25 @@ class TestLeidenOefenopgave(TestCase):
self.assertRewrite([
'(x - 1) ^ 2',
'(x - 1)(x - 1)',
'xx + x(-1) + (-1)x + (-1)(-1)',
'x ^ (1 + 1) + x(-1) + (-1)x + (-1)(-1)',
'x ^ 2 + x(-1) + (-1)x + (-1)(-1)',
'x ^ 2 - x * 1 + (-1)x + (-1)(-1)',
'x ^ 2 - x + (-1)x + (-1)(-1)',
'x ^ 2 - x - 1x + (-1)(-1)',
'x ^ 2 - x - x + (-1)(-1)',
'x ^ 2 - x - x - -1',
'xx + x * -1 + (-1)x + (-1) * -1',
'xx + x * -1 + (-1)x - -1',
'xx + x * -1 + (-1)x + 1',
'xx - x * 1 + (-1)x + 1',
'xx - x + (-1)x + 1',
'xx - x - 1x + 1',
'xx - x - x + 1',
'x ^ (1 + 1) - x - x + 1',
'x ^ 2 - x - x + 1',
'x ^ 2 + (1 + 1)(-x) + 1',
'x ^ 2 + 2(-x) + 1',
'x ^ 2 + (1 + 1) * -x + 1',
'x ^ 2 + 2 * -x + 1',
'x ^ 2 - 2x + 1',
])
def test_1_4_1(self):
self.assertRewrite([
'x * -1 + 1x',
'-x * 1 + 1x',
'-x + 1x',
'x * -1 + x',
'-x * 1 + x',
'-x + x',
'(-1 + 1)x',
'0x',
......@@ -112,23 +113,23 @@ class TestLeidenOefenopgave(TestCase):
def test_1_4_2(self):
self.assertRewrite([
'x * -1 - 1x',
'-x * 1 - 1x',
'-x - 1x',
'x * -1 - x',
'-x * 1 - x',
'-x - x',
'(1 + 1)(-x)',
'2(-x)',
'(1 + 1) * -x',
'2 * -x',
'-2x',
])
def test_1_4_3(self):
self.assertRewrite([
'x * -1 + x * -1',
'-x * 1 + x(-1)',
'-x + x(-1)',
'-x * 1 + x * -1',
'-x + x * -1',
'-x - x * 1',
'-x - x',
'(1 + 1)(-x)',
'2(-x)',
'(1 + 1) * -x',
'2 * -x',
'-2x',
])
......@@ -144,19 +145,21 @@ class TestLeidenOefenopgave(TestCase):
def test_1_7(self):
self.assertRewrite([
'(4x + 5) * -(5 - 4x)',
'(4x + 5)(-5 - -4x)',
'(4x + 5)(-5 + 4x)',
'4x(-5) + 4x * 4x + 5(-5) + 5 * 4x',
'(-20)x + 4x * 4x + 5(-5) + 5 * 4x',
'-20x + 4x * 4x + 5(-5) + 5 * 4x',
'-20x + 16xx + 5(-5) + 5 * 4x',
'-20x + 16x ^ (1 + 1) + 5(-5) + 5 * 4x',
'-20x + 16x ^ 2 + 5(-5) + 5 * 4x',
'-20x + 16x ^ 2 - 25 + 5 * 4x',
'-20x + 16x ^ 2 - 25 + 20x',
'(-1 + 1)20x + 16x ^ 2 - 25',
'0 * 20x + 16x ^ 2 - 25',
'0 + 16x ^ 2 - 25',
'-(4x + 5)(5 - 4x)',
'-(4x * 5 + 4x * -4x + 5 * 5 + 5 * -4x)',
'-(20x + 4x * -4x + 5 * 5 + 5 * -4x)',
'-(20x + (-16)xx + 5 * 5 + 5 * -4x)',
'-(20x + (-16)xx + 25 + 5 * -4x)',
'-(20x + (-16)xx + 25 + (-20)x)',
'-(20x - 16xx + 25 + (-20)x)',
'-(20x - 16xx + 25 - 20x)',
'-(20x - 16x ^ (1 + 1) + 25 - 20x)',
'-(20x - 16x ^ 2 + 25 - 20x)',
'-((1 - 1)20x - 16x ^ 2 + 25)',
'-(0 * 20x - 16x ^ 2 + 25)',
'-(0 - 16x ^ 2 + 25)',
'-(-16x ^ 2 + 25)',
'--16x ^ 2 - 25',
'16x ^ 2 - 25',
])
......@@ -176,7 +179,7 @@ class TestLeidenOefenopgave(TestCase):
def test_4_2(self):
self.assertRewrite([
'2/7 - 4/11',
'2 / 7 - 4 / 11',
'22 / 77 - 28 / 77',
'(22 - 28) / 77',
'(-6) / 77',
......
......@@ -16,17 +16,6 @@ from tests.rulestestcase import RulesTestCase as TestCase
class TestLeidenOefenopgaveV12(TestCase):
def test_1_a(self):
self.assertRewrite([
'-5(x^2 - 3x + 6)',
'-(5x ^ 2 + 5(-3x) + 5 * 6)',
'-(5x ^ 2 - 5 * 3x + 5 * 6)',
'-(5x ^ 2 - 15x + 5 * 6)',
'-(5x ^ 2 - 15x + 30)',
'-5x ^ 2 - -15x - 30',
'-5x ^ 2 + 15x - 30',
])
def test_1_d(self):
self.assertRewrite([
'(2x + x)x',
......@@ -40,30 +29,28 @@ class TestLeidenOefenopgaveV12(TestCase):
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',
'-(2 * 6x + 2 * -4)(6x - 4)x',
'-(12x + 2 * -4)(6x - 4)x',
'-(12x - 8)(6x - 4)x',
'-(12x - 8)(6xx + (-4)x)',
'-(12x - 8)(6x ^ (1 + 1) + (-4)x)',
'-(12x - 8)(6x ^ 2 + (-4)x)',
'-(12x - 8)(6xx - 4x)',
'-(12x - 8)(6x ^ (1 + 1) - 4x)',
'-(12x - 8)(6x ^ 2 - 4x)',
'-(12x * 6x ^ 2 + 12x(-4x) + (-8)6x ^ 2 + (-8)(-4x))',
'-(72xx ^ 2 + 12x(-4x) + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ (1 + 2) + 12x(-4x) + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 + 12x(-4x) + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 12x * 4x + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48xx + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48x ^ (1 + 1) + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48x ^ 2 + (-8)6x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48x ^ 2 + (-48)x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48x ^ 2 - 48x ^ 2 + (-8)(-4x))',
'-(72x ^ 3 - 48x ^ 2 - 48x ^ 2 - 8(-4x))',
'-(72x ^ 3 - 48x ^ 2 - 48x ^ 2 - -8 * 4x)',
'-(72x ^ 3 - 48x ^ 2 - 48x ^ 2 - -32x)',
'-(12x * 6x ^ 2 + 12x * -4x + (-8)6x ^ 2 + (-8) * -4x)',
'-(72x x ^ 2 + 12x * -4x + (-8)6x ^ 2 + (-8) * -4x)',
'-(72x x ^ 2 + (-48)xx + (-8)6x ^ 2 + (-8) * -4x)',
'-(72x x ^ 2 + (-48)xx + (-48)x ^ 2 + (-8) * -4x)',
'-(72x x ^ 2 + (-48)xx + (-48)x ^ 2 + (--32)x)',
'-(72x x ^ 2 + (-48)xx + (-48)x ^ 2 + 32x)',
'-(72x x ^ 2 - 48xx + (-48)x ^ 2 + 32x)',
'-(72x x ^ 2 - 48xx - 48x ^ 2 + 32x)',
'-(72x ^ (1 + 2) - 48xx - 48x ^ 2 + 32x)',
'-(72x ^ 3 - 48xx - 48x ^ 2 + 32x)',
'-(72x ^ 3 - 48x ^ (1 + 1) - 48x ^ 2 + 32x)',
'-(72x ^ 3 - 48x ^ 2 - 48x ^ 2 + 32x)',
'-(72x ^ 3 + (1 + 1)(-48x ^ 2) + 32x)',
'-(72x ^ 3 + 2(-48x ^ 2) + 32x)',
'-(72x ^ 3 - 2 * 48x ^ 2 + 32x)',
'-(72x ^ 3 + (1 + 1) * -48x ^ 2 + 32x)',
'-(72x ^ 3 + 2 * -48x ^ 2 + 32x)',
'-(72x ^ 3 + (-96)x ^ 2 + 32x)',
'-(72x ^ 3 - 96x ^ 2 + 32x)',
'-72x ^ 3 - -96x ^ 2 - 32x',
'-72x ^ 3 + 96x ^ 2 - 32x',
......@@ -71,23 +58,23 @@ class TestLeidenOefenopgaveV12(TestCase):
def test_2_a(self):
self.assertRewrite([
'(a ^ 2 * b ^ -1) ^ 3(ab ^ 2)',
'(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',
'(a ^ 2 * b ^ -1) ^ 3 * a b ^ 2',
'(a ^ 2 * 1 / b ^ 1) ^ 3 * a b ^ 2',
'(a ^ 2 * 1 / b) ^ 3 * a b ^ 2',
'((a ^ 2 * 1) / b) ^ 3 * a b ^ 2',
'(a ^ 2 / b) ^ 3 * a b ^ 2',
'(a ^ 2) ^ 3 / b ^ 3 * a b ^ 2',
'a ^ (2 * 3) / b ^ 3 * a b ^ 2',
'a ^ 6 / b ^ 3 * a b ^ 2',
'(a ^ 6 * a) / b ^ 3 * b ^ 2',
'a ^ (6 + 1) / b ^ 3 * b ^ 2',
'a ^ 7 / b ^ 3 * b ^ 2',
'(a ^ 7 * b ^ 2) / 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',
'b ^ 2 / b ^ 3 * a ^ 7',
'b ^ (2 - 3)a ^ 7',
'b ^ -1 * a ^ 7',
'1 / b ^ 1 * a ^ 7',
'1 / b * a ^ 7',
'(1a ^ 7) / b',
'a ^ 7 / b',
......@@ -124,9 +111,9 @@ class TestLeidenOefenopgaveV12(TestCase):
def test_2_f(self):
self.assertRewrite([
'(4b) ^ -2',
'4 ^ (-2)b ^ (-2)',
'1 / 4 ^ 2 * b ^ (-2)',
'1 / 16 * b ^ (-2)',
'4 ^ -2 * b ^ -2',
'1 / 4 ^ 2 * b ^ -2',
'1 / 16 * b ^ -2',
'1 / 16 * 1 / b ^ 2',
'(1 * 1) / (16b ^ 2)',
'1 / (16b ^ 2)',
......
......@@ -14,7 +14,7 @@
# along with TRS. If not, see <http://www.gnu.org/licenses/>.
from src.node import ExpressionNode as N, ExpressionLeaf as L, Scope, \
nary_node, get_scope, OP_ADD, infinity, absolute, sin, cos, tan, log, \
ln, der, integral, indef, eq, negation_to_node
ln, der, integral, indef, eq
from tests.rulestestcase import RulesTestCase, tree
......@@ -121,8 +121,8 @@ class TestNode(RulesTestCase):
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])
root, a, b, c, d = tree('ab * -cd, a, b, -c, d')
self.assertEqual(get_scope(root), [a, b, c, d])
def test_get_scope_index(self):
self.assertEqual(self.scope.index(self.a), 0)
......@@ -244,15 +244,15 @@ class TestNode(RulesTestCase):
self.assertTrue(ma.contains(a))
def test_construct_function_derivative(self):
self.assertEqual(str(tree('der(x ^ 2)')), '[x ^ 2]\'')
self.assertEqual(str(tree('der(der(x ^ 2))')), '[x ^ 2]\'\'')
self.assertEqual(str(tree('der(x ^ 2, x)')), 'd/dx (x ^ 2)')
self.assertEqual(str(tree("(x ^ 2)'")), "[x ^ 2]'")
self.assertEqual(str(tree("(x ^ 2)''")), "[x ^ 2]''")
self.assertEqual(str(tree('d/dx x ^ 2')), 'd/dx x ^ 2')
def test_construct_function_logarithm(self):
self.assertEqual(str(tree('log(x, e)')), 'ln(x)')
self.assertEqual(str(tree('log(x, 10)')), 'log(x)')
self.assertEqual(str(tree('log(x, 2)')), 'log_2(x)')
self.assertEqual(str(tree('log(x, g)')), 'log(x, g)')
self.assertEqual(str(tree('log(x, e)')), 'ln x')
self.assertEqual(str(tree('log(x, 10)')), 'log x')
self.assertEqual(str(tree('log(x, 2)')), 'log_2 x')
self.assertEqual(str(tree('log(x, g)')), 'log_g x')
def test_construct_function_integral(self):
self.assertEqual(str(tree('int x ^ 2')), 'int x ^ 2 dx')
......@@ -318,9 +318,3 @@ class TestNode(RulesTestCase):
def test_eq(self):
x, a, b, expect = tree('x, a, b, x + a = b')
self.assertEqual(eq(x + a, b), expect)
def test_negation_to_node(self):
a = tree('a')
self.assertEqual(negation_to_node(-a), N('-', a))
self.assertEqual(negation_to_node(-(a + 1)), N('-', a + 1))
self.assertEqual(negation_to_node(-(a - 1)), N('-', a + N('-', L(1))))
......@@ -89,12 +89,13 @@ class TestParser(RulesTestCase):
self.assertEqual(tree('sin x'), sin(x))
self.assertEqual(tree('sin 2 x'), sin(2) * x) # FIXME: correct?
self.assertEqual(tree('sin x ^ 2'), sin(x ** 2))
self.assertEqual(tree('sin(x) ^ 2'), sin(x) ** 2)
self.assertEqual(tree('sin^2 x'), 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)))
self.assertEqual(tree('sin cos(x) ^ 2'), sin(cos(x) ** 2))
self.assertEqual(tree('sin cos(x) ^ 2'), sin(cos(x ** 2)))
self.assertEqual(tree('sin (cos x) ^ 2'), sin(cos(x) ** 2))
def test_brackets(self):
self.assertEqual(*tree('[x], x'))
......@@ -145,7 +146,7 @@ class TestParser(RulesTestCase):
# FIXME: self.assertEqual(tree('a' + token + 'a'), a * t * a)
def test_integral(self):
x, y, dx, a, b, l2 = tree('x, y, dx, a, b, 2')
x, y, dx, a, b, l2, oo = tree('x, y, dx, a, b, 2, oo')
self.assertEqual(tree('int x'), integral(x, x))
self.assertEqual(tree('int x ^ 2'), integral(x ** 2, x))
......@@ -162,21 +163,18 @@ class TestParser(RulesTestCase):
self.assertEqual(tree('int_a^(b2) x'), integral(x, x, a, b * 2))
self.assertEqual(tree('int x ^ 2 + 1'), integral(x ** 2, x) + 1)
self.assertEqual(tree('int x ^ 2 + 1 dx'), integral(x ** 2 + 1, x))
self.assertEqual(tree('int_a^b x ^ 2 dx'), integral(x ** 2, x, a, b))
self.assertEqual(tree('int_a^(b2) x ^ 2 + 1 dx'),
integral(x ** 2 + 1, x, a, b * 2))
self.assertEqual(tree('int_(a^2)^b x ^ 2 + 1 dx'),
integral(x ** 2 + 1, x, a ** 2, b))
self.assertEqual(tree('int_a x ^ 2 dx'), integral(x ** 2, x, a, oo))
self.assertEqual(tree('int_(-a)^b x dx'), integral(x, x, -a, b))
# FIXME: self.assertEqual(tree('int_-a^b x dx'), integral(x, x, -a, b))
#self.assertEqual(tree('int_-a^b x dx'), integral(x, x, -a, b))
def test_indefinite_integral(self):
x2, a, b = tree('x ^ 2, a, b')
x2, a, b, oo = tree('x ^ 2, a, b, oo')
self.assertEqual(tree('[x ^ 2]_a^b'), indef(x2, a, b))
self.assertEqual(tree('(x ^ 2)_a'), indef(x2, a, oo))
self.assertEqual(tree('(x ^ 2)_a^b'), indef(x2, a, b))
def test_absolute_value(self):
x = tree('x')
......
......@@ -14,7 +14,7 @@
# along with TRS. If not, see <http://www.gnu.org/licenses/>.
import unittest
from src.possibilities import MESSAGES, Possibility as P
from src.possibilities import MESSAGES, Possibility as P, flatten_mult
from tests.rulestestcase import tree
from src.parser import Parser
......@@ -80,23 +80,6 @@ class TestPossibilities(unittest.TestCase):
'<Possibility root="3 + 4" handler=add_numerics' \
' args=(<Scope of "3 + 4">, 3, 4)>')
#def test_filter_duplicates(self):
# a, b = ab = tree('a + b')
# p0 = P(a, dummy_handler, (1, 2))
# p1 = P(ab, dummy_handler, (1, 2))
# p2 = P(ab, dummy_handler, (1, 2, 3))
# p3 = P(ab, dummy_handler_msg, (1, 2))
# self.assertEqual(filter_duplicates([]), [])
# self.assertEqual(filter_duplicates([p0, p1]), [p1])
# self.assertEqual(filter_duplicates([p1, p2]), [p1, p2])
# self.assertEqual(filter_duplicates([p1, p3]), [p1, p3])
# self.assertEqual(filter_duplicates([p0, p1, p2, p3]), [p1, p2, p3])
# # Docstrings example
# (l1, l2), l3 = left, l3 = right = tree('1 + 2 + 3')
# p0 = P(left, add_numerics, (1, 2, 1, 2))
# p1 = P(right, add_numerics, (1, 2, 1, 2))
# p2 = P(right, add_numerics, (1, 3, 1, 3))
# p3 = P(right, add_numerics, (2, 3, 2, 3))
# self.assertEqual(filter_duplicates([p0, p1, p2, p3]), [p1, p2, p3])
def test_flatten_mult(self):
self.assertEqual(flatten_mult(tree('2(xx)')), tree('2xx'))
self.assertEqual(flatten_mult(tree('2(xx) + 1')), tree('2xx + 1'))
......@@ -28,50 +28,50 @@ from tests.rulestestcase import RulesTestCase, tree
class TestRulesDerivatives(RulesTestCase):
def test_get_derivation_variable(self):
xy0, xy1, x, l1 = tree('der(xy, x), der(xy), der(x), der(1)')
xy0, xy1, x, l1 = tree('d/dx xy, (xy)\', x\', 1\'')
self.assertEqual(get_derivation_variable(xy0), 'x')
self.assertEqual(get_derivation_variable(xy1), 'x')
self.assertEqual(get_derivation_variable(x), 'x')
self.assertIsNone(get_derivation_variable(l1))
def test_match_zero_derivative(self):
root = tree('der(x, y)')
root = tree('d/dy x')
self.assertEqualPos(match_zero_derivative(root),
[P(root, zero_derivative)])
root = tree('der(2)')
root = tree('d/dx 2')
self.assertEqualPos(match_zero_derivative(root),
[P(root, zero_derivative)])
def test_zero_derivative(self):
root = tree('der(1)')
root = tree('d/dx 1')
self.assertEqual(zero_derivative(root, ()), 0)
def test_match_one_derivative(self):
root = tree('der(x)')
root = tree('d/dx x')
self.assertEqualPos(match_one_derivative(root),
[P(root, one_derivative)])
root = tree('der(x, x)')
root = tree('d/dx x')
self.assertEqualPos(match_one_derivative(root),
[P(root, one_derivative)])
def test_one_derivative(self):
root = tree('der(x)')
root = tree('d/dx x')
self.assertEqual(one_derivative(root, ()), 1)
def test_match_const_deriv_multiplication(self):
root = tree('der(2x)')
root = tree('d/dx 2x')
l2, x = root[0]
self.assertEqualPos(match_const_deriv_multiplication(root),
[P(root, const_deriv_multiplication, (Scope(root[0]), l2, x))])
(x, y), x = root = tree('der(xy, x)')
(x, y), x = root = tree('d/dx xy')
self.assertEqualPos(match_const_deriv_multiplication(root),
[P(root, const_deriv_multiplication, (Scope(root[0]), y, x))])
def test_match_const_deriv_multiplication_multiple_constants(self):
root = tree('der(2x * 3)')
root = tree('d/dx 2x * 3')
(l2, x), l3 = root[0]
scope = Scope(root[0])
self.assertEqualPos(match_const_deriv_multiplication(root),
......@@ -79,33 +79,33 @@ class TestRulesDerivatives(RulesTestCase):
P(root, const_deriv_multiplication, (scope, l3, x))])
def test_const_deriv_multiplication(self):
root = tree('der(2x)')
root = tree('d/dx 2x')
l2, x = root[0]
args = Scope(root[0]), l2, x
self.assertEqual(const_deriv_multiplication(root, args),
l2 * der(x, x))
def test_match_variable_power(self):
root, x, l2 = tree('der(x ^ 2), x, 2')
root, x, l2 = tree('d/dx x ^ 2, x, 2')
self.assertEqualPos(match_variable_power(root),
[P(root, variable_root)])
root = tree('der(2 ^ x)')
root = tree('d/dx 2 ^ x')
self.assertEqualPos(match_variable_power(root),
[P(root, variable_exponent)])
def test_match_variable_power_chain_rule(self):
root, x, l2, x3 = tree('der((x ^ 3) ^ 2), x, 2, x ^ 3')
root, x, l2, x3 = tree('d/dx (x ^ 3) ^ 2, x, 2, x ^ 3')
self.assertEqualPos(match_variable_power(root),
[P(root, chain_rule, (x3, variable_root, ()))])
root = tree('der(2 ^ x ^ 3)')
root = tree('d/dx 2 ^ x ^ 3')
self.assertEqualPos(match_variable_power(root),
[P(root, chain_rule, (x3, variable_exponent, ()))])
# Below is not mathematically underivable, it's just not within the
# scope of our program
root, x = tree('der(x ^ x), x')
root, x = tree('d/dx x ^ x, x')
self.assertEqualPos(match_variable_power(root),
[P(root, power_rule)])
......@@ -116,138 +116,138 @@ class TestRulesDerivatives(RulesTestCase):
def test_power_rule_chain(self):
self.assertRewrite([
"[x ^ x]'",
"[e ^ ln(x ^ x)]'",
"e ^ ln(x ^ x)[ln(x ^ x)]'",
"x ^ x * [ln(x ^ x)]'",
"x ^ x * [xln(x)]'",
"x ^ x * ([x]' * ln(x) + x[ln(x)]')",
"x ^ x * (1ln(x) + x[ln(x)]')",
"x ^ x * (ln(x) + x[ln(x)]')",
"x ^ x * (ln(x) + x * 1 / x)",
"x ^ x * (ln(x) + (x * 1) / x)",
"x ^ x * (ln(x) + x / x)",
"x ^ x * (ln(x) + 1)",
"x ^ x * ln(x) + x ^ x * 1",
"x ^ x * ln(x) + x ^ x",
"[e ^ (ln x ^ x)]'",
"e ^ (ln x ^ x)[ln x ^ x]'",
"x ^ x * [ln x ^ x]'",
"x ^ x * [x ln x]'",
"x ^ x * ([x]' * ln x + x[ln x]')",
"x ^ x * (1ln x + x[ln x]')",
"x ^ x * (ln x + x[ln x]')",
"x ^ x * (ln x + x * 1 / x)",
"x ^ x * (ln x + (x * 1) / x)",
"x ^ x * (ln x + x / x)",
"x ^ x * (ln x + 1)",
"x ^ x * ln x + x ^ x * 1",
"x ^ x * ln x + x ^ x",
])
def test_variable_root(self):
root = tree('der(x ^ 2)')
root = tree('d/dx x ^ 2')
x, n = root[0]
self.assertEqual(variable_root(root, ()), n * x ** (n - 1))
def test_variable_exponent(self):
root = tree('der(2 ^ x)')
root = tree('d/dx 2 ^ x')
g, x = root[0]
self.assertEqual(variable_exponent(root, ()), g ** x * ln(g))
root = tree('der(e ^ x)')
root = tree('d/dx e ^ x')
e, x = root[0]
self.assertEqual(variable_exponent(root, ()), e ** x)
def test_chain_rule(self):
root = tree('der(2 ^ x ^ 3)')
root = tree('(2 ^ x ^ 3)\'')
l2, x3 = root[0]
x, l3 = x3
self.assertEqual(chain_rule(root, (x3, variable_exponent, ())),
l2 ** x3 * ln(l2) * der(x3))
def test_match_logarithmic(self):
root = tree('der(log(x))')
root = tree('d/dx log(x)')
self.assertEqualPos(match_logarithmic(root), [P(root, logarithmic)])
def test_match_logarithmic_chain_rule(self):
root, f = tree('der(log(x ^ 2)), x ^ 2')
root, f = tree('d/dx log(x ^ 2), x ^ 2')
self.assertEqualPos(match_logarithmic(root),
[P(root, chain_rule, (f, logarithmic, ()))])
def test_logarithmic(self):
root, x, l1, l10 = tree('der(log(x)), x, 1, 10')
root, x, l1, l10 = tree('d/dx log(x), x, 1, 10')
self.assertEqual(logarithmic(root, ()), l1 / (x * ln(l10)))
root, x, l1, l10 = tree('der(ln(x)), x, 1, 10')
root, x, l1, l10 = tree('d/dx ln(x), x, 1, 10')
self.assertEqual(logarithmic(root, ()), l1 / x)
def test_match_goniometric(self):
root = tree('der(sin(x))')
root = tree('d/dx sin(x)')
self.assertEqualPos(match_goniometric(root), [P(root, sinus)])
root = tree('der(cos(x))')
root = tree('d/dx cos(x)')
self.assertEqualPos(match_goniometric(root), [P(root, cosinus)])
root = tree('der(tan(x))')
root = tree('d/dx tan(x)')
self.assertEqualPos(match_goniometric(root), [P(root, tangens)])
def test_match_goniometric_chain_rule(self):
root, x2 = tree('der(sin(x ^ 2)), x ^ 2')
root, x2 = tree('d/dx sin(x ^ 2), x ^ 2')
self.assertEqualPos(match_goniometric(root),
[P(root, chain_rule, (x2, sinus, ()))])
root = tree('der(cos(x ^ 2))')
root = tree('d/dx cos(x ^ 2)')
self.assertEqualPos(match_goniometric(root),
[P(root, chain_rule, (x2, cosinus, ()))])
def test_sinus(self):
root, x = tree('der(sin(x)), x')
root, x = tree('d/dx sin(x), x')
self.assertEqual(sinus(root, ()), cos(x))
def test_cosinus(self):
root, x = tree('der(cos(x)), x')
root, x = tree('d/dx cos(x), x')
self.assertEqual(cosinus(root, ()), -sin(x))
def test_tangens(self):
root, x = tree('der(tan(x), x), x')
root, x = tree('d/dx tan(x), x')
self.assertEqual(tangens(root, ()), der(sin(x) / cos(x), x))
root = tree('der(tan(x))')
root = tree('tan(x)\'')
self.assertEqual(tangens(root, ()), der(sin(x) / cos(x)))
def test_match_sum_product_rule_sum(self):
root = tree('der(x ^ 2 + x)')
root = tree('d/dx (x ^ 2 + x)')
x2, x = f = root[0]
self.assertEqualPos(match_sum_product_rule(root),
[P(root, sum_rule, (Scope(f), x2)),
P(root, sum_rule, (Scope(f), x))])
root = tree('der(x ^ 2 + 3 + x)')
root = tree('d/dx (x ^ 2 + 3 + x)')
self.assertEqualPos(match_sum_product_rule(root),
[P(root, sum_rule, (Scope(root[0]), x2)),
P(root, sum_rule, (Scope(root[0]), x))])
def test_match_sum_product_rule_product(self):
root = tree('der(x ^ 2 * x)')
root = tree('d/dx x ^ 2 * x')
x2, x = f = root[0]
self.assertEqualPos(match_sum_product_rule(root),
[P(root, product_rule, (Scope(f), x2)),
P(root, product_rule, (Scope(f), x))])
def test_match_sum_product_rule_none(self):
root = tree('der(2 + 2)')
root = tree('d/dx (2 + 2)')
self.assertEqualPos(match_sum_product_rule(root), [])
root = tree('der(x ^ 2 * 2)')
root = tree('d/dx x ^ 2 * 2')
self.assertEqualPos(match_sum_product_rule(root), [])
def test_sum_rule(self):
root = tree('der(x ^ 2 + x)')
root = tree('(x ^ 2 + x)\'')
x2, x = f = root[0]
self.assertEqual(sum_rule(root, (Scope(f), x2)), der(x2) + der(x))
self.assertEqual(sum_rule(root, (Scope(f), x)), der(x) + der(x2))
root = tree('der(x ^ 2 + 3 + x)')
root = tree('(x ^ 2 + 3 + x)\'')
(x2, l3), x = f = root[0]
self.assertEqual(sum_rule(root, (Scope(f), x2)), der(x2) + der(l3 + x))
self.assertEqual(sum_rule(root, (Scope(f), x)), der(x) + der(x2 + l3))
def test_product_rule(self):
root = tree('der(x ^ 2 * x)')
root = tree('(x ^ 2 * x)\'')
x2, x = f = root[0]
self.assertEqual(product_rule(root, (Scope(f), x2)),
der(x2) * x + x2 * der(x))
self.assertEqual(product_rule(root, (Scope(f), x)),
der(x) * x2 + x * der(x2))
root = tree('der(x ^ 2 * x * x ^ 3)')
root = tree('(x ^ 2 * x * x ^ 3)\'')
(x2, x), x3 = f = root[0]
self.assertEqual(product_rule(root, (Scope(f), x2)),
der(x2) * (x * x3) + x2 * der(x * x3))
......@@ -257,15 +257,15 @@ class TestRulesDerivatives(RulesTestCase):
der(x3) * (x2 * x) + x3 * der(x2 * x))
def test_match_quotient_rule(self):
root = tree('der(x ^ 2 / x)')
root = tree('d/dx x ^ 2 / x')
self.assertEqualPos(match_quotient_rule(root),
[P(root, quotient_rule)])
root = tree('der(x ^ 2 / 2)')
root = tree('d/dx x ^ 2 / 2')
self.assertEqualPos(match_quotient_rule(root), [])
def test_quotient_rule(self):
root = tree('der(x ^ 2 / x)')
root = tree('(x ^ 2 / x)\'')
f, g = root[0]
self.assertEqual(quotient_rule(root, ()),
(der(f) * g - f * der(g)) / g ** 2)
......
......@@ -320,17 +320,17 @@ class TestRulesFractions(RulesTestCase):
'1 / (1 / b - 1 / a)',
'(b * 1) / (b(1 / b - 1 / a))',
'b / (b(1 / b - 1 / a))',
'b / (b * 1 / b + b(-1 / a))',
'b / ((b * 1) / b + b(-1 / a))',
'b / (b / b + b(-1 / a))',
'b / (1 + b(-1 / a))',
'b / (b * 1 / b + b * -1 / a)',
'b / (b * 1 / b - b * 1 / a)',
'b / ((b * 1) / b - b * 1 / a)',
'b / (b / b - b * 1 / a)',
'b / (1 - b * 1 / a)',
'b / (1 - (b * 1) / a)',
'b / (1 - b / a)',
'(ab) / (a(1 - b / a))',
'(ab) / (a * 1 + a(-b / a))',
'(ab) / (a + a(-b / a))',
'(ab) / (a - ab / a)',
'(ab) / (a * 1 + a * -b / a)',
'(ab) / (a + a * -b / a)',
'(ab) / (a - a b / a)',
'(ab) / (a - (ab) / a)',
'(ab) / (a - b)',
])
......@@ -30,30 +30,30 @@ class TestRulesGoniometry(RulesTestCase):
self.assertEqual(doctest.testmod(m=goniometry)[0], 0)
def test_match_add_quadrants(self):
s, c = root = tree('sin(t) ^ 2 + cos(t) ^ 2')
s, c = root = tree('sin^2 t + cos^2 t')
self.assertEqualPos(match_add_quadrants(root),
[P(root, add_quadrants, (Scope(root), s, c))])
c, s = root = tree('cos(t) ^ 2 + sin(t) ^ 2')
c, s = root = tree('cos^2 t + sin^2 t')
self.assertEqualPos(match_add_quadrants(root),
[P(root, add_quadrants, (Scope(root), s, c))])
(s, a), c = root = tree('sin(t) ^ 2 + a + cos(t) ^ 2')
(s, a), c = root = tree('sin^2 t + a + cos^2 t')
self.assertEqualPos(match_add_quadrants(root),
[P(root, add_quadrants, (Scope(root), s, c))])
(s, c0), c1 = root = tree('sin(t) ^ 2 + cos(t) ^ 2 + cos(t) ^ 2')
(s, c0), c1 = root = tree('sin^2 t + cos^2 t + cos^2 t')
self.assertEqualPos(match_add_quadrants(root),
[P(root, add_quadrants, (Scope(root), s, c0)),
P(root, add_quadrants, (Scope(root), s, c1))])
root = tree('sin(t) ^ 2 + cos(y) ^ 2')
root = tree('sin^2 t + cos^2 y')
self.assertEqualPos(match_add_quadrants(root), [])
root = tree('sin(t) ^ 2 - cos(t) ^ 2')
root = tree('sin^2 t - cos^2 t')
self.assertEqualPos(match_add_quadrants(root), [])
s, c = root = tree('-sin(t) ^ 2 - cos(t) ^ 2')
s, c = root = tree('-sin^2 t - cos^2 t')
self.assertEqualPos(match_add_quadrants(root),
[P(root, factor_out_quadrant_negation, (Scope(root), s, c))])
......
......@@ -141,9 +141,9 @@ class TestRulesIntegrals(RulesTestCase):
self.assertRewrite([
'int a / x',
'int a * 1 / x dx',
'aint 1 / x dx',
'a(int 1 / x dx)',
'a(ln|x| + C)',
'aln|x| + aC',
'a ln|x| + aC',
# FIXME: 'aln|x| + C', # ac -> C
])
......@@ -176,24 +176,24 @@ class TestRulesIntegrals(RulesTestCase):
self.assertEqual(cosinus_integral(root, ()), expect)
def test_match_sum_rule_integral(self):
(f, g), x = root = tree('int 2x + 3x dx')
(f, g), x = root = tree('int (2x + 3x) dx')
self.assertEqualPos(match_sum_rule_integral(root),
[P(root, sum_rule_integral, (Scope(root[0]), f))])
((f, g), h), x = root = tree('int 2x + 3x + 4x dx')
((f, g), h), x = root = tree('int (2x + 3x + 4x) dx')
self.assertEqualPos(match_sum_rule_integral(root),
[P(root, sum_rule_integral, (Scope(root[0]), f)),
P(root, sum_rule_integral, (Scope(root[0]), g)),
P(root, sum_rule_integral, (Scope(root[0]), h))])
def test_sum_rule_integral(self):
((f, g), h), x = root = tree('int 2x + 3x + 4x dx')
((f, g), h), x = root = tree('int (2x + 3x + 4x) dx')
self.assertEqual(sum_rule_integral(root, (Scope(root[0]), f)),
tree('int 2x dx + int 3x + 4x dx'))
tree('int 2x dx + int (3x + 4x) dx'))
self.assertEqual(sum_rule_integral(root, (Scope(root[0]), g)),
tree('int 3x dx + int 2x + 4x dx'))
tree('int 3x dx + int (2x + 4x) dx'))
self.assertEqual(sum_rule_integral(root, (Scope(root[0]), h)),
tree('int 4x dx + int 2x + 3x dx'))
tree('int 4x dx + int (2x + 3x) dx'))
def test_match_remove_indef_constant(self):
Fx, a, b = root = tree('[2x + C]_a^b')
......
......@@ -98,8 +98,8 @@ class TestRulesLineq(RulesTestCase):
'2x = -3x - 5',
'2x - -3x = -3x - 5 - -3x',
'2x + 3x = -3x - 5 - -3x',
'(2 + 3)x = -3x - 5 - -3x',
'5x = -3x - 5 - -3x',
'2x + 3x = -3x - 5 + 3x',
'(2 + 3)x = -3x - 5 + 3x',
'5x = -3x - 5 + 3x',
'5x = (-1 + 1)3x - 5',
'5x = 0 * 3x - 5',
......@@ -116,11 +116,11 @@ class TestRulesLineq(RulesTestCase):
def test_match_move_term_chain_advanced(self):
self.assertRewrite([
'-x = a',
'(-x)(-1) = a(-1)',
'-x(-1) = a(-1)',
'--x * 1 = a(-1)',
'--x = a(-1)',
'x = 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',
])
......
......@@ -106,8 +106,8 @@ class TestRulesNegation(RulesTestCase):
def test_double_negated_division(self):
self.assertRewrite([
'(-a) / (-b)',
'-a / (-b)',
'(-a) / -b',
'-a / -b',
'--a / b',
'a / b',
])
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