Commit c9d72b47 authored by Taddeus Kroes's avatar Taddeus Kroes

Fine-tuned fraction rules and updated unit tests accordingly.

parent 08555af7
from itertools import combinations, product
import copy
from .utils import least_common_multiple, partition, is_numeric_node, \
evals_to_numeric
......@@ -288,28 +289,6 @@ MESSAGES[divide_by_fraction] = \
_('Move {3} to nominator of fraction {1} / {2}.')
def fraction_scopes(node):
"""
Get the multiplication scopes of the nominator and denominator of a
fraction.
"""
assert node.is_op(OP_DIV)
nominator, denominator = node
if nominator.is_op(OP_MUL):
n_scope = Scope(nominator)
else:
n_scope = Scope(N(OP_MUL, nominator))
if denominator.is_op(OP_MUL):
d_scope = Scope(denominator)
else:
d_scope = Scope(N(OP_MUL, denominator))
return n_scope, d_scope
def is_power_combination(a, b):
"""
Check if two nodes are powers that can be combined in a fraction, for
......@@ -328,37 +307,73 @@ def is_power_combination(a, b):
return a == b
def mult_scope(node):
"""
Get the multiplication scope of a node that may or may no be a
multiplication itself.
"""
if node.is_op(OP_MUL):
return Scope(node)
return Scope(N(OP_MUL, node))
def remove_from_mult_scope(scope, node):
if len(scope) == 1:
scope.replace(node, L(1))
else:
scope.remove(node)
return scope.as_nary_node()
def match_extract_fraction_terms(node):
"""
Divide nominator and denominator by the same part.
Divide nominator and denominator by the same part. If the same root of a
power appears in both nominator and denominator, also extract it so that it
can be reduced to a single power by power division rules.
Examples:
a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e)
ab / (ac) -> a / a * (c / e) # =>* c / e
a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e) # -> a^(b - d)(c / e)
#If the same root appears in both nominator and denominator, extract it so
#that it can be reduced to a single power by power division rules.
#a ^ p * b / a ^ q -> a ^ p / a ^ q * b / 1
#a ^ p * b / a -> a ^ p / a * b / 1
#a * b / a ^ q -> a / a ^ q * b / 1
ac / b and eval(c) not in Z and eval(a / b) in Z -> a / b * c
"""
# TODO: ac / b -> a / b * c
assert node.is_op(OP_DIV)
nominator, denominator = node
n_scope, d_scope = fraction_scopes(node)
n_scope, d_scope = map(mult_scope, node)
p = []
if len(n_scope) == 1 and len(d_scope) == 1:
return p
# Look for matching parts in scopes
for n, d in product(n_scope, d_scope):
if is_power_combination(n, d):
nominator, denominator = node
for n in n_scope:
# ac / b
if not evals_to_numeric(n):
a_scope = mult_scope(nominator)
a = remove_from_mult_scope(a_scope, n)
if evals_to_numeric(a / denominator):
p.append(P(node, extract_nominator_term, (a, n)))
# a ^ b * c / (a ^ d * e)
for d in [d for d in d_scope if is_power_combination(n, d)]:
p.append(P(node, extract_fraction_terms, (n_scope, d_scope, n, d)))
return p
def extract_nominator_term(root, args):
"""
ac / b and eval(c) not in Z and eval(a / b) in Z -> a / b * c
"""
a, c = args
return a / root[1] * c
def extract_fraction_terms(root, args):
"""
ab / a -> a / a * (b / 1)
......@@ -368,17 +383,8 @@ def extract_fraction_terms(root, args):
"""
n_scope, d_scope, n, d = args
if len(n_scope) == 1:
n_scope.replace(n, L(1))
else:
n_scope.remove(n)
if len(d_scope) == 1:
d_scope.replace(d, L(1))
else:
d_scope.remove(d)
return n / d * (n_scope.as_nary_node() / d_scope.as_nary_node())
return n / d * (remove_from_mult_scope(n_scope, n) \
/ remove_from_mult_scope(d_scope, d))
MESSAGES[extract_fraction_terms] = _('Extract {3} / {4} from fraction {0}.')
......@@ -63,22 +63,22 @@ class TestLeidenOefenopgaveV12(TestCase):
'(a2b^-1)^3(ab2)',
'(a ^ 2 * (1 / b ^ 1)) ^ 3 * ab ^ 2',
'(a ^ 2 * (1 / b)) ^ 3 * ab ^ 2',
'(a ^ 2 * 1 / b) ^ 3 * ab ^ 2',
'(1a ^ 2 / b) ^ 3 * ab ^ 2',
'(a ^ 2 / b) ^ 3 * ab ^ 2',
'(a ^ 2) ^ 3 / b ^ 3 * ab ^ 2',
'a ^ (2 * 3) / b ^ 3 * ab ^ 2',
'a ^ 6 / b ^ 3 * ab ^ 2',
'aa ^ 6 / b ^ 3 * b ^ 2',
'a ^ (1 + 6) / b ^ 3 * b ^ 2',
'a ^ 6 * a / b ^ 3 * b ^ 2',
'a ^ (6 + 1) / b ^ 3 * b ^ 2',
'a ^ 7 / b ^ 3 * b ^ 2',
'b ^ 2 * a ^ 7 / b ^ 3',
'b ^ 2 / b ^ 3 * a ^ 7 / 1',
'b ^ (2 - 3)a ^ 7 / 1',
'b ^ (-1)a ^ 7 / 1',
'1 / b ^ 1 * a ^ 7 / 1',
'1 / b * a ^ 7 / 1',
'a ^ 7 * 1 / b / 1',
'a ^ 7 / b / 1',
'a ^ 7 * b ^ 2 / b ^ 3',
'b ^ 2 / b ^ 3 * (a ^ 7 / 1)',
'b ^ (2 - 3)(a ^ 7 / 1)',
'b ^ (-1)(a ^ 7 / 1)',
'1 / b ^ 1 * (a ^ 7 / 1)',
'1 / b * (a ^ 7 / 1)',
'1 / b * a ^ 7',
'1a ^ 7 / b',
'a ^ 7 / b',
])
......@@ -106,7 +106,8 @@ class TestLeidenOefenopgaveV12(TestCase):
self.assertRewrite([
'4b^-2',
'4(1 / b ^ 2)',
'4 * 1 / b ^ 2',
'1 * 4 / b ^ 2',
'4 / b ^ 2',
])
def test_2_f(self):
......
......@@ -113,7 +113,7 @@ class TestRulesDerivatives(RulesTestCase):
"e ^ (xln(x))(ln(x) + x(1 / (xln(e))))",
"e ^ (xln(x))(ln(x) + x(1 / (x * 1)))",
"e ^ (xln(x))(ln(x) + x(1 / x))",
"e ^ (xln(x))(ln(x) + x * 1 / x)",
"e ^ (xln(x))(ln(x) + 1x / x)",
"e ^ (xln(x))(ln(x) + x / x)",
"e ^ (xln(x))(ln(x) + 1)",
"e ^ ln(x ^ x)(ln(x) + 1)",
......
......@@ -3,7 +3,7 @@ from src.rules.fractions import match_constant_division, division_by_one, \
equalize_denominators, add_nominators, match_multiply_fractions, \
multiply_fractions, multiply_with_fraction, match_divide_fractions, \
divide_fraction, divide_by_fraction, match_extract_fraction_terms, \
constant_to_fraction, extract_fraction_terms
constant_to_fraction, extract_nominator_term, extract_fraction_terms
from src.node import ExpressionNode as N, Scope, OP_MUL
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -234,6 +234,23 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), ap, a))])
(l2, a), l3 = n, d = root = tree('2a / 3')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_nominator_term, (2, a))])
root = tree('2*4 / 3')
self.assertEqualPos(match_extract_fraction_terms(root), [])
n, d = root = tree('2a / 2')
self.assertEqualPos(match_extract_fraction_terms(root),
[P(root, extract_fraction_terms, (Scope(n), lscp(d), 2, 2)),
P(root, extract_nominator_term, (2, a))])
def test_extract_nominator_term(self):
root, expect = tree('2a / 3, 2 / 3 * a')
l2, a = root[0]
self.assertEqual(extract_nominator_term(root, (l2, a)), expect)
def test_extract_fraction_terms_basic(self):
root, expect = tree('ab / (ca), a / a * (b / c)')
n, d = root
......
......@@ -75,7 +75,9 @@ class TestRulesLineq(RulesTestCase):
'5x = 0 - 5',
'5x = -5',
'5x / 5 = (-5) / 5',
'x / 1 = (-5) / 5',
'5 / 5 * (x / 1) = (-5) / 5',
'1(x / 1) = (-5) / 5',
'1x = (-5) / 5',
'x = (-5) / 5',
'x = -5 / 5',
'x = -1',
......
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