Related
This question already has answers here:
Plot a point on a line closest to a point
(1 answer)
Python: point on a line closest to third point
(3 answers)
Python: Closest Point to a line
(1 answer)
Closed 3 months ago.
This post was edited and submitted for review 3 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have the problem of finding the point which is closest to a line from an array of x- and y-data.
The line is semi-infinite originating from the origin at (0,0) and running into the direction of a given angle.
The x,y data of the points are given in relation to the origin.
How do I find the closest point (and its distance) to the line in line direction (not opposite)?
This is an example of the data I have:
import numpy as np
import matplotlib.pyplot as plt
def main():
depth = np.random.random((100))*20+50
angle = np.linspace(0, 2*np.pi, 100)
x,y = depth2xy(depth, angle)
line = np.random.random_sample()*2*np.pi
# fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
plt.scatter(x, y)
plt.plot([0,100*np.cos(line)], [0, 100*np.sin(line)], markersize=10, color = "r")
plt.show()
def depth2xy(depth, angle):
x, y = np.zeros(len(depth)), np.zeros(len(depth))
for i in range(len(depth)):
x[i] = depth[i]*np.cos(angle[i])
y[i] = depth[i]*np.sin(angle[i])
return x,y
if __name__ == "__main__": main()
I could try a brute force approach, iterating over different distances along the line to find the ultimate smallest distance.
But as time efficiency is critical my case and the algorithm would not perform as well as I think it could, I would rather try an analytical approach.
I also thought about scipy.spatial.distance, but I am not sure how this would work for a line.
Your assigned line passes through the origin, its parametric equation is
x = u cos(a)
y = u sin(a)
and you can see the parameter u is simply the (oriented) distance beteween the origin and a point on the assigned line.
Now, consider a point of coordinates X and Y, a line perpendicular to the assigned one has the parametric equation
x = X - v sin(a)
y = Y + v cos(a)
and again, the parameter v is simply the (oriented) distance between (X, Y) and a point on a line passing per (X, Y) and perpendicular to the assigned one.
The intersection is given by the equation
X = u cos(a) + v sin(a)
Y = u sin(a) - v cos(a)
you can check by inspection that the solution of the system is
u = X cos(a) + Y sin(a)
v = X sin(a) - Y cos(a)
The distance of the point (X, Y) from the assigned line is hence
d = | X sin(a) - Y cos(a) |
A Python Implementation
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20221126)
X = 2*np.random.random(32)-1
Y = 2*np.random.random(32)-1
fig, ax = plt.subplots()
ax.set_xlim((-1.2, 1.2))
ax.set_ylim((-1.2, 1.2))
ax.grid(1)
ax.set_aspect(1)
ax.scatter(X, Y, s=80, ec='k', color='y')
a = 2*np.random.random()*np.pi
s, c = np.sin(a), np.cos(a)
plt.plot((0, c), (0, s), color='k')
plt.plot((-s, s), (c, -c), color='r')
# strike out "bad" points
bad = X*c+Y*s<0
plt.scatter(X[bad], Y[bad], marker='x', color='k')
# consider only good (i.e., not bad) points
Xg, Yg = X[~bad], Y[~bad]
# compute all distances (but for good points only)
d = np.abs(Xg*s-Yg*c)
# find the nearest point and hilight it
imin = np.argmin(d)
plt.scatter(Xg[imin], Yg[imin], ec='k', color='r')
plt.show()
An OVERDONE Example
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(20221126)
X = 2*np.random.random(32)-1
Y = 2*np.random.random(32)-1
fig, axs = plt.subplots(2, 4, figsize=(10,5), layout='constrained')
for ax, a in zip(axs.flat,
(2.8, 1.8, 1.4, 0.2,
3.4, 4.5, 4.9, 6.0)):
ax.set_xlim((-1.2, 1.2))
ax.set_xticks((-1, -0.5, 0, 0.5, 1.0))
ax.set_ylim((-1.2, 1.2))
ax.grid(1)
ax.set_aspect(1)
ax.set_title('$\\alpha \\approx %d^o$'%round(np.rad2deg(a)))
ax.scatter(X, Y, s=80, ec='k', color='yellow')
s, c = np.sin(a), np.cos(a)
ax.arrow(0, 0, 1.2*c, 1.2*s, fc='k',
length_includes_head=True,
head_width=0.08, head_length=0.1)
# divide the drawing surface in two semiplanes
if abs(c)>abs(s):
if c>0:
ax.plot((1.2*s, -1.2*s), (-1.2, 1.2))
else:
ax.plot((-1.2*s, 1.2*s), (-1.2, 1.2))
elif abs(s)>=abs(c):
if s>0:
ax.plot((-1.2, 1.2), (1.2*c, -1.2*c))
else:
ax.plot((-1.2, 1.2), (-1.2*c, 1.2*c))
# strike out "bad" points
bad = X*c+Y*s<0
ax.scatter(X[bad], Y[bad], marker='x', color='k')
# consider only good (i.e., not bad) points
Xg, Yg = X[~bad], Y[~bad]
# compute all distances (but for good points only)
d = np.abs(Xg*s-Yg*c)
# find the nearest point and hilight it
imin = np.argmin(d)
ax.scatter(Xg[imin], Yg[imin], s=80, ec='k', color='yellow')
ax.scatter(Xg[imin], Yg[imin], s= 10, color='k', alpha=1.0)
plt.show()
Let P be a point from your know data set. Let Q be the projection of this point on the line. You can use an analytic approach to determine the exact location of Q:
OQ is the segment from the origin to the Q point. It is aligned to the line.
PQ is the distance of the point P to the line.
from geometry, the dot product between QP and OQ is zero (the two segments are orthogonal to each other). From this equation we can compute the point Q.
After that, you simply compute all distances and find the shortest one.
I'm going to use SymPy for the analytical part, Numpy for the numerical part and Matplotlib for plotting:
from sympy import *
import numpy as np
import matplotlib.pyplot as plt
xq, xp, yq, yp, m = symbols("x_Q, x_P, y_Q, y_P, m")
A = Matrix([xq - xp, yq - yp])
B = Matrix([xq, yq])
# this equations contains two unkowns: xq, yq
eq = A.dot(B)
# but we know the line equation: yq = m * xq, so we substitute it into
# eq and solve for xq
xq_expr = solve(eq.subs(yq, m * xq), xq)[1]
print(xq_expr)
# (m*y_P + x_P)/(m**2 + 1)
# generate data
mv = -0.5
xp_vals = np.random.uniform(2, 10, 30)
yp_vals = np.random.uniform(2, 10, 30)
# convert the symbolic expression to a numerical function
f = lambdify([m, xp, yp], xq_expr)
# compute the projections on the line
xq_vals = f(mv, xp_vals, yp_vals)
yq_vals = mv * xq_vals
# compute the distance
d = np.sqrt((xp_vals - xq_vals)**2 + (yp_vals - yq_vals)**2)
# find the index of the shortest distance
idx = d.argmin()
fig, ax = plt.subplots()
xline = np.linspace(0, 10)
yline = mv * xline
ax.plot(xline, yline, "k:", label="line")
ax.scatter(xq_vals, yq_vals, label="Q", marker=".")
ax.scatter(xp_vals, yp_vals, label="P", marker="*")
ax.plot([xp_vals[idx], xq_vals[idx]], [yp_vals[idx], yq_vals[idx]], "r", label="min distance")
ax.set_aspect("equal")
ax.legend()
plt.show()
I wrote a matplotlib program that plots a 2d surface embedded in three dimensional space using plot_trisurf() and then plots a vector field defined on the surface using quiver(). I'd like the surface to be opaque to the vector field but instead the program plots both the vectors that are in front of the surface and those that are behind the surface with respect to the camera, despite the surface's alpha value being 1.0.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri
fig = plt.figure(figsize=plt.figaspect(1.0) * 1.7)
# Make a mesh in the space of parameterisation variables u and v
u = np.linspace(0, 2.0 * np.pi, endpoint=True, num=30) # u: theta
v = np.linspace(0, 2.0 * np.pi, endpoint=True, num=60) # v: phi
u, v = np.meshgrid(u, v)
u, v = u.flatten(), v.flatten()
x, y, z = F(u, v)
# Triangulate parameter space to determine the triangles
tri = mtri.Triangulation(u, v)
# Plot the surface. The triangles in parameter space determine which x, y, z
# points are connected by an edge.
ax = fig.add_subplot(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=plt.cm.magma, alpha = 1.0)
xl = ax.get_xlim()
yl = ax.get_ylim()
ax.set_zlim(xl[0], xl[1])
plt.show()
Here's where the vector field gets plotted:
alpha = 1.0
lenght = 0.25
ax.quiver(xf, yf, zf, ox, oy, oz, color='red', alpha=alpha, length=lenght, normalize=True)
Here's an example that shows how the full vector field gets plotted.
I've also tried adding zorder parameters in the plotting functions but with no success: ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap=plt.cm.magma, alpha = 1.0, zorder = 2)
Set antialiased=False in plot_trisurf
I need to calculate the line integral between two points (x1,y1) and (x2,y2) under a surface defined by values on a meshgrid.
I'm not exactly sure on the best tool/approach to use for this process using python.
As I do not have a function which represents the surface, instead values at points on a evenly spaaced meshgrid I am assuming I will need to use one of the following methods
trapz -- Use trapezoidal rule to compute integral from samples.
cumtrapz -- Use trapezoidal rule to cumulatively compute integral.
simps -- Use Simpson's rule to compute integral from samples.
romb -- Use Romberg Integration to compute integral from
(2**k + 1) evenly-spaced samples.
Any help or guidance would be appreciated.
Edit:
import numpy as np
from scipy import interpolate
def f(x, y):
return x**2 + x*y + y*2 + 1
xl = np.linspace(-1.5, 1.5, 101,endpoint = True)
X, Y = np.meshgrid(xl, xl)
Z = f(X, Y)
#And a 2D Line:
arr_2D = np.linspace(start=[-1, 1.2], stop=[0, 1.5], num=101,endpoint =
True) #Creates a 2D line between these two points
#Then we create a multidimensional linear interpolator:
XY = np.stack([X.ravel(), Y.ravel()]).T
S = interpolate.LinearNDInterpolator(XY, Z.ravel())
print(S)
#To interpolate points from 2D curve on the 3D surface:
St = S(arr_2D)
#We also compute the curvilinear coordinates of the 2D curve:
#Using curvilinear coordinates based on cumulative arc length, the integral to solve looks like:
Sd = np.cumsum(np.sqrt(np.sum(np.diff(arr_2D, axis=0)**2, axis=1)))
print(Sd)
I = np.trapz(St[:-1], Sd) # 2.041770932394164
print("Integral: ",I)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = plt.axes(projection="3d")
x_line = np.linspace(start=[-1], stop=[1.5], num=100,endpoint = True)
y_line = np.linspace(start=[-1.2], stop=[1.5], num=100,endpoint = True)
ax.plot3D(x_line, y_line, 'red') #Line which represents integral
ax.plot_wireframe(X, Y, Z, color='green') #Represents the surface
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('Time')
plt.show()
fig = plt.figure()
ax = plt.axes()
ax.fill_between(Sd, St)
ax.set_xlabel('x')
ax.set_ylabel('Z')
plt.show()
Provided you have surface points (we can even relax the requirement of regular grid) and curve points, then basic analysis provided by numpy and scipy packages should do the trick.
First, let's create a trial dataset for your problem.
import numpy as np
from scipy import interpolate
Mainly a 3D surface:
def f(x, y):
return x**2 + x*y + y*2 + 1
xl = np.linspace(-1.5, 1.5, 101)
X, Y = np.meshgrid(xl, xl)
Z = f(X, Y)
And a 2D curve:
t = np.linspace(0, 1, 1001)
xt = t**2*np.cos(2*np.pi*t**2)
yt = t**3*np.sin(2*np.pi*t**3)
The complete setup looks like:
axe = plt.axes(projection='3d')
axe.plot_surface(X, Y, Z, cmap='jet', alpha=0.5)
axe.plot(xt, yt, 0)
axe.plot(xt, yt, St)
axe.view_init(elev=25, azim=-45)
Then we create a multidimensional linear interpolator:
XY = np.stack([X.ravel(), Y.ravel()]).T
S = interpolate.LinearNDInterpolator(XY, Z.ravel())
To interpolate points from 2D curve on the 3D surface:
xyt = np.stack([xt, yt]).T
St = S(xyt)
We also compute the curvilinear coordinates of the 2D curve:
Sd = np.cumsum(np.sqrt(np.sum(np.diff(xyt, axis=0)**2, axis=1)))
Using curvilinear coordinates based on cumulative arc length, the integral to solve looks like:
fig, axe = plt.subplots()
axe.plot(Sd, St[:-1])
axe.fill_between(Sd, St[:-1], alpha=0.5)
axe.grid()
Finally we integrate using the method of our choice, here the simplest Trapezoidal Rule from numpy:
I = np.trapz(St[:-1], Sd) # 2.041770932394164
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()
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.