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, \ ...@@ -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)))
...@@ -162,7 +166,7 @@ MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).') ...@@ -162,7 +166,7 @@ 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)
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