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
a85a7f71
Commit
a85a7f71
authored
May 29, 2013
by
Peter Baratta
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Rename variables; get rid of OPS
parent
72d149ca
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
87 additions
and
83 deletions
+87
-83
common/lib/calc/calc.py
+87
-83
No files found.
common/lib/calc/calc.py
View file @
a85a7f71
...
@@ -11,16 +11,15 @@ import operator
...
@@ -11,16 +11,15 @@ import operator
import
re
import
re
import
numpy
import
numpy
import
numbers
import
scipy.constants
import
scipy.constants
from
pyparsing
import
Word
,
nums
,
Literal
from
pyparsing
import
(
Word
,
nums
,
Literal
,
from
pyparsing
import
ZeroOrMore
,
MatchFirst
ZeroOrMore
,
MatchFirst
,
from
pyparsing
import
Optional
,
Forward
Optional
,
Forward
,
from
pyparsing
import
CaselessLiteral
CaselessLiteral
,
from
pyparsing
import
NoMatch
,
stringEnd
,
Suppress
,
Combine
NoMatch
,
stringEnd
,
Suppress
,
Combine
)
default_functions
=
{
'sin'
:
numpy
.
sin
,
DEFAULT_FUNCTIONS
=
{
'sin'
:
numpy
.
sin
,
'cos'
:
numpy
.
cos
,
'cos'
:
numpy
.
cos
,
'tan'
:
numpy
.
tan
,
'tan'
:
numpy
.
tan
,
'sqrt'
:
numpy
.
sqrt
,
'sqrt'
:
numpy
.
sqrt
,
...
@@ -34,7 +33,7 @@ default_functions = {'sin': numpy.sin,
...
@@ -34,7 +33,7 @@ default_functions = {'sin': numpy.sin,
'fact'
:
math
.
factorial
,
'fact'
:
math
.
factorial
,
'factorial'
:
math
.
factorial
'factorial'
:
math
.
factorial
}
}
default_variables
=
{
'j'
:
numpy
.
complex
(
0
,
1
),
DEFAULT_VARIABLES
=
{
'j'
:
numpy
.
complex
(
0
,
1
),
'e'
:
numpy
.
e
,
'e'
:
numpy
.
e
,
'pi'
:
numpy
.
pi
,
'pi'
:
numpy
.
pi
,
'k'
:
scipy
.
constants
.
k
,
'k'
:
scipy
.
constants
.
k
,
...
@@ -43,22 +42,15 @@ default_variables = {'j': numpy.complex(0, 1),
...
@@ -43,22 +42,15 @@ default_variables = {'j': numpy.complex(0, 1),
'q'
:
scipy
.
constants
.
e
'q'
:
scipy
.
constants
.
e
}
}
ops
=
{
"^"
:
operator
.
pow
,
"*"
:
operator
.
mul
,
"/"
:
operator
.
truediv
,
"+"
:
operator
.
add
,
"-"
:
operator
.
sub
,
}
# We eliminated extreme ones, since they're rarely used, and potentially
# We eliminated extreme ones, since they're rarely used, and potentially
# confusing. They may also conflict with variables if we ever allow e.g.
# confusing. They may also conflict with variables if we ever allow e.g.
# 5R instead of 5*R
# 5R instead of 5*R
suffixes
=
{
'
%
'
:
0.01
,
'k'
:
1e3
,
'M'
:
1e6
,
'G'
:
1e9
,
SUFFIXES
=
{
'
%
'
:
0.01
,
'k'
:
1e3
,
'M'
:
1e6
,
'G'
:
1e9
,
'T'
:
1e12
,
# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
'T'
:
1e12
,
# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
'c'
:
1e-2
,
'm'
:
1e-3
,
'u'
:
1e-6
,
'c'
:
1e-2
,
'm'
:
1e-3
,
'u'
:
1e-6
,
'n'
:
1e-9
,
'p'
:
1e-12
}
# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
'n'
:
1e-9
,
'p'
:
1e-12
}
# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
log
=
logging
.
getLogger
(
"mitx.courseware.capa"
)
LOG
=
logging
.
getLogger
(
"mitx.courseware.capa"
)
class
UndefinedVariable
(
Exception
):
class
UndefinedVariable
(
Exception
):
...
@@ -73,13 +65,12 @@ class UndefinedVariable(Exception):
...
@@ -73,13 +65,12 @@ class UndefinedVariable(Exception):
# raise self
# raise self
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.
Otherwise, raise an UndefinedVariable containing all bad variables.
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.
...
@@ -88,19 +79,22 @@ def check_variables(string, variables):
...
@@ -88,19 +79,22 @@ def check_variables(string, variables):
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
general_whitespace
=
re
.
compile
(
'[^
\\
w]+'
)
# List of all alnums in string
possible_variables
=
re
.
split
(
general_whitespace
,
string
)
bad_variables
=
list
()
bad_variables
=
list
()
for
v
in
possible_variables
:
for
v
ar
in
possible_variables
:
if
len
(
v
)
==
0
:
if
len
(
v
ar
)
==
0
:
continue
continue
if
v
[
0
]
<=
'9'
and
'0'
<=
v
:
# Skip things that begin with numbers
if
v
ar
[
0
]
<=
'9'
and
'0'
<=
var
:
# Skip things that begin with numbers
continue
continue
if
v
not
in
variables
:
if
v
ar
not
in
variables
:
bad_variables
.
append
(
v
)
bad_variables
.
append
(
v
ar
)
if
len
(
bad_variables
)
>
0
:
if
len
(
bad_variables
)
>
0
:
raise
UndefinedVariable
(
' '
.
join
(
bad_variables
))
raise
UndefinedVariable
(
' '
.
join
(
bad_variables
))
def
lower_dict
(
d
):
def
lower_dict
(
input_dict
):
"""
"""
takes each key in the dict and makes it lowercase, still mapping to the
takes each key in the dict and makes it lowercase, still mapping to the
same value.
same value.
...
@@ -109,7 +103,8 @@ def lower_dict(d):
...
@@ -109,7 +103,8 @@ def lower_dict(d):
variables that have the same lowercase representation. It would be hard to
variables that have the same lowercase representation. It would be hard to
tell which is used in the final dict and which isn't.
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
(),
input_dict
[
k
])
for
k
in
input_dict
])
# The following few functions define parse actions, which are run on lists of
# The following few functions define parse actions, which are run on lists of
# results from each parse component. They convert the strings and (previously
# results from each parse component. They convert the strings and (previously
...
@@ -119,32 +114,37 @@ def super_float(text):
...
@@ -119,32 +114,37 @@ 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
):
def
number_parse_action
(
parse_result
):
"""
"""
Create a float out of its string parts
Create a float out of its string parts
e.g. [ '7', '.', '13' ] -> [ 7.13 ]
e.g. [ '7', '.', '13' ] -> [ 7.13 ]
Calls super_float above
Calls super_float above
"""
"""
return
[
super_float
(
""
.
join
(
x
))]
return
super_float
(
""
.
join
(
parse_result
))
def
exp_parse_action
(
x
):
def
exp_parse_action
(
parse_result
):
"""
"""
Take a list of numbers and exponentiate them, right to left
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
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 ^
# pyparsing.ParseResults doesn't play well with reverse()
x
.
reverse
()
parse_result
=
parse_result
.
asList
()
x
=
reduce
(
lambda
a
,
b
:
b
**
a
,
x
)
parse_result
.
reverse
()
return
x
# the result of an exponentiation is called a power
power
=
reduce
(
lambda
a
,
b
:
b
**
a
,
parse_result
)
return
power
def
parallel
(
x
):
def
parallel
(
parse_result
):
"""
"""
Compute numbers according to the parallel resistors operator
Compute numbers according to the parallel resistors operator
...
@@ -154,15 +154,17 @@ def parallel(x):
...
@@ -154,15 +154,17 @@ def parallel(x):
Return NaN if there is a zero among the inputs
Return NaN if there is a zero among the inputs
"""
"""
x
=
list
(
x
)
# convert from pyparsing.ParseResults, which doesn't support '0 in parse_result'
if
len
(
x
)
==
1
:
parse_result
=
parse_result
.
asList
()
return
x
[
0
]
if
len
(
parse_result
)
==
1
:
if
0
in
x
:
return
parse_result
[
0
]
if
0
in
parse_result
:
return
float
(
'nan'
)
return
float
(
'nan'
)
x
=
[
1.
/
e
for
e
in
x
if
isinstance
(
e
,
numbers
.
Number
)]
# Ignore ||
reciprocals
=
[
1.
/
e
for
e
in
parse_result
]
return
1.
/
sum
(
x
)
return
1.
/
sum
(
reciprocals
)
def
sum_parse_action
(
x
):
# [ 1 + 2 - 3 ] -> 0
def
sum_parse_action
(
parse_result
):
"""
"""
Add the inputs
Add the inputs
...
@@ -171,29 +173,35 @@ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
...
@@ -171,29 +173,35 @@ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
Allow a leading + or -
Allow a leading + or -
"""
"""
total
=
0.0
total
=
0.0
op
=
ops
[
'+'
]
current_op
=
operator
.
add
for
e
in
x
:
for
token
in
parse_result
:
if
e
in
set
(
'+-'
):
if
token
is
'+'
:
op
=
ops
[
e
]
current_op
=
operator
.
add
elif
token
is
'-'
:
current_op
=
operator
.
sub
else
:
else
:
total
=
op
(
total
,
e
)
total
=
current_op
(
total
,
token
)
return
total
return
total
def
prod_parse_action
(
x
):
# [ 1 * 2 / 3 ] => 0.66
def
prod_parse_action
(
parse_result
):
"""
"""
Multiply the inputs
Multiply the inputs
[ 1, '*', 2, '/', 3 ] => 0.66
[ 1, '*', 2, '/', 3 ] => 0.66
"""
"""
prod
=
1.0
prod
=
1.0
op
=
ops
[
'*'
]
current_op
=
operator
.
mul
for
e
in
x
:
for
token
in
parse_result
:
if
e
in
set
(
'*/'
):
if
token
is
'*'
:
op
=
ops
[
e
]
current_op
=
operator
.
mul
elif
token
is
'/'
:
current_op
=
operator
.
truediv
else
:
else
:
prod
=
op
(
prod
,
e
)
prod
=
current_op
(
prod
,
token
)
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
...
@@ -202,20 +210,12 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -202,20 +210,12 @@ def evaluator(variables, functions, string, cs=False):
cs: Case sensitive
cs: Case sensitive
"""
"""
# 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))
all_variables
=
copy
.
copy
(
default_variables
)
all_functions
=
copy
.
copy
(
default_functions
)
def
func_parse_action
(
x
):
return
[
all_functions
[
x
[
0
]](
x
[
1
])]
if
not
cs
:
all_variables
=
lower_dict
(
all_variables
)
all_functions
=
lower_dict
(
all_functions
)
all_variables
=
copy
.
copy
(
DEFAULT_VARIABLES
)
all_functions
=
copy
.
copy
(
DEFAULT_FUNCTIONS
)
all_variables
.
update
(
variables
)
all_variables
.
update
(
variables
)
all_functions
.
update
(
functions
)
all_functions
.
update
(
functions
)
...
@@ -234,7 +234,7 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -234,7 +234,7 @@ def evaluator(variables, functions, string, cs=False):
return
float
(
'nan'
)
return
float
(
'nan'
)
# SI suffixes and percent
# SI suffixes and percent
number_suffix
=
MatchFirst
([
Literal
(
k
)
for
k
in
suffixes
.
keys
()])
number_suffix
=
MatchFirst
([
Literal
(
k
)
for
k
in
SUFFIXES
.
keys
()])
plus_minus
=
Literal
(
'+'
)
|
Literal
(
'-'
)
plus_minus
=
Literal
(
'+'
)
|
Literal
(
'-'
)
times_div
=
Literal
(
'*'
)
|
Literal
(
'/'
)
times_div
=
Literal
(
'*'
)
|
Literal
(
'/'
)
...
@@ -249,11 +249,10 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -249,11 +249,10 @@ def evaluator(variables, functions, string, cs=False):
number
=
(
inner_number
number
=
(
inner_number
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
plus_minus
)
+
number_part
)
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
plus_minus
)
+
number_part
)
+
Optional
(
number_suffix
))
+
Optional
(
number_suffix
))
number
=
number
.
setParseAction
(
number_parse_action
)
# Convert to number
number
.
setParseAction
(
number_parse_action
)
# Convert to number
# Predefine recursive variables
# Predefine recursive variables
expr
=
Forward
()
expr
=
Forward
()
factor
=
Forward
()
# Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution.
# Handle variables passed in. E.g. if we have {'R':0.5}, we make the substitution.
# Special case for no variables because of how we understand PyParsing is put together
# Special case for no variables because of how we understand PyParsing is put together
...
@@ -261,9 +260,10 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -261,9 +260,10 @@ def evaluator(variables, functions, string, cs=False):
# We sort the list so that var names (like "e2") match before
# We sort the list so that var names (like "e2") match before
# mathematical constants (like "e"). This is kind of a hack.
# mathematical constants (like "e"). This is kind of a hack.
all_variables_keys
=
sorted
(
all_variables
.
keys
(),
key
=
len
,
reverse
=
True
)
all_variables_keys
=
sorted
(
all_variables
.
keys
(),
key
=
len
,
reverse
=
True
)
literal_all_vars
=
[
CasedLiteral
(
k
)
for
k
in
all_variables_keys
]
varnames
=
MatchFirst
([
CasedLiteral
(
k
)
for
k
in
all_variables_keys
])
varnames
=
MatchFirst
(
literal_all_vars
)
varnames
.
setParseAction
(
varnames
.
setParseAction
(
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
])
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
]
)
else
:
else
:
# all_variables includes DEFAULT_VARIABLES, which isn't empty
# all_variables includes DEFAULT_VARIABLES, which isn't empty
# this is unreachable. Get rid of it?
# this is unreachable. Get rid of it?
...
@@ -273,7 +273,9 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -273,7 +273,9 @@ def evaluator(variables, functions, string, cs=False):
if
len
(
all_functions
)
>
0
:
if
len
(
all_functions
)
>
0
:
funcnames
=
MatchFirst
([
CasedLiteral
(
k
)
for
k
in
all_functions
.
keys
()])
funcnames
=
MatchFirst
([
CasedLiteral
(
k
)
for
k
in
all_functions
.
keys
()])
function
=
funcnames
+
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
function
=
funcnames
+
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
function
.
setParseAction
(
func_parse_action
)
function
.
setParseAction
(
lambda
x
:
[
all_functions
[
x
[
0
]](
x
[
1
])]
)
else
:
else
:
# see note above (this is unreachable)
# see note above (this is unreachable)
function
=
NoMatch
()
function
=
NoMatch
()
...
@@ -281,11 +283,13 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -281,11 +283,13 @@ def evaluator(variables, functions, string, cs=False):
atom
=
number
|
function
|
varnames
|
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
atom
=
number
|
function
|
varnames
|
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
# Do the following in the correct order to preserve order of operation
# Do the following in the correct order to preserve order of operation
factor
<<
(
atom
+
ZeroOrMore
(
"^"
+
atom
))
.
setParseAction
(
exp_parse_action
)
# 7^6
pow_term
=
atom
+
ZeroOrMore
(
Suppress
(
"^"
)
+
atom
)
paritem
=
factor
+
ZeroOrMore
(
Literal
(
'||'
)
+
factor
)
# 5k || 4k
pow_term
.
setParseAction
(
exp_parse_action
)
# 7^6
paritem
=
paritem
.
setParseAction
(
parallel
)
par_term
=
pow_term
+
ZeroOrMore
(
Suppress
(
'||'
)
+
pow_term
)
# 5k || 4k
term
=
paritem
+
ZeroOrMore
(
times_div
+
paritem
)
# 7 * 5 / 4 - 3
par_term
.
setParseAction
(
parallel
)
term
=
term
.
setParseAction
(
prod_parse_action
)
prod_term
=
par_term
+
ZeroOrMore
(
times_div
+
par_term
)
# 7 * 5 / 4 - 3
expr
<<
Optional
(
plus_minus
)
+
term
+
ZeroOrMore
(
plus_minus
+
term
)
# -5 + 4 - 3
prod_term
.
setParseAction
(
prod_parse_action
)
expr
=
expr
.
setParseAction
(
sum_parse_action
)
sum_term
=
Optional
(
plus_minus
)
+
prod_term
+
ZeroOrMore
(
plus_minus
+
prod_term
)
# -5 + 4 - 3
sum_term
.
setParseAction
(
sum_parse_action
)
expr
<<
sum_term
# finish the recursion
return
(
expr
+
stringEnd
)
.
parseString
(
string
)[
0
]
return
(
expr
+
stringEnd
)
.
parseString
(
string
)[
0
]
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