Skip to content
Snippets Groups Projects
Commit 1c68fcb3 authored by Taddeus Kroes's avatar Taddeus Kroes
Browse files

Merged conflicts.

parents 1709f872 aa2d137d
No related branches found
No related tags found
No related merge requests found
graph_drawing @ 79a5b196
Subproject commit 21f0710c80184b6ea18827808ec40af924f0c8a8 Subproject commit 79a5b196645611ab8b610685a8b1cf7ceef362af
...@@ -9,6 +9,8 @@ from graph_drawing.graph import generate_graph ...@@ -9,6 +9,8 @@ from graph_drawing.graph import generate_graph
from graph_drawing.line import generate_line from graph_drawing.line import generate_line
from graph_drawing.node import Node, Leaf from graph_drawing.node import Node, Leaf
from unicode_math import PI as u_PI
TYPE_OPERATOR = 1 TYPE_OPERATOR = 1
TYPE_IDENTIFIER = 2 TYPE_IDENTIFIER = 2
...@@ -45,6 +47,9 @@ OP_HINT = 17 ...@@ -45,6 +47,9 @@ OP_HINT = 17
OP_REWRITE_ALL = 18 OP_REWRITE_ALL = 18
OP_REWRITE = 19 OP_REWRITE = 19
# Special identifierd
PI = 'pi'
TYPE_MAP = { TYPE_MAP = {
int: TYPE_INTEGER, int: TYPE_INTEGER,
...@@ -352,7 +357,17 @@ class ExpressionLeaf(Leaf, ExpressionBase): ...@@ -352,7 +357,17 @@ class ExpressionLeaf(Leaf, ExpressionBase):
return self.negated == other.negated and self.type == other.type \ return self.negated == other.negated and self.type == other.type \
and self.value == other.value and self.value == other.value
def __str__(self):
val = str(self.value)
# Replace PI leaf by the Greek character
if val == PI:
val = u_PI
return '-' * self.negated + val
def __repr__(self): def __repr__(self):
return str(self)
return '-' * self.negated + str(self.value) return '-' * self.negated + str(self.value)
def equals(self, other, ignore_negation=False): def equals(self, other, ignore_negation=False):
......
...@@ -15,7 +15,7 @@ from pybison import BisonParser, BisonSyntaxError ...@@ -15,7 +15,7 @@ from pybison import BisonParser, BisonSyntaxError
from graph_drawing.graph import generate_graph from graph_drawing.graph import generate_graph
from node import ExpressionNode as Node, ExpressionLeaf as Leaf, OP_MAP, \ from node import ExpressionNode as Node, ExpressionLeaf as Leaf, OP_MAP, \
TOKEN_MAP, TYPE_OPERATOR, OP_COMMA, OP_NEG, OP_MUL, Scope TOKEN_MAP, TYPE_OPERATOR, OP_COMMA, OP_NEG, OP_MUL, Scope, PI
from rules import RULES from rules import RULES
from possibilities import filter_duplicates, pick_suggestion, apply_suggestion from possibilities import filter_duplicates, pick_suggestion, apply_suggestion
...@@ -45,7 +45,7 @@ class Parser(BisonParser): ...@@ -45,7 +45,7 @@ class Parser(BisonParser):
# Words to be ignored by preprocessor # Words to be ignored by preprocessor
words = zip(*filter(lambda (s, op): TOKEN_MAP[op] == 'FUNCTION', \ words = zip(*filter(lambda (s, op): TOKEN_MAP[op] == 'FUNCTION', \
OP_MAP.iteritems()))[0] + ('raise', 'graph') OP_MAP.iteritems()))[0] + ('raise', 'graph', PI)
# Output directory of generated pybison files, including a trailing slash. # Output directory of generated pybison files, including a trailing slash.
buildDirectory = PYBISON_BUILD + '/' buildDirectory = PYBISON_BUILD + '/'
...@@ -418,9 +418,10 @@ class Parser(BisonParser): ...@@ -418,9 +418,10 @@ class Parser(BisonParser):
% (option, target)) # pragma: nocover % (option, target)) # pragma: nocover
# ----------------------------------------- # -----------------------------------------
# operator tokens # PI and operator tokens
# ----------------------------------------- # -----------------------------------------
operators = '' operators = '"%s"%s{ returntoken(IDENTIFIER); }\n' \
% (PI, ' ' * (8 - len(PI)))
functions = [] functions = []
for op_str, op in OP_MAP.iteritems(): for op_str, op in OP_MAP.iteritems():
......
from ..node import OP_ADD, OP_MUL, OP_DIV, OP_POW, OP_NEG 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 .groups import match_combine_groups
from .factors import match_expand from .factors import match_expand
from .powers import match_add_exponents, match_subtract_exponents, \ from .powers import match_add_exponents, match_subtract_exponents, \
...@@ -12,10 +13,12 @@ from .fractions import match_constant_division, match_add_constant_fractions, \ ...@@ -12,10 +13,12 @@ from .fractions import match_constant_division, match_add_constant_fractions, \
from .negation import match_negated_factor, match_negate_polynome, \ from .negation import match_negated_factor, match_negate_polynome, \
match_negated_division match_negated_division
from .sort import match_sort_multiplicants from .sort import match_sort_multiplicants
from .goniometry import match_add_quadrants, match_negated_parameter, \
match_half_pi_subtraction, match_standard_radian
RULES = { RULES = {
OP_ADD: [match_add_numerics, match_add_constant_fractions, OP_ADD: [match_add_numerics, match_add_constant_fractions,
match_combine_groups], match_combine_groups, match_add_quadrants],
OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents, OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
match_expand_and_add_fractions, match_multiply_zero, match_expand_and_add_fractions, match_multiply_zero,
match_negated_factor, match_multiply_one, match_negated_factor, match_multiply_one,
...@@ -26,4 +29,9 @@ RULES = { ...@@ -26,4 +29,9 @@ RULES = {
match_remove_negative_exponent, match_exponent_to_root, match_remove_negative_exponent, match_exponent_to_root,
match_extend_exponent, match_constant_exponent], match_extend_exponent, match_constant_exponent],
OP_NEG: [match_negate_polynome], OP_NEG: [match_negate_polynome],
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],
} }
from .utils import is_fraction
from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_ADD, \ from ..node import ExpressionNode as N, ExpressionLeaf as L, Scope, OP_ADD, \
OP_POW, OP_MUL, OP_SIN, OP_COS, OP_TAN OP_POW, OP_MUL, OP_DIV, OP_SIN, OP_COS, OP_TAN, PI, TYPE_OPERATOR
from ..possibilities import Possibility as P, MESSAGES from ..possibilities import Possibility as P, MESSAGES
from ..translate import _ from ..translate import _
...@@ -18,7 +19,7 @@ def tan(*args): ...@@ -18,7 +19,7 @@ def tan(*args):
def match_add_quadrants(node): def match_add_quadrants(node):
""" """
sin(x) ^ 2 + cos(x) ^ 2 -> 1 sin(t) ^ 2 + cos(t) ^ 2 -> 1
""" """
assert node.is_op(OP_ADD) assert node.is_op(OP_ADD)
...@@ -36,9 +37,139 @@ def match_add_quadrants(node): ...@@ -36,9 +37,139 @@ def match_add_quadrants(node):
def add_quadrants(root, args): def add_quadrants(root, args):
""" """
sin(x) ^ 2 + cos(x) ^ 2 -> 1 sin(t) ^ 2 + cos(t) ^ 2 -> 1
""" """
return L(1) return L(1)
MESSAGES[add_quadrants] = _('Add the sinus and cosinus quadrants to 1.') MESSAGES[add_quadrants] = _('Add the sinus and cosinus quadrants to 1.')
def match_negated_parameter(node):
"""
sin(-t) -> -sin(t)
cos(-t) -> cos(t)
"""
assert node.is_op(OP_SIN) or node.is_op(OP_COS)
t = node[0]
if t.negated:
if node.op == OP_SIN:
return [P(node, negated_sinus_parameter, (t,))]
return [P(node, negated_cosinus_parameter, (t,))]
return []
def negated_sinus_parameter(root, args):
"""
sin(-t) -> -sin(t)
"""
return -sin(+args[0])
MESSAGES[negated_sinus_parameter] = \
_('Bring the negation from the sinus parameter {1} to the outside.')
def negated_cosinus_parameter(root, args):
"""
cos(-t) -> cos(t)
"""
return cos(+args[0])
MESSAGES[negated_cosinus_parameter] = \
_('Remove the negation from the cosinus parameter {1}.')
def match_half_pi_subtraction(node):
"""
sin(pi / 2 - t) -> cos(t)
cos(pi / 2 - t) -> sin(t)
"""
assert node.is_op(OP_SIN) or node.is_op(OP_COS)
if node[0].is_op(OP_ADD):
half_pi, t = node[0]
if half_pi == L(PI) / 2:
if node.op == OP_SIN:
return [P(node, half_pi_subtraction_sinus, (t,))]
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 constants table.
| 0 | pi / 6 | pi / 4 | pi / 3 | pi / 2
----+---+-----------+-----------+-----------+-------
sin | 0 | 1/2 | sqrt(2)/2 | sqrt(3)/2 | 1
cos | 1 | sqrt(3)/2 | sqrt(2)/2 | 1/2 | 0
tan | 0 | sqrt(3)/3 | 1 | sqrt(3) | -
"""
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}.')
from ..node import ExpressionLeaf as L, OP_MUL, OP_DIV
def gcd(a, b): def gcd(a, b):
""" """
Return greatest common divisor using Euclid's Algorithm. Return greatest common divisor using Euclid's Algorithm.
...@@ -20,3 +23,34 @@ def least_common_multiple(*args): ...@@ -20,3 +23,34 @@ def least_common_multiple(*args):
Return lcm of args. Return lcm of args.
""" """
return reduce(lcm, args) return reduce(lcm, args)
def is_fraction(node, nominator, denominator):
"""
Check if a node represents the fraction of a given nominator and
denominator.
>>> from ..node import ExpressionLeaf as L
>>> l1, l2, a = L('a'), L(1), L(2)
>>> is_fraction(a / l2, a, 2)
True
>>> is_fraction(l1 / l2 * a, a, 2)
True
>>> is_fraction(l2 / l1 * a, a, 2)
False
"""
if node.is_op(OP_DIV):
nom, denom = node
return nom == nominator and denom == denominator
if node.is_op(OP_MUL):
# 1 / denominator * nominator
# nominator * 1 / denominator
left, right = node
fraction = L(1) / denominator
return (left == nominator and right == fraction) \
or (right == nominator and left == fraction)
return False
# vim: set fileencoding=utf-8 :
SQRT = ''
CBRT = ''
FORT = ''
PI = 'π'
INFINITY = ''
SUP = {
'0': '',
'1': '¹',
'2': '²',
'3': '³',
'3': '',
'5': '',
'6': '',
'7': '',
'8': '',
'9': '',
}
DOT = ''
from src.rules.goniometry import match_add_quadrants, add_quadrants # vim: set fileencoding=utf-8 :
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 src.possibilities import Possibility as P
from tests.rulestestcase import RulesTestCase, tree from tests.rulestestcase import RulesTestCase, tree
from src.rules import goniometry
import doctest
class TestRulesGoniometry(RulesTestCase): class TestRulesGoniometry(RulesTestCase):
def test_doctest(self):
self.assertEqual(doctest.testmod(m=goniometry)[0], 0)
def test_match_add_quadrants(self): def test_match_add_quadrants(self):
root = tree('sin x ^ 2 + cos x ^ 2') root = tree('sin t ^ 2 + cos t ^ 2')
possibilities = match_add_quadrants(root) possibilities = match_add_quadrants(root)
self.assertEqualPos(possibilities, [P(root, add_quadrants, ())]) self.assertEqualPos(possibilities, [P(root, add_quadrants, ())])
def test_add_quadrants(self): def test_add_quadrants(self):
self.assertEqual(add_quadrants(None, ()), 1) self.assertEqual(add_quadrants(None, ()), 1)
def test_match_negated_parameter(self):
s, c = tree('sin -t, cos -t')
t = s[0]
self.assertEqualPos(match_negated_parameter(s), \
[P(s, negated_sinus_parameter, (t,))])
self.assertEqualPos(match_negated_parameter(c), \
[P(c, negated_cosinus_parameter, (t,))])
def test_negated_sinus_parameter(self):
s = tree('sin -t')
t = s[0]
self.assertEqual(negated_sinus_parameter(s, (t,)), -sin(+t))
def test_negated_cosinus_parameter(self):
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)
import unittest import unittest
from src.rules.utils import least_common_multiple from src.rules.utils import least_common_multiple, is_fraction
from tests.rulestestcase import tree
class TestRulesUtils(unittest.TestCase): class TestRulesUtils(unittest.TestCase):
...@@ -9,3 +10,11 @@ class TestRulesUtils(unittest.TestCase): ...@@ -9,3 +10,11 @@ class TestRulesUtils(unittest.TestCase):
self.assertEqual(least_common_multiple(5, 6), 30) self.assertEqual(least_common_multiple(5, 6), 30)
self.assertEqual(least_common_multiple(5, 6, 15), 30) self.assertEqual(least_common_multiple(5, 6, 15), 30)
self.assertEqual(least_common_multiple(2, 4), 4) self.assertEqual(least_common_multiple(2, 4), 4)
def test_is_fraction(self):
l1, a = tree('1, a')
self.assertTrue(is_fraction(a / 2, a, 2))
self.assertTrue(is_fraction(l1 / 2 * a, a, 2))
self.assertTrue(is_fraction(a * (l1 / 2), a, 2))
self.assertFalse(is_fraction(l1 / 3 * a, a, 2))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment