diff --git a/courseware/capa_problem.py b/courseware/capa_problem.py
index 0c43b3bd28..072565acd4 100644
--- a/courseware/capa_problem.py
+++ b/courseware/capa_problem.py
@@ -1,10 +1,16 @@
-# For calculator:
-# http://pyparsing.wikispaces.com/file/view/fourFn.py
-
import random, numpy, math, scipy, sys, StringIO, os, struct, json
from xml.dom.minidom import parse, parseString
+from calc import evaluator
+
+def strip_dict(d):
+ ''' Takes a dict. Returns an identical dict, with all non-word keys stripped out. '''
+ d=dict([(k, float(d[k])) for k in d if type(k)==str and \
+ k.isalnum() and \
+ (type(d[k]) == float or type(d[k]) == int) ])
+ return d
+
class LoncapaProblem():
def get_state(self):
''' Stored per-user session data neeeded to:
@@ -71,15 +77,15 @@ class LoncapaProblem():
# Loop through the nodes of the problem, and
for e in dom.childNodes:
- print e, ot
+# print e, ot
#
if e.localName=='script':
- print e.childNodes[0].data
+ #print e.childNodes[0].data
exec e.childNodes[0].data in g,self.context
elif e.localName=='endouttext':
ot=False
elif ot:
- print e, "::", e.toxml()
+# print e, "::", e.toxml()
e.writexml(buf)
elif e.localName=='startouttext':
ot=True
@@ -93,7 +99,7 @@ class LoncapaProblem():
self.text=buf.getvalue()
self.text=self.contextualize_text(self.text)
- print self.text
+# print self.text
self.filename=filename
done=False
@@ -129,29 +135,34 @@ class LoncapaProblem():
if id not in answers:
correct_map[id]='incorrect' # Should always be there
else:
- correct_map[id]=self.grade_nr(self.questions[key],
- self.answers[id])
+ #correct_map[id]=self.grade_nr(self.questions[key],
+ # self.answers[id])
+ grader=self.graders[self.questions[key]['type']]
+ print grader
+ correct_map[id]=grader(self, self.questions[key],
+ self.answers[id])
self.correct_map=correct_map
return correct_map
+
## Internal methods
- def number(self,text):
- ''' Convert a number to a float, understanding suffixes '''
- try:
- text.strip()
- suffixes={'%':0.01,'k':1e3,'M':1e6,'G':1e9,'T':1e12,'P':1e15,
- 'E':1e18,'Z':1e21,'Y':1e24,'c':1e-2,'m':1e-3,'u':1e-6,
- 'n':1e-9,'p':1e-12,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
- if text[-1] in suffixes:
- return float(text[:-1])*suffixes[text[-1]]
- else:
- return float(text)
- except:
- return 0 # TODO: Better error handling?
+# def number(self,text):
+# ''' Convert a number to a float, understanding suffixes '''
+# try:
+# text.strip()
+# suffixes={'%':0.01,'k':1e3,'M':1e6,'G':1e9,'T':1e12,'P':1e15,
+# 'E':1e18,'Z':1e21,'Y':1e24,'c':1e-2,'m':1e-3,'u':1e-6,
+# 'n':1e-9,'p':1e-12,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
+# if text[-1] in suffixes:
+# return float(text[:-1])*suffixes[text[-1]]
+# else:
+# return float(text)
+# except:
+# return 0 # TODO: Better error handling?
def grade_nr(self, question, answer):
- error = abs(self.number(answer) - question['answer'])
+ error = abs(evaluator({},{},answer) - question['answer'])
allowed_error = abs(question['answer']*question['tolerance'])
if error <= allowed_error:
return 'correct'
@@ -165,13 +176,14 @@ class LoncapaProblem():
tolerance=e.getAttribute('default')
self.lid+=1
id=str(self.gid)+'_'+str(self.lid)
- problem={"answer":self.number(self.contextualize_text(answer)),
+ problem={"answer":evaluator({},{},self.contextualize_text(answer)),
"type":"numericalresponse",
- "tolerance":self.number(self.contextualize_text(tolerance)),
+ "tolerance":evaluator({},{},self.contextualize_text(tolerance)),
"id":id,
"lid":self.lid,
}
self.questions[self.lid]=problem
+
if id in self.answers:
value=self.answers[id]
else:
@@ -185,9 +197,64 @@ class LoncapaProblem():
html=' '.format(id=id,value=value,icon=icon)
return html
+ def grade_fr(self, question, answer):
+ correct = True
+ for i in range(question['samples_count']):
+ instructor_variables = strip_dict(dict(self.context))
+ student_variables = dict()
+ for var in question['sample_range']:
+ value = random.uniform(*question['sample_range'][var])
+ instructor_variables[str(var)] = value
+ student_variables[str(var)] = value
+ instructor_result = evaluator(instructor_variables,{},str(question['answer']))
+ student_result = evaluator(student_variables,{},str(answer))
+ if abs( student_result - instructor_result ) > question['tolerance']:
+ return "incorrect"
+
+ return "correct"
- graders={'numericalresponse':grade_nr}
- handlers={'numericalresponse':handle_nr}
+ def handle_fr(self, element):
+ ## Extract description from element
+ samples=element.getAttribute('samples')
+ variables=samples.split('@')[0].split(',')
+ numsamples=int(samples.split('@')[1].split('#')[1])
+ sranges=zip(*map(lambda x:map(float, x.split(",")), samples.split('@')[1].split('#')[0].split(':')))
+ answer=element.getAttribute('answer')
+ for e in element.childNodes:
+ if e.nodeType==1 and e.getAttribute('type')=="tolerance":
+ tolerance=e.getAttribute('default')
+
+ # Store element
+ self.lid+=1
+ id=str(self.gid)+'_'+str(self.lid)
+ problem={"answer":self.contextualize_text(answer),
+ "type":"formularesponse",
+ "tolerance":evaluator({},{},self.contextualize_text(tolerance)),
+ "sample_range":dict(zip(variables, sranges)),
+ "samples_count": numsamples,
+ "id":id,
+ "lid":self.lid,
+ }
+ self.questions[self.lid]=problem
+
+ # Generate HTML
+ if id in self.answers:
+ value=self.answers[id]
+ else:
+ value=""
+ icon='bullet'
+ if id in self.correct_map and self.correct_map[id]=='correct':
+ icon='check'
+ if id in self.correct_map and self.correct_map[id]=='incorrect':
+ icon='close'
+
+ html=' '.format(id=id,value=value,icon=icon)
+ return html
+
+ graders={'numericalresponse':grade_nr,
+ 'formularesponse':grade_fr}
+ handlers={'numericalresponse':handle_nr,
+ 'formularesponse':handle_fr}
def contextualize_text(self, text):
''' Takes a string with variables. E.g. $a+$b.