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

Tweaked some logarithmic rules.

parent 6b44c68e
No related branches found
No related tags found
No related merge requests found
...@@ -22,7 +22,7 @@ from .derivatives import match_zero_derivative, \ ...@@ -22,7 +22,7 @@ from .derivatives import match_zero_derivative, \
match_goniometric, match_sum_product_rule, match_quotient_rule match_goniometric, match_sum_product_rule, match_quotient_rule
from .logarithmic import match_constant_logarithm, \ from .logarithmic import match_constant_logarithm, \
match_add_logarithms, match_raised_base, match_factor_out_exponent, \ match_add_logarithms, match_raised_base, match_factor_out_exponent, \
match_factor_in_multiplicant match_factor_in_multiplicant, match_expand_terms
from .integrals import match_solve_indef, match_constant_integral, \ from .integrals import match_solve_indef, match_constant_integral, \
match_integrate_variable_power, match_factor_out_constant, \ match_integrate_variable_power, match_factor_out_constant, \
match_division_integral, match_function_integral match_division_integral, match_function_integral
...@@ -56,7 +56,8 @@ RULES = { ...@@ -56,7 +56,8 @@ RULES = {
match_variable_power, match_const_deriv_multiplication, match_variable_power, match_const_deriv_multiplication,
match_logarithmic, match_goniometric, match_sum_product_rule, match_logarithmic, match_goniometric, match_sum_product_rule,
match_quotient_rule], match_quotient_rule],
OP_LOG: [match_constant_logarithm, match_factor_out_exponent], OP_LOG: [match_constant_logarithm, match_factor_out_exponent,
match_expand_terms],
OP_INT: [match_integrate_variable_power, match_constant_integral, OP_INT: [match_integrate_variable_power, match_constant_integral,
match_factor_out_constant, match_division_integral, match_factor_out_constant, match_division_integral,
match_function_integral], match_function_integral],
......
from itertools import combinations, product from itertools import combinations, product, ifilterfalse
from .utils import find_variables, partition from .utils import find_variables, partition, divides, is_numeric_node
from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \ from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \
Scope, log Scope, log, DEFAULT_LOGARITHM_BASE, E, OP_DIV
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -11,8 +11,8 @@ def match_constant_logarithm(node): ...@@ -11,8 +11,8 @@ def match_constant_logarithm(node):
""" """
log_1(a) -> # raise ValueError for base 1 log_1(a) -> # raise ValueError for base 1
log(1) -> 0 log(1) -> 0
log(a, a) -> 1 # Explicit possibility to prevent cycles log(a, a) -> 1
log(a, a) -> log(a) / log(a) # -> 1 log(a, b) and b not in (10, e) -> log(a) / log(b)
""" """
assert node.is_op(OP_LOG) assert node.is_op(OP_LOG)
...@@ -30,8 +30,9 @@ def match_constant_logarithm(node): ...@@ -30,8 +30,9 @@ def match_constant_logarithm(node):
if raised == base: if raised == base:
# log(a, a) -> 1 # log(a, a) -> 1
p.append(P(node, base_equals_raised)) p.append(P(node, base_equals_raised))
# log(a, a) -> log(a) / log(a) # -> 1 p.append(P(node, divide_same_base))
# TODO: When to do this except for this case? elif base not in (DEFAULT_LOGARITHM_BASE, E):
# log(a, b) -> log(a) / log(b)
p.append(P(node, divide_same_base)) p.append(P(node, divide_same_base))
return p return p
...@@ -56,12 +57,12 @@ def base_equals_raised(root, args): ...@@ -56,12 +57,12 @@ def base_equals_raised(root, args):
return L(1).negate(root.negated) return L(1).negate(root.negated)
MESSAGES[base_equals_raised] = _('Logarithm {0} recuces to 1.') MESSAGES[base_equals_raised] = _('Logarithm {0} reduces to 1.')
def divide_same_base(root, args): def divide_same_base(root, args):
""" """
log(a, b) -> log(a) / log(b) log(a, b) and b != 10 -> log(a) / log(b)
""" """
raised, base = root raised, base = root
...@@ -73,10 +74,10 @@ MESSAGES[divide_same_base] = _('Apply log_b(a) = log(a) / log(b) on {0}.') ...@@ -73,10 +74,10 @@ MESSAGES[divide_same_base] = _('Apply log_b(a) = log(a) / log(b) on {0}.')
def match_add_logarithms(node): def match_add_logarithms(node):
""" """
log(a) + log(b) -> log(ab) log(a) + log(b) and a,b in Z -> log(ab)
-log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab) -log(a) - log(b) and a,b in Z -> -(log(a) + log(b)) # -> -log(ab)
log(a) - log(b) -> log(a / b) log(a) - log(b) and a/b in Z -> log(a / b)
-log(a) + log(b) -> log(b / a) -log(a) + log(b) and a/b in Z -> log(b / a)
""" """
assert node.is_op(OP_ADD) assert node.is_op(OP_ADD)
...@@ -86,7 +87,10 @@ def match_add_logarithms(node): ...@@ -86,7 +87,10 @@ def match_add_logarithms(node):
for log_a, log_b in combinations(logarithms, 2): for log_a, log_b in combinations(logarithms, 2):
# Compare base # Compare base
if log_a[1] != log_b[1]: (a, base_a), (b, base_b) = log_a, log_b
if base_a != base_b or not a.is_numeric() \
or not b.is_numeric(): # pragma: nocover
continue continue
a_negated = log_a.negated == 1 a_negated = log_a.negated == 1
...@@ -98,10 +102,10 @@ def match_add_logarithms(node): ...@@ -98,10 +102,10 @@ def match_add_logarithms(node):
elif a_negated and b_negated: elif a_negated and b_negated:
# -log(a) - log(b) -> -(log(a) + log(b)) # -log(a) - log(b) -> -(log(a) + log(b))
p.append(P(node, expand_negations, (scope, log_a, log_b))) p.append(P(node, expand_negations, (scope, log_a, log_b)))
elif not log_a.negated and b_negated: elif not log_a.negated and b_negated and divides(b.value, a.value):
# log(a) - log(b) -> log(a / b) # log(a) - log(b) -> log(a / b)
p.append(P(node, subtract_logarithms, (scope, log_a, log_b))) p.append(P(node, subtract_logarithms, (scope, log_a, log_b)))
elif a_negated and not log_b.negated: elif a_negated and not log_b.negated and divides(a.value, b.value):
# -log(a) + log(b) -> log(b / a) # -log(a) + log(b) -> log(b / a)
p.append(P(node, subtract_logarithms, (scope, log_b, log_a))) p.append(P(node, subtract_logarithms, (scope, log_b, log_a)))
...@@ -161,8 +165,8 @@ MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).') ...@@ -161,8 +165,8 @@ MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).')
def match_raised_base(node): def match_raised_base(node):
""" """
g ^ log_g(a) -> a g ^ log_g(a) -> a
g ^ (blog_g(a)) -> g ^ log_g(a ^ b) g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
""" """
assert node.is_op(OP_POW) assert node.is_op(OP_POW)
...@@ -188,11 +192,18 @@ def match_raised_base(node): ...@@ -188,11 +192,18 @@ def match_raised_base(node):
def factor_in_exponent_multiplicant(root, args): def factor_in_exponent_multiplicant(root, args):
"""
g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
"""
r, e = root r, e = root
return r ** factor_in_multiplicant(e, args) return r ** factor_in_multiplicant(e, args)
MESSAGES[factor_in_exponent_multiplicant] = \
_('Bring {2} into {3} as exponent so that the power can be removed.')
def raised_base(root, args): def raised_base(root, args):
""" """
g ^ log_g(a) -> a g ^ log_g(a) -> a
...@@ -285,3 +296,53 @@ def factor_in_multiplicant(root, args): ...@@ -285,3 +296,53 @@ def factor_in_multiplicant(root, args):
MESSAGES[factor_in_multiplicant] = \ MESSAGES[factor_in_multiplicant] = \
_('Bring multiplicant {2} into {3} as the exponent of {3[0]}.') _('Bring multiplicant {2} into {3} as the exponent of {3[0]}.')
def match_expand_terms(node):
"""
log(ab) and a not in Z -> log(a) + log(b)
log(a / b) -> log(a) - log(b)
"""
assert node.is_op(OP_LOG)
exp = node[0]
if exp.is_op(OP_MUL):
scope = Scope(exp)
return [P(node, expand_multiplication_terms, (scope, n)) \
for n in ifilterfalse(is_numeric_node, scope)]
if exp.is_op(OP_DIV):
return [P(node, expand_division_terms)]
return []
def expand_multiplication_terms(root, args):
"""
log(ab) and a not in Z -> log(a) + log(b)
"""
scope, n = args
scope.remove(n)
base = root[1]
addition = log(n, base=base) + log(scope.as_nary_node(), base=base)
return addition.negate(root.negated)
MESSAGES[expand_multiplication_terms] = _('Extract {2} from {0}.')
def expand_division_terms(root, args):
"""
log(a / b) -> log(a) - log(b)
"""
division, base = root
n, d = division
addition = log(n.negate(division.negated), base=base) - log(d, base=base)
return addition.negate(root.negated)
MESSAGES[expand_division_terms] = _('Expand {0} to a subtraction.')
...@@ -4,7 +4,9 @@ from src.rules.logarithmic import log, match_constant_logarithm, \ ...@@ -4,7 +4,9 @@ from src.rules.logarithmic import log, match_constant_logarithm, \
subtract_logarithms, match_raised_base, raised_base, \ subtract_logarithms, match_raised_base, raised_base, \
match_factor_out_exponent, split_negative_exponent, \ match_factor_out_exponent, split_negative_exponent, \
factor_out_exponent, match_factor_in_multiplicant, \ factor_out_exponent, match_factor_in_multiplicant, \
factor_in_multiplicant factor_in_multiplicant, match_expand_terms, \
expand_multiplication_terms, expand_division_terms, \
factor_in_exponent_multiplicant
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
...@@ -30,10 +32,21 @@ class TestRulesLogarithmic(RulesTestCase): ...@@ -30,10 +32,21 @@ class TestRulesLogarithmic(RulesTestCase):
[P(root, base_equals_raised), [P(root, base_equals_raised),
P(root, divide_same_base)]) P(root, divide_same_base)])
root = tree('log(a, b)')
self.assertEqualPos(match_constant_logarithm(root),
[P(root, divide_same_base)])
def test_logarithm_of_one(self): def test_logarithm_of_one(self):
root = tree('log 1') root = tree('log 1')
self.assertEqual(logarithm_of_one(root, ()), 0) self.assertEqual(logarithm_of_one(root, ()), 0)
def test_base_equals_raised(self):
root, expect = tree('log(a, a), 1')
self.assertEqual(base_equals_raised(root, ()), expect)
root, expect = tree('-log(a, a), -1')
self.assertEqual(base_equals_raised(root, ()), expect)
def test_divide_same_base(self): def test_divide_same_base(self):
root, l5, l6 = tree('log(5, 6), 5, 6') root, l5, l6 = tree('log(5, 6), 5, 6')
self.assertEqual(divide_same_base(root, ()), log(l5) / log(l6)) self.assertEqual(divide_same_base(root, ()), log(l5) / log(l6))
...@@ -42,19 +55,27 @@ class TestRulesLogarithmic(RulesTestCase): ...@@ -42,19 +55,27 @@ class TestRulesLogarithmic(RulesTestCase):
root = tree('log a + ln b') root = tree('log a + ln b')
self.assertEqualPos(match_add_logarithms(root), []) self.assertEqualPos(match_add_logarithms(root), [])
log_a, log_b = root = tree('log a + log b') # log(ab) is not desired if ab is not reduceable
root = tree('log a + log b')
self.assertEqualPos(match_add_logarithms(root), [])
log_a, log_b = root = tree('log 2 + log 3')
self.assertEqualPos(match_add_logarithms(root), self.assertEqualPos(match_add_logarithms(root),
[P(root, add_logarithms, (Scope(root), log_a, log_b))]) [P(root, add_logarithms, (Scope(root), log_a, log_b))])
log_a, log_b = root = tree('-log a - log b') log_a, log_b = root = tree('-log 2 - log 3')
self.assertEqualPos(match_add_logarithms(root), self.assertEqualPos(match_add_logarithms(root),
[P(root, expand_negations, (Scope(root), log_a, log_b))]) [P(root, expand_negations, (Scope(root), log_a, log_b))])
log_a, log_b = root = tree('log a - log b') # log(2 / 3) is not desired because 2 / 3 cannot be reduced
log_a, log_b = root = tree('log 2 - log 3')
self.assertEqualPos(match_add_logarithms(root), [])
log_a, log_b = root = tree('log 4 - log 2')
self.assertEqualPos(match_add_logarithms(root), self.assertEqualPos(match_add_logarithms(root),
[P(root, subtract_logarithms, (Scope(root), log_a, log_b))]) [P(root, subtract_logarithms, (Scope(root), log_a, log_b))])
log_a, log_b = root = tree('-log a + log b') log_a, log_b = root = tree('-log 2 + log 4')
self.assertEqualPos(match_add_logarithms(root), self.assertEqualPos(match_add_logarithms(root),
[P(root, subtract_logarithms, (Scope(root), log_b, log_a))]) [P(root, subtract_logarithms, (Scope(root), log_b, log_a))])
...@@ -93,6 +114,18 @@ class TestRulesLogarithmic(RulesTestCase): ...@@ -93,6 +114,18 @@ class TestRulesLogarithmic(RulesTestCase):
root = tree('2 ^ log_3(a)') root = tree('2 ^ log_3(a)')
self.assertEqualPos(match_raised_base(root), []) self.assertEqualPos(match_raised_base(root), [])
root = tree('e ^ (2ln x)')
l2, lnx = mul = root[1]
self.assertEqualPos(match_raised_base(root),
[P(root, factor_in_exponent_multiplicant,
(Scope(mul), l2, lnx))])
def test_factor_in_exponent_multiplicant(self):
root, expect = tree('e ^ (2ln x), e ^ ln x ^ 2')
l2, lnx = mul = root[1]
self.assertEqual(factor_in_exponent_multiplicant(root,
(Scope(mul), l2, lnx)), expect)
def test_raised_base(self): def test_raised_base(self):
root, a = tree('2 ^ log_2(a), a') root, a = tree('2 ^ log_2(a), a')
self.assertEqual(raised_base(root, (root[1][0],)), a) self.assertEqual(raised_base(root, (root[1][0],)), a)
...@@ -136,3 +169,25 @@ class TestRulesLogarithmic(RulesTestCase): ...@@ -136,3 +169,25 @@ class TestRulesLogarithmic(RulesTestCase):
l2, log3 = root l2, log3 = root
self.assertEqual(factor_in_multiplicant(root, (Scope(root), l2, log3)), self.assertEqual(factor_in_multiplicant(root, (Scope(root), l2, log3)),
expect) expect)
def test_match_expand_terms(self):
ab, base = root = tree('log(2x)')
a, b = ab
self.assertEqualPos(match_expand_terms(root),
[P(root, expand_multiplication_terms, (Scope(ab), b))])
root = tree('log(2 * 3)')
self.assertEqualPos(match_expand_terms(root), [])
root = tree('log(2 / a)')
self.assertEqualPos(match_expand_terms(root),
[P(root, expand_division_terms)])
def test_expand_multiplication_terms(self):
root, expect = tree('log(2x), log x + log 2')
self.assertEqual(expand_multiplication_terms(root,
(Scope(root[0]), root[0][1])), expect)
def test_expand_division_terms(self):
root, expect = tree('log(2 / x), log 2 - log x')
self.assertEqual(expand_division_terms(root, ()), expect)
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