numpy 在我的pyside/sympy/matplotlin函数绘图仪中难以绘制3D函数

abithluo  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(108)

我目前正在使用PySide6开发函数绘图仪GUI应用程序。该应用程序允许用户以字符串的形式输入数学函数,然后对其进行解析和绘制。虽然该应用程序在2D函数上运行良好,但在绘制3D函数时遇到了困难。
在我的实现中,我使用SymPy解析函数字符串并生成用于绘图的数据点。当我试图将数据重塑为Matplotlib中plot_surface函数的适当形状时,就会出现问题。
下面是演示该问题的相关代码片段的简化版本:

import re

import numpy as np
import sympy
from PySide6.QtWidgets import QMessageBox
from mpl_toolkits.mplot3d import Axes3D

class FunctionProcessor():
...
    def parse_3d_function(self):
        self.function_string = self.function_string.replace("^", "**")
        x, y = sympy.symbols('x y')
        function = sympy.sympify(self.function_string)

        x_data = np.linspace(self.x_range[0], self.x_range[1], 101)
        y_data = np.linspace(self.x_range[0], self.x_range[1], 101)

        x_data, y_data = np.meshgrid(x_data, y_data)
        z_data = np.array([function.subs({x: xi, y: yi}) for xi, yi in zip(x_data, y_data)])

        return x_data, y_data, z_data

个字符
我得到错误ValueError: Argument Z must be 2-dimensional.
我试图搜索文档,发现sympy有plot3d功能,但我需要的图形embdeed在GUI中,而不是在一个单独的窗口

wnrlj8wa

wnrlj8wa1#

1.为什么代码中有2个meshgrid

parse_3d_function返回3个数组,包括x_datay_data,它们是在此函数中通过以下方式获得的:

x_data = np.linspace(self.x_range[0], self.x_range[1], 101)
        y_data = np.linspace(self.x_range[0], self.x_range[1], 101)

        x_data, y_data = np.meshgrid(x_data, y_data)

字符串
然后你在plot_3d里面做的是

x_data, y_data, z_data = self.function_processor.parse_3d_function()
        x_data, y_data = meshgrid(x_data, y_data)


那么x_data, y_data是一个网格的网格?
这意味着一些东西,当然,但可能不是你想要的

2.这里没有zip

如果你想计算xy所有可能组合z,那么不要使用zip

z_data = np.array([function.subs({x: xi, y: yi}) for xi, yi in zip(x_data, y_data)])


可能也没有达到你的预期。这里的xiyi是x和y值的“行”。如果“function.subs”是矢量化的,那么就没有问题。它可以在一整行x和y上工作,并计算一行结果。但如果是这样,那么,为什么不直接调用function.subs({x:x_data, y:y_data})呢?如果它是矢量化的,那么它也应该工作。
另一方面,如果函数没有被向量化,也就是说,如果xy被认为只是标量,那么zip不会下降2步。它不会并行地对两个网格进行2嵌套循环迭代。
你得自己去。例如(仍然使用您的“纯Python复合列表”样式。但最好的方法应该是依靠矢量化。我们不知道你的function.sub是什么,但如果它来自sympify,它可能不是。
所以呢

z_data = np.array([[function.subs({x:x, y:y}) for x in x_data] for y in y_data])


其中x_datay_data是1d阵列(因此在此之前没有meshgrid)

3.无网格:广播

而且,顺便说一句,如果是矢量化的,那么它是meshgrid不必要使用的另一个例子。广播就足够了。

x_data = np.linspace(self.x_range[0], self.x_range[1], 101)
y_data = np.linspace(self.x_range[0], self.x_range[1], 101)
z_data = fn(x_data[None,:], y_data[:,None])
# ...
ax.plot_surface(x_data[None,:], y_data[:,None], z_data)


meshgrid在这里所做的就是浪费好的内存。另外,function.subs内部可能有一些优化,因为xy本质上是两个一维数组,而不是二维数组。例如,如果你正在计算x²+y²,那么x_data[None,:]**2+y_data[:,None]**2是2N **2运算(N对x,N对y),然后是N²加法。而x,y=np.meshgrid(x_data,y_data); x**2+y**2是2N² **2操作(N²用于x,N²用于y)和N²加法。不算meshgrid本身的成本。
所以,广播时没有网格就足够了。
在这里,你没有任何向量化的函数。但是正如您所看到的(本答案的第2节),计算z的网格的方法也不使用meshgrid

x_data = np.linspace(self.x_range[0], self.x_range[1], 101)
y_data = np.linspace(self.x_range[0], self.x_range[1], 101)
z_data = np.array([[function.subs({x:x, y:y}) for x in x_data] for y in y_data])
# ...
ax.plot_surface(x_data[None,:], y_data[:,None], z_data)

4.`function.subs矢量化

你可以通过某种方式将这个函数向量化,这样它的行为就更像一个经典的numpy函数

fn=np.vectorized(lambda xi,yi:function.subs({x:xi,y:yi}))
x_data = np.linspace(self.x_range[0], self.x_range[1], 101)
y_data = np.linspace(self.x_range[0], self.x_range[1], 101)
z_data = fn(x_data[None,:], y_data[:,None])
ax.plot_surface(x_data[None,:], y_data[:,None], z_data)


它不能提供有意义的加速。Is不是真正的向量化(function.subs仍然被调用101²次)。但它有一个好处,让你回到一个非常经典的情况:我有x和y的向量,我想通过numpy运算来计算所有可能的x和y的z。这是由一个

ax.plot_surface(x_data[None,:], y_data[:,None], numpyOp(x_data[None,:], y_data[:,None]))


这就是我做的,一旦我向量化你的函数。
(Well,在现实中,如果我相信我经常在这里看到的,它是“经典”完成与

xx,yy=np.meshgrid(x_data,y_data)
ax.plot_surface(xx,yy,numpyOp(xx,yy))


但是,正如我所说,这是一个错误。就像你看到的meshgrid的95%的使用一样,这是对内存和计算时间的浪费,可以通过广播更好地完成)

相关问题