diff --git a/courseware/capa/calc.py b/courseware/capa/calc.py index bd4136d393..41ced03e58 100644 --- a/courseware/capa/calc.py +++ b/courseware/capa/calc.py @@ -1,17 +1,41 @@ +import copy import math import operator +import numpy + from pyparsing import Word, alphas, nums, oneOf, Literal from pyparsing import ZeroOrMore, OneOrMore, StringStart from pyparsing import StringEnd, Optional, Forward from pyparsing import CaselessLiteral, Group, StringEnd from pyparsing import NoMatch, stringEnd +default_functions = {'sin' : numpy.sin, + 'cos' : numpy.cos, + 'tan' : numpy.tan, + 'sqrt': numpy.sqrt, + 'log10':numpy.log10, + 'log2':numpy.log2, + 'ln': numpy.log, + 'arccos':numpy.arccos, + 'arcsin':numpy.arcsin, + 'arctan':numpy.arctan, + 'abs':numpy.abs + } +default_variables = {'j':numpy.complex(0,1), + 'e':numpy.complex(numpy.e) + } + def evaluator(variables, functions, string): ''' Evaluate an expression. Variables are passed as a dictionary from string to value. Unary functions are passed as a dictionary from string to function ''' + all_variables = copy.copy(default_variables) + all_variables.update(variables) + all_functions = copy.copy(default_functions) + all_functions.update(functions) + if string.strip() == "": return float('nan') ops = { "^" : operator.pow, @@ -38,7 +62,7 @@ def evaluator(variables, functions, string): def number_parse_action(x): # [ '7' ] -> [ 7 ] return [super_float("".join(x))] def exp_parse_action(x): # [ 2 ^ 3 ^ 2 ] -> 512 - x = [e for e in x if type(e) == float] # Ignore ^ + x = [e for e in x if type(e) in [float, numpy.float64, numpy.complex]] # Ignore ^ x.reverse() x=reduce(lambda a,b:b**a, x) return x @@ -68,7 +92,7 @@ def evaluator(variables, functions, string): prod=op(prod, e) return prod def func_parse_action(x): - return [functions[x[0]](x[1])] + return [all_functions[x[0]](x[1])] number_suffix=reduce(lambda a,b:a|b, map(Literal,suffixes.keys()), NoMatch()) # SI suffixes and percent (dot,minus,plus,times,div,lpar,rpar,exp)=map(Literal,".-+*/()^") @@ -94,14 +118,14 @@ def evaluator(variables, functions, string): # Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution. # Special case for no variables because of how we understand PyParsing is put together - if len(variables)>0: - varnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), variables.keys())) - varnames.setParseAction(lambda x:map(lambda y:variables[y], x)) + if len(all_variables)>0: + varnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_variables.keys())) + varnames.setParseAction(lambda x:map(lambda y:all_variables[y], x)) else: varnames=NoMatch() # Same thing for functions. - if len(functions)>0: - funcnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), functions.keys())) + if len(all_functions)>0: + funcnames = sreduce(lambda x,y:x|y, map(lambda x: CaselessLiteral(x), all_functions.keys())) function = funcnames+lpar.suppress()+expr+rpar.suppress() function.setParseAction(func_parse_action) else: @@ -119,7 +143,7 @@ def evaluator(variables, functions, string): if __name__=='__main__': variables={'R1':2.0, 'R3':4.0} - functions={'sin':math.sin, 'cos':math.cos} + functions={'sin':numpy.sin, 'cos':numpy.cos} print "X",evaluator(variables, functions, "10000||sin(7+5)-6k") print "X",evaluator(variables, functions, "13") print evaluator({'R1': 2.0, 'R3':4.0}, {}, "13") diff --git a/util/views.py b/util/views.py index 5f0400d143..83d525708a 100644 --- a/util/views.py +++ b/util/views.py @@ -25,7 +25,7 @@ def calculate(request): 'equation':equation} track.views.server_track(request, 'error:calc', event, page='calc') return HttpResponse(json.dumps({'result':'Invalid syntax'})) - return HttpResponse(json.dumps({'result':result})) + return HttpResponse(json.dumps({'result':str(result)})) def send_feedback(request): ''' Feeback mechanism in footer of every page. '''