Merge pull request #731 from MITx/feature/ichuang/symmath_improvements
improvements in symbolic math checking. Merging now because it is needed for Physics courses.
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -140,13 +140,20 @@ def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym=
|
||||
# msg += "<p/> dot test " + to_latex(dot(sympy.Symbol('x'),sympy.Symbol('y')))
|
||||
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('<', '<'),
|
||||
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('<', '<')
|
||||
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('<', '<')
|
||||
msg += '<p>pmathml=<pre>%s</pre></p>' % mmlans.replace('<', '<')
|
||||
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('<','<')
|
||||
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('<', '<')
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user