Commit 5ac16510 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Added rule 'a/b + c/d -> ad/(bd) + bc/(bd) -> (ad + bc)/(bd)' and some helper rules.

parent 6c30a8e6
......@@ -26,9 +26,6 @@
- "sin^2 x" is supported by parser, but not yet by line printer.
- 1/x + 2/(x+1) -> (3x + 1) / (x(x + 1)):
a/b + c/d -> ad/(bd) + bc/(bd) -> (ad + bc)/(bd)
#- To work 100% correctly, POW and SUB (^ and _) should have a precedence equal
# to INTEGRAL when used for integral bounds.
......
......@@ -659,7 +659,7 @@ def negate(node, n=1, clone=False):
Negate the given node n times. If clone is set to true, return a new node
so that the original node is not altered.
"""
assert n >= 0
#assert n >= 0
if clone:
node = node.clone()
......
......@@ -25,7 +25,8 @@ from .numerics import match_add_numerics, match_divide_numerics, \
match_multiply_numerics, match_raise_numerics
from .fractions import match_constant_division, match_add_fractions, \
match_multiply_fractions, match_divide_fractions, \
match_extract_fraction_terms, match_division_in_denominator
match_extract_fraction_terms, match_division_in_denominator, \
match_combine_fractions, match_remove_division_negation
from .negation import match_negated_factor, match_negate_polynome, \
match_negated_division
from .sort import match_sort_polynome, match_sort_monomial
......@@ -50,14 +51,16 @@ from .sqrt import match_reduce_sqrt
RULES = {
OP_ADD: [match_add_numerics, match_add_fractions,
match_combine_groups, match_add_quadrants,
match_add_logarithms, match_sort_polynome],
match_add_logarithms, match_sort_polynome,
match_combine_fractions],
OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
match_negated_factor, match_multiply_fractions,
match_factor_in_multiplicant, match_sort_monomial],
OP_DIV: [match_subtract_exponents, match_divide_numerics,
match_constant_division, match_divide_fractions,
match_negated_division, match_extract_fraction_terms,
match_division_in_denominator],
match_division_in_denominator,
match_remove_division_negation],
OP_POW: [match_multiply_exponents, match_duplicate_exponent,
match_raised_fraction, match_remove_negative_child,
match_exponent_to_root, match_extend_exponent,
......
......@@ -20,6 +20,7 @@ from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DIV, \
OP_ADD, OP_MUL, negate
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
from .negation import negate_polynome
def match_constant_division(node):
......@@ -300,7 +301,7 @@ def divide_by_fraction(root, args):
MESSAGES[divide_by_fraction] = \
_('Move {3} to nominator of fraction `{1} / {2}`.')
_('Move {3} to the nominator of fraction `{1} / {2}`.')
def is_power_combination(a, b):
......@@ -464,3 +465,72 @@ def multiply_with_term(root, args):
MESSAGES[multiply_with_term] = \
_('Multiply nominator and denominator of {0} with {1}.')
def match_combine_fractions(node):
"""
a/b + c/d -> ad/(bd) + bc/(bd) # -> (ad + bc)/(bd)
"""
assert node.is_op(OP_ADD)
scope = Scope(node)
fractions = [n for n in scope if n.is_op(OP_DIV)]
p = []
for left, right in combinations(fractions, 2):
p.append(P(node, combine_fractions, (scope, left, right)))
return p
def combine_fractions(root, args):
"""
a/b + c/d -> ad/(bd) + bc/(bd)
"""
scope, ab, cd = args
(a, b), (c, d) = ab, cd
a = negate(a, ab.negated)
d = negate(d, cd.negated)
scope.replace(ab, a * d / (b * d) + b * c / (b * d))
scope.remove(cd)
return scope.as_nary_node()
MESSAGES[combine_fractions] = _('Combine fraction {2} and {3}.')
def match_remove_division_negation(node):
"""
-a / (-b + c) -> a / (--b - c)
"""
assert node.is_op(OP_DIV)
nom, denom = node
if node.negated:
if nom.is_op(OP_ADD) and any([n.negated for n in Scope(nom)]):
return [P(node, remove_division_negation, (True, nom))]
if denom.is_op(OP_ADD) and any([n.negated for n in Scope(denom)]):
return [P(node, remove_division_negation, (False, denom))]
return []
def remove_division_negation(root, args):
"""
-a / (-b + c) -> a / (--b - c)
"""
nom, denom = root
if args[0]:
nom = negate_polynome(nom, ())
else:
denom = negate_polynome(denom, ())
return negate(nom / denom, root.negated - 1)
MESSAGES[remove_division_negation] = \
_('Move negation from fraction {0} to polynome {2}.')
......@@ -19,7 +19,8 @@ from src.rules.fractions import match_constant_division, division_by_one, \
divide_fraction, divide_by_fraction, match_extract_fraction_terms, \
constant_to_fraction, extract_nominator_term, extract_fraction_terms, \
match_division_in_denominator, multiply_with_term, \
divide_fraction_by_term
divide_fraction_by_term, match_combine_fractions, combine_fractions, \
match_remove_division_negation, remove_division_negation
from src.node import ExpressionNode as N, Scope, OP_MUL
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -318,19 +319,43 @@ class TestRulesFractions(RulesTestCase):
def test_multiply_with_term_chain(self):
self.assertRewrite([
'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 * 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 - a b / a)',
'(ab) / (a - (ab) / a)',
'1 / ((1 * -a) / (b * -a) + (b * 1) / (b * -a))',
'1 / ((1 * -a + b * 1) / (b * -a))',
'1 / ((-a + b * 1) / (b * -a))',
'1 / ((-a + b) / (b * -a))',
'1 / ((-a + b) / (-ba))',
'1 / (-(-a + b) / (ba))',
'1 / ((--a - b) / (ba))',
'1 / ((a - b) / (ba))',
'(1ba) / (a - b)',
'(ba) / (a - b)',
'(ab) / (a - b)',
])
def test_match_combine_fractions(self):
ab, cd = root = tree('a / b + c / d')
self.assertEqualPos(match_combine_fractions(root),
[P(root, combine_fractions, (Scope(root), ab, cd))])
def test_combine_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b + c / d')
self.assertEqual(combine_fractions(root, (Scope(root), ab, cd)),
a * d / (b * d) + b * c / (b * d))
def test_match_remove_division_negation(self):
root = tree('-(-a + b) / c')
self.assertEqualPos(match_remove_division_negation(root),
[P(root, remove_division_negation, (True, root[0]))])
root = tree('-a / (-b + c)')
self.assertEqualPos(match_remove_division_negation(root),
[P(root, remove_division_negation, (False, root[1]))])
def test_remove_division_negation(self):
(a, b), c = root = tree('-(-a + b) / c')
self.assertEqual(remove_division_negation(root, (True, root[0])),
(-a - b) / c)
a, (b, c) = root = tree('-a / (-b + c)')
self.assertEqual(remove_division_negation(root, (False, root[1])),
+a / (-b - c))
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