Let the distance between the centers of two identical ellipses with semi-axes $a, b$ be $a+b$.
The first ellipse rotates around its center with a constant angular velocity $w$.
The second ellipse rotates so that it is in contact with the first ellipse (i.e. there is a one point of contact between the two ellipses).
Calculate the speed of the second ellipse depending on the rotation angle of the first ellipse. Find the locus of the points of contact of two ellipses.
To find the contact line, to solve the system of three equations
$$x_1(t,p)=x_2(s,q)$$
$$y_1(t,p)=y_2(s,q)$$
$$\frac{x_1(t,p)_t}{x_2(s,q)_s}=\frac{y_1(t,p)_t}{y_2(s,q)_s}$$
Here $$p$$ and $$q$$ are the rotation angles of the ellipses. The system of equations is solved using standard Python tools.
The locus contact points are two lines - one in the form of an infinity
symbol (1 line) and the second in the form of a line close to an ellipse (2 line), passes through points $(a,0)$, $(b,0)$.
For each value $p$ of the rotation angle of the first ellipse, the corresponding value of the rotation angle $q$ for the second ellipse is sought.
I find set of $p,q$ - but this set mixture values for 1 line and values for 2 line. How to sort this set in two sets for values for 1 line and values for 2 line ? This sets give correct animation this problem by Python.
# two equals ellipses (a*cos(t),b*sin(t)), (a*cos(s)+a+b,b*sin(s))
# contact rotation about centers (0,0) and (a+b,0)
# front and rear contact - two variants
# the first ellipse has a constant angular velocity of rotation
import scipy.optimize
import numpy as np
import math as m
import matplotlib.pyplot as plt
import time
fig = plt.figure()
plt.xlabel('X')
plt.ylabel('Y')
ax = fig.gca()
plt.axis('equal')
P=[]
def xx(p,t,sd):
return a*np.cos(t)*np.cos(p)+b*np.sin(t)*np.sin(p)+sd
def yy(p,t,sd):
return -a*np.cos(t)*np.sin(p)+b*np.sin(t)*np.cos(p)+sd
def f(y):
t,q,s = y[:3]
eq1=yy(p,t,0)-yy(q,s,0)
eq2=xx(p,t,0)-xx(q,s,a+b)
eq3=(a*m.sin(p)*m.sin(t) + b*m.cos(p)*m.cos(t))*(-a*m.sin(s)*m.cos(q) + b*m.sin(q)*m.cos(s))-(
(a*m.sin(q)*m.sin(s) + b*m.cos(q)*m.cos(s))*(-a*m.sin(t)*m.cos(p) + b*m.sin(p)*m.cos(t)))
return(eq1,eq2,eq3)
w, a, b, h, N =0, 3, 2, 2*m.pi, 36
for k in range (N):
p=h/N*k
x0 = np.array([3.3,0.3,-p+m.pi/6])
sol = scipy.optimize.root(f, x0, method='lm')
t,q,s=sol.x
e1,e2,e3=f(sol.x)
if abs(e1)+abs(e2)+abs(e3)<10**(-7):
w=w+1
q=m.fmod(q,2*m.pi)
#print (p,q)
P.append([p,q])
x, y =xx(p,t,0), yy(p,t,0)
plt.plot(x,y,'ro',markersize=1.)
ksi = np.arange(0, 2*np.pi, 0.01)
#plt.plot(np.sin(ksi)/2+(a+b)/2, 1*np.cos(ksi), lw=1)
for pq in P:
p, q =pq[0],pq[1]
plt.plot(xx(q,ksi,a+b), yy(q,ksi,0), lw=1)
plt.plot(xx(p,ksi,0), yy(p,ksi,0), lw=1)
plt.plot(0, 0, 'ro',markersize=2.)
plt.plot(a+b, 0, 'ro',markersize=2.)
print(w)
plt.grid()
plt.show()
#time.sleep(1)
#plt.plot(xx(q,ksi,a+b), yy(q,ksi,0), lw=0)
#plt.plot(xx(p,ksi,0), yy(p,ksi,0), lw=0)
Example animation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
N=20
N1=120
a=3
b=2
t = np.arange(0, 2*np.pi+4*np.pi/N1, 2*np.pi/N1)
x = a*np.cos(t)
y = b*np.sin(t)
fig, ax = plt.subplots()
plt.axis('equal')
line1, = ax.plot(x, y, color = "r")
line2, = ax.plot(y+2*a+b, x, color = "g")
def update(t, x, y, line1, line2):
line2.set_data(x*np.cos(t/N)+y*np.sin(t/N), -x*np.sin(t/N)+y*np.cos(t/N))
line1.set_data(y*np.cos(-t/N)+(x)*np.sin(-t/N)+a+b , -y*np.sin(-t/N)+x*np.cos(-t/N))
return [line1,line2]
ani = animation.FuncAnimation(fig, update, int(2*np.pi*N), fargs=[ x, y, line1, line2],
interval=295, blit=True)
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
Related
I have plotted my 8 corner points with center of a cuboid as in figure .
I have tried with the scatter but now i want the surface plot connecting these 8 points.
when i have tried with the surface plot i am unable to attend to that. Can you please suggest any solution for that
l = 0.3
w = 0.4
h = 0.1
center =
[2.10737, -0.100085, 0.716869]
F=
[[array([[1.]]) array([[-0.001]]) array([[-0.017]])]
[array([[0.]]) array([[-0.999]]) array([[0.037]])]
[array([[0.017]]) array([[0.037]]) array([[0.999]])]]
def cuboid(center, size):
ox, oy, oz = center
l, w, h = size
ax = fig.gca(projection='3d') ##plot the project cuboid
X=[ox-l/2,ox-l/2,ox-l/2,ox-l/2,ox+l/2,ox+l/2,ox+l/2,ox+l/2]
Y=[oy+w/2,oy-w/2,oy-w/2,oy+w/2,oy+w/2,oy-w/2,oy-w/2,oy+w/2]
Z=[oz-h/2,oz-h/2,oz+h/2,oz+h/2,oz+h/2,oz+h/2,oz-h/2,oz-h/2]
X_new = ([])
Y_new = ([])
Z_new = ([])
for i in range(0,8):
c=np.matrix([[X[i]],
[Y[i]],
[Z[i]]])
u=F*c
X_new = np.append(X_new, u.item(0))
Y_new = np.append(Y_new, u.item(1))
Z_new = np.append(Z_new, u.item(2))
ax.scatter(X_new,Y_new,Z_new,c='darkred',marker='o') #the plot of points after rotated
ax.scatter(ox,oy,oz,c='crimson',marker='o') #the previous plot of points before rotated
## Add title
plt.title('Plot_for_PSM', fontsize=20)
plt.gca().invert_yaxis()
##labelling the axes
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
Here a solution.
###Added import
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
###Addded size and center in this format in order to manipulate them easily
size = [0.3, 0.4, 0.1]
center = [2.10737, -0.100085, 0.716869]
###This numpy vector will be used to store the position of the sides
side = np.zeros((8,3))
###Just re-ordered your matrix in some np.arrays
F = [[np.array([1., -0.001, -0.017])],
[np.array([0., -0.999, 0.037])],
[np.array([0.017, 0.037, 0.999])]]
def cuboid(center, size):
ox, oy, oz = center
l, w, h = size
###Added the fig in order to be able to plot it later
fig = plt.figure()
ax = fig.gca(projection='3d') ##plot the project cuboid
X=[ox-l/2,ox-l/2,ox-l/2,ox-l/2,ox+l/2,ox+l/2,ox+l/2,ox+l/2]
Y=[oy+w/2,oy-w/2,oy-w/2,oy+w/2,oy+w/2,oy-w/2,oy-w/2,oy+w/2]
Z=[oz-h/2,oz-h/2,oz+h/2,oz+h/2,oz+h/2,oz+h/2,oz-h/2,oz-h/2]
X_new = ([])
Y_new = ([])
Z_new = ([])
for i in range(0,8):
c=np.matrix([[X[i]],
[Y[i]],
[Z[i]]])
u=F*c
X_new = np.append(X_new, u.item(0))
Y_new = np.append(Y_new, u.item(1))
Z_new = np.append(Z_new, u.item(2))
###Doing a dot product between F and c like you did earlier but using np.dot as we're now working with Numpy format
side[i,:] = np.dot(F, c)
###Storing the position of every points
sides = [[side[0],side[1],side[2],side[3]],
[side[4],side[5],side[6],side[7]],
[side[0],side[1],side[4],side[5]],
[side[2],side[3],side[4],side[5]],
[side[1],side[2],side[5],side[6]],
[side[4],side[7],side[0],side[3]]]
###Scatter plot
ax.scatter(X_new,Y_new,Z_new,c='darkred',marker='o') #the plot of points after rotated
ax.scatter(ox,oy,oz,c='crimson',marker='o') #the previous plot of points before rotated
### Add title
plt.title('Plot_for_PSM', fontsize=20)
plt.gca().invert_yaxis()
##labelling the axes
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
###This draw the plane sides as you wanted
ax.add_collection3d(Poly3DCollection(sides, facecolors='blue', linewidths=1, edgecolors='r', alpha=.25))
cuboid(center, size)
###Mandatory to plot the cube
plt.show()
It uses Poly3DCollection, Line3DCollection from mpl_toolkits to draw 6 plane square, representing the sides of the cube.
The first step is to find the 4 coords of every side. Then you need to use Poly3DCollection to plot it.
I have made this code that applies the spherical harmonics in a spherical manner as I am trying to model stellar pulsation modes. Ideally, I'd like to be able to have an image that rotates that can be saved as a gif image. I have found a few examples of code for doing this but none of it seems to apply to my code or uses python packages that aren't available to me. I'm not sure if this is too far out of my range of skills in python as I'm very much a beginner.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
#Turn off the axis planes so only the sphere is visible
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
plt.savefig('m4_l4.png') #saves a .png file of my figure
plt.show() #Plots the figure
#figure saved for m=1, 2, 3, 4 and l=2, 3, 5, 6 respectively then all 6 were put together to form a single figure
I've also got an image showing what my code outputs currently. It's just a still sphere, of course. Thank you in advance! sphere4_4
Change the last part of your code to generate a set of figures (see below). In this case I create num = 10 frames, you can change this number if you want. Then open a terminal and type
convert m4_l4*.png m4_l4.gif
And this is the result
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
for idx, angle in enumerate(np.linspace(0, 360, 10)):
axes.view_init(30, angle)
plt.draw()
#Turn off the axis planes so only the sphere is visible
plt.savefig('m4_l4-%04d.png' % idx) #saves a .png file of my figure
plt.show()
I'm struggling with creating a quite complex 3d figure in python, specifically using iPython notebook. I can partition the content of the graph into two sections:
The (x,y) plane: Here a two-dimensional random walk is bobbing around, let's call it G(). I would like to plot part of this trajectory on the (x,y) plane. Say, 10% of all the data points of G(). As G() bobs around, it visits some (x,y) pairs more frequently than others. I would like to estimate this density of G() using a kernel estimation approach and draw it as contour lines on the (x,y) plane.
The (z) plane: Here, I would like to draw a mesh or (transparent) surface plot of the information theoretical surprise of a bivariate normal. Surprise is simply -log(p(i)) or the negative (base 2) logarithm of outcome i. Given the bivariate normal, each (x,y) pair has some probability p(x,y) and the surprise of this is simply -log(p(x,y)).
Essentially these two graphs are independent. Assume the interval of the random walk G() is [xmin,xmax],[ymin,ymax] and of size N. The bivariate normal in the z-plane should be drawn from the same interval, such that for each (x,y) pair in the random walk, I can draw a (dashed) line from some subset of the random walk n < N to the bivariate normal. Assume that G(10) = (5,5) then I would like to draw a dashed line from (5,5) up the Z-axes, until it hits the bivariate normal.
So far, I've managed to plot G() in a 3-d space, and estimate the density f(X,Y) using scipy.stats.gaussian_kde. In another (2d) graph, I have the sort of contour lines I want. What I don't have, is the contour lines in the 3d-plot using the estimated KDE density. I also don't have the bivariate normal plot, or the projection of a few random points from the random walk, to the surface of the bivariate normal. I've added a hand drawn figure, which might ease intuition (ignore the label on the z-axis and the fact that there is no mesh.. difficult to draw!)
Any input, even just partial, such as how to draw the contour lines in the (x,y) plane of the 3d graph, or a mesh of a bivariate normal would be much appreciated.
Thanks!
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(400):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
fig1 = plt.figure()
ax = fig1.gca(projection='3d')
ax.plot(x, y, label='Random walk')
sns.kdeplot(data[0,:], data[1,:], 0)
ax.scatter(x[-1], y[-1], c='b', marker='o') # End point
ax.legend()
fig2 = plt.figure()
sns.kdeplot(data[0,:], data[1,:])
Calling randomwalk() initialises and plots this:
Edit #1:
Made some progress, actually the only thing I need is to restrict the height of the dashed vertical lines to the bivariate. Any ideas?
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(40):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 20)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(20,5)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.scatter(x[-1], y[-1], c='k', marker='o') # End point
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(5):
ax.plot([M[i,0], M[i,0]],[M[i,1], M[i,1]], [0,10],'k--',alpha=0.8, linewidth=0.5)
ax.set_zlim(0, 50)
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
Final code,
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(50):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 100)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(50,10)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(10):
x = [M[i,0], M[i,0]]
y = [M[i,1], M[i,1]]
z = [0,-np.log(bivariate_normal(M[i,0],M[i,1]))]
ax.plot(x,y,z,'k--',alpha=0.8, linewidth=0.5)
ax.scatter(x, y, z, c='k', marker='o')
I have a random walker in the (x,y) plane and a -log(bivariate gaussian) in the (x,y,z) plane. These two datasets are essentially independent.
I want to sample, say 5 (x,y) pairs of the random walker and draw vertical lines up the z-axis and terminate the vertical line when it "meets" the bivariate gaussian.
This is my code so far:
import matplotlib as mpl
import matplotlib.pyplot as plt
import random
import numpy as np
import seaborn as sns
import scipy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.mlab import bivariate_normal
%matplotlib inline
# Data for random walk
def randomwalk():
mpl.rcParams['legend.fontsize'] = 10
xyz = []
cur = [0, 0]
for _ in range(40):
axis = random.randrange(0, 2)
cur[axis] += random.choice([-1, 1])
xyz.append(cur[:])
# Get density
x, y = zip(*xyz)
data = np.vstack([x,y])
kde = scipy.stats.gaussian_kde(data)
density = kde(data)
# Data for bivariate gaussian
a = np.linspace(-7.5, 7.5, 40)
b = a
X,Y = np.meshgrid(a, b)
Z = bivariate_normal(X, Y)
surprise_Z = -np.log(Z)
# Get random points from walker and plot up z-axis to the gaussian
M = data[:,np.random.choice(20,5)].T
# Plot figure
fig = plt.figure(figsize=(10, 7))
ax = fig.gca(projection='3d')
ax.plot(x, y, 'grey', label='Random walk') # Walker
ax.scatter(x[-1], y[-1], c='k', marker='o') # End point
ax.legend()
surf = ax.plot_surface(X, Y, surprise_Z, rstride=1, cstride=1,
cmap = plt.cm.gist_heat_r, alpha=0.1, linewidth=0.1)
#fig.colorbar(surf, shrink=0.5, aspect=7, cmap=plt.cm.gray_r)
for i in range(5):
ax.plot([M[i,0], M[i,0]],[M[i,1], M[i,1]], [0,10],'k--',alpha=0.8, linewidth=0.5)
ax.set_zlim(0, 50)
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
Which produces
As you can see the only thing I'm struggling with is how to terminate the vertical lines when they meet the appropriate Z-value. Any ideas are welcome!
You're currently only letting those lines get to a height of 10 by using [0,10] as the z coordinates. You can change your loop to the following:
for i in range(5):
x = [M[i,0], M[i,0]]
y = [M[i,1], M[i,1]]
z = [0,-np.log(bivariate_normal(M[i,0],M[i,1]))]
ax.plot(x,y,z,'k--',alpha=0.8, linewidth=0.5)
This takes the x and y coordinates for each point you loop over and calculates the height of overlying Gaussian for that point and plots to there. Here is a plot with the linestyle changed to emphasize the lines relevant to the question:
I have ran into a problem relating to the drawing of the Ellipsoid.
The ellipsoid that I am drawing to draw is the following:
x**2/16 + y**2/16 + z**2/16 = 1.
So I saw a lot of references relating to calculating and plotting of an Ellipse void and in multiple questions a cartesian to spherical or vice versa calculation was mentioned.
Ran into a website that had a calculator for it, but I had no idea on how to successfully perform this calculation. Also I am not sure as to what the linspaces should be set to. Have seen the ones that I have there as defaults, but as I got no previous experience with these libraries, I really don't know what to expect from it.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
multip = (1, 1, 1)
# Radii corresponding to the coefficients:
rx, ry, rz = 1/np.sqrt(multip)
# Spherical Angles
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
# Cartesian coordinates
#Lots of uncertainty.
#x =
#y =
#z =
# Plot:
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='b')
# Axis modifications
max_radius = max(rx, ry, rz)
for axis in 'xyz':
getattr(ax, 'set_{}lim'.format(axis))((-max_radius, max_radius))
plt.show()
Your ellipsoid is not just an ellipsoid, it's a sphere.
Notice that if you use the substitution formulas written below for x, y and z, you'll get an identity. It is in general easier to plot such a surface of revolution in a different coordinate system (spherical in this case), rather than attempting to solve an implicit equation (which in most plotting programs ends up jagged, unless you take some countermeasures).
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
phi = np.linspace(0,2*np.pi, 256).reshape(256, 1) # the angle of the projection in the xy-plane
theta = np.linspace(0, np.pi, 256).reshape(-1, 256) # the angle from the polar axis, ie the polar angle
radius = 4
# Transformation formulae for a spherical coordinate system.
x = radius*np.sin(theta)*np.cos(phi)
y = radius*np.sin(theta)*np.sin(phi)
z = radius*np.cos(theta)
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, color='b')