简体   繁体   中英

Plot 3D isosurface in python

I am trying to plot the following function in python using plotly or matplotlib for a given value of omega:

omega = (1/(12*np.pi*r**3)*((3*np.cos(THETA)**2-1)+1.5*np.sin(THETA)**2*np.cos(2*PHI)))

To do this I specify the value of omega , calculate r and then convert from polar to cartesian coordinates.

import numpy as np
import plotly.graph_objects as go

omega = 10
theta, phi = np.linspace(0,2*np.pi, 400), np.linspace(0,np.pi, 400)
THETA,PHI = np.meshgrid(theta,phi)

#Calculate R for a given value of omega
R1 = (1/(12*np.pi*omega)*((3*np.cos(THETA)**2-1)+1.5**np.sin(THETA)**2*np.cos(2*PHI)))
R = np.sign(R1) * (np.abs(R1))**(1/3)

#Convert to cartesians
X = R * np.sin(PHI) * np.cos(THETA)
Y = R * np.sin(PHI) * np.sin(THETA)
Z = R * np.cos(PHI)

#Plot isosurface plot
fig = go.Figure(data=[go.Surface(z=Z, x=X, y=Y)])
fig.show()

However, by doing this I lose the information about the positive and negative lobes, ie here both are plotted.

在此处输入图像描述

I can get round this by setting negative values of omega to NaN . This switches off the negative lobes but results in rendering artefacts for the graphs.

import numpy as np
import plotly.graph_objects as go

omega = 10
theta, phi = np.linspace(0,2*np.pi, 400), np.linspace(0,np.pi, 400)
THETA,PHI = np.meshgrid(theta,phi)

#Calculate R for a given value of omega
R1 = (1/(12*np.pi*omega)*((3*np.cos(THETA)**2-1)+1.5**np.sin(THETA)**2*np.cos(2*PHI)))
R = np.sign(R1) * (np.abs(R1))**(1/3)

#Remove negative lobes
R[R1 < 0.] = np.NaN

#Convert to cartesians
X = R * np.sin(PHI) * np.cos(THETA)
Y = R * np.sin(PHI) * np.sin(THETA)
Z = R * np.cos(PHI)

#Plot isosurface plot
fig = go.Figure(data=[go.Surface(z=Z, x=X, y=Y)])
fig.show()

在此处输入图像描述

I'm not sure how to overcome this issue - if I increase the number of points for THETA and PHI , the graph renders very slowly, and it's still not possible to increase the number of points sufficiently to remove the artefacts completely. Ideally I would pass r , theta and phi values to the plotting function and plot an isosurface at a given value of omega , but this is possible only in cartesians. Converting the whole function to cartesians would lead to f(x,y,z,r ) which I also can't plot.

You will find here a built-in way to plot an isosurface with pyvista .

Or you can use my implementation of the marching cubes (original code from the R package misc3d ):

from math import cos, sin
def f(ρ, θ, ϕ):
    return (1/(12*np.pi*ρ**3)*((3*cos(θ)**2-1)+1.5*sin(θ)**2*cos(2*ϕ)))

ρmax = 0.3
θmax = 4*np.pi
ϕmax = 2*np.pi 
nρ = 200
nθ = 200
nϕ = 200

mesh = marchingCubes(
    f, 10, 0.001, ρmax, 0, θmax, 0, ϕmax, nρ, nθ, nϕ
)
print(mesh)

def sph2cart(sph):
    ρ = sph[0]
    θ = sph[1]
    ϕ = sph[2]
    return [
        ρ * cos(θ) * sin(ϕ),
        ρ * sin(θ) * sin(ϕ),
        ρ * cos(ϕ)
    ]
mesh.points = np.apply_along_axis(sph2cart, 1, mesh.points)

mesh.plot(smooth_shading=True, specular=5, color="purple")

I found that theta must go to 4pi and phi must go to 2pi, otherwise the surface is not closed. Here is the result:

在此处输入图像描述

Observe the hole at the center. It corresponds to the minimal value of rho (one can't set it to 0 otherwise there would be a division by zero).

Now, if you want to get this surface in plotly , you can extract the vertices and the faces (triangles) from the mesh object, and use Mesh3d in plotly:

vertices = mesh.points
triangles = mesh.faces.reshape(-1, 4)

import plotly.graph_objects as go
import plotly.io as pio


fig = go.Figure(data=[
    go.Mesh3d(
        x=vertices[:,0],
        y=vertices[:,1],
        z=vertices[:,2],
        colorbar_title='z',
        colorscale=[[0, 'gold'],
                    [0.5, 'mediumturquoise'],
                    [1, 'magenta']],
        # i, j and k give the vertices of triangles
        i=triangles[:,1],
        j=triangles[:,2],
        k=triangles[:,3],
        name='y',
        showscale=True
    )
])

#fig.show()

pio.write_html(fig, file='SO.html', auto_open=True)

在此处输入图像描述

To get colors:

distances = np.linalg.norm(vertices, axis=1)
distances = distances / (distances.max())

fig = go.Figure(data=[
    go.Mesh3d(
        x=vertices[:,0],
        y=vertices[:,1],
        z=vertices[:,2],
        colorbar_title='z',
        colorscale=[[0, 'gold'],
                    [0.5, 'mediumturquoise'],
                    [1, 'magenta']],
        # Intensity of each vertex, which will be interpolated and color-coded
        intensity=distances,
        # i, j and k give the vertices of triangles
        i=triangles[:,1],
        j=triangles[:,2],
        k=triangles[:,3],
        name='y',
        showscale=True
    )
])

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM