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 ' + + + cos + + ( + θ + ) + + + + + [ + + + + 1 + + + 0 + + + + + 0 + + + 1 + + + + ] + + + + + [ + + + + 0 + + + 1 + + + + + 1 + + + 0 + + + + ] + + + +''' + return formula(xmlstr) + +def test6(): # imaginary numbers + xmlstr = u''' + + + 1 + + + i + + +''' + 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)