Python 递归符号计算-提高性能
在我的研究中,我试图解决科尔莫戈罗夫倒向方程,即对 $$Af=b(x)f'(x)+\sigma(x)f'(x)$$ 对于特定的b(x)和\sigma(x),我试图看到在计算更高的Af功率时表达式的系数增长有多快。我很难从分析的角度得出这个结论,因此我试图从经验的角度来看待这个趋势 首先,我使用了Python 递归符号计算-提高性能,python,sympy,symbolic-math,differential-equations,autograd,Python,Sympy,Symbolic Math,Differential Equations,Autograd,在我的研究中,我试图解决科尔莫戈罗夫倒向方程,即对 $$Af=b(x)f'(x)+\sigma(x)f'(x)$$ 对于特定的b(x)和\sigma(x),我试图看到在计算更高的Af功率时表达式的系数增长有多快。我很难从分析的角度得出这个结论,因此我试图从经验的角度来看待这个趋势 首先,我使用了sympy: from sympy import * import matplotlib.pyplot as plt import re import math import numpy as np im
sympy
:
from sympy import *
import matplotlib.pyplot as plt
import re
import math
import numpy as np
import time
np.set_printoptions(suppress=True)
x = Symbol('x')
b = Function('b')(x)
g = Function('g')(x)
def new_coef(gamma, beta, coef_minus2, coef_minus1, coef):
return expand(simplify(gamma*coef_minus2 + beta*coef_minus1 + 2*gamma*coef_minus1.diff(x)\
+beta*coef.diff(x)+gamma*coef.diff(x,2)))
def new_coef_first(gamma, beta, coef):
return expand(simplify(beta*coef.diff(x)+gamma*coef.diff(x,2)))
def new_coef_second(gamma, beta, coef_minus1, coef):
return expand(simplify(beta*coef_minus1 + 2*gamma*coef_minus1.diff(x)\
+beta*coef.diff(x)+gamma*coef.diff(x,2)))
def new_coef_last(gamma, beta, coef_minus2):
return lambda x: gamma(x)*coef_minus2(x)
def new_coef_last(gamma, beta, coef_minus2):
return expand(simplify(gamma*coef_minus2 ))
def new_coef_second_to_last(gamma, beta, coef_minus2, coef_minus1):
return expand(simplify(gamma*coef_minus2 + beta*coef_minus1 + 2*gamma*coef_minus1.diff(x)))
def set_to_zero(expression):
expression = expression.subs(Derivative(b, x, x, x), 0)
expression = expression.subs(Derivative(b, x, x), 0)
expression = expression.subs(Derivative(g, x, x, x, x), 0)
expression = expression.subs(Derivative(g, x, x, x), 0)
return expression
def sum_of_coef(expression):
sum_of_coef = 0
for i in str(expression).split(' + '):
if i[0:1] == '(':
i = i[1:]
integers = re.findall(r'\b\d+\b', i)
if len(integers) > 0:
length_int = len(integers[0])
if i[0:length_int] == integers[0]:
sum_of_coef += int(integers[0])
else:
sum_of_coef += 1
else:
sum_of_coef += 1
return sum_of_coef
power = 6
charar = np.zeros((power, power*2), dtype=Symbol)
coef_sum_array = np.zeros((power, power*2))
charar[0,0] = b
charar[0,1] = g
coef_sum_array[0,0] = 1
coef_sum_array[0,1] = 1
for i in range(1, power):
#print(i)
for j in range(0, (i+1)*2):
#print(j, ':')
#start_time = time.time()
if j == 0:
charar[i,j] = set_to_zero(new_coef_first(g, b, charar[i-1, j]))
elif j == 1:
charar[i,j] = set_to_zero(new_coef_second(g, b, charar[i-1, j-1], charar[i-1, j]))
elif j == (i+1)*2-2:
charar[i,j] = set_to_zero(new_coef_second_to_last(g, b, charar[i-1, j-2], charar[i-1, j-1]))
elif j == (i+1)*2-1:
charar[i,j] = set_to_zero(new_coef_last(g, b, charar[i-1, j-2]))
else:
charar[i,j] = set_to_zero(new_coef(g, b, charar[i-1, j-2], charar[i-1, j-1], charar[i-1, j]))
#print("--- %s seconds for expression---" % (time.time() - start_time))
#start_time = time.time()
coef_sum_array[i,j] = sum_of_coef(charar[i,j])
#print("--- %s seconds for coeffiecients---" % (time.time() - start_time))
coef_sum_array
然后,研究自动差异化并使用autograd:
import autograd.numpy as np
from autograd import grad
import time
np.set_printoptions(suppress=True)
b = lambda x: 1 + x
g = lambda x: 1 + x + x**2
def new_coef(gamma, beta, coef_minus2, coef_minus1, coef):
return lambda x: gamma(x)*coef_minus2(x) + beta(x)*coef_minus1(x) + 2*gamma(x)*grad(coef_minus1)(x)\
+beta(x)*grad(coef)(x)+gamma(x)*grad(grad(coef))(x)
def new_coef_first(gamma, beta, coef):
return lambda x: beta(x)*grad(coef)(x)+gamma(x)*grad(grad(coef))(x)
def new_coef_second(gamma, beta, coef_minus1, coef):
return lambda x: beta(x)*coef_minus1(x) + 2*gamma(x)*grad(coef_minus1)(x)\
+beta(x)*grad(coef)(x)+gamma(x)*grad(grad(coef))(x)
def new_coef_last(gamma, beta, coef_minus2):
return lambda x: gamma(x)*coef_minus2(x)
def new_coef_second_to_last(gamma, beta, coef_minus2, coef_minus1):
return lambda x: gamma(x)*coef_minus2(x) + beta(x)*coef_minus1(x) + 2*gamma(x)*grad(coef_minus1)(x)
power = 6
coef_sum_array = np.zeros((power, power*2))
coef_sum_array[0,0] = b(1.0)
coef_sum_array[0,1] = g(1.0)
charar = [b, g]
for i in range(1, power):
print(i)
charar_new = []
for j in range(0, (i+1)*2):
if j == 0:
new_funct = new_coef_first(g, b, charar[j])
elif j == 1:
new_funct = new_coef_second(g, b, charar[j-1], charar[j])
elif j == (i+1)*2-2:
new_funct = new_coef_second_to_last(g, b, charar[j-2], charar[j-1])
elif j == (i+1)*2-1:
new_funct = new_coef_last(g, b, charar[j-2])
else:
new_funct = new_coef(g, b, charar[j-2], charar[j-1], charar[j])
coef_sum_array[i,j] = new_funct(1.0)
charar_new.append(new_funct)
charar = charar_new
coef_sum_array
然而,我对它们的速度都不满意。我希望至少进行1000次迭代,而在运行simpy方法3天后,我得到了30:/
我希望第二种方法(数值)可以优化,以避免每次都重新计算表达式。不幸的是,我自己看不到这个解决方案。另外,我也试过枫树,但又一次运气不佳。概述
这里有两个关于导数的公式很有趣:
f(x)*g(x)
的n阶导数的方法,看起来很像c[i][j]
的一般公式如下:
c[i][j]=g*c[i-1][j-2]+b*c[i-1][j-1]+2*g*c'[i-1][j-1]+g*c'[i-1][j]
(其中byc'[i][j]
和c'[i][j]
是c[i][j]
的一阶和二阶导数)
因此,根据上面提到的莱布尼兹规则,我直觉地认为,计算出的系数应该与(或者至少它们应该有某种组合关系)相关
优化#1
在原始代码中,函数sum_to_coef(f)
将表达式f
序列化为字符串,然后丢弃看起来不像数字的所有内容,然后对剩余的数字求和
在这里,我们可以通过遍历表达式树并收集所需内容来避免序列化
def coef(f)之和:
s=0
如果f.func==添加:
对于f.args中的sum_项:
res=如果sum_term.是数字,则为sum_term.1
如果len(sum_term.args)==0:
s+=res
持续
first=和项.args[0]
如果first.is_Number==True:
res=第一
其他:
res=1
s+=res
elif f.func==Mul:
first=f.args[0]
如果first.is_Number==True:
s=第一
其他:
s=1
elif f.func==功率:
s=1
返回s
优化#2
在函数中,将u设置为0(expr)
所有b
的二阶和三阶导数,以及g
的三阶和四阶导数均替换为0
我们可以将所有这些替换折叠成一个语句,如下所示:
b3,b2=b.diff(x,3),b.diff(x,2)
g4,g3=g.diff(x,4),g.diff(x,3)
def将_设置为_零(表达式):
expression=expression.subs({b3:0,b2:0,g4:0,g3:0})
返回表达式
优化#3
在原始代码中,对于每个单元格c[i][j]
我们调用simplify
。这对性能有很大的影响,但实际上我们可以跳过这个调用,因为幸运的是,我们的表达式只是导数或未知函数乘积的和
那么这条线呢
charar[i,j]=将_设置为零(展开(简化(expr)))
变成
charar[i,j]=将_设置为_零(扩展(expr))
优化#4
下面的方法也曾尝试过,但效果甚微
对于两个连续的j值,我们计算了两次c'[i-1][j-1]
j-1 c[i-1][j-3] c[i-1][j-2] c[i-1][j-1]
j c[i-1][j-2] c[i-1][j-1] c[i-1][j]
如果查看else
分支中的循环公式,您会发现c'[i-1][j-1]
已经计算过了。它可以缓存,但这种优化
在代码的SymPy版本中几乎没有效果
这里还需要指出的是,计算这些导数涉及到的是symphy的调用树。它实际上更大,但这里是它的一部分:
我们还可以使用该模块生成火焰图,以查看花费的时间:
据我所知,34%的时间花在\u eval\u derivative\u n\u时间上,10%的时间花在函数getit
中,来自假设.py
,12%的时间花在subs(…)
,12%的时间花在扩展(…)
优化#5
显然,当合并到SymPy时,它还引入了性能回归
其中一个例子是使用SymEngine来提高大量使用衍生工具的代码的性能
因此,我已将提到的代码移植到,并注意到它的运行速度比SymPy版本的power=8
快98倍(而power=30
快4320倍)
所需的模块可以通过pip3安装--user symmengine
安装
#/usr/bin/python3
从symengine导入*
导入pprint
x=var(“x”)
b=功能(“b”)(x)
g=函数(“g”)(x)
b3,b2=b.diff(x,3),b.diff(x,2)
g4,g3=g.diff(x,4),g.diff(x,3)
def设置为零(e):
e=e.subs({b3:0,b2:0,g4:0,g3:0})
返回e
定义系数(f)之和:
s=0
如果f.func==添加:
对于f.args中的sum_项:
res=1
如果len(sum_term.args)==0:
s+=res
持续
first=和项.args[0]
如果first.is_Number==True:
res=第一
其他:
res=1
s+=res
elif f.func==Mul:
first=f.args[0]
如果first.is_Number==True:
time slope = 5.69
memory slope = 2.62
time slope = 2.95
memory slope = 1.35
match-growth.py --infile ./tests/modif7_bench.txt --outfile time.png --col1 N --col2 time --top 1
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 5, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 17, 40, 31, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 53, 292, 487, 330, 106, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 161, 1912, 6091, 7677, 4693, 1520, 270, 25, 1, 0, 0, 0, 0, 0, 0]
[1, 485, 11956, 68719, 147522, 150706, 83088, 26573, 5075, 575, 36, 1, 0, 0, 0, 0]
[1, 1457, 73192, 735499, 2568381, 4118677, 3528928, 1772038, 550620, 108948, 13776, 1085, 49, 1, 0, 0]
[1, 4373, 443524, 7649215, 42276402, 102638002, 130209104, 96143469, 44255170, 13270378, 2658264, 358890, 32340, 1876, 64, 1]