diff --git a/djangoapps/courseware/capa/capa_problem.py b/djangoapps/courseware/capa/capa_problem.py
index 44cd52d7cd..d98757d18e 100644
--- a/djangoapps/courseware/capa/capa_problem.py
+++ b/djangoapps/courseware/capa/capa_problem.py
@@ -26,7 +26,7 @@ from mako.template import Template
from util import contextualize_text
import inputtypes
-from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse
+from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse, SymbolicResponse
import calc
import eia
@@ -42,6 +42,7 @@ response_types = {'numericalresponse':NumericalResponse,
'truefalseresponse':TrueFalseResponse,
'imageresponse':ImageResponse,
'optionresponse':OptionResponse,
+ 'symbolicresponse':SymbolicResponse,
}
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput','optioninput']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
@@ -55,6 +56,7 @@ html_transforms = {'problem': {'tag':'div'},
"externalresponse": {'tag':'span'},
"schematicresponse": {'tag':'span'},
"formularesponse": {'tag':'span'},
+ "symbolicresponse": {'tag':'span'},
"multiplechoiceresponse": {'tag':'span'},
"text": {'tag':'span'},
"math": {'tag':'span'},
@@ -70,7 +72,7 @@ global_context={'random':random,
# These should be removed from HTML output, including all subelements
html_problem_semantics = ["responseparam", "answer", "script"]
# These should be removed from HTML output, but keeping subelements
-html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text","externalresponse"]
+html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text","externalresponse",'symbolicresponse']
# removed in MC
## These should be transformed
@@ -218,8 +220,10 @@ class LoncapaProblem(object):
#for script in tree.xpath('/problem/script'):
for script in tree.findall('.//script'):
- if 'javascript' in script.get('type'): continue # skip javascript
- if 'perl' in script.get('type'): continue # skip perl
+ stype = script.get('type')
+ if stype:
+ if 'javascript' in stype: continue # skip javascript
+ if 'perl' in stype: continue # skip perl
# TODO: evaluate only python
code = script.text
XMLESC = {"'": "'", """: '"'}
diff --git a/djangoapps/courseware/capa/responsetypes.py b/djangoapps/courseware/capa/responsetypes.py
index fc8bc35120..3809c0c526 100644
--- a/djangoapps/courseware/capa/responsetypes.py
+++ b/djangoapps/courseware/capa/responsetypes.py
@@ -221,7 +221,10 @@ class NumericalResponse(GenericResponse):
class CustomResponse(GenericResponse):
'''
- Custom response. The python code to be run should be in .... Example:
+ Custom response. The python code to be run should be in ...
+ or in a
+
+ Example:
@@ -263,6 +266,7 @@ def sympy_check2():
'''
def __init__(self, xml, context, system=None):
self.xml = xml
+ self.system = system
## CRITICAL TODO: Should cover all entrytypes
## NOTE: xpath will look at root of XML tree, not just
## what's in xml. @id=id keeps us in the right customresponse.
@@ -271,8 +275,8 @@ def sympy_check2():
self.answer_ids += [x.get('id') for x in xml.findall('textbox')] # also allow textbox inputs
self.context = context
- # if has an "expect" attribute then save that
- self.expect = xml.get('expect')
+ # if has an "expect" (or "answer") attribute then save that
+ self.expect = xml.get('expect') or xml.get('answer')
self.myid = xml.get('id')
if settings.DEBUG:
@@ -351,9 +355,14 @@ def sympy_check2():
'answers':student_answers, # dict of student's responses, with keys being entry box IDs
'correct':correct, # the list to be filled in by the check function
'messages':messages, # the list of messages to be filled in by the check function
+ 'options':self.xml.get('options'), # any options to be passed to the cfn
'testdat':'hello world',
})
+ # pass self.system.debug to cfn
+ # if hasattr(self.system,'debug'): self.context['debug'] = self.system.debug
+ self.context['debug'] = settings.DEBUG
+
# exec the check function
if type(self.code)==str:
try:
@@ -381,7 +390,7 @@ def sympy_check2():
if settings.DEBUG:
log.debug('[courseware.capa.responsetypes.customresponse] answer_given=%s' % answer_given)
- # log.info('nargs=%d, args=%s' % (nargs,args))
+ log.info('nargs=%d, args=%s, kwargs=%s' % (nargs,args,kwargs))
ret = fn(*args[:nargs],**kwargs)
except Exception,err:
@@ -436,6 +445,32 @@ def sympy_check2():
#-----------------------------------------------------------------------------
+class SymbolicResponse(CustomResponse):
+ """
+ Symbolic math response checking, using symmath library.
+
+ Example:
+
+
+ Compute \[ \exp\left(-i \frac{\theta}{2} \left[ \begin{matrix} 0 & 1 \\ 1 & 0 \end{matrix} \right] \right) \]
+ and give the resulting \(2\times 2\) matrix:
+
+
+
+
+ Your input should be typed in as a list of lists, eg [[1,2],[3,4]].
+
+
+ """
+ def __init__(self, xml, context, system=None):
+ xml.set('cfn','symmath_check')
+ code = "from symmath import *"
+ exec code in context,context
+ CustomResponse.__init__(self,xml,context,system)
+
+
+#-----------------------------------------------------------------------------
+
class ExternalResponse(GenericResponse):
"""
Grade the student's input using an external server.
@@ -502,6 +537,30 @@ class StudentInputError(Exception):
#-----------------------------------------------------------------------------
class FormulaResponse(GenericResponse):
+ '''
+ Checking of symbolic math response using numerical sampling.
+
+ Example:
+
+
+
+
+
+
+
+ Give an equation for the relativistic energy of an object with mass m.
+
+
+
+
+
+
+
+
+ '''
def __init__(self, xml, context, system=None):
self.xml = xml
self.correct_answer = contextualize_text(xml.get('answer'), context)
diff --git a/lib/symmath/formula.py b/lib/symmath/formula.py
index bfff38996d..09bcf8693d 100644
--- a/lib/symmath/formula.py
+++ b/lib/symmath/formula.py
@@ -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 '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():
'''
return formula(xmlstr)
+
+def test5(): # sum of two matrices
+ xmlstr = u'''
+
+'''
+ return formula(xmlstr)
+
+def test6(): # imaginary numbers
+ xmlstr = u'''
+
+'''
+ return formula(xmlstr,options='imaginaryi')
diff --git a/lib/symmath/symmath_check.py b/lib/symmath/symmath_check.py
index 89ae219bc1..09621e27ca 100644
--- a/lib/symmath/symmath_check.py
+++ b/lib/symmath/symmath_check.py
@@ -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 += 'adict=%s' % (repr(adict).replace('<','<'))
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 += '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))
+ 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) mmlans=%s' % repr(mmlans).replace('<','<')
@@ -186,13 +193,20 @@ def symmath_check(expect,ans,adict={},abname=''):
fsym = f.sympy
msg += 'You entered: %s
' % to_latex(f.sympy)
except Exception,err:
- msg += "Error %s in converting to sympy
" % str(err).replace('<','<')
- if DEBUG: msg += "%s
" % traceback.format_exc()
+ msg += "Error %s in evaluating your expression '%s' as a valid equation
" % (str(err).replace('<','<'),
+ ans)
+ if DEBUG:
+ 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}
# 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)'
+ msg += 'DEBUG messages:
'
msg += "Got: %s
" % repr(fsym)
# msg += "Got: %s" % str([type(x) for x in fsym.atoms()]).replace('<','<')
msg += "Expecting: %s
" % repr(fexpect).replace('**','^').replace('hat(I)','hat(i)')
# msg += "Expecting: %s" % str([type(x) for x in fexpect.atoms()]).replace('<','<')
if diff:
msg += "Difference: %s
" % to_latex(diff)
+ msg += '
'
return {'ok':False,'msg':msg,'ex':fexpect,'got':fsym}