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
11 years ago
by
Peter Baratta
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Rename variables; get rid of OPS
parent
72d149ca
Hide 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
import
re
import
numpy
import
numbers
import
scipy.constants
from
pyparsing
import
Word
,
nums
,
Literal
from
pyparsing
import
ZeroOrMore
,
MatchFirst
from
pyparsing
import
Optional
,
Forward
from
pyparsing
import
CaselessLiteral
from
pyparsing
import
NoMatch
,
stringEnd
,
Suppress
,
Combine
from
pyparsing
import
(
Word
,
nums
,
Literal
,
ZeroOrMore
,
MatchFirst
,
Optional
,
Forward
,
CaselessLiteral
,
NoMatch
,
stringEnd
,
Suppress
,
Combine
)
default_functions
=
{
'sin'
:
numpy
.
sin
,
DEFAULT_FUNCTIONS
=
{
'sin'
:
numpy
.
sin
,
'cos'
:
numpy
.
cos
,
'tan'
:
numpy
.
tan
,
'sqrt'
:
numpy
.
sqrt
,
...
...
@@ -34,7 +33,7 @@ default_functions = {'sin': numpy.sin,
'fact'
:
math
.
factorial
,
'factorial'
:
math
.
factorial
}
default_variables
=
{
'j'
:
numpy
.
complex
(
0
,
1
),
DEFAULT_VARIABLES
=
{
'j'
:
numpy
.
complex
(
0
,
1
),
'e'
:
numpy
.
e
,
'pi'
:
numpy
.
pi
,
'k'
:
scipy
.
constants
.
k
,
...
...
@@ -43,22 +42,15 @@ default_variables = {'j': numpy.complex(0, 1),
'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
# confusing. They may also conflict with variables if we ever allow e.g.
# 5R instead of 5*R
suffixes
=
{
'
%
'
:
0.01
,
'k'
:
1e3
,
'M'
:
1e6
,
'G'
:
1e9
,
SUFFIXES
=
{
'
%
'
:
0.01
,
'k'
:
1e3
,
'M'
:
1e6
,
'G'
:
1e9
,
'T'
:
1e12
,
# 'P':1e15,'E':1e18,'Z':1e21,'Y':1e24,
'c'
:
1e-2
,
'm'
:
1e-3
,
'u'
:
1e-6
,
'n'
:
1e-9
,
'p'
:
1e-12
}
# ,'f':1e-15,'a':1e-18,'z':1e-21,'y':1e-24}
log
=
logging
.
getLogger
(
"mitx.courseware.capa"
)
LOG
=
logging
.
getLogger
(
"mitx.courseware.capa"
)
class
UndefinedVariable
(
Exception
):
...
...
@@ -73,13 +65,12 @@ class UndefinedVariable(Exception):
# raise self
general_whitespace
=
re
.
compile
(
'[^
\\
w]+'
)
def
check_variables
(
string
,
variables
):
"""
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
elegant approach pretty hopeless.
...
...
@@ -88,19 +79,22 @@ def check_variables(string, variables):
undefined_variable.setParseAction(lambda x:UndefinedVariable("".join(x)).raiseself())
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
()
for
v
in
possible_variables
:
if
len
(
v
)
==
0
:
for
v
ar
in
possible_variables
:
if
len
(
v
ar
)
==
0
:
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
if
v
not
in
variables
:
bad_variables
.
append
(
v
)
if
v
ar
not
in
variables
:
bad_variables
.
append
(
v
ar
)
if
len
(
bad_variables
)
>
0
:
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
same value.
...
...
@@ -109,7 +103,8 @@ def lower_dict(d):
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
(),
input_dict
[
k
])
for
k
in
input_dict
])
# The following few functions define parse actions, which are run on lists of
# results from each parse component. They convert the strings and (previously
...
...
@@ -119,32 +114,37 @@ def super_float(text):
"""
Like float, but with si extensions. 1k goes to 1000
"""
if
text
[
-
1
]
in
suffixes
:
return
float
(
text
[:
-
1
])
*
suffixes
[
text
[
-
1
]]
if
text
[
-
1
]
in
SUFFIXES
:
return
float
(
text
[:
-
1
])
*
SUFFIXES
[
text
[
-
1
]]
else
:
return
float
(
text
)
def
number_parse_action
(
x
):
def
number_parse_action
(
parse_result
):
"""
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
(
parse_result
))
def
exp_parse_action
(
x
):
def
exp_parse_action
(
parse_result
):
"""
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
.
reverse
()
x
=
reduce
(
lambda
a
,
b
:
b
**
a
,
x
)
return
x
# pyparsing.ParseResults doesn't play well with reverse()
parse_result
=
parse_result
.
asList
()
parse_result
.
reverse
()
# 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
...
...
@@ -154,15 +154,17 @@ def parallel(x):
Return NaN if there is a zero among the inputs
"""
x
=
list
(
x
)
if
len
(
x
)
==
1
:
return
x
[
0
]
if
0
in
x
:
# convert from pyparsing.ParseResults, which doesn't support '0 in parse_result'
parse_result
=
parse_result
.
asList
()
if
len
(
parse_result
)
==
1
:
return
parse_result
[
0
]
if
0
in
parse_result
:
return
float
(
'nan'
)
x
=
[
1.
/
e
for
e
in
x
if
isinstance
(
e
,
numbers
.
Number
)]
# Ignore ||
return
1.
/
sum
(
x
)
reciprocals
=
[
1.
/
e
for
e
in
parse_result
]
return
1.
/
sum
(
reciprocals
)
def
sum_parse_action
(
x
):
# [ 1 + 2 - 3 ] -> 0
def
sum_parse_action
(
parse_result
):
"""
Add the inputs
...
...
@@ -171,29 +173,35 @@ def sum_parse_action(x): # [ 1 + 2 - 3 ] -> 0
Allow a leading + or -
"""
total
=
0.0
op
=
ops
[
'+'
]
for
e
in
x
:
if
e
in
set
(
'+-'
):
op
=
ops
[
e
]
current_op
=
operator
.
add
for
token
in
parse_result
:
if
token
is
'+'
:
current_op
=
operator
.
add
elif
token
is
'-'
:
current_op
=
operator
.
sub
else
:
total
=
op
(
total
,
e
)
total
=
current_op
(
total
,
token
)
return
total
def
prod_parse_action
(
x
):
# [ 1 * 2 / 3 ] => 0.66
def
prod_parse_action
(
parse_result
):
"""
Multiply the inputs
[ 1, '*', 2, '/', 3 ] => 0.66
"""
prod
=
1.0
op
=
ops
[
'*'
]
for
e
in
x
:
if
e
in
set
(
'*/'
):
op
=
ops
[
e
]
current_op
=
operator
.
mul
for
token
in
parse_result
:
if
token
is
'*'
:
current_op
=
operator
.
mul
elif
token
is
'/'
:
current_op
=
operator
.
truediv
else
:
prod
=
op
(
prod
,
e
)
prod
=
current_op
(
prod
,
token
)
return
prod
def
evaluator
(
variables
,
functions
,
string
,
cs
=
False
):
"""
Evaluate an expression. Variables are passed as a dictionary
...
...
@@ -202,20 +210,12 @@ def evaluator(variables, functions, string, cs=False):
cs: Case sensitive
"""
# log.debug("variables: {0}".format(variables))
# log.debug("functions: {0}".format(functions))
# 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
)
# LOG.debug("variables: {0}".format(variables))
# LOG.debug("functions: {0}".format(functions))
# LOG.debug("string: {0}".format(string))
all_variables
=
copy
.
copy
(
DEFAULT_VARIABLES
)
all_functions
=
copy
.
copy
(
DEFAULT_FUNCTIONS
)
all_variables
.
update
(
variables
)
all_functions
.
update
(
functions
)
...
...
@@ -234,7 +234,7 @@ def evaluator(variables, functions, string, cs=False):
return
float
(
'nan'
)
# 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
(
'-'
)
times_div
=
Literal
(
'*'
)
|
Literal
(
'/'
)
...
...
@@ -249,11 +249,10 @@ def evaluator(variables, functions, string, cs=False):
number
=
(
inner_number
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
plus_minus
)
+
number_part
)
+
Optional
(
number_suffix
))
number
=
number
.
setParseAction
(
number_parse_action
)
# Convert to number
number
.
setParseAction
(
number_parse_action
)
# Convert to number
# Predefine recursive variables
expr
=
Forward
()
factor
=
Forward
()
# 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
...
...
@@ -261,9 +260,10 @@ def evaluator(variables, functions, string, cs=False):
# We sort the list so that var names (like "e2") match before
# mathematical constants (like "e"). This is kind of a hack.
all_variables_keys
=
sorted
(
all_variables
.
keys
(),
key
=
len
,
reverse
=
True
)
literal_all_vars
=
[
CasedLiteral
(
k
)
for
k
in
all_variables_keys
]
varnames
=
MatchFirst
(
literal_all_vars
)
varnames
.
setParseAction
(
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
])
varnames
=
MatchFirst
([
CasedLiteral
(
k
)
for
k
in
all_variables_keys
])
varnames
.
setParseAction
(
lambda
x
:
[
all_variables
[
k
]
for
k
in
x
]
)
else
:
# all_variables includes DEFAULT_VARIABLES, which isn't empty
# this is unreachable. Get rid of it?
...
...
@@ -273,7 +273,9 @@ def evaluator(variables, functions, string, cs=False):
if
len
(
all_functions
)
>
0
:
funcnames
=
MatchFirst
([
CasedLiteral
(
k
)
for
k
in
all_functions
.
keys
()])
function
=
funcnames
+
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
function
.
setParseAction
(
func_parse_action
)
function
.
setParseAction
(
lambda
x
:
[
all_functions
[
x
[
0
]](
x
[
1
])]
)
else
:
# see note above (this is unreachable)
function
=
NoMatch
()
...
...
@@ -281,11 +283,13 @@ def evaluator(variables, functions, string, cs=False):
atom
=
number
|
function
|
varnames
|
Suppress
(
"("
)
+
expr
+
Suppress
(
")"
)
# Do the following in the correct order to preserve order of operation
factor
<<
(
atom
+
ZeroOrMore
(
"^"
+
atom
))
.
setParseAction
(
exp_parse_action
)
# 7^6
paritem
=
factor
+
ZeroOrMore
(
Literal
(
'||'
)
+
factor
)
# 5k || 4k
paritem
=
paritem
.
setParseAction
(
parallel
)
term
=
paritem
+
ZeroOrMore
(
times_div
+
paritem
)
# 7 * 5 / 4 - 3
term
=
term
.
setParseAction
(
prod_parse_action
)
expr
<<
Optional
(
plus_minus
)
+
term
+
ZeroOrMore
(
plus_minus
+
term
)
# -5 + 4 - 3
expr
=
expr
.
setParseAction
(
sum_parse_action
)
pow_term
=
atom
+
ZeroOrMore
(
Suppress
(
"^"
)
+
atom
)
pow_term
.
setParseAction
(
exp_parse_action
)
# 7^6
par_term
=
pow_term
+
ZeroOrMore
(
Suppress
(
'||'
)
+
pow_term
)
# 5k || 4k
par_term
.
setParseAction
(
parallel
)
prod_term
=
par_term
+
ZeroOrMore
(
times_div
+
par_term
)
# 7 * 5 / 4 - 3
prod_term
.
setParseAction
(
prod_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
]
This diff is collapsed.
Click to expand it.
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