From c79843c5418847272bd143b1c777f874c3c29fbf Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Fri, 26 Apr 2013 15:24:04 -0400 Subject: [PATCH 1/6] Add factorial as a default function for formula response --- common/lib/capa/capa/calc.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/calc.py b/common/lib/capa/capa/calc.py index c3fe6b656b..4d6c49fe99 100644 --- a/common/lib/capa/capa/calc.py +++ b/common/lib/capa/capa/calc.py @@ -24,7 +24,9 @@ default_functions = {'sin': numpy.sin, 'arccos': numpy.arccos, 'arcsin': numpy.arcsin, 'arctan': numpy.arctan, - 'abs': numpy.abs + 'abs': numpy.abs, + 'fact': math.factorial, + 'factorial': math.factorial } default_variables = {'j': numpy.complex(0, 1), 'e': numpy.e, @@ -246,4 +248,9 @@ if __name__ == '__main__': print evaluator({}, {}, "5+1*j") print evaluator({}, {}, "j||1") print evaluator({}, {}, "e^(j*pi)") - print evaluator({}, {}, "5+7 QWSEKO") + print evaluator({}, {}, "fact(5)") + print evaluator({}, {}, "factorial(5)") + try: + print evaluator({}, {}, "5+7 QWSEKO") + except UndefinedVariable: + print "Successfully caught undefined variable" From 696879bd260b31baf24d83d89fabf089b95d38dd Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 29 Apr 2013 00:40:23 -0400 Subject: [PATCH 2/6] Add unit tests for formularesponse type --- .../lib/capa/capa/tests/test_responsetypes.py | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index bf64d3cc69..ab21a845eb 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -33,10 +33,16 @@ class ResponseTest(unittest.TestCase): xml = self.xml_factory.build_xml(**kwargs) return lcp.LoncapaProblem(xml, '1', system=test_system) - def assert_grade(self, problem, submission, expected_correctness): + def assert_grade(self, problem, submission, expected_correctness, msg=None): input_dict = {'1_2_1': submission} correct_map = problem.grade_answers(input_dict) - self.assertEquals(correct_map.get_correctness('1_2_1'), expected_correctness) + if msg is None: + self.assertEquals(correct_map.get_correctness('1_2_1'), expected_correctness) + else: + if correct_map.get_correctness('1_2_1')!= expected_correctness: + print submission + print correct_map + self.assertEquals(correct_map.get_correctness('1_2_1'), expected_correctness, msg) def assert_answer_format(self, problem): answers = problem.get_question_answers() @@ -358,6 +364,84 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, '3*x', 'incorrect') + def test_parallel_resistors(self): + """Test parallel resistors""" + sample_dict = {'R1': (10,10), 'R2': (2,2), 'R3': (5,5), 'R4': (1,1)} + + # Test problem + problem = self.build_problem(sample_dict=sample_dict, + num_samples=10, + tolerance=0.01, + answer="R1||R2") + # Expect answer to be marked correct + input_formula = "R1||R2" + self.assert_grade(problem, input_formula, "correct") + + # Expect random number to be marked incorrect + input_formula = "13" + self.assert_grade(problem, input_formula, "incorrect") + + # Expect incorrect answer marked incorrect + input_formula = "R3||R4" + self.assert_grade(problem, input_formula, "incorrect") + + def test_default_variables(self): + """Test the default variables provided in common/lib/capa/capa/calc.py""" + # which are: j (complex number), e, pi, k, c, T, q + + # Sample x in the range [-10,10] + sample_dict = {'x': (-10, 10)} + default_variables = [('j', 2, 3), ('e', 2, 3), ('pi', 2, 3), ('c', 2, 3), ('T', 2, 3), + ('k', 2*10**23, 3*10**23), # note k = scipy.constants.k = 1.3806488e-23 + ('q', 2*10**19, 3*10**19)] # note k = scipy.constants.e = 1.602176565e-19 + for (var, cscalar, iscalar) in default_variables: + # The expected solution is numerically equivalent to cscalar*var + correct = '{0}*x*{1}'.format(cscalar, var) + incorrect = '{0}*x*{1}'.format(iscalar, var) + problem = self.build_problem(sample_dict=sample_dict, + num_samples=10, + tolerance=0.01, + answer=correct) + # Expect that the inputs are graded correctly + + self.assert_grade(problem, correct, 'correct', + msg="Failed on variable {0}; the given, correct answer was {1} but graded 'incorrect'".format(var, correct)) + self.assert_grade(problem, incorrect, 'incorrect', + msg="Failed on variable {0}; the given, incorrect answer was {1} but graded 'correct'".format(var, incorrect)) + + def test_default_functions(self): + """Test the default functions provided in common/lib/capa/capa/calc.py""" + # which are: sin, cos, tan, sqrt, log10, log2, ln, + # arccos, arcsin, arctan, abs, + # fact, factorial + + # Sample x in the range [-10,10] + import random + w = random.randint(3, 10) + sample_dict = {'x': (-10, 10), 'y': (1, 10), 'z':(-1, 1), 'w':(w,w)} + default_functions = [('sin', 2, 3, 'x'), ('cos', 2, 3, 'x'), ('tan', 2, 3, 'x'), ('sqrt', 2, 3, 'y'), ('log10', 2, 3, 'y'), + ('log2', 2, 3, 'y'), ('ln', 2, 3, 'y'), ('arccos', 2, 3, 'z'), ('arcsin', 2, 3, 'z'), ('arctan', 2, 3, 'x'), + ('abs', 2, 3, 'x'), ('fact', 2, 3, 'w'), ('factorial', 2, 3, 'w'),] + for (func, cscalar, iscalar, var) in default_functions: + print 'func is: {0}'.format(func) + # The expected solution is numerically equivalent to cscalar*func(var) + correct = '{0}*{1}({2})'.format(cscalar, func, var) + incorrect = '{0}*{1}({2})'.format(iscalar, func, var) + problem = self.build_problem(sample_dict=sample_dict, + num_samples=10, + tolerance=0.01, + answer=correct) + # Expect that the inputs are graded correctly + + self.assert_grade(problem, correct, 'correct', + msg="Failed on function {0}; the given, correct answer was {1} but graded 'incorrect'".format(func, correct)) + self.assert_grade(problem, incorrect, 'incorrect', + msg="Failed on function {0}; the given, incorrect answer was {1} but graded 'correct'".format(func, incorrect)) + + + + + class StringResponseTest(ResponseTest): from response_xml_factory import StringResponseXMLFactory xml_factory_class = StringResponseXMLFactory From ed43afb08fd6e0338210534194499646e437b4fd Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 29 Apr 2013 00:44:04 -0400 Subject: [PATCH 3/6] Add additional unit test comment --- common/lib/capa/capa/tests/test_responsetypes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index ab21a845eb..6576a1ce86 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -414,11 +414,15 @@ class FormulaResponseTest(ResponseTest): # which are: sin, cos, tan, sqrt, log10, log2, ln, # arccos, arcsin, arctan, abs, # fact, factorial - - # Sample x in the range [-10,10] + import random w = random.randint(3, 10) - sample_dict = {'x': (-10, 10), 'y': (1, 10), 'z':(-1, 1), 'w':(w,w)} + sample_dict = {'x': (-10, 10), # Sample x in the range [-10,10] + 'y': (1, 10), # Sample y in the range [1,10] - logs, arccos need positive inputs + 'z':(-1, 1), # Sample z in the range [1,10] - for arcsin, arctan + 'w':(w,w)} # Sample w is a random, positive integer - factorial needs a positive, integer input, + # and the way formularesponse is defined, we can only specify a float range + default_functions = [('sin', 2, 3, 'x'), ('cos', 2, 3, 'x'), ('tan', 2, 3, 'x'), ('sqrt', 2, 3, 'y'), ('log10', 2, 3, 'y'), ('log2', 2, 3, 'y'), ('ln', 2, 3, 'y'), ('arccos', 2, 3, 'z'), ('arcsin', 2, 3, 'z'), ('arctan', 2, 3, 'x'), ('abs', 2, 3, 'x'), ('fact', 2, 3, 'w'), ('factorial', 2, 3, 'w'),] From 46792c82de8f4b3305bf5122f0a908788e56946f Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 29 Apr 2013 00:56:07 -0400 Subject: [PATCH 4/6] Remove debugging print statements --- common/lib/capa/capa/tests/test_responsetypes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 6576a1ce86..28d8c7b29f 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -39,9 +39,6 @@ class ResponseTest(unittest.TestCase): if msg is None: self.assertEquals(correct_map.get_correctness('1_2_1'), expected_correctness) else: - if correct_map.get_correctness('1_2_1')!= expected_correctness: - print submission - print correct_map self.assertEquals(correct_map.get_correctness('1_2_1'), expected_correctness, msg) def assert_answer_format(self, problem): From 6993daf116962b9c528268c8399f8ef1348e80cc Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 29 Apr 2013 10:48:25 -0400 Subject: [PATCH 5/6] Fix pep8 violations --- common/lib/capa/capa/calc.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/lib/capa/capa/calc.py b/common/lib/capa/capa/calc.py index 4d6c49fe99..bb1fb97153 100644 --- a/common/lib/capa/capa/calc.py +++ b/common/lib/capa/capa/calc.py @@ -114,18 +114,18 @@ def evaluator(variables, functions, string, cs=False): return float('nan') ops = {"^": operator.pow, - "*": operator.mul, - "/": operator.truediv, - "+": operator.add, - "-": operator.sub, - } + "*": operator.mul, + "/": operator.truediv, + "+": operator.add, + "-": operator.sub, + } # We eliminated extreme ones, since they're rarely used, and potentially # confusing. They may also conflict with variables if we ever allow e.g. # 5R instead of 5*R 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} + '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} def super_float(text): ''' Like float, but with si extensions. 1k goes to 1000''' From f95331dcc1b4359af20a38d98b189334f2f5e3c3 Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 29 Apr 2013 14:22:10 -0400 Subject: [PATCH 6/6] fix more pep8 violations --- .../lib/capa/capa/tests/test_responsetypes.py | 71 +++++++++---------- 1 file changed, 32 insertions(+), 39 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 28d8c7b29f..7a43fff4c9 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -7,6 +7,7 @@ from datetime import datetime import json from nose.plugins.skip import SkipTest import os +import random import unittest import textwrap @@ -14,7 +15,7 @@ from . import test_system import capa.capa_problem as lcp from capa.responsetypes import LoncapaProblemError, \ - StudentInputError, ResponseError + StudentInputError, ResponseError from capa.correctmap import CorrectMap from capa.util import convert_files_to_filenames from capa.xqueue_interface import dateformat @@ -360,10 +361,9 @@ class FormulaResponseTest(ResponseTest): self.assert_grade(problem, '2*x', 'correct') self.assert_grade(problem, '3*x', 'incorrect') - def test_parallel_resistors(self): """Test parallel resistors""" - sample_dict = {'R1': (10,10), 'R2': (2,2), 'R3': (5,5), 'R4': (1,1)} + sample_dict = {'R1': (10, 10), 'R2': (2, 2), 'R3': (5, 5), 'R4': (1, 1)} # Test problem problem = self.build_problem(sample_dict=sample_dict, @@ -388,9 +388,9 @@ class FormulaResponseTest(ResponseTest): # Sample x in the range [-10,10] sample_dict = {'x': (-10, 10)} - default_variables = [('j', 2, 3), ('e', 2, 3), ('pi', 2, 3), ('c', 2, 3), ('T', 2, 3), - ('k', 2*10**23, 3*10**23), # note k = scipy.constants.k = 1.3806488e-23 - ('q', 2*10**19, 3*10**19)] # note k = scipy.constants.e = 1.602176565e-19 + default_variables = [('j', 2, 3), ('e', 2, 3), ('pi', 2, 3), ('c', 2, 3), ('T', 2, 3), + ('k', 2 * 10 ** 23, 3 * 10 ** 23), # note k = scipy.constants.k = 1.3806488e-23 + ('q', 2 * 10 ** 19, 3 * 10 ** 19)] # note k = scipy.constants.e = 1.602176565e-19 for (var, cscalar, iscalar) in default_variables: # The expected solution is numerically equivalent to cscalar*var correct = '{0}*x*{1}'.format(cscalar, var) @@ -399,30 +399,29 @@ class FormulaResponseTest(ResponseTest): num_samples=10, tolerance=0.01, answer=correct) + # Expect that the inputs are graded correctly - - self.assert_grade(problem, correct, 'correct', + self.assert_grade(problem, correct, 'correct', msg="Failed on variable {0}; the given, correct answer was {1} but graded 'incorrect'".format(var, correct)) - self.assert_grade(problem, incorrect, 'incorrect', + self.assert_grade(problem, incorrect, 'incorrect', msg="Failed on variable {0}; the given, incorrect answer was {1} but graded 'correct'".format(var, incorrect)) def test_default_functions(self): """Test the default functions provided in common/lib/capa/capa/calc.py""" - # which are: sin, cos, tan, sqrt, log10, log2, ln, + # which are: sin, cos, tan, sqrt, log10, log2, ln, # arccos, arcsin, arctan, abs, # fact, factorial - - import random - w = random.randint(3, 10) - sample_dict = {'x': (-10, 10), # Sample x in the range [-10,10] - 'y': (1, 10), # Sample y in the range [1,10] - logs, arccos need positive inputs - 'z':(-1, 1), # Sample z in the range [1,10] - for arcsin, arctan - 'w':(w,w)} # Sample w is a random, positive integer - factorial needs a positive, integer input, - # and the way formularesponse is defined, we can only specify a float range - default_functions = [('sin', 2, 3, 'x'), ('cos', 2, 3, 'x'), ('tan', 2, 3, 'x'), ('sqrt', 2, 3, 'y'), ('log10', 2, 3, 'y'), - ('log2', 2, 3, 'y'), ('ln', 2, 3, 'y'), ('arccos', 2, 3, 'z'), ('arcsin', 2, 3, 'z'), ('arctan', 2, 3, 'x'), - ('abs', 2, 3, 'x'), ('fact', 2, 3, 'w'), ('factorial', 2, 3, 'w'),] + w = random.randint(3, 10) + sample_dict = {'x': (-10, 10), # Sample x in the range [-10,10] + 'y': (1, 10), # Sample y in the range [1,10] - logs, arccos need positive inputs + 'z': (-1, 1), # Sample z in the range [1,10] - for arcsin, arctan + 'w': (w, w)} # Sample w is a random, positive integer - factorial needs a positive, integer input, + # and the way formularesponse is defined, we can only specify a float range + + default_functions = [('sin', 2, 3, 'x'), ('cos', 2, 3, 'x'), ('tan', 2, 3, 'x'), ('sqrt', 2, 3, 'y'), ('log10', 2, 3, 'y'), + ('log2', 2, 3, 'y'), ('ln', 2, 3, 'y'), ('arccos', 2, 3, 'z'), ('arcsin', 2, 3, 'z'), ('arctan', 2, 3, 'x'), + ('abs', 2, 3, 'x'), ('fact', 2, 3, 'w'), ('factorial', 2, 3, 'w')] for (func, cscalar, iscalar, var) in default_functions: print 'func is: {0}'.format(func) # The expected solution is numerically equivalent to cscalar*func(var) @@ -432,17 +431,14 @@ class FormulaResponseTest(ResponseTest): num_samples=10, tolerance=0.01, answer=correct) + # Expect that the inputs are graded correctly - - self.assert_grade(problem, correct, 'correct', + self.assert_grade(problem, correct, 'correct', msg="Failed on function {0}; the given, correct answer was {1} but graded 'incorrect'".format(func, correct)) - self.assert_grade(problem, incorrect, 'incorrect', + self.assert_grade(problem, incorrect, 'incorrect', msg="Failed on function {0}; the given, incorrect answer was {1} but graded 'correct'".format(func, incorrect)) - - - class StringResponseTest(ResponseTest): from response_xml_factory import StringResponseXMLFactory xml_factory_class = StringResponseXMLFactory @@ -989,14 +985,13 @@ class CustomResponseTest(ResponseTest): with self.assertRaises(ResponseError): problem.grade_answers({'1_2_1': '42'}) - def test_module_imports_inline(self): ''' Check that the correct modules are available to custom response scripts ''' - for module_name in ['random', 'numpy', 'math', 'scipy', + for module_name in ['random', 'numpy', 'math', 'scipy', 'calc', 'eia', 'chemcalc', 'chemtools', 'miller', 'draganddrop']: @@ -1006,26 +1001,25 @@ class CustomResponseTest(ResponseTest): script = textwrap.dedent(''' correct[0] = 'correct' assert('%s' in globals())''' % module_name) - + # Create the problem problem = self.build_problem(answer=script) - # Expect that we can grade an answer without + # Expect that we can grade an answer without # getting an exception try: problem.grade_answers({'1_2_1': '42'}) except ResponseError: - self.fail("Could not use name '%s' in custom response" - % module_name) - + self.fail("Could not use name '{0}s' in custom response".format(module_name)) + def test_module_imports_function(self): ''' Check that the correct modules are available to custom response scripts ''' - for module_name in ['random', 'numpy', 'math', 'scipy', + for module_name in ['random', 'numpy', 'math', 'scipy', 'calc', 'eia', 'chemcalc', 'chemtools', 'miller', 'draganddrop']: @@ -1036,18 +1030,17 @@ class CustomResponseTest(ResponseTest): def check_func(expect, answer_given): assert('%s' in globals()) return True''' % module_name) - + # Create the problem problem = self.build_problem(script=script, cfn="check_func") - # Expect that we can grade an answer without + # Expect that we can grade an answer without # getting an exception try: problem.grade_answers({'1_2_1': '42'}) except ResponseError: - self.fail("Could not use name '%s' in custom response" - % module_name) + self.fail("Could not use name '{0}s' in custom response".format(module_name)) class SchematicResponseTest(ResponseTest):