我目前正在尝试使用SymPy来生成和数字评估函数及其渐变。为简单起见,我将使用以下函数作为示例(请记住,实际函数更长):
import sympy as sp
def g(x):
return sp.cos(x) + sp.cos(x)**2 + sp.cos(x)**3
很容易用数字方式评估这个函数及其衍生物:
import numpy as np
g_expr = sp.lambdify(x,g(x),modules='numpy')
dg_expr = sp.lambdify(x,sp.diff(g(x)),modules='numpy')
print g_expr(np.linspace(0,1,50))
print dg_expr(np.linspace(0,1,50))
然而,对于我的真实函数,lambdify在生成数值函数和评估方面都很慢。由于我函数中的许多元素都是相似的,我想在lambdify中使用公共子表达式消除(cse)来加速这个过程。我知道SymPy有一个内置函数来执行cse,
>>> print sp.cse(g(x))
([(x0, cos(x))], [x0**3 + x0**2 + x0])
但是不知道使用什么语法来在lambdify函数中使用这个结果(我仍然希望使用x作为我的输入参数):
>>> g_expr_fast = sp.lambdify(x,sp.cse(g(x)),modules='numpy')
>>> print g_expr_fast(np.linspace(0,1,50))
Traceback (most recent call last):
File "test3.py", line 34, in <module>
print g_expr1(nx1)
File "<string>", line 1, in <lambda>
NameError: global name 'x0' is not defined
任何有关如何在lambdify中使用cse的帮助将不胜感激。或者,如果有更好的方法来加速我的渐变计算,我也很感激听到这些。
如果相关,我使用的是Python 2.7.3和SymPy 0.7.6。
答案 0 :(得分:1)
所以这可能不是最佳的方式,但对于我的小例子,它可以工作。
遵循代码的想法是对每个公共子表达式进行简化,并生成一个可能包含所有参数的新函数。我添加了一些额外的sin和cos术语来添加先前子表达式的可能依赖项。
import sympy as sp
import sympy.abc
import numpy as np
import matplotlib.pyplot as pl
def g(x):
return sp.cos(x) + sp.cos(x)**2 + sp.cos(x)**3 + sp.sin(sp.cos(x)+sp.sin(x))**4 + sp.sin(x) - sp.cos(3*x) + sp.sin(x)**2
repl, redu=sp.cse(g(sp.abc.x))
funs = []
syms = [sp.abc.x]
for i, v in enumerate(repl):
funs.append(sp.lambdify(syms,v[1],modules='numpy'))
syms.append(v[0])
glam = sp.lambdify(syms,redu[0],modules='numpy')
x = np.linspace(-1,5,10)
xs=[x]
for f in funs:
xs.append(f(*xs))
print glam(*xs)
glamlam = sp.lambdify(sp.abc.x,g(sp.abc.x),modules='numpy')
print glamlam(x)
print np.allclose(glamlam(x),glam(*xs))
repl包含:
[(x0, cos(x)), (x1, sin(x)), (x2, x0 + x1)]
和redu包含
[x0**3 + x0**2 + x1**2 + x2 + sin(x2)**4 - cos(3*x)]
因此funs
包含lambdified的所有子表达式,列表xs
包含评估的每个子表达式,这样可以最终正确地提供glam
。 xs
随着每个子表达式而增长,最终可能会变成瓶颈。
您可以对sp.cse(sp.diff(g(sp.abc.x)))
的表达式采用相同的方法。
答案 1 :(得分:1)
可以提高计算速度:
我假设这是“一次在sympy中计算函数,以后在不同的项目中多次使用”这种情况。因此,其中包含一些手动复制粘贴和创建的文件。 但是可以使用这些功能以及编译步骤来自动创建新文件,但是现在我不再赘述了。
我遇到了类似的问题,并且对不同的方法进行了一些基准测试。我使用的函数很长(len(str(expr)) = 45857
),cse(expr)
将其分解为72个子表达式。在这里复制粘贴太长了,但是这里是将sympy创建的函数的速度提高100x-1000x的步骤。
A)评估单个浮点数
是时候用每个参数一个浮点值评估函数了。使用timeit myfunc(*params)
。
modules="numpy"
进行lambdify:277µs str(expr)
到函数定义:275µs(无差异)cse
之后的表达式复制粘贴:8.2 µs(提高了33倍)cse(optimizations="basic")
之后的表达式复制粘贴:7.6µs(提高了36倍)func_numba_f()
:0.25µs(改进了1090倍)autowrap
:0.47 µs(改进589倍) B)评估1000个浮点数的np.array
str(expr)
复制粘贴到函数定义:15100 µs |每个值15.1µs cse
之后的表达式复制粘贴:493 µs |每个值0.49µs(提高31倍)cse(optimizations="basic")
之后的表达式的复制粘贴:413µs |每个值0.41µs(提高了37倍)func_numba_arr()
:114µs |每个值0.11µs(提高了132倍)str(expr)
cse
之后的表达式的复制粘贴repl, redu = cse(K)
for variable, expr in repl:
print(f"{variable} = {expr}")
print(redu[0])
。cse(optimizations="basic")
之后的表达式optimizations="basic"
src_mymodule.py
from numba.pycc import CC
cc = CC("my_numba_module")
@cc.export("func_numba_f", "f8(f8, f8, f8, f8, f8)")
@cc.export("func_numba_arr", "f8[:](f8[:],f8[:],f8[:],f8[:],f8[:])")
def myfunc(x1, x2, x3, x4, x5):
# your function definition here
return value
if __name__ == "__main__":
cc.compile()
func_numba_f()
中,有五个浮点型输入变量和一个浮点型输出变量。 f8
表示浮动。func_numba_arr()
是使用dtype="float64"
或dtype="float32"
处理np.arrays的版本,具体取决于您用于编译的内容。python src_mymodule.py
一次编译代码。这将创建my_numba_module.cp38-win_amd64.pyd
或类似的内容。只能与文件名中的相同python版本和位数一起使用。from my_numba_module import func_numba_f, func_numba_arr
out = func_numba_f(4,3,2,1,100)
# or:
args = [np.array([x]*N, dtype='float64') for x in (4,3,2,1,100)]
out_arr = func_numba_arr(*args)
autowrap
from sympy.utilities.autowrap import autowrap
func = autowrap(expr, backend='cython')
temp_dir
参数,它将保存所有源文件(.c,.h,.pyx)和可用于导入文件的.pyd(win)/。so(unix)文件。稍后使用(假设temp_dir
在sys.path
中)from wrapper_module_1 import autofunc_c
func = np.vectorize(func)