Mayavi : surface plot and 3D contours - python

Good evening everyone, I am a long time user of Matplotlib and I recently discovered Mayavi.
With Matplotlib, I can plot a 3D surface with projected contours of the surface plot for each axis and I was wondering if the same could be done with Mayavi.
Here is an example of what I have done so far with Matplotlib (source), but I have not been able to find a similar way to plot contours with Mayavi on the internet:
Can someone who knows Mayavi tell me if I can plot 3D contours for each axis like with Matplotlib?
Matplotlib code
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
plt.show()
Mayavi code
from mpl_toolkits.mplot3d import axes3d
from mayavi import mlab
X, Y, Z = axes3d.get_test_data(0.05)
Z = np.rollaxis(Z,0,2)
X = np.rollaxis(X,0,2)
Y = np.rollaxis(Y,0,2)
mlab.surf(X, Y, Z, warp_scale="auto", opacity=1)
mlab.axes(xlabel='X', ylabel='Y', zlabel='Z')
mlab.show()

Related

Plot an energy potential with matplotlib

I want to plot the gravitational energy potential to highlight its extremums (the Lagrangian points around two celestial bodies).
Here is the function that returns the potential for each set of coordinates x and y:
def gravitational_potential(M,m,R,x,y):
G = 6.674*10**(-11)
omega2 = G*(M+m)/(R**3)
r = np.sqrt(x**2+y**2)
r2 = R*m/(M+m)
r1 = R-r2
phi = -G*(M/abs(r-r1)+m/abs(r-r2))-1/2*omega2*(x**2+y**2)
return phi
I want to use meshgrid and plot_surface to plot the surface and the contour of the potential but it doesn't work.
What am I doing wrong ?
PS: I managed to plot the potential with WolframAlpha so I know the math works.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
def gravitational_potential(M,m,R,x,y):
G = 6.674*10**(-11)
omega2 = G*(M+m)/(R**3)
r = np.sqrt(x**2+y**2)
r2 = R*m/(M+m)
r1 = R-r2
phi = -G*(M/abs(r-r1)+m/abs(r-r2))-1/2*omega2*(x**2+y**2)
return phi
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y = np.meshgrid(np.arange(-20, 20, 0.5), np.arange(-20, 20, 0.5))
M = 10
m = 1
R = 10
Z = gravitational_potential(M,m,R,X,Y)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.9)
cset = ax.contour(X, Y, Z, zdir='z', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-20, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=20, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-20, 20)
ax.set_ylabel('Y')
ax.set_ylim(-20, 20)
ax.set_zlabel('Z')
ax.set_zlim(-40, 40)
plt.show()
When I execute it I get the following:
runfile('C:/Users/python/Google Drive/lagrangepoint_maths/potential/gravitational_potential.py', wdir='C:/Users/python/Google Drive/lagrangepoint_maths/potential')
C:/Users/python/Google Drive/lagrangepoint_maths/potential/gravitational_potential.py:13: RuntimeWarning: divide by zero encountered in divide
phi = -G*(M/abs(r-r1)+m/abs(r-r2))-1/2*omega2*(x**2+y**2)
This is not really what I want. There is something wrong with Z. I want something like that:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.9)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
plt.show()
All of this are things one may just debug one by one:
Integer division in python 2 results in 0 if the nominator is smaller than the denominator. You may from __future__ import division or correct your code to divide by floats.
If you want to show numbers between -2 x 10^-8 and +2 x 10^-8 it is not useful to set the z_limits to -40 to 40.
If you want to show small features in the plot, you should not set the plotting resolution coarsely to rstride=8, cstride=8.
In total you would arrive at something like this:

Mark projected min and max in 3D surface-/contourplot

I am using the standard matplotlib surfaceplot as an example here.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)][1]][1]
I would like to mark the two extrema of the surface with a "X" on their respective position on the contour.
How can this be achieved?
I tried:
max_column = np.argmax(np.max(Z, axis=0))
max_row = np.argmax(np.max(Z, axis=1))
min_column = np.argmin(np.min(Z, axis=0))
min_row = np.argmin(np.min(Z, axis=1))
target = [max_row,max_column,0]
ax.plot([target[0]],[target[1]],[0],'r',marker = u'X',markersize = 8)
I guess I need somehow the projected coordinates.
Additionally I would like to draw a hairline-cross with lines on the 2D plane where the extrema are.
First you need to find out the points corresponding to the minimum and maximum of the Z array.
You can then plot those points, where setting one of the coordinates to the values of the respective offset from the contour lets them be projected.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
# calc index of min/max Z value
xmin, ymin = np.unravel_index(np.argmin(Z), Z.shape)
xmax, ymax = np.unravel_index(np.argmax(Z), Z.shape)
# min max points in 3D space (x,y,z)
mi = (X[xmin,ymin], Y[xmin,ymin], Z.min())
ma = (X[xmax, ymax], Y[xmax, ymax], Z.max())
# Arrays for plotting,
# first row for points in xplane, last row for points in 3D space
Ami = np.array([mi]*4)
Ama = np.array([ma]*4)
for i, v in enumerate([-40,40,-100]):
Ami[i,i] = v
Ama[i,i] = v
#plot points.
ax.plot(Ami[:,0], Ami[:,1], Ami[:,2], marker="o", ls="", c=cm.coolwarm(0.))
ax.plot(Ama[:,0], Ama[:,1], Ama[:,2], marker="o", ls="", c=cm.coolwarm(1.))
ax.view_init(azim=-45, elev=19)
plt.savefig(__file__+".png")
plt.show()

Minimum path on potential energy surface using matplotlib

I used Matplotlib to plot a 3D potential energy surface.
But, I also want to show a reaction path with the lowest potential barrier between the minima of a potential energy surface by arrows.
Can any one help me with this: this is the my code for the 3D surface:
#!/usr/bin/python
INPUT='input.txt'
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.mlab import griddata
from matplotlib import cm
from pylab import *
from matplotlib.ticker import LinearLocator, FormatStrFormatter
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
data = np.genfromtxt(INPUT)
x = data[:,0]
y = data[:,1]
z = data[:,2]
xi = np.linspace(min(x), max(x))
yi = np.linspace(min(y), max(y))
X, Y = np.meshgrid(xi, yi)
Z = griddata(x, y, z, xi, yi, interp='linear')
ax.contourf(X, Y, Z, 30, zdir='z', offset=-7, linewidth=0.2, cmap=cm.jet, antialiased=False, shade=True)
C = ax.contour(X, Y, Z, 30, colors='k', linewidths=0.2)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet, linewidth=0, antialiased=False, shade=False)
fig.colorbar(surf)
ax.clabel(C, inline=1, fontsize=10)
ax.set_xlabel(r"$d$$_{3}$ ($\AA$)")
ax.set_ylabel(r"$d$$_{4}$ ($\AA$)")
ax.set_zlabel(r"$\Delta$$E$$_{e}$ (kcal/mol)")
plt.show()
I want a graph like the one below:
3D surface
Source: http://pubs.acs.org/doi/abs/10.1021/ja801727k

pyplot static color colormap

I'm using the following code to generate a pyplot with a colormap:
# Create a 3d plot
fig = pyplot.figure()
ax = fig.gca(projection='3d')
# Plot the frequency-time
ax.plot_surface(X, Y, Z, rstride=4, cstride=5 + (samplerate / 20), alpha=0.3)
cset = ax.contour(X, Y, Z, zdir='y', offset=tlen, cmap=cm.coolwarm)
However I would like to use a colormap with a static color: #066. So no gradient effect.
Is there anyway to achieve this?
Greetings,
Mat
If you want to use a constant color don't pass the cmap parameter, use the colors instead:
cset = ax.contour(X, Y, Z, zdir='y', offset=tlen, colors='r')

Matplotlib: imshow in 3d plot

In the plot below, taken from matplotlib's gallery, contourf is used to create a 2d plot beneath the 3d one. My question is, is it possible to use imshow to do the same thing? I would like the colors in the 2d plot to be smoother.
Making the 2d plot seems to be possible because contourf accepts a zdir argument, while I've looked and imshow doesn't. That suggests that it isn't possible, but why not? pcolor would also get the job done, is it possible with that?
Just specify the levels= option for the contourf, e.g.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1)
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contourf(X, Y, Z, zdir='z', offset=-100,
levels=np.linspace(-100,100,1200),cmap=plt.cm.jet)
cset = ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=plt.cm.jet)
cset = ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=plt.cm.jet)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
plt.show()
A little longer code then sega_sai's answer but faster and to my experience much better for more complex surfaces.
Use plot_surface to plot a flat surface where you want it and facecolors to color it with the values you want
You might need to make your data smoother with scipy's zoom
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt,numpy as np
plt.clf()
fig = plt.figure(1)
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_surface(X, Y, Z, rstride=8, cstride=8, alpha=0.3)
cset = ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=plt.cm.jet)
cset = ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=plt.cm.jet)
### strating here:
# normalize Z to [0..1]
Z=Z-Z.min()
Z=Z/Z.max()
#use zoom to make your data smoother
from scipy.ndimage.interpolation import zoom
#make data 5 times smoother
X=zoom(X,5)
Y=zoom(Y,5)
Z=zoom(Z,5)
#draw a surface at -100, using the facecolors command to color it with the values of Z
cset = ax.plot_surface(X, Y, np.zeros_like(Z)-100,facecolors=plt.cm.jet(Z),shade=False)
ax.set_xlabel('X')
ax.set_xlim(-40, 40)
ax.set_ylabel('Y')
ax.set_ylim(-40, 40)
ax.set_zlabel('Z')
ax.set_zlim(-100, 100)
plt.show()
This also makes it a little harder to create a color bar, in order to that:
cb = plt.cm.ScalarMappable(cmap=plt.cm.jet)
cb.set_array(Z)
plt.colorbar(cb)
plt.show()

Categories