symbolic math: add <symbolicresponse> type; improve error handling in

symmath library; add options for matrices, qubit to symmath_check
This commit is contained in:
ichuang
2012-06-02 17:17:48 -04:00
parent 9dab2ce4e7
commit 2f33bce7ad
4 changed files with 201 additions and 30 deletions

View File

@@ -30,7 +30,7 @@ from lxml import etree
import requests
from copy import deepcopy
print "[lib.sympy_check.formula] Warning: Dark code. Needs review before enabling in prod."
print "[lib.symmath.formula] Warning: Dark code. Needs review before enabling in prod."
os.environ['PYTHONIOENCODING'] = 'utf-8'
@@ -143,11 +143,12 @@ 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=''):
def __init__(self,expr,asciimath='',options=None):
self.expr = expr.strip()
self.asciimath = asciimath
self.the_cmathml = None
self.the_sympy = None
self.options = options
def is_presentation_mathml(self):
return '<mstyle' in self.expr
@@ -249,7 +250,8 @@ class formula(object):
def make_sympy(self,xml=None):
'''
Return sympy expression for the math formula
Return sympy expression for the math formula.
The math formula is converted to Content MathML then that is parsed.
'''
if self.the_sympy: return self.the_sympy
@@ -258,7 +260,11 @@ class formula(object):
if not self.is_mathml():
return my_sympify(self.expr)
if self.is_presentation_mathml():
xml = etree.fromstring(str(self.cmathml))
try:
cmml = self.cmathml
xml = etree.fromstring(str(cmml))
except Exception,err:
raise Exception,'Err %s while converting cmathml to xml; cmml=%s' % (err,cmml)
xml = self.fix_greek_in_mathml(xml)
self.the_sympy = self.make_sympy(xml[0])
else:
@@ -277,7 +283,7 @@ class formula(object):
# 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_plus(*args): return args[0] if len(args)==1 else op_plus(*args[:-1])+args[-1]
def op_times(*args): return reduce(operator.mul,args)
def op_minus(*args):
@@ -317,7 +323,7 @@ class formula(object):
elif tag=='msup': return '^'.join([parsePresentationMathMLSymbol(y) for y in xml])
raise Exception,'[parsePresentationMathMLSymbol] unknown tag %s' % tag
# parser tree for content MathML
# parser tree for Content MathML
tag = gettag(xml)
print "tag = ",tag
@@ -328,7 +334,13 @@ class formula(object):
if opstr in opdict:
op = opdict[opstr]
args = [ self.make_sympy(x) for x in xml[1:]]
return op(*args)
try:
res = op(*args)
except Exception,err:
self.args = args
self.op = op
raise Exception,'[formula] error=%s failed to apply %s to args=%s' % (err,opstr,args)
return res
else:
raise Exception,'[formula]: unknown operator tag %s' % (opstr)
@@ -351,7 +363,7 @@ class formula(object):
return float(xml.text)
elif tag=='ci': # variable (symbol)
if len(xml)>0 and (gettag(xml[0])=='msub' or gettag(xml[0])=='msup'):
if len(xml)>0 and (gettag(xml[0])=='msub' or gettag(xml[0])=='msup'): # subscript or superscript
usym = parsePresentationMathMLSymbol(xml[0])
sym = sympy.Symbol(str(usym))
else:
@@ -359,7 +371,11 @@ class formula(object):
if 'hat' in usym:
sym = my_sympify(usym)
else:
sym = sympy.Symbol(str(usym))
if usym=='i': print "options=",self.options
if usym=='i' and 'imaginary' in self.options: # i = sqrt(-1)
sym = sympy.I
else:
sym = sympy.Symbol(str(usym))
return sym
else: # unknown tag
@@ -462,3 +478,78 @@ def test4():
</math>
'''
return formula(xmlstr)
def test5(): # sum of two matrices
xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mstyle displaystyle="true">
<mrow>
<mi>cos</mi>
<mrow>
<mo>(</mo>
<mi>&#x3B8;</mi>
<mo>)</mo>
</mrow>
</mrow>
<mo>&#x22C5;</mo>
<mrow>
<mo>[</mo>
<mtable>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
</mtable>
<mo>]</mo>
</mrow>
<mo>+</mo>
<mrow>
<mo>[</mo>
<mtable>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
</mtable>
<mo>]</mo>
</mrow>
</mstyle>
</math>
'''
return formula(xmlstr)
def test6(): # imaginary numbers
xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mstyle displaystyle="true">
<mn>1</mn>
<mo>+</mo>
<mi>i</mi>
</mstyle>
</math>
'''
return formula(xmlstr,options='imaginaryi')

View File

@@ -137,7 +137,7 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False
#
# This is one of the main entry points to call.
def symmath_check(expect,ans,adict={},abname=''):
def symmath_check(expect,ans,dynamath=None,options=None,debug=None):
'''
Check a symbolic mathematical expression using sympy.
The input may be presentation MathML. Uses formula.
@@ -148,22 +148,27 @@ def symmath_check(expect,ans,adict={},abname=''):
# msg += '<p/>adict=%s' % (repr(adict).replace('<','&lt;'))
threshold = 1.0e-3
DEBUG = True
DEBUG = debug
# options
do_matrix = 'matrix' in (options or '')
do_qubit = 'qubit' in (options or '')
do_imaginary = 'imaginary' in (options or '')
# parse expected answer
try:
fexpect = my_sympify(str(expect))
fexpect = my_sympify(str(expect),matrix=do_matrix,do_qubit=do_qubit)
except Exception,err:
msg += '<p>Error %s in parsing OUR expected answer "%s"</p>' % (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))
fans = my_sympify(str(ans),matrix=do_matrix,do_qubit=do_qubit)
except Exception,err:
fans = None
if fexpect.is_number and fans and fans.is_number:
if hasattr(fexpect,'is_number') and fexpect.is_number and fans and hasattr(fans,'is_number') and fans.is_number:
if abs(abs(fans-fexpect)/fexpect)<threshold:
return {'ok':True,'msg':msg}
else:
@@ -175,10 +180,12 @@ def symmath_check(expect,ans,adict={},abname=''):
return {'ok':True,'msg':msg}
# convert mathml answer to formula
mmlbox = abname+'_fromjs'
if mmlbox in adict:
mmlans = adict[mmlbox]
f = formula(mmlans)
try:
if dynamath:
mmlans = dynamath[0]
except Exception,err:
return {'ok':False,'msg':'[symmath_check] failed to get MathML for input; dynamath=%s' % dynamath}
f = formula(mmlans,options=options)
# get sympy representation of the formula
# if DEBUG: msg += '<p/> mmlans=%s' % repr(mmlans).replace('<','&lt;')
@@ -186,13 +193,20 @@ def symmath_check(expect,ans,adict={},abname=''):
fsym = f.sympy
msg += '<p>You entered: %s</p>' % to_latex(f.sympy)
except Exception,err:
msg += "<p>Error %s in converting to sympy</p>" % str(err).replace('<','&lt;')
if DEBUG: msg += "<p><pre>%s</pre></p>" % traceback.format_exc()
msg += "<p>Error %s in evaluating your expression '%s' as a valid equation</p>" % (str(err).replace('<','&lt;'),
ans)
if DEBUG:
msg += '<hr>'
msg += '<p><font color="blue">DEBUG messages:</p>'
msg += "<p><pre>%s</pre></p>" % traceback.format_exc()
msg += '<p>cmathml=<pre>%s</pre></p>' % f.cmathml.replace('<','&lt;')
msg += '<p>pmathml=<pre>%s</pre></p>' % mmlans.replace('<','&lt;')
msg += '<hr>'
return {'ok':False,'msg':msg}
# compare with expected
if fexpect.is_number:
if fsym.is_number:
if hasattr(fexpect,'is_number') and fexpect.is_number:
if hasattr(fsym,'is_number') and fsym.is_number:
if abs(abs(fsym-fexpect)/fexpect)<threshold:
return {'ok':True,'msg':msg}
return {'ok':False,'msg':msg}
@@ -225,12 +239,15 @@ def symmath_check(expect,ans,adict={},abname=''):
diff = None
if DEBUG:
msg += '<hr>'
msg += '<p><font color="blue">DEBUG messages:</p>'
msg += "<p>Got: %s</p>" % repr(fsym)
# msg += "<p/>Got: %s" % str([type(x) for x in fsym.atoms()]).replace('<','&lt;')
msg += "<p>Expecting: %s</p>" % repr(fexpect).replace('**','^').replace('hat(I)','hat(i)')
# msg += "<p/>Expecting: %s" % str([type(x) for x in fexpect.atoms()]).replace('<','&lt;')
if diff:
msg += "<p>Difference: %s</p>" % to_latex(diff)
msg += '<hr>'
return {'ok':False,'msg':msg,'ex':fexpect,'got':fsym}