Commit 7a9605a7 authored by Taddeus Kroes's avatar Taddeus Kroes

Commenced some chanches to fraction rules (unit tests are still failing).

parent 42662f45
......@@ -9,9 +9,9 @@ from .powers import match_add_exponents, match_subtract_exponents, \
from .numerics import match_add_numerics, match_divide_numerics, \
match_multiply_numerics, match_multiply_zero, match_multiply_one, \
match_raise_numerics
from .fractions import match_constant_division, match_add_constant_fractions, \
match_expand_and_add_fractions, match_multiply_fractions, \
match_divide_fractions, match_equal_fraction_parts
from .fractions import match_constant_division, match_add_fractions, \
match_multiply_fractions, match_divide_fractions, \
match_equal_fraction_parts
from .negation import match_negated_factor, match_negate_polynome, \
match_negated_division
from .sort import match_sort_multiplicants
......@@ -30,12 +30,11 @@ from src.rules.integrals import match_solve_indef, match_constant_integral, \
from src.rules.lineq import match_move_term
RULES = {
OP_ADD: [match_add_numerics, match_add_constant_fractions,
OP_ADD: [match_add_numerics, match_add_fractions,
match_combine_groups, match_add_quadrants,
match_add_logarithms],
OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
match_expand_and_add_fractions, match_multiply_zero,
match_negated_factor, match_multiply_one,
match_multiply_zero, match_negated_factor, match_multiply_one,
match_sort_multiplicants, match_multiply_fractions,
match_factor_in_multiplicant],
OP_DIV: [match_subtract_exponents, match_divide_numerics,
......
from itertools import combinations, product
from .utils import least_common_multiple, partition
from ..node import ExpressionLeaf as L, Scope, OP_DIV, OP_ADD, OP_MUL, \
OP_POW, nary_node, negate
from .utils import least_common_multiple, partition, is_numeric_node, \
evals_to_numeric
from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DIV, \
OP_ADD, OP_MUL, OP_POW, nary_node, negate
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -68,51 +69,76 @@ def division_by_self(root, args):
MESSAGES[division_by_self] = _('Division of {1} by itself reduces to 1.')
def match_add_constant_fractions(node):
def match_add_fractions(node):
"""
1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4 # Equalize denominators
2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4
2 / 4 + 3 / 4 -> 5 / 4 # Equal denominators, so nominators can
# be added
2 / 4 - 3 / 4 -> -1 / 4
1 / 2 + 3 / 4 -> 4 / 8 + 6 / 8 # Equalize denominators by multiplying
# them with eachother
a / b + c / b and a, c in Z -> (a + c) / b
a / b + c / d and a, b, c, d in Z -> a' / e + c' / e # e = lcm(b, d)
# | e = b * d
a / b + c and a, b, c in Z -> a / b + b / b * c # =>* (a + bc) / b
"""
assert node.is_op(OP_ADD)
p = []
scope = Scope(node)
fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
numerics = filter(is_numeric_node, others)
fractions = filter(lambda node: node.is_op(OP_DIV), scope)
for a, b in combinations(fractions, 2):
na, da = a
nb, db = b
for ab, cd in combinations(fractions, 2):
a, b = ab
c, d = cd
if da == db:
if b == d:
# Equal denominators, add nominators to create a single fraction
p.append(P(node, add_nominators, (a, b)))
elif da.is_numeric() and db.is_numeric():
p.append(P(node, add_nominators, (scope, ab, cd)))
elif all(map(is_numeric_node, (a, b, c, d))):
# Denominators are both numeric, rewrite both fractions to the
# least common multiple of their denominators. Later, the
# nominators will be added
denom = least_common_multiple(da.value, db.value)
p.append(P(node, equalize_denominators, (scope, a, b, denom)))
lcm = least_common_multiple(b.value, d.value)
p.append(P(node, equalize_denominators, (scope, ab, cd, lcm)))
# Also, add the (non-recommended) possibility to multiply the
# denominators
p.append(P(node, equalize_denominators, (scope, a, b,
da.value * db.value)))
# denominators. Do this only if the multiplication is not equal to
# the least common multiple, to avoid duplicate possibilities
mult = b.value * d.value
if mult != lcm:
p.append(P(node, equalize_denominators, (scope, ab, cd, mult)))
for ab, c in product(fractions, numerics):
a, b = ab
if a.is_numeric() and b.is_numeric():
# Fraction of constants added to a constant -> create a single
# constant fraction
p.append(P(node, constant_to_fraction, (scope, ab, c)))
return p
def add_nominators(root, args):
"""
a / b + c / b and a, c in Z -> (a + c) / b
"""
scope, ab, cb = args
a, b = ab
c = cb[0]
# Replace the left node with the new expression, transfer fraction
# negations to nominators
scope.replace(ab, (a.negate(ab.negated) + c.negate(cb.negated)) / b)
scope.remove(cb)
return scope.as_nary_node()
MESSAGES[add_nominators] = \
_('Add the nominators of {2} and {3} to create a single fraction.')
def equalize_denominators(root, args):
"""
1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4
1 / 2 - 3 / 4 -> 2 / 4 - 3 / 4
a / 2 + b / 4 -> 2a / 4 + b / 4
a / b + c / d and a, b, c, d in Z -> a' / e + c' / e
"""
scope, denom = args[::3]
......@@ -132,65 +158,46 @@ def equalize_denominators(root, args):
return scope.as_nary_node()
MESSAGES[equalize_denominators] = _('Equalize the denominators of divisions'
' {2} and {3} to {4}.')
MESSAGES[equalize_denominators] = \
_('Equalize the denominators of divisions' ' {2} and {3} to {4}.')
def add_nominators(root, args):
def constant_to_fraction(root, args):
"""
a / b + c / b -> (a + c) / b
a / b - c / b -> (a - c) / b
-(a / b) + c / b -> -((a + c) / b)
-(a / b) - c / b -> (c - a) / -b
a / b + c and a, b, c in Z -> a / b + b / b * c # =>* (a + bc) / b
"""
# TODO: is 'add' Appropriate when rewriting to "(a + (-c)) / b"?
ab, cb = args
a, b = ab
scope = Scope(root)
# Replace the left node with the new expression
scope.replace(ab, (a + cb[0].negate(cb.negated)) / b)
# Remove the right node
scope.remove(cb)
scope, ab, c = args
b = ab[1]
scope.replace(c, b / b * c)
return scope.as_nary_node()
# TODO: convert this to a lambda. Example: 22 / 77 - 28 / 77. the "-" is above
# the "28/77" division.
MESSAGES[add_nominators] = _('Add the nominators of {1} and {2}.')
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)
"""
# TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
assert node.is_op(OP_MUL)
p = []
return p
MESSAGES[constant_to_fraction] = \
_('Rewrite constant {3} to a fraction to be able to add it to {2}.')
def match_multiply_fractions(node):
"""
a / b * (c / d) -> ac / (bd)
a * (b / c) -> ab / c
a / b * c and a, c in Z or (a = 1 and eval(b) not in Z) -> ac / b
"""
assert node.is_op(OP_MUL)
p = []
scope = Scope(node)
fractions, others = partition(lambda n: n.is_op(OP_DIV), scope)
numerics = filter(is_numeric_node, others)
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)))
for ab, c in product(fractions, others):
a, b = ab
if (a.is_numeric() and c.is_numeric()) or \
(a == 1 and evals_to_numeric(b)):
p.append(P(node, multiply_with_fraction, (scope, ab, c)))
return p
......@@ -203,7 +210,7 @@ def multiply_fractions(root, args):
a, b = ab
c, d = cd
scope.replace(ab, a * c / (b * d))
scope.replace(ab, (a * c / (b * d)).negate(ab.negated + cd.negated))
scope.remove(cd)
return scope.as_nary_node()
......@@ -214,18 +221,19 @@ MESSAGES[multiply_fractions] = _('Multiply fractions {2} and {3}.')
def multiply_with_fraction(root, args):
"""
a * (b / c) -> ab / c
a / b * c and a, c in Z or a == 1 -> ac / b
"""
scope, a, bc = args
b, c = bc
scope, ab, c = args
a, b = ab
scope.replace(a, a * b / c)
scope.remove(bc)
scope.replace(ab, (a * c / b).negate(ab.negated))
scope.remove(c)
return scope.as_nary_node()
MESSAGES[multiply_with_fraction] = _('Multiply {2} with fraction {3}.')
MESSAGES[multiply_with_fraction] = \
_('Multiply {3} with the nominator of fraction {2}.')
def match_divide_fractions(node):
......@@ -306,32 +314,48 @@ def fraction_scopes(node):
nominator, denominator = node
if nominator.is_op(OP_MUL):
n_scope = list(Scope(nominator))
n_scope = Scope(nominator)
else:
n_scope = [nominator]
n_scope = Scope(N(OP_MUL, nominator))
if denominator.is_op(OP_MUL):
d_scope = list(Scope(denominator))
d_scope = Scope(denominator)
else:
d_scope = [denominator]
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
example:
a and a^2
a^2 and a^2
a^2 and a
"""
if a.is_power():
a = a[0]
if b.is_power():
b = b[0]
return a == b
def match_equal_fraction_parts(node):
"""
Divide nominator and denominator by the same part.
Examples:
ab / (ac) -> b / c
ab / a -> b / 1
a / (ab) -> 1 / b
a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e)
If the same root appears in both nominator and denominator, extrct 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
#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
"""
assert node.is_op(OP_DIV)
......@@ -343,88 +367,111 @@ def match_equal_fraction_parts(node):
return p
# Look for matching parts in scopes
for i, n in enumerate(n_scope):
for j, d in enumerate(d_scope):
if n.equals(d, ignore_negation=True):
p.append(P(node, divide_fraction_parts,
(negate(n, 0), n_scope, d_scope, i, j)))
if n.is_op(OP_POW):
a = n[0]
if d == a or (d.is_op(OP_POW) and d[0] == a):
# a ^ p * b / a -> a ^ p / a * b
p.append(P(node, extract_divided_roots,
(a, n_scope, d_scope, i, j)))
elif d.is_op(OP_POW) and n == d[0]:
# a * b / a ^ q -> a / a ^ q * b
p.append(P(node, extract_divided_roots,
(d[0], n_scope, d_scope, i, j)))
for n, d in product(n_scope, d_scope):
if is_power_combination(n, d):
p.append(P(N, extract_fraction_terms, (n_scope, d_scope, n, d)))
#for i, n in enumerate(n_scope):
# for j, d in enumerate(d_scope):
# if n.equals(d, ignore_negation=True):
# p.append(P(node, divide_fraction_parts,
# (negate(n, 0), n_scope, d_scope, i, j)))
# if n.is_op(OP_POW):
# a = n[0]
# if d == a or (d.is_op(OP_POW) and d[0] == a):
# # a ^ p * b / a -> a ^ p / a * b
# p.append(P(node, extract_divided_roots,
# (a, n_scope, d_scope, i, j)))
# elif d.is_op(OP_POW) and n == d[0]:
# # a * b / a ^ q -> a / a ^ q * b
# p.append(P(node, extract_divided_roots,
# (d[0], n_scope, d_scope, i, j)))
return p
def remove_from_scopes(n_scope, d_scope, i, j):
a_n, a_d = n_scope[i], d_scope[j]
del n_scope[i]
del d_scope[j]
if not n_scope:
# Last element of nominator scope, replace by 1
nom = L(1)
elif len(n_scope) == 1:
# Only one element left, no multiplication
nom = n_scope[0]
else:
# Still a multiplication
nom = nary_node('*', n_scope)
if not d_scope:
denom = L(1)
elif len(n_scope) == 1:
denom = d_scope[0]
else:
denom = nary_node('*', d_scope)
return a_n, a_d, nom, denom
def divide_fraction_parts(root, args):
def extract_fraction_terms(root, args):
"""
Divide nominator and denominator by the same part.
Examples:
ab / (ac) -> b / c
ab / a -> b / 1
a / (ab) -> 1 / b
-ab / a -> -b / 1
a ^ b * c / (a ^ d * e) -> a ^ b / a ^ d * (c / e)
"""
a, n_scope, d_scope, i, j = args
n, d = root
a_n, a_d, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
# Move negation of removed part to nominator and denominator
return nom.negate(n.negated + a_n.negated) \
/ denom.negate(d.negated + a_d.negated)
MESSAGES[divide_fraction_parts] = \
_('Divide nominator and denominator in {0} by {1}.')
n_scope, d_scope, n, d = args
if len(n_scope) == 1:
n_scope.replace(n, L(1))
else:
n_scope.remove(n)
def extract_divided_roots(root, args):
"""
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
"""
a, n_scope, d_scope, i, j = args
n, d = root
ap, aq, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
if len(d_scope) == 1:
d_scope.replace(d, L(1))
else:
d_scope.remove(n)
return ap / aq * nom.negate(n.negated) / denom.negate(d.negated)
return n / d * (n_scope.as_nary_node() / d_scope.as_nary_node())
MESSAGES[extract_divided_roots] = \
_('Extract the root {1} from nominator and denominator in {0}.')
#def remove_from_scopes(n_scope, d_scope, i, j):
# a_n, a_d = n_scope[i], d_scope[j]
#
# del n_scope[i]
# del d_scope[j]
#
# if not n_scope:
# # Last element of nominator scope, replace by 1
# nom = L(1)
# elif len(n_scope) == 1:
# # Only one element left, no multiplication
# nom = n_scope[0]
# else:
# # Still a multiplication
# nom = nary_node('*', n_scope)
#
# if not d_scope:
# denom = L(1)
# elif len(n_scope) == 1:
# denom = d_scope[0]
# else:
# denom = nary_node('*', d_scope)
#
# return a_n, a_d, nom, denom
#
#
#def divide_fraction_parts(root, args):
# """
# Divide nominator and denominator by the same part.
#
# Examples:
# ab / (ac) -> b / c
# ab / a -> b / 1
# a / (ab) -> 1 / b
# -ab / a -> -b / 1
# """
# a, n_scope, d_scope, i, j = args
# n, d = root
# a_n, a_d, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
#
# # Move negation of removed part to nominator and denominator
# return nom.negate(n.negated + a_n.negated) \
# / denom.negate(d.negated + a_d.negated)
#
#
#MESSAGES[divide_fraction_parts] = \
# _('Divide nominator and denominator in {0} by {1}.')
#
#
#def extract_divided_roots(root, args):
# """
# 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
# """
# a, n_scope, d_scope, i, j = args
# n, d = root
# ap, aq, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)
#
# return ap / aq * nom.negate(n.negated) / denom.negate(d.negated)
#
#
#MESSAGES[extract_divided_roots] = \
# _('Extract the root {1} from nominator and denominator in {0}.')
from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_MUL, OP_DIV
from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_MUL, OP_DIV, \
OP_ADD, OP_POW, OP_SQRT
def greatest_common_divisor(a, b):
......@@ -132,3 +133,23 @@ def divides(m, n):
Check if m | n (m divides n).
"""
return not divmod(n, m)[1]
def is_numeric_node(node):
"""
Check if a node is numeric.
"""
return node.is_numeric()
def evals_to_numeric(node):
"""
Check if a node will eventually evaluate to a numeric value, by checking if
all leaves are numeric and there are only operators that can be
considerered a constant or will evaluate to one (+, *, /, ^, sqrt).
"""
if node.is_leaf:
return node.is_numeric()
return node.op in (OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_SQRT) \
and all(map(evals_to_numeric, node))
from src.rules.fractions import match_constant_division, division_by_one, \
division_of_zero, division_by_self, match_add_constant_fractions, \
division_of_zero, division_by_self, match_add_fractions, \
equalize_denominators, add_nominators, match_multiply_fractions, \
multiply_fractions, multiply_with_fraction, match_divide_fractions, \
divide_fraction, divide_by_fraction, match_equal_fraction_parts, \
divide_fraction_parts, extract_divided_roots
constant_to_fraction, extract_fraction_terms
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -49,44 +49,51 @@ class TestRulesFractions(RulesTestCase):
self.assertEqualNodes(division_by_self(root, ()), one)
def test_match_add_constant_fractions(self):
def test_match_add_fractions(self):
a, b, c, l1, l2, l3, l4 = tree('a,b,c,1,2,3,4')
n0, n1 = root = l1 / l2 + l3 / l4
possibilities = match_add_constant_fractions(root)
possibilities = match_add_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (Scope(root), n0, n1, 4)),
P(root, equalize_denominators, (Scope(root), n0, n1, 8))])
(((n0, n1), n2), n3), n4 = root = a + l1 / l2 + b + l3 / l4 + c
possibilities = match_add_constant_fractions(root)
possibilities = match_add_fractions(root)
self.assertEqualPos(possibilities,
[P(root, equalize_denominators, (Scope(root), n1, n3, 4)),
P(root, equalize_denominators, (Scope(root), n1, n3, 8))])
n0, n1 = root = l2 / l4 + l3 / l4
possibilities = match_add_constant_fractions(root)
possibilities = match_add_fractions(root)
self.assertEqualPos(possibilities,
[P(root, add_nominators, (n0, n1))])
[P(root, add_nominators, (Scope(root), n0, n1))])
(((n0, n1), n2), n3), n4 = root = a + l2 / l4 + b + l3 / l4 + c
possibilities = match_add_constant_fractions(root)
possibilities = match_add_fractions(root)
self.assertEqualPos(possibilities,
[P(root, add_nominators, (n1, n3))])
[P(root, add_nominators, (Scope(root), n1, n3))])
def test_add_constant_fractions_with_negation(self):
def test_match_add_fractions_constant_to_fraction(self):
l23, l1 = root = tree('2 / 3 + 1')
self.assertEqualPos(match_add_fractions(root),
[P(root, constant_to_fraction, (Scope(root), l23, l1))])
def test_add_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,
self.assertEqualPos(match_add_fractions(root),
[P(root, equalize_denominators, (Scope(root), n1, n3, 4)),
P(root, equalize_denominators, (Scope(root), n1, n3, 8))])
n0, n1 = root = l1 / l2 + l4 / l3
self.assertEqualPos(match_add_fractions(root),
[P(root, equalize_denominators, (Scope(root), n0, n1, 6))])
(((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))])
self.assertEqualPos(match_add_fractions(root),
[P(root, add_nominators, (Scope(root), n1, n3))])
def test_equalize_denominators(self):
a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
......@@ -113,30 +120,44 @@ class TestRulesFractions(RulesTestCase):
def test_add_nominators(self):
a, b, c = tree('a,b,c')
n0, n1 = root = a / b + c / b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b)
self.assertEqualNodes(add_nominators(root, (Scope(root), n0, n1)),
(a + c) / b)
n0, n1 = root = a / b + -c / b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + -c) / b)
self.assertEqualNodes(add_nominators(root, (Scope(root), n0, n1)),
(a + -c) / b)
n0, n1 = root = a / b + -(c / b)
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + -c) / b)
self.assertEqualNodes(add_nominators(root, (Scope(root), n0, n1)),
(a + -c) / b)
n0, n1 = root = a / -b + c / -b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / -b)
self.assertEqualNodes(add_nominators(root, (Scope(root), n0, n1)),
(a + c) / -b)
n0, n1 = root = a / -b + -c / -b
self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + -c) / -b)
self.assertEqualNodes(add_nominators(root, (Scope(root), n0, n1)),
(a + -c) / -b)
def test_constant_to_fraction(self):
root, e = tree('2 / 3 + 1, 2 / 3 + 3 / 3 * 1')
l23, l1 = root
self.assertEqual(constant_to_fraction(root, (Scope(root), l23, l1)), e)
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)')
(ab, e), cd = root = tree('4 / b * 2 * (3 / 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))])
P(root, multiply_with_fraction, (Scope(root), ab, e)),
P(root, multiply_with_fraction, (Scope(root), cd, e))])
ab, c = root = tree('1 / sqrt(3) * 2')
self.assertEqualPos(match_multiply_fractions(root),
[P(root, multiply_with_fraction, (Scope(root), ab, c))])
def test_multiply_fractions(self):
(a, b), (c, d) = ab, cd = root = tree('a / b * (c / d)')
......@@ -165,78 +186,114 @@ class TestRulesFractions(RulesTestCase):
self.assertEqual(divide_by_fraction(root, (a, b, c)), a * c / b)
def test_match_equal_fraction_parts(self):
(a, b), (c, a) = root = tree('ab / (ca)')
root, a, b, c = tree('ab / (ca), a, b, c')
n, d = root
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a, b], [c, a], 0, 1))])
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
(a, b), a = root = tree('ab / a')
n, d = root = tree('ab / a')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a, b], [a], 0, 0))])
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
a, (a, b) = root = tree('a / (ab)')
n, d = root = tree('a / (ab)')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, [a], [a, b], 0, 0))])
[P(root, extract_fraction_terms, (Scope(n), Scope(d), a, a))])
root = tree('abc / (cba)')
((a, b), c) = root[0]
s0, s1 = [a, b, c], [c, b, a]
n, d = root = tree('abc / (cba)')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, divide_fraction_parts, (a, s0, s1, 0, 2)),
P(root, divide_fraction_parts, (b, s0, s1, 1, 1)),
P(root, divide_fraction_parts, (c, s0, s1, 2, 0))])
[P(root, extract_fraction_terms, (Scope(n), scope(d), a, a)),
P(root, extract_fraction_terms, (Scope(n), scope(d), b, b)),
P(root, extract_fraction_terms, (Scope(n), scope(d), c, c))])
root = tree('a / a')
self.assertEqualPos(match_equal_fraction_parts(root), [])
(ap, b), aq = root = tree('a ^ p * b / a ^ q')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [ap, b], [aq], 0, 0))])
self.assertequalpos(match_equal_fraction_parts(root),
[p(root, extract_fraction_terms, (a, [ap, b], [aq], 0, 0))])
(a, b), aq = root = tree('a * b / a ^ q')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [a, b], [aq], 0, 0))])
self.assertequalpos(match_equal_fraction_parts(root),
[p(root, extract_fraction_terms, (a, [a, b], [aq], 0, 0))])
(ap, b), a = root = tree('a ^ p * b / a')
self.assertEqualPos(match_equal_fraction_parts(root),
[P(root, extract_divided_roots, (a, [ap, b], [a], 0, 0))])
def test_divide_fraction_parts(self):
(a, b), (c, a) = root = tree('ab / (ca)')
result = divide_fraction_parts(root, (a, [a, b], [c, a], 0, 1))
self.assertEqual(result, b / c)
(a, b), a = root = tree('ab / a')
result = divide_fraction_parts(root, (a, [a, b], [a], 0, 0))
self.assertEqual(result, b / 1)
root, l1 = tree('a / (ab), 1')
a, (a, b) = root
result = divide_fraction_parts(root, (a, [a], [a, b], 0, 0))
self.assertEqual(result, l1 / b)
root = tree('abc / (cba)')
((a, b), c) = root[0]
result = divide_fraction_parts(root, (a, [a, b, c], [c, b, a], 0, 2))
self.assertEqual(result, b * c / (c * b))
result = divide_fraction_parts(root, (b, [a, b, c], [c, b, a], 1, 1))
self.assertEqual(result, a * c / (c * a))
result = divide_fraction_parts(root, (c, [a, b, c], [c, b, a], 2, 0))
self.assertEqual(result, a * b / (b * a))
(a, b), a = root = tree('(-a)b / a')
result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
self.assertEqual(result, -b / 1)
def test_extract_divided_roots(self):
r, a = tree('a ^ p * b / a ^ q, a')
((a, p), b), (a, q) = (ap, b), aq = r
self.assertEqual(extract_divided_roots(r, (a, [ap, b], [aq], 0, 0)),
a ** p / a ** q * b / 1)
r = tree('a * b / a ^ q, a')
self.assertEqual(extract_divided_roots(r, (a, [a, b], [aq], 0, 0)),
a / a ** q * b / 1)
r = tree('a ^ p * b / a, a')
self.assertEqual(extract_divided_roots(r, (a, [ap, b], [a], 0, 0)),
a ** p / a * b / 1)
self.assertequalpos(match_equal_fraction_parts(root),
[p(root, extract_fraction_terms, (a, [ap, b], [a], 0, 0))])
#def test_match_equal_fraction_parts(self):
# (a, b), (c, a) = root = tree('ab / (ca)')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, [a, b], [c, a], 0, 1))])
# (a, b), a = root = tree('ab / a')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, [a, b], [a], 0, 0))])
# a, (a, b) = root = tree('a / (ab)')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, [a], [a, b], 0, 0))])
# root = tree('abc / (cba)')
# ((a, b), c) = root[0]
# s0, s1 = [a, b, c], [c, b, a]
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, s0, s1, 0, 2)),
# P(root, divide_fraction_parts, (b, s0, s1, 1, 1)),
# P(root, divide_fraction_parts, (c, s0, s1, 2, 0))])
# root = tree('-a / a')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, divide_fraction_parts, (a, [-a], [a], 0, 0))])
# (ap, b), aq = root = tree('a ^ p * b / a ^ q')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [ap, b], [aq], 0, 0))])
# (a, b), aq = root = tree('a * b / a ^ q')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [a, b], [aq], 0, 0))])
# (ap, b), a = root = tree('a ^ p * b / a')
# self.assertEqualPos(match_equal_fraction_parts(root),
# [P(root, extract_divided_roots, (a, [ap, b], [a], 0, 0))])
#def test_divide_fraction_parts(self):
# (a, b), (c, a) = root = tree('ab / (ca)')
# result = divide_fraction_parts(root, (a, [a, b], [c, a], 0, 1))
# self.assertEqual(result, b / c)
# (a, b), a = root = tree('ab / a')
# result = divide_fraction_parts(root, (a, [a, b], [a], 0, 0))
# self.assertEqual(result, b / 1)
# root, l1 = tree('a / (ab), 1')
# a, (a, b) = root
# result = divide_fraction_parts(root, (a, [a], [a, b], 0, 0))
# self.assertEqual(result, l1 / b)
# root = tree('abc / (cba)')
# ((a, b), c) = root[0]
# result = divide_fraction_parts(root, (a, [a, b, c], [c, b, a], 0, 2))
# self.assertEqual(result, b * c / (c * b))
# result = divide_fraction_parts(root, (b, [a, b, c], [c, b, a], 1, 1))
# self.assertEqual(result, a * c / (c * a))
# result = divide_fraction_parts(root, (c, [a, b, c], [c, b, a], 2, 0))
# self.assertEqual(result, a * b / (b * a))
# (a, b), a = root = tree('-ab / a')
# result = divide_fraction_parts(root, (a, [-a, b], [a], 0, 0))
# self.assertEqual(result, -b / 1)
#def test_extract_divided_roots(self):
# r, a = tree('a ^ p * b / a ^ q, a')
# ((a, p), b), (a, q) = (ap, b), aq = r
# self.assertEqual(extract_divided_roots(r, (a, [ap, b], [aq], 0, 0)),
# a ** p / a ** q * b / 1)
# r = tree('a * b / a ^ q, a')
# self.assertEqual(extract_divided_roots(r, (a, [a, b], [aq], 0, 0)),
# a / a ** q * b / 1)
# r = tree('a ^ p * b / a, a')
# self.assertEqual(extract_divided_roots(r, (a, [ap, b], [a], 0, 0)),
# a ** p / a * b / 1)
from src.rules import utils
from src.rules.utils import least_common_multiple, is_fraction, partition, \
find_variables, first_sorted_variable, find_variable, substitute, \
divides
divides, evals_to_numeric
from tests.rulestestcase import tree, RulesTestCase
......@@ -65,3 +65,13 @@ class TestRulesUtils(RulesTestCase):
self.assertTrue(divides(7, 21))
self.assertFalse(divides(4, 2))
self.assertFalse(divides(2, 3))
def test_evals_to_numeric(self):
self.assertTrue(evals_to_numeric(tree('1')))
self.assertFalse(evals_to_numeric(tree('a')))
self.assertTrue(evals_to_numeric(tree('1 + 2')))
self.assertFalse(evals_to_numeric(tree('1 + a')))
self.assertTrue(evals_to_numeric(tree('1 + 2 / 2 * 9')))
self.assertFalse(evals_to_numeric(tree('int 1')))
self.assertFalse(evals_to_numeric(tree('int a')))
self.assertTrue(evals_to_numeric(tree('sqrt 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