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