Python 递归符号计算-提高性能

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

在我的研究中,我试图解决科尔莫戈罗夫倒向方程,即对 $$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
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(g(x))的n阶导数的方法,看起来很像
  • 这是一种快速求
    f(x)*g(x)
    的n阶导数的方法,看起来很像
  • 这两个问题都在第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]

    (其中by
    c'[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]