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
72d149ca
Commit
72d149ca
authored
May 29, 2013
by
Peter Baratta
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add docstrings and comments
parent
ed45c505
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
71 additions
and
11 deletions
+71
-11
common/lib/calc/calc.py
+70
-11
common/lib/capa/capa/responsetypes.py
+1
-0
No files found.
common/lib/calc/calc.py
View file @
72d149ca
"""
Parser and evaluator for FormulaResponse and NumericalResponse
Uses pyparsing to parse. Main function as of now is evaluator().
"""
import
copy
import
copy
import
logging
import
logging
import
math
import
math
...
@@ -56,6 +62,10 @@ log = logging.getLogger("mitx.courseware.capa")
...
@@ -56,6 +62,10 @@ log = logging.getLogger("mitx.courseware.capa")
class
UndefinedVariable
(
Exception
):
class
UndefinedVariable
(
Exception
):
"""
Used to indicate the student input of a variable, which was unused by the
instructor.
"""
pass
pass
# unused for now
# unused for now
# def raiseself(self):
# def raiseself(self):
...
@@ -67,7 +77,8 @@ general_whitespace = re.compile('[^\\w]+')
...
@@ -67,7 +77,8 @@ general_whitespace = re.compile('[^\\w]+')
def
check_variables
(
string
,
variables
):
def
check_variables
(
string
,
variables
):
'''Confirm the only variables in string are defined.
"""
Confirm the only variables in string are defined.
Pyparsing uses a left-to-right parser, which makes the more
Pyparsing uses a left-to-right parser, which makes the more
elegant approach pretty hopeless.
elegant approach pretty hopeless.
...
@@ -76,7 +87,7 @@ def check_variables(string, variables):
...
@@ -76,7 +87,7 @@ def check_variables(string, variables):
undefined_variable = achar + Word(alphanums)
undefined_variable = achar + Word(alphanums)
undefined_variable.setParseAction(lambda x:UndefinedVariable("".join(x)).raiseself())
undefined_variable.setParseAction(lambda x:UndefinedVariable("".join(x)).raiseself())
varnames = varnames | undefined_variable
varnames = varnames | undefined_variable
'''
"""
possible_variables
=
re
.
split
(
general_whitespace
,
string
)
# List of all alnums in string
possible_variables
=
re
.
split
(
general_whitespace
,
string
)
# List of all alnums in string
bad_variables
=
list
()
bad_variables
=
list
()
for
v
in
possible_variables
:
for
v
in
possible_variables
:
...
@@ -90,26 +101,59 @@ def check_variables(string, variables):
...
@@ -90,26 +101,59 @@ def check_variables(string, variables):
raise
UndefinedVariable
(
' '
.
join
(
bad_variables
))
raise
UndefinedVariable
(
' '
.
join
(
bad_variables
))
def
lower_dict
(
d
):
def
lower_dict
(
d
):
"""
takes each key in the dict and makes it lowercase, still mapping to the
same value.
keep in mind that it is possible (but not useful?) to define different
variables that have the same lowercase representation. It would be hard to
tell which is used in the final dict and which isn't.
"""
return
dict
([(
k
.
lower
(),
d
[
k
])
for
k
in
d
])
return
dict
([(
k
.
lower
(),
d
[
k
])
for
k
in
d
])
# The following few functions define parse actions, which are run on lists of
# results from each parse component. They convert the strings and (previously
# calculated) numbers into the number that component represents.
def
super_float
(
text
):
def
super_float
(
text
):
''' Like float, but with si extensions. 1k goes to 1000'''
"""
Like float, but with si extensions. 1k goes to 1000
"""
if
text
[
-
1
]
in
suffixes
:
if
text
[
-
1
]
in
suffixes
:
return
float
(
text
[:
-
1
])
*
suffixes
[
text
[
-
1
]]
return
float
(
text
[:
-
1
])
*
suffixes
[
text
[
-
1
]]
else
:
else
:
return
float
(
text
)
return
float
(
text
)
def
number_parse_action
(
x
):
# [ '7' ] -> [ 7 ]
def
number_parse_action
(
x
):
"""
Create a float out of its string parts
e.g. [ '7', '.', '13' ] -> [ 7.13 ]
Calls super_float above
"""
return
[
super_float
(
""
.
join
(
x
))]
return
[
super_float
(
""
.
join
(
x
))]
def
exp_parse_action
(
x
):
# [ 2 ^ 3 ^ 2 ] -> 512
def
exp_parse_action
(
x
):
"""
Take a list of numbers and exponentiate them, right to left
e.g. [ 3, 2, 3 ] (which is 3^2^3 = 3^(2^3)) -> 6561
"""
x
=
[
e
for
e
in
x
if
isinstance
(
e
,
numbers
.
Number
)]
# Ignore ^
x
=
[
e
for
e
in
x
if
isinstance
(
e
,
numbers
.
Number
)]
# Ignore ^
x
.
reverse
()
x
.
reverse
()
x
=
reduce
(
lambda
a
,
b
:
b
**
a
,
x
)
x
=
reduce
(
lambda
a
,
b
:
b
**
a
,
x
)
return
x
return
x
def
parallel
(
x
):
# Parallel resistors [ 1 2 ] => 2/3
def
parallel
(
x
):
# convert from pyparsing.ParseResults, which doesn't support '0 in x'
"""
Compute numbers according to the parallel resistors operator
BTW it is commutative. Its formula is given by
out = 1 / (1/in1 + 1/in2 + ...)
e.g. [ 1, 2 ] => 2/3
Return NaN if there is a zero among the inputs
"""
x
=
list
(
x
)
x
=
list
(
x
)
if
len
(
x
)
==
1
:
if
len
(
x
)
==
1
:
return
x
[
0
]
return
x
[
0
]
...
@@ -119,6 +163,13 @@ def parallel(x): # Parallel resistors [ 1 2 ] => 2/3
...
@@ -119,6 +163,13 @@ def parallel(x): # Parallel resistors [ 1 2 ] => 2/3
return
1.
/
sum
(
x
)
return
1.
/
sum
(
x
)
def
sum_parse_action
(
x
):
# [ 1 + 2 - 3 ] -> 0
def
sum_parse_action
(
x
):
# [ 1 + 2 - 3 ] -> 0
"""
Add the inputs
[ 1, '+', 2, '-', 3 ] -> 0
Allow a leading + or -
"""
total
=
0.0
total
=
0.0
op
=
ops
[
'+'
]
op
=
ops
[
'+'
]
for
e
in
x
:
for
e
in
x
:
...
@@ -129,6 +180,11 @@ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
...
@@ -129,6 +180,11 @@ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
return
total
return
total
def
prod_parse_action
(
x
):
# [ 1 * 2 / 3 ] => 0.66
def
prod_parse_action
(
x
):
# [ 1 * 2 / 3 ] => 0.66
"""
Multiply the inputs
[ 1, '*', 2, '/', 3 ] => 0.66
"""
prod
=
1.0
prod
=
1.0
op
=
ops
[
'*'
]
op
=
ops
[
'*'
]
for
e
in
x
:
for
e
in
x
:
...
@@ -139,14 +195,13 @@ def prod_parse_action(x): # [ 1 * 2 / 3 ] => 0.66
...
@@ -139,14 +195,13 @@ def prod_parse_action(x): # [ 1 * 2 / 3 ] => 0.66
return
prod
return
prod
def
evaluator
(
variables
,
functions
,
string
,
cs
=
False
):
def
evaluator
(
variables
,
functions
,
string
,
cs
=
False
):
'''
"""
Evaluate an expression. Variables are passed as a dictionary
Evaluate an expression. Variables are passed as a dictionary
from string to value. Unary functions are passed as a dictionary
from string to value. Unary functions are passed as a dictionary
from string to function. Variables must be floats.
from string to function. Variables must be floats.
cs: Case sensitive
cs: Case sensitive
TODO: Fix it so we can pass integers and complex numbers in variables dict
"""
'''
# log.debug("variables: {0}".format(variables))
# log.debug("variables: {0}".format(variables))
# log.debug("functions: {0}".format(functions))
# log.debug("functions: {0}".format(functions))
# log.debug("string: {0}".format(string))
# log.debug("string: {0}".format(string))
...
@@ -187,7 +242,8 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -187,7 +242,8 @@ def evaluator(variables, functions, string, cs=False):
# 0.33 or 7 or .34 or 16.
# 0.33 or 7 or .34 or 16.
inner_number
=
(
number_part
+
Optional
(
"."
+
Optional
(
number_part
)))
|
(
"."
+
number_part
)
inner_number
=
(
number_part
+
Optional
(
"."
+
Optional
(
number_part
)))
|
(
"."
+
number_part
)
inner_number
=
Combine
(
inner_number
)
# by default pyparsing allows spaces between tokens--this prevents that
# by default pyparsing allows spaces between tokens--Combine prevents that
inner_number
=
Combine
(
inner_number
)
# 0.33k or -17
# 0.33k or -17
number
=
(
inner_number
number
=
(
inner_number
...
@@ -209,6 +265,8 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -209,6 +265,8 @@ def evaluator(variables, functions, string, cs=False):
varnames
=
MatchFirst
(
literal_all_vars
)
varnames
=
MatchFirst
(
literal_all_vars
)
varnames
.
setParseAction
(
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
])
varnames
.
setParseAction
(
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
])
else
:
else
:
# all_variables includes DEFAULT_VARIABLES, which isn't empty
# this is unreachable. Get rid of it?
varnames
=
NoMatch
()
varnames
=
NoMatch
()
# Same thing for functions.
# Same thing for functions.
...
@@ -217,6 +275,7 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -217,6 +275,7 @@ def evaluator(variables, functions, string, cs=False):
function
=
funcnames
+
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
function
=
funcnames
+
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
function
.
setParseAction
(
func_parse_action
)
function
.
setParseAction
(
func_parse_action
)
else
:
else
:
# see note above (this is unreachable)
function
=
NoMatch
()
function
=
NoMatch
()
atom
=
number
|
function
|
varnames
|
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
atom
=
number
|
function
|
varnames
|
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
...
...
common/lib/capa/capa/responsetypes.py
View file @
72d149ca
...
@@ -1717,6 +1717,7 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1717,6 +1717,7 @@ class FormulaResponse(LoncapaResponse):
student_variables
=
dict
()
student_variables
=
dict
()
# ranges give numerical ranges for testing
# ranges give numerical ranges for testing
for
var
in
ranges
:
for
var
in
ranges
:
# TODO: allow specified ranges (i.e. integers and complex numbers) for random variables
value
=
random
.
uniform
(
*
ranges
[
var
])
value
=
random
.
uniform
(
*
ranges
[
var
])
instructor_variables
[
str
(
var
)]
=
value
instructor_variables
[
str
(
var
)]
=
value
student_variables
[
str
(
var
)]
=
value
student_variables
[
str
(
var
)]
=
value
...
...
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