Эх сурвалжийг харах

Added rules that calculate logarithm exponents to see if a logarithm can be reduced to a number.

Taddeus Kroes 14 жил өмнө
parent
commit
76db99131f

+ 36 - 4
src/rules/logarithmic.py

@@ -1,4 +1,5 @@
 from itertools import combinations, product, ifilterfalse
+import math
 
 from .utils import find_variables, partition, divides, is_numeric_node
 from ..node import ExpressionLeaf as L, OP_LOG, OP_ADD, OP_MUL, OP_POW, \
@@ -219,18 +220,29 @@ 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)
+    log(b, a) and a ** y = b with y in Z  ->  log(a ^ y, a)  # =>* y
     """
     assert node.is_op(OP_LOG)
 
     p = []
+    exp, base = node
 
-    if node[0].is_power():
-        a, b = node[0]
+    if exp.is_power():
+        a, b = exp
 
         if b.negated:
             p.append(P(node, split_negative_exponent))
 
-        p.append(P(node, factor_out_exponent))
+        if a == base:
+            p.append(P(node, factor_out_exponent_important))
+        else:
+            p.append(P(node, factor_out_exponent))
+    elif exp.is_numeric() and not exp.negated:
+        b, a = exp.value, base.value
+        y = int(round(math.log(b, a)))
+
+        if b == a ** y:
+            p.append(P(node, make_raised_base, (y,)))
 
     return p
 
@@ -257,7 +269,27 @@ def factor_out_exponent(root, args):
     return b * log(a, base=base)
 
 
-MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][0]} from {0}.')
+MESSAGES[factor_out_exponent] = _('Factor out exponent {0[0][1]} from {0}.')
+
+
+def factor_out_exponent_important(root, args):
+    return factor_out_exponent(root, args)
+
+
+MESSAGES[factor_out_exponent_important] = MESSAGES[factor_out_exponent]
+
+
+def make_raised_base(root, args):
+    """
+    log(b, a) and b ** y = a with y in Z  ->  log(a ^ y, a)  # =>* y
+    """
+    exp, base = root
+    y = L(args[0])
+
+    return log(base.clone() ** y, base=base).negate(root.negated)
+
+
+MESSAGES[make_raised_base] = _('Write {0[0]} as a power of {0[1]}.')
 
 
 def match_factor_in_multiplicant(node):

+ 4 - 2
src/rules/precedences.py

@@ -1,8 +1,8 @@
 from .factors import expand_double, expand_single
 from .sort import move_constant
-from .numerics import reduce_fraction_constants
+from .numerics import reduce_fraction_constants, raise_numerics
 from .logarithmic import factor_in_exponent_multiplicant, \
-        factor_out_exponent, raised_base
+        factor_out_exponent, raised_base, factor_out_exponent_important
 from .derivatives import chain_rule
 from .negation import double_negation, negated_factor, negated_nominator, \
         negated_denominator
@@ -35,6 +35,8 @@ RELATIVE = [
 
         # Expand 'single' before 'double' to avoid unnessecary complexity
         (expand_single, expand_double),
+
+        (factor_out_exponent_important, raise_numerics),
         ]
 
 

+ 34 - 1
tests/test_rules_logarithmic.py

@@ -6,7 +6,8 @@ from src.rules.logarithmic import log, match_constant_logarithm, \
         factor_out_exponent, match_factor_in_multiplicant, \
         factor_in_multiplicant, match_expand_terms, \
         expand_multiplication_terms, expand_division_terms, \
-        factor_in_exponent_multiplicant
+        factor_in_exponent_multiplicant, factor_out_exponent_important, \
+        make_raised_base
 from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -140,6 +141,27 @@ class TestRulesLogarithmic(RulesTestCase):
                 [P(root, split_negative_exponent),
                  P(root, factor_out_exponent)])
 
+    def test_match_factor_out_exponent_important(self):
+        root = tree('log(10 ^ 2)')
+        self.assertEqualPos(match_factor_out_exponent(root),
+                [P(root, factor_out_exponent_important)])
+
+    def test_match_factor_out_exponent_make_raised_base(self):
+        root = tree('log(100)')
+        self.assertEqualPos(match_factor_out_exponent(root),
+                [P(root, make_raised_base, (2,))])
+
+        root = tree('log(1000)')
+        self.assertEqualPos(match_factor_out_exponent(root),
+                [P(root, make_raised_base, (3,))])
+
+        root = tree('log_2(16)')
+        self.assertEqualPos(match_factor_out_exponent(root),
+                [P(root, make_raised_base, (4,))])
+
+        root = tree('log(99)')
+        self.assertEqualPos(match_factor_out_exponent(root), [])
+
     def test_split_negative_exponent(self):
         root, expect = tree('log(a ^ -b), log((a ^ b) ^ -1)')
         self.assertEqual(split_negative_exponent(root, ()), expect)
@@ -148,6 +170,17 @@ class TestRulesLogarithmic(RulesTestCase):
         ((a, l2), l10) = root = tree('log(a ^ 2)')
         self.assertEqual(factor_out_exponent(root, ()), l2 * log(a))
 
+    def test_make_raised_base(self):
+        root, expect = tree('log(1000), log(10 ^ 3)')
+        self.assertEqual(make_raised_base(root, (3,)), expect)
+
+        root, expect = tree('log_2(64), log_2(2 ^ 4)')
+        self.assertEqual(make_raised_base(root, (4,)), expect)
+
+    def test_factor_out_exponent_important(self):
+        ((a, l2), l10) = root = tree('log(10 ^ 2)')
+        self.assertEqual(factor_out_exponent_important(root, ()), l2 * log(a))
+
     def test_match_factor_in_multiplicant(self):
         (l2, log_3) = root = tree('2log(3)')
         self.assertEqualPos(match_factor_in_multiplicant(root),