1

I'm trying to create a plot a bit like this:

enter image description here

Where there are spheres above all the minima.

The surface can be approximated with a sin(x)*sin(y) plot:

import numpy as np
import matplotlib.pyplot as plt

def func(x, y):
  return np.sin(2*np.pi*x)*np.sin(2*np.pi*y) / 3

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-1.0, 1.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([func(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)

ax.plot_surface(X, Y, Z, color="grey")
ax.set_zlim3d(-1,1)

plt.show()

However I'm unsure how to add evenly spaced spheres into this. Would anyone be able to help?

Toby Hawkins
  • 247
  • 3
  • 11
  • Maybe you want to mention where the problem lies. I think with a little bit of searching you'll find how to plot a sphere. Then, plotting more spheres shouldn't be a problem either, nor would placing them at certain positions be one. The more you go into detail about the actual problem, the higher the chances of getting help. Else, one might understand this question as "please do the work for me", which many people are allergic to. – ImportanceOfBeingErnest Aug 16 '17 at 22:00
  • That was not my intention. I didn't think posting failed attempts would be useful for a problem that 'seemed' like it wouldn't take long at all for someone who knew what they were doing - but noted for the future. Sadly, as ml4294's answer highlights, it appears this question is unsolvable in matplotlib. – Toby Hawkins Aug 16 '17 at 23:06

2 Answers2

2

You need to determine the minima of the function, which are (with your parametrization) at (x = integer + 0.25, y=integer + 0.75) or the other way round. Then you can simply parametrize the spheres using spherical coordinates (for example as done here: python matplotlib: drawing 3D sphere with circumferences) and plot the spheres.

Now comes some good news and some bad news:

1.) The good news is that the minima are correctly determined and that the spheres are created. In the below plot you can see that they are right above the blue parts of the surface plot (where the blue parts show indeed the minima).

2.) The bad news is that you will have a hard time looking for another angle where the spheres are actually correctly rendered. I do not know a solution to this rather annoying behaviour, therefore you will probably have to play around until you have found the right angle. Have fun!

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def func(x, y):
  return np.sin(2*np.pi*x)*np.sin(2*np.pi*y) / 3

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-2.0, 2.0, 0.05)

# Get the minima of the function.
minsx1 = np.arange(int(np.amin(x)) + 0.25, int(np.amax(x)) + 0.25 + 1, 1)
minsy1 = np.arange(int(np.amin(y)) + 0.75, int(np.amax(y)) + 0.75 + 1, 1)
minsx2 = np.arange(int(np.amin(x)) + 0.75, int(np.amax(x)) + 0.75 + 1, 1)
minsy2 = np.arange(int(np.amin(y)) + 0.25, int(np.amax(y)) + 0.25 + 1, 1)

X, Y = np.meshgrid(x, y)
zs = np.array([func(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)

# Color map for better detection of minima (blue)
ax.plot_surface(X, Y, Z, cmap="viridis")
ax.set_zlim3d(-1,1)

# Spherical coordinates
r = 0.15
phi = np.linspace(0, 2 * np.pi, 30)
theta = np.linspace(0, np.pi, 30)

# Write spherical coordinates in cartesian coordinates.
x = r * np.outer(np.cos(phi), np.sin(theta))
y = r * np.outer(np.sin(phi), np.sin(theta))
z = r * np.outer(np.ones(np.size(phi)), np.cos(theta))

# Plot the spheres.
for xp in minsx1:
    for yp in minsy1:
        sphere = ax.plot_surface(x+xp, y+yp, z+0.35, color='r')
for xp in minsx2:
    for yp in minsy2:
        sphere = ax.plot_surface(x+xp, y+yp, z+0.35, color='r')
ax.view_init(elev=90, azim=0)
plt.savefig('test.png')
plt.show()

enter image description here

ml4294
  • 2,559
  • 5
  • 24
  • 24
  • That is rather frustrating, but thanks a lot! It would seem based on this https://stackoverflow.com/questions/23188561/matplotlib-3d-plot-zorder-issue that this is an unsolvable problem in matplotlib. I'm going to try the suggestion there and play around with other packages. I'll leave this question open for a bit in the hopes someone will come up with a fully-working solution, else I'll mark yours. – Toby Hawkins Aug 16 '17 at 22:52
  • 1
    I think you will not be able to solve this with matplotlib, so probably the solution of @ImportanceOfBeingErnest is the way to go here. – ml4294 Aug 17 '17 at 07:16
2

Using matplotlib one will inevitably run into problems of objects being hidden behind others. This is also stated in the matplotlib 3d FAQ and the recommendation is to use mayavi.

In mayavi the solution would look like this:

from mayavi import mlab
import numpy as np

### SURFACE '''
x,y = np.meshgrid(np.linspace(-2.5,2), np.linspace(-2,2))
f = lambda x,y: .4*np.sin(2*np.pi*x)*np.sin(2*np.pi*y)
z=f(x,y)
mlab.surf(x.T,y.T,z.T, colormap="copper")

### SPHERES '''
px,py = np.meshgrid(np.arange(-2,2)+.25, np.arange(-2,2)+.75)
px,py = px.flatten(),py.flatten()
pz = np.ones_like(px)*0.05
r = np.ones_like(px)*.4
mlab.points3d(px,py,pz,r, color=(0.9,0.05,.3), scale_factor=1)


mlab.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks for this! I'm unfamiliar with mayavi so I'll have to look into it given what I know now about matplotlib's limitations (I normally only need to make 2D plots). For anyone else reading this and also unfamiliar with mayavi; I found it's currently only compatible with Python <3.6 and qt4, so to use this solution an appropriate environment must first be set up. See: https://www.scivision.co/mayavi-qt5-create-conda-env-qt4/ – Toby Hawkins Aug 17 '17 at 01:12