Skip to content
Snippets Groups Projects
Commit c03390ba authored by Taddeus Kroes's avatar Taddeus Kroes
Browse files

Implemented and tested some new rules for fractions.

parent 3a090e5f
No related branches found
No related tags found
No related merge requests found
......@@ -4,8 +4,8 @@ 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, match_constant_exponent
match_raised_fraction, match_remove_negative_exponent, \
match_exponent_to_root, match_extend_exponent, match_constant_exponent
from .numerics import match_add_numerics, match_divide_numerics, \
match_multiply_numerics, match_multiply_zero, match_multiply_one, \
match_raise_numerics
......@@ -27,9 +27,9 @@ RULES = {
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, match_constant_exponent,
match_raise_numerics],
match_raised_fraction, match_remove_negative_exponent,
match_exponent_to_root, match_extend_exponent,
match_constant_exponent, match_raise_numerics],
OP_NEG: [match_negate_polynome],
OP_SIN: [match_negated_parameter, match_half_pi_subtraction,
match_standard_radian],
......
from itertools import combinations
from itertools import combinations, product
from .utils import least_common_multiple
from .utils import least_common_multiple, partition
from ..node import ExpressionLeaf as L, Scope, negate, OP_DIV, OP_ADD, OP_MUL
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -178,17 +178,20 @@ def match_expand_and_add_fractions(node):
def match_multiply_fractions(node):
"""
a / b * (c / d) -> ac / (bd)
a * (b / c) -> ab / c
"""
# TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
assert node.is_op(OP_MUL)
p = []
scope = Scope(node)
fractions = filter(lambda n: n.is_op(OP_DIV), scope)
fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
for ab, cd in combinations(fractions, 2):
p.append(P(node, multiply_fractions, (scope, ab, cd)))
for a, bc in product(others, fractions):
p.append(P(node, multiply_with_fraction, (scope, a, bc)))
return p
......@@ -207,3 +210,19 @@ def multiply_fractions(root, args):
MESSAGES[multiply_fractions] = _('Multiply fractions {2} and {3}.')
def multiply_with_fraction(root, args):
"""
a * (b / c) -> ab / c
"""
scope, a, bc = args
b, c = bc
scope.replace(a, a * b / c)
scope.remove(bc)
return scope.as_nary_node()
MESSAGES[multiply_with_fraction] = _('Multiply {2} with fraction {3}.')
......@@ -138,10 +138,10 @@ def match_duplicate_exponent(node):
"""
assert node.is_op(OP_POW)
left, right = node
root, exponent = node
if left.is_op(OP_MUL):
return [P(node, duplicate_exponent, (list(Scope(left)), right))]
if root.is_op(OP_MUL):
return [P(node, duplicate_exponent, (list(Scope(root)), exponent))]
return []
......@@ -163,6 +163,33 @@ def duplicate_exponent(root, args):
MESSAGES[duplicate_exponent] = _('Duplicate the exponent {2}.')
def match_raised_fraction(node):
"""
(a / b) ^ p -> a^p / b^p
"""
assert node.is_op(OP_POW)
root, exponent = node
if root.is_op(OP_DIV):
return [P(node, raised_fraction, (root, exponent))]
return []
def raised_fraction(root, args):
"""
(a / b) ^ p -> a^p / b^p
"""
(a, b), p = args
return a ** p / b ** p
MESSAGES[raised_fraction] = _('Apply the exponent {2} to the nominator and'
' denominator of fraction {1}.')
def match_remove_negative_exponent(node):
"""
a ^ -p -> 1 / a ^ p
......
......@@ -54,3 +54,20 @@ def is_fraction(node, nominator, denominator):
or (right == nominator and left == fraction)
return False
def partition(callback, iterable):
"""
Partition an iterable into two parts using a callback that returns a
boolean.
Example:
>>> partition(lambda x: x & 1, range(6))
([1, 3, 5], [0, 2, 4])
"""
a, b = [], []
for item in iterable:
(a if callback(item) else b).append(item)
return a, b
......@@ -56,11 +56,16 @@ class TestLeidenOefenopgaveV12(TestCase):
'(a2b^-1)^3(ab2)',
'(a ^ 2 * (1 / b ^ 1)) ^ 3 * ab ^ 2',
'(a ^ 2 * (1 / b)) ^ 3 * ab ^ 2',
'(a ^ 2) ^ 3 * (1 / b) ^ 3 * ab ^ 2',
'a ^ (2 * 3)(1 / b) ^ 3 * ab ^ 2',
'a ^ 6 * (1 / b) ^ 3 * ab ^ 2',
'a ^ (6 + 1)(1 / b) ^ 3 * b ^ 2',
'a ^ 7 * (1 / b) ^ 3 * b ^ 2',
'(a ^ 2 * 1 / b) ^ 3 * ab ^ 2',
'(a ^ 2 / b) ^ 3 * ab ^ 2',
'(a ^ 2) ^ 3 / b ^ 3 * ab ^ 2',
'a ^ (2 * 3) / b ^ 3 * ab ^ 2',
'a ^ 6 / b ^ 3 * ab ^ 2',
'aa ^ 6 / b ^ 3 * b ^ 2',
'a ^ (1 + 6) / b ^ 3 * b ^ 2',
'a ^ 7 / b ^ 3 * b ^ 2',
# FIXME: 'b ^ 2 * a ^ 7 / b ^ 3',
# FIXME: 'b ^ (2 - 3) * a ^ 7',
])
def test_2_b(self):
......@@ -87,7 +92,7 @@ class TestLeidenOefenopgaveV12(TestCase):
self.assertRewrite([
'4b^-2',
'4(1 / b ^ 2)',
# FIXME: '4 * 1/b ^ 2',
'4 * 1 / b ^ 2',
])
def test_2_f(self):
......
from src.rules.fractions import match_constant_division, division_by_one, \
division_of_zero, division_by_self, match_add_constant_fractions, \
equalize_denominators, add_nominators, match_multiply_fractions, \
multiply_fractions
multiply_fractions, multiply_with_fraction
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -127,10 +127,15 @@ class TestRulesFractions(RulesTestCase):
def test_match_multiply_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b * (c / d)')
self.assertEqualPos(match_multiply_fractions(root),
[P(root, multiply_fractions, (Scope(root), ab, cd))])
(ab, e), cd = root = tree('a / b * e * (c / d)')
self.assertEqualPos(match_multiply_fractions(root),
[P(root, multiply_fractions, (Scope(root), ab, cd)),
P(root, multiply_with_fraction, (Scope(root), e, ab)),
P(root, multiply_with_fraction, (Scope(root), e, cd))])
def test_multiply_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b * (c / d)')
self.assertEqual(multiply_fractions(root, (Scope(root), ab, cd)),
......
......@@ -2,6 +2,7 @@ from src.rules.powers import match_add_exponents, add_exponents, \
match_subtract_exponents, subtract_exponents, \
match_multiply_exponents, multiply_exponents, \
match_duplicate_exponent, duplicate_exponent, \
match_raised_fraction, raised_fraction, \
match_remove_negative_exponent, remove_negative_exponent, \
match_exponent_to_root, exponent_to_root, \
match_constant_exponent, remove_power_of_zero, remove_power_of_one
......@@ -95,6 +96,18 @@ class TestRulesPowers(RulesTestCase):
self.assertEqualPos(possibilities,
[P(root, duplicate_exponent, ([a, b], p))])
def test_match_raised_fraction(self):
ab, p = root = tree('(a / b) ^ p')
self.assertEqualPos(match_raised_fraction(root),
[P(root, raised_fraction, (ab, p))])
def test_raised_fraction(self):
ab, p = root = tree('(a / b) ^ p')
a, b = ab
self.assertEqual(raised_fraction(root, (ab, p)), a ** p / b ** p)
def test_match_remove_negative_exponent(self):
a, p = tree('a,p')
root = a ** -p
......
import unittest
from src.rules.utils import least_common_multiple, is_fraction
from src.rules.utils import least_common_multiple, is_fraction, partition
from tests.rulestestcase import tree
......@@ -18,3 +18,7 @@ class TestRulesUtils(unittest.TestCase):
self.assertTrue(is_fraction(l1 / 2 * a, a, 2))
self.assertTrue(is_fraction(a * (l1 / 2), a, 2))
self.assertFalse(is_fraction(l1 / 3 * a, a, 2))
def test_partition(self):
self.assertEqual(partition(lambda x: x & 1, range(6)),
([1, 3, 5], [0, 2, 4]))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment