diff --git a/.gitignore b/.gitignore index 0c66ec45..be2b2712 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ analysis-master/dist data-analysis/config/ analysis-master/tra_analysis/equation/__pycache__/Expression.cpython-38.pyc analysis-master/tra_analysis/equation/__pycache__/__init__.cpython-38.pyc +analysis-master/tra_analysis/equation/__pycache__/parser.cpython-38.pyc +analysis-master/tra_analysis/equation/__pycache__/py2.cpython-38.pyc diff --git a/analysis-master/test_analysis.py b/analysis-master/test_analysis.py index d281b5ef..da5286ab 100644 --- a/analysis-master/test_analysis.py +++ b/analysis-master/test_analysis.py @@ -1,6 +1,7 @@ from tra_analysis import analysis as an from tra_analysis import metrics from tra_analysis import fits +from tra_analysis.equation.parser import BNF def test_(): test_data_linear = [1, 3, 6, 7, 9] @@ -32,4 +33,51 @@ def test_(): assert all(a == b for a, b in zip(an.Sort().bubblesort(test_data_scrambled), test_data_sorted)) assert all(a == b for a, b in zip(an.Sort().cyclesort(test_data_scrambled), test_data_sorted)) assert all(a == b for a, b in zip(an.Sort().cocktailsort(test_data_scrambled), test_data_sorted)) - assert fits.CircleFit(x=[0,0,-1,1], y=[1, -1, 0, 0]).LSC() == (0.0, 0.0, 1.0, 0.0) \ No newline at end of file + assert fits.CircleFit(x=[0,0,-1,1], y=[1, -1, 0, 0]).LSC() == (0.0, 0.0, 1.0, 0.0) + + test_equation() + +def test_equation(): + + parser = BNF() + + assert parser.eval("9") == 9.0 + assert parser.eval("-9") == -9.0 + assert parser.eval("--9") == 9.0 + assert parser.eval("-E") == -2.718281828459045 + assert parser.eval("9 + 3 + 6") == 18.0 + assert parser.eval("9 + 3 / 11") == 9.272727272727273 + assert parser.eval("(9 + 3)") == 12.0 + assert parser.eval("(9+3) / 11") == 1.0909090909090908 + assert parser.eval("9 - 12 - 6") == -9.0 + assert parser.eval("9 - (12 - 6)") == 3.0 + assert parser.eval("2*3.14159") == 6.28318 + assert parser.eval("3.1415926535*3.1415926535 / 10") == 0.9869604400525172 + assert parser.eval("PI * PI / 10") == 0.9869604401089358 + assert parser.eval("PI*PI/10") == 0.9869604401089358 + assert parser.eval("PI^2") == 9.869604401089358 + assert parser.eval("round(PI^2)") == 10 + assert parser.eval("6.02E23 * 8.048") == 4.844896e+24 + assert parser.eval("e / 3") == 0.9060939428196817 + assert parser.eval("sin(PI/2)") == 1.0 + assert parser.eval("10+sin(PI/4)^2") == 10.5 + assert parser.eval("trunc(E)") == 2 + assert parser.eval("trunc(-E)") == -2 + assert parser.eval("round(E)") == 3 + assert parser.eval("round(-E)") == -3 + assert parser.eval("E^PI") == 23.140692632779263 + assert parser.eval("exp(0)") == 1.0 + assert parser.eval("exp(1)") == 2.718281828459045 + assert parser.eval("2^3^2") == 512.0 + assert parser.eval("(2^3)^2") == 64.0 + assert parser.eval("2^3+2") == 10.0 + assert parser.eval("2^3+5") == 13.0 + assert parser.eval("2^9") == 512.0 + assert parser.eval("sgn(-2)") == -1 + assert parser.eval("sgn(0)") == 0 + assert parser.eval("sgn(0.1)") == 1 + assert parser.eval("sgn(cos(PI/4))") == 1 + assert parser.eval("sgn(cos(PI/2))") == 0 + assert parser.eval("sgn(cos(PI*3/4))") == -1 + assert parser.eval("+(sgn(cos(PI/4)))") == 1 + assert parser.eval("-(sgn(cos(PI/4)))") == -1 \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/Expression.py b/analysis-master/tra_analysis/equation/Expression.py deleted file mode 100644 index df2f73ff..00000000 --- a/analysis-master/tra_analysis/equation/Expression.py +++ /dev/null @@ -1,68 +0,0 @@ -# Titan Robotics Team 2022: Expression submodule -# Written by Arthur Lu -# Notes: -# this should be imported as a python module using 'from tra_analysis import equation' -# setup: - -__version__ = "0.0.1-alpha" - -__changelog__ = """changelog: - 0.0.1-alpha: - - took items from equation.ipynb and ported here -""" - -__author__ = ( - "Arthur Lu ", -) - -import re -from decimal import Decimal -from functools import reduce - -class Expression: - - def __init__(self, string): - - self.string = string - - def add(self, string): - while(len(re.findall("[+]{1}[-]?", string)) != 0): - string = re.sub("[-]?\d+[.]?\d*[+]{1}[-]?\d+[.]?\d*", str("%f" % reduce((lambda x, y: x + y), [Decimal(i) for i in re.split("[+]{1}", re.search("[-]?\d+[.]?\d*[+]{1}[-]?\d+[.]?\d*", string).group())])), string, 1) - return string - - def sub(self, string): - while(len(re.findall("\d+[.]?\d*[-]{1,2}\d+[.]?\d*", string)) != 0): - g = re.search("\d+[.]?\d*[-]{1,2}\d+[.]?\d*", string).group() - if(re.search("[-]{1,2}", g).group() == "-"): - r = re.sub("[-]{1}", "+-", g, 1) - string = re.sub(g, r, string, 1) - elif(re.search("[-]{1,2}", g).group() == "--"): - r = re.sub("[-]{2}", "+", g, 1) - string = re.sub(g, r, string, 1) - else: - pass - return string - - def mul(self, string): - while(len(re.findall("[*]{1}[-]?", string)) != 0): - string = re.sub("[-]?\d+[.]?\d*[*]{1}[-]?\d+[.]?\d*", str("%f" % reduce((lambda x, y: x * y), [Decimal(i) for i in re.split("[*]{1}", re.search("[-]?\d+[.]?\d*[*]{1}[-]?\d+[.]?\d*", string).group())])), string, 1) - return string - - def div(self, string): - while(len(re.findall("[/]{1}[-]?", string)) != 0): - string = re.sub("[-]?\d+[.]?\d*[/]{1}[-]?\d+[.]?\d*", str("%f" % reduce((lambda x, y: x / y), [Decimal(i) for i in re.split("[/]{1}", re.search("[-]?\d+[.]?\d*[/]{1}[-]?\d+[.]?\d*", string).group())])), string, 1) - return string - - def exp(self, string): - while(len(re.findall("[\^]{1}[-]?", string)) != 0): - string = re.sub("[-]?\d+[.]?\d*[\^]{1}[-]?\d+[.]?\d*", str("%f" % reduce((lambda x, y: x ** y), [Decimal(i) for i in re.split("[\^]{1}", re.search("[-]?\d+[.]?\d*[\^]{1}[-]?\d+[.]?\d*", string).group())])), string, 1) - return string - - def evaluate(self): - string = self.string - string = self.exp(string) - string = self.div(string) - string = self.mul(string) - string = self.sub(string) - string = self.add(string) - return string \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/__init__.py b/analysis-master/tra_analysis/equation/__init__.py index 5948e0da..447703d3 100644 --- a/analysis-master/tra_analysis/equation/__init__.py +++ b/analysis-master/tra_analysis/equation/__init__.py @@ -1 +1 @@ -from .Expression import Expression \ No newline at end of file +from .parser import * \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser.py b/analysis-master/tra_analysis/equation/parser.py new file mode 100644 index 00000000..14e627fc --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser.py @@ -0,0 +1,119 @@ +# Titan Robotics Team 2022: Expression submodule +# Written by Arthur Lu +# Notes: +# this should be imported as a python module using 'from tra_analysis import equation' +# adapted from https://github.com/pyparsing/pyparsing/blob/master/examples/fourFn.py +# setup: + +from __future__ import division + +__version__ = "0.0.2-alpha" + +__changelog__ = """changelog: + 0.0.2-alpha: + - wrote BNF using pyparsing and uses a BNF metasyntax + - renamed this submodule parser + 0.0.1-alpha: + - took items from equation.ipynb and ported here +""" + +__author__ = ( + "Arthur Lu ", +) + +from pyparsing import (Literal, CaselessLiteral, Word, Combine, Group, Optional, ZeroOrMore, Forward, nums, alphas, oneOf) +from . import py2 +import math +import operator + +class BNF(object): + + def pushFirst(self, strg, loc, toks): + self.exprStack.append(toks[0]) + + def pushUMinus(self, strg, loc, toks): + if toks and toks[0] == '-': + self.exprStack.append('unary -') + + def __init__(self): + """ + expop :: '^' + multop :: '*' | '/' + addop :: '+' | '-' + integer :: ['+' | '-'] '0'..'9'+ + atom :: PI | E | real | fn '(' expr ')' | '(' expr ')' + factor :: atom [ expop factor ]* + term :: factor [ multop factor ]* + expr :: term [ addop term ]* + """ + point = Literal(".") + e = CaselessLiteral("E") + fnumber = Combine(Word("+-" + nums, nums) + + Optional(point + Optional(Word(nums))) + + Optional(e + Word("+-" + nums, nums))) + ident = Word(alphas, alphas + nums + "_$") + plus = Literal("+") + minus = Literal("-") + mult = Literal("*") + div = Literal("/") + lpar = Literal("(").suppress() + rpar = Literal(")").suppress() + addop = plus | minus + multop = mult | div + expop = Literal("^") + pi = CaselessLiteral("PI") + expr = Forward() + atom = ((Optional(oneOf("- +")) + + (ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst)) + | Optional(oneOf("- +")) + Group(lpar + expr + rpar) + ).setParseAction(self.pushUMinus) + factor = Forward() + factor << atom + \ + ZeroOrMore((expop + factor).setParseAction(self.pushFirst)) + term = factor + \ + ZeroOrMore((multop + factor).setParseAction(self.pushFirst)) + expr << term + \ + ZeroOrMore((addop + term).setParseAction(self.pushFirst)) + + self.bnf = expr + + epsilon = 1e-12 + + self.opn = {"+": operator.add, + "-": operator.sub, + "*": operator.mul, + "/": operator.truediv, + "^": operator.pow} + self.fn = {"sin": math.sin, + "cos": math.cos, + "tan": math.tan, + "exp": math.exp, + "abs": abs, + "trunc": lambda a: int(a), + "round": round, + "sgn": lambda a: abs(a) > epsilon and py2.cmp(a, 0) or 0} + + def evaluateStack(self, s): + op = s.pop() + if op == 'unary -': + return -self.evaluateStack(s) + if op in "+-*/^": + op2 = self.evaluateStack(s) + op1 = self.evaluateStack(s) + return self.opn[op](op1, op2) + elif op == "PI": + return math.pi + elif op == "E": + return math.e + elif op in self.fn: + return self.fn[op](self.evaluateStack(s)) + elif op[0].isalpha(): + return 0 + else: + return float(op) + + def eval(self, num_string, parseAll=True): + self.exprStack = [] + results = self.bnf.parseString(num_string, parseAll) + val = self.evaluateStack(self.exprStack[:]) + return val \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/py2.py b/analysis-master/tra_analysis/equation/py2.py new file mode 100644 index 00000000..e9afa9a5 --- /dev/null +++ b/analysis-master/tra_analysis/equation/py2.py @@ -0,0 +1,21 @@ +# Titan Robotics Team 2022: py2 module +# Written by Arthur Lu +# Notes: +# this module should only be used internally, contains old python 2.X functions that have been removed. +# setup: + +from __future__ import division + +__version__ = "1.0.0" + +__changelog__ = """changelog: + 1.0.0: + - added cmp function +""" + +__author__ = ( + "Arthur Lu ", +) + +def cmp(a, b): + return (a > b) - (a < b) \ No newline at end of file