Răsfoiți Sursa

Fixed some integral rules using negation and added a power rule:

- Added a power rule that factors out negation from a raised root.
- Reduction of "int_(-r)^(r) pi (r^2 - x^2) dx" is now supported.
Taddeus Kroes 13 ani în urmă
părinte
comite
e3a6479568

+ 2 - 2
src/rules/__init__.py

@@ -19,7 +19,7 @@ from .groups import match_combine_groups
 from .factors import match_expand
 from .powers import match_add_exponents, match_subtract_exponents, \
         match_multiply_exponents, match_duplicate_exponent, \
-        match_raised_fraction, match_remove_negative_exponent, \
+        match_raised_fraction, match_remove_negative_child, \
         match_exponent_to_root, match_extend_exponent, match_constant_exponent
 from .numerics import match_add_numerics, match_divide_numerics, \
         match_multiply_numerics, match_raise_numerics
@@ -59,7 +59,7 @@ RULES = {
                  match_negated_division, match_extract_fraction_terms,
                  match_division_in_denominator],
         OP_POW: [match_multiply_exponents, match_duplicate_exponent,
-                 match_raised_fraction, match_remove_negative_exponent,
+                 match_raised_fraction, match_remove_negative_child,
                  match_exponent_to_root, match_extend_exponent,
                  match_constant_exponent, match_raise_numerics,
                  match_raised_base],

+ 10 - 10
src/rules/integrals.py

@@ -15,7 +15,7 @@
 from .utils import find_variables, substitute, find_variable
 from ..node import ExpressionLeaf as L, OP_INT, OP_INT_INDEF, OP_MUL, OP_DIV, \
         OP_LOG, OP_SIN, OP_COS, Scope, sin, cos, ln, integral, indef, \
-        absolute, OP_ADD
+        absolute, OP_ADD, negate
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -188,7 +188,7 @@ def match_factor_out_constant(node):
     fx, x = node[:2]
 
     if fx.negated:
-        return [P(node, split_negation_to_constant)]
+        return [P(node, factor_out_integral_negation)]
 
     if not fx.is_op(OP_MUL):
         return []
@@ -203,15 +203,15 @@ def match_factor_out_constant(node):
     return p
 
 
-def split_negation_to_constant(root, args):
+def factor_out_integral_negation(root, args):
     """
     int -f(x) dx  ->  int -1 * f(x) dx  # =>*  -int f(x) dx
     """
-    return integral(-L(1) * root[0].reduce_negation(), *root[1:])
+    return -integral(root[0].reduce_negation(), *root[1:])
 
 
-MESSAGES[split_negation_to_constant] = _('Write the negation of {0[0]} as an' \
-        ' explicit `-1` and bring it outside of the integral.')
+MESSAGES[factor_out_integral_negation] = \
+        _('Bring the negation of {0[0]} outside of the integral.')
 
 
 def factor_out_constant(root, args):
@@ -221,7 +221,7 @@ def factor_out_constant(root, args):
     scope, c = args
     scope.remove(c)
 
-    return c * integral(scope.as_nary_node(), *root[1:])
+    return negate(c * integral(scope.as_nary_node(), *root[1:]), root.negated)
 
 
 MESSAGES[factor_out_constant] = _('Factor out {2} from integral {0}.')
@@ -351,11 +351,11 @@ def sum_rule_integral(root, args):
     int f(x) + g(x) dx  ->  int f(x) dx + int g(x) dx
     """
     scope, f = args
-    x = root[1]
+    args = root[1:]
     scope.remove(f)
-    addition = integral(f, x) + integral(scope.as_nary_node(), x)
+    addition = integral(f, *args) + integral(scope.as_nary_node(), *args)
 
-    return addition.negate(root.negated)
+    return negate(addition, root.negated)
 
 
 MESSAGES[sum_rule_integral] = _('Apply the sum rule to {0}.')

+ 22 - 7
src/rules/powers.py

@@ -204,32 +204,47 @@ MESSAGES[raised_fraction] = _('Apply the exponent {2} to the nominator and'
         ' denominator of fraction {1}.')
 
 
-def match_remove_negative_exponent(node):
+def match_remove_negative_child(node):
     """
-    a ^ -p  ->  1 / a ^ p
+    a ^ -p                           ->  1 / a ^ p
+    (-a) ^ p and p is an odd number  ->  -a ^ p
     """
     assert node.is_op(OP_POW)
 
     a, p = node
+    pos = []
 
     if p.negated:
-        return [P(node, remove_negative_exponent, (a, p))]
+        pos.append(P(node, remove_negative_exponent))
 
-    return []
+    if a.negated and p.is_int() and p.value & 1:
+        pos.append(P(node, remove_negative_root))
+
+    return pos
 
 
 def remove_negative_exponent(root, args):
     """
-    a^-p  ->  1 / a^p
+    a ^ -p  ->  1 / a ^ p
     """
-    a, p = args
-
+    a, p = root
     return L(1) / a ** p.reduce_negation()
 
 
 MESSAGES[remove_negative_exponent] = _('Remove negative exponent {2}.')
 
 
+def remove_negative_root(root, args):
+    """
+    (-a) ^ p and p is an odd number  ->  -a ^ p
+    """
+    a, p = root
+    return -(a.reduce_negation() ** p)
+
+
+MESSAGES[remove_negative_root] = _('Remove negative exponent {2}.')
+
+
 def match_exponent_to_root(node):
     """
     a^(1 / m)  ->  sqrt(a, m)

+ 5 - 5
tests/test_rules_integrals.py

@@ -16,7 +16,7 @@ from src.rules.integrals import indef, choose_constant, solve_integral, \
         match_solve_indef, solve_indef, match_integrate_variable_power, \
         integrate_variable_root, integrate_variable_exponent, \
         match_constant_integral, constant_integral, single_variable_integral, \
-        match_factor_out_constant, split_negation_to_constant, \
+        match_factor_out_constant, factor_out_integral_negation, \
         factor_out_constant, match_division_integral, division_integral, \
         extend_division_integral, match_function_integral, \
         logarithm_integral, sinus_integral, cosinus_integral, \
@@ -111,11 +111,11 @@ class TestRulesIntegrals(RulesTestCase):
 
         root = tree('int -x2 dx')
         self.assertEqualPos(match_factor_out_constant(root),
-                [P(root, split_negation_to_constant)])
+                [P(root, factor_out_integral_negation)])
 
-    def test_split_negation_to_constant(self):
-        root, expect = tree('int -x ^ 2 dx, int (-1)x ^ 2 dx')
-        self.assertEqual(split_negation_to_constant(root, ()), expect)
+    def test_factor_out_integral_negation(self):
+        root, expect = tree('int -x ^ 2 dx, -int x ^ 2 dx')
+        self.assertEqual(factor_out_integral_negation(root, ()), expect)
 
     def test_factor_out_constant(self):
         root, expect = tree('int cx dx, c int x dx')

+ 25 - 10
tests/test_rules_powers.py

@@ -17,8 +17,8 @@ from src.rules.powers import match_add_exponents, add_exponents, \
         match_multiply_exponents, multiply_exponents, \
         match_duplicate_exponent, duplicate_exponent, \
         match_raised_fraction, raised_fraction, \
-        match_remove_negative_exponent, remove_negative_exponent, \
-        match_exponent_to_root, exponent_to_root, \
+        match_remove_negative_child, remove_negative_exponent, \
+        remove_negative_root, match_exponent_to_root, exponent_to_root, \
         match_constant_exponent, remove_power_of_zero, remove_power_of_one
 from src.node import Scope, ExpressionNode as N
 from src.possibilities import Possibility as P
@@ -122,13 +122,22 @@ class TestRulesPowers(RulesTestCase):
 
         self.assertEqual(raised_fraction(root, (ab, p)), a ** p / b ** p)
 
-    def test_match_remove_negative_exponent(self):
-        a, p = tree('a,p')
-        root = a ** -p
+    def test_match_remove_negative_child(self):
+        root = tree('a ^ -p')
+        self.assertEqualPos(match_remove_negative_child(root),
+                [P(root, remove_negative_exponent)])
 
-        possibilities = match_remove_negative_exponent(root)
-        self.assertEqualPos(possibilities,
-                [P(root, remove_negative_exponent, (a, -p))])
+        root = tree('(-a) ^ 3')
+        self.assertEqualPos(match_remove_negative_child(root),
+                [P(root, remove_negative_root)])
+
+        root = tree('(-a) ^ 2')
+        self.assertEqualPos(match_remove_negative_child(root), [])
+
+        root = tree('(-a) ^ -3')
+        self.assertEqualPos(match_remove_negative_child(root),
+                [P(root, remove_negative_exponent),
+                 P(root, remove_negative_root)])
 
     def test_match_exponent_to_root(self):
         a, n, m, l1 = tree('a,n,m,1')
@@ -178,10 +187,16 @@ class TestRulesPowers(RulesTestCase):
     def test_remove_negative_exponent(self):
         a, p, l1 = tree('a,-p,1')
         root = a ** p
-
-        self.assertEqualNodes(remove_negative_exponent(root, (a, p)),
+        self.assertEqualNodes(remove_negative_exponent(root, ()),
                               l1 / a ** +p)
 
+    def test_remove_negative_root(self):
+        root, expect = tree('(-a) ^ 3, -a ^ 3')
+        self.assertEqualNodes(remove_negative_root(root, ()), expect)
+
+        root, expect = tree('(-a) ^ -3, -a ^ -3')
+        self.assertEqualNodes(remove_negative_root(root, ()), expect)
+
     def test_exponent_to_root(self):
         a, n, m, l1 = tree('a,n,m,1')
         root = a ** (n / m)

+ 4 - 0
tests/test_validation.py

@@ -40,3 +40,7 @@ class TestValidation(TestCase):
 
     #def test_advanced_failure(self):
     #    self.assertFalse(validate('(x-1)^3+(x-1)^3', '4a+4b'))
+
+    def test_sphere_volume(self):
+        self.assertTrue(validate('int_(-r)^(r) pi * (r^2 - x^2) dx',
+                                 '4 / 3 * pi * r ^ 3'))