Explorar o código

Added more logarithmic rules.

Taddeus Kroes %!s(int64=14) %!d(string=hai) anos
pai
achega
59de76406e

+ 5 - 3
src/rules/__init__.py

@@ -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],
         }

+ 102 - 4
src/rules/logarithmic.py

@@ -1,5 +1,6 @@
-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]}.')

+ 0 - 1
tests/test_rules_derivatives.py

@@ -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',
         ])

+ 45 - 5
tests/test_rules_logarithmic.py

@@ -1,7 +1,10 @@
 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)