From 94b149f973f0534935bfeac9972e1692ee20950b Mon Sep 17 00:00:00 2001 From: Piotr Mitros Date: Thu, 15 Dec 2011 23:49:02 -0500 Subject: [PATCH] MathJAX and formula answers work --- courseware/capa_problem.py | 121 ++++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 27 deletions(-) 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.