diff --git a/.gitignore b/.gitignore index 2545560b..d0bd9faa 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ analysis-master/tra_analysis/.ipynb_checkpoints .pytest_cache analysis-master/tra_analysis/metrics/__pycache__ analysis-master/dist -data-analysis/config/ \ No newline at end of file +data-analysis/config/ +analysis-master/tra_analysis/equation/__pycache__/* +analysis-master/tra_analysis/equation/parser/__pycache__/* +analysis-master/tra_analysis/equation/parser/Hybrid_Utils/__pycache__/* diff --git a/analysis-master/requirements.txt b/analysis-master/requirements.txt index 33435d14..3d106798 100644 --- a/analysis-master/requirements.txt +++ b/analysis-master/requirements.txt @@ -2,4 +2,5 @@ numpy scipy scikit-learn six -matplotlib \ No newline at end of file +matplotlib +pyparsing \ No newline at end of file diff --git a/analysis-master/test_analysis.py b/analysis-master/test_analysis.py index 5676ca08..8ce87a60 100644 --- a/analysis-master/test_analysis.py +++ b/analysis-master/test_analysis.py @@ -115,6 +115,7 @@ def test_(): assert Fit.CircleFit(x=[0,0,-1,1], y=[1, -1, 0, 0]).LSC() == (0.0, 0.0, 1.0, 0.0) svm(test_data_2D_pairs, test_labels_2D_pairs, validation_data_2D_pairs, validation_labels_2D_pairs) + test_equation() def svm(data, labels, test_data, test_labels): @@ -143,3 +144,50 @@ def svm(data, labels, test_data, test_labels): for i in range(len(test_data)): assert sig_kernel.predict([test_data[i]]).tolist() == [test_labels[i]] + + 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 diff --git a/analysis-master/tra_analysis/__init__.py b/analysis-master/tra_analysis/__init__.py index 7e99fd3f..f1bca258 100644 --- a/analysis-master/tra_analysis/__init__.py +++ b/analysis-master/tra_analysis/__init__.py @@ -30,10 +30,11 @@ __author__ = ( __all__ = [ ] -from . import Analysis +from . import Analysis as Analysis from .Array import Array from .ClassificationMetric import ClassificationMetric from . import CorrelationTest +from .equation import Expression from . import Fit from . import KNN from . import NaiveBayes diff --git a/analysis-master/tra_analysis/equation/Expression.py b/analysis-master/tra_analysis/equation/Expression.py new file mode 100644 index 00000000..4ab2767a --- /dev/null +++ b/analysis-master/tra_analysis/equation/Expression.py @@ -0,0 +1,37 @@ +# Titan Robotics Team 2022: Expression submodule +# Written by Arthur Lu +# Notes: +# this should be imported as a python module using 'from tra_analysis.Equation import Expression' +# TODO: +# - add option to pick parser backend +# - fix unit tests +# setup: + +__version__ = "0.0.1-alpha" + +__changelog__ = """changelog: + 0.0.1-alpha: + - used the HybridExpressionParser as backend for Expression +""" + +__author__ = ( + "Arthur Lu ", +) + +__all__ = { + "Expression" +} + +import re +from .parser import BNF, RegexInplaceParser, HybridExpressionParser, Core, equation_base + +class Expression(HybridExpressionParser): + + expression = None + core = None + + def __init__(self,expression,argorder=[],*args,**kwargs): + self.core = Core() + equation_base.equation_extend(self.core) + self.core.recalculateFMatch() + super().__init__(self.core, expression, argorder=[],*args,**kwargs) \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/__init__.py b/analysis-master/tra_analysis/equation/__init__.py new file mode 100644 index 00000000..8bfe8316 --- /dev/null +++ b/analysis-master/tra_analysis/equation/__init__.py @@ -0,0 +1,22 @@ +# 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: + - made first prototype of Expression +""" + +__author__ = ( + "Arthur Lu ", +) + +__all__ = { + "Expression" +} + +from .Expression import Expression \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/BNF.py b/analysis-master/tra_analysis/equation/parser/BNF.py new file mode 100644 index 00000000..97b2bfb4 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/BNF.py @@ -0,0 +1,97 @@ +from __future__ import division +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/parser/Hybrid.py b/analysis-master/tra_analysis/equation/parser/Hybrid.py new file mode 100644 index 00000000..4f41c7b1 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/Hybrid.py @@ -0,0 +1,521 @@ +from .Hybrid_Utils import Core, ExpressionFunction, ExpressionVariable, ExpressionValue +import sys + +if sys.version_info >= (3,): + xrange = range + basestring = str + +class HybridExpressionParser(object): + + def __init__(self,core,expression,argorder=[],*args,**kwargs): + super(HybridExpressionParser,self).__init__(*args,**kwargs) + if isinstance(expression,type(self)): # clone the object + self.core = core + self.__args = list(expression.__args) + self.__vars = dict(expression.__vars) # intenral array of preset variables + self.__argsused = set(expression.__argsused) + self.__expr = list(expression.__expr) + self.variables = {} # call variables + else: + self.__expression = expression + self.__args = argorder; + self.__vars = {} # intenral array of preset variables + self.__argsused = set() + self.__expr = [] # compiled equation tokens + self.variables = {} # call variables + self.__compile() + del self.__expression + + def __getitem__(self, name): + if name in self.__argsused: + if name in self.__vars: + return self.__vars[name] + else: + return None + else: + raise KeyError(name) + + def __setitem__(self,name,value): + + if name in self.__argsused: + self.__vars[name] = value + else: + raise KeyError(name) + + def __delitem__(self,name): + + if name in self.__argsused: + if name in self.__vars: + del self.__vars[name] + else: + raise KeyError(name) + + def __contains__(self, name): + + return name in self.__argsused + + def __call__(self,*args,**kwargs): + + if len(self.__expr) == 0: + return None + self.variables = {} + self.variables.update(self.core.constants) + self.variables.update(self.__vars) + if len(args) > len(self.__args): + raise TypeError("<{0:s}.{1:s}({2:s}) object at {3:0=#10x}>() takes at most {4:d} arguments ({5:d} given)".format( + type(self).__module__,type(self).__name__,repr(self),id(self),len(self.__args),len(args))) + for i in xrange(len(args)): + if i < len(self.__args): + if self.__args[i] in kwargs: + raise TypeError("<{0:s}.{1:s}({2:s}) object at {3:0=#10x}>() got multiple values for keyword argument '{4:s}'".format( + type(self).__module__,type(self).__name__,repr(self),id(self),self.__args[i])) + self.variables[self.__args[i]] = args[i] + self.variables.update(kwargs) + for arg in self.__argsused: + if arg not in self.variables: + min_args = len(self.__argsused - (set(self.__vars.keys()) | set(self.core.constants.keys()))) + raise TypeError("<{0:s}.{1:s}({2:s}) object at {3:0=#10x}>() takes at least {4:d} arguments ({5:d} given) '{6:s}' not defined".format( + type(self).__module__,type(self).__name__,repr(self),id(self),min_args,len(args)+len(kwargs),arg)) + expr = self.__expr[::-1] + args = [] + while len(expr) > 0: + t = expr.pop() + r = t(args,self) + args.append(r) + if len(args) > 1: + return args + else: + return args[0] + + def __next(self,__expect_op): + if __expect_op: + m = self.core.gematch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'CLOSE' + m = self.core.smatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + return ",",'SEP' + m = self.core.omatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'OP' + else: + m = self.core.gsmatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'OPEN' + m = self.core.vmatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groupdict(0) + if g['dec']: + if g["ivalue"]: + return complex(int(g["rsign"]+"1")*float(g["rvalue"])*10**int(g["rexpoent"]),int(g["isign"]+"1")*float(g["ivalue"])*10**int(g["iexpoent"])),'VALUE' + elif g["rexpoent"] or g["rvalue"].find('.')>=0: + return int(g["rsign"]+"1")*float(g["rvalue"])*10**int(g["rexpoent"]),'VALUE' + else: + return int(g["rsign"]+"1")*int(g["rvalue"]),'VALUE' + elif g["hex"]: + return int(g["hexsign"]+"1")*int(g["hexvalue"],16),'VALUE' + elif g["oct"]: + return int(g["octsign"]+"1")*int(g["octvalue"],8),'VALUE' + elif g["bin"]: + return int(g["binsign"]+"1")*int(g["binvalue"],2),'VALUE' + else: + raise NotImplemented("'{0:s}' Values Not Implemented Yet".format(m.string)) + m = self.core.nmatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'NAME' + m = self.core.fmatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'FUNC' + m = self.core.umatch.match(self.__expression) + if m != None: + self.__expression = self.__expression[m.end():] + g = m.groups() + return g[0],'UNARY' + return None + + def show(self): + """Show RPN tokens + + This will print out the internal token list (RPN) of the expression + one token perline. + """ + for expr in self.__expr: + print(expr) + + def __str__(self): + """str(fn) + + Generates a Printable version of the Expression + + Returns + ------- + str + Latex String respresation of the Expression, suitable for rendering the equation + """ + expr = self.__expr[::-1] + if len(expr) == 0: + return "" + args = []; + while len(expr) > 0: + t = expr.pop() + r = t.toStr(args,self) + args.append(r) + if len(args) > 1: + return args + else: + return args[0] + + def __repr__(self): + """repr(fn) + + Generates a String that correctrly respresents the equation + + Returns + ------- + str + Convert the Expression to a String that passed to the constructor, will constuct + an identical equation object (in terms of sequence of tokens, and token type/value) + """ + expr = self.__expr[::-1] + if len(expr) == 0: + return "" + args = []; + while len(expr) > 0: + t = expr.pop() + r = t.toRepr(args,self) + args.append(r) + if len(args) > 1: + return args + else: + return args[0] + + def __iter__(self): + return iter(self.__argsused) + + def __lt__(self, other): + if isinstance(other, Expression): + return repr(self) < repr(other) + else: + raise TypeError("{0:s} is not an {1:s} Object, and can't be compared to an Expression Object".format(repr(other), type(other))) + + def __eq__(self, other): + if isinstance(other, Expression): + return repr(self) == repr(other) + else: + raise TypeError("{0:s} is not an {1:s} Object, and can't be compared to an Expression Object".format(repr(other), type(other))) + + def __combine(self,other,op): + if op not in self.core.ops or not isinstance(other,(int,float,complex,type(self),basestring)): + return NotImplemented + else: + obj = type(self)(self) + if isinstance(other,(int,float,complex)): + obj.__expr.append(ExpressionValue(other)) + else: + if isinstance(other,basestring): + try: + other = type(self)(other) + except: + raise SyntaxError("Can't Convert string, \"{0:s}\" to an Expression Object".format(other)) + obj.__expr += other.__expr + obj.__argsused |= other.__argsused + for v in other.__args: + if v not in obj.__args: + obj.__args.append(v) + for k,v in other.__vars.items(): + if k not in obj.__vars: + obj.__vars[k] = v + elif v != obj.__vars[k]: + raise RuntimeError("Predifined Variable Conflict in '{0:s}' two differing values defined".format(k)) + fn = self.core.ops[op] + obj.__expr.append(ExpressionFunction(fn['func'],fn['args'],fn['str'],fn['latex'],op,False)) + return obj + + def __rcombine(self,other,op): + if op not in self.core.ops or not isinstance(other,(int,float,complex,type(self),basestring)): + return NotImplemented + else: + obj = type(self)(self) + if isinstance(other,(int,float,complex)): + obj.__expr.insert(0,ExpressionValue(other)) + else: + if isinstance(other,basestring): + try: + other = type(self)(other) + except: + raise SyntaxError("Can't Convert string, \"{0:s}\" to an Expression Object".format(other)) + obj.__expr = other.__expr + self.__expr + obj.__argsused = other.__argsused | self.__expr + __args = other.__args + for v in obj.__args: + if v not in __args: + __args.append(v) + obj.__args = __args + for k,v in other.__vars.items(): + if k not in obj.__vars: + obj.__vars[k] = v + elif v != obj.__vars[k]: + raise RuntimeError("Predifined Variable Conflict in '{0:s}' two differing values defined".format(k)) + fn = self.core.ops[op] + obj.__expr.append(ExpressionFunction(fn['func'],fn['args'],fn['str'],fn['latex'],op,False)) + return obj + + def __icombine(self,other,op): + if op not in self.core.ops or not isinstance(other,(int,float,complex,type(self),basestring)): + return NotImplemented + else: + obj = self + if isinstance(other,(int,float,complex)): + obj.__expr.append(ExpressionValue(other)) + else: + if isinstance(other,basestring): + try: + other = type(self)(other) + except: + raise SyntaxError("Can't Convert string, \"{0:s}\" to an Expression Object".format(other)) + obj.__expr += other.__expr + obj.__argsused |= other.__argsused + for v in other.__args: + if v not in obj.__args: + obj.__args.append(v) + for k,v in other.__vars.items(): + if k not in obj.__vars: + obj.__vars[k] = v + elif v != obj.__vars[k]: + raise RuntimeError("Predifined Variable Conflict in '{0:s}' two differing values defined".format(k)) + fn = self.core.ops[op] + obj.__expr.append(ExpressionFunction(fn['func'],fn['args'],fn['str'],fn['latex'],op,False)) + return obj + + def __apply(self,op): + fn = self.core.unary_ops[op] + obj = type(self)(self) + obj.__expr.append(ExpressionFunction(fn['func'],1,fn['str'],fn['latex'],op,False)) + return obj + + def __applycall(self,op): + fn = self.core.functions[op] + if 1 not in fn['args'] or '*' not in fn['args']: + raise RuntimeError("Can't Apply {0:s} function, dosen't accept only 1 argument".format(op)) + obj = type(self)(self) + obj.__expr.append(ExpressionFunction(fn['func'],1,fn['str'],fn['latex'],op,False)) + return obj + + def __add__(self,other): + return self.__combine(other,'+') + + def __sub__(self,other): + return self.__combine(other,'-') + + def __mul__(self,other): + return self.__combine(other,'*') + + def __div__(self,other): + return self.__combine(other,'/') + + def __truediv__(self,other): + return self.__combine(other,'/') + + def __pow__(self,other): + return self.__combine(other,'^') + + def __mod__(self,other): + return self.__combine(other,'%') + + def __and__(self,other): + return self.__combine(other,'&') + + def __or__(self,other): + return self.__combine(other,'|') + + def __xor__(self,other): + return self.__combine(other,'') + + def __radd__(self,other): + return self.__rcombine(other,'+') + + def __rsub__(self,other): + return self.__rcombine(other,'-') + + def __rmul__(self,other): + return self.__rcombine(other,'*') + + def __rdiv__(self,other): + return self.__rcombine(other,'/') + + def __rtruediv__(self,other): + return self.__rcombine(other,'/') + + def __rpow__(self,other): + return self.__rcombine(other,'^') + + def __rmod__(self,other): + return self.__rcombine(other,'%') + + def __rand__(self,other): + return self.__rcombine(other,'&') + + def __ror__(self,other): + return self.__rcombine(other,'|') + + def __rxor__(self,other): + return self.__rcombine(other,'') + + def __iadd__(self,other): + return self.__icombine(other,'+') + + def __isub__(self,other): + return self.__icombine(other,'-') + + def __imul__(self,other): + return self.__icombine(other,'*') + + def __idiv__(self,other): + return self.__icombine(other,'/') + + def __itruediv__(self,other): + return self.__icombine(other,'/') + + def __ipow__(self,other): + return self.__icombine(other,'^') + + def __imod__(self,other): + return self.__icombine(other,'%') + + def __iand__(self,other): + return self.__icombine(other,'&') + + def __ior__(self,other): + return self.__icombine(other,'|') + + def __ixor__(self,other): + return self.__icombine(other,'') + + def __neg__(self): + return self.__apply('-') + + def __invert__(self): + return self.__apply('!') + + def __abs__(self): + return self.__applycall('abs') + + def __getfunction(self,op): + if op[1] == 'FUNC': + fn = self.core.functions[op[0]] + fn['type'] = 'FUNC' + elif op[1] == 'UNARY': + fn = self.core.unary_ops[op[0]] + fn['type'] = 'UNARY' + fn['args'] = 1 + elif op[1] == 'OP': + fn = self.core.ops[op[0]] + fn['type'] = 'OP' + return fn + + def __compile(self): + self.__expr = [] + stack = [] + argc = [] + __expect_op = False + v = self.__next(__expect_op) + while v != None: + if not __expect_op and v[1] == "OPEN": + stack.append(v) + __expect_op = False + elif __expect_op and v[1] == "CLOSE": + op = stack.pop() + while op[1] != "OPEN": + fs = self.__getfunction(op) + self.__expr.append(ExpressionFunction(fs['func'],fs['args'],fs['str'],fs['latex'],op[0],False)) + op = stack.pop() + if len(stack) > 0 and stack[-1][0] in self.core.functions: + op = stack.pop() + fs = self.core.functions[op[0]] + args = argc.pop() + if fs['args'] != '+' and (args != fs['args'] and args not in fs['args']): + raise SyntaxError("Invalid number of arguments for {0:s} function".format(op[0])) + self.__expr.append(ExpressionFunction(fs['func'],args,fs['str'],fs['latex'],op[0],True)) + __expect_op = True + elif __expect_op and v[0] == ",": + argc[-1] += 1 + op = stack.pop() + while op[1] != "OPEN": + fs = self.__getfunction(op) + self.__expr.append(ExpressionFunction(fs['func'],fs['args'],fs['str'],fs['latex'],op[0],False)) + op = stack.pop() + stack.append(op) + __expect_op = False + elif __expect_op and v[0] in self.core.ops: + fn = self.core.ops[v[0]] + if len(stack) == 0: + stack.append(v) + __expect_op = False + v = self.__next(__expect_op) + continue + op = stack.pop() + if op[0] == "(": + stack.append(op) + stack.append(v) + __expect_op = False + v = self.__next(__expect_op) + continue + fs = self.__getfunction(op) + while True: + if (fn['prec'] >= fs['prec']): + self.__expr.append(ExpressionFunction(fs['func'],fs['args'],fs['str'],fs['latex'],op[0],False)) + if len(stack) == 0: + stack.append(v) + break + op = stack.pop() + if op[0] == "(": + stack.append(op) + stack.append(v) + break + fs = self.__getfunction(op) + else: + stack.append(op) + stack.append(v) + break + __expect_op = False + elif not __expect_op and v[0] in self.core.unary_ops: + fn = self.core.unary_ops[v[0]] + stack.append(v) + __expect_op = False + elif not __expect_op and v[0] in self.core.functions: + stack.append(v) + argc.append(1) + __expect_op = False + elif not __expect_op and v[1] == 'NAME': + self.__argsused.add(v[0]) + if v[0] not in self.__args: + self.__args.append(v[0]) + self.__expr.append(ExpressionVariable(v[0])) + __expect_op = True + elif not __expect_op and v[1] == 'VALUE': + self.__expr.append(ExpressionValue(v[0])) + __expect_op = True + else: + raise SyntaxError("Invalid Token \"{0:s}\" in Expression, Expected {1:s}".format(v,"Op" if __expect_op else "Value")) + v = self.__next(__expect_op) + if len(stack) > 0: + op = stack.pop() + while op != "(": + fs = self.__getfunction(op) + self.__expr.append(ExpressionFunction(fs['func'],fs['args'],fs['str'],fs['latex'],op[0],False)) + if len(stack) > 0: + op = stack.pop() + else: + break \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/ExpressionCore.py b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/ExpressionCore.py new file mode 100644 index 00000000..f45de197 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/ExpressionCore.py @@ -0,0 +1,237 @@ +import math +import sys +import re + +if sys.version_info >= (3,): + xrange = range + basestring = str + +class ExpressionObject(object): + def __init__(self,*args,**kwargs): + super(ExpressionObject,self).__init__(*args,**kwargs) + + def toStr(self,args,expression): + return "" + + def toRepr(self,args,expression): + return "" + + def __call__(self,args,expression): + pass + +class ExpressionValue(ExpressionObject): + def __init__(self,value,*args,**kwargs): + super(ExpressionValue,self).__init__(*args,**kwargs) + self.value = value + + def toStr(self,args,expression): + if (isinstance(self.value,complex)): + V = [self.value.real,self.value.imag] + E = [0,0] + B = [0,0] + out = ["",""] + for i in xrange(2): + if V[i] == 0: + E[i] = 0 + B[i] = 0 + else: + E[i] = int(math.floor(math.log10(abs(V[i])))) + B[i] = V[i]*10**-E[i] + if E[i] in [0,1,2,3] and str(V[i])[-2:] == ".0": + B[i] = int(V[i]) + E[i] = 0 + if E[i] in [-1,-2] and len(str(V[i])) <= 7: + B[i] = V[i] + E[i] = 0 + if i == 1: + fmt = "{{0:+{0:s}}}" + else: + fmt = "{{0:-{0:s}}}" + if type(B[i]) == int: + out[i] += fmt.format('d').format(B[i]) + else: + out[i] += fmt.format('.5f').format(B[i]).rstrip("0.") + if i == 1: + out[i] += "\\imath" + if E[i] != 0: + out[i] += "\\times10^{{{0:d}}}".format(E[i]) + return "\\left(" + ''.join(out) + "\\right)" + elif (isinstance(self.value,float)): + V = self.value + E = 0 + B = 0 + out = "" + if V == 0: + E = 0 + B = 0 + else: + E = int(math.floor(math.log10(abs(V)))) + B = V*10**-E + if E in [0,1,2,3] and str(V)[-2:] == ".0": + B = int(V) + E = 0 + if E in [-1,-2] and len(str(V)) <= 7: + B = V + E = 0 + if type(B) == int: + out += "{0:-d}".format(B) + else: + out += "{0:-.5f}".format(B).rstrip("0.") + if E != 0: + out += "\\times10^{{{0:d}}}".format(E) + return "\\left(" + out + "\\right)" + else: + return out + else: + return str(self.value) + + def toRepr(self,args,expression): + return str(self.value) + + def __call__(self,args,expression): + return self.value + + def __repr__(self): + return "<{0:s}.{1:s}({2:s}) object at {3:0=#10x}>".format(type(self).__module__,type(self).__name__,str(self.value),id(self)) + +class ExpressionFunction(ExpressionObject): + def __init__(self,function,nargs,form,display,id,isfunc,*args,**kwargs): + super(ExpressionFunction,self).__init__(*args,**kwargs) + self.function = function + self.nargs = nargs + self.form = form + self.display = display + self.id = id + self.isfunc = isfunc + + def toStr(self,args,expression): + params = [] + for i in xrange(self.nargs): + params.append(args.pop()) + if self.isfunc: + return str(self.display.format(','.join(params[::-1]))) + else: + return str(self.display.format(*params[::-1])) + + def toRepr(self,args,expression): + params = [] + for i in xrange(self.nargs): + params.append(args.pop()) + if self.isfunc: + return str(self.form.format(','.join(params[::-1]))) + else: + return str(self.form.format(*params[::-1])) + + def __call__(self,args,expression): + params = [] + for i in xrange(self.nargs): + params.append(args.pop()) + return self.function(*params[::-1]) + + def __repr__(self): + return "<{0:s}.{1:s}({2:s},{3:d}) object at {4:0=#10x}>".format(type(self).__module__,type(self).__name__,str(self.id),self.nargs,id(self)) + +class ExpressionVariable(ExpressionObject): + def __init__(self,name,*args,**kwargs): + super(ExpressionVariable,self).__init__(*args,**kwargs) + self.name = name + + def toStr(self,args,expression): + return str(self.name) + + def toRepr(self,args,expression): + return str(self.name) + + def __call__(self,args,expression): + if self.name in expression.variables: + return expression.variables[self.name] + else: + return 0 # Default variables to return 0 + + def __repr__(self): + return "<{0:s}.{1:s}({2:s}) object at {3:0=#10x}>".format(type(self).__module__,type(self).__name__,str(self.name),id(self)) + +class Core(): + + constants = {} + unary_ops = {} + ops = {} + functions = {} + smatch = re.compile(r"\s*,") + vmatch = re.compile(r"\s*" + "(?:" + "(?P" + "(?P[+-]?)" + r"\s*0o" + "(?P[0-7]+)" + ")|(?P" + "(?P[+-]?)" + r"\s*0x" + "(?P[0-9a-fA-F]+)" + ")|(?P" + "(?P[+-]?)" + r"\s*0b" + "(?P[01]+)" + ")|(?P" + "(?P[+-]?)" + r"\s*" + r"(?P(?:\d+\.\d+|\d+\.|\.\d+|\d+))" + "(?:" + "[Ee]" + r"(?P[+-]?\d+)" + ")?" + "(?:" + r"\s*" + r"(?P(?(rvalue)\+|))?" + r"\s*" + "(?P(?(rvalue)(?(sep)[+-]?|[+-])|[+-]?)?)" + r"\s*" + r"(?P(?:\d+\.\d+|\d+\.|\.\d+|\d+))" + "(?:" + "[Ee]" + r"(?P[+-]?\d+)" + ")?" + "[ij]" + ")?" + ")" + ")") + nmatch = re.compile(r"\s*([a-zA-Z_][a-zA-Z0-9_]*)") + gsmatch = re.compile(r'\s*(\()') + gematch = re.compile(r'\s*(\))') + + def recalculateFMatch(self): + + fks = sorted(self.functions.keys(), key=len, reverse=True) + oks = sorted(self.ops.keys(), key=len, reverse=True) + uks = sorted(self.unary_ops.keys(), key=len, reverse=True) + self.fmatch = re.compile(r'\s*(' + '|'.join(map(re.escape,fks)) + ')') + self.omatch = re.compile(r'\s*(' + '|'.join(map(re.escape,oks)) + ')') + self.umatch = re.compile(r'\s*(' + '|'.join(map(re.escape,uks)) + ')') + + def addFn(self,id,str,latex,args,func): + self.functions[id] = { + 'str': str, + 'latex': latex, + 'args': args, + 'func': func} + + def addOp(self,id,str,latex,single,prec,func): + if single: + raise RuntimeError("Single Ops Not Yet Supported") + self.ops[id] = { + 'str': str, + 'latex': latex, + 'args': 2, + 'prec': prec, + 'func': func} + + def addUnaryOp(self,id,str,latex,func): + self.unary_ops[id] = { + 'str': str, + 'latex': latex, + 'args': 1, + 'prec': 0, + 'func': func} + + def addConst(self,name,value): + self.constants[name] = value \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/__init__.py b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/__init__.py new file mode 100644 index 00000000..9fcda82e --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/__init__.py @@ -0,0 +1,2 @@ +from . import equation_base as equation_base +from .ExpressionCore import ExpressionValue, ExpressionFunction, ExpressionVariable, Core \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/equation_base.py b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/equation_base.py new file mode 100644 index 00000000..5710c965 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/equation_base.py @@ -0,0 +1,106 @@ +try: + import numpy as np + has_numpy = True +except ImportError: + import math + has_numpy = False +try: + import scipy.constants + has_scipy = True +except ImportError: + has_scipy = False +import operator as op +from .similar import sim, nsim, gsim, lsim + +def equation_extend(core): + def product(*args): + if len(args) == 1 and has_numpy: + return np.prod(args[0]) + else: + return reduce(op.mul,args,1) + + def sumargs(*args): + if len(args) == 1: + return sum(args[0]) + else: + return sum(args) + + core.addOp('+',"({0:s} + {1:s})","\\left({0:s} + {1:s}\\right)",False,3,op.add) + core.addOp('-',"({0:s} - {1:s})","\\left({0:s} - {1:s}\\right)",False,3,op.sub) + core.addOp('*',"({0:s} * {1:s})","\\left({0:s} \\times {1:s}\\right)",False,2,op.mul) + core.addOp('/',"({0:s} / {1:s})","\\frac{{{0:s}}}{{{1:s}}}",False,2,op.truediv) + core.addOp('%',"({0:s} % {1:s})","\\left({0:s} \\bmod {1:s}\\right)",False,2,op.mod) + core.addOp('^',"({0:s} ^ {1:s})","{0:s}^{{{1:s}}}",False,1,op.pow) + core.addOp('**',"({0:s} ^ {1:s})","{0:s}^{{{1:s}}}",False,1,op.pow) + core.addOp('&',"({0:s} & {1:s})","\\left({0:s} \\land {1:s}\\right)",False,4,op.and_) + core.addOp('|',"({0:s} | {1:s})","\\left({0:s} \\lor {1:s}\\right)",False,4,op.or_) + core.addOp('',"({0:s} {1:s})","\\left({0:s} \\oplus {1:s}\\right)",False,4,op.xor) + core.addOp('&|',"({0:s} {1:s})","\\left({0:s} \\oplus {1:s}\\right)",False,4,op.xor) + core.addOp('|&',"({0:s} {1:s})","\\left({0:s} \\oplus {1:s}\\right)",False,4,op.xor) + core.addOp('==',"({0:s} == {1:s})","\\left({0:s} = {1:s}\\right)",False,5,op.eq) + core.addOp('=',"({0:s} == {1:s})","\\left({0:s} = {1:s}\\right)",False,5,op.eq) + core.addOp('~',"({0:s} ~ {1:s})","\\left({0:s} \\approx {1:s}\\right)",False,5,sim) + core.addOp('!~',"({0:s} !~ {1:s})","\\left({0:s} \\not\\approx {1:s}\\right)",False,5,nsim) + core.addOp('!=',"({0:s} != {1:s})","\\left({0:s} \\neg {1:s}\\right)",False,5,op.ne) + core.addOp('<>',"({0:s} != {1:s})","\\left({0:s} \\neg {1:s}\\right)",False,5,op.ne) + core.addOp('><',"({0:s} != {1:s})","\\left({0:s} \\neg {1:s}\\right)",False,5,op.ne) + core.addOp('<',"({0:s} < {1:s})","\\left({0:s} < {1:s}\\right)",False,5,op.lt) + core.addOp('>',"({0:s} > {1:s})","\\left({0:s} > {1:s}\\right)",False,5,op.gt) + core.addOp('<=',"({0:s} <= {1:s})","\\left({0:s} \\leq {1:s}\\right)",False,5,op.le) + core.addOp('>=',"({0:s} >= {1:s})","\\left({0:s} \\geq {1:s}\\right)",False,5,op.ge) + core.addOp('=<',"({0:s} <= {1:s})","\\left({0:s} \\leq {1:s}\\right)",False,5,op.le) + core.addOp('=>',"({0:s} >= {1:s})","\\left({0:s} \\geq {1:s}\\right)",False,5,op.ge) + core.addOp('<~',"({0:s} <~ {1:s})","\\left({0:s} \lessapprox {1:s}\\right)",False,5,lsim) + core.addOp('>~',"({0:s} >~ {1:s})","\\left({0:s} \\gtrapprox {1:s}\\right)",False,5,gsim) + core.addOp('~<',"({0:s} <~ {1:s})","\\left({0:s} \lessapprox {1:s}\\right)",False,5,lsim) + core.addOp('~>',"({0:s} >~ {1:s})","\\left({0:s} \\gtrapprox {1:s}\\right)",False,5,gsim) + core.addUnaryOp('!',"(!{0:s})","\\neg{0:s}",op.not_) + core.addUnaryOp('-',"-{0:s}","-{0:s}",op.neg) + core.addFn('abs',"abs({0:s})","\\left|{0:s}\\right|",1,op.abs) + core.addFn('sum',"sum({0:s})","\\sum\\left({0:s}\\right)",'+',sumargs) + core.addFn('prod',"prod({0:s})","\\prod\\left({0:s}\\right)",'+',product) + if has_numpy: + core.addFn('floor',"floor({0:s})","\\lfloor {0:s} \\rfloor",1,np.floor) + core.addFn('ceil',"ceil({0:s})","\\lceil {0:s} \\rceil",1,np.ceil) + core.addFn('round',"round({0:s})","\\lfloor {0:s} \\rceil",1,np.round) + core.addFn('sin',"sin({0:s})","\\sin\\left({0:s}\\right)",1,np.sin) + core.addFn('cos',"cos({0:s})","\\cos\\left({0:s}\\right)",1,np.cos) + core.addFn('tan',"tan({0:s})","\\tan\\left({0:s}\\right)",1,np.tan) + core.addFn('re',"re({0:s})","\\Re\\left({0:s}\\right)",1,np.real) + core.addFn('im',"re({0:s})","\\Im\\left({0:s}\\right)",1,np.imag) + core.addFn('sqrt',"sqrt({0:s})","\\sqrt{{{0:s}}}",1,np.sqrt) + core.addConst("pi",np.pi) + core.addConst("e",np.e) + core.addConst("Inf",np.Inf) + core.addConst("NaN",np.NaN) + else: + core.addFn('floor',"floor({0:s})","\\lfloor {0:s} \\rfloor",1,math.floor) + core.addFn('ceil',"ceil({0:s})","\\lceil {0:s} \\rceil",1,math.ceil) + core.addFn('round',"round({0:s})","\\lfloor {0:s} \\rceil",1,round) + core.addFn('sin',"sin({0:s})","\\sin\\left({0:s}\\right)",1,math.sin) + core.addFn('cos',"cos({0:s})","\\cos\\left({0:s}\\right)",1,math.cos) + core.addFn('tan',"tan({0:s})","\\tan\\left({0:s}\\right)",1,math.tan) + core.addFn('re',"re({0:s})","\\Re\\left({0:s}\\right)",1,complex.real) + core.addFn('im',"re({0:s})","\\Im\\left({0:s}\\right)",1,complex.imag) + core.addFn('sqrt',"sqrt({0:s})","\\sqrt{{{0:s}}}",1,math.sqrt) + core.addConst("pi",math.pi) + core.addConst("e",math.e) + core.addConst("Inf",float("Inf")) + core.addConst("NaN",float("NaN")) + if has_scipy: + core.addConst("h",scipy.constants.h) + core.addConst("hbar",scipy.constants.hbar) + core.addConst("m_e",scipy.constants.m_e) + core.addConst("m_p",scipy.constants.m_p) + core.addConst("m_n",scipy.constants.m_n) + core.addConst("c",scipy.constants.c) + core.addConst("N_A",scipy.constants.N_A) + core.addConst("mu_0",scipy.constants.mu_0) + core.addConst("eps_0",scipy.constants.epsilon_0) + core.addConst("k",scipy.constants.k) + core.addConst("G",scipy.constants.G) + core.addConst("g",scipy.constants.g) + core.addConst("q",scipy.constants.e) + core.addConst("R",scipy.constants.R) + core.addConst("sigma",scipy.constants.e) + core.addConst("Rb",scipy.constants.Rydberg) \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/similar.py b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/similar.py new file mode 100644 index 00000000..faf2979e --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/Hybrid_Utils/similar.py @@ -0,0 +1,49 @@ +_tol = 1e-5 + +def sim(a,b): + if (a==b): + return True + elif a == 0 or b == 0: + return False + if (a_tol + else: + return (1-b/a)>_tol + +def gsim(a,b): + if a >= b: + return True + return (1-a/b)<=_tol + +def lsim(a,b): + if a <= b: + return True + return (1-b/a)<=_tol + +def set_tol(value=1e-5): + r"""Set Error Tolerance + + Set the tolerance for detriming if two numbers are simliar, i.e + :math:`\left|\frac{a}{b}\right| = 1 \pm tolerance` + + Parameters + ---------- + value: float + The Value to set the tolerance to show be very small as it respresents the + percentage of acceptable error in detriming if two values are the same. + """ + global _tol + if isinstance(value,float): + _tol = value + else: + raise TypeError(type(value)) \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/RegexInplaceParser.py b/analysis-master/tra_analysis/equation/parser/RegexInplaceParser.py new file mode 100644 index 00000000..d8886878 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/RegexInplaceParser.py @@ -0,0 +1,51 @@ +import re +from decimal import Decimal +from functools import reduce + +class RegexInplaceParser(object): + + 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/parser/__init__.py b/analysis-master/tra_analysis/equation/parser/__init__.py new file mode 100644 index 00000000..7c0a4447 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/__init__.py @@ -0,0 +1,34 @@ +# Titan Robotics Team 2022: Expression submodule +# Written by Arthur Lu +# Notes: +# this should be imported as a python module using 'from tra_analysis.Equation import parser' +# setup: + +__version__ = "0.0.4-alpha" + +__changelog__ = """changelog: + 0.0.4-alpha: + - moved individual parsers to their own files + 0.0.3-alpha: + - readded old regex based parser as RegexInplaceParser + 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 ", +) + +__all__ = { + "BNF", + "RegexInplaceParser", + "HybridExpressionParser" +} + +from .BNF import BNF as BNF +from .RegexInplaceParser import RegexInplaceParser as RegexInplaceParser +from .Hybrid import HybridExpressionParser +from .Hybrid_Utils import equation_base, Core \ No newline at end of file diff --git a/analysis-master/tra_analysis/equation/parser/py2.py b/analysis-master/tra_analysis/equation/parser/py2.py new file mode 100644 index 00000000..862f0e98 --- /dev/null +++ b/analysis-master/tra_analysis/equation/parser/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