Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
trs
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
trs
Commits
41047c7e
Commit
41047c7e
authored
Feb 20, 2012
by
Taddeus Kroes
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'negated'
parents
d419cb36
c050ce57
Changes
29
Hide whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
829 additions
and
555 deletions
+829
-555
external/graph_drawing
external/graph_drawing
+1
-1
src/node.py
src/node.py
+106
-37
src/parser.py
src/parser.py
+46
-45
src/possibilities.py
src/possibilities.py
+26
-5
src/rules/__init__.py
src/rules/__init__.py
+14
-13
src/rules/factors.py
src/rules/factors.py
+6
-2
src/rules/fractions.py
src/rules/fractions.py
+10
-5
src/rules/goniometry.py
src/rules/goniometry.py
+32
-0
src/rules/groups.py
src/rules/groups.py
+24
-19
src/rules/negation.py
src/rules/negation.py
+77
-45
src/rules/numerics.py
src/rules/numerics.py
+77
-26
src/rules/poly.py
src/rules/poly.py
+0
-92
src/rules/powers.py
src/rules/powers.py
+45
-8
tests/rulestestcase.py
tests/rulestestcase.py
+19
-9
tests/test_b1_ch08.py
tests/test_b1_ch08.py
+3
-3
tests/test_b1_ch10.py
tests/test_b1_ch10.py
+3
-3
tests/test_calc.py
tests/test_calc.py
+2
-2
tests/test_exception.py
tests/test_exception.py
+1
-6
tests/test_leiden_oefenopgave.py
tests/test_leiden_oefenopgave.py
+58
-24
tests/test_leiden_oefenopgave_v12.py
tests/test_leiden_oefenopgave_v12.py
+34
-0
tests/test_node.py
tests/test_node.py
+22
-18
tests/test_parser.py
tests/test_parser.py
+12
-0
tests/test_possibilities.py
tests/test_possibilities.py
+5
-7
tests/test_rules_goniometry.py
tests/test_rules_goniometry.py
+17
-0
tests/test_rules_groups.py
tests/test_rules_groups.py
+42
-16
tests/test_rules_negation.py
tests/test_rules_negation.py
+56
-12
tests/test_rules_numerics.py
tests/test_rules_numerics.py
+50
-32
tests/test_rules_poly.py
tests/test_rules_poly.py
+0
-112
tests/test_rules_powers.py
tests/test_rules_powers.py
+41
-13
No files found.
graph_drawing
@
821bdb8f
Subproject commit 8
4ad376b81ac72e163bacd7b538df16cac9be15
3
Subproject commit 8
21bdb8f8408fb36ee1d92ada162589794a8c5b
3
src/node.py
View file @
41047c7e
...
...
@@ -29,9 +29,21 @@ OP_MOD = 7
# N-ary (functions)
OP_INT
=
8
OP_EXPAND
=
9
OP_COMMA
=
10
OP_SQRT
=
11
OP_COMMA
=
9
OP_SQRT
=
10
# Goniometry
OP_SIN
=
11
OP_COS
=
12
OP_TAN
=
13
OP_SOLVE
=
14
OP_EQ
=
15
OP_POSSIBILITIES
=
16
OP_HINT
=
17
OP_REWRITE_ALL
=
18
OP_REWRITE
=
19
TYPE_MAP
=
{
...
...
@@ -41,19 +53,45 @@ TYPE_MAP = {
}
OP_MAP
=
{
','
:
OP_COMMA
,
'+'
:
OP_ADD
,
# Either substraction or negation. Skip the operator sign in 'x' (= 2).
'-'
:
OP_SUB
,
'*'
:
OP_MUL
,
'/'
:
OP_DIV
,
'^'
:
OP_POW
,
'
mod'
:
OP_MOD
,
'
int'
:
OP_INT
,
'
expand'
:
OP_EXPAND
,
'
sin'
:
OP_SIN
,
'
cos'
:
OP_COS
,
'
tan'
:
OP_TAN
,
'sqrt'
:
OP_SQRT
,
','
:
OP_COMMA
,
'int'
:
OP_INT
,
'solve'
:
OP_SOLVE
,
'='
:
OP_EQ
,
'??'
:
OP_POSSIBILITIES
,
'?'
:
OP_HINT
,
'@@'
:
OP_REWRITE_ALL
,
'@'
:
OP_REWRITE
,
}
TOKEN_MAP
=
{
OP_COMMA
:
'COMMA'
,
OP_ADD
:
'PLUS'
,
OP_SUB
:
'MINUS'
,
OP_MUL
:
'TIMES'
,
OP_DIV
:
'DIVIDE'
,
OP_POW
:
'POW'
,
OP_SQRT
:
'SQRT'
,
OP_SIN
:
'SIN'
,
OP_COS
:
'COS'
,
OP_TAN
:
'TAN'
,
OP_INT
:
'INT'
,
OP_SOLVE
:
'SOLVE'
,
OP_EQ
:
'EQ'
,
OP_POSSIBILITIES
:
'POSSIBILITIES'
,
OP_HINT
:
'HINT'
,
OP_REWRITE_ALL
:
'REWRITE_ALL'
,
OP_REWRITE
:
'REWRITE'
,
}
def
to_expression
(
obj
):
return
obj
if
isinstance
(
obj
,
ExpressionBase
)
else
ExpressionLeaf
(
obj
)
...
...
@@ -112,8 +150,11 @@ class ExpressionBase(object):
def
is_op
(
self
,
op
):
return
not
self
.
is_leaf
and
self
.
op
==
op
def
is_power
(
self
):
return
not
self
.
is_leaf
and
self
.
op
==
OP_POW
def
is_power
(
self
,
exponent
=
None
):
if
self
.
is_leaf
or
self
.
op
!=
OP_POW
:
return
False
return
exponent
==
None
or
self
[
1
]
==
exponent
def
is_nary
(
self
):
return
not
self
.
is_leaf
and
self
.
op
in
[
OP_ADD
,
OP_SUB
,
OP_MUL
]
...
...
@@ -145,8 +186,13 @@ class ExpressionBase(object):
def
__pow__
(
self
,
other
):
return
ExpressionNode
(
'^'
,
self
,
to_expression
(
other
))
def
__pos__
(
self
):
return
self
.
reduce_negation
()
def
reduce_negation
(
self
,
n
=
1
):
"""Remove n negation flags from the node."""
assert
self
.
negated
return
self
.
negate
(
-
n
)
def
negate
(
self
,
n
=
1
):
...
...
@@ -160,9 +206,6 @@ class ExpressionNode(Node, ExpressionBase):
self
.
type
=
TYPE_OPERATOR
self
.
op
=
OP_MAP
[
args
[
0
]]
if
hasattr
(
self
.
op
,
'__call__'
):
self
.
op
=
self
.
op
(
args
)
def
__str__
(
self
):
# pragma: nocover
return
generate_line
(
self
)
...
...
@@ -170,10 +213,8 @@ class ExpressionNode(Node, ExpressionBase):
"""
Check strict equivalence.
"""
if
isinstance
(
other
,
ExpressionNode
):
return
self
.
op
==
other
.
op
and
self
.
nodes
==
other
.
nodes
return
False
return
isinstance
(
other
,
ExpressionNode
)
and
self
.
op
==
other
.
op
\
and
self
.
negated
==
other
.
negated
and
self
.
nodes
==
other
.
nodes
def
substitute
(
self
,
old_child
,
new_child
):
self
.
nodes
[
self
.
nodes
.
index
(
old_child
)]
=
new_child
...
...
@@ -245,7 +286,7 @@ class ExpressionNode(Node, ExpressionBase):
return
(
self
[
0
],
self
[
1
],
ExpressionLeaf
(
1
))
return
(
self
[
1
],
self
[
0
],
ExpressionLeaf
(
1
))
def
equals
(
self
,
other
):
def
equals
(
self
,
other
,
ignore_negation
=
False
):
"""
Perform a non-strict equivalence check between two nodes:
- If the other node is a leaf, it cannot be equal to this node.
...
...
@@ -256,18 +297,14 @@ class ExpressionNode(Node, ExpressionBase):
- If both nodes are divisions, the nominator and denominator have to be
non-strictly equal.
"""
if
not
other
.
is_op
(
self
.
op
):
# FIXME: this is if-clause is a problem. To fix this problem
# permanently, normalize ("x * -1" -> "-1x") before comparing to
# the other node.
if
not
isinstance
(
other
,
ExpressionNode
)
or
other
.
op
!=
self
.
op
:
return
False
if
self
.
op
in
(
OP_ADD
,
OP_MUL
):
s0
=
Scope
(
self
)
s1
=
set
(
Scope
(
other
))
# Scopes sould be of equal size
# Scopes s
h
ould be of equal size
if
len
(
s0
)
!=
len
(
s1
):
return
False
...
...
@@ -291,7 +328,10 @@ class ExpressionNode(Node, ExpressionBase):
if
not
child
.
equals
(
other
[
i
]):
return
False
return
True
if
ignore_negation
:
return
True
return
self
.
negated
==
other
.
negated
class
ExpressionLeaf
(
Leaf
,
ExpressionBase
):
...
...
@@ -306,16 +346,33 @@ class ExpressionLeaf(Leaf, ExpressionBase):
other_type
=
type
(
other
)
if
other_type
in
TYPE_MAP
:
return
TYPE_MAP
[
other_type
]
==
self
.
type
and
self
.
value
==
other
return
TYPE_MAP
[
other_type
]
==
self
.
type
\
and
self
.
actual_value
()
==
other
return
other
.
type
==
self
.
type
and
self
.
value
==
other
.
value
return
self
.
negated
==
other
.
negated
and
self
.
type
==
other
.
type
\
and
self
.
value
==
other
.
value
def
equals
(
self
,
other
):
def
__repr__
(
self
):
return
'-'
*
self
.
negated
+
str
(
self
.
value
)
def
equals
(
self
,
other
,
ignore_negation
=
False
):
"""
Check non-strict equivalence.
Between leaves, this is the same as strict equivalence.
Between leaves, this is the same as strict equivalence, except when
negations must be ignored.
"""
return
self
==
other
if
ignore_negation
:
other_type
=
type
(
other
)
if
other_type
in
(
int
,
float
):
return
TYPE_MAP
[
other_type
]
==
self
.
type
\
and
self
.
value
==
abs
(
other
)
elif
other_type
==
str
:
return
self
.
type
==
TYPE_IDENTIFIER
and
self
.
value
==
other
return
self
.
type
==
other
.
type
and
self
.
value
==
other
.
value
else
:
return
self
==
other
def
extract_polynome_properties
(
self
):
"""
...
...
@@ -350,7 +407,14 @@ class Scope(object):
def
__iter__
(
self
):
return
iter
(
self
.
nodes
)
def
remove
(
self
,
node
,
replacement
=
None
):
def
__eq__
(
self
,
other
):
return
isinstance
(
other
,
Scope
)
and
self
.
node
==
other
.
node
\
and
self
.
nodes
==
other
.
nodes
def
__repr__
(
self
):
return
'<Scope of "%s">'
%
repr
(
self
.
node
)
def
remove
(
self
,
node
,
**
kwargs
):
if
node
.
is_leaf
:
node_cmp
=
hash
(
node
)
else
:
...
...
@@ -363,8 +427,8 @@ class Scope(object):
n_cmp
=
n
if
n_cmp
==
node_cmp
:
if
replacement
!=
None
:
self
[
i
]
=
replacement
if
'replacement'
in
kwargs
:
self
[
i
]
=
kwargs
[
'replacement'
]
else
:
del
self
.
nodes
[
i
]
...
...
@@ -373,8 +437,11 @@ class Scope(object):
raise
ValueError
(
'Node "%s" is not in the scope of "%s".'
%
(
node
,
self
.
node
))
def
replace
(
self
,
node
,
replacement
):
self
.
remove
(
node
,
replacement
=
replacement
)
def
as_nary_node
(
self
):
return
nary_node
(
self
.
node
.
value
,
self
.
nodes
)
return
nary_node
(
self
.
node
.
value
,
self
.
nodes
)
.
negate
(
self
.
node
.
negated
)
def
nary_node
(
operator
,
scope
):
...
...
@@ -405,7 +472,9 @@ def get_scope(node):
def
negate
(
node
,
n
=
1
):
"""Negate the given node n times."""
node
=
node
.
clone
()
node
.
negated
=
n
assert
n
>=
0
new_node
=
node
.
clone
()
new_node
.
negated
=
n
return
node
return
n
ew_n
ode
src/parser.py
View file @
41047c7e
...
...
@@ -3,8 +3,6 @@ This parser will parse the given input and build an expression tree. Grammar
file for the supported mathematical expressions.
"""
from
node
import
ExpressionNode
as
Node
,
ExpressionLeaf
as
Leaf
import
os.path
PYBISON_BUILD
=
os
.
path
.
realpath
(
'build/external/pybison'
)
EXTERNAL_MODS
=
os
.
path
.
realpath
(
'external'
)
...
...
@@ -16,7 +14,8 @@ sys.path.insert(1, EXTERNAL_MODS)
from
pybison
import
BisonParser
,
BisonSyntaxError
from
graph_drawing.graph
import
generate_graph
from
node
import
TYPE_OPERATOR
,
OP_COMMA
,
OP_NEG
from
node
import
ExpressionNode
as
Node
,
ExpressionLeaf
as
Leaf
,
OP_MAP
,
\
TOKEN_MAP
,
TYPE_OPERATOR
,
OP_COMMA
,
OP_NEG
,
OP_MUL
,
Scope
from
rules
import
RULES
from
possibilities
import
filter_duplicates
,
pick_suggestion
,
apply_suggestion
...
...
@@ -52,10 +51,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'
,
'POSSIBILITIES'
,
'PLUS'
,
'MINUS'
,
'TIMES'
,
'DIVIDE'
,
'POW'
,
'LPAREN'
,
'RPAREN'
,
'COMMA'
,
'HINT'
,
'REWRITE'
,
'NEWLINE'
,
'QUIT'
,
'RAISE'
,
'GRAPH'
,
'SQRT'
]
tokens
=
[
'NUMBER'
,
'IDENTIFIER'
,
'NEWLINE'
,
'QUIT'
,
'RAISE'
,
'GRAPH'
,
\
'LPAREN'
,
'RPAREN'
]
+
TOKEN_MAP
.
values
()
# ------------------------------
# precedences
...
...
@@ -74,13 +71,20 @@ class Parser(BisonParser):
BisonParser
.
__init__
(
self
,
**
kwargs
)
self
.
interactive
=
kwargs
.
get
(
'interactive'
,
0
)
self
.
timeout
=
kwargs
.
get
(
'timeout'
,
0
)
self
.
possibilities
=
self
.
last_possibilities
=
[]
self
.
reset
()
def
reset
(
self
):
self
.
read_buffer
=
''
self
.
read_queue
=
Queue
.
Queue
()
self
.
subtree_map
=
{}
#
self.subtree_map = {}
self
.
root_node
=
None
self
.
possibilities
=
self
.
last_possibilities
=
[]
def
run
(
self
,
*
args
,
**
kwargs
):
self
.
reset
()
return
super
(
Parser
,
self
).
run
(
*
args
,
**
kwargs
)
# Override default read method with a version that prompts for input.
def
read
(
self
,
nbytes
):
...
...
@@ -186,32 +190,16 @@ class Parser(BisonParser):
if
not
retval
.
negated
and
retval
.
type
!=
TYPE_OPERATOR
:
return
retval
if
self
.
subtree_map
and
retval
.
type
==
TYPE_OPERATOR
:
# Update the subtree map to let the subtree point to its parent
# node.
parent_nodes
=
self
.
subtree_map
.
keys
()
for
child
in
retval
:
if
child
in
parent_nodes
:
self
.
subtree_map
[
child
]
=
retval
if
retval
.
type
==
TYPE_OPERATOR
and
retval
.
op
in
RULES
:
handlers
=
RULES
[
retval
.
op
]
else
:
handlers
=
[]
if
retval
.
negated
:
handlers
+
=
RULES
[
OP_NEG
]
handlers
=
RULES
[
OP_NEG
]
for
handler
in
handlers
:
possibilities
=
handler
(
retval
)
# Record the subtree root node in order to avoid tree traversal.
# At this moment, the node is the root node since the expression is
# parser using the left-innermost parsing strategy.
for
p
in
possibilities
:
self
.
subtree_map
[
p
.
root
]
=
None
self
.
possibilities
.
extend
(
possibilities
)
return
retval
...
...
@@ -231,8 +219,7 @@ class Parser(BisonParser):
if
not
suggestion
:
return
self
.
root_node
expression
=
apply_suggestion
(
self
.
root_node
,
self
.
subtree_map
,
suggestion
)
expression
=
apply_suggestion
(
self
.
root_node
,
suggestion
)
if
self
.
verbose
:
print
'After application, expression='
,
expression
...
...
@@ -350,9 +337,14 @@ class Parser(BisonParser):
"""
if
option
==
0
:
# rule: NEG exp
node
=
values
[
1
]
node
.
negated
+=
1
return
node
# Add negation to the left-most child
if
values
[
1
].
is_leaf
or
values
[
1
].
op
!=
OP_MUL
:
values
[
1
].
negated
+=
1
else
:
child
=
Scope
(
values
[
1
])[
0
]
child
.
negated
+=
1
return
values
[
1
]
raise
BisonSyntaxError
(
'Unsupported option %d in target "%s".'
%
(
option
,
target
))
# pragma: nocover
...
...
@@ -371,8 +363,18 @@ class Parser(BisonParser):
if
option
==
4
:
# rule: exp MINUS exp
node
=
values
[
2
]
node
.
negated
+=
1
return
Node
(
'+'
,
values
[
0
],
node
)
# Add negation to the left-most child
if
node
.
is_leaf
or
node
.
op
!=
OP_MUL
:
node
.
negated
+=
1
else
:
node
=
Scope
(
node
)[
0
]
node
.
negated
+=
1
# Explicit call the hook handler on the created unary negation.
node
=
self
.
hook_handler
(
'binary'
,
4
,
names
,
values
,
node
)
return
Node
(
'+'
,
values
[
0
],
values
[
2
])
raise
BisonSyntaxError
(
'Unsupported option %d in target "%s".'
%
(
option
,
target
))
# pragma: nocover
...
...
@@ -388,6 +390,15 @@ class Parser(BisonParser):
raise
BisonSyntaxError
(
'Unsupported option %d in target "%s".'
%
(
option
,
target
))
# pragma: nocover
# -----------------------------------------
# operator tokens
# -----------------------------------------
operators
=
''
for
op_str
,
op
in
OP_MAP
.
iteritems
():
operators
+=
'"%s"%s{ returntoken(%s); }
\
n
'
\
%
(
op_str
,
' '
*
(
8
-
len
(
op_str
)),
TOKEN_MAP
[
op
])
# -----------------------------------------
# raw lex script, verbatim here
# -----------------------------------------
...
...
@@ -415,8 +426,6 @@ class Parser(BisonParser):
yylloc.first_column = yycolumn; \
yylloc.last_column = yycolumn + yyleng; \
yycolumn += yyleng;
/*[a-zA-Z][0-9]+ { returntoken(CONCAT_POW); }*/
%}
%option yylineno
...
...
@@ -427,19 +436,11 @@ class Parser(BisonParser):
[a-zA-Z] { returntoken(IDENTIFIER); }
"(" { returntoken(LPAREN); }
")" { returntoken(RPAREN); }
"+" { returntoken(PLUS); }
"-" { returntoken(MINUS); }
"*" { returntoken(TIMES); }
"^" { returntoken(POW); }
"/" { returntoken(DIVIDE); }
"," { returntoken(COMMA); }
"??" { returntoken(POSSIBILITIES); }
"?" { returntoken(HINT); }
"@" { returntoken(REWRITE); }
"quit" { yyterminate(); returntoken(QUIT); }
"""
+
operators
+
r"""
"raise" { returntoken(RAISE); }
"graph" { returntoken(GRAPH); }
"
sqrt" { returntoken(SQR
T); }
"
quit" { yyterminate(); returntoken(QUI
T); }
[ \t\v\f] { }
[\n] { yycolumn = 0; returntoken(NEWLINE); }
...
...
src/possibilities.py
View file @
41047c7e
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
...
...
@@ -60,7 +63,27 @@ def pick_suggestion(possibilities):
return
possibilities
[
suggestion
]
def
apply_suggestion
(
root
,
subtree_map
,
suggestion
):
def
find_parent_node
(
root
,
child
):
nodes
=
[
root
]
while
nodes
:
node
=
nodes
.
pop
()
while
node
:
if
node
.
type
!=
TYPE_OPERATOR
:
break
if
child
in
node
:
return
node
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 +91,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 +105,5 @@ def apply_suggestion(root, subtree_map, suggestion):
if
parent_node
:
parent_node
.
substitute
(
suggestion
.
root
,
subtree
)
return
root
return
subtree
src/rules/__init__.py
View file @
41047c7e
from
..node
import
OP_ADD
,
OP_MUL
,
OP_DIV
,
OP_POW
,
OP_NEG
from
.poly
import
match_combine_polynomes
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_extend_exponent
,
match_constant_exponent
from
.numerics
import
match_
add_numerics
,
match_divide
_numerics
,
\
match_multiply_
numerics
,
match_multiply_zero
,
match_multiply_one
from
.fractions
import
match_constant_division
,
match_add_constant_fractions
,
\
match_expand_and_add_fractions
from
.negation
import
match_negate_group
,
match_negated_division
from
.negation
import
match_negated_factor
,
match_negate_polynome
,
\
match_negated_division
RULES
=
{
OP_ADD
:
[
match_add_
constant_fractions
,
match_combine_polynomes
,
\
OP_ADD
:
[
match_add_
numerics
,
match_add_constant_fractions
,
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
,
\
OP_MUL
:
[
match_multiply_numerics
,
match_expand
,
match_add_exponents
,
match_expand_and_add_fractions
,
match_multiply_zero
,
match_negated_factor
,
match_multiply_one
],
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_POW
:
[
match_multiply_exponents
,
match_duplicate_exponent
,
match_remove_negative_exponent
,
match_exponent_to_root
,
match_extend_exponent
,
match_constant_exponent
],
OP_NEG
:
[
match_negate_
polynome
],
}
src/rules/factors.py
View file @
41047c7e
...
...
@@ -21,6 +21,10 @@ def match_expand(node):
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
.
re
mov
e
(
a
,
a
*
b
+
a
*
c
)
scope
.
re
plac
e
(
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
.
re
mov
e
(
ab
,
a
*
c
+
a
*
d
+
b
*
c
+
b
*
d
)
scope
.
re
plac
e
(
ab
,
a
*
c
+
a
*
d
+
b
*
c
+
b
*
d
)
# Remove the right addition
scope
.
remove
(
cd
)
...
...
src/rules/fractions.py
View file @
41047c7e
from
itertools
import
combinations
from
.utils
import
least_common_multiple
from
..node
import
ExpressionLeaf
as
L
,
Scope
,
OP_DIV
,
OP_ADD
,
OP_MUL
from
..node
import
ExpressionLeaf
as
L
,
Scope
,
negate
,
OP_DIV
,
OP_ADD
,
OP_MUL
from
..possibilities
import
Possibility
as
P
,
MESSAGES
from
..translate
import
_
...
...
@@ -112,10 +112,15 @@ def equalize_denominators(root, args):
mult
=
denom
/
d
.
value
if
mult
!=
1
:
n
=
L
(
n
.
value
*
mult
)
if
n
.
is_numeric
()
else
L
(
mult
)
*
n
if
n
.
is_numeric
():
n
=
L
(
n
.
value
*
mult
)
else
:
n
=
L
(
mult
)
*
n
scope
.
remove
(
fraction
,
negate
(
n
/
L
(
d
.
value
*
mult
),
fraction
.
negated
))
#n = L(n.value * mult) if n.is_numeric() else L(mult) * n
scope
.
replace
(
fraction
,
negate
(
n
/
L
(
d
.
value
*
mult
),
fraction
.
negated
))
return
scope
.
as_nary_node
()
...
...
@@ -137,7 +142,7 @@ def add_nominators(root, args):
scope
=
Scope
(
root
)
# Replace the left node with the new expression
scope
.
re
move
(
ab
,
(
a
+
negate
(
cb
[
0
],
cb
.
negated
))
/
b
)
scope
.
re
place
(
ab
,
(
a
+
cb
[
0
].
negate
(
cb
.
negated
))
/
b
)
# Remove the right node
scope
.
remove
(
cb
)
...
...
src/rules/goniometry.py
0 → 100644
View file @
41047c7e
from
..node
import
ExpressionNode
as
N
,
ExpressionLeaf
as
L
,
Scope
,
\
OP_ADD
,
OP_POW
,
OP_MUL
,
OP_SIN
,
OP_COS
,
OP_TAN
from
..possibilities
import
Possibility
as
P
,
MESSAGES
from
..translate
import
_
def
match_add_quadrants
(
node
):
"""
sin(x) ^ 2 + cos(x) ^ 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(x) ^ 2 + cos(x) ^ 2 -> 1
"""
return
L
(
1
)
MESSAGES
[
add_quadrants
]
=
_
(
'Add the sinus and cosinus quadrants to 1.'
)
src/rules/groups.py
View file @
41047c7e
from
itertools
import
combinations
from
..node
import
ExpressionNode
as
Node
,
ExpressionLeaf
as
Leaf
,
Scope
,
\
OP_ADD
,
OP_MUL
OP_ADD
,
OP_MUL
,
nary_node
,
negate
from
..possibilities
import
Possibility
as
P
,
MESSAGES
from
..translate
import
_
...
...
@@ -22,44 +22,49 @@ def match_combine_groups(node):
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
):
for
i
,
sub_node
in
enumerate
(
n_
scope
):
if
sub_node
.
is_numeric
():
others
=
[
scope
[
j
]
for
j
in
range
(
i
)
+
range
(
i
+
1
,
l
)]
others
=
[
n_
scope
[
j
]
for
j
in
range
(
i
)
+
range
(
i
+
1
,
l
)]
if
len
(
others
)
==
1
:
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
.
re
mov
e
(
n0
,
(
c0
+
c1
)
*
g0
)
scope
.
re
plac
e
(
n0
,
(
c0
+
c1
)
*
g0
)
# Remove the right node
scope
.
remove
(
n1
)
...
...
@@ -68,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.'
)
src/rules/negation.py
View file @
41047c7e
from
..node
import
get_scope
,
nary_nod
e
,
OP_ADD
,
OP_MUL
,
OP_DIV
from
..node
import
Scop
e
,
OP_ADD
,
OP_MUL
,
OP_DIV
from
..possibilities
import
Possibility
as
P
,
MESSAGES
from
..translate
import
_
def
match_negate
_group
(
node
):
def
match_negate
d_factor
(
node
):
"""
--a -> a
-(a * ... * -b) -> ab
-(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
.
negated
assert
node
.
is_op
(
OP_MUL
)
if
node
.
negated
==
2
:
# --a
return
[
P
(
node
,
double_negation
,
(
node
,))]
p
=
[]
scope
=
Scope
(
node
)
if
not
node
.
is_leaf
:
scope
=
get_scope
(
node
)
# FIXME: The negation that is brought outside is assigned to the first
# element in the scope during the next parsing step:
# -ab -> -(ab), but -(ab) is printed as -ab
for
factor
in
scope
[
1
:]:
if
factor
.
negated
:
p
.
append
(
P
(
node
,
negated_factor
,
(
scope
,
factor
)))
if
node
.
is_op
(
OP_MUL
)
and
any
(
map
(
lambda
n
:
n
.
negated
,
scope
)):
# -(-a)b
return
[
P
(
node
,
negate_group
,
(
node
,
scope
))]
return
p
if
node
.
is_op
(
OP_ADD
):
# -(ab + c) -> -ab - c
# -(-ab + c) -> ab - c
return
[
P
(
node
,
negate_polynome
,
(
node
,
scope
))]
return
[]
def
negated_factor
(
root
,
args
):
"""
a * -b -> -ab
"""
scope
,
factor
=
args
scope
[
0
]
=
-
scope
[
0
]
scope
.
replace
(
factor
,
+
factor
)
return
scope
.
as_nary_node
()
def
negate_group
(
root
,
args
):
MESSAGES
[
negated_factor
]
=
\
_
(
'Bring negation of {2} to the outside of the multiplication.'
)
def
match_negate_polynome
(
node
):
"""
-
(a * -3c) -> a * 3c
-(a
* ... * -b) -> a
b
-
-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
):
if
n
.
negated
:
scope
[
i
]
=
n
.
reduce_negation
()
p
=
[]
if
node
.
negated
==
2
:
# --a
p
.
append
(
P
(
node
,
double_negation
,
()))
return
nary_node
(
'*'
,
scope
).
reduce_negation
()
if
node
.
is_op
(
OP_ADD
):
# -(a + b) -> -a - b
p
.
append
(
P
(
node
,
negate_polynome
,
()))
return
p
MESSAGES
[
negate_group
]
=
_
(
'Apply negation to the polynome {1[0]}.'
)
def
double_negation
(
root
,
args
):
"""
--a -> a
"""
return
root
.
reduce_negation
(
2
)
MESSAGES
[
double_negation
]
=
_
(
'Remove double negation in {0}.'
)
def
negate_polynome
(
root
,
args
):
"""
-(
-ab + ... + c) -> --ab + ... + -c
-(
a + b) -> -a - b
"""
node
,
scope
=
args
scope
=
Scope
(
root
)
# Negate each group
for
i
,
n
in
enumerate
(
scope
):
scope
[
i
]
=
-
n
return
nary_node
(
'+'
,
scope
)
return
+
scope
.
as_nary_node
(
)
MESSAGES
[
negate_polynome
]
=
_
(
'Apply negation to the subexpression {1[0]}.'
)
def
double_negation
(
root
,
args
):
"""
--a -> a
"""
return
negate
(
args
[
0
],
args
[
0
].
negated
-
2
)
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
):
...
...
@@ -82,11 +114,11 @@ def match_negated_division(node):
a
,
b
=
node
if
a
.
negated
and
b
.
negated
:
return
[
P
(
node
,
double_negated_division
,
(
node
,
))]
return
[
P
(
node
,
double_negated_division
,
())]
elif
a
.
negated
:
return
[
P
(
node
,
single_negated_division
,
(
a
[
0
]
,
b
))]
return
[
P
(
node
,
single_negated_division
,
(
+
a
,
b
))]
elif
b
.
negated
:
return
[
P
(
node
,
single_negated_division
,
(
a
,
b
[
0
]
))]
return
[
P
(
node
,
single_negated_division
,
(
a
,
+
b
))]
return
[]
...
...
@@ -113,11 +145,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
src/rules/numerics.py
View file @
41047c7e
from
itertools
import
combinations
from
..node
import
ExpressionLeaf
as
Leaf
,
Scope
,
negate
,
OP_DIV
,
OP_MUL
from
..node
import
ExpressionLeaf
as
Leaf
,
Scope
,
negate
,
OP_ADD
,
OP_DIV
,
\
OP_MUL
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.
...
...
@@ -15,19 +16,43 @@ def add_numerics(root, args):
-2 + 3 -> 1
-2 + -3 -> -5
"""
n0
,
n1
,
c0
,
c1
=
args
scope
=
Scope
(
root
)
assert
node
.
is_op
(
OP_ADD
)
p
=
[]
scope
=
Scope
(
node
)
numerics
=
filter
(
lambda
n
:
n
.
is_numeric
(),
scope
)
for
c0
,
c1
in
combinations
(
numerics
,
2
):
p
.
append
(
P
(
node
,
add_numerics
,
(
scope
,
c0
,
c1
)))
return
p
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
()
if
value
<
0
:
leaf
=
Leaf
(
-
value
).
negate
()
else
:
leaf
=
Leaf
(
value
)
# Replace the left node with the new expression
scope
.
re
move
(
n0
,
Leaf
(
c0
.
actual_value
()
+
c1
.
actual_value
(
)))
scope
.
re
place
(
c0
,
Leaf
(
abs
(
value
)).
negate
(
int
(
value
<
0
)))
# Remove the right node
scope
.
remove
(
n
1
)
scope
.
remove
(
c
1
)
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):
...
...
@@ -130,6 +155,42 @@ def multiply_zero(root, args):
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
...
...
@@ -140,14 +201,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
.
actual_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
,
v
1
)))
for
c0
,
c1
in
combinations
(
numerics
,
2
):
p
.
append
(
P
(
node
,
multiply_numerics
,
(
scope
,
c0
,
c
1
)))
return
p
...
...
@@ -159,24 +217,17 @@ 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
,
c0
,
c1
=
args
scope
=
Scope
(
root
)
# 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
(
n
1
)
scope
.
remove
(
c
1
)
return
scope
.
as_nary_node
()
MESSAGES
[
multiply_numerics
]
=
_
(
'Multiply constant {
1} with {2
}.'
)
MESSAGES
[
multiply_numerics
]
=
_
(
'Multiply constant {
2} with {3
}.'
)
src/rules/poly.py
deleted
100644 → 0
View file @
d419cb36
from
itertools
import
combinations
from
..node
import
Scope
,
OP_ADD
from
..possibilities
import
Possibility
as
P
,
MESSAGES
from
.numerics
import
add_numerics
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
(
lambda
n
:
n
.
is_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
()
src/rules/powers.py
View file @
41047c7e
...
...
@@ -17,8 +17,9 @@ def match_add_exponents(node):
p
=
[]
powers
=
{}
scope
=
Scope
(
node
)
for
n
in
Scope
(
node
)
:
for
n
in
scope
:
if
n
.
is_identifier
():
s
=
n
exponent
=
L
(
1
)
...
...
@@ -26,7 +27,7 @@ def match_add_exponents(node):
# 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 +42,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 +51,10 @@ 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
# Replace the left node with the new expression
scope
.
re
mov
e
(
n0
,
a
**
(
p
+
q
))
scope
.
re
plac
e
(
n0
,
a
**
(
p
+
q
))
# Remove the right node
scope
.
remove
(
n1
)
...
...
@@ -62,7 +62,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
):
...
...
@@ -162,7 +162,7 @@ MESSAGES[duplicate_exponent] = _('Duplicate the exponent {2}.')
def
match_remove_negative_exponent
(
node
):
"""
a
^-p -> 1 / a^
p
a
^ -p -> 1 / a ^
p
"""
assert
node
.
is_op
(
OP_POW
)
...
...
@@ -237,3 +237,40 @@ def extend_exponent(root, args):
return
left
*
left
**
L
(
right
.
value
-
1
)
return
left
*
left
def
match_constant_exponent
(
node
):
"""
(a + ... + z)^n -> (a + ... + z)(a + ... + z)^(n - 1) # n > 1
"""
assert
node
.
is_op
(
OP_POW
)
exponent
=
node
[
1
]
if
exponent
==
0
:
return
[
P
(
node
,
remove_power_of_zero
,
())]
if
exponent
==
1
:
return
[
P
(
node
,
remove_power_of_one
,
())]
return
[]
def
remove_power_of_zero
(
root
,
args
):
"""
a ^ 0 -> 1
"""
return
L
(
1
)
MESSAGES
[
remove_power_of_zero
]
=
_
(
'Power of zero {0} rewrites to 1.'
)
def
remove_power_of_one
(
root
,
args
):
"""
a ^ 1 -> a
"""
return
root
[
0
]
MESSAGES
[
remove_power_of_one
]
=
_
(
'Remove the power of one in {0}.'
)
tests/rulestestcase.py
View file @
41047c7e
...
...
@@ -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
tests/test_b1_ch08.py
View file @
41047c7e
...
...
@@ -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'
),
])
tests/test_b1_ch10.py
View file @
41047c7e
...
...
@@ -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
))
...
...
tests/test_calc.py
View file @
41047c7e
...
...
@@ -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
))]
...
...
tests/test_exception.py
View file @
41047c7e
...
...
@@ -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'
])
tests/test_leiden_oefenopgave.py
View file @
41047c7e
...
...
@@ -30,6 +30,8 @@ class TestLeidenOefenopgave(TestCase):
'(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)'
,
'(x ^ 2 + (1 + 1)x + 1 * 1)(x + 1)'
,
'(x ^ 2 + 2x + 1 * 1)(x + 1)'
,
'(x ^ 2 + 2x + 1)(x + 1)'
,
...
...
@@ -40,9 +42,11 @@ class TestLeidenOefenopgave(TestCase):
'x ^ 3 + x ^ (1 + 1) * 2 + (x ^ 2 + 2x) * 1 + 1x + 1 * 1'
,
'x ^ 3 + x ^ 2 * 2 + (x ^ 2 + 2x) * 1 + 1x + 1 * 1'
,
'x ^ 3 + x ^ 2 * 2 + 1 * x ^ 2 + 1 * 2x + 1x + 1 * 1'
,
'x ^ 3 + x ^ 2 * 2 + x ^ 2 + 1 * 2x + 1x + 1 * 1'
,
'x ^ 3 + (2 + 1) * x ^ 2 + 1 * 2x + 1x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + 1 * 2x + 1x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + 2x + 1x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + 2x + x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + (2 + 1)x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + 3x + 1 * 1'
,
'x ^ 3 + 3 * x ^ 2 + 3x + 1'
,
...
...
@@ -56,6 +60,8 @@ class TestLeidenOefenopgave(TestCase):
'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'
,
'x ^ 2 + (1 + 1)x + 1 * 1'
,
'x ^ 2 + 2x + 1 * 1'
,
'x ^ 2 + 2x + 1'
],
...
...
@@ -68,23 +74,44 @@ class TestLeidenOefenopgave(TestCase):
'xx + x * -1 - 1x - 1 * -1'
,
'x ^ (1 + 1) + x * -1 - 1x - 1 * -1'
,
'x ^ 2 + x * -1 - 1x - 1 * -1'
,
# FIXME: 'x ^ 2 + (-1 - 1)x - 1 * -1',
# FIXME: 'x ^ 2 - 2x - 1 * -1',
# FIXME: 'x ^ 2 - 2x - -1',
# FIXME: 'x ^ 2 - 2x + 1',
'x ^ 2 - x * 1 - 1x - 1 * -1'
,
'x ^ 2 - x - 1x - 1 * -1'
,
'x ^ 2 - x - x - 1 * -1'
,
'x ^ 2 + (1 + 1) * -x - 1 * -1'
,
'x ^ 2 + 2 * -x - 1 * -1'
,
'x ^ 2 - 2x - 1 * -1'
,
'x ^ 2 - 2x - -1'
,
'x ^ 2 - 2x + 1'
,
]]:
self
.
assertRewrite
(
chain
)
def
test_1_4_1
(
self
):
self
.
assertRewrite
([
'x * -1 + 1x'
,
'(-1 + 1)x'
,
'0x'
,])
# FIXME: '0'])
self
.
assertRewrite
([
'x * -1 + 1x'
,
'-x * 1 + 1x'
,
'-x + 1x'
,
'-x + x'
,
'(-1 + 1)x'
,
'0x'
,
'0'
])
def
test_1_4_2
(
self
):
# FIXME: self.assertRewrite(['x * -1 - 1x', '(-1 + -1)x', '-2x'])
pass
self
.
assertRewrite
([
'x * -1 - 1x'
,
'-x * 1 - 1x'
,
'-x - 1x'
,
'-x - x'
,
'(1 + 1) * -x'
,
'2 * -x'
,
'-2x'
])
def
test_1_4_3
(
self
):
# FIXME: self.assertRewrite(['x * -1 + x * -1', '(-1 + -1)x', '-2x'])
pass
self
.
assertRewrite
([
'x * -1 + x * -1'
,
'-x * 1 + x * -1'
,
'-x + x * -1'
,
'-x - x * 1'
,
'-x - x'
,
'(1 + 1) * -x'
,
'2 * -x'
,
'-2x'
])
def
test_1_5
(
self
):
self
.
assertRewrite
([
'(2x + x)x'
,
'(2 + 1)xx'
,
'3xx'
,
...
...
@@ -113,18 +140,25 @@ class TestLeidenOefenopgave(TestCase):
def
test_3
(
self
):
pass
def
test_4
(
self
):
for
exp
,
solution
in
[
(
'2/15 + 1/4'
,
'8 / 60 + 15 / 60'
),
(
'8/60 + 15/60'
,
'(8 + 15) / 60'
),
(
'(8 + 15) / 60'
,
'23 / 60'
),
(
'2/7 - 4/11'
,
'22 / 77 - 28 / 77'
),
(
'22/77 - 28/77'
,
'(22 - 28) / 77'
),
(
'(22 - 28)/77'
,
'-6 / 77'
),
# FIXME: ('(7/3) * (3/5)', '7 / 5'),
# FIXME: ('(3/4) / (5/6)', '9 / 10'),
# FIXME: ('1/4 * 1/x', '1 / (4x)'),
# FIXME: ('(3/x^2) / (x/7)', '21 / x^3'),
# FIXME: ('1/x + 2/(x+1)', '(3x + 1) / (x * (x + 1))'),
]:
self
.
assertEqual
(
str
(
rewrite
(
exp
)),
solution
)
def
test_4_1
(
self
):
self
.
assertRewrite
([
'2/15 + 1/4'
,
'8 / 60 + 15 / 60'
,
'(8 + 15) / 60'
,
'23 / 60'
])
def
test_4_2
(
self
):
self
.
assertRewrite
([
'2/7 - 4/11'
,
'22 / 77 - 28 / 77'
,
'(22 - 28) / 77'
,
'-6 / 77'
])
#def test_4_3(self):
# self.assertRewrite(['(7/3) * (3/5)', '7 / 5'])
#def test_4_4(self):
# self.assertRewrite(['(3/4) / (5/6)', '9 / 10'])
#def test_4_5(self):
# self.assertRewrite(['1/4 * 1/x', '1 / (4x)'])
#def test_4_6(self):
# self.assertRewrite(['(3/x^2) / (x/7)', '21 / x^3'])
#def test_4_7(self):
# self.assertRewrite(['1/x + 2/(x+1)', '(3x + 1) / (x * (x + 1))'])
tests/test_leiden_oefenopgave_v12.py
0 → 100644
View file @
41047c7e
from
tests.rulestestcase
import
RulesTestCase
as
TestCase
,
rewrite
class
TestLeidenOefenopgaveV12
(
TestCase
):
def
test_1_e
(
self
):
self
.
assertRewrite
([
'-2(6x - 4) ^ 2 * x'
,
'-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'
,
'(-72 * x ^ (1 + 1) - 12x * -4 + 8 * 6x + 8 * -4)x'
,
'(-72 * x ^ 2 - 12x * -4 + 8 * 6x + 8 * -4)x'
,
'(-72 * x ^ 2 - -48x + 8 * 6x + 8 * -4)x'
,
'(-72 * x ^ 2 + 48x + 8 * 6x + 8 * -4)x'
,
'(-72 * x ^ 2 + 48x + 48x + 8 * -4)x'
,
'(-72 * x ^ 2 + (1 + 1) * 48x + 8 * -4)x'
,
'(-72 * x ^ 2 + 2 * 48x + 8 * -4)x'
,
'(-72 * x ^ 2 + 96x + 8 * -4)x'
,
'(-72 * x ^ 2 + 96x - 32)x'
,
'x(-72 * x ^ 2 + 96x) + x * -32'
,
'x * -72 * x ^ 2 + x * 96x + x * -32'
,
'-x * 72 * x ^ 2 + x * 96x + x * -32'
,
'-x * 72 * x ^ 2 + x ^ (1 + 1) * 96 + x * -32'
,
'-x * 72 * x ^ 2 + x ^ 2 * 96 + x * -32'
,
'-x * 72 * x ^ 2 + x ^ 2 * 96 - x * 32'
])
# FIXME: '-x ^ (1 + 2) * 72 + x ^ 2 * 96 - x * 32',
# FIXME: '-x ^ 3 * 72 + x ^ 2 * 96 - x * 32',
# FIXME: '-72x ^ 3 + x ^ 2 * 96 - x * 32',
# FIXME: '-72x ^ 3 + 96x ^ 2 - x * 32',
# FIXME: '-72x ^ 3 + 96x ^ 2 - 32x'])
tests/test_node.py
View file @
41047c7e
...
...
@@ -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
)
tests/test_parser.py
View file @
41047c7e
...
...
@@ -23,3 +23,15 @@ class TestParser(unittest.TestCase):
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
)
tests/test_possibilities.py
View file @
41047c7e
...
...
@@ -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'
)
...
...
tests/test_rules_goniometry.py
0 → 100644
View file @
41047c7e
from
src.rules.goniometry
import
match_add_quadrants
,
add_quadrants
from
src.possibilities
import
Possibility
as
P
from
tests.rulestestcase
import
RulesTestCase
,
tree
class
TestRulesGoniometry
(
RulesTestCase
):
def
test_match_add_quadrants
(
self
):
return
root
=
tree
(
'sin(x) ^ 2 + cos(x) ^ 2'
)
possibilities
=
match_add_quadrants
(
root
)
self
.
assertEqualPos
(
possibilities
,
[
P
(
root
,
add_quadrants
,
())])
def
test_add_quadrants
(
self
):
return
root
=
tree
(
'sin(x) ^ 2 + cos(x) ^ 2'
)
self
.
assertEqual
(
add_quadrants
(
root
,
()),
1
)
tests/test_rules_groups.py
View file @
41047c7e
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
)
tests/test_rules_negation.py
View file @
41047c7e
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'
)),
[])
...
...
@@ -14,31 +58,31 @@ class TestRulesNegation(RulesTestCase):
l1
,
l2
=
root
=
tree
(
'-1 / 2'
)
possibilities
=
match_negated_division
(
root
)
self
.
assertEqualPos
(
possibilities
,
[
P
(
root
,
single_negated_division
,
(
l1
[
0
]
,
l2
))])
[
P
(
root
,
single_negated_division
,
(
+
l1
,
l2
))])
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
))
self
.
assertEqualNodes
(
single_negated_division
(
root
,
(
+
l1
,
l2
)),
-
(
+
l1
/
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
)
tests/test_rules_numerics.py
View file @
41047c7e
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
,
match_multiply_numerics
,
\
multiply_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'
)
...
...
@@ -71,45 +85,49 @@ class TestRulesNumerics(RulesTestCase):
root
=
i3
*
i2
self
.
assertEqual
(
match_multiply_numerics
(
root
),
[
P
(
root
,
multiply_numerics
,
(
i3
,
i2
,
3
,
2
))])
[
P
(
root
,
multiply_numerics
,
(
Scope
(
root
),
i3
,
i
2
))])
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
,
i
2
))])
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
)
tests/test_rules_poly.py
deleted
100644 → 0
View file @
d419cb36
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
)
tests/test_rules_powers.py
View file @
41047c7e
...
...
@@ -3,9 +3,10 @@ from src.rules.powers import match_add_exponents, add_exponents, \
match_multiply_exponents
,
multiply_exponents
,
\
match_duplicate_exponent
,
duplicate_exponent
,
\
match_remove_negative_exponent
,
remove_negative_exponent
,
\
match_exponent_to_root
,
exponent_to_root
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 +18,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 +26,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 +36,16 @@ 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_subtract_exponents_powers
(
self
):
a
,
p
,
q
=
tree
(
'a,p,q'
)
...
...
@@ -84,7 +93,7 @@ class TestRulesPowers(RulesTestCase):
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 +112,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 +141,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 +156,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
])
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment