Python Sympy:lambdify,这样数组上的操作总是会产生数组,也会产生常量?
我需要计算用户在许多点上给出的函数(f')的导数。点位于列表中(或numpy.array、pandas.Series…)。当f'依赖于同向变量时,我得到了期望值,但当f'是常数时,我得不到期望值:Python Sympy:lambdify,这样数组上的操作总是会产生数组,也会产生常量?,python,numpy,sympy,Python,Numpy,Sympy,我需要计算用户在许多点上给出的函数(f')的导数。点位于列表中(或numpy.array、pandas.Series…)。当f'依赖于同向变量时,我得到了期望值,但当f'是常数时,我得不到期望值: 将sympy作为sp导入 f1=sp.sympify('1') f2=sp.sympify('t') lamb1=sp.lambdify('t',f1) lamb2=sp.lambdify('t',f2) 打印(lamb1([1,2,3])) 打印(lamb2([1,2,3])) 我获得: 1 [1
将sympy作为sp导入
f1=sp.sympify('1')
f2=sp.sympify('t')
lamb1=sp.lambdify('t',f1)
lamb2=sp.lambdify('t',f2)
打印(lamb1([1,2,3]))
打印(lamb2([1,2,3]))
我获得:
1
[1, 2, 3]
第二个是可以的,但是我希望第一个是一个列表
这些函数位于矩阵中,是辛运算(如求导数)的最终结果。f1和f2的确切形式因问题而异。
lamb1
是一个返回常量1:def lamb1(x):返回1
的函数
lamb2
是一个返回其参数的函数:def lamb2(x):返回x
因此,输出非常符合预期
这里有一个可能有效的方法。我将f2
的测试函数改为t*t
,因为这在我的测试中更烦人(处理Pow(t,2))
将sympy作为sp导入
将numpy作为np导入
f1=sp.sympify('1')
f2=sp.sympify('t*t')
def np_lambdify(变量名,函数):
lamb=sp.lambdify(varname,func,modules=['numpy'])
如果函数是常数():
返回lambda t:np.full_-like(t,lamb(t))
其他:
返回lambda t:lamb(np.数组(t))
lamb1=np_lambdify('t',f1)
lamb2=np_lambdify('t',f2)
印刷品(λ1(1))
打印(lamb1([1,2,3]))
印刷品(2(2))
打印(lamb2([1,2,3]))
产出:
1
[1 1 1]
4
[1 4 9]
我经常使用技巧
t*0+1
创建一个与输入长度相同的零向量,然后将其每个元素加1。它与NumPy合作;检查它是否与Symphy一起工作 我从不使用lambdify,所以我对它的工作方式不能太挑剔。但是,您似乎需要愚弄它,给它一个表达式,它不会简化为标量,当用数字计算时,它会减少到所需的值:
>>> import numpy as np
>>> lambdify('t','(1+t)*t-t**2-t+42','numpy')(np.array([1,2,3]))
array([42, 42, 42])
通过
isympy/ipython
内省:
In [28]: lamb2??
Signature: lamb2(t)
Docstring:
Created with lambdify. Signature:
func(arg_0)
Expression:
t
Source code:
def _lambdifygenerated(t):
return (t)
首先:
In [29]: lamb1??
Signature: lamb1(t)
Docstring:
Created with lambdify. Signature:
func(arg_0)
Expression:
1
Source code:
def _lambdifygenerated(t):
return (1)
因此返回输入参数;另一个只返回常量,而不考虑输入lambdify
将sympy
进行相当简单的词汇翻译,将其转换为numpy
Python
编辑
将函数放入sp.Matrix
:
In [55]: lamb3 = lambdify('t',Matrix([f1,f2]))
In [56]: lamb3??
...
def _lambdifygenerated(t):
return (array([[1], [t]]))
...
In [57]: lamb3(np.arange(3))
Out[57]:
array([[1],
[array([0, 1, 2])]], dtype=object)
这将返回一个numpy数组;但由于形状的混合,结果是对象数据类型,而不是2d
我们可以通过直接阵列生成看到这一点:
In [53]: np.array([[1],[1,2,3]])
Out[53]: array([list([1]), list([1, 2, 3])], dtype=object)
In [54]: np.array([np.ones(3,int),[1,2,3]])
Out[54]:
array([[1, 1, 1],
[1, 2, 3]])
sympy
和np.array
都不会试图“广播”该常量。有一些numpy构造可以做到这一点,比如乘法和加法,但是这个简单的sympy函数和lambdify不能做到
编辑
frompyfunc
是一种将数组传递给只使用标量输入的函数的方法。虽然lamb2
与数组输入一起工作,但您对lamb1
情况不满意,或者可能对lamb3
不满意
In [60]: np.frompyfunc(lamb1,1,1)([1,2,3])
Out[60]: array([1, 1, 1], dtype=object)
In [61]: np.frompyfunc(lamb2,1,1)([1,2,3])
Out[61]: array([1, 2, 3], dtype=object)
这[61]比简单的lamb2([1,2,3])
要慢,因为它可以有效地进行迭代
In [62]: np.frompyfunc(lamb3,1,1)([1,2,3])
Out[62]:
array([array([[1],
[1]]), array([[1],
[2]]),
array([[1],
[3]])], dtype=object)
在这种矩阵情况下,结果是数组的数组。但由于形状匹配,它们可以组合成一个数组(以各种方式):
通常情况下,
lambdify
返回常量实际上不是问题,因为NumPy的广播语义会自动将常量视为具有适当形状的常量数组
如果这是一个问题,您可以使用像
def broadcast(fun):
return lambda *x: numpy.broadcast_arrays(fun(*x), *x)[0]
(这是从对这个问题有更多讨论的
请注意,使用
broadcast
比JohanC的回答中的full_like
更好,因为广播的常量数组实际上不会占用更多内存,而full_like
会复制内存中的常量来制作数组。我明白你的意思,谢谢。然而,在我使用它的脚本中,正如我所提到的,这两个求值是在一个矩阵中进行的(函数是另一个函数的梯度)。因此,当我求值时,我得到类似于[1,1,2,3]的东西,但我需要[1,1,1],[1,2,3],,是否有任何方法来识别它们,并在lamb1的情况下使用类似于[1]*len([1,2,3])的东西来获得所需的[1,1,1]可能有用。它不会很快,但对于您的一般情况,它可能是最干净的解决方案。您的解决方案很有用,但我更喜欢@JohanC的解决方案(我不知道哪个是最好的还是最快的)f1和f2是用户给出的函数的导数,如我所说,使用Symphy的目的是避免用户必须派生和定义函数。此外,导数的数量取决于问题,你的解不是通解。f1和f2是由symphy.diff计算的(我在问题中省略了它),我不能修改它们。
def broadcast(fun):
return lambda *x: numpy.broadcast_arrays(fun(*x), *x)[0]