MathJAX and formula answers work
This commit is contained in:
@@ -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='<input type="text" name="input_{id}" id="input_{id}" value="{value}"><span class="ui-icon ui-icon-{icon}" style="display:inline-block;" id="status_{id}"></span> '.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='<input type="text" name="input_{id}" id="input_{id}" value="{value}"><span class="ui-icon ui-icon-{icon}" style="display:inline-block;" id="status_{id}"></span> '.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.
|
||||
|
||||
Reference in New Issue
Block a user