From d70b74c7010f02f04377624c499b5f68543763c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 19 Nov 2012 18:18:08 +0200 Subject: [PATCH 01/29] added miller indexes grading for crystallography problems --- common/lib/capa/capa/capa_problem.py | 4 +- common/lib/capa/capa/chem/miller.py | 193 +++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 common/lib/capa/capa/chem/miller.py diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 451891d067..db42fb698a 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -33,6 +33,7 @@ from xml.sax.saxutils import unescape import chem import chem.chemcalc import chem.chemtools +import chem.miller import calc from correctmap import CorrectMap @@ -67,7 +68,8 @@ global_context = {'random': random, 'calc': calc, 'eia': eia, 'chemcalc': chem.chemcalc, - 'chemtools': chem.chemtools} + 'chemtools': chem.chemtools, + 'miller': chem.miller} # These should be removed from HTML output, including all subelements html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup"] diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py new file mode 100644 index 0000000000..38b2ea88e2 --- /dev/null +++ b/common/lib/capa/capa/chem/miller.py @@ -0,0 +1,193 @@ +# 1) Calculate miller indices by points coordinates +# 2) Grader for miller indeces and lattice type + + +import numpy as np +import math +import fractions as fr +import decimal +import unittest + + +def lcm(a, b): + """ return lcm of a,b """ + return a * b / fr.gcd(a, b) + + +def section_to_fraction(distance): + """ Convert float distance, that plane cut on axis + to fraction. Return inverted fraction + + """ + if np.isnan(distance): # plane || to axis (or contains axis) + print distance, 0 + # return inverted fration to a == nan == 1/0 => 0 / 1 + return fr.Fraction(0, 1) + elif distance == 0: # plane goes through origin + return fr.Fraction(1, 1) # ERROR, need shift of coordinates + else: + # limit_denominator to closest nicest fraction + fract = fr.Fraction(distance).limit_denominator() # 5 / 2 : numerator / denominator + print 'Distance', distance, 'Inverted fraction', fract + # return inverted fraction + return fr.Fraction(fract.denominator, fract.numerator) + + +def sub_miller(sections): + ''' Calculate miller indices. + Plane does not intersect origin + ''' + fracts = [section_to_fraction(section) for section in sections] + + print sections, fracts + + common_denominator = reduce(lcm, [fract.denominator for fract in fracts]) + print 'common_denominator:', common_denominator + + # 2) lead to a common denominator + # 3) throw away denominator + miller = [fract.numerator * math.fabs(common_denominator) / fract.denominator for fract in fracts] + + # import ipdb; ipdb.set_trace() + # nice output: + output = '(' + ''.join(map(str, map(decimal.Decimal, miller))) + ')' + print 'Miller indices:', output + return output + + +def miller(points): + """Calculate miller indices of plane + """ + + print "\nCalculating miller indices:" + print 'Points:\n', points + # calculate normal to plane + N = np.cross(points[1] - points[0], points[2] - points[0]) + print "Normal:", N + + # origin + O = np.array([0, 0, 0]) + + # point of plane + P = points[0] + + # equation of a line for axes: O + (B-O) * t + # t - parameters, B = [Bx, By, Bz]: + B = map(np.array, [[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]) + + # coordinates of intersections with axis: + sections = [np.dot(P - O, N) / np.dot(B_axis, N) if np.dot(B_axis, N) != 0 else np.nan for B_axis in B] + # import ipdb; ipdb.set_trace() + + if any(x == 0 for x in sections): # Plane goes through origin. + # Need to shift plane out of origin. + # For this change origin position + + # 1) find cube vertex, not crossed by plane + vertices = [ + # top + np.array([1.0, 1.0, 1.0]), + np.array([0.0, 0.0, 1.0]), + np.array([1.0, 0.0, 1.0]), + np.array([0.0, 1.0, 1.0]), + # bottom, except 0,0,0 + np.array([1.0, 0.0, 0.0]), + np.array([0.0, 1.0, 0.0]), + np.array([1.0, 1.0, 1.0]), + ] + + for vertex in vertices: + if np.dot(vertex - O, N) != 0: # vertex in plane + new_origin = vertex + break + + # get axis with center in new origin + X = np.array([1 - new_origin[0], new_origin[1], new_origin[2]]) + Y = np.array([new_origin[0], 1 - new_origin[1], new_origin[2]]) + Z = np.array([new_origin[0], new_origin[1], 1 - new_origin[2]]) + + # 2) calculate miller indexes by new origin + new_axis = [X - new_origin, Y - new_origin, Z - new_origin] + # coordinates of intersections with axis: + sections = [np.dot(P - new_origin, N) / np.dot(B_axis, N) if np.dot(B_axis, N) != 0 else np.nan for B_axis in new_axis] + # 3) fix signs of indexes + # 0 -> 1, 1 -> -1 + sections = (1 - 2 * new_origin) * sections + return sub_miller(sections) + + +class Test_Crystallography_Grader(unittest.TestCase): + ''' test crystallography grade function ''' + + def test_1(self): + x = np.array([0.5, 0, 0]) + y = np.array([0, 0.5, 0]) + z = np.array([0, 0, 0.5]) + self.assertEqual(miller(np.array([x, y, z])), '(222)') + + def test_2(self): + x = np.array([1, 0, 0]) + y = np.array([0, 1, 0]) + z = np.array([0, 0, 1]) + self.assertEqual(miller(np.array([x, y, z])), '(111)') + + def test_3(self): + x = np.array([1, 0.5, 1]) + y = np.array([1, 1, 0.5]) + z = np.array([0.5, 1, 1]) + self.assertEqual(miller(np.array([x, y, z])), '(222)') + + def test_4(self): + x = np.array([1. / 3, 1., 0]) + y = np.array([0, 2. / 3., 0]) + z = np.array([0, 1, 1. / 3]) + self.assertEqual(miller(np.array([x, y, z])), '(-33-3)') + + def test_5(self): + x = np.array([1. / 3, 1., 0]) + y = np.array([0, 1. / 3., 0]) + z = np.array([0, 1, 1. / 3]) + self.assertEqual(miller(np.array([x, y, z])), '(-63-6)') + + def test_6(self): + x = np.array([0, 1. / 4., 0]) + y = np.array([1. / 4, 0, 0]) + z = np.array([0, 0, 1. / 4]) + self.assertEqual(miller(np.array([x, y, z])), '(444)') + + def test_7(self): # goes throug origin + x = np.array([0, 1., 0]) + y = np.array([1., 0, 0]) + z = np.array([0.5, 1., 0]) + self.assertEqual(miller(np.array([x, y, z])), '(00-1)') + + def test_8(self): + x = np.array([0, 1., 0.5]) + y = np.array([1., 0, 0.5]) + z = np.array([0.5, 1., 0.5]) + self.assertEqual(miller(np.array([x, y, z])), '(002)') + + def test_9(self): + x = np.array([0, 1. / 4., 0]) + y = np.array([1. / 4, 0, 0]) + z = np.array([0, 0, 1. / 4]) + self.assertEqual(miller(np.array([x, y, z])), '(444)') + + def test_10(self): + x = np.array([0, 1. / 4., 0]) + y = np.array([1. / 4, 0, 0]) + z = np.array([0, 0, 1. / 4]) + self.assertEqual(miller(np.array([x, y, z])), '(444)') + + +def suite(): + + testcases = [Test_Crystallography_Grader] + suites = [] + for testcase in testcases: + suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase)) + return unittest.TestSuite(suites) + +if __name__ == "__main__": + unittest.TextTestRunner(verbosity=2).run(suite()) + From ae37b621a8634aff8a4ec8fb2b5456a3609d8e85 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Mon, 19 Nov 2012 18:19:36 +0200 Subject: [PATCH 02/29] Update to crystallography html. --- common/lib/capa/capa/templates/crystallography.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 2370f59dd2..5be1030eb0 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -2,7 +2,6 @@
-
@@ -19,7 +18,7 @@
% endif - Date: Tue, 20 Nov 2012 14:19:48 +0200 Subject: [PATCH 03/29] Updated crystallography template to reflect changes made in the JS part. --- .../capa/capa/templates/crystallography.html | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 5be1030eb0..3099faef7b 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -1,33 +1,37 @@
-
+
+ + +
- % if status == 'unsubmitted': -
+
% elif status == 'correct': -
+
% elif status == 'incorrect': -
+
% elif status == 'incomplete': -
+
% endif + % if hidden: -
+
% endif - + + % if hidden: + style="display:none;" + % endif + /> + +

% if status == 'unsubmitted': unanswered % elif status == 'correct': @@ -37,14 +41,15 @@ % elif status == 'incomplete': incomplete % endif -

+

-

+

- % if msg: - ${msg|n} - % endif -% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: -
-% endif + % if msg: + ${msg|n} + % endif + + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: +
+ % endif
From aaeffa5316f526e34b6c12db51a1952fcda81d4d Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Wed, 21 Nov 2012 16:52:17 +0200 Subject: [PATCH 04/29] Updated template for crystallography so that it corresponds to the cnahges made in the JS part. --- common/lib/capa/capa/templates/crystallography.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 3099faef7b..30d9693ecb 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -1,7 +1,13 @@
- +
+ Type: +
+
+ Triangle: + Full: +
From d5dd725537786dc4b739301197ced40defb5569c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Wed, 21 Nov 2012 17:53:51 +0200 Subject: [PATCH 05/29] miller indices calculation --- common/lib/capa/capa/chem/miller.py | 176 ++++++++++++++++++++-------- 1 file changed, 124 insertions(+), 52 deletions(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index 38b2ea88e2..a3adc7ffb4 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -1,12 +1,9 @@ -# 1) Calculate miller indices by points coordinates -# 2) Grader for miller indeces and lattice type - - import numpy as np import math import fractions as fr import decimal import unittest +import json def lcm(a, b): @@ -27,7 +24,8 @@ def section_to_fraction(distance): return fr.Fraction(1, 1) # ERROR, need shift of coordinates else: # limit_denominator to closest nicest fraction - fract = fr.Fraction(distance).limit_denominator() # 5 / 2 : numerator / denominator + # import ipdb; ipdb.set_trace() + fract = fr.Fraction(distance).limit_denominator(10) # 5 / 2 : numerator / denominator print 'Distance', distance, 'Inverted fraction', fract # return inverted fraction return fr.Fraction(fract.denominator, fract.numerator) @@ -50,7 +48,9 @@ def sub_miller(sections): # import ipdb; ipdb.set_trace() # nice output: - output = '(' + ''.join(map(str, map(decimal.Decimal, miller))) + ')' + # output = '(' + ''.join(map(str, map(decimal.Decimal, miller))) + ')' + # import ipdb; ipdb.set_trace() + output = '(' + ','.join(map(str, map(decimal.Decimal, miller))) + ')' print 'Miller indices:', output return output @@ -59,11 +59,11 @@ def miller(points): """Calculate miller indices of plane """ - print "\nCalculating miller indices:" - print 'Points:\n', points + # print "\nCalculating miller indices:" + # print 'Points:\n', points # calculate normal to plane N = np.cross(points[1] - points[0], points[2] - points[0]) - print "Normal:", N + # print "Normal:", N # origin O = np.array([0, 0, 0]) @@ -116,73 +116,146 @@ def miller(points): return sub_miller(sections) -class Test_Crystallography_Grader(unittest.TestCase): +def grade(user_input, correct_answer): + ''' + Format: + user_input: {"lattice":"sc","points":[["0.77","0.00","1.00"],["0.78","1.00","0.00"],["0.00","1.00","0.72"]]} + "lattice" is one of: "", "sc", "bcc", "fcc" + correct_answer: {'miller': '(00-1)', 'lattice': 'bcc'} + ''' + def negative(s): + # import ipdb; ipdb.set_trace() + output = '' + i = 1 + while i in range(1, len(s) - 1): + if s[i] in (',', ' '): + output += s[i] + elif s[i] not in ('-', '0'): + output += '-' + s[i] + elif s[i] == '0': + output += s[i] + else: + i += 1 + output += s[i] + i += 1 + # import ipdb; ipdb.set_trace() + return '(' + output + ')' + + user_answer = json.loads(user_input) + + if user_answer['lattice'] != correct_answer['lattice']: + return False + + points = [map(float, p) for p in user_answer['points']] + points = [np.array(point) for point in points] + # import ipdb; ipdb.set_trace() + print miller(points), (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')) + + if miller(points) in (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')): + return True + + return False + + +class Test_Crystallography_Miller(unittest.TestCase): ''' test crystallography grade function ''' def test_1(self): - x = np.array([0.5, 0, 0]) - y = np.array([0, 0.5, 0]) - z = np.array([0, 0, 0.5]) - self.assertEqual(miller(np.array([x, y, z])), '(222)') + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}' + self.assertTrue(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) def test_2(self): - x = np.array([1, 0, 0]) - y = np.array([0, 1, 0]) - z = np.array([0, 0, 1]) - self.assertEqual(miller(np.array([x, y, z])), '(111)') + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,1,1)', 'lattice': 'bcc'})) def test_3(self): - x = np.array([1, 0.5, 1]) - y = np.array([1, 1, 0.5]) - z = np.array([0.5, 1, 1]) - self.assertEqual(miller(np.array([x, y, z])), '(222)') + user_input = '{"lattice": "bcc", "points": [["1.00", "0.50", "1.00"], ["1.00", "1.00", "0.50"], ["0.50", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) def test_4(self): - x = np.array([1. / 3, 1., 0]) - y = np.array([0, 2. / 3., 0]) - z = np.array([0, 1, 1. / 3]) - self.assertEqual(miller(np.array([x, y, z])), '(-33-3)') + user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.664", "0.00"], ["0.00", "1.00", "0.33"]]}' + self.assertTrue(grade(user_input, {'miller': '(-3, 3, -3)', 'lattice': 'bcc'})) def test_5(self): - x = np.array([1. / 3, 1., 0]) - y = np.array([0, 1. / 3., 0]) - z = np.array([0, 1, 1. / 3]) - self.assertEqual(miller(np.array([x, y, z])), '(-63-6)') + user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.33", "0.00"], ["0.00", "1.00", "0.33"]]}' + self.assertTrue(grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'})) def test_6(self): - x = np.array([0, 1. / 4., 0]) - y = np.array([1. / 4, 0, 0]) - z = np.array([0, 0, 1. / 4]) - self.assertEqual(miller(np.array([x, y, z])), '(444)') + user_input = '{"lattice": "bcc", "points": [["0.00", "0.25", "0.00"], ["0.25", "0.00", "0.00"], ["0.00", "0.00", "0.25"]]}' + self.assertTrue(grade(user_input, {'miller': '(4,4,4)', 'lattice': 'bcc'})) def test_7(self): # goes throug origin - x = np.array([0, 1., 0]) - y = np.array([1., 0, 0]) - z = np.array([0.5, 1., 0]) - self.assertEqual(miller(np.array([x, y, z])), '(00-1)') + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "0.00", "0.00"], ["0.50", "1.00", "0.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,0,-1)', 'lattice': 'bcc'})) def test_8(self): - x = np.array([0, 1., 0.5]) - y = np.array([1., 0, 0.5]) - z = np.array([0.5, 1., 0.5]) - self.assertEqual(miller(np.array([x, y, z])), '(002)') + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.50"], ["1.00", "0.00", "0.50"], ["0.50", "1.00", "0.50"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,0,2)', 'lattice': 'bcc'})) def test_9(self): - x = np.array([0, 1. / 4., 0]) - y = np.array([1. / 4, 0, 0]) - z = np.array([0, 0, 1. / 4]) - self.assertEqual(miller(np.array([x, y, z])), '(444)') + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "1.00", "1.00"], ["1.00", "0.00", "0.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,1,0)', 'lattice': 'bcc'})) def test_10(self): - x = np.array([0, 1. / 4., 0]) - y = np.array([1. / 4, 0, 0]) - z = np.array([0, 0, 1. / 4]) - self.assertEqual(miller(np.array([x, y, z])), '(444)') + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "0.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) + + def test_11(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "0.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,1,2)', 'lattice': 'bcc'})) + + def test_12(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["0.00", "0.00", "0.50"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,1,-2)', 'lattice': 'bcc'})) + + def test_13(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.50", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(2,0,1)', 'lattice': 'bcc'})) + + def test_14(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "0.00", "1.00"], ["0.50", "1.00", "0.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(2,-1,0)', 'lattice': 'bcc'})) + + def test_15(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) + + def test_16(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) + + def test_17(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "1.00"], ["1.00", "1.00", "0.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(-1,1,1)', 'lattice': 'bcc'})) + + def test_18(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) + + def test_19(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(-1,1,0)', 'lattice': 'bcc'})) + + def test_20(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,0,1)', 'lattice': 'bcc'})) + + def test_21(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(-1,0,1)', 'lattice': 'bcc'})) + + def test_22(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,1,1)', 'lattice': 'bcc'})) + + def test_23(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'})) def suite(): - testcases = [Test_Crystallography_Grader] + testcases = [Test_Crystallography_Miller] suites = [] for testcase in testcases: suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase)) @@ -190,4 +263,3 @@ def suite(): if __name__ == "__main__": unittest.TextTestRunner(verbosity=2).run(suite()) - From 33159824a5593d3e30422924ec31221a9e16c333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Wed, 21 Nov 2012 18:02:48 +0200 Subject: [PATCH 06/29] added test for lattices --- common/lib/capa/capa/chem/miller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index a3adc7ffb4..3fa49c046b 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -252,6 +252,10 @@ class Test_Crystallography_Miller(unittest.TestCase): user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' self.assertTrue(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'})) + def test_wrong_lattice(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertFalse(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'fcc'})) + def suite(): From 111667defdb29b1d09004a93357ded0a890262bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Wed, 21 Nov 2012 19:02:40 +0200 Subject: [PATCH 07/29] UI delta and tests: --- common/lib/capa/capa/chem/miller.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index 3fa49c046b..8a98732874 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -16,12 +16,13 @@ def section_to_fraction(distance): to fraction. Return inverted fraction """ + # import ipdb; ipdb.set_trace() if np.isnan(distance): # plane || to axis (or contains axis) print distance, 0 # return inverted fration to a == nan == 1/0 => 0 / 1 return fr.Fraction(0, 1) - elif distance == 0: # plane goes through origin - return fr.Fraction(1, 1) # ERROR, need shift of coordinates + elif math.fabs(distance) <= 0.05: # plane goes through origin, 0.02 - UI delta + return fr.Fraction(1 if distance >= 0 else -1, 1) # ERROR, need shift of coordinates else: # limit_denominator to closest nicest fraction # import ipdb; ipdb.set_trace() @@ -51,7 +52,7 @@ def sub_miller(sections): # output = '(' + ''.join(map(str, map(decimal.Decimal, miller))) + ')' # import ipdb; ipdb.set_trace() output = '(' + ','.join(map(str, map(decimal.Decimal, miller))) + ')' - print 'Miller indices:', output + # print 'Miller indices:', output return output @@ -252,9 +253,17 @@ class Test_Crystallography_Miller(unittest.TestCase): user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' self.assertTrue(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'})) + def test_24(self): + user_input = '{"lattice": "bcc", "points": [["0.66", "0.00", "0.00"], ["0.00", "0.66", "0.00"], ["0.00", "0.00", "0.66"]]}' + self.assertTrue(grade(user_input, {'miller': '(3,3,3)', 'lattice': 'bcc'})) + + def test_25(self): + user_input = u'{"lattice":"","points":[["0.00","0.00","0.01"],["1.00","1.00","0.01"],["0.00","1.00","1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': ''})) + def test_wrong_lattice(self): user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' - self.assertFalse(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'fcc'})) + self.assertFalse(grade(user_input, {'miller': '(3,3,3)', 'lattice': 'fcc'})) def suite(): From 166ff823a79e52eab0e33e72aa68dbb563b548ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 22 Nov 2012 12:53:21 +0200 Subject: [PATCH 08/29] removed hidden parameter to template, style='hidden' is set by default for input --- common/lib/capa/capa/inputtypes.py | 7 ++----- common/lib/capa/capa/templates/crystallography.html | 13 +------------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 0b2250f98d..ec1cda83c7 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -671,18 +671,15 @@ class Crystallography(InputTypeBase): """ Note: height, width are required. """ - return [Attribute('size', None), - Attribute('height'), + return [Attribute('height'), Attribute('width'), - - # can probably be removed (textline should prob be always-hidden) - Attribute('hidden', ''), ] registry.register(Crystallography) # ------------------------------------------------------------------------- + class VseprInput(InputTypeBase): """ Input for molecular geometry--show possible structures, let student diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 30d9693ecb..79779384c8 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -23,19 +23,8 @@
% endif - % if hidden: -
- % endif - +

% if status == 'unsubmitted': From 19c463b7390a5bde585669ff9246e3a690c2d00e Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 22 Nov 2012 13:55:50 +0200 Subject: [PATCH 09/29] Removed radio buttons for selecting the intersection type from crystallography template. --- common/lib/capa/capa/templates/crystallography.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 30d9693ecb..1d679ff445 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -4,10 +4,6 @@

Type:
-
- Triangle: - Full: -
From 3e8e576e28b1caf26808e9f9b99a5bbb66719c63 Mon Sep 17 00:00:00 2001 From: Valera Rozuvan Date: Thu, 22 Nov 2012 16:52:59 +0200 Subject: [PATCH 10/29] Slight change in caption for lattice select. --- common/lib/capa/capa/templates/crystallography.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 102b6136bb..8dcbff354b 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -2,7 +2,7 @@
- Type: + Lattice:
From 77c831eb103878e22765eb183e359111f393d893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 22 Nov 2012 18:43:41 +0200 Subject: [PATCH 11/29] documenation for chem.miller --- docs/source/capa.rst | 6 ++++ docs/source/chem.rst | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 docs/source/chem.rst diff --git a/docs/source/capa.rst b/docs/source/capa.rst index f83d89f52d..345855af5e 100644 --- a/docs/source/capa.rst +++ b/docs/source/capa.rst @@ -1,9 +1,15 @@ ******************************************* Capa module ******************************************* +Contents: .. module:: capa +.. toctree:: + :maxdepth: 2 + + chem.rst + Calc ==== diff --git a/docs/source/chem.rst b/docs/source/chem.rst new file mode 100644 index 0000000000..26e01a3238 --- /dev/null +++ b/docs/source/chem.rst @@ -0,0 +1,69 @@ +******************************************* +Chem module +******************************************* + +.. module:: chem + +Miller +====== + +.. automodule:: capa.chem.miller + :members: + :show-inheritance: + +UI part and inputtypes +---------------------- +Miller module is used in the system in crystallography problems. +Crystallography is a class in :mod:`capa` inputtypes module. +It uses *crystallography.html* for rendering and **crystallography.js** +for UI part. + +Documentation from **crystallography.js**:: + + For a crystallographic problem of the type + + Given a plane definition via miller indexes, specify it by plotting points on the edges + of a 3D cube. Additionally, select the correct Bravais cubic lattice type depending on the + physical crystal mentioned in the problem. + + we create a graph which contains a cube, and a 3D Cartesian coordinate system. The interface + will allow to plot 3 points anywhere along the edges of the cube, and select which type of + Bravais lattice should be displayed along with the basic cube outline. + + When 3 points are successfully plotted, an intersection of the resulting plane (defined by + the 3 plotted points), and the cube, will be automatically displayed for clarity. + + After lotting the three points, it is possible to continue plotting additional points. By + doing so, the point that was plotted first (from the three that already exist), will be + removed, and the new point will be added. The intersection of the resulting new plane and + the cube will be redrawn. + + The UI has been designed in such a way, that the user is able to determine which point will + be removed next (if adding a new point). This is achieved via filling the to-be-removed point + with a different color. + + + +Chemcalc +======== + +.. automodule:: capa.chem.chemcalc + :members: + :show-inheritance: + +Chemtools +========= + +.. automodule:: capa.chem.chemtools + :members: + :show-inheritance: + + +Tests +===== + +.. automodule:: capa.chem.tests + :members: + :show-inheritance: + + From 4b4b2c9927dd28de403cd5488b29ec280465f1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 22 Nov 2012 18:44:31 +0200 Subject: [PATCH 12/29] added documentation, tests, and rounding to closes 0.05 value --- common/lib/capa/capa/chem/miller.py | 322 +++++++++++++++++++--------- 1 file changed, 223 insertions(+), 99 deletions(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index 8a98732874..f397e9d35e 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -1,3 +1,5 @@ +""" Calculation of Miller indices """ + import numpy as np import math import fractions as fr @@ -7,150 +9,256 @@ import json def lcm(a, b): - """ return lcm of a,b """ + """ + Returns least common multiple of a, b + + Args: + a, b: floats + + Returns: + float + """ return a * b / fr.gcd(a, b) -def section_to_fraction(distance): - """ Convert float distance, that plane cut on axis - to fraction. Return inverted fraction +def segment_to_fraction(distance): + """ + Converts lengths of which the plane cuts the axes to fraction. + + Tries convert distance to closest nicest fraction with denominator less or + equal than 10. It is + purely for simplicity and clearance of learning purposes. Jenny: 'In typical + courses students usually do not encounter indices any higher than 6'. + + If distance is not a number (numpy nan), it means that plane is parallel to + axis or contains it. Inverted fraction to nan (nan is 1/0) = 0 / 1 is + returned + + Generally (special cases): + + a) if distance is smaller than some constant, i.g. 0.01011, + than fraction's denominator usually much greater than 10. + + b) Also, if student will set point on 0.66 -> 1/3, so it is 333 plane, + But if he will slightly move the mouse and click on 0.65 -> it will be + (16,15,16) plane. That's why we are doing adjustments for points coordinates, + to the closest tick, tick + tick / 2 value. And now UI sends to server only + values multiple to 0.05 (half of tick). Same rounding is implemented for + unittests. + + But if one will want to calculate miller indices with exact coordinates and + with nice fractions (which produce small Miller indices), he may want shift + to new origin if segments are like S = (0.015, > 0.05, >0.05) - close to zero + in one coordinate. He may update S to (0, >0.05, >0.05) and shift origin. + In this way he can recieve nice small fractions. Also there is can be + degenerated case when S = (0.015, 0.012, >0.05) - if update S to (0, 0, >0.05) - + it is a line. This case should be considered separately. Small nice Miller + numbers and possibility to create very small segments can not be implemented + at same time). + + + Args: + distance: float distance that plane cuts on axis, it must not be 0. + Distance is multiple of 0.05. + + Returns: + Inverted fraction. + 0 / 1 if distance is nan """ - # import ipdb; ipdb.set_trace() - if np.isnan(distance): # plane || to axis (or contains axis) - print distance, 0 - # return inverted fration to a == nan == 1/0 => 0 / 1 + if np.isnan(distance): return fr.Fraction(0, 1) - elif math.fabs(distance) <= 0.05: # plane goes through origin, 0.02 - UI delta - return fr.Fraction(1 if distance >= 0 else -1, 1) # ERROR, need shift of coordinates else: - # limit_denominator to closest nicest fraction - # import ipdb; ipdb.set_trace() - fract = fr.Fraction(distance).limit_denominator(10) # 5 / 2 : numerator / denominator - print 'Distance', distance, 'Inverted fraction', fract - # return inverted fraction + fract = fr.Fraction(distance).limit_denominator(10) return fr.Fraction(fract.denominator, fract.numerator) -def sub_miller(sections): - ''' Calculate miller indices. - Plane does not intersect origin +def sub_miller(segments): ''' - fracts = [section_to_fraction(section) for section in sections] + Calculates Miller indices from segments. - print sections, fracts + Algorithm: + 1. Obtain inverted fraction from segments + + 2. Find common denominator of inverted fractions + + 3. Lead fractions to common denominator and throws denominator away. + + 4. Return obtained values. + + Args: + List of 3 floats, meaning distances that plane cuts on x, y, z axes. + Any float not equals zero, it means that plane does not intersect origin, + i. e. shift of origin has already been done. + + Returns: + String that represents Miller indices, e.g: (-6,3,-6) or (2,2,2) + ''' + fracts = [segment_to_fraction(segment) for segment in segments] common_denominator = reduce(lcm, [fract.denominator for fract in fracts]) - print 'common_denominator:', common_denominator - - # 2) lead to a common denominator - # 3) throw away denominator - miller = [fract.numerator * math.fabs(common_denominator) / fract.denominator for fract in fracts] - - # import ipdb; ipdb.set_trace() - # nice output: - # output = '(' + ''.join(map(str, map(decimal.Decimal, miller))) + ')' - # import ipdb; ipdb.set_trace() - output = '(' + ','.join(map(str, map(decimal.Decimal, miller))) + ')' - # print 'Miller indices:', output - return output + miller = ([fract.numerator * math.fabs(common_denominator) / + fract.denominator for fract in fracts]) + return'(' + ','.join(map(str, map(decimal.Decimal, miller))) + ')' def miller(points): - """Calculate miller indices of plane + """ + Calculates Miller indices from points. + + Algorithm: + + 1. Calculate normal vector to a plane that goes trough all points. + + 2. Set origin. + + 3. Create Cartesian coordinate system (Ccs). + + 4. Find the lengths of segments of which the plane cuts the axes. Equation + of a line for axes: Origin + (Coordinate_vector - Origin) * parameter. + + 5. If plane goes trough Origin: + + a) Find new random origin: find unit cube vertex, not crossed by a plane. + + b) Repeat 2-4. + + c) Fix signs of segments after Origin shift. This means to consider + original directions of axes. I.g.: Origin was 0,0,0 and became + new_origin. If new_origin has same Y coordinate as Origin, then segment + does not change its sign. But if new_origin has another Y coordinate than + origin (was 0, became 1), than segment has to change its sign (it now + lies on negative side of Y axis). New Origin 0 value of X or Y or Z + coordinate means that segment does not change sign, 1 value -> does + change. So new sign is (1 - 2 * new_origin): 0 -> 1, 1 -> -1 + + 6. Run function that calculates miller indices from segments. + + Args: + List of points. Each point is list of float coordinates. Order of + coordinates in point's list: x, y, z. Points are different! + + Returns: + String that represents Miller indices, e.g: (-6,3,-6) or (2,2,2) """ - # print "\nCalculating miller indices:" - # print 'Points:\n', points - # calculate normal to plane N = np.cross(points[1] - points[0], points[2] - points[0]) - # print "Normal:", N - - # origin O = np.array([0, 0, 0]) - - # point of plane - P = points[0] - - # equation of a line for axes: O + (B-O) * t - # t - parameters, B = [Bx, By, Bz]: - B = map(np.array, [[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]) - - # coordinates of intersections with axis: - sections = [np.dot(P - O, N) / np.dot(B_axis, N) if np.dot(B_axis, N) != 0 else np.nan for B_axis in B] - # import ipdb; ipdb.set_trace() - - if any(x == 0 for x in sections): # Plane goes through origin. - # Need to shift plane out of origin. - # For this change origin position - - # 1) find cube vertex, not crossed by plane - vertices = [ - # top - np.array([1.0, 1.0, 1.0]), - np.array([0.0, 0.0, 1.0]), - np.array([1.0, 0.0, 1.0]), - np.array([0.0, 1.0, 1.0]), - # bottom, except 0,0,0 - np.array([1.0, 0.0, 0.0]), - np.array([0.0, 1.0, 0.0]), - np.array([1.0, 1.0, 1.0]), - ] - + P = points[0] # point of plane + Ccs = map(np.array, [[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]) + segments = ([np.dot(P - O, N) / np.dot(ort, N) if np.dot(ort, N) != 0 else + np.nan for ort in Ccs]) + if any(x == 0 for x in segments): # Plane goes through origin. + vertices = [ # top: + np.array([1.0, 1.0, 1.0]), + np.array([0.0, 0.0, 1.0]), + np.array([1.0, 0.0, 1.0]), + np.array([0.0, 1.0, 1.0]), + # bottom, except 0,0,0: + np.array([1.0, 0.0, 0.0]), + np.array([0.0, 1.0, 0.0]), + np.array([1.0, 1.0, 1.0]), + ] for vertex in vertices: - if np.dot(vertex - O, N) != 0: # vertex in plane + if np.dot(vertex - O, N) != 0: # vertex not in plane new_origin = vertex break - - # get axis with center in new origin + # obtain new axes with center in new origin X = np.array([1 - new_origin[0], new_origin[1], new_origin[2]]) Y = np.array([new_origin[0], 1 - new_origin[1], new_origin[2]]) Z = np.array([new_origin[0], new_origin[1], 1 - new_origin[2]]) + new_Ccs = [X - new_origin, Y - new_origin, Z - new_origin] + segments = ([np.dot(P - new_origin, N) / np.dot(ort, N) if + np.dot(ort, N) != 0 else np.nan for ort in new_Ccs]) + # fix signs of indices: 0 -> 1, 1 -> -1 ( + segments = (1 - 2 * new_origin) * segments - # 2) calculate miller indexes by new origin - new_axis = [X - new_origin, Y - new_origin, Z - new_origin] - # coordinates of intersections with axis: - sections = [np.dot(P - new_origin, N) / np.dot(B_axis, N) if np.dot(B_axis, N) != 0 else np.nan for B_axis in new_axis] - # 3) fix signs of indexes - # 0 -> 1, 1 -> -1 - sections = (1 - 2 * new_origin) * sections - return sub_miller(sections) + return sub_miller(segments) def grade(user_input, correct_answer): ''' - Format: - user_input: {"lattice":"sc","points":[["0.77","0.00","1.00"],["0.78","1.00","0.00"],["0.00","1.00","0.72"]]} - "lattice" is one of: "", "sc", "bcc", "fcc" - correct_answer: {'miller': '(00-1)', 'lattice': 'bcc'} + Grade crystallography problem. + + Returns true if lattices are the same and Miller indices are same or minus + same. E.g. (2,2,2) = (2, 2, 2) or (-2, -2, -2). Because sign depends only + on student's selection of origin. + + Args: + user_input, correct_answer: json. Format: + + user_input: {"lattice":"sc","points":[["0.77","0.00","1.00"], + ["0.78","1.00","0.00"],["0.00","1.00","0.72"]]} + + correct_answer: {'miller': '(00-1)', 'lattice': 'bcc'} + + "lattice" is one of: "", "sc", "bcc", "fcc" + + Returns: + True or false. ''' - def negative(s): - # import ipdb; ipdb.set_trace() + def negative(m): + """ + Change sign of Miller indices. + + Args: + m: string with meaning of Miller indices. E.g.: + (-6,3,-6) -> (6, -3, 6) + + Returns: + String with changed signs. + """ output = '' i = 1 - while i in range(1, len(s) - 1): - if s[i] in (',', ' '): - output += s[i] - elif s[i] not in ('-', '0'): - output += '-' + s[i] - elif s[i] == '0': - output += s[i] + while i in range(1, len(m) - 1): + if m[i] in (',', ' '): + output += m[i] + elif m[i] not in ('-', '0'): + output += '-' + m[i] + elif m[i] == '0': + output += m[i] else: i += 1 - output += s[i] + output += m[i] i += 1 - # import ipdb; ipdb.set_trace() return '(' + output + ')' + def round0_25(point): + """ + Rounds point coordinates to closest 0.5 value. + + Args: + point: list of float coordinates. Order of coordinates: x, y, z. + + Returns: + list of coordinates rounded to closes 0.5 value + """ + rounded_points = [] + for coord in point: + base = math.floor(coord * 10) + fractional_part = (coord * 10 - base) + aliquot0_25 = math.floor(fractional_part / 0.25) + if aliquot0_25 == 0.0: + rounded_points.append(base / 10) + if aliquot0_25 in (1.0, 2.0): + rounded_points.append(base / 10 + 0.05) + if aliquot0_25 == 3.0: + rounded_points.append(base / 10 + 0.1) + return rounded_points + user_answer = json.loads(user_input) if user_answer['lattice'] != correct_answer['lattice']: return False points = [map(float, p) for p in user_answer['points']] + + # round point to closes 0.05 value + points = [round0_25(point) for point in points] + points = [np.array(point) for point in points] - # import ipdb; ipdb.set_trace() - print miller(points), (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')) + print miller(points), (correct_answer['miller'].replace(' ', ''), + negative(correct_answer['miller']).replace(' ', '')) if miller(points) in (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')): return True @@ -159,7 +267,7 @@ def grade(user_input, correct_answer): class Test_Crystallography_Miller(unittest.TestCase): - ''' test crystallography grade function ''' + ''' Tests for crystallography grade function.''' def test_1(self): user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}' @@ -178,8 +286,10 @@ class Test_Crystallography_Miller(unittest.TestCase): self.assertTrue(grade(user_input, {'miller': '(-3, 3, -3)', 'lattice': 'bcc'})) def test_5(self): + """ return true only in case points coordinates are exact. + But if they transform to closest 0.05 value it is not true""" user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.33", "0.00"], ["0.00", "1.00", "0.33"]]}' - self.assertTrue(grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'})) + self.assertFalse(grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'})) def test_6(self): user_input = '{"lattice": "bcc", "points": [["0.00", "0.25", "0.00"], ["0.25", "0.00", "0.00"], ["0.00", "0.00", "0.25"]]}' @@ -261,6 +371,20 @@ class Test_Crystallography_Miller(unittest.TestCase): user_input = u'{"lattice":"","points":[["0.00","0.00","0.01"],["1.00","1.00","0.01"],["0.00","1.00","1.00"]]}' self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': ''})) + def test_26(self): + user_input = u'{"lattice":"","points":[["0.00","0.01","0.00"],["1.00","0.00","0.00"],["0.00","0.00","1.00"]]}' + self.assertTrue(grade(user_input, {'miller': '(0,-1,0)', 'lattice': ''})) + + def test_27(self): + """ rounding to 0.35""" + user_input = u'{"lattice":"","points":[["0.33","0.00","0.00"],["0.00","0.33","0.00"],["0.00","0.00","0.33"]]}' + self.assertTrue(grade(user_input, {'miller': '(3,3,3)', 'lattice': ''})) + + def test_28(self): + """ rounding to 0.30""" + user_input = u'{"lattice":"","points":[["0.30","0.00","0.00"],["0.00","0.30","0.00"],["0.00","0.00","0.30"]]}' + self.assertTrue(grade(user_input, {'miller': '(10,10,10)', 'lattice': ''})) + def test_wrong_lattice(self): user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' self.assertFalse(grade(user_input, {'miller': '(3,3,3)', 'lattice': 'fcc'})) From 01b59923726d6a42841adabc23c41fcea9e7905e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Thu, 22 Nov 2012 18:53:35 +0200 Subject: [PATCH 13/29] comment outputs --- common/lib/capa/capa/chem/miller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index f397e9d35e..f1482de80f 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -257,8 +257,8 @@ def grade(user_input, correct_answer): points = [round0_25(point) for point in points] points = [np.array(point) for point in points] - print miller(points), (correct_answer['miller'].replace(' ', ''), - negative(correct_answer['miller']).replace(' ', '')) + # print miller(points), (correct_answer['miller'].replace(' ', ''), + # negative(correct_answer['miller']).replace(' ', '')) if miller(points) in (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')): return True From 7fcf04eaf5aab47bb8d3030ab5e2aec5b4c5aabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Fri, 2 Nov 2012 14:14:13 +0200 Subject: [PATCH 14/29] add shapely --- requirements.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 28b12404a1..ac50bd1691 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,11 +8,11 @@ lxml boto mako python-memcached -python-openid +python-openid path.py django_debug_toolbar fs -beautifulsoup +beautifulsoup beautifulsoup4 feedparser requests @@ -37,7 +37,7 @@ django-jasmine django-keyedcache django-mako django-masquerade -django-openid-auth +django-openid-auth django-robots django-ses django-storages @@ -54,3 +54,4 @@ dogstatsd-python # Taking out MySQL-python for now because it requires mysql to be installed, so breaks updates on content folks' envs. # MySQL-python sphinx +Shapely From 88c98b9a56b42b0541e8ab1dc7be6ad05ce6a5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Fri, 2 Nov 2012 14:16:37 +0200 Subject: [PATCH 15/29] added multiple regions support --- common/lib/capa/capa/responsetypes.py | 63 +++++++++++++++++---------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index b990c489b3..85efd70cee 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -23,6 +23,7 @@ import abc import os import subprocess import xml.sax.saxutils as saxutils +from shapely.geometry import Polygon, Point # specific library imports from calc import evaluator, UndefinedVariable @@ -1720,12 +1721,20 @@ class ImageResponse(LoncapaResponse): Lon-CAPA requires that each has a inside it. That doesn't make sense to me (Ike). Instead, let's have it such that should contain one or more stanzas. Each should specify - a rectangle, given as an attribute, defining the correct answer. + a rectangle(s) or region(s), given as an attribute, defining the correct answer. + + Rectangle(s) are more prioritized over regions due to simplicity and backward compatibility. + In this example regions will be ignored: + + + Regions is list of lists [region1, region2, region3, ...] where regionN is ordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. """ snippets = [{'snippet': ''' - + + + '''}] response_tag = 'imageresponse' @@ -1733,7 +1742,7 @@ class ImageResponse(LoncapaResponse): def setup_response(self): self.ielements = self.inputfields - self.answer_ids = [ie.get('id') for ie in self.ielements] + self.answer_ids = [ie.get('id') for ie in self.ielements] def get_score(self, student_answers): correct_map = CorrectMap() @@ -1743,7 +1752,7 @@ class ImageResponse(LoncapaResponse): given = student_answers[aid] # this should be a string of the form '[x,y]' correct_map.set(aid, 'incorrect') - if not given: # No answer to parse. Mark as incorrect and move on + if not given: # No answer to parse. Mark as incorrect and move on continue # parse given answer @@ -1753,29 +1762,37 @@ class ImageResponse(LoncapaResponse): 'error grading %s (input=%s)' % (aid, given)) (gx, gy) = [int(x) for x in m.groups()] - # Check whether given point lies in any of the solution rectangles - solution_rectangles = expectedset[aid].split(';') - for solution_rectangle in solution_rectangles: - # parse expected answer - # TODO: Compile regexp on file load - m = re.match('[\(\[]([0-9]+),([0-9]+)[\)\]]-[\(\[]([0-9]+),([0-9]+)[\)\]]', - solution_rectangle.strip().replace(' ', '')) - if not m: - msg = 'Error in problem specification! cannot parse rectangle in %s' % ( - etree.tostring(self.ielements[aid], pretty_print=True)) - raise Exception('[capamodule.capa.responsetypes.imageinput] ' + msg) - (llx, lly, urx, ury) = [int(x) for x in m.groups()] - - # answer is correct if (x,y) is within the specified rectangle - if (llx <= gx <= urx) and (lly <= gy <= ury): - correct_map.set(aid, 'correct') - break + rectangles, regions = expectedset + if rectangles[aid]: # rectangles part - for backward compatibility + # Check whether given point lies in any of the solution rectangles + solution_rectangles = rectangles[aid].split(';') + for solution_rectangle in solution_rectangles: + # parse expected answer + # TODO: Compile regexp on file load + m = re.match('[\(\[]([0-9]+),([0-9]+)[\)\]]-[\(\[]([0-9]+),([0-9]+)[\)\]]', + solution_rectangle.strip().replace(' ', '')) + if not m: + msg = 'Error in problem specification! cannot parse rectangle in %s' % ( + etree.tostring(self.ielements[aid], pretty_print=True)) + raise Exception('[capamodule.capa.responsetypes.imageinput] ' + msg) + (llx, lly, urx, ury) = [int(x) for x in m.groups()] + # answer is correct if (x,y) is within the specified rectangle + if (llx <= gx <= urx) and (lly <= gy <= ury): + correct_map.set(aid, 'correct') + break + else: # rectangles are more prioretized for same id + if regions[aid]: + parsed_region = json.loads(regions[aid]) + for region in parsed_region: + if Polygon(region).contains(Point(gx, gy)): + correct_map.set(aid, 'correct') + break return correct_map def get_answers(self): - return dict([(ie.get('id'), ie.get('rectangle')) for ie in self.ielements]) - + return (dict([(ie.get('id'), ie.get('rectangle')) for ie in self.ielements]), + dict([(ie.get('id'), ie.get('regions')) for ie in self.ielements])) #----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses # FIXME: To be replaced by auto-registration From 406e7e29c04bcbe829752a802f675f22c1a8629b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Fri, 2 Nov 2012 17:25:18 +0200 Subject: [PATCH 16/29] added geos to requirements --- brew-formulas.txt | 15 ++++++++------- create-dev-env.sh | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/brew-formulas.txt b/brew-formulas.txt index b5b555e2a0..e06829a43a 100644 --- a/brew-formulas.txt +++ b/brew-formulas.txt @@ -1,10 +1,11 @@ -readline -sqlite -gdbm -pkg-config -gfortran -python -yuicompressor +readline +sqlite +gdbm +pkg-config +gfortran +python +yuicompressor node graphviz mysql +geos diff --git a/create-dev-env.sh b/create-dev-env.sh index e481d3fd5e..2a7f68d3b0 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -99,7 +99,7 @@ NUMPY_VER="1.6.2" SCIPY_VER="0.10.1" BREW_FILE="$BASE/mitx/brew-formulas.txt" LOG="/var/tmp/install-$(date +%Y%m%d-%H%M%S).log" -APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor nodejs npm graphviz graphviz-dev mysql-server libmysqlclient-dev" +APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor nodejs npm graphviz graphviz-dev mysql-server libmysqlclient-dev geos" if [[ $EUID -eq 0 ]]; then error "This script should not be run using sudo or as the root user" From 03c338d6e3a64235fbc8f7f32da060b5922d704d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 26 Nov 2012 18:26:16 +0200 Subject: [PATCH 17/29] added geos library for linux --- create-dev-env.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create-dev-env.sh b/create-dev-env.sh index 2a7f68d3b0..5edc765e4f 100755 --- a/create-dev-env.sh +++ b/create-dev-env.sh @@ -99,7 +99,7 @@ NUMPY_VER="1.6.2" SCIPY_VER="0.10.1" BREW_FILE="$BASE/mitx/brew-formulas.txt" LOG="/var/tmp/install-$(date +%Y%m%d-%H%M%S).log" -APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor nodejs npm graphviz graphviz-dev mysql-server libmysqlclient-dev geos" +APT_PKGS="pkg-config curl git python-virtualenv build-essential python-dev gfortran liblapack-dev libfreetype6-dev libpng12-dev libxml2-dev libxslt-dev yui-compressor nodejs npm graphviz graphviz-dev mysql-server libmysqlclient-dev libgeos-dev" if [[ $EUID -eq 0 ]]; then error "This script should not be run using sudo or as the root user" From b4fb0cc463fa58ba4a8bc2f0bbef5da1a7c9e0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 26 Nov 2012 18:34:38 +0200 Subject: [PATCH 18/29] fixed line length --- common/lib/capa/capa/responsetypes.py | 41 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 85efd70cee..730cdb12cf 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1718,23 +1718,33 @@ class ImageResponse(LoncapaResponse): which produces an [x,y] coordinate pair. The click is correct if it falls within a region specified. This region is a union of rectangles. - Lon-CAPA requires that each has a inside it. That - doesn't make sense to me (Ike). Instead, let's have it such that - should contain one or more stanzas. Each should specify - a rectangle(s) or region(s), given as an attribute, defining the correct answer. + Lon-CAPA requires that each has a inside it. + That doesn't make sense to me (Ike). Instead, let's have it such that + should contain one or more stanzas. + Each should specify a rectangle(s) or region(s), given as an + attribute, defining the correct answer. - Rectangle(s) are more prioritized over regions due to simplicity and backward compatibility. - In this example regions will be ignored: - + Rectangle(s) are more prioritized over regions due to simplicity and + backward compatibility. In this example regions will be ignored: + - Regions is list of lists [region1, region2, region3, ...] where regionN is ordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. + Regions is list of lists [region1, region2, region3, ...] where regionN + is ordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. """ snippets = [{'snippet': ''' - - - - - + + + + + '''}] response_tag = 'imageresponse' @@ -1748,8 +1758,9 @@ class ImageResponse(LoncapaResponse): correct_map = CorrectMap() expectedset = self.get_answers() - for aid in self.answer_ids: # loop through IDs of fields in our stanza - given = student_answers[aid] # this should be a string of the form '[x,y]' + for aid in self.answer_ids: # loop through IDs of + # fields in our stanza + given = student_answers[aid] # this should be a string of the form '[x,y]' correct_map.set(aid, 'incorrect') if not given: # No answer to parse. Mark as incorrect and move on From 647d1514aa7d4433595943727ecec57ddd4a71f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= Date: Mon, 26 Nov 2012 18:38:46 +0200 Subject: [PATCH 19/29] support both regions and rectangles --- common/lib/capa/capa/responsetypes.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 730cdb12cf..74c801b450 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1724,14 +1724,15 @@ class ImageResponse(LoncapaResponse): Each should specify a rectangle(s) or region(s), given as an attribute, defining the correct answer. - Rectangle(s) are more prioritized over regions due to simplicity and - backward compatibility. In this example regions will be ignored: Regions is list of lists [region1, region2, region3, ...] where regionN is ordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. + + Returns: + True, if click is inside any region or rectangle. Otherwise False. """ snippets = [{'snippet': ''' Date: Tue, 27 Nov 2012 13:09:58 +0200 Subject: [PATCH 20/29] support for single list syntax in regions --- common/lib/capa/capa/responsetypes.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 74c801b450..000d77d655 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1758,9 +1758,10 @@ class ImageResponse(LoncapaResponse): def get_score(self, student_answers): correct_map = CorrectMap() expectedset = self.get_answers() - + # import ipdb; ipdb.set_trace() for aid in self.answer_ids: # loop through IDs of # fields in our stanza + # import ipdb; ipdb.set_trace() given = student_answers[aid] # this should be a string of the form '[x,y]' correct_map.set(aid, 'incorrect') @@ -1795,10 +1796,16 @@ class ImageResponse(LoncapaResponse): break if regions[aid]: parsed_region = json.loads(regions[aid]) - for region in parsed_region: - if Polygon(region).contains(Point(gx, gy)): - correct_map.set(aid, 'correct') - break + if parsed_region: + if type(parsed_region[0][0]) != list: + # we have [[1,2],[3,4],[5,6] - single region + # instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]] + # or [[[1,2],[3,4],[5,6]] - multiple regions syntax + parsed_region = [parsed_region] + for region in parsed_region: + if Polygon(region).contains(Point(gx, gy)): + correct_map.set(aid, 'correct') + break return correct_map def get_answers(self): From 5723672e490c4d19ddedbcb6e6ea40cce2e6f822 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 13:16:47 +0200 Subject: [PATCH 21/29] speed improvements --- common/lib/capa/capa/responsetypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 000d77d655..aeabacef59 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1794,7 +1794,9 @@ class ImageResponse(LoncapaResponse): if (llx <= gx <= urx) and (lly <= gy <= ury): correct_map.set(aid, 'correct') break - if regions[aid]: + # import ipdb; ipdb.set_trace() + if correct_map[aid]['correctness'] != 'correct' and regions[aid]: + import ipdb; ipdb.set_trace() parsed_region = json.loads(regions[aid]) if parsed_region: if type(parsed_region[0][0]) != list: From 72f9358f64d055d46466e1adf836f368aaf4182d Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 13:18:37 +0200 Subject: [PATCH 22/29] tests for imageresponse regions --- .../capa/tests/test_files/imageresponse.xml | 19 ++++++++++++ .../lib/capa/capa/tests/test_responsetypes.py | 30 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/tests/test_files/imageresponse.xml b/common/lib/capa/capa/tests/test_files/imageresponse.xml index 34dba37e3b..c4590b211f 100644 --- a/common/lib/capa/capa/tests/test_files/imageresponse.xml +++ b/common/lib/capa/capa/tests/test_files/imageresponse.xml @@ -18,4 +18,23 @@ Hello

Use conservation of energy.

+ + + + + + + +Click on either of the two positions as discussed previously. + +Click on either of the two positions as discussed previously. + + +Click on either of the two positions as discussed previously. + +

Use conservation of energy.

+
+
+ + diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index bcac555b5e..3564ad3fe8 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -52,24 +52,52 @@ class ImageResponseTest(unittest.TestCase): def test_ir_grade(self): imageresponse_file = os.path.dirname(__file__) + "/test_files/imageresponse.xml" test_lcp = lcp.LoncapaProblem(open(imageresponse_file).read(), '1', system=test_system) - correct_answers = {'1_2_1': '(490,11)-(556,98)', + # testing regions only + correct_answers = { + #regions + '1_2_1': '(490,11)-(556,98)', '1_2_2': '(242,202)-(296,276)', '1_2_3': '(490,11)-(556,98);(242,202)-(296,276)', '1_2_4': '(490,11)-(556,98);(242,202)-(296,276)', '1_2_5': '(490,11)-(556,98);(242,202)-(296,276)', + #testing regions and rectanges + '1_3_1': '(490,11)-(556,98)', + '1_3_2': '(242,202)-(296,276)', + '1_3_3': '(490,11)-(556,98);(242,202)-(296,276)', + '1_3_4': '(490,11)-(556,98);(242,202)-(296,276)', + '1_3_5': '(490,11)-(556,98);(242,202)-(296,276)', } test_answers = {'1_2_1': '[500,20]', '1_2_2': '[250,300]', '1_2_3': '[500,20]', '1_2_4': '[250,250]', '1_2_5': '[10,10]', + + '1_3_1': '[500,20]', + '1_3_2': '[15,15]', + '1_3_3': '[500,20]', + '1_3_4': '[115,115]', + '1_3_5': '[15,15]', + '1_3_6': '[20,20]', + '1_3_7': '[20,20]', } + + # regions self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_3'), 'correct') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_4'), 'correct') self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_5'), 'incorrect') + # regions and rectangles + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_1'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_2'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_3'), 'incorrect') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_4'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_5'), 'correct') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_6'), 'incorrect') + self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_3_7'), 'correct') + class SymbolicResponseTest(unittest.TestCase): def test_sr_grade(self): From adef5d6f7eb1af0f12fdfc543d3822ad91253cad Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 14:11:11 +0200 Subject: [PATCH 23/29] polygon is created from points via convex_hull --- common/lib/capa/capa/responsetypes.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index aeabacef59..ae5d764568 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -23,7 +23,7 @@ import abc import os import subprocess import xml.sax.saxutils as saxutils -from shapely.geometry import Polygon, Point +from shapely.geometry import Point, MultiPoint # specific library imports from calc import evaluator, UndefinedVariable @@ -1796,16 +1796,20 @@ class ImageResponse(LoncapaResponse): break # import ipdb; ipdb.set_trace() if correct_map[aid]['correctness'] != 'correct' and regions[aid]: - import ipdb; ipdb.set_trace() + # import ipdb; ipdb.set_trace() parsed_region = json.loads(regions[aid]) if parsed_region: if type(parsed_region[0][0]) != list: - # we have [[1,2],[3,4],[5,6] - single region - # instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]] - # or [[[1,2],[3,4],[5,6]] - multiple regions syntax + # we have [[1,2],[3,4],[5,6]] - single region + # instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]]] + # or [[[1,2],[3,4],[5,6]]] - multiple regions syntax parsed_region = [parsed_region] + # if aid =='1_3_6': + # import ipdb; ipdb.set_trace() for region in parsed_region: - if Polygon(region).contains(Point(gx, gy)): + polygon = MultiPoint(region).convex_hull + if (polygon.type == 'Polygon' and + polygon.contains(Point(gx, gy))): correct_map.set(aid, 'correct') break return correct_map From 896e922858adfbda5e3117186d154292c1865103 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 14:27:19 +0200 Subject: [PATCH 24/29] cleaning and documeting --- common/lib/capa/capa/responsetypes.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index ae5d764568..20e7c43577 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1726,10 +1726,14 @@ class ImageResponse(LoncapaResponse): + regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/> Regions is list of lists [region1, region2, region3, ...] where regionN - is ordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. + is disordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. + + If there is only one region in the list, simpler notation can be used: + regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly + setting outer list) Returns: True, if click is inside any region or rectangle. Otherwise False. @@ -1743,9 +1747,9 @@ class ImageResponse(LoncapaResponse): rectangle="(10,10)-(20,30);(12,12)-(40,60)" /> + regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/> + regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/>
'''}] response_tag = 'imageresponse' @@ -1758,16 +1762,12 @@ class ImageResponse(LoncapaResponse): def get_score(self, student_answers): correct_map = CorrectMap() expectedset = self.get_answers() - # import ipdb; ipdb.set_trace() for aid in self.answer_ids: # loop through IDs of # fields in our stanza - # import ipdb; ipdb.set_trace() given = student_answers[aid] # this should be a string of the form '[x,y]' - correct_map.set(aid, 'incorrect') if not given: # No answer to parse. Mark as incorrect and move on continue - # parse given answer m = re.match('\[([0-9]+),([0-9]+)]', given.strip().replace(' ', '')) if not m: @@ -1794,9 +1794,7 @@ class ImageResponse(LoncapaResponse): if (llx <= gx <= urx) and (lly <= gy <= ury): correct_map.set(aid, 'correct') break - # import ipdb; ipdb.set_trace() if correct_map[aid]['correctness'] != 'correct' and regions[aid]: - # import ipdb; ipdb.set_trace() parsed_region = json.loads(regions[aid]) if parsed_region: if type(parsed_region[0][0]) != list: @@ -1804,8 +1802,6 @@ class ImageResponse(LoncapaResponse): # instead of [[[1,2],[3,4],[5,6], [[1,2],[3,4],[5,6]]] # or [[[1,2],[3,4],[5,6]]] - multiple regions syntax parsed_region = [parsed_region] - # if aid =='1_3_6': - # import ipdb; ipdb.set_trace() for region in parsed_region: polygon = MultiPoint(region).convex_hull if (polygon.type == 'Polygon' and From 95151ad33a6deabba01c9b1cfab7acd789a2537c Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 14:27:44 +0200 Subject: [PATCH 25/29] tests updates for imageresponse --- .../capa/tests/test_files/imageresponse.xml | 4 ++-- .../lib/capa/capa/tests/test_responsetypes.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/common/lib/capa/capa/tests/test_files/imageresponse.xml b/common/lib/capa/capa/tests/test_files/imageresponse.xml index c4590b211f..41c9f01218 100644 --- a/common/lib/capa/capa/tests/test_files/imageresponse.xml +++ b/common/lib/capa/capa/tests/test_files/imageresponse.xml @@ -22,14 +22,14 @@ Hello

- + Click on either of the two positions as discussed previously. Click on either of the two positions as discussed previously. - + Click on either of the two positions as discussed previously.

Use conservation of energy.

diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 3564ad3fe8..be734bdb88 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -54,18 +54,20 @@ class ImageResponseTest(unittest.TestCase): test_lcp = lcp.LoncapaProblem(open(imageresponse_file).read(), '1', system=test_system) # testing regions only correct_answers = { - #regions - '1_2_1': '(490,11)-(556,98)', + #regions + '1_2_1': '(490,11)-(556,98)', '1_2_2': '(242,202)-(296,276)', '1_2_3': '(490,11)-(556,98);(242,202)-(296,276)', '1_2_4': '(490,11)-(556,98);(242,202)-(296,276)', '1_2_5': '(490,11)-(556,98);(242,202)-(296,276)', #testing regions and rectanges - '1_3_1': '(490,11)-(556,98)', - '1_3_2': '(242,202)-(296,276)', - '1_3_3': '(490,11)-(556,98);(242,202)-(296,276)', - '1_3_4': '(490,11)-(556,98);(242,202)-(296,276)', - '1_3_5': '(490,11)-(556,98);(242,202)-(296,276)', + '1_3_1': 'rectangle="(490,11)-(556,98)" regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_2': 'rectangle="(490,11)-(556,98)" regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_3': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_4': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_5': 'regions="[[[10,10], [20,10], [20, 30]]]"', + '1_3_6': 'regions="[[10,10], [30,30], [15, 15]]"', + '1_3_7': 'regions="[[10,10], [30,30], [10, 30], [30, 10]]"', } test_answers = {'1_2_1': '[500,20]', '1_2_2': '[250,300]', @@ -79,7 +81,7 @@ class ImageResponseTest(unittest.TestCase): '1_3_4': '[115,115]', '1_3_5': '[15,15]', '1_3_6': '[20,20]', - '1_3_7': '[20,20]', + '1_3_7': '[20,15]', } # regions From b0a85fde540941be127f317cd4f131ecbbdc4ddb Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 14:29:04 +0200 Subject: [PATCH 26/29] fix line widths --- .../lib/capa/capa/tests/test_responsetypes.py | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index be734bdb88..9eecef3986 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -54,34 +54,37 @@ class ImageResponseTest(unittest.TestCase): test_lcp = lcp.LoncapaProblem(open(imageresponse_file).read(), '1', system=test_system) # testing regions only correct_answers = { - #regions - '1_2_1': '(490,11)-(556,98)', - '1_2_2': '(242,202)-(296,276)', - '1_2_3': '(490,11)-(556,98);(242,202)-(296,276)', - '1_2_4': '(490,11)-(556,98);(242,202)-(296,276)', - '1_2_5': '(490,11)-(556,98);(242,202)-(296,276)', - #testing regions and rectanges - '1_3_1': 'rectangle="(490,11)-(556,98)" regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', - '1_3_2': 'rectangle="(490,11)-(556,98)" regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', - '1_3_3': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', - '1_3_4': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', - '1_3_5': 'regions="[[[10,10], [20,10], [20, 30]]]"', - '1_3_6': 'regions="[[10,10], [30,30], [15, 15]]"', - '1_3_7': 'regions="[[10,10], [30,30], [10, 30], [30, 10]]"', + #regions + '1_2_1': '(490,11)-(556,98)', + '1_2_2': '(242,202)-(296,276)', + '1_2_3': '(490,11)-(556,98);(242,202)-(296,276)', + '1_2_4': '(490,11)-(556,98);(242,202)-(296,276)', + '1_2_5': '(490,11)-(556,98);(242,202)-(296,276)', + #testing regions and rectanges + '1_3_1': 'rectangle="(490,11)-(556,98)" \ + regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_2': 'rectangle="(490,11)-(556,98)" \ + regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_3': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_4': 'regions="[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"', + '1_3_5': 'regions="[[[10,10], [20,10], [20, 30]]]"', + '1_3_6': 'regions="[[10,10], [30,30], [15, 15]]"', + '1_3_7': 'regions="[[10,10], [30,30], [10, 30], [30, 10]]"', } - test_answers = {'1_2_1': '[500,20]', - '1_2_2': '[250,300]', - '1_2_3': '[500,20]', - '1_2_4': '[250,250]', - '1_2_5': '[10,10]', + test_answers = { + '1_2_1': '[500,20]', + '1_2_2': '[250,300]', + '1_2_3': '[500,20]', + '1_2_4': '[250,250]', + '1_2_5': '[10,10]', - '1_3_1': '[500,20]', - '1_3_2': '[15,15]', - '1_3_3': '[500,20]', - '1_3_4': '[115,115]', - '1_3_5': '[15,15]', - '1_3_6': '[20,20]', - '1_3_7': '[20,15]', + '1_3_1': '[500,20]', + '1_3_2': '[15,15]', + '1_3_3': '[500,20]', + '1_3_4': '[115,115]', + '1_3_5': '[15,15]', + '1_3_6': '[20,20]', + '1_3_7': '[20,15]', } # regions From dcd52801ef341f4b65f4f7e5fba5ecec772f3f41 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 27 Nov 2012 16:10:51 +0200 Subject: [PATCH 27/29] case when user sends less than 3 points, and tests for this case --- common/lib/capa/capa/chem/miller.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index f1482de80f..77c10dd350 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -253,13 +253,15 @@ def grade(user_input, correct_answer): points = [map(float, p) for p in user_answer['points']] + if len(points) < 3: + return False + # round point to closes 0.05 value points = [round0_25(point) for point in points] points = [np.array(point) for point in points] # print miller(points), (correct_answer['miller'].replace(' ', ''), # negative(correct_answer['miller']).replace(' ', '')) - if miller(points) in (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')): return True @@ -269,6 +271,18 @@ def grade(user_input, correct_answer): class Test_Crystallography_Miller(unittest.TestCase): ''' Tests for crystallography grade function.''' + def test_empty_points(self): + user_input = '{"lattice": "bcc", "points": []}' + self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_only_one_point(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"]]}' + self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_only_two_points(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"]]}' + self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + def test_1(self): user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}' self.assertTrue(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) From 2865c7cad49dffb880515e2ec7c1f4dd4747d246 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Wed, 28 Nov 2012 16:57:30 +0200 Subject: [PATCH 28/29] moved tests from miller to tests --- common/lib/capa/capa/chem/miller.py | 149 ---------------------------- common/lib/capa/capa/chem/tests.py | 147 ++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 154 deletions(-) diff --git a/common/lib/capa/capa/chem/miller.py b/common/lib/capa/capa/chem/miller.py index 77c10dd350..4c10e60ecc 100644 --- a/common/lib/capa/capa/chem/miller.py +++ b/common/lib/capa/capa/chem/miller.py @@ -4,7 +4,6 @@ import numpy as np import math import fractions as fr import decimal -import unittest import json @@ -266,151 +265,3 @@ def grade(user_input, correct_answer): return True return False - - -class Test_Crystallography_Miller(unittest.TestCase): - ''' Tests for crystallography grade function.''' - - def test_empty_points(self): - user_input = '{"lattice": "bcc", "points": []}' - self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) - - def test_only_one_point(self): - user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"]]}' - self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) - - def test_only_two_points(self): - user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"]]}' - self.assertFalse(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) - - def test_1(self): - user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}' - self.assertTrue(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) - - def test_2(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,1,1)', 'lattice': 'bcc'})) - - def test_3(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.50", "1.00"], ["1.00", "1.00", "0.50"], ["0.50", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) - - def test_4(self): - user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.664", "0.00"], ["0.00", "1.00", "0.33"]]}' - self.assertTrue(grade(user_input, {'miller': '(-3, 3, -3)', 'lattice': 'bcc'})) - - def test_5(self): - """ return true only in case points coordinates are exact. - But if they transform to closest 0.05 value it is not true""" - user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.33", "0.00"], ["0.00", "1.00", "0.33"]]}' - self.assertFalse(grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'})) - - def test_6(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.25", "0.00"], ["0.25", "0.00", "0.00"], ["0.00", "0.00", "0.25"]]}' - self.assertTrue(grade(user_input, {'miller': '(4,4,4)', 'lattice': 'bcc'})) - - def test_7(self): # goes throug origin - user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "0.00", "0.00"], ["0.50", "1.00", "0.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,0,-1)', 'lattice': 'bcc'})) - - def test_8(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.50"], ["1.00", "0.00", "0.50"], ["0.50", "1.00", "0.50"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,0,2)', 'lattice': 'bcc'})) - - def test_9(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "1.00", "1.00"], ["1.00", "0.00", "0.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,1,0)', 'lattice': 'bcc'})) - - def test_10(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "0.00", "0.00"], ["0.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) - - def test_11(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "0.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,1,2)', 'lattice': 'bcc'})) - - def test_12(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["0.00", "0.00", "0.50"], ["1.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,1,-2)', 'lattice': 'bcc'})) - - def test_13(self): - user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.50", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(2,0,1)', 'lattice': 'bcc'})) - - def test_14(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "0.00", "1.00"], ["0.50", "1.00", "0.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(2,-1,0)', 'lattice': 'bcc'})) - - def test_15(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) - - def test_16(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) - - def test_17(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "1.00"], ["1.00", "1.00", "0.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(-1,1,1)', 'lattice': 'bcc'})) - - def test_18(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) - - def test_19(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(-1,1,0)', 'lattice': 'bcc'})) - - def test_20(self): - user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,0,1)', 'lattice': 'bcc'})) - - def test_21(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(-1,0,1)', 'lattice': 'bcc'})) - - def test_22(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,1,1)', 'lattice': 'bcc'})) - - def test_23(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'})) - - def test_24(self): - user_input = '{"lattice": "bcc", "points": [["0.66", "0.00", "0.00"], ["0.00", "0.66", "0.00"], ["0.00", "0.00", "0.66"]]}' - self.assertTrue(grade(user_input, {'miller': '(3,3,3)', 'lattice': 'bcc'})) - - def test_25(self): - user_input = u'{"lattice":"","points":[["0.00","0.00","0.01"],["1.00","1.00","0.01"],["0.00","1.00","1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(1,-1,1)', 'lattice': ''})) - - def test_26(self): - user_input = u'{"lattice":"","points":[["0.00","0.01","0.00"],["1.00","0.00","0.00"],["0.00","0.00","1.00"]]}' - self.assertTrue(grade(user_input, {'miller': '(0,-1,0)', 'lattice': ''})) - - def test_27(self): - """ rounding to 0.35""" - user_input = u'{"lattice":"","points":[["0.33","0.00","0.00"],["0.00","0.33","0.00"],["0.00","0.00","0.33"]]}' - self.assertTrue(grade(user_input, {'miller': '(3,3,3)', 'lattice': ''})) - - def test_28(self): - """ rounding to 0.30""" - user_input = u'{"lattice":"","points":[["0.30","0.00","0.00"],["0.00","0.30","0.00"],["0.00","0.00","0.30"]]}' - self.assertTrue(grade(user_input, {'miller': '(10,10,10)', 'lattice': ''})) - - def test_wrong_lattice(self): - user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' - self.assertFalse(grade(user_input, {'miller': '(3,3,3)', 'lattice': 'fcc'})) - - -def suite(): - - testcases = [Test_Crystallography_Miller] - suites = [] - for testcase in testcases: - suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase)) - return unittest.TestSuite(suites) - -if __name__ == "__main__": - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/common/lib/capa/capa/chem/tests.py b/common/lib/capa/capa/chem/tests.py index 34d903ec1d..571526f915 100644 --- a/common/lib/capa/capa/chem/tests.py +++ b/common/lib/capa/capa/chem/tests.py @@ -1,13 +1,15 @@ import codecs from fractions import Fraction -from pyparsing import ParseException import unittest from chemcalc import (compare_chemical_expression, divide_chemical_expression, render_to_html, chemical_equations_equal) +import miller + local_debug = None + def log(s, output_type=None): if local_debug: print s @@ -37,7 +39,6 @@ class Test_Compare_Equations(unittest.TestCase): self.assertFalse(chemical_equations_equal('2H2 + O2 -> H2O2', '2O2 + 2H2 -> 2H2O2')) - def test_different_arrows(self): self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2', '2O2 + 2H2 -> 2H2O2')) @@ -56,7 +57,6 @@ class Test_Compare_Equations(unittest.TestCase): self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2', 'O2 + H2 -> H2O2', exact=True)) - def test_syntax_errors(self): self.assertFalse(chemical_equations_equal('H2 + O2 a-> H2O2', '2O2 + 2H2 -> 2H2O2')) @@ -311,7 +311,6 @@ class Test_Render_Equations(unittest.TestCase): log(out + ' ------- ' + correct, 'html') self.assertEqual(out, correct) - def test_render_eq3(self): s = "H^+ + OH^- <= H2O" # unsupported arrow out = render_to_html(s) @@ -320,10 +319,148 @@ class Test_Render_Equations(unittest.TestCase): self.assertEqual(out, correct) +class Test_Crystallography_Miller(unittest.TestCase): + ''' Tests for crystallography grade function.''' + + def test_empty_points(self): + user_input = '{"lattice": "bcc", "points": []}' + self.assertFalse(miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_only_one_point(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"]]}' + self.assertFalse(miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_only_two_points(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"]]}' + self.assertFalse(miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_1(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_2(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,1,1)', 'lattice': 'bcc'})) + + def test_3(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.50", "1.00"], ["1.00", "1.00", "0.50"], ["0.50", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'})) + + def test_4(self): + user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.664", "0.00"], ["0.00", "1.00", "0.33"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(-3, 3, -3)', 'lattice': 'bcc'})) + + def test_5(self): + """ return true only in case points coordinates are exact. + But if they transform to closest 0.05 value it is not true""" + user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.33", "0.00"], ["0.00", "1.00", "0.33"]]}' + self.assertFalse(miller.grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'})) + + def test_6(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.25", "0.00"], ["0.25", "0.00", "0.00"], ["0.00", "0.00", "0.25"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(4,4,4)', 'lattice': 'bcc'})) + + def test_7(self): # goes throug origin + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "0.00", "0.00"], ["0.50", "1.00", "0.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,0,-1)', 'lattice': 'bcc'})) + + def test_8(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.50"], ["1.00", "0.00", "0.50"], ["0.50", "1.00", "0.50"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,0,2)', 'lattice': 'bcc'})) + + def test_9(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "1.00", "1.00"], ["1.00", "0.00", "0.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,1,0)', 'lattice': 'bcc'})) + + def test_10(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "0.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) + + def test_11(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "0.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,1,2)', 'lattice': 'bcc'})) + + def test_12(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["0.00", "0.00", "0.50"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,1,-2)', 'lattice': 'bcc'})) + + def test_13(self): + user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.50", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(2,0,1)', 'lattice': 'bcc'})) + + def test_14(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "0.00", "1.00"], ["0.50", "1.00", "0.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(2,-1,0)', 'lattice': 'bcc'})) + + def test_15(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) + + def test_16(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'})) + + def test_17(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "1.00"], ["1.00", "1.00", "0.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(-1,1,1)', 'lattice': 'bcc'})) + + def test_18(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'})) + + def test_19(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(-1,1,0)', 'lattice': 'bcc'})) + + def test_20(self): + user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,0,1)', 'lattice': 'bcc'})) + + def test_21(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(-1,0,1)', 'lattice': 'bcc'})) + + def test_22(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,1,1)', 'lattice': 'bcc'})) + + def test_23(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'})) + + def test_24(self): + user_input = '{"lattice": "bcc", "points": [["0.66", "0.00", "0.00"], ["0.00", "0.66", "0.00"], ["0.00", "0.00", "0.66"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': 'bcc'})) + + def test_25(self): + user_input = u'{"lattice":"","points":[["0.00","0.00","0.01"],["1.00","1.00","0.01"],["0.00","1.00","1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': ''})) + + def test_26(self): + user_input = u'{"lattice":"","points":[["0.00","0.01","0.00"],["1.00","0.00","0.00"],["0.00","0.00","1.00"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(0,-1,0)', 'lattice': ''})) + + def test_27(self): + """ rounding to 0.35""" + user_input = u'{"lattice":"","points":[["0.33","0.00","0.00"],["0.00","0.33","0.00"],["0.00","0.00","0.33"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': ''})) + + def test_28(self): + """ rounding to 0.30""" + user_input = u'{"lattice":"","points":[["0.30","0.00","0.00"],["0.00","0.30","0.00"],["0.00","0.00","0.30"]]}' + self.assertTrue(miller.grade(user_input, {'miller': '(10,10,10)', 'lattice': ''})) + + def test_wrong_lattice(self): + user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}' + self.assertFalse(miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': 'fcc'})) + def suite(): - testcases = [Test_Compare_Expressions, Test_Divide_Expressions, Test_Render_Equations] + testcases = [Test_Compare_Expressions, + Test_Divide_Expressions, + Test_Render_Equations, + Test_Crystallography_Miller] suites = [] for testcase in testcases: suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase)) From 74b30d8c204dfe4d40e80365a97b9d33d93489a9 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Wed, 28 Nov 2012 17:02:07 +0200 Subject: [PATCH 29/29] removed no-longer relevant template params --- common/lib/capa/capa/tests/test_inputtypes.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 826d304717..dafd31bdc7 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -407,13 +407,11 @@ class CrystallographyTest(unittest.TestCase): def test_rendering(self): height = '12' width = '33' - size = '10' xml_str = """""".format(h=height, w=width, s=size) + />""".format(h=height, w=width) element = etree.fromstring(xml_str) @@ -428,9 +426,7 @@ class CrystallographyTest(unittest.TestCase): expected = {'id': 'prob_1_2', 'value': value, 'status': 'unsubmitted', - 'size': size, 'msg': '', - 'hidden': '', 'width': width, 'height': height, }