Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
c2d5c04d
Commit
c2d5c04d
authored
Dec 04, 2012
by
Brian Wilson
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into fix/brian/nonascii-html-xmodule
parents
cde20758
ba9280f3
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
611 additions
and
51 deletions
+611
-51
brew-formulas.txt
+1
-0
common/lib/capa/capa/capa_problem.py
+3
-1
common/lib/capa/capa/chem/miller.py
+267
-0
common/lib/capa/capa/chem/tests.py
+142
-5
common/lib/capa/capa/inputtypes.py
+2
-5
common/lib/capa/capa/responsetypes.py
+52
-15
common/lib/capa/capa/templates/crystallography.html
+12
-17
common/lib/capa/capa/tests/test_files/imageresponse.xml
+19
-0
common/lib/capa/capa/tests/test_inputtypes.py
+1
-5
common/lib/capa/capa/tests/test_responsetypes.py
+35
-2
create-dev-env.sh
+1
-1
docs/source/capa.rst
+6
-0
docs/source/chem.rst
+69
-0
requirements.txt
+1
-0
No files found.
brew-formulas.txt
View file @
c2d5c04d
...
...
@@ -8,3 +8,4 @@ yuicompressor
node
graphviz
mysql
geos
common/lib/capa/capa/capa_problem.py
View file @
c2d5c04d
...
...
@@ -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"
]
...
...
common/lib/capa/capa/chem/miller.py
0 → 100644
View file @
c2d5c04d
""" Calculation of Miller indices """
import
numpy
as
np
import
math
import
fractions
as
fr
import
decimal
import
json
def
lcm
(
a
,
b
):
"""
Returns least common multiple of a, b
Args:
a, b: floats
Returns:
float
"""
return
a
*
b
/
fr
.
gcd
(
a
,
b
)
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
"""
if
np
.
isnan
(
distance
):
return
fr
.
Fraction
(
0
,
1
)
else
:
fract
=
fr
.
Fraction
(
distance
)
.
limit_denominator
(
10
)
return
fr
.
Fraction
(
fract
.
denominator
,
fract
.
numerator
)
def
sub_miller
(
segments
):
'''
Calculates Miller indices from segments.
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
])
miller
=
([
fract
.
numerator
*
math
.
fabs
(
common_denominator
)
/
fract
.
denominator
for
fract
in
fracts
])
return
'('
+
','
.
join
(
map
(
str
,
map
(
decimal
.
Decimal
,
miller
)))
+
')'
def
miller
(
points
):
"""
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)
"""
N
=
np
.
cross
(
points
[
1
]
-
points
[
0
],
points
[
2
]
-
points
[
0
])
O
=
np
.
array
([
0
,
0
,
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 not in plane
new_origin
=
vertex
break
# 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
return
sub_miller
(
segments
)
def
grade
(
user_input
,
correct_answer
):
'''
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
(
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
(
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
+=
m
[
i
]
i
+=
1
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'
]]
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
return
False
common/lib/capa/capa/chem/tests.py
View file @
c2d5c04d
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
))
...
...
common/lib/capa/capa/inputtypes.py
View file @
c2d5c04d
...
...
@@ -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
...
...
common/lib/capa/capa/responsetypes.py
View file @
c2d5c04d
...
...
@@ -23,6 +23,7 @@ import abc
import
os
import
subprocess
import
xml.sax.saxutils
as
saxutils
from
shapely.geometry
import
Point
,
MultiPoint
# specific library imports
from
calc
import
evaluator
,
UndefinedVariable
...
...
@@ -1717,15 +1718,38 @@ 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 <imageresponse> has a <foilgroup> inside it. That
doesn't make sense to me (Ike). Instead, let's have it such that <imageresponse>
should contain one or more <imageinput> stanzas. Each <imageinput> should specify
a rectangle, given as an attribute, defining the correct answer.
Lon-CAPA requires that each <imageresponse> has a <foilgroup> inside it.
That doesn't make sense to me (Ike). Instead, let's have it such that
<imageresponse> should contain one or more <imageinput> stanzas.
Each <imageinput> should specify a rectangle(s) or region(s), given as an
attribute, defining the correct answer.
<imageinput src="/static/images/Lecture2/S2_p04.png" width="811" height="610"
rectangle="(10,10)-(20,30);(12,12)-(40,60)"
regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/>
Regions is list of lists [region1, region2, region3, ...] where regionN
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.
"""
snippets
=
[{
'snippet'
:
'''<imageresponse>
<imageinput src="image1.jpg" width="200" height="100" rectangle="(10,10)-(20,30)" />
<imageinput src="image2.jpg" width="210" height="130" rectangle="(12,12)-(40,60)" />
<imageinput src="image2.jpg" width="210" height="130" rectangle="(10,10)-(20,30);(12,12)-(40,60)" />
<imageinput src="image1.jpg" width="200" height="100"
rectangle="(10,10)-(20,30)" />
<imageinput src="image2.jpg" width="210" height="130"
rectangle="(12,12)-(40,60)" />
<imageinput src="image3.jpg" width="210" height="130"
rectangle="(10,10)-(20,30);(12,12)-(40,60)" />
<imageinput src="image4.jpg" width="811" height="610"
rectangle="(10,10)-(20,30);(12,12)-(40,60)"
regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/>
<imageinput src="image5.jpg" width="200" height="200"
regions="[[[10,10], [20,30], [40, 10]], [[100,100], [120,130], [110,150]]]"/>
</imageresponse>'''
}]
response_tag
=
'imageresponse'
...
...
@@ -1738,14 +1762,12 @@ class ImageResponse(LoncapaResponse):
def
get_score
(
self
,
student_answers
):
correct_map
=
CorrectMap
()
expectedset
=
self
.
get_answers
()
for
aid
in
self
.
answer_ids
:
# loop through IDs of <imageinput>
fields in our stanza
for
aid
in
self
.
answer_ids
:
# loop through IDs of <imageinput>
#
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
continue
# parse given answer
m
=
re
.
match
(
'
\
[([0-9]+),([0-9]+)]'
,
given
.
strip
()
.
replace
(
' '
,
''
))
if
not
m
:
...
...
@@ -1753,8 +1775,10 @@ class ImageResponse(LoncapaResponse):
'error grading
%
s (input=
%
s)'
%
(
aid
,
given
))
(
gx
,
gy
)
=
[
int
(
x
)
for
x
in
m
.
groups
()]
rectangles
,
regions
=
expectedset
if
rectangles
[
aid
]:
# rectangles part - for backward compatibility
# Check whether given point lies in any of the solution rectangles
solution_rectangles
=
expectedset
[
aid
]
.
split
(
';'
)
solution_rectangles
=
rectangles
[
aid
]
.
split
(
';'
)
for
solution_rectangle
in
solution_rectangles
:
# parse expected answer
# TODO: Compile regexp on file load
...
...
@@ -1770,12 +1794,25 @@ class ImageResponse(LoncapaResponse):
if
(
llx
<=
gx
<=
urx
)
and
(
lly
<=
gy
<=
ury
):
correct_map
.
set
(
aid
,
'correct'
)
break
if
correct_map
[
aid
][
'correctness'
]
!=
'correct'
and
regions
[
aid
]:
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
parsed_region
=
[
parsed_region
]
for
region
in
parsed_region
:
polygon
=
MultiPoint
(
region
)
.
convex_hull
if
(
polygon
.
type
==
'Polygon'
and
polygon
.
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
...
...
common/lib/capa/capa/templates/crystallography.html
View file @
c2d5c04d
<section
id=
"inputtype_${id}"
class=
"capa_inputtype"
>
<div
id=
"holder"
style=
"width:${width};height:${height}"
></div>
<div
class=
"crystalography_problem"
style=
"width:${width};height:${height}"
></div>
<div
class=
"input_lattice"
>
Lattice:
<select></select>
</div>
<div
class=
"script_placeholder"
data-src=
"/static/js/raphael.js"
></div>
<div
class=
"script_placeholder"
data-src=
"/static/js/sylvester.js"
></div>
<div
class=
"script_placeholder"
data-src=
"/static/js/underscore-min.js"
></div>
<div
class=
"script_placeholder"
data-src=
"/static/js/crystallography.js"
></div>
% if status == 'unsubmitted':
<div
class=
"unanswered"
id=
"status_${id}"
>
% elif status == 'correct':
...
...
@@ -15,18 +18,9 @@
% elif status == 'incomplete':
<div
class=
"incorrect"
id=
"status_${id}"
>
% endif
% if hidden:
<div
style=
"display:none;"
name=
"${hidden}"
inputid=
"input_${id}"
/>
% endif
<input
type=
"text"
name=
"input_${id}"
id=
"input_${id}"
value=
"${value|h}"
%
if
size:
size=
"${size}"
%
endif
%
if
hidden:
style=
"display:none;"
%
endif
/>
<input
type=
"text"
name=
"input_${id}"
id=
"input_${id}"
value=
"${value|h}"
style=
"display:none;"
/>
<p
class=
"status"
>
% if status == 'unsubmitted':
...
...
@@ -45,7 +39,8 @@
% if msg:
<span
class=
"message"
>
${msg|n}
</span>
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
</div>
% endif
</section>
common/lib/capa/capa/tests/test_files/imageresponse.xml
View file @
c2d5c04d
...
...
@@ -18,4 +18,23 @@ Hello</p></text>
<text><p>
Use conservation of energy.
</p></text>
</hintgroup>
</imageresponse>
<imageresponse
max=
"1"
loncapaid=
"12"
>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
rectangle=
"(490,11)-(556,98)"
regions=
"[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"
/>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
rectangle=
"(490,11)-(556,98)"
regions=
'[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]'
/>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
regions=
"[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"
/>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
regions=
"[[[10,10], [20,10], [20, 30]], [[100,100], [120,100], [120,150]]]"
/>
<text>
Click on either of the two positions as discussed previously.
</text>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
regions=
"[[[10,10], [20,10], [20, 30]]]"
/>
<text>
Click on either of the two positions as discussed previously.
</text>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
regions=
"[[10,10], [30,30], [15, 15]]"
/>
<imageinput
src=
"/static/Physics801/Figures/Skier-conservation of energy.jpg"
width=
"560"
height=
"388"
regions=
"[[10,10], [30,30], [10, 30], [30, 10]]"
/>
<text>
Click on either of the two positions as discussed previously.
</text>
<hintgroup
showoncorrect=
"no"
>
<text><p>
Use conservation of energy.
</p></text>
</hintgroup>
</imageresponse>
</problem>
common/lib/capa/capa/tests/test_inputtypes.py
View file @
c2d5c04d
...
...
@@ -407,13 +407,11 @@ class CrystallographyTest(unittest.TestCase):
def
test_rendering
(
self
):
height
=
'12'
width
=
'33'
size
=
'10'
xml_str
=
"""<crystallography id="prob_1_2"
height="{h}"
width="{w}"
size="{s}"
/>"""
.
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
,
}
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
c2d5c04d
...
...
@@ -52,24 +52,57 @@ 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'
:
'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]'
,
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]'
,
}
# 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
):
...
...
create-dev-env.sh
View file @
c2d5c04d
...
...
@@ -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
libgeos-dev
"
if
[[
$EUID
-eq
0
]]
;
then
error
"This script should not be run using sudo or as the root user"
...
...
docs/source/capa.rst
View file @
c2d5c04d
*******************************************
Capa module
*******************************************
Contents:
.. module:: capa
.. toctree::
:maxdepth: 2
chem.rst
Calc
====
...
...
docs/source/chem.rst
0 → 100644
View file @
c2d5c04d
*******************************************
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:
requirements.txt
View file @
c2d5c04d
...
...
@@ -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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment