diff --git a/lib/sympy_check/__init__.py b/lib/sympy_check/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/sympy_check/formula.py b/lib/sympy_check/formula.py new file mode 100644 index 0000000000..01ced8650c --- /dev/null +++ b/lib/sympy_check/formula.py @@ -0,0 +1,461 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# File: formula.py +# Date: 04-May-12 +# Author: I. Chuang +# +# flexible python representation of a symbolic mathematical formula. +# Acceptes Presentation MathML, Content MathML (and could also do OpenMath) +# Provides sympy representation. + +import os, sys, string, re +import operator +import sympy +from sympy.printing.latex import LatexPrinter +from sympy.printing.str import StrPrinter +from sympy import latex, sympify +from sympy.physics.quantum.qubit import * +from sympy.physics.quantum.state import * +# from sympy import exp, pi, I +# from sympy.core.operations import LatticeOp +# import sympy.physics.quantum.qubit + +import urllib +from xml.sax.saxutils import escape, unescape +import sympy +import unicodedata +from lxml import etree +#import subprocess +import requests +from copy import deepcopy + +print "Warning: Dark code. Needs review before enabling in prod." + +os.environ['PYTHONIOENCODING'] = 'utf-8' + +#----------------------------------------------------------------------------- + +class dot(sympy.operations.LatticeOp): # my dot product + zero = sympy.Symbol('dotzero') + identity = sympy.Symbol('dotidentity') + +#class dot(sympy.Mul): # my dot product +# is_Mul = False + +def _print_dot(self,expr): + return '{((%s) \cdot (%s))}' % (expr.args[0],expr.args[1]) + +LatexPrinter._print_dot = _print_dot + +#----------------------------------------------------------------------------- +# unit vectors (for 8.02) + +def _print_hat(self,expr): return '\\hat{%s}' % str(expr.args[0]).lower() + +LatexPrinter._print_hat = _print_hat +StrPrinter._print_hat = _print_hat + +#----------------------------------------------------------------------------- +# helper routines + +def to_latex(x): + if x==None: return '' + # LatexPrinter._print_dot = _print_dot + xs = latex(x) + xs = xs.replace(r'\XI','XI') # workaround for strange greek + #return '%s{}{}' % (xs[1:-1]) + if xs[0]=='$': + return '[mathjax]%s[/mathjax]
' % (xs[1:-1]) # for sympy v6 + return '[mathjax]%s[/mathjax]
' % (xs) # for sympy v7 + +def my_evalf(expr,chop=False): + if type(expr)==list: + try: + return [x.evalf(chop=chop) for x in expr] + except: + return expr + try: + return expr.evalf(chop=chop) + except: + return expr + +#----------------------------------------------------------------------------- +# my version of sympify to import expression into sympy + +def my_sympify(expr,normphase=False,matrix=False,abcsym=False,do_qubit=False,symtab=None): + # make all lowercase real? + if symtab: + varset = symtab + else: + varset = {'p':sympy.Symbol('p'), + 'g':sympy.Symbol('g'), + 'e':sympy.E, # for exp + 'i':sympy.I, # lowercase i is also sqrt(-1) + 'Q':sympy.Symbol('Q'), # otherwise it is a sympy "ask key" + #'X':sympy.sympify('Matrix([[0,1],[1,0]])'), + #'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'), + #'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'), + 'ZZ':sympy.Symbol('ZZ'), # otherwise it is the PythonIntegerRing + 'XI':sympy.Symbol('XI'), # otherwise it is the capital \XI + 'hat':sympy.Function('hat'), # for unit vectors (8.02) + } + if do_qubit: # turn qubit(...) into Qubit instance + varset.update({'qubit':sympy.physics.quantum.qubit.Qubit, + 'Ket':sympy.physics.quantum.state.Ket, + 'dot':dot, + 'bit':sympy.Function('bit'), + }) + if abcsym: # consider all lowercase letters as real symbols, in the parsing + for letter in string.lowercase: + if letter in varset: # exclude those already done + continue + varset.update({letter:sympy.Symbol(letter,real=True)}) + + sexpr = sympify(expr,locals=varset) + if normphase: # remove overall phase if sexpr is a list + if type(sexpr)==list: + if sexpr[0].is_number: + ophase = sympy.sympify('exp(-I*arg(%s))' % sexpr[0]) + sexpr = [ sympy.Mul(x,ophase) for x in sexpr ] + + def to_matrix(x): # if x is a list of lists, and is rectangular, then return Matrix(x) + if not type(x)==list: + return x + for row in x: + if (not type(row)==list): + return x + rdim = len(x[0]) + for row in x: + if not len(row)==rdim: + return x + return sympy.Matrix(x) + + if matrix: + sexpr = to_matrix(sexpr) + return sexpr + +#----------------------------------------------------------------------------- +# class for symbolic mathematical formulas + +class formula(object): + ''' + Representation of a mathematical formula object. Accepts mathml math expression for constructing, + and can produce sympy translation. The formula may or may not include an assignment (=). + ''' + def __init__(self,expr,asciimath=''): + self.expr = expr.strip() + self.asciimath = asciimath + self.the_cmathml = None + self.the_sympy = None + + def is_presentation_mathml(self): + return 'f-2" + # this is really terrible for turning into cmathml. + # undo this here. + def fix_pmathml(xml): + for k in xml: + tag = gettag(k) + if tag=='mrow': + if len(k)==2: + if gettag(k[0])=='mi' and k[0].text in ['f','g'] and gettag(k[1])=='mo': + idx = xml.index(k) + xml.insert(idx,deepcopy(k[0])) # drop the container + xml.insert(idx+1,deepcopy(k[1])) + xml.remove(k) + fix_pmathml(k) + + fix_pmathml(xml) + + # hat i is turned into i^ ; mangle this into hat(f) + # hat i also somtimes turned into j ^ + + def fix_hat(xml): + for k in xml: + tag = gettag(k) + if tag=='mover': + if len(k)==2: + if gettag(k[0])=='mi' and gettag(k[1])=='mo' and str(k[1].text)=='^': + newk = etree.Element('mi') + newk.text = 'hat(%s)' % k[0].text + xml.replace(k,newk) + if gettag(k[0])=='mrow' and gettag(k[0][0])=='mi' and gettag(k[1])=='mo' and str(k[1].text)=='^': + newk = etree.Element('mi') + newk.text = 'hat(%s)' % k[0][0].text + xml.replace(k,newk) + fix_hat(k) + fix_hat(xml) + + self.xml = xml + return self.xml + + def get_content_mathml(self): + if self.the_cmathml: return self.the_cmathml + + # pre-process the presentation mathml before sending it to snuggletex to convert to content mathml + xml = self.preprocess_pmathml(self.expr) + pmathml = etree.tostring(xml,pretty_print=True) + self.the_pmathml = pmathml + + # convert to cmathml + self.the_cmathml = self.GetContentMathML(self.asciimath,pmathml) + return self.the_cmathml + + cmathml = property(get_content_mathml,None,None,'content MathML representation') + + def make_sympy(self,xml=None): + ''' + Return sympy expression for the math formula + ''' + + if self.the_sympy: return self.the_sympy + + if xml==None: # root + if not self.is_mathml(): + return my_sympify(self.expr) + if self.is_presentation_mathml(): + xml = etree.fromstring(str(self.cmathml)) + xml = self.fix_greek_in_mathml(xml) + self.the_sympy = self.make_sympy(xml[0]) + else: + xml = etree.fromstring(self.expr) + xml = self.fix_greek_in_mathml(xml) + self.the_sympy = self.make_sympy(xml[0]) + return self.the_sympy + + def gettag(x): + return re.sub('{http://[^}]+}','',x.tag) + + # simple math + def op_divide(*args): + if not len(args)==2: + raise Exception,'divide given wrong number of arguments!' + # print "divide: arg0=%s, arg1=%s" % (args[0],args[1]) + return sympy.Mul(args[0],sympy.Pow(args[1],-1)) + + def op_plus(*args): return sum(args) + def op_times(*args): return reduce(operator.mul,args) + + def op_minus(*args): + if len(args)==1: + return -args[0] + if not len(args)==2: + raise Exception,'minus given wrong number of arguments!' + #return sympy.Add(args[0],-args[1]) + return args[0]-args[1] + + opdict = {'plus': op_plus, + 'divide' : operator.div, + 'times' : op_times, + 'minus' : op_minus, + #'plus': sympy.Add, + #'divide' : op_divide, + #'times' : sympy.Mul, + 'minus' : op_minus, + 'root' : sympy.sqrt, + 'power' : sympy.Pow, + 'sin': sympy.sin, + 'cos': sympy.cos, + } + + # simple sumbols + nums1dict = {'pi': sympy.pi, + } + + def parsePresentationMathMLSymbol(xml): + ''' + Parse , , , and + ''' + tag = gettag(xml) + if tag=='mn': return xml.text + elif tag=='mi': return xml.text + elif tag=='msub': return '_'.join([parsePresentationMathMLSymbol(y) for y in xml]) + elif tag=='msup': return '^'.join([parsePresentationMathMLSymbol(y) for y in xml]) + raise Exception,'[parsePresentationMathMLSymbol] unknown tag %s' % tag + + # parser tree for content MathML + tag = gettag(xml) + print "tag = ",tag + + # first do compound objects + + if tag=='apply': # apply operator + opstr = gettag(xml[0]) + if opstr in opdict: + op = opdict[opstr] + args = [ self.make_sympy(x) for x in xml[1:]] + return op(*args) + else: + raise Exception,'[formula]: unknown operator tag %s' % (opstr) + + elif tag=='list': # square bracket list + if gettag(xml[0])=='matrix': + return self.make_sympy(xml[0]) + else: + return [ self.make_sympy(x) for x in xml ] + + elif tag=='matrix': + return sympy.Matrix([ self.make_sympy(x) for x in xml ]) + + elif tag=='vector': + return [ self.make_sympy(x) for x in xml ] + + # atoms are below + + elif tag=='cn': # number + return sympy.sympify(xml.text) + return float(xml.text) + + elif tag=='ci': # variable (symbol) + if len(xml)>0 and (gettag(xml[0])=='msub' or gettag(xml[0])=='msup'): + usym = parsePresentationMathMLSymbol(xml[0]) + sym = sympy.Symbol(str(usym)) + else: + usym = unicode(xml.text) + if 'hat' in usym: + sym = my_sympify(usym) + else: + sym = sympy.Symbol(str(usym)) + return sym + + else: # unknown tag + raise Exception,'[formula] unknown tag %s' % tag + + sympy = property(make_sympy,None,None,'sympy representation') + + def GetContentMathML(self,asciimath,mathml): + # URL = 'http://192.168.1.2:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo' + URL = 'http://127.0.0.1:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo' + + if 1: + payload = {'asciiMathInput':asciimath, + 'asciiMathML':mathml, + #'asciiMathML':unicode(mathml).encode('utf-8'), + } + headers = {'User-Agent':"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13"} + r = requests.post(URL,data=payload,headers=headers) + r.encoding = 'utf-8' + ret = r.text + #print "encoding: ",r.encoding + + # return ret + + mode = 0 + cmathml = [] + for k in ret.split('\n'): + if 'conversion to Content MathML' in k: + mode = 1 + continue + if mode==1: + if '

Maxima Input Form

' in k: + mode = 0 + continue + cmathml.append(k) + # return '\n'.join(cmathml) + cmathml = '\n'.join(cmathml[2:]) + cmathml = '\n' + unescape(cmathml) + '\n' + # print cmathml + #return unicode(cmathml) + return cmathml + +#----------------------------------------------------------------------------- + +def test1(): + xmlstr = ''' + + + + 1 + 2 + + + ''' + return formula(xmlstr) + +def test2(): + xmlstr = u''' + + + + 1 + + + 2 + α + + + + ''' + return formula(xmlstr) + +def test3(): + xmlstr = ''' + + + + 1 + + + 2 + γ + + + + ''' + return formula(xmlstr) + +def test4(): + xmlstr = u''' + + + 1 + + + + 2 + α + + + +''' + return formula(xmlstr) diff --git a/lib/sympy_check/sympy_check2.py b/lib/sympy_check/sympy_check2.py new file mode 100644 index 0000000000..abb70ed165 --- /dev/null +++ b/lib/sympy_check/sympy_check2.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# File: sympy_check2.py +# Date: 02-May-12 +# Author: I. Chuang +# +# Use sympy to check for expression equality +# +# Takes in math expressions given as Presentation MathML (from ASCIIMathML), converts to Content MathML using SnuggleTeX + +import os, sys, string, re +import traceback +from formula import * + +#----------------------------------------------------------------------------- +# check function interface + +def sympy_check(expect,ans,adict={},symtab=None,extra_options=None): + + options = {'__MATRIX__':False,'__ABC__':False,'__LOWER__':False} + if extra_options: options.update(extra_options) + for op in options: # find options in expect string + if op in expect: + expect = expect.replace(op,'') + options[op] = True + expect = expect.replace('__OR__','__or__') # backwards compatibility + + if options['__LOWER__']: + expect = expect.lower() + ans = ans.lower() + + try: + ret = check(expect,ans, + matrix=options['__MATRIX__'], + abcsym=options['__ABC__'], + symtab=symtab, + ) + except Exception, err: + return {'ok': False, + 'msg': 'Error %s
Failed in evaluating check(%s,%s)' % (err,expect,ans) + } + return ret + +#----------------------------------------------------------------------------- +# pretty generic checking function + +def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False,do_qubit=True,symtab=None,dosimplify=False): + """ + Returns dict with + + 'ok': True if check is good, False otherwise + 'msg': response message (in HTML) + + "expect" may have multiple possible acceptable answers, separated by "__OR__" + + """ + + if "__or__" in expect: # if multiple acceptable answers + eset = expect.split('__or__') # then see if any match + for eone in eset: + ret = check(eone,given,numerical,matrix,normphase,abcsym,do_qubit,symtab,dosimplify) + if ret['ok']: + return ret + return ret + + flags = {} + if "__autonorm__" in expect: + flags['autonorm']=True + expect = expect.replace('__autonorm__','') + matrix = True + + threshold = 1.0e-3 + if "__threshold__" in expect: + (expect,st) = expect.split('__threshold__') + threshold = float(st) + numerical=True + + if str(given)=='' and not (str(expect)==''): + return {'ok': False, 'msg': ''} + + try: + xgiven = my_sympify(given,normphase,matrix,do_qubit=do_qubit,abcsym=abcsym,symtab=symtab) + except Exception,err: + return {'ok': False,'msg': 'Error %s
in evaluating your expression "%s"' % (err,given)} + + try: + xexpect = my_sympify(expect,normphase,matrix,do_qubit=do_qubit,abcsym=abcsym,symtab=symtab) + except Exception,err: + return {'ok': False,'msg': 'Error %s
in evaluating OUR expression "%s"' % (err,expect)} + + if 'autonorm' in flags: # normalize trace of matrices + try: + xgiven /= xgiven.trace() + except Exception, err: + return {'ok': False,'msg': 'Error %s
in normalizing trace of your expression %s' % (err,to_latex(xgiven))} + try: + xexpect /= xexpect.trace() + except Exception, err: + return {'ok': False,'msg': 'Error %s
in normalizing trace of OUR expression %s' % (err,to_latex(xexpect))} + + msg = 'Your expression was evaluated as ' + to_latex(xgiven) + # msg += '
Expected ' + to_latex(xexpect) + + # msg += "
flags=%s" % flags + + if matrix and numerical: + xgiven = my_evalf(xgiven,chop=True) + dm = my_evalf(sympy.Matrix(xexpect)-sympy.Matrix(xgiven),chop=True) + msg += " = " + to_latex(xgiven) + if abs(dm.vec().norm().evalf())expect='%s', given='%s'" % (expect,given) # debugging + # msg += "

dot test " + to_latex(dot(sympy.Symbol('x'),sympy.Symbol('y'))) + return {'ok': False,'msg': msg } + +#----------------------------------------------------------------------------- +# Check function interface, which takes pmathml input + +def sympy_check2(expect,ans,adict={},abname=''): + + msg = '' + # msg += '

abname=%s' % abname + # msg += '

adict=%s' % (repr(adict).replace('<','<')) + + threshold = 1.0e-3 + DEBUG = True + + # parse expected answer + try: + fexpect = my_sympify(str(expect)) + except Exception,err: + msg += '

Error %s in parsing OUR expected answer "%s"

' % (err,expect) + return {'ok':False,'msg':msg} + + # if expected answer is a number, try parsing provided answer as a number also + try: + fans = my_sympify(str(ans)) + except Exception,err: + fans = None + + if fexpect.is_number and fans and fans.is_number: + if abs(abs(fans-fexpect)/fexpect) + + + 1 + 2 + + + ( + 1 + + + + + + k + e + + + Q + + q + + + m + + + g + + + + h + 2 + + + + ) + + + +'''.strip() + z = "1/2(1+(k_e* Q* q)/(m *g *h^2))" + r = sympy_check2(x,z,{'a':z,'a_fromjs':y},'a') + return r +