Added support for fractions with negation and addition of negative numbers.

parent d5c29c2b
...@@ -189,6 +189,9 @@ class ExpressionNode(Node, ExpressionBase): ...@@ -189,6 +189,9 @@ class ExpressionNode(Node, ExpressionBase):
>>> n2 = N('*', N('^', r, e), c) >>> n2 = N('*', N('^', r, e), c)
>>> n2.extract_polynome() >>> n2.extract_polynome()
(c, r, e) (c, r, e)
>>> n3 = N('-', r)
>>> n3.extract_polynome()
(1, -r, 1)
""" """
# TODO: change "get_polynome" -> "extract_polynome". # TODO: change "get_polynome" -> "extract_polynome".
# TODO: change retval of c * r ^ e to (c, r, e). # TODO: change retval of c * r ^ e to (c, r, e).
...@@ -198,6 +201,10 @@ class ExpressionNode(Node, ExpressionBase): ...@@ -198,6 +201,10 @@ class ExpressionNode(Node, ExpressionBase):
if self.is_power(): if self.is_power():
return (ExpressionLeaf(1), self[0], self[1]) return (ExpressionLeaf(1), self[0], self[1])
# rule: -r -> (1, r, 1)
if self.is_op(OP_NEG):
return (ExpressionLeaf(1), -self[0], ExpressionLeaf(1))
if self.op != OP_MUL: if self.op != OP_MUL:
return return
......
from itertools import combinations from itertools import combinations
from .utils import nary_node, least_common_multiple from .utils import nary_node, least_common_multiple
from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL, OP_NEG
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -63,15 +63,23 @@ def match_add_constant_fractions(node): ...@@ -63,15 +63,23 @@ def match_add_constant_fractions(node):
1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4 # Equalize denominators 1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4 # Equalize denominators
2 / 4 + 3 / 4 -> 5 / 4 # Equal denominators, so nominators can 2 / 4 + 3 / 4 -> 5 / 4 # Equal denominators, so nominators can
# be added # be added
2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
2 / 4 - 3 / 4 -> -1 / 4 # Equal denominators, so nominators can
# be subtracted
""" """
assert node.is_op(OP_ADD) assert node.is_op(OP_ADD)
p = [] p = []
fractions = filter(lambda n: n.is_op(OP_DIV), node.get_scope())
def is_division(node):
return node.is_op(OP_DIV) or \
(node.is_op(OP_NEG) and node[0].is_op(OP_DIV))
fractions = filter(is_division, node.get_scope())
for a, b in combinations(fractions, 2): for a, b in combinations(fractions, 2):
na, da = a na, da = a if a.is_op(OP_DIV) else a[0]
nb, db = b nb, db = b if b.is_op(OP_DIV) else b[0]
if da == db: if da == db:
# Equal denominators, add nominators to create a single fraction # Equal denominators, add nominators to create a single fraction
...@@ -96,11 +104,15 @@ def equalize_denominators(root, args): ...@@ -96,11 +104,15 @@ def equalize_denominators(root, args):
scope = root.get_scope() scope = root.get_scope()
for fraction in args[:2]: for fraction in args[:2]:
n, d = fraction n, d = fraction[0] if fraction.is_op(OP_NEG) else fraction
mult = denom / d.value mult = denom / d.value
if mult != 1: if mult != 1:
n = L(n.value * mult) if n.is_numeric() else L(mult) * n n = L(n.value * mult) if n.is_numeric() else L(mult) * n
if fraction.is_op(OP_NEG):
scope[scope.index(fraction)] = -(n / L(d.value * mult))
else:
scope[scope.index(fraction)] = n / L(d.value * mult) scope[scope.index(fraction)] = n / L(d.value * mult)
return nary_node('+', scope) return nary_node('+', scope)
...@@ -109,15 +121,23 @@ def equalize_denominators(root, args): ...@@ -109,15 +121,23 @@ def equalize_denominators(root, args):
def add_nominators(root, args): def add_nominators(root, args):
""" """
a / b + c / b -> (a + c) / b a / b + c / b -> (a + c) / b
a / b + (-c / b) -> (a + (-c)) / b
""" """
# TODO: is 'add' Appropriate when rewriting to "(a + (-c)) / b"?
ab, cb = args ab, cb = args
a, b = ab a, b = ab
if cb[0].is_op(OP_NEG):
c = cb[0][0]
substitution = (a + (-c)) / b
else:
c = cb[0] c = cb[0]
substitution = (a + c) / b
scope = root.get_scope() scope = root.get_scope()
# Replace the left node with the new expression # Replace the left node with the new expression
scope[scope.index(ab)] = (a + c) / b scope[scope.index(ab)] = substitution
# Remove the right node # Remove the right node
scope.remove(cb) scope.remove(cb)
...@@ -128,7 +148,9 @@ def add_nominators(root, args): ...@@ -128,7 +148,9 @@ def add_nominators(root, args):
def match_expand_and_add_fractions(node): def match_expand_and_add_fractions(node):
""" """
a * b / c + d * b / c -> (a + d) * (b / c) a * b / c + d * b / c -> (a + d) * (b / c)
a * b / c + (- d * b / c) -> (a + (-d)) * (b / c)
""" """
# TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
assert node.is_op(OP_MUL) assert node.is_op(OP_MUL)
p = [] p = []
......
...@@ -18,6 +18,7 @@ def match_combine_groups(node): ...@@ -18,6 +18,7 @@ def match_combine_groups(node):
ab + 2ab -> 3ab ab + 2ab -> 3ab
ab + ba -> 2ab ab + ba -> 2ab
""" """
# TODO: handle OP_NEG nodes
assert node.is_op(OP_ADD) assert node.is_op(OP_ADD)
p = [] p = []
......
from itertools import combinations from itertools import combinations
from .utils import nary_node from .utils import nary_node
from ..node import ExpressionLeaf as Leaf, OP_DIV, OP_MUL from ..node import ExpressionLeaf as Leaf, OP_DIV, OP_MUL, OP_NEG
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -12,9 +12,15 @@ def add_numerics(root, args): ...@@ -12,9 +12,15 @@ def add_numerics(root, args):
Example: Example:
2 + 3 -> 5 2 + 3 -> 5
2 + -3 -> -1
-2 + 3 -> 1
-2 + -3 -> -5
""" """
n0, n1, c0, c1 = args n0, n1, c0, c1 = args
c0 = (-c0[0].value) if c0.is_op(OP_NEG) else c0.value
c1 = (-c1[0].value) if c1.is_op(OP_NEG) else c1.value
scope = root.get_scope() scope = root.get_scope()
# Replace the left node with the new expression # Replace the left node with the new expression
......
from itertools import combinations from itertools import combinations
from ..node import OP_ADD from ..node import OP_ADD, OP_NEG
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
from .utils import nary_node from .utils import nary_node
from .numerics import add_numerics from .numerics import add_numerics
def is_numeric_or_negated_numeric(n):
return n.is_numeric() or (n.is_op(OP_NEG) and n[0].is_numeric())
def match_combine_polynomes(node, verbose=False): def match_combine_polynomes(node, verbose=False):
""" """
n + exp + m -> exp + (n + m) n + exp + m -> exp + (n + m)
...@@ -49,9 +53,12 @@ def match_combine_polynomes(node, verbose=False): ...@@ -49,9 +53,12 @@ def match_combine_polynomes(node, verbose=False):
# roots, or: same root and exponent -> combine coefficients. # roots, or: same root and exponent -> combine coefficients.
# TODO: Addition with zero, e.g. a + 0 -> a # TODO: Addition with zero, e.g. a + 0 -> a
if c0 == 1 and c1 == 1 and e0 == 1 and e1 == 1 \ if c0 == 1 and c1 == 1 and e0 == 1 and e1 == 1 \
and r0.is_numeric() and r1.is_numeric(): and all(map(is_numeric_or_negated_numeric, [r0, r1])):
# 2 + 3 -> 5 # 2 + 3 -> 5
p.append(P(node, add_numerics, (n0, n1, r0.value, r1.value))) # 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: elif c0.is_numeric() and c1.is_numeric() and r0 == r1 and e0 == e1:
# 2a + 2a -> 4a # 2a + 2a -> 4a
# a + 2a -> 3a # a + 2a -> 3a
......
...@@ -120,6 +120,8 @@ def apply_expressions(base_class, expressions, fail=True, silent=False, ...@@ -120,6 +120,8 @@ def apply_expressions(base_class, expressions, fail=True, silent=False,
if fail: if fail:
raise raise
def graph(parser, *exp, **kwargs): def graph(parser, *exp, **kwargs):
return generate_graph(ParserWrapper(parser, **kwargs).run(exp)) return generate_graph(ParserWrapper(parser, **kwargs).run(exp))
......
...@@ -32,7 +32,9 @@ class TestLeidenOefenopgave(TestCase): ...@@ -32,7 +32,9 @@ class TestLeidenOefenopgave(TestCase):
('2/15 + 1/4', '8 / 60 + 15 / 60'), ('2/15 + 1/4', '8 / 60 + 15 / 60'),
('8/60 + 15/60', '(8 + 15) / 60'), ('8/60 + 15/60', '(8 + 15) / 60'),
('(8 + 15) / 60', '23 / 60'), ('(8 + 15) / 60', '23 / 60'),
# FIXME: ('2/7 - 4/11', '-6 / 77'), ('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: ('(7/3) * (3/5)', '7 / 5'),
# FIXME: ('(3/4) / (5/6)', '9 / 10'), # FIXME: ('(3/4) / (5/6)', '9 / 10'),
# FIXME: ('1/4 * 1/x', '1 / (4x)'), # FIXME: ('1/4 * 1/x', '1 / (4x)'),
......
...@@ -24,3 +24,7 @@ class TestRewrite(TestCase): ...@@ -24,3 +24,7 @@ class TestRewrite(TestCase):
def test_addition_identifiers_rewrite(self): def test_addition_identifiers_rewrite(self):
self.assertRewrite(['2 + 3a + 4', '6 + 3a']) self.assertRewrite(['2 + 3a + 4', '6 + 3a'])
def test_division_rewrite(self):
self.assertRewrite(['2/7 - 4/11', '22 / 77 + -28 / 77',
'(22 + -28) / 77', '-6 / 77'])
...@@ -68,6 +68,19 @@ class TestRulesFractions(RulesTestCase): ...@@ -68,6 +68,19 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualPos(possibilities, self.assertEqualPos(possibilities,
[P(root, add_nominators, (n1, n3))]) [P(root, add_nominators, (n1, n3))])
def test_add_constant_fractions_with_negation(self):
a, b, c, l1, l2, l3, l4 = tree('a,b,c,1,2,3,4')
(((n0, n1), n2), n3), n4 = root = a + l2 / l2 + b + (-l3 / l4) + c
possibilities = match_add_constant_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (n1, n3, 4))])
(((n0, n1), n2), n3), n4 = root = a + l2 / l4 + b + (-l3 / l4) + c
possibilities = match_add_constant_fractions(root)
self.assertEqualPos(possibilities,
[P(root, add_nominators, (n1, n3))])
def test_equalize_denominators(self): def test_equalize_denominators(self):
a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4') a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
...@@ -79,8 +92,22 @@ class TestRulesFractions(RulesTestCase): ...@@ -79,8 +92,22 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)), self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
(l2 * a) / l4 + b / l4) (l2 * a) / l4 + b / l4)
#2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
n0, n1 = root = l1 / l2 + (-l3 / l4)
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
l2 / l4 + (-l3 / l4))
#2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
n0, n1 = root = a / l2 + (-b / l4)
self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
(l2 * a) / l4 + (-b / l4))
def test_add_nominators(self): def test_add_nominators(self):
a, b, c = tree('a,b,c') a, b, c = tree('a,b,c')
n0, n1 = root = a / b + c / b n0, n1 = root = a / b + c / b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b) self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b)
#2 / 4 + 3 / -4 -> 2 / 4 + -3 / 4
#2 / 4 - 3 / 4 -> -1 / 4 # Equal denominators, so nominators can
n0, n1 = root = a / b + (-c / b)
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + (-c)) / b)
...@@ -10,8 +10,16 @@ class TestRulesNumerics(RulesTestCase): ...@@ -10,8 +10,16 @@ class TestRulesNumerics(RulesTestCase):
def test_add_numerics(self): def test_add_numerics(self):
l0, a, l1 = tree('1,a,2') l0, a, l1 = tree('1,a,2')
self.assertEqual(add_numerics(l0 + l1, (l0, l1, 1, 2)), 3) self.assertEqual(add_numerics(l0 + l1, (l0, l1, L(1), L(2))), 3)
self.assertEqual(add_numerics(l0 + a + l1, (l0, l1, 1, 2)), L(3) + a) self.assertEqual(add_numerics(l0 + a + l1, (l0, l1, L(1), L(2))),
L(3) + a)
def test_add_numerics_negations(self):
l0, a, l1 = tree('1,a,2')
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)
def test_match_divide_numerics(self): def test_match_divide_numerics(self):
a, b, i2, i3, i6, f1, f2, f3 = tree('a,b,2,3,6,1.0,2.0,3.0') a, b, i2, i3, i6, f1, f2, f3 = tree('a,b,2,3,6,1.0,2.0,3.0')
......
...@@ -36,14 +36,12 @@ class TestRulesPoly(RulesTestCase): ...@@ -36,14 +36,12 @@ class TestRulesPoly(RulesTestCase):
self.assertEqualPos(possibilities, self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 2, 1, 'a', 3))]) [P(root, combine_polynomes, (a1, a2, 2, 1, 'a', 3))])
def test_identifiers_coeff_exponent_both(self): def test_identifiers_coeff_exponent_both(self):
a1, a2 = root = tree('2a3+2a3') a1, a2 = root = tree('2a3+2a3')
possibilities = match_combine_polynomes(root) possibilities = match_combine_polynomes(root)
self.assertEqualPos(possibilities, self.assertEqualPos(possibilities,
[P(root, combine_polynomes, (a1, a2, 2, 2, 'a', 3))]) [P(root, combine_polynomes, (a1, a2, 2, 2, 'a', 3))])
def test_basic_subexpressions(self): def test_basic_subexpressions(self):
a_b, c, d = tree('a+b,c,d') a_b, c, d = tree('a+b,c,d')
left, right = root = tree('(a+b)^d + (a+b)^d') left, right = root = tree('(a+b)^d + (a+b)^d')
......
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