Python 如何在matplotlib中绘制由二次方程定义的椭球体的三维绘图?

Python 如何在matplotlib中绘制由二次方程定义的椭球体的三维绘图?,python,matplotlib,3d,ellipse,quadratic-curve,Python,Matplotlib,3d,Ellipse,Quadratic Curve,我有一个椭球体的一般公式: A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2 = 0 其中A、B、C、D、E、F、G是常数因子 如何在matplotlib中将此方程式绘制为3D绘图?(最好是线框。) 我看到了这一点,但它是参数形式的,我不知道如何在这段代码中放置z坐标。有没有一种方法可以在不使用参数化形式的情况下保持通用形式来绘制此图 我开始把它放在某种代码中,比如: 从mpl_工具包导入mplot3d %matplotlib笔记本 将numpy

我有一个椭球体的一般公式:

A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2 = 0
其中A、B、C、D、E、F、G是常数因子

如何在matplotlib中将此方程式绘制为3D绘图?(最好是线框。)

我看到了这一点,但它是参数形式的,我不知道如何在这段代码中放置z坐标。有没有一种方法可以在不使用参数化形式的情况下保持通用形式来绘制此图

我开始把它放在某种代码中,比如:

从mpl_工具包导入mplot3d
%matplotlib笔记本
将numpy作为np导入
将matplotlib.pyplot作为plt导入
定义f(x,y):
返回((A*x**2+C*y**2+D*x+E*y+B*x*y+F))
定义f(z):
返回G*z**2
x=np.linspace(-2200185018030)
y=np.linspace(-100,60,30)
z=np.linspace(-100,60,30)
十、 Y,Z=np.网格(X,Y,Z)
图=plt.图()
ax=图添加_子图(111,投影='3d')
X.plot_线框(X,Y,Z,rstride=10,cstride=10)
ax.set_xlabel('x')
ax.set_ylabel('y'))
ax.setzlabel('z');
我得到了这个错误:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-1-95b1296ae6a4> in <module>()
     18 fig = plt.figure()
     19 ax = fig.add_subplot(111, projection='3d')
---> 20 ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
     21 ax.set_xlabel('x')
     22 ax.set_ylabel('y')

C:\Program Files (x86)\Microsoft Visual Studio\Shared\Anaconda3_64\lib\site-packages\mpl_toolkits\mplot3d\axes3d.py in plot_wireframe(self, X, Y, Z, *args, **kwargs)
   1847         had_data = self.has_data()
   1848         if Z.ndim != 2:
-> 1849             raise ValueError("Argument Z must be 2-dimensional.")
   1850         # FIXME: Support masked arrays
   1851         X, Y, Z = np.broadcast_arrays(X, Y, Z)

ValueError: Argument Z must be 2-dimensional.
---------------------------------------------------------------------------
ValueError回溯(最近一次调用上次)
在()
18图=plt.图()
19 ax=图添加_子图(111,投影='3d')
--->20 ax.plot_线框(X,Y,Z,rstride=10,cstride=10)
21 ax.集合标签('x')
22 ax.set_ylabel('y')
C:\Program Files(x86)\Microsoft Visual Studio\Shared\Anaconda3\u 64\lib\site packages\mpl\u工具包\mplot3d\axes3d.py在plot\u线框中(self、X、Y、Z、*args、**kwargs)
1847 had_data=self.has_data()
1848如果Z.ndim!=2:
->1849 raise VALUETERROR(“参数Z必须是二维的。”)
1850#FIXME:支持屏蔽阵列
1851 X,Y,Z=np.广播_数组(X,Y,Z)
ValueError:参数Z必须是二维的。

旁注,但您得到的并不是三维椭球体的最一般方程。你的方程式可以改写为

A*x**2 + C*y**2 + D*x + E*y + B*x*y = - G*z**2 - F,
这意味着,实际上,对于
z
的每个值,可以得到不同级别的二维椭圆,并且切片相对于
z=0
平面对称。这显示了椭球是如何不一般的,它有助于检查结果,以确保我们得到的是有意义的

假设我们取一个一般点
r0=[x0,y0,z0]
,那么

r0 @ M @ r0 + b0 @ r0 + c0 == 0
在哪里

M=[ab/20
B/2 C 0
0克],
b0=[D,E,0],
c0=F
在哪里

你可以把你的函数和,但那将是次优的:你将需要一个网格近似为你的函数,这是非常昂贵的做足够的分辨率,你必须为这个采样明智地选择域

相反,您可以对您的数据执行一个测试,以概括您自己链接的数据

第一步是将
M
对角化为
M=V@D@V.T
,其中
D
为。因为它是一个实对称矩阵,所以这总是可能的,
V
是。那么我们有

r0 @ V @ D @ V.T @ r0 + b0 @ r0 + c0 == 0
我们可以重新组合为

(V.T @ r0) @ D @ (V.T @ r0) + b0 @ V @ (V.T @ r0) + c0 == 0
这激发了辅助坐标r1=V.T@r0和向量b1=b0@V的定义,我们得到

r1 @ D @ r1 + b1 @ r1 + c0 == 0.
由于
D
是一个对称矩阵,其特征值
d1、d2、d3
在其对角线上,因此上述为等式

d1 * x1**2 + d2 * x2**2 + d3 * x3**3 + b11 * x1 + b12 * x2 + b13 * x3 + c0 == 0
其中
r1=[x1,x2,x3]
b1=[b11,b12,b13]

剩下的是从
r1
切换到
r2
,这样我们就删除了线性项:

d1 * (x1 + b11/(2*d1))**2 + d2 * (x2 + b12/(2*d2))**2 + d3 * (x3 + b13/(2*d3))**2 - b11**2/(4*d1) - b12**2/(4*d2) - b13**2/(4*d3) + c0 == 0
所以我们定义

r2 = [x2, y2, z2]
x2 = x1 + b11/(2*d1)
y2 = y1 + b12/(2*d2)
z2 = z1 + b13/(2*d3)
c2 = b11**2/(4*d1) b12**2/(4*d2) b13**2/(4*d3) - c0.
对于这些,我们终于有了

d1 * x2**2 + d2 * y2**2 + d3 * z2**2 == c2,
d1/c2 * x2**2 + d2/c2 * y2**2 + d3/c2 * z2**2 == 1
这是二阶曲面的标准形式。为了使其有意义地对应于椭球体,我们必须确保
d1
d2
d3
c2
都是严格正的。如果保证了这一点,则标准形式的半长轴是
sqrt(c2/d1)
sqrt(c2/d2)
sqrt(c2/d3)

下面是我们要做的:

  • 确保参数对应于椭球体
  • 为极角和方位角生成θ和φ网格
  • 计算转换后的坐标
    [x2,y2,z2]
  • 将它们移回(通过
    r2-r1
    )以获得
    [x1,y1,z1]
  • 通过
    V
    将坐标变换回来,以获得
    r0
    ,即我们感兴趣的实际
    [x,y,z]
    坐标
  • 以下是我将如何实现这一点:

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    
    def get_transforms(A, B, C, D, E, F, G): 
        """ Get transformation matrix and shift for a 3d ellipsoid 
    
        Assume A*x**2 + C*y**2 + D*x + E*y + B*x*y + F + G*z**2 = 0, 
        use principal axis transformation and verify that the inputs 
        correspond to an ellipsoid. 
    
        Returns: (d, V, s) tuple of arrays 
            d: shape (3,) of semi-major axes in the canonical form 
               (X/d1)**2 + (Y/d2)**2 + (Z/d3)**2 = 1 
            V: shape (3,3) of the eigensystem 
            s: shape (3,) shift from the linear terms 
        """ 
    
        # construct original matrix 
        M = np.array([[A, B/2, 0], 
                      [B/2, C, 0], 
                      [0, 0, G]]) 
        # construct original linear coefficient vector 
        b0 = np.array([D, E, 0]) 
        # constant term 
        c0 = F 
    
        # compute eigensystem 
        D, V = np.linalg.eig(M) 
        if (D <= 0).any(): 
            raise ValueError("Parameter matrix is not positive definite!") 
    
        # transform the shift 
        b1 = b0 @ V 
    
        # compute the final shift vector 
        s = b1 / (2 * D) 
    
        # compute the final constant term, also has to be positive 
        c2 = (b1**2 / (4 * D)).sum() - c0 
        if c2 <= 0: 
            print(b1, D, c0, c2) 
            raise ValueError("Constant in the canonical form is not positive!")
    
        # compute the semi-major axes 
        d = np.sqrt(c2 / D) 
    
        return d, V, s 
    
    def get_ellipsoid_coordinates(A, B, C, D, E, F, G, n_theta=20, n_phi=40): 
        """Compute coordinates of an ellipsoid on an ellipsoidal grid 
    
        Returns: x, y, z arrays of shape (n_theta, n_phi) 
        """ 
    
        # get canonical grid 
        theta,phi = np.mgrid[0:np.pi:n_theta*1j, 0:2*np.pi:n_phi*1j] 
        r2 = np.array([np.sin(theta) * np.cos(phi), 
                       np.sin(theta) * np.sin(phi), 
                       np.cos(theta)]) # shape (3, n_theta, n_phi) 
    
        # get transformation data 
        d, V, s = get_transforms(A, B, C, D, E, F, G)  # could be *args I guess 
    
        # shift and transform back the coordinates 
        r1 = d[:,None,None]*r2 - s[:,None,None]  # broadcast along first of three axes
        r0 = (V @ r1.reshape(3, -1)).reshape(r1.shape)  # shape (3, n_theta, n_phi) 
    
        return r0  # unpackable to x, y, z of shape (n_theta, n_phi)
    
    从这里开始的实际绘图是微不足道的。使用以保持轴相等:

    # create 3d axes
    fig = plt.figure() 
    ax = fig.add_subplot(111, projection='3d')
    
    # plot the data
    ax.plot_wireframe(x, y, z)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    
    # scaling hack
    bbox_min = np.min([x, y, z])
    bbox_max = np.max([x, y, z])
    ax.auto_scale_xyz([bbox_min, bbox_max], [bbox_min, bbox_max], [bbox_min, bbox_max])
    
    plt.show()
    
    结果如下:

    旋转它可以很好地看到,曲面确实是相对于
    z=0
    平面的反射对称,这从方程中可以明显看出

    您可以将
    n_theta
    n_phi
    关键字参数更改为函数,以生成具有不同网格的网格。有趣的是,您可以获取单位球体上的任何分散点,并将其插入函数
    get_椭球体_坐标
    中的
    r2
    的定义中(只要此数组的第一维度大小为3),并且输出坐标将具有相同的形状,但它们将被转换到实际的椭球体上

    您还可以使用其他库来可视化曲面,例如mayavi,您可以在其中绘制我们刚刚计算的曲面,或者绘制其中内置的曲面

    # create 3d axes
    fig = plt.figure() 
    ax = fig.add_subplot(111, projection='3d')
    
    # plot the data
    ax.plot_wireframe(x, y, z)
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    
    # scaling hack
    bbox_min = np.min([x, y, z])
    bbox_max = np.max([x, y, z])
    ax.auto_scale_xyz([bbox_min, bbox_max], [bbox_min, bbox_max], [bbox_min, bbox_max])
    
    plt.show()