Commit 59de7640 authored by Taddeus Kroes's avatar Taddeus Kroes

Added more logarithmic rules.

parent a9885848
......@@ -22,7 +22,8 @@ from src.rules.derivatives import match_zero_derivative, \
match_const_deriv_multiplication, match_logarithmic, \
match_goniometric, match_sum_product_rule, match_quotient_rule
from src.rules.logarithmic import match_constant_logarithm, \
match_add_logarithms, match_raised_base
match_add_logarithms, match_raised_base, match_factor_out_exponent, \
match_factor_in_multiplicant
RULES = {
OP_ADD: [match_add_numerics, match_add_constant_fractions,
......@@ -31,7 +32,8 @@ RULES = {
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_sort_multiplicants, match_multiply_fractions],
match_sort_multiplicants, match_multiply_fractions,
match_factor_in_multiplicant],
OP_DIV: [match_subtract_exponents, match_divide_numerics,
match_constant_division, match_divide_fractions,
match_negated_division, match_equal_fraction_parts],
......@@ -50,5 +52,5 @@ RULES = {
match_variable_power, match_const_deriv_multiplication,
match_logarithmic, match_goniometric, match_sum_product_rule,
match_quotient_rule],
OP_LOG: [match_constant_logarithm],
OP_LOG: [match_constant_logarithm, match_factor_out_exponent],
}
from itertools import combinations
from itertools import combinations, product
from .utils import find_variables
from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_LOG, E, \
OP_ADD, OP_MUL, OP_POW, Scope
from ..possibilities import Possibility as P, MESSAGES
......@@ -21,9 +22,9 @@ 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
"""
# TODO: base and raised
assert node.is_op(OP_LOG)
raised, base = node
......@@ -38,7 +39,10 @@ def match_constant_logarithm(node):
p.append(P(node, logarithm_of_one))
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))
return p
......@@ -50,12 +54,22 @@ def logarithm_of_one(root, args):
"""
raised, base = root
return L(0)
return L(0).negate(root.negated)
MESSAGES[logarithm_of_one] = _('Logarithm of one reduces to zero.')
def base_equals_raised(root, args):
"""
log(a, a) -> 1
"""
return L(1).negate(root.negated)
MESSAGES[base_equals_raised] = _('Logarithm {0} recuces to 1.')
def divide_same_base(root, args):
"""
log(a, b) -> log(a) / log(b)
......@@ -120,7 +134,7 @@ def add_logarithms(root, args):
MESSAGES[add_logarithms] = _('Apply log(a) + log(b) = log(ab).')
#_('Combine two logarithms with the same base: {2} and {3}.')
#_('Combine logarithms with the same base: {2} and {3}.')
def expand_negations(root, args):
......@@ -178,3 +192,87 @@ def raised_base(root, args):
MESSAGES[raised_base] = _('Apply g ^ log_g(a) = a on {0}.')
def match_factor_out_exponent(node):
"""
This match simplifies a power with a variable in it to a multiplication:
log(a ^ b) -> blog(a)
log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
"""
assert node.is_op(OP_LOG)
p = []
if node[0].is_power():
a, b = node[0]
if b.negated:
p.append(P(node, split_negative_exponent))
p.append(P(node, factor_out_exponent))
return p
def split_negative_exponent(root, args):
"""
log(a ^ -b) -> log((a ^ b) ^ -1) # =>* -log(a ^ b)
"""
(a, b), base = root
return log((a ** +b) ** -L(1), base=base)
MESSAGES[split_negative_exponent] = \
_('Split and factor out the negative exponent within logarithm {0}.')
def factor_out_exponent(root, args):
"""
log(a ^ b) -> blog(a)
"""
(a, b), base = root
return b * log(a, base=base)
MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][0]} from {0}.')
def match_factor_in_multiplicant(node):
"""
Only bring a multiplicant inside a logarithms if both the multiplicant and
the logaritm's content are constants. This will yield a new simplification
of constants inside the logarithm.
2log(2) -> log(2 ^ 2) # -> log(4)
2log(2 / 4) -> log((2 / 4) ^ 2) # =>* log(1 / 4)
"""
assert node.is_op(OP_MUL)
scope = Scope(node)
constants = filter(lambda n: n.is_int(), node)
logarithms = filter(lambda n: n.is_op(OP_LOG) \
and not len(find_variables(n)), node)
p = []
for constant, logarithm in product(constants, logarithms):
p.append(P(node, factor_in_multiplicant, (scope, constant, logarithm)))
return p
def factor_in_multiplicant(root, args):
"""
alog(b) -> log(b ^ a)
"""
scope, a, log_b = args
b, base = log_b
scope.replace(a, log(b ** a, base=base))
scope.remove(log_b)
return scope.as_nary_node()
MESSAGES[factor_in_multiplicant] = \
_('Bring multiplicant {2} into {3} as the exponent of {3[0]}.')
......@@ -232,7 +232,6 @@ class TestRulesDerivatives(RulesTestCase):
self.assertRewrite([
'der(e ^ x)',
'e ^ x * ln(e)',
'e ^ x * (log(e) / log(e))',
'e ^ x * 1',
'e ^ x',
])
from src.rules.logarithmic import log, ln, match_constant_logarithm, \
logarithm_of_one, divide_same_base, match_add_logarithms, \
add_logarithms, expand_negations, subtract_logarithms, \
match_raised_base, raised_base
base_equals_raised, logarithm_of_one, divide_same_base, \
match_add_logarithms, add_logarithms, expand_negations, \
subtract_logarithms, match_raised_base, raised_base, \
match_factor_out_exponent, split_negative_exponent, \
factor_out_exponent, match_factor_in_multiplicant, \
factor_in_multiplicant
from src.node import Scope
from src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree
......@@ -18,11 +21,13 @@ class TestRulesLogarithmic(RulesTestCase):
root = tree('log 10')
self.assertEqualPos(match_constant_logarithm(root),
[P(root, divide_same_base)])
[P(root, base_equals_raised),
P(root, divide_same_base)])
root = tree('log(a, a)')
self.assertEqualPos(match_constant_logarithm(root),
[P(root, divide_same_base)])
[P(root, base_equals_raised),
P(root, divide_same_base)])
def test_logarithm_of_one(self):
root = tree('log 1')
......@@ -90,3 +95,38 @@ class TestRulesLogarithmic(RulesTestCase):
def test_raised_base(self):
root, a = tree('2 ^ log_2(a), a')
self.assertEqual(raised_base(root, (root[1][0],)), a)
def test_match_factor_out_exponent(self):
for root in tree('log(a ^ 2), log(2 ^ a), log(a ^ a), log(2 ^ 2)'):
self.assertEqualPos(match_factor_out_exponent(root),
[P(root, factor_out_exponent)])
root = tree('log(a ^ -b)')
self.assertEqualPos(match_factor_out_exponent(root),
[P(root, split_negative_exponent),
P(root, factor_out_exponent)])
def test_split_negative_exponent(self):
root, expect = tree('log(a ^ -b), log((a ^ b) ^ -1)')
self.assertEqual(split_negative_exponent(root, ()), expect)
def test_factor_out_exponent(self):
((a, l2), l10) = root = tree('log(a ^ 2)')
self.assertEqual(factor_out_exponent(root, ()), l2 * log(a))
def test_match_factor_in_multiplicant(self):
root, log_3 = tree('2log(3), log(3)')
self.assertEqualPos(match_factor_in_multiplicant(root),
[P(root, factor_in_multiplicant, (Scope(root), 2, log_3))])
root = tree('2log(a)')
self.assertEqualPos(match_factor_in_multiplicant(root), [])
root = tree('alog(3)')
self.assertEqualPos(match_factor_in_multiplicant(root), [])
def test_factor_in_multiplicant(self):
root, expect = tree('2log(3), log(3 ^ 2)')
l2, log3 = root
self.assertEqual(factor_in_multiplicant(root, (Scope(root), l2, log3)),
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