forked from sympy/sympy
-
Notifications
You must be signed in to change notification settings - Fork 1
/
lambdify.py
220 lines (193 loc) · 6.52 KB
/
lambdify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""
This module provides convenient functions to transform sympy expressions to
lambda functions which can be used to calculate numerical values very fast.
"""
from __future__ import division
from sympy.core import sympify
# These are the namespaces the lambda functions will use.
MATH = {}
MPMATH = {}
NUMPY = {}
SYMPY = {}
# Mappings between sympy and other modules function names.
MATH_TRANSLATIONS = {
"abs":"fabs",
"ceiling":"ceil",
"E":"e",
"ln":"log",
}
MPMATH_TRANSLATIONS = {
"ceiling":"ceil",
"chebyshevt":"chebyt",
"chebyshevu":"chebyu",
"E":"e",
"I":"j",
"ln":"log",
#"lowergamma":"lower_gamma",
"oo":"inf",
#"uppergamma":"upper_gamma",
"LambertW":"lambertw",
"Matrix":"matrix",
}
NUMPY_TRANSLATIONS = {
"acos":"arccos",
"acosh":"arccosh",
"arg":"angle",
"asin":"arcsin",
"asinh":"arcsinh",
"atan":"arctan",
"atan2":"arctan2",
"atanh":"arctanh",
"ceiling":"ceil",
"E":"e",
"im":"imag",
"ln":"log",
"Matrix":"matrix",
"max_":"amax",
"min_":"amin",
"oo":"inf",
"re":"real",
}
# Available modules:
MODULES = {
"math":(MATH, MATH_TRANSLATIONS, ("from math import *",)),
"mpmath":(MPMATH, MPMATH_TRANSLATIONS, ("from sympy.mpmath import *",)),
"numpy":(NUMPY, NUMPY_TRANSLATIONS, ("from numpy import *",)),
"sympy":(SYMPY, {}, ("from sympy.functions import *",
"from sympy.matrices import Matrix",
"from sympy import Integral"))
}
def _import(module, reload="False"):
"""
Creates a global translation dictionary for module.
The argument module has to be one of the following strings: "math",
"mpmath", "numpy", "sympy".
These dictionaries map names of python functions to their equivalent in
other modules.
"""
if not module in MODULES:
raise NameError("This module can't be used for lambdification.")
namespace, translations, import_commands = MODULES[module]
# Clear namespace or exit
if namespace:
# The namespace was already generated, don't do it again if not forced.
if reload:
namespace.clear()
else:
return
# It's possible that numpy is not available.
for import_command in import_commands:
try:
exec import_command in {}, namespace
except ImportError:
raise ImportError("Can't import %s with command %s" % (module, import_command))
# Add translated names to namespace
for sympyname, translation in translations.iteritems():
namespace[sympyname] = namespace[translation]
def lambdify(args, expr, modules=None):
"""
Returns a lambda function for fast calculation of numerical values.
Usage:
>>> from sympy import symbols, sqrt, sin
>>> x,y,z = symbols('xyz')
>>> f = lambdify(x, x**2)
>>> f(2)
4
>>> f = lambdify((x,y,z), [z,y,x])
>>> f(1,2,3)
[3, 2, 1]
>>> f = lambdify(x, sqrt(x))
>>> f(4)
2.0
>>> f = lambdify((x,y), sin(x*y)**2)
>>> f(0, 5)
0.0
If not specified differently by the user, Sympy functions are replaced as
far as possible by either python-math, numpy (if available) or mpmath
functions - exactly in this order.
To change this behaviour, the "modules" argument can be used.
It accepts:
- the strings "math", "mpmath", "numpy", "sympy"
- any modules (e.g. math)
- dictionaries that map names of sympy functions to arbitrary functions
- lists that contain a mix of the arguments above. (Entries that are first
in the list have higher priority)
Examples:
(1) Use one of the provided modules:
>> f = lambdify(x, sin(x), "math")
Attention: Functions that are not in the math module will throw a name
error when the lambda function is evaluated! So this would
be better:
>> f = lambdify(x, sin(x)*gamma(x), ("math", "mpmath", "sympy"))
(2) Use some other module:
>> import numpy
>> f = lambdify((x,y), tan(x*y), numpy)
Attention: There are naming diferences between numpy and sympy. So if
you simply take the numpy module, e.g. sympy.atan will not be
translated to numpy.arctan. Use the modified module instead
by passing the string "numpy".
(3) Use own dictionaries:
>> def my_cool_function(x): ...
>> dic = {"sin" : my_cool_function}
>> f = lambdify(x, sin(x), dic)
Now f would look like:
>> lambda x: my_cool_function(x)
"""
# If the user hasn't specified any modules, use what is available.
if modules is None:
# Use either numpy (if available) or python.math where possible.
# XXX: This leads to different behaviour on different systems and
# might be the reason for irreproducible errors.
try:
_import("numpy")
modules = ("math", "numpy", "mpmath", "sympy")
except ImportError:
modules = ("math", "mpmath", "sympy")
# Get the needed namespaces.
if isinstance(modules, dict): # Check for dict before "__iter__"
namespace = _get_namespace(modules)
elif hasattr(modules, "__iter__"):
namespace = {}
for m in modules:
buf = _get_namespace(m)
buf.update(namespace)
namespace = buf
else:
namespace = _get_namespace(modules)
# Create lambda function.
lstr = lambdastr(args, expr)
return eval(lstr, namespace)
def _get_namespace(m):
"""
This is used by _lambdify to parse it's arguments.
"""
if isinstance(m, str):
_import(m)
return MODULES[m][0]
elif isinstance(m, dict):
return m
elif hasattr(m, "__dict__"):
return m.__dict__
else:
raise TypeError("Argument must be either a string, dict or module but it is: %s" % m)
def lambdastr(args, expr):
"""
Returns a string that can be evaluated to a lambda function.
>>> from sympy import symbols
>>> x,y,z = symbols('xyz')
>>> lambdastr(x, x**2)
'lambda x: (x**2)'
>>> lambdastr((x,y,z), [z,y,x])
'lambda x,y,z: ([z, y, x])'
"""
#XXX: This has to be done here because of circular imports
from sympy.printing.lambdarepr import lambdarepr
# Transform everything to strings.
expr = lambdarepr(expr)
if isinstance(args, str):
pass
elif hasattr(args, "__iter__"):
args = ",".join(str(a) for a in args)
else:
args = str(args)
return "lambda %s: (%s)" % (args, expr)