From 672ede14d196b9128c2ad13fcbcff24911ad86e9 Mon Sep 17 00:00:00 2001 From: ichuang Date: Wed, 19 Sep 2012 20:37:26 -0400 Subject: [PATCH 1/3] improvements in symbolic math checking - more standard functions, use new capa_alert div for error messages, clean up debugging --- lms/lib/symmath/formula.py | 26 +++++++++++++++++++++++++- lms/lib/symmath/symmath_check.py | 32 +++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py index 4aa9f60d30..c71a79bac7 100644 --- a/lms/lib/symmath/formula.py +++ b/lms/lib/symmath/formula.py @@ -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 diff --git a/lms/lib/symmath/symmath_check.py b/lms/lib/symmath/symmath_check.py index 4af012d2cb..09810b8820 100644 --- a/lms/lib/symmath/symmath_check.py +++ b/lms/lib/symmath/symmath_check.py @@ -140,13 +140,20 @@ def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym= # msg += "

dot test " + to_latex(dot(sympy.Symbol('x'),sympy.Symbol('y'))) return {'ok': False, 'msg': msg} +#----------------------------------------------------------------------------- +# helper function to convert all

to + +def make_error_message(msg): + # msg = msg.replace('

','

').replace('

','

') + msg = '
%s
' % 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,6 +166,11 @@ 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 '') @@ -169,7 +181,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None): fexpect = my_sympify(str(expect), matrix=do_matrix, do_qubit=do_qubit) except Exception, err: msg += '

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

' % (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: @@ -205,16 +217,18 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None): msg += '

You entered: %s

' % to_latex(f.sympy) except Exception, err: log.exception("Error evaluating expression '%s' as a valid equation" % ans) - msg += "

Error %s in evaluating your expression '%s' as a valid equation

" % (str(err).replace('<', '<'), - ans) + msg += "

Error in evaluating your expression '%s' as a valid equation

" % (ans) + if "Illegal math" in str(err): + msg += "

Illegal math expression

" if DEBUG: + msg += 'Error: %s' % str(err).replace('<', '<') msg += '
' msg += '

DEBUG messages:

' msg += "

%s

" % traceback.format_exc() msg += '

cmathml=

%s

' % f.cmathml.replace('<', '<') msg += '

pmathml=

%s

' % mmlans.replace('<', '<') msg += '
' - 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 +240,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None): msg += "

given = %s

" % repr(ans) msg += "

fsym = %s

" % repr(fsym) # msg += "

cmathml =

%s

" % 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 +253,11 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None): return {'ok': True, 'msg': msg} except sympy.ShapeError: msg += "

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 += "

Error %s in comparing expected (a list) and your answer

" % str(err).replace('<', '<') if DEBUG: msg += "

%s
" % traceback.format_exc() - return {'ok': False, 'msg': msg} + return {'ok': False, 'msg': make_error_message(msg)} #diff = (fexpect-fsym).simplify() #fsym = fsym.simplify() From f1e5d6976ef5c1f680b7e6822a9b72bb3545df46 Mon Sep 17 00:00:00 2001 From: ichuang Date: Wed, 19 Sep 2012 20:49:36 -0400 Subject: [PATCH 2/3] fix tabs fallback check when no tabs specified --- lms/djangoapps/courseware/tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 8ecd8137df..cad70980e2 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -166,7 +166,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 From b9af428b40309c42abba0c71f5552cbb1b1eed3a Mon Sep 17 00:00:00 2001 From: ichuang Date: Thu, 20 Sep 2012 14:37:21 -0400 Subject: [PATCH 3/3] formula.py handle imaginary correctly; symmath_check add numerical option --- lms/lib/symmath/formula.py | 3 +-- lms/lib/symmath/symmath_check.py | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py index c71a79bac7..1698b004d9 100644 --- a/lms/lib/symmath/formula.py +++ b/lms/lib/symmath/formula.py @@ -409,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)) diff --git a/lms/lib/symmath/symmath_check.py b/lms/lib/symmath/symmath_check.py index 09810b8820..bcb4a0d490 100644 --- a/lms/lib/symmath/symmath_check.py +++ b/lms/lib/symmath/symmath_check.py @@ -175,6 +175,7 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None 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: @@ -196,6 +197,13 @@ def symmath_check(expect, ans, dynamath=None, options=None, debug=None, xml=None msg += '

You entered: %s

' % 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 += '

You entered: %s (note that a numerical answer is expected)

' % to_latex(fans) + return {'ok': False, 'msg': msg} + if fexpect == fans: msg += '

You entered: %s

' % to_latex(fans) return {'ok': True, 'msg': msg}