Jelajahi Sumber

Added some rules for logarithms.

Taddeus Kroes 14 tahun lalu
induk
melakukan
68ce57a9ab
3 mengubah file dengan 194 tambahan dan 3 penghapusan
  1. 6 2
      src/rules/__init__.py
  2. 141 1
      src/rules/logarithmic.py
  3. 47 0
      tests/test_rules_logarithmic.py

+ 6 - 2
src/rules/__init__.py

@@ -1,5 +1,5 @@
 from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS, \
-        OP_TAN, OP_DER
+        OP_TAN, OP_DER, OP_LOG
 from .groups import match_combine_groups
 from .factors import match_expand
 from .powers import match_add_exponents, match_subtract_exponents, \
@@ -21,10 +21,13 @@ from src.rules.derivatives import match_zero_derivative, \
         match_one_derivative, match_variable_power, \
         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
 
 RULES = {
         OP_ADD: [match_add_numerics, match_add_constant_fractions,
-                 match_combine_groups, match_add_quadrants],
+                 match_combine_groups, match_add_quadrants,
+                 match_add_logarithms],
         OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
                  match_expand_and_add_fractions, match_multiply_zero,
                  match_negated_factor, match_multiply_one,
@@ -46,4 +49,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],
         }

+ 141 - 1
src/rules/logarithmic.py

@@ -1,4 +1,7 @@
-from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_LOG, E
+from itertools import combinations
+
+from ..node import ExpressionNode as N, ExpressionLeaf as L, OP_LOG, E, \
+        OP_ADD, OP_MUL, Scope
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -12,3 +15,140 @@ def log(exponent, base=10):
 
 def ln(exponent):
     return log(exponent, base=E)
+
+
+def match_constant_logarithm(node):
+    """
+    log_1(a)   ->  # raise ValueError for base 1
+    log(1)     ->  0
+    log(a, a)  ->  log(a) / log(a)  # ->  1
+    """
+    # TODO: base and raised
+    assert node.is_op(OP_LOG)
+
+    raised, base = node
+
+    if base == 1:
+        raise ValueError('Logarithm with base 1 does not exist.')
+
+    p = []
+
+    if raised == 1:
+        p.append(P(node, logarithm_of_one))
+
+    if raised == base:
+        p.append(P(node, divide_same_base))
+
+    return p
+
+
+def logarithm_of_one(root, args):
+    """
+    log(1)  ->  0
+    """
+    raised, base = root
+
+    return log(raised) / log(base)
+
+
+MESSAGES[logarithm_of_one] = _('Logarithm of one reduces to zero.')
+
+
+def divide_same_base(root, args):
+    """
+    log(a, b)  ->  log(a) / log(b)
+    """
+    raised, base = root
+
+    return log(raised) / log(base)
+
+
+MESSAGES[divide_same_base] = _('Apply log_b(a)  ->  log(a) / log(b).')
+
+
+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)
+    """
+    assert node.is_op(OP_ADD)
+
+    p = []
+    scope = Scope(node)
+    logarithms = filter(lambda n: n.is_op(OP_LOG), scope)
+
+    for log_a, log_b in combinations(logarithms, 2):
+        # Compare base
+        if log_a[1] != log_b[1]:
+            continue
+
+        a_negated = log_a.negated == 1
+        b_negated = log_b.negated == 1
+
+        if not log_a.negated and not log_b.negated:
+            # log(a) + log(b)  ->  log(ab)
+            p.append(P(node, add_logarithms, (scope, log_a, log_b)))
+        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:
+            # 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:
+            # -log(a) + log(b)  ->  log(b / a)
+            p.append(P(node, subtract_logarithms, (scope, log_b, log_a)))
+
+    return p
+
+
+def add_logarithms(root, args):
+    """
+    log(a) + log(b)  ->  log(ab)
+    """
+    scope, log_a, log_b = args
+    a, base = log_a
+    b = log_b[0]
+
+    scope.replace(log_a, log(a * b, base=base))
+    scope.remove(log_b)
+
+    return scope.as_nary_node()
+
+
+MESSAGES[add_logarithms] = _('Apply log(a) + log(b) = log(ab).')
+        #_('Combine two logarithms with the same base: {2} and {3}.')
+
+
+def expand_negations(root, args):
+    """
+    -log(a) - log(b)  ->  -(log(a) + log(b))  # ->  -log(ab)
+    """
+    scope, log_a, log_b = args
+
+    scope.replace(log_a, -(+log_a + +log_b))
+    scope.remove(log_b)
+
+    return scope.as_nary_node()
+
+
+MESSAGES[expand_negations] = \
+        _('Apply -log(a) - log(b) = -(log(a) + log(b)).')
+
+
+def subtract_logarithms(root, args):
+    """
+    log(a) - log(b)  ->  log(a / b)
+    """
+    scope, log_a, log_b = args
+    a, base = log_a
+    b = log_b[0]
+
+    scope.replace(log_a, log(a / b, base=base))
+    scope.remove(log_b)
+
+    return scope.as_nary_node()
+
+
+MESSAGES[subtract_logarithms] = _('Apply log(a) - log(b) = log(a / b).')

+ 47 - 0
tests/test_rules_logarithmic.py

@@ -0,0 +1,47 @@
+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
+from src.node import Scope
+from src.possibilities import Possibility as P
+from tests.rulestestcase import RulesTestCase, tree
+
+
+class TestRulesLogarithmic(RulesTestCase):
+
+    def test_match_constant_logarithm(self):
+        self.assertRaises(ValueError, tree, 'log_1(a)')
+
+        root = tree('log 1')
+        self.assertEqualPos(match_constant_logarithm(root),
+                [P(root, logarithm_of_one)])
+
+        root = tree('log 10')
+        self.assertEqualPos(match_constant_logarithm(root),
+                [P(root, divide_same_base)])
+
+        root = tree('log(a, a)')
+        self.assertEqualPos(match_constant_logarithm(root),
+                [P(root, divide_same_base)])
+
+    def test_match_add_logarithms(self):
+        log_a, log_b = root = tree('log a + log b')
+        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')
+        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')
+        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')
+        self.assertEqualPos(match_add_logarithms(root),
+                [P(root, subtract_logarithms, (Scope(root), log_b, log_a))])
+
+    def test_add_logarithms(self):
+        root, a, b = tree('log a + log b, a, b')
+        log_a, log_b = root
+        self.assertEqual(add_logarithms(root, (Scope(root), log_a, log_b)),
+                         log(a * b))