如何绘制grad(f(x,y))?

6
我想计算和绘制任意两个变量的标量函数的梯度。如果你真的需要一个具体的例子,假设f=x^2+y^2,其中x从-10到10,y也是一样。我该如何计算和绘制grad(f)?解决方案应该是向量,并且我应该看到向量线。我是Python新手,请使用简单的语言。
编辑:
@Andras Deak:谢谢您的帖子,我尝试了您建议的,但使用了我定义的函数V(x,y)代替您的测试函数(fun=3*x^2-5*y^2)。代码如下,但报错了。
import numpy as np
import math 
import sympy
import matplotlib.pyplot as plt

def V(x,y):
    t=[]
    for k in range (1,3): 
        for l in range (1,3):
            t.append(0.000001*np.sin(2*math.pi*k*0.5)/((4*(math.pi)**2)* (k**2+l**2)))  
            term = t* np.sin(2 * math.pi * k * x/0.004) * np.cos(2 * math.pi * l * y/0.004)
            return term 
    return term.sum()

x,y=sympy.symbols('x y')
fun=V(x,y)
gradfun=[sympy.diff(fun,var) for var in (x,y)]
numgradfun=sympy.lambdify([x,y],gradfun)

X,Y=np.meshgrid(np.arange(-10,11),np.arange(-10,11))
graddat=numgradfun(X,Y)
plt.figure()
plt.quiver(X,Y,graddat[0],graddat[1])
plt.show()

AttributeError: 'Mul' object has no attribute 'sin'

假设我移除了"sin",会出现另一个错误:
TypeError: can't multiply sequence by non-int of type 'Mul'

我阅读了 sympy 的教程,其中提到 "符号计算系统如 SymPy 的真正优势在于能够以符号形式进行各种计算"。我理解这一点,但不明白为什么我无法把 x 和 y 符号与浮点数相乘。

有什么解决方法吗? :( 请帮帮我!

更新

@Andras Deak:我想简化一下内容,因此从 V(x,y)和 Cn * Dm 的原始公式中删除了许多常量。正如你指出的,这导致 sin 函数总是返回0(我刚注意到)。对此表示歉意。当我详细阅读您的评论后,我将在今天稍后更新帖子。非常感谢!

更新 2

我更改了电压表达式中的系数,结果如下:

plot

看起来不错,但箭头指向相反的方向(它们应该从红色点出发进入蓝色点)。您知道我怎么能改变那个吗?如果可能的话,请告诉我如何增加箭头的大小?我尝试了另一个主题中建议的方法 (Computing and drawing vector fields):

skip = (slice(None, None, 3), slice(None, None, 3))

这个绘图仅显示每三个箭头,并且matplotlib会自动缩放,但这对我没有用(当我添加此选项时,无论我输入的数字是多少,都没有任何反应)。非常感谢你的帮助!

那么问题出在哪里呢?是梯度计算,还是使用2D显示器表示4D数据的表现形式? - Andrey Nasonov
我知道如何在Python中绘制二维函数。但是我不知道如何计算和绘制矢量函数,即标量函数的梯度(因此,grad(V)= dV / dx * ex + dV / dy * ey,其中ex和ey是正交向量)。 - dota
我添加了 [tag:python] 和 [tag:numpy] 标签,下次请确保包含这些标签。如果您不同意,请随时更改/还原,这样做似乎更快。现在我想:可能应该更多地使用 sympy,而不是 numpy... 如果您找出如何计算梯度,这个 可以帮助您使用该函数进行绘图。 - Andras Deak -- Слава Україні
Python中的自动微分怎么样? 这是一个看起来不错的包 https://pypi.python.org/pypi/ad/ - Severin Pappadeux
你最后发的链接让我有些困惑。那个主题并没有涉及到绘图(据我理解,他们只是谈论了数值评估)。 - dota
1个回答

6

这里提供了一个使用 sympynumpy 的解决方案。这是我第一次使用 sympy,所以其他人可能会想出更好、更优雅的解决方案。

import sympy

#define symbolic vars, function
x,y=sympy.symbols('x y')
fun=3*x**2-5*y**2

#take the gradient symbolically
gradfun=[sympy.diff(fun,var) for var in (x,y)]

#turn into a bivariate lambda for numpy
numgradfun=sympy.lambdify([x,y],gradfun)

现在你可以使用numgradfun(1,3)来计算在(x,y)==(1,3)处的梯度。然后可以将此函数用于绘图,你说过你可以做到这一点。
对于绘图,例如,您可以使用matplotlibquiver,如下所示:
import numpy as np
import matplotlib.pyplot as plt

X,Y=np.meshgrid(np.arange(-10,11),np.arange(-10,11))
graddat=numgradfun(X,Y)

plt.figure()
plt.quiver(X,Y,graddat[0],graddat[1])
plt.show()

enter image description here

更新

您添加了一个函数计算的规范。它包含依赖于xy的项的乘积,这似乎破坏了我的上述解决方案。我设法想出了一个新的解决方案以满足您的需求。然而,您的函数似乎没有太多意义。从您编辑的问题中:

t.append(0.000001*np.sin(2*math.pi*k*0.5)/((4*(math.pi)**2)* (k**2+l**2)))
term = t* np.sin(2 * math.pi * k * x/0.004) * np.cos(2 * math.pi * l * y/0.004)

另一方面,从您对此答案的相应评论中可以看出:
V(x,y)= [Cn * Dm * sin(2pinx)* cos(2pimy)]的总和; sum的范围是-10到10; Cn和Dm是系数,我计算出CkDl = sin(2pik)/(k ^ 2 + l ^ 2)(我在这里使用k和l作为n和m之和的索引之一)。
我有几个问题:前因子中的sin(2*pi*k)和sin(2*pi*k / 2)(竞争版本中的两个版本)始终对于整数k都为零,在每个(x,y)处给出一个恒定的零V。此外,在您的代码中,三角函数中缺少神奇的频率因子。如果您将x乘以4e-3,则会显着更改函数的空间依赖性(通过将波长大约乘以一千倍)。因此,您确实应该决定您的函数是什么。
因此,这里是一个解决方案,其中我假设:

V(x,y)=sum_{k,l = 1 to 10} C_{k,l} * sin(2*pi*k*x)*cos(2*pi*l*y),其中
C_{k,l}=sin(2*pi*k/4)/((4*pi^2)*(k^2+l^2))*1e-6

这是函数的各个版本的组合,修改了前置因子中的sin(2*pi*k/4),以便获得非零函数。我期望你能够根据实际需求调整数值因子,当你找到正确的数学模型后。

所以这就是完整的代码:

import sympy as sp
import numpy as np
import matplotlib.pyplot as plt

def CD(k,l):
    #return sp.sin(2*sp.pi*k/2)/((4*sp.pi**2)*(k**2+l**2))*1e-6
    return sp.sin(2*sp.pi*k/4)/((4*sp.pi**2)*(k**2+l**2))*1e-6

def Vkl(x,y,k,l):
    return CD(k,l)*sp.sin(2*sp.pi*k*x)*sp.cos(2*sp.pi*l*y)

def V(x,y,kmax,lmax):
    k,l=sp.symbols('k l',integers=True)
    return sp.summation(Vkl(x,y,k,l),(k,1,kmax),(l,1,lmax))


#define symbolic vars, function
kmax=10
lmax=10
x,y=sp.symbols('x y')
fun=V(x,y,kmax,lmax)

#take the gradient symbolically
gradfun=[sp.diff(fun,var) for var in (x,y)]

#turn into bivariate lambda for numpy
numgradfun=sp.lambdify([x,y],gradfun,'numpy')
numfun=sp.lambdify([x,y],fun,'numpy')

#plot
X,Y=np.meshgrid(np.linspace(-10,10,51),np.linspace(-10,10,51))
graddat=numgradfun(X,Y)
fundat=numfun(X,Y)

hf=plt.figure()
hc=plt.contourf(X,Y,fundat,np.linspace(fundat.min(),fundat.max(),25))
plt.quiver(X,Y,graddat[0],graddat[1])
plt.colorbar(hc)
plt.show()

为了更好地展示,我使用了一些辅助函数来定义您的V(x,y)函数。 我将求和截止值作为文字参数kmaxlmax留下:在您的代码中,这些值为3,在您的注释中,它们被设置为10,实际上应该是无限。

梯度与以前的方式相同,但是当使用lambdify转换为numpy函数时,必须设置一个附加的字符串参数'numpy'。 这将允许生成的numpy lambda接受数组输入(基本上它将使用np.sin而不是math.sincos也是如此)。

我还将网格的定义从array更改为np.linspace:这通常更方便。 由于您的函数在整数网格点几乎是常数,因此我为绘图创建了更密集的网格(在保持原始限制为(-10,10)的情况下使用了51个点)。

为了更清晰地说明,我包含了更多的图形:一个 contourf 来显示函数的值(等高线应该始终与梯度向量正交),以及一个 colorbar 来指示函数的值。这是结果:

enter image description here

这篇文章的结构显然不是最好的,但我并不想过多偏离您的规定。这张图中的箭头实际上几乎看不见,但正如您所看到的(也从V的定义中可以看出),您的函数是周期性的,因此如果您使用较小的限制和更少的网格点绘制相同的东西,您将会看到更多的特征和更大的箭头。

为什么要使用numgradfun(1,3)?我想在每个x和y点上得到梯度。我知道如何绘制标量函数,但是我不知道如何绘制带有小箭头的向量函数。 - dota
你是说,你计算并绘制了在范围从-10到11的每个x和y中grad(3x^2-5y^2)? - dota
在区间[-10,11)上,即在11的一侧开放(否则:是)。我强烈建议查看所涉及方法的帮助文档;最好不要使用自己不理解的代码。我之所以这么说是因为range(1,3)非常基础,它会给你返回[1,2] - Andras Deak -- Слава Україні
@AndrasDeak 没错,而且同时学习和回答的急切心情,真是太棒了! - Divakar
@dota,如果你能明确地说明你的函数V(x,y)在数学上应该是什么样子,我可能会在有时间的时候尝试为你解决它。 - Andras Deak -- Слава Україні
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接