Commit cd9b80c5 by ichuang

Merge pull request #731 from MITx/feature/ichuang/symmath_improvements

improvements in symbolic math checking.

Merging now because it is needed for Physics courses.
parents 8251ee60 984a7179
......@@ -173,7 +173,7 @@ def get_course_tabs(user, course, active_page):
"""
Return the tabs to show a particular user, as a list of CourseTab items.
"""
if not course.tabs:
if not hasattr(course,'tabs') or not course.tabs:
return get_default_tabs(user, course, active_page)
# TODO (vshnayder): There needs to be a place to call this right after course
......
......@@ -105,6 +105,7 @@ def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False
'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"
'I': sympy.Symbol('I'), # otherwise it is sqrt(-1)
#'X':sympy.sympify('Matrix([[0,1],[1,0]])'),
#'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'),
#'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'),
......@@ -273,11 +274,16 @@ class formula(object):
if not self.is_mathml():
return my_sympify(self.expr)
if self.is_presentation_mathml():
cmml = None
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)
if 'conversion from Presentation MathML to Content MathML was not successful' in cmml:
msg = "Illegal math expression"
else:
msg = 'Err %s while converting cmathml to xml; cmml=%s' % (err, cmml)
raise Exception, msg
xml = self.fix_greek_in_mathml(xml)
self.the_sympy = self.make_sympy(xml[0])
else:
......@@ -320,6 +326,24 @@ class formula(object):
'power': sympy.Pow,
'sin': sympy.sin,
'cos': sympy.cos,
'tan': sympy.tan,
'cot': sympy.cot,
'sinh': sympy.sinh,
'cosh': sympy.cosh,
'coth': sympy.coth,
'tanh': sympy.tanh,
'asin': sympy.asin,
'acos': sympy.acos,
'atan': sympy.atan,
'atan2': sympy.atan2,
'acot': sympy.acot,
'asinh': sympy.asinh,
'acosh': sympy.acosh,
'atanh': sympy.atanh,
'acoth': sympy.acoth,
'exp': sympy.exp,
'log': sympy.log,
'ln': sympy.ln,
}
# simple sumbols
......@@ -385,8 +409,7 @@ class formula(object):
if 'hat' in usym:
sym = my_sympify(usym)
else:
if usym == 'i': print "options=", self.options
if usym == 'i' and 'imaginary' in self.options: # i = sqrt(-1)
if usym == 'i' and self.options is not None and 'imaginary' in self.options: # i = sqrt(-1)
sym = sympy.I
else:
sym = sympy.Symbol(str(usym))
......
......@@ -141,12 +141,19 @@ def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym=
return {'ok': False, 'msg': msg}
#-----------------------------------------------------------------------------
# helper function to convert all <p> to <span class='inline-error'>
def make_error_message(msg):
# msg = msg.replace('<p>','<p><span class="inline-error">').replace('</p>','</span></p>')
msg = '<div class="capa_alert">%s</div>' % msg
return msg
#-----------------------------------------------------------------------------
# Check function interface, which takes pmathml input
#
# This is one of the main entry points to call.
def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None):
'''
Check a symbolic mathematical expression using sympy.
The input may be presentation MathML. Uses formula.
......@@ -159,17 +166,23 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
threshold = 1.0e-3
DEBUG = debug
if xml is not None:
DEBUG = xml.get('debug',False) # override debug flag using attribute in symbolicmath xml
if DEBUG in ['0','False']:
DEBUG = False
# options
do_matrix = 'matrix' in (options or '')
do_qubit = 'qubit' in (options or '')
do_imaginary = 'imaginary' in (options or '')
do_numerical = 'numerical' in (options or '')
# parse expected answer
try:
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}
return {'ok': False, 'msg': make_error_message(msg)}
# if expected answer is a number, try parsing provided answer as a number also
try:
......@@ -184,6 +197,13 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
msg += '<p>You entered: %s</p>' % to_latex(fans)
return {'ok': False, 'msg': msg}
if do_numerical: # numerical answer expected - force numerical comparison
if abs(abs(fans - fexpect) / fexpect) < threshold:
return {'ok': True, 'msg': msg}
else:
msg += '<p>You entered: %s (note that a numerical answer is expected)</p>' % to_latex(fans)
return {'ok': False, 'msg': msg}
if fexpect == fans:
msg += '<p>You entered: %s</p>' % to_latex(fans)
return {'ok': True, 'msg': msg}
......@@ -205,16 +225,18 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
msg += '<p>You entered: %s</p>' % to_latex(f.sympy)
except Exception, err:
log.exception("Error evaluating expression '%s' as a valid equation" % ans)
msg += "<p>Error %s in evaluating your expression '%s' as a valid equation</p>" % (str(err).replace('<', '&lt;'),
ans)
msg += "<p>Error in evaluating your expression '%s' as a valid equation</p>" % (ans)
if "Illegal math" in str(err):
msg += "<p>Illegal math expression</p>"
if DEBUG:
msg += 'Error: %s' % str(err).replace('<', '&lt;')
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}
return {'ok': False, 'msg': make_error_message(msg)}
# compare with expected
if hasattr(fexpect, 'is_number') and fexpect.is_number:
......@@ -226,7 +248,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
msg += "<p>given = %s</p>" % repr(ans)
msg += "<p>fsym = %s</p>" % repr(fsym)
# msg += "<p>cmathml = <pre>%s</pre></p>" % str(f.cmathml).replace('<','&lt;')
return {'ok': False, 'msg': msg}
return {'ok': False, 'msg': make_error_message(msg)}
if fexpect == fsym:
return {'ok': True, 'msg': msg}
......@@ -239,11 +261,11 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
return {'ok': True, 'msg': msg}
except sympy.ShapeError:
msg += "<p>Error - your input vector or matrix has the wrong dimensions"
return {'ok': False, 'msg': msg}
return {'ok': False, 'msg': make_error_message(msg)}
except Exception, err:
msg += "<p>Error %s in comparing expected (a list) and your answer</p>" % str(err).replace('<', '&lt;')
if DEBUG: msg += "<p/><pre>%s</pre>" % traceback.format_exc()
return {'ok': False, 'msg': msg}
return {'ok': False, 'msg': make_error_message(msg)}
#diff = (fexpect-fsym).simplify()
#fsym = fsym.simplify()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment