Browse Source

Implemented substitution for solving sets of linear equations..

Taddeus Kroes 14 years ago
parent
commit
71bb582369
5 changed files with 78 additions and 11 deletions
  1. 3 2
      src/parser.py
  2. 4 2
      src/rules/__init__.py
  3. 48 2
      src/rules/lineq.py
  4. 8 4
      src/rules/precedences.py
  5. 15 1
      tests/test_rules_lineq.py

+ 3 - 2
src/parser.py

@@ -294,8 +294,9 @@ class Parser(BisonParser):
             while self.possibilities:
                 # Find the first implicit possibliity in the list
                 # FIXME: Is it smart to apply a rule that is not a hint?
-                # ANSWER: Yes, but there must be an extra list that prevents
-                # deliberately generated implicit rules from being applied
+                # ANSWER: Yes, but there must be something like an extra list
+                # that prevents deliberately generated implicit rules from
+                # being applied
                 #sugg = self.possibilities[0]
 
                 #if sugg.handler not in IMPLICIT_RULES:

+ 4 - 2
src/rules/__init__.py

@@ -1,5 +1,6 @@
 from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS, \
-        OP_TAN, OP_DER, OP_LOG, OP_INT, OP_INT_INDEF, OP_EQ, OP_ABS, OP_SQRT
+        OP_TAN, OP_DER, OP_LOG, OP_INT, OP_INT_INDEF, OP_EQ, OP_ABS, OP_SQRT, \
+        OP_AND
 from .groups import match_combine_groups
 from .factors import match_expand
 from .powers import match_add_exponents, match_subtract_exponents, \
@@ -27,7 +28,7 @@ from .integrals import match_solve_indef, match_constant_integral, \
         match_integrate_variable_power, match_factor_out_constant, \
         match_division_integral, match_function_integral, \
         match_sum_rule_integral, match_remove_indef_constant
-from .lineq import match_move_term
+from .lineq import match_move_term, match_multiple_equations
 from .absolute import match_factor_out_abs_term
 from .sqrt import match_reduce_sqrt
 
@@ -67,4 +68,5 @@ RULES = {
         OP_EQ: [match_move_term],
         OP_ABS: [match_factor_out_abs_term],
         OP_SQRT: [match_reduce_sqrt],
+        OP_AND: [match_multiple_equations],
         }

+ 48 - 2
src/rules/lineq.py

@@ -1,6 +1,8 @@
-from .utils import find_variable, evals_to_numeric
+from itertools import permutations
+
+from .utils import find_variable, evals_to_numeric, substitute
 from ..node import ExpressionLeaf as L, Scope, OP_EQ, OP_ADD, OP_MUL, OP_DIV, \
-        eq, OP_ABS
+        eq, OP_ABS, OP_AND
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -148,3 +150,47 @@ def split_absolute_equation(root, args):
 
 MESSAGES[split_absolute_equation] = _('Split absolute equation {0} into a ' \
                                       'negative and a positive equation.')
+
+
+def match_multiple_equations(node):
+    """
+    Multiple equations can be solved using substitution and/or elimination.
+
+    Substitution rule:
+    x = a ^^ f(x) = g(x)  ->  x = a ^^ f(a) = g(a)  # Substitute x with a
+    Substitution example:
+    x = ay + b ^^ cx + dy = e  ->  x = ay + b ^^ c(ay + b) + dy = e
+                             # =>*  x = eval(a * eval((e - bc) / (ca + b)) + b)
+                             #      ^^ y = eval((e - bc) / (ca + b))
+    """
+    assert node.is_op(OP_AND)
+
+    scope = Scope(node)
+    equations = filter(lambda exp: exp.is_op(OP_EQ), scope)
+    p = []
+
+    if len(equations) < 2:
+        return p
+
+    for eq0, eq1 in permutations(equations, 2):
+        x, subs = eq0
+
+        # Substitution rule
+        if x.is_variable() and eq1.contains(x):
+            p.append(P(node, substitute_variable, (scope, x, subs, eq1)))
+
+    return p
+
+
+def substitute_variable(root, args):
+    """
+    Substitution rule:
+    x = a ^^ f(x) = g(x)  ->  x = a ^^ f(a) = g(a)  # Substitute x with a
+    """
+    scope, x, subs, eq = args
+    scope.replace(eq, substitute(eq, x, subs))
+
+    return scope.as_nary_node()
+
+
+MESSAGES[substitute_variable] = _('Substitute {2} with {3} in {4}.')

+ 8 - 4
src/rules/precedences.py

@@ -12,6 +12,7 @@ from .fractions import multiply_with_fraction, extract_fraction_terms, \
 from .integrals import factor_out_constant, integrate_variable_root
 from .powers import remove_power_of_one
 from .sqrt import quadrant_sqrt, extract_sqrt_mult_priority
+from .lineq import substitute_variable, swap_sides
 
 
 # Functions to move to the beginning of the possibilities list. Pairs of within
@@ -39,7 +40,7 @@ LOW = [
 # higher priority than B. This list ignores occurences in the HIGH or LOW lists
 # above
 RELATIVE = [
-        # Precedences needed for 'power rule'
+        # Precedences needed for 'power rule' (derivative of an exponentiation)
         (chain_rule, raised_base),
         (raised_base, factor_out_exponent),
 
@@ -58,11 +59,15 @@ RELATIVE = [
         # root first
         (extract_sqrt_mult_priority, multiply_numerics),
 
-        # sqrt(2 ^ 2)  ->  2  # not sqrt 4
+        # sqrt(2 ^ 2)  ->  2  # rather than sqrt(4)
         (quadrant_sqrt, raise_numerics),
 
-        #
+        # Prevent cycles that are caused by multiplication reductions when
+        # splitting up fractions
         (extract_fraction_terms, multiply_numerics),
+
+        # Prevent useless swapping when solving multiple equations
+        (substitute_variable, swap_sides),
         ]
 
 
@@ -84,6 +89,5 @@ IMPLICIT_RULES = [
         remove_zero,
         remove_power_of_one,
         negated_factor,
-        multiply_numerics,
         add_numerics,
         ]

+ 15 - 1
tests/test_rules_lineq.py

@@ -1,5 +1,7 @@
 from src.rules.lineq import match_move_term, swap_sides, subtract_term, \
-        divide_term, multiply_term, split_absolute_equation
+        divide_term, multiply_term, split_absolute_equation, \
+        match_multiple_equations, substitute_variable
+from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
 
@@ -107,3 +109,15 @@ class TestRulesLineq(RulesTestCase):
             'x = -a * 1',
             'x = -a',
         ])
+
+    def test_match_multiple_equations(self):
+        eq0, eq1 = root = tree('x = 2 ^^ ay + x = 3')
+        x = eq0[0]
+        self.assertEqualPos(match_multiple_equations(root),
+                [P(root, substitute_variable, (Scope(root), x, 2, eq1))])
+
+    def test_substitute_variable(self):
+        root, expect = tree('x = 2 ^^ ay + x = 3, x = 2 ^^ ay + 2 = 3')
+        (x, l2), eq = root
+        self.assertEqual(substitute_variable(root, ((Scope(root), x, l2, eq))),
+                         expect)