Commit a0a12318 authored by Taddeus Kroes's avatar Taddeus Kroes

Tweaked some logarithmic rules.

parent 6b44c68e
......@@ -22,7 +22,7 @@ from .derivatives import match_zero_derivative, \
match_goniometric, match_sum_product_rule, match_quotient_rule
from .logarithmic import match_constant_logarithm, \
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, \
match_integrate_variable_power, match_factor_out_constant, \
match_division_integral, match_function_integral
......@@ -56,7 +56,8 @@ RULES = {
match_variable_power, match_const_deriv_multiplication,
match_logarithmic, match_goniometric, match_sum_product_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,
match_factor_out_constant, match_division_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, \
Scope, log
Scope, log, DEFAULT_LOGARITHM_BASE, E, OP_DIV
from ..possibilities import Possibility as P, MESSAGES
from ..translate import _
......@@ -11,8 +11,8 @@ def match_constant_logarithm(node):
"""
log_1(a) -> # raise ValueError for base 1
log(1) -> 0
log(a, a) -> 1 # Explicit possibility to prevent cycles
log(a, a) -> log(a) / log(a) # -> 1
log(a, a) -> 1
log(a, b) and b not in (10, e) -> log(a) / log(b)
"""
assert node.is_op(OP_LOG)
......@@ -30,8 +30,9 @@ def match_constant_logarithm(node):
if raised == base:
# log(a, a) -> 1
p.append(P(node, base_equals_raised))
# log(a, a) -> log(a) / log(a) # -> 1
# TODO: When to do this except for this case?
p.append(P(node, divide_same_base))
elif base not in (DEFAULT_LOGARITHM_BASE, E):
# log(a, b) -> log(a) / log(b)
p.append(P(node, divide_same_base))
return p
......@@ -56,12 +57,12 @@ def base_equals_raised(root, args):
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):
"""
log(a, b) -> log(a) / log(b)
log(a, b) and b != 10 -> log(a) / log(b)
"""
raised, base = root
......@@ -73,10 +74,10 @@ MESSAGES[divide_same_base] = _('Apply log_b(a) = log(a) / log(b) on {0}.')
def match_add_logarithms(node):
"""
log(a) + log(b) -> log(ab)
-log(a) - log(b) -> -(log(a) + log(b)) # -> -log(ab)
log(a) - log(b) -> log(a / b)
-log(a) + log(b) -> log(b / a)
log(a) + log(b) and a,b in Z -> log(ab)
-log(a) - log(b) and a,b in Z -> -(log(a) + log(b)) # -> -log(ab)
log(a) - log(b) and a/b in Z -> log(a / b)
-log(a) + log(b) and a/b in Z -> log(b / a)
"""
assert node.is_op(OP_ADD)
......@@ -86,7 +87,10 @@ def match_add_logarithms(node):
for log_a, log_b in combinations(logarithms, 2):
# 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
a_negated = log_a.negated == 1
......@@ -98,10 +102,10 @@ def match_add_logarithms(node):
elif a_negated and b_negated:
# -log(a) - log(b) -> -(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)
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)
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).')
def match_raised_base(node):
"""
g ^ log_g(a) -> a
g ^ (blog_g(a)) -> g ^ log_g(a ^ b)
g ^ log_g(a) -> a
g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
"""
assert node.is_op(OP_POW)
......@@ -188,11 +192,18 @@ def match_raised_base(node):
def factor_in_exponent_multiplicant(root, args):
"""
g ^ (b * log_g(a)) -> g ^ log_g(a ^ b)
"""
r, e = root
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):
"""
g ^ log_g(a) -> a
......@@ -285,3 +296,53 @@ def factor_in_multiplicant(root, args):
MESSAGES[factor_in_multiplicant] = \
_('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, \
subtract_logarithms, match_raised_base, raised_base, \
match_factor_out_exponent, split_negative_exponent, \
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.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -30,10 +32,21 @@ class TestRulesLogarithmic(RulesTestCase):
[P(root, base_equals_raised),
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):
root = tree('log 1')
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):
root, l5, l6 = tree('log(5, 6), 5, 6')
self.assertEqual(divide_same_base(root, ()), log(l5) / log(l6))
......@@ -42,19 +55,27 @@ class TestRulesLogarithmic(RulesTestCase):
root = tree('log a + ln b')
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),
[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),
[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),
[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),
[P(root, subtract_logarithms, (Scope(root), log_b, log_a))])
......@@ -93,6 +114,18 @@ class TestRulesLogarithmic(RulesTestCase):
root = tree('2 ^ log_3(a)')
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):
root, a = tree('2 ^ log_2(a), a')
self.assertEqual(raised_base(root, (root[1][0],)), a)
......@@ -136,3 +169,25 @@ class TestRulesLogarithmic(RulesTestCase):
l2, log3 = root
self.assertEqual(factor_in_multiplicant(root, (Scope(root), l2, log3)),
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)
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