Commit 5cf274a2 authored by Taddeus Kroes's avatar Taddeus Kroes

Implemented sum rule, product rule and quotient rule.

parent 0f16cb2c
...@@ -20,7 +20,7 @@ from .goniometry import match_add_quadrants, match_negated_parameter, \ ...@@ -20,7 +20,7 @@ from .goniometry import match_add_quadrants, match_negated_parameter, \
from src.rules.derivatives import match_zero_derivative, \ from src.rules.derivatives import match_zero_derivative, \
match_one_derivative, match_variable_power, \ match_one_derivative, match_variable_power, \
match_const_deriv_multiplication, match_logarithmic, \ match_const_deriv_multiplication, match_logarithmic, \
match_goniometric match_goniometric, match_sum_product_rule, match_quotient_rule
RULES = { RULES = {
OP_ADD: [match_add_numerics, match_add_constant_fractions, OP_ADD: [match_add_numerics, match_add_constant_fractions,
...@@ -44,5 +44,6 @@ RULES = { ...@@ -44,5 +44,6 @@ RULES = {
OP_TAN: [match_standard_radian], OP_TAN: [match_standard_radian],
OP_DER: [match_zero_derivative, match_one_derivative, OP_DER: [match_zero_derivative, match_one_derivative,
match_variable_power, match_const_deriv_multiplication, match_variable_power, match_const_deriv_multiplication,
match_logarithmic, match_goniometric], match_logarithmic, match_goniometric, match_sum_product_rule,
match_quotient_rule],
} }
...@@ -4,7 +4,7 @@ from .utils import find_variables ...@@ -4,7 +4,7 @@ from .utils import find_variables
from .logarithmic import ln from .logarithmic import ln
from .goniometry import sin, cos from .goniometry import sin, cos
from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_DER, \ 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 ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -13,6 +13,13 @@ def der(f, x=None): ...@@ -13,6 +13,13 @@ def der(f, x=None):
return N('der', f, x) if x else N('der', f) 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): def get_derivation_variable(node, variables=None):
""" """
Find the variable to derive over. Find the variable to derive over.
...@@ -70,6 +77,17 @@ def match_zero_derivative(node): ...@@ -70,6 +77,17 @@ def match_zero_derivative(node):
return [] 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): def match_one_derivative(node):
""" """
der(x) -> 1 # Implicit x der(x) -> 1 # Implicit x
...@@ -96,17 +114,6 @@ def one_derivative(root, args): ...@@ -96,17 +114,6 @@ def one_derivative(root, args):
MESSAGES[one_derivative] = _('Variable {0[0]} has derivative 1.') 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): def match_const_deriv_multiplication(node):
""" """
der(c * f(x), x) -> c * der(f(x), x) der(c * f(x), x) -> c * der(f(x), x)
...@@ -293,11 +300,105 @@ def tangens(root, args): ...@@ -293,11 +300,105 @@ def tangens(root, args):
""" """
der(tan(x), x) -> der(sin(x) / cos(x), x) der(tan(x), x) -> der(sin(x) / cos(x), x)
""" """
f = root[0][0] x = root[0][0]
x = root[1] if len(root) > 1 else None
return der(sin(f) / cos(f), x) return der(sin(x) / cos(x), second_arg(root))
MESSAGES[tangens] = \ MESSAGES[tangens] = \
_('Convert the tanges to a division and apply the product rule.') _('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}.')
...@@ -3,7 +3,9 @@ from src.rules.derivatives import der, get_derivation_variable, \ ...@@ -3,7 +3,9 @@ from src.rules.derivatives import der, get_derivation_variable, \
zero_derivative, match_variable_power, variable_root, \ zero_derivative, match_variable_power, variable_root, \
variable_exponent, match_const_deriv_multiplication, \ variable_exponent, match_const_deriv_multiplication, \
const_deriv_multiplication, chain_rule, match_logarithmic, \ 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.logarithmic import ln
from src.rules.goniometry import sin, cos from src.rules.goniometry import sin, cos
from src.node import Scope from src.node import Scope
...@@ -158,3 +160,71 @@ class TestRulesDerivatives(RulesTestCase): ...@@ -158,3 +160,71 @@ class TestRulesDerivatives(RulesTestCase):
root = tree('der(tan(x))') root = tree('der(tan(x))')
self.assertEqual(tangens(root, ()), der(sin(x) / cos(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)
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