Commit c03390ba authored by Taddeus Kroes's avatar Taddeus Kroes

Implemented and tested some new rules for fractions.

parent 3a090e5f
......@@ -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]))
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