Parcourir la source

Added the basics for integral rewrite rules.

Taddeus Kroes il y a 14 ans
Parent
commit
47c00a9117
2 fichiers modifiés avec 161 ajouts et 0 suppressions
  1. 116 0
      src/rules/integrals.py
  2. 45 0
      tests/test_rules_integrals.py

+ 116 - 0
src/rules/integrals.py

@@ -0,0 +1,116 @@
+from .utils import find_variables, first_sorted_variable, infinity, \
+        replace_variable
+from .logarithmic import ln
+#from .goniometry import sin, cos
+from ..node import ExpressionLeaf as L, OP_INT
+from ..possibilities import Possibility as P, MESSAGES
+from ..translate import _
+
+
+#def ader(f, x=None):
+#    """
+#    Anti-derivative.
+#    """
+#    return N(OP_INT, f, x)
+
+
+def integral_params(integral):
+    """
+    Get integral parameters:
+    - If f(x) and x are both specified, return them.
+    - If only f(x) is specified, find x.
+    """
+    if len(integral) > 1:
+        assert integral[1].is_identifier()
+        return tuple(integral[:2])
+
+    f = integral[0]
+    variables = find_variables(integral)
+
+    if not len(variables):
+        return f, None
+
+    return f, L(first_sorted_variable(variables))
+
+
+def choose_constant(integral):
+    """
+    Choose a constant to be added to the antiderivative.
+    """
+    occupied = find_variables(integral)
+    c = 'c'
+    i = 96
+
+    while c in occupied:
+        i += 2 if i == 98 else 1
+        c = chr(i)
+
+    return L(c)
+
+
+def solve_integral(integral, F):
+    """
+    Solve an integral given its anti-derivative F:
+    - First, finish the anti-derivative by adding a constant.
+    - If no bounds are specified, return the anti-derivative.
+    - If only a lower bound is specified, set the upper bound to infinity.
+    - Given a lower bound a and upper bound b, the solution is F(b) - F(a).
+    """
+    F += choose_constant(integral)
+
+    if len(integral) < 3:
+        return F
+
+    x = integral[1]
+    lower = integral[2]
+    upper = infinity() if len(integral) < 4 else integral[3]
+
+    # TODO: add notation [F(x)]_a^b
+    return replace_variable(F, x, lower) - replace_variable(F, x, upper)
+
+
+def match_integrate_variable_power(node):
+    """
+    int(x ^ n, x)  ->  x ^ (n + 1) / (n + 1) + c
+    int(g ^ x, x)  ->  g ^ x / ln(g)
+    """
+    assert node.is_op(OP_INT)
+
+    f, x = integral_params(node)
+
+    if f.is_power():
+        root, exponent = f
+
+        if root == x and not exponent.contains(x):
+            return [P(node, integrate_variable_root)]
+
+        if exponent == x and not root.contains(x):
+            return [P(node, integrate_variable_exponent)]
+
+    return []
+
+
+def integrate_variable_root(root, args):
+    """
+    int(x ^ n, x)  ->  x ^ (n + 1) / (n + 1) + c
+    """
+    x, n = root[0]
+
+    return solve_integral(root, x ** (n + 1) / (n + 1))
+
+
+MESSAGES[integrate_variable_root] = \
+        _('Apply standard integral int(x ^ n) = x ^ (n + 1) / (n + 1) + c.')
+
+
+def integrate_variable_exponent(root, args):
+    """
+    int(g ^ x, x)  ->  g ^ x / ln(g)
+    """
+    g, x = root[0]
+
+    return solve_integral(root, g ** x / ln(g))
+
+
+MESSAGES[integrate_variable_exponent] = \
+        _('Apply standard integral int(g ^ x) = g ^ x / ln(g) + c.')

+ 45 - 0
tests/test_rules_integrals.py

@@ -0,0 +1,45 @@
+from src.rules.integrals import integral_params, choose_constant, \
+        match_integrate_variable_power, integrate_variable_root, \
+        integrate_variable_exponent
+from src.rules.logarithmic import ln
+#from .goniometry import sin, cos
+from src.possibilities import Possibility as P
+from tests.rulestestcase import RulesTestCase, tree
+
+
+class TestRulesIntegrals(RulesTestCase):
+
+    def test_integral_params(self):
+        f, x = root = tree('int(fx, x)')
+        self.assertEqual(integral_params(root), (f, x))
+
+        root = tree('int(fx)')
+        self.assertEqual(integral_params(root), (f, x))
+
+        root = tree('int(3)')
+        self.assertEqual(integral_params(root), (3, None))
+
+    def test_choose_constant(self):
+        a, b, c = tree('a, b, c')
+        self.assertEqual(choose_constant(tree('int(x ^ n, x)')), c)
+        self.assertEqual(choose_constant(tree('int(x ^ c, x)')), a)
+        self.assertEqual(choose_constant(tree('int(a ^ c, a)')), b)
+
+    def test_match_integrate_variable_power(self):
+        for root in tree('int(x ^ n, x), int(x ^ n)'):
+            self.assertEqualPos(match_integrate_variable_power(root),
+                    [P(root, integrate_variable_root)])
+
+        for root in tree('int(g ^ x, x), int(g ^ x)'):
+            self.assertEqualPos(match_integrate_variable_power(root),
+                    [P(root, integrate_variable_exponent)])
+
+    def test_integrate_variable_root(self):
+        ((x, n),), c = root, c = tree('int(x ^ n), c')
+        self.assertEqual(integrate_variable_root(root, ()),
+                         x ** (n + 1) / (n + 1) + c)
+
+    def test_integrate_variable_exponent(self):
+        ((g, x),), c = root, c = tree('int(g ^ x), c')
+        self.assertEqual(integrate_variable_exponent(root, ()),
+                         g ** x / ln(g) + c)