소스 검색

Implemented sum rule, product rule and quotient rule.

Taddeus Kroes 14 년 전
부모
커밋
5cf274a2a5
3개의 변경된 파일190개의 추가작업 그리고 18개의 파일을 삭제
  1. 3 2
      src/rules/__init__.py
  2. 116 15
      src/rules/derivatives.py
  3. 71 1
      tests/test_rules_derivatives.py

+ 3 - 2
src/rules/__init__.py

@@ -20,7 +20,7 @@ from .goniometry import match_add_quadrants, match_negated_parameter, \
 from src.rules.derivatives import match_zero_derivative, \
         match_one_derivative, match_variable_power, \
         match_const_deriv_multiplication, match_logarithmic, \
-        match_goniometric
+        match_goniometric, match_sum_product_rule, match_quotient_rule
 
 RULES = {
         OP_ADD: [match_add_numerics, match_add_constant_fractions,
@@ -44,5 +44,6 @@ RULES = {
         OP_TAN: [match_standard_radian],
         OP_DER: [match_zero_derivative, match_one_derivative,
                  match_variable_power, match_const_deriv_multiplication,
-                 match_logarithmic, match_goniometric],
+                 match_logarithmic, match_goniometric, match_sum_product_rule,
+                 match_quotient_rule],
         }

+ 116 - 15
src/rules/derivatives.py

@@ -4,7 +4,7 @@ from .utils import find_variables
 from .logarithmic import ln
 from .goniometry import sin, cos
 from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DER, \
-        OP_MUL, OP_LOG, OP_SIN, OP_COS, OP_TAN
+        OP_MUL, OP_LOG, OP_SIN, OP_COS, OP_TAN, OP_ADD, OP_DIV
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -13,6 +13,13 @@ def der(f, x=None):
     return N('der', f, x) if x else N('der', f)
 
 
+def second_arg(node):
+    """
+    Get the second child of a node if it exists, None otherwise.
+    """
+    return node[1] if len(node) > 1 else None
+
+
 def get_derivation_variable(node, variables=None):
     """
     Find the variable to derive over.
@@ -70,6 +77,17 @@ def match_zero_derivative(node):
     return []
 
 
+def zero_derivative(root, args):
+    """
+    der(x, y)  ->  0
+    der(n)     ->  0
+    """
+    return L(0)
+
+
+MESSAGES[zero_derivative] = _('Constant {0[0]} has derivative 0.')
+
+
 def match_one_derivative(node):
     """
     der(x)     ->  1  # Implicit x
@@ -96,17 +114,6 @@ def one_derivative(root, args):
 MESSAGES[one_derivative] = _('Variable {0[0]} has derivative 1.')
 
 
-def zero_derivative(root, args):
-    """
-    der(x, y)  ->  0
-    der(n)     ->  0
-    """
-    return L(0)
-
-
-MESSAGES[zero_derivative] = _('Constant {0[0]} has derivative 0.')
-
-
 def match_const_deriv_multiplication(node):
     """
     der(c * f(x), x)  ->  c * der(f(x), x)
@@ -293,11 +300,105 @@ def tangens(root, args):
     """
     der(tan(x), x)  ->  der(sin(x) / cos(x), x)
     """
-    f = root[0][0]
-    x = root[1] if len(root) > 1 else None
+    x = root[0][0]
 
-    return der(sin(f) / cos(f), x)
+    return der(sin(x) / cos(x), second_arg(root))
 
 
 MESSAGES[tangens] = \
         _('Convert the tanges to a division and apply the product rule.')
+
+
+def match_sum_product_rule(node):
+    """
+    [f(x) + g(x)]'  ->  f'(x) + g'(x)
+    [f(x) * g(x)]'  ->  f'(x) * g(x) + f(x) * g'(x)
+    """
+    assert node.is_op(OP_DER)
+
+    x = get_derivation_variable(node)
+
+    if not x or node[0].is_leaf or node[0].op not in (OP_ADD, OP_MUL):
+        return []
+
+    scope = Scope(node[0])
+    x = L(x)
+    functions = [n for n in scope if n.contains(x)]
+
+    if len(functions) < 2:
+        return []
+
+    p = []
+    handler = sum_rule if node[0].op == OP_ADD else product_rule
+
+    for f in functions:
+        p.append(P(node, handler, (scope, f)))
+
+    return p
+
+
+def sum_rule(root, args):
+    """
+    [f(x) + g(x)]'  ->  f'(x) + g'(x)
+    """
+    scope, f = args
+    x = second_arg(root)
+
+    scope.remove(f)
+
+    return der(f, x) + der(scope.as_nary_node(), x)
+
+
+MESSAGES[sum_rule] = _('Apply the sum rule to {0}.')
+
+
+def product_rule(root, args):
+    """
+    [f(x) * g(x)]'  ->  f'(x) * g(x) + f(x) * g'(x)
+
+    Note that implicitely:
+    [f(x) * g(x) * h(x)]'  ->  f'(x) * (g(x) * h(x)) + f(x) * [g(x) * h(x)]'
+    """
+    scope, f = args
+    x = second_arg(root)
+
+    scope.remove(f)
+    gh = scope.as_nary_node()
+
+    return der(f, x) * gh + f * der(gh, x)
+
+
+MESSAGES[product_rule] = _('Apply the product rule to {0}.')
+
+
+def match_quotient_rule(node):
+    """
+    [f(x) / g(x)]'  ->  (f'(x) * g(x) - f(x) * g'(x)) / g(x) ^ 2
+    """
+    assert node.is_op(OP_DER)
+
+    x = get_derivation_variable(node)
+
+    if not x or not node[0].is_op(OP_DIV):
+        return []
+
+    f, g = node[0]
+    x = L(x)
+
+    if f.contains(x) and g.contains(x):
+        return [P(node, quotient_rule)]
+
+    return []
+
+
+def quotient_rule(root, args):
+    """
+    [f(x) / g(x)]'  ->  (f'(x) * g(x) - f(x) * g'(x)) / g(x) ^ 2
+    """
+    f, g = root[0]
+    x = second_arg(root)
+
+    return (der(f, x) * g - f * der(g, x)) / g ** 2
+
+
+MESSAGES[quotient_rule] = _('Apply the quotient rule to {0}.')

+ 71 - 1
tests/test_rules_derivatives.py

@@ -3,7 +3,9 @@ from src.rules.derivatives import der, get_derivation_variable, \
         zero_derivative, match_variable_power, variable_root, \
         variable_exponent, match_const_deriv_multiplication, \
         const_deriv_multiplication, chain_rule, match_logarithmic, \
-        logarithmic, match_goniometric, sinus, cosinus, tangens
+        logarithmic, match_goniometric, sinus, cosinus, tangens, \
+        match_sum_product_rule, sum_rule, product_rule, match_quotient_rule, \
+        quotient_rule
 from src.rules.logarithmic import ln
 from src.rules.goniometry import sin, cos
 from src.node import Scope
@@ -158,3 +160,71 @@ class TestRulesDerivatives(RulesTestCase):
 
         root = tree('der(tan(x))')
         self.assertEqual(tangens(root, ()), der(sin(x) / cos(x)))
+
+    def test_match_sum_product_rule_sum(self):
+        root = tree('der(x ^ 2 + x)')
+        x2, x = f = root[0]
+        self.assertEqualPos(match_sum_product_rule(root),
+                [P(root, sum_rule, (Scope(f), x2)),
+                 P(root, sum_rule, (Scope(f), x))])
+
+        root = tree('der(x ^ 2 + 3 + x)')
+        self.assertEqualPos(match_sum_product_rule(root),
+                [P(root, sum_rule, (Scope(root[0]), x2)),
+                 P(root, sum_rule, (Scope(root[0]), x))])
+
+    def test_match_sum_product_rule_product(self):
+        root = tree('der(x ^ 2 * x)')
+        x2, x = f = root[0]
+        self.assertEqualPos(match_sum_product_rule(root),
+                [P(root, product_rule, (Scope(f), x2)),
+                 P(root, product_rule, (Scope(f), x))])
+
+    def test_match_sum_product_rule_none(self):
+        root = tree('der(x ^ 2 + 2)')
+        self.assertEqualPos(match_sum_product_rule(root), [])
+
+        root = tree('der(x ^ 2 * 2)')
+        self.assertEqualPos(match_sum_product_rule(root), [])
+
+    def test_sum_rule(self):
+        root = tree('der(x ^ 2 + x)')
+        x2, x = f = root[0]
+        self.assertEqual(sum_rule(root, (Scope(f), x2)), der(x2) + der(x))
+        self.assertEqual(sum_rule(root, (Scope(f), x)), der(x) + der(x2))
+
+        root = tree('der(x ^ 2 + 3 + x)')
+        (x2, l3), x = f = root[0]
+        self.assertEqual(sum_rule(root, (Scope(f), x2)), der(x2) + der(l3 + x))
+        self.assertEqual(sum_rule(root, (Scope(f), x)), der(x) + der(x2 + l3))
+
+    def test_product_rule(self):
+        root = tree('der(x ^ 2 * x)')
+        x2, x = f = root[0]
+        self.assertEqual(product_rule(root, (Scope(f), x2)),
+                         der(x2) * x + x2 * der(x))
+        self.assertEqual(product_rule(root, (Scope(f), x)),
+                         der(x) * x2 + x * der(x2))
+
+        root = tree('der(x ^ 2 * x * x ^ 3)')
+        (x2, x), x3 = f = root[0]
+        self.assertEqual(product_rule(root, (Scope(f), x2)),
+                         der(x2) * (x * x3) + x2 * der(x * x3))
+        self.assertEqual(product_rule(root, (Scope(f), x)),
+                         der(x) * (x2 * x3) + x * der(x2 * x3))
+        self.assertEqual(product_rule(root, (Scope(f), x3)),
+                         der(x3) * (x2 * x) + x3 * der(x2 * x))
+
+    def test_match_quotient_rule(self):
+        root = tree('der(x ^ 2 / x)')
+        self.assertEqualPos(match_quotient_rule(root),
+                [P(root, quotient_rule)])
+
+        root = tree('der(x ^ 2 / 2)')
+        self.assertEqualPos(match_quotient_rule(root), [])
+
+    def test_quotient_rule(self):
+        root = tree('der(x ^ 2 / x)')
+        f, g = root[0]
+        self.assertEqual(quotient_rule(root, ()),
+                         (der(f) * g - f * der(g)) / g ** 2)