Taddeus Kroes 14 лет назад
Родитель
Сommit
aa2d137d2d
3 измененных файлов с 108 добавлено и 11 удалено
  1. 8 4
      src/rules/__init__.py
  2. 63 4
      src/rules/goniometry.py
  3. 37 3
      tests/test_rules_goniometry.py

+ 8 - 4
src/rules/__init__.py

@@ -1,4 +1,5 @@
-from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS
+from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG, OP_SIN, OP_COS, \
+        OP_TAN
 from .groups import match_combine_groups
 from .factors import match_expand
 from .powers import match_add_exponents, match_subtract_exponents, \
@@ -13,7 +14,7 @@ from .negation import match_negated_factor, match_negate_polynome, \
         match_negated_division
 from .sort import match_sort_multiplicants
 from .goniometry import match_add_quadrants, match_negated_parameter, \
-        match_half_pi_subtraction
+        match_half_pi_subtraction, match_standard_radian
 
 RULES = {
         OP_ADD: [match_add_numerics, match_add_constant_fractions,
@@ -28,6 +29,9 @@ RULES = {
                  match_remove_negative_exponent, match_exponent_to_root,
                  match_extend_exponent, match_constant_exponent],
         OP_NEG: [match_negate_polynome],
-        OP_SIN: [match_negated_parameter, match_half_pi_subtraction],
-        OP_COS: [match_negated_parameter, match_half_pi_subtraction],
+        OP_SIN: [match_negated_parameter, match_half_pi_subtraction,
+                 match_standard_radian],
+        OP_COS: [match_negated_parameter, match_half_pi_subtraction,
+                 match_standard_radian],
+        OP_TAN: [match_standard_radian],
         }

+ 63 - 4
src/rules/goniometry.py

@@ -1,6 +1,6 @@
 from .utils import is_fraction
 from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_ADD, \
-        OP_POW, OP_MUL, OP_SIN, OP_COS, OP_TAN, PI
+        OP_POW, OP_MUL, OP_DIV, OP_SIN, OP_COS, OP_TAN, PI, TYPE_OPERATOR
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -102,9 +102,44 @@ def match_half_pi_subtraction(node):
     return []
 
 
+def is_pi_frac(node, denominator):
+    """
+    Check if a node is a fraction of 1 multiplied with PI.
+
+    Example:
+    >>> print is_pi_frac(L(1) / 2 * L(PI), 2)
+    True
+    """
+    if not node.is_op(OP_MUL):
+        return False
+
+    frac, pi = node
+
+    if not frac.is_op(OP_DIV) or not pi.is_leaf or pi.value != PI:
+        return False
+
+    n, d = frac
+
+    return n == 1 and d == denominator
+
+
+def sqrt(value):
+    return N('sqrt', L(value))
+
+
+l0, l1, sq2, sq3 = L(0), L(1), sqrt(2), sqrt(3)
+half = l1 / 2
+
+CONSTANTS = {
+    OP_SIN: [l0, half, half * sq2, half * sq3, l1],
+    OP_COS: [l1, half * sq3, half * sq2, half, l0],
+    OP_TAN: [l0, l1 / 3 * sq3, l1, sq3]
+}
+
+
 def match_standard_radian(node):
     """
-    Apply a direct constant calculation from the following table.
+    Apply a direct constant calculation from the constants table.
 
         | 0 | pi / 6    | pi / 4    | pi / 3    | pi / 2
     ----+---+-----------+-----------+-----------+-------
@@ -112,5 +147,29 @@ def match_standard_radian(node):
     cos | 1 | sqrt(3)/2 | sqrt(2)/2 | 1/2       | 0
     tan | 0 | sqrt(3)/3 | 1         | sqrt(3)   | -
     """
-    # TODO: implement
-    pass
+    assert node.type == TYPE_OPERATOR and node.op in (OP_SIN, OP_COS, OP_TAN)
+
+    t = node[0]
+
+    if t == 0:
+        return [P(node, standard_radian, (node.op, 0))]
+
+    denoms = [6, 4, 3]
+
+    if node.op != OP_TAN:
+        denoms.append(2)
+
+    for i, denominator in enumerate(denoms):
+        if is_pi_frac(t, denominator):
+            return [P(node, standard_radian, (node.op, i + 1))]
+
+    return []
+
+
+def standard_radian(root, args):
+    op, column = args
+
+    return CONSTANTS[op][column].clone()
+
+
+MESSAGES[standard_radian] = _('Replace standard radian {0}.')

+ 37 - 3
tests/test_rules_goniometry.py

@@ -1,13 +1,20 @@
 # vim: set fileencoding=utf-8 :
-from src.rules.goniometry import match_add_quadrants, add_quadrants, \
-        match_negated_parameter, negated_sinus_parameter, \
-        negated_cosinus_parameter, sin, cos
+from src.rules.goniometry import sin, cos, tan, match_add_quadrants, \
+        add_quadrants, match_negated_parameter, negated_sinus_parameter, \
+        negated_cosinus_parameter, match_standard_radian, standard_radian, \
+        is_pi_frac
+from src.node import PI, OP_SIN, OP_COS, OP_TAN
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
+from src.rules import goniometry
+import doctest
 
 
 class TestRulesGoniometry(RulesTestCase):
 
+    def test_doctest(self):
+        self.assertEqual(doctest.testmod(m=goniometry)[0], 0)
+
     def test_match_add_quadrants(self):
         root = tree('sin t ^ 2 + cos t ^ 2')
         possibilities = match_add_quadrants(root)
@@ -35,3 +42,30 @@ class TestRulesGoniometry(RulesTestCase):
         c = tree('cos -t')
         t = c[0]
         self.assertEqual(negated_cosinus_parameter(c, (t,)), cos(+t))
+
+    def test_is_pi_frac(self):
+        l1, pi = tree('1,' + PI)
+
+        self.assertTrue(is_pi_frac(l1 / 2 * pi, 2))
+        self.assertFalse(is_pi_frac(l1 / 2 * pi, 3))
+        self.assertFalse(is_pi_frac(l1 * pi, 3))
+
+    def test_match_standard_radian(self):
+        s, c, t = tree('sin(1 / 6 * pi), cos(1 / 2 * pi), tan(0)')
+
+        self.assertEqualPos(match_standard_radian(s), \
+                [P(s, standard_radian, (OP_SIN, 1))])
+
+        self.assertEqualPos(match_standard_radian(c), \
+                [P(c, standard_radian, (OP_COS, 4))])
+
+        self.assertEqualPos(match_standard_radian(t), \
+                [P(t, standard_radian, (OP_TAN, 0))])
+
+    def test_standard_radian(self):
+        l0, l1, sq3, pi6, pi4, pi2 = tree('0,1,sqrt(3),1/6*pi,1/4*pi,1/2*pi')
+
+        self.assertEqual(standard_radian(sin(pi6), (OP_SIN, 1)), l1 / 2)
+        self.assertEqual(standard_radian(sin(pi2), (OP_SIN, 4)), 1)
+        self.assertEqual(standard_radian(cos(l0), (OP_COS, 0)), 1)
+        self.assertEqual(standard_radian(tan(pi4), (OP_TAN, 3)), sq3)