Python 2D circular surface in 3D - python

I am trying to generate the top/bottom of a cylindrical surface. I was able to obtain the lateral surface here: Generating a Cylindrical Surface with np.outer. I would like to use np.outer again for consistency. I thought I understood the answers in the link however if I understood correctly then the following should work:
R = 5
h = 5
u = np.linspace(0, 2*np.pi, 100)
x = R * np.outer(np.ones(np.size(u)), np.cos(u))
y = R * np.outer(np.ones(np.size(u)), np.sin(u))
z = h * np.outer(np.ones(np.size(u)), np.ones(np.size(u)))
however in my plots, no surface is generated. Am I still not using np.outer correctly? Why is no surface generated?

There is no visible disk because all the points which you are creating have exactly the same distance to the center and surface which spans between "inner circle" and "outer circle" is infinitely thin. In order to see the disk, radius needs to vary between 0 and your desired value (5 in the example).
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
R = np.linspace(0, 5, 100)
h = 5
u = np.linspace(0, 2*np.pi, 100)
x = np.outer(R, np.cos(u))
y = np.outer(R, np.sin(u))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x,y,h) # z in case of disk which is parallel to XY plane is constant and you can directly use h
fig.show()

Related

Shrinking ellipsoid in matplotlib

I am new to animations with matplotlib, and I am trying to animate a shrinking ellipsoid.
Specifically, I want to animate an ellipsoid that shrinks its axes proportionally. (Mathematically, I'm looking for a shrinking factor of e^(-t) multiplied to each axis, where t is time.)
I have made a function of time t that outputs a static ellipsoid with the code below:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from mpl_toolkits.mplot3d import Axes3D
def param_surface(t):
fig = plt.figure(figsize = (10, 10))
ax = fig.add_subplot(111, projection='3d')
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = axis_a(4 * t / 50) * np.outer(np.cos(u), np.sin(v))
y = axis_a(4 * t / 50) * np.outer(np.sin(u), np.sin(v))
z = axis_b(4 * t / 50) * np.outer(np.ones(np.size(u)), np.cos(v))
return(ax.plot_surface(x, y, z, rstride = 4, cstride = 4))
I have seen animations (such as the one here: https://pythonmatplotlibtips.blogspot.com/2018/11/animation-3d-surface-plot-funcanimation-matplotlib.html) that allow you to animate 3D plots in which z is defined as a function of x, y. However, in the case of the shrinking ellipsoid, I need to use spherical coordinates, which complicates things.
Can someone explain what to add to my code to go from static to the desired shrinking animation?

Plot a 1D array on 3 radii in a polar heat map

I have a 3 1D arrays: radius, angle and temperature. Together they form a 2D temperature map of a ring.
The arrays take the form:
r = [0,0,0,1,1,1,2,2,2]
th = [0.,0.78539816,1.57079633,2.35619449,3.14159265,3.92699082,4.71238898,5.49778714,6.28318531]
z = [-1,2,5,2,4,-1,3,2,3]
I don't understand how I can make those z data fall on the right coordinates.
I can make it work, with random numbers, using the following simple code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig = plt.figure()
ax = Axes3D(fig)
rad = np.linspace(.2, 1, 4)
azm = np.linspace(0, 2 * np.pi, 9)
r, th = np.meshgrid(rad, azm)
z = np.random.rand(9,4) ** th * r
ax0 = plt.subplot(projection="polar")
im = plt.pcolormesh(th, r, z, cmap='bwr')
plt.plot(azm, r, color='k', ls='none')
plt.axis('off')
cbar = fig.colorbar(im)
ax0.set_title('3 radii polar heat map')
This is how my example code comes out
I ended splitting the list z into 3 lists and made a matrix out of them using z_mat = np.array([z1,z2,z3]). I took care that the lists for rad and azm contained one item more than z, which is a requirement for pcolormesh. After that I transposed the matrix to have the same dimensions as r and th using z_mat_trans = z_mat.transpose()

Matplotlib - contour and quiver plot in projected polar coordinates

I need to plot contour and quiver plots of scalar and vector fields defined on an uneven grid in (r,theta) coordinates.
As a minimal example of the problem I have, consider the contour plot of a Stream function for a magnetic dipole, contours of such a function are streamlines of the corresponeding vector field (in this case, the magnetic field).
The code below takes an uneven grid in (r,theta) coordinates, maps it to the cartesian plane and plots a contour plot of the stream function.
import numpy as np
import matplotlib.pyplot as plt
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
psi = np.zeros((N_r, N_theta))
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig, ax = plt.subplots()
# ax.plot(x,y,'b.') # plot grid points
ax.set_aspect('equal')
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
plt.show()
For some reason though, the plot I get doesn't look right:
If I interchange x and y in the contour function call, I get the desired result:
Same thing happens when I try to make a quiver plot of a vector field defined on the same grid and mapped to the x-y plane, except that interchanging x and y in the function call no longer works.
It seems like I made a stupid mistake somewhere but I can't figure out what it is.
If psi = m * np.sin(theta_matrix)**2 / r_matrix
then psi increases as theta goes from 0 to pi/2 and psi decreases as r increases.
So a contour line for psi should increase in r as theta increases. That results
in a curve that goes counterclockwise as it radiates out from the center. This is
consistent with the first plot you posted, and the result returned by the first version of your code with
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
An alternative way to confirm the plausibility of the result is to look at a surface plot of psi:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_aspect('equal')
ax.plot_surface(x, y, psi, rstride=8, cstride=8, alpha=0.3)
ax.contour(x, y, psi, colors='black',levels=contour_levels)
plt.show()

Setting edgecolor to match facecolor in trisurf

I am trying to use python3 and matplotlib (version 1.4.0) to plot a scalar function defined on the surface of a sphere. I would like to have faces distributed relatively evenly over the sphere, so I am not using a meshgrid. This has led me to use plot_trisurf to plot my function. I have tested it with a trivial scalar function, and am having the problem that there are rendering artefacts along the edges of the faces:
The code I used to create the plot is below:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as mtri
from scipy.spatial import ConvexHull
def points_on_sphere(N):
""" Generate N evenly distributed points on the unit sphere centered at
the origin. Uses the 'Golden Spiral'.
Code by Chris Colbert from the numpy-discussion list.
"""
phi = (1 + np.sqrt(5)) / 2 # the golden ratio
long_incr = 2*np.pi / phi # how much to increment the longitude
dz = 2.0 / float(N) # a unit sphere has diameter 2
bands = np.arange(N) # each band will have one point placed on it
z = bands * dz - 1 + (dz/2) # the height z of each band/point
r = np.sqrt(1 - z*z) # project onto xy-plane
az = bands * long_incr # azimuthal angle of point modulo 2 pi
x = r * np.cos(az)
y = r * np.sin(az)
return x, y, z
def average_g(triples):
return np.mean([triple[2] for triple in triples])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = points_on_sphere(2**12)
Triples = np.array(list(zip(X, Y, Z)))
hull = ConvexHull(Triples)
triangles = hull.simplices
colors = np.array([average_g([Triples[idx] for idx in triangle]) for
triangle in triangles])
collec = ax.plot_trisurf(mtri.Triangulation(X, Y, triangles),
Z, shade=False, cmap=plt.get_cmap('Blues'), array=colors,
edgecolors='none')
collec.autoscale()
plt.show()
This problem appears to have been discussed in this question, but I can't seem to figure out how to set the edgecolors to match the facecolors. The two things I've tried are setting edgecolors='face' and calling collec.set_edgecolors() with a variety of arguments, but those throw AttributeError: 'Poly3DCollection' object has no attribute '_facecolors2d'.
How am I supposed to set the edgecolor equal to the facecolor in a trisurf plot?
You can set antialiased argument of plot_trisurf() to False. Here is the result:

Plotting implicit equations in 3d

I'd like to plot implicit equation F(x,y,z) = 0 in 3D. Is it possible in Matplotlib?
You can trick matplotlib into plotting implicit equations in 3D. Just make a one-level contour plot of the equation for each z value within the desired limits. You can repeat the process along the y and z axes as well for a more solid-looking shape.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
def plot_implicit(fn, bbox=(-2.5,2.5)):
''' create a plot of an implicit function
fn ...implicit function (plot where fn==0)
bbox ..the x,y,and z limits of plotted interval'''
xmin, xmax, ymin, ymax, zmin, zmax = bbox*3
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
A = np.linspace(xmin, xmax, 100) # resolution of the contour
B = np.linspace(xmin, xmax, 15) # number of slices
A1,A2 = np.meshgrid(A,A) # grid on which the contour is plotted
for z in B: # plot contours in the XY plane
X,Y = A1,A2
Z = fn(X,Y,z)
cset = ax.contour(X, Y, Z+z, [z], zdir='z')
# [z] defines the only level to plot for this contour for this value of z
for y in B: # plot contours in the XZ plane
X,Z = A1,A2
Y = fn(X,y,Z)
cset = ax.contour(X, Y+y, Z, [y], zdir='y')
for x in B: # plot contours in the YZ plane
Y,Z = A1,A2
X = fn(x,Y,Z)
cset = ax.contour(X+x, Y, Z, [x], zdir='x')
# must set plot limits because the contour will likely extend
# way beyond the displayed level. Otherwise matplotlib extends the plot limits
# to encompass all values in the contour.
ax.set_zlim3d(zmin,zmax)
ax.set_xlim3d(xmin,xmax)
ax.set_ylim3d(ymin,ymax)
plt.show()
Here's the plot of the Goursat Tangle:
def goursat_tangle(x,y,z):
a,b,c = 0.0,-5.0,11.8
return x**4+y**4+z**4+a*(x**2+y**2+z**2)**2+b*(x**2+y**2+z**2)+c
plot_implicit(goursat_tangle)
You can make it easier to visualize by adding depth cues with creative colormapping:
Here's how the OP's plot looks:
def hyp_part1(x,y,z):
return -(x**2) - (y**2) + (z**2) - 1
plot_implicit(hyp_part1, bbox=(-100.,100.))
Bonus: You can use python to functionally combine these implicit functions:
def sphere(x,y,z):
return x**2 + y**2 + z**2 - 2.0**2
def translate(fn,x,y,z):
return lambda a,b,c: fn(x-a,y-b,z-c)
def union(*fns):
return lambda x,y,z: np.min(
[fn(x,y,z) for fn in fns], 0)
def intersect(*fns):
return lambda x,y,z: np.max(
[fn(x,y,z) for fn in fns], 0)
def subtract(fn1, fn2):
return intersect(fn1, lambda *args:-fn2(*args))
plot_implicit(union(sphere,translate(sphere, 1.,1.,1.)), (-2.,3.))
Update: I finally have found an easy way to render 3D implicit surface with matplotlib and scikit-image, see my other answer. I left this one for whom is interested in plotting parametric 3D surfaces.
Motivation
Late answer, I just needed to do the same and I found another way to do it at some extent. So I am sharing this another perspective.
This post does not answer: (1) How to plot any implicit function F(x,y,z)=0? But does answer: (2) How to plot parametric surfaces (not all implicit functions, but some of them) using mesh with matplotlib?
#Paul's method has the advantage to be non parametric, therefore we can plot almost anything we want using contour method on each axe, it fully addresses (1). But matplotlib cannot easily build a mesh from this method, so we cannot directly get a surface from it, instead we get plane curves in all directions. This is what motivated my answer, I wanted to address (2).
Rendering mesh
If we are able to parametrize (this may be hard or impossible), with at most 2 parameters, the surface we want to plot then we can plot it with matplotlib.plot_trisurf method.
That is, from an implicit equation F(x,y,z)=0, if we are able to get a parametric system S={x=f(u,v), y=g(u,v), z=h(u,v)} then we can plot it easily with matplotlib without having to resort to contour.
Then, rendering such a 3D surface boils down to:
# Render:
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap='jet', antialiased=True)
Where (x, y, z) are vectors (not meshgrid, see ravel) functionally computed from parameters (u, v) and triangles parameter is a Triangulation derived from (u,v) parameters to shoulder the mesh construction.
Imports
Required imports are:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
from matplotlib.tri import Triangulation
Some surfaces
Lets parametrize some surfaces...
Sphere
# Parameters:
theta = np.linspace(0, 2*np.pi, 20)
phi = np.linspace(0, np.pi, 20)
theta, phi = np.meshgrid(theta, phi)
rho = 1
# Parametrization:
x = np.ravel(rho*np.cos(theta)*np.sin(phi))
y = np.ravel(rho*np.sin(theta)*np.sin(phi))
z = np.ravel(rho*np.cos(phi))
# Triangulation:
tri = Triangulation(np.ravel(theta), np.ravel(phi))
Cone
theta = np.linspace(0, 2*np.pi, 20)
rho = np.linspace(-2, 2, 20)
theta, rho = np.meshgrid(theta, rho)
x = np.ravel(rho*np.cos(theta))
y = np.ravel(rho*np.sin(theta))
z = np.ravel(rho)
tri = Triangulation(np.ravel(theta), np.ravel(rho))
Torus
a, c = 1, 4
u = np.linspace(0, 2*np.pi, 20)
v = u.copy()
u, v = np.meshgrid(u, v)
x = np.ravel((c + a*np.cos(v))*np.cos(u))
y = np.ravel((c + a*np.cos(v))*np.sin(u))
z = np.ravel(a*np.sin(v))
tri = Triangulation(np.ravel(u), np.ravel(v))
Möbius Strip
u = np.linspace(0, 2*np.pi, 20)
v = np.linspace(-1, 1, 20)
u, v = np.meshgrid(u, v)
x = np.ravel((2 + (v/2)*np.cos(u/2))*np.cos(u))
y = np.ravel((2 + (v/2)*np.cos(u/2))*np.sin(u))
z = np.ravel(v/2*np.sin(u/2))
tri = Triangulation(np.ravel(u), np.ravel(v))
Limitation
Most of the time, Triangulation is required in order to coordinate mesh construction of plot_trisurf method, and this object only accepts two parameters, so we are limited to 2D parametric surfaces. It is unlikely we could represent the Goursat Tangle with this method.
Matplotlib expects a series of points; it will do the plotting if you can figure out how to render your equation.
Referring to Is it possible to plot implicit equations using Matplotlib? Mike Graham's answer suggests using scipy.optimize to numerically explore the implicit function.
There is an interesting gallery at http://xrt.wikidot.com/gallery:implicit showing a variety of raytraced implicit functions - if your equation matches one of these, it might give you a better idea what you are looking at.
Failing that, if you care to share the actual equation, maybe someone can suggest an easier approach.
As far as I know, it is not possible. You have to solve this equation numerically by yourself. Using scipy.optimize is a good idea. The simplest case is that you know the range of the surface that you want to plot, and just make a regular grid in x and y, and try to solve equation F(xi,yi,z)=0 for z, giving a starting point of z. Following is a very dirty code that might help you
from scipy import *
from scipy import optimize
xrange = (0,1)
yrange = (0,1)
density = 100
startz = 1
def F(x,y,z):
return x**2+y**2+z**2-10
x = linspace(xrange[0],xrange[1],density)
y = linspace(yrange[0],yrange[1],density)
points = []
for xi in x:
for yi in y:
g = lambda z:F(xi,yi,z)
res = optimize.fsolve(g, startz, full_output=1)
if res[2] == 1:
zi = res[0]
points.append([xi,yi,zi])
points = array(points)
Actually there is an easy way to plot implicit 3D surface with the scikit-image package. The key is the marching_cubes method.
import numpy as np
from skimage import measure
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
Then we compute the function over a 3D meshgrid, in this example we use the goursat_tangle method #Paul defined in its answer:
xl = np.linspace(-3, 3, 50)
X, Y, Z = np.meshgrid(xl, xl, xl)
F = goursat_tangle(X, Y, Z)
The magic is happening here with marching_cubes:
verts, faces, normals, values = measure.marching_cubes(F, 0, spacing=[np.diff(xl)[0]]*3)
verts -= 3
We just need to correct vertices coordinates as they are expressed in Voxel coordinates (hence scaling using spacing switch and the subsequent origin shift).
Finally it is just about rendering the iso-surface using tri_surface:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(verts[:, 0], verts[:, 1], faces, verts[:, 2], cmap='jet', lw=0)
Which returns:
Have you looked at mplot3d on matplotlib?
Finally, I did it (I updated my matplotlib to 1.0.1).
Here is code:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
def hyp_part1(x,y,z):
return -(x**2) - (y**2) + (z**2) - 1
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x_range = np.arange(-100,100,10)
y_range = np.arange(-100,100,10)
X,Y = np.meshgrid(x_range,y_range)
A = np.linspace(-100, 100, 15)
A1,A2 = np.meshgrid(A,A)
for z in A:
X,Y = A1, A2
Z = hyp_part1(X,Y,z)
ax.contour(X, Y, Z+z, [z], zdir='z')
for y in A:
X,Z= A1, A2
Y = hyp_part1(X,y,Z)
ax.contour(X, Y+y, Z, [y], zdir='y')
for x in A:
Y,Z = A1, A2
X = hyp_part1(x,Y,Z)
ax.contour(X+x, Y, Z, [x], zdir='x')
ax.set_zlim3d(-100,100)
ax.set_xlim3d(-100,100)
ax.set_ylim3d(-100,100)
Here is result:
Thank You, Paul!
MathGL (GPL plotting library) can plot it easily. Just create a data mesh with function values f[i,j,k] and use Surf3() function to make isosurface at value f[i,j,k]=0. See this sample.

Categories

Resources