Is it possible to add clipping planes to a Mayavi scene?
Here is an example:
import numpy as np
import mayavi.mlab as mlab
mlab.figure()
u, v = np.mgrid[0:2*np.pi:150j, 0:np.pi:150j]
r = 2 + np.sin(7 * u + 5 * v)
x = r * np.cos(u) * np.sin(v)
y = r * np.sin(u) * np.sin(v)
z = r * np.cos(v)
mlab.mesh(x, y, z)
mlab.show()
which produces:
But I would like to add a couple of clipping planes to obtain something similar to this:
Related
I'm trying to plot a 3D superball in python matplotlib, where a superball is defined as a general mathematical shape that can be used to describe rounded cubes using a shape parameter p, where for p = 1 the shape is equal to that of a sphere.
This paper claims that the superball is defined by using modified spherical coordinates with:
x = r*cos(u)**1/p * sin(v)**1/p
y = r*cos(u)**1/p * sin(v)**1/p
z = r*cos(v)**1/p
with u = phi and v = theta.
I managed to get the code running, at least for p = 1 which generates a sphere - exactly as it should do:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
r, p = 1, 1
# Make data
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
u, v = np.meshgrid(u, v)
x = r * np.cos(u)**(1/p) * np.sin(v)**(1/p)
y = r * np.sin(u)**(1/p) * np.sin(v)**(1/p)
z = r * np.cos(v)**(1/p)
# Plot the surface
ax.plot_surface(x, y, z)
plt.show()
This is a 3D plot of the code above for p = 1.
However, as I put in any other value for p, e.g. 2, it's giving me only a partial shape, while it should actually give me a full superball.
This is a 3D plot of the code above for p = 2.
I believe the fix is more of mathematical nature, but how can this be fixed?
When plotting a regular sphere, we transform positive and negative coordinates differently:
Positives: x**0.5
Negatives: -1 * abs(x)**0.5
For the superball variants, apply the same logic using np.sign and np.abs:
power = lambda base, exp: np.sign(base) * np.abs(base)**exp
x = r * power(np.cos(u), 1/p) * power(np.sin(v), 1/p)
y = r * power(np.sin(u), 1/p) * power(np.sin(v), 1/p)
z = r * power(np.cos(v), 1/p)
Full example for p = 4:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(subplot_kw={'projection': '3d'})
r, p = 1, 4
# Make the data
u = np.linspace(0, 2 * np.pi)
v = np.linspace(0, np.pi)
u, v = np.meshgrid(u, v)
# Transform the coordinates
# Positives: base**exp
# Negatives: -abs(base)**exp
power = lambda base, exp: np.sign(base) * np.abs(base)**exp
x = r * power(np.cos(u), 1/p) * power(np.sin(v), 1/p)
y = r * power(np.sin(u), 1/p) * power(np.sin(v), 1/p)
z = r * power(np.cos(v), 1/p)
# Plot the surface
ax.plot_surface(x, y, z)
plt.show()
Is is possible to have a plot, something like This random triangle, but instead of . we have Roses?
I mean, How can we plot the below Rose in random locations??
import numpy as np
import matplotlib.pyplot as plt
t,k = np.linspace(0,2*np.pi,1000),5
x = np.cos(k*t)*np.cos(t)
y = np.cos(k*t)*np.sin(t)
plt.plot(x,y,'r')
plt.axis('off')
plt.axis('square')
plt.show()
yes! you just plot your rose at lots of random points as sampled from your linked out question.
first I've refactored the method so it returns size points uniformly sampled within the given triangle:
import numpy as np
import matplotlib.pyplot as plt
def trisample(A, B, C, size=1):
r1 = np.random.rand(size)
r2 = np.random.rand(size)
s1 = np.sqrt(r1)
p1 = 1 - s1
p2 = (1 - r2) * s1
p3 = r2 * s1
x = A[0] * p1 + B[0] * p2 + C[0] * p3
y = A[1] * p1 + B[1] * p2 + C[1] * p3
return x, y
next we calculate a few of your roses:
t, k, z = np.linspace(0, np.pi, 5*5+1), 5, 0.1
x_r = np.cos(k*t) * np.sin(t) * z
y_r = np.cos(k*t) * np.cos(t) * z
note that your pi*2 meant that it was orbited twice so I've dropped that, also I only use 5 points per "petal" to speed things up. z scales the roses down so they fit into the triangle
finally we sample some points in the triangle, and plot them as you did:
for x_t, y_t in zip(*trisample([1,1], [5,3], [2,5], 100)):
plt.plot(x_r + x_t, y_r + y_t, lw=1)
plt.axis('off')
plt.axis('square');
which gives something like the following:
You need a random start point for the rose. I guess you could something like thatin this manner:
import numpy as np
import matplotlib.pyplot as plt
import random
t,k = np.linspace(0,2*np.pi,1000),5
x_start = random.randint(0, 10)
y_start = random.randint(0, 10)
x = x_start + np.cos(k*t)*np.cos(t)
y = y_start + np.cos(k*t)*np.sin(t)
plt.plot(x,y,'r')
plt.axis('off')
plt.axis('square')
plt.show()
Now I have two functions respectively are
rho(u) = np.exp( (-2.0 / 0.2) * (u**0.2-1.0) )
psi( w(x-u) ) = (1/(4.0 * math.sqrt(np.pi))) * np.exp(- ((w * (x-u))**2) / 4.0) * (2.0 - (w * (x-u))**2)
And then I want to integrate 'rho(u) * psi( w(x-u) )' with respect to 'u'. So that the integral result can be one function with respect to 'w' and 'x'.
Here's my Python code snippet as I try to solve this integral.
import numpy as np
import math
import matplotlib.pyplot as plt
from scipy import integrate
x = np.linspace(0,10,1000)
w = np.linspace(0,10,500)
u = np.linspace(0,10,1000)
rho = np.exp((-2.0/0.2)*(u**0.2-1.0))
value = np.zeros((500,1000),dtype="float32")
# Integrate the products of rho with
# (1/(4.0*math.sqrt(np.pi)))*np.exp(- ((w[i]*(x[j]-u))**2) / 4.0)*(2.0 - (w[i]*(x[j]-u))**2)
for i in range(len(w)):
for j in range(len(x)):
value[i,j] =value[i,j]+ integrate.simps(rho*(1/(4.0*math.sqrt(np.pi)))*np.exp(- ((w[i]*(x[j]-u))**2) / 4.0)*(2.0 - (w[i]*(x[j]-u))**2),u)
plt.imshow(value,origin='lower')
plt.colorbar()
As illustrated above, when I do the integration, I used nesting for loops. We all know that such a way is inefficient.
So I want to ask whether there are methods not using for loop.
Here is a possibility using scipy.integrate.quad_vec. It executes in 6 seconds on my machine, which I believe is acceptable. It is true, however, that I have used a step of 0.1 only for both x and w, but such a resolution seems to be a good compromise on a single core.
from functools import partial
import matplotlib.pyplot as plt
from numpy import empty, exp, linspace, pi, sqrt
from scipy.integrate import quad_vec
from time import perf_counter
def func(u, x):
rho = exp(-10 * (u ** 0.2 - 1))
var = w * (x - u)
psi = exp(-var ** 2 / 4) * (2 - var ** 2) / 4 / sqrt(pi)
return rho * psi
begin = perf_counter()
x = linspace(0, 10, 101)
w = linspace(0, 10, 101)
res = empty((x.size, w.size))
for i, xVal in enumerate(x):
res[i], err = quad_vec(partial(func, x=xVal), 0, 10)
print(f'{perf_counter() - begin} s')
plt.contourf(w, x, res)
plt.colorbar()
plt.xlabel('w')
plt.ylabel('x')
plt.show()
UPDATE
I had not realised, but one can also work with a multi-dimensional array in quad_vec. The updated approach below enables to increase the resolution by a factor of 2 for both x and w, and keep an execution time of around 7 seconds. Additionally, no more visible for loops.
import matplotlib.pyplot as plt
from numpy import exp, mgrid, pi, sqrt
from scipy.integrate import quad_vec
from time import perf_counter
def func(u):
rho = exp(-10 * (u ** 0.2 - 1))
var = w * (x - u)
psi = exp(-var ** 2 / 4) * (2 - var ** 2) / 4 / sqrt(pi)
return rho * psi
begin = perf_counter()
x, w = mgrid[0:10:201j, 0:10:201j]
res, err = quad_vec(func, 0, 10)
print(f'{perf_counter() - begin} s')
plt.contourf(w, x, res)
plt.colorbar()
plt.xlabel('w')
plt.ylabel('x')
plt.show()
Addressing the comment
Just add the following lines before plt.show() to have both axes scale logarithmically.
plt.gca().set_xlim(0.05, 10)
plt.gca().set_ylim(0.05, 10)
plt.gca().set_xscale('log')
plt.gca().set_yscale('log')
I am doing simple integration, only thing is that I want to keep 'n' as a variable. How can I do this while still integrating over t?
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
import scipy.integrate as integrate
from scipy.integrate import quad
import math as m
y = lambda t: 3*t
T = 4 #period
n = 1
w = 2*np.pi*n/T
#for odd functions
def integrand(t):
#return y*(2/T)*np.sin(w*t)
return y(t)*np.sin(n*w*t)
Bn = (2/T)*quad(integrand,-T/2,T/2)[0]
print(Bn)
Using quad, you cannot. Perhaps you're looking for symbolic integration like you would do with pen and paper; sympy can help you here:
import sympy
x = sympy.Symbol("x")
t = sympy.Symbol("t")
T = sympy.Symbol("T")
n = sympy.Symbol("n", positive=True)
w = 2 * sympy.pi * n / T
y = 3 * t
out = 2 / T * sympy.integrate(y * sympy.sin(n * w * t), (t, -T/2, T/2))
print(out)
2*(-3*T**2*cos(pi*n**2)/(2*pi*n**2) + 3*T**2*sin(pi*n**2)/(2*pi**2*n**4))/T
If you want to evaluate the integral for many n, quadpy can help:
import numpy as np
from quadpy import quad
y = lambda t: 3 * t
T = 4
n = np.linspace(0.5, 4.5, 20)
w = 2 * np.pi * n / T
# for odd functions
def integrand(t):
return y(t) * np.sin(np.multiply.outer(n * w, t))
Bn = (2 / T) * quad(integrand, -T / 2, T / 2)[0]
print(Bn)
[ 2.95202424 4.88513496 4.77595051 1.32599514 -1.93954768 -0.23784853
1.11558278 -0.95397681 0.63709387 -0.4752673 0.45818227 -0.4740128
0.35943759 -0.01510463 -0.30348511 0.09861289 0.25428048 0.10030723
-0.06099483 -0.13128359]
Below is my code, I'm supposed to use the electric field equation and the given variables to create a density plot and surface plot of the equation. I'm getting "invalid dimensions for image data" probably because the function E takes multiple variables and is trying to display them all as multiple dimensions. I know the issue is that I have to turn E into an array so that the density plot can be displayed, but I cannot figure out how to do so. Please help.
import numpy as np
from numpy import array,empty,linspace,exp,cos,sqrt,pi
import matplotlib.pyplot as plt
lam = 500 #Nanometers
x = linspace(-10*lam,10*lam,10)
z = linspace(-20*lam,20*lam,10)
w0 = lam
E0 = 5
def E(E0,w0,x,z,lam):
E = np.zeros((len(x),len(z)))
for i in z:
for j in x:
E = ((E0 * w0) / w(z,w0,zR(w0,lam)))
E = E * exp((-r(x)**2) / (w(z,w0,zR(w0,lam)))**2)
E = E * cos((2 * pi / lam) * (z + (r(x)**2 / (2 * Rz(z,zR,lam)))))
return E
def r(x):
r = sqrt(x**2)
return r
def w(z,w0,lam):
w = w0 * sqrt(1 + (z / zR(w0,lam))**2)
return w
def Rz(z,w0,lam):
Rz = z * (1 + (zR(w0,lam) / z)**2)
return Rz
def zR(w0,lam):
zR = pi * lam
return zR
p = E(E0,w0,x,z,lam)
plt.imshow(p)
It took me way too much time and thinking but I finally figured it out after searching for similar examples of codes for similar problems. The correct code looks like:
import numpy as np
from numpy import array,empty,linspace,exp,cos,sqrt,pi
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
lam = 500*10**-9 #Nanometers
x1 = linspace(-10*lam,10*lam,100)
z1 = linspace(-20*lam,20*lam,100)
[x,y] = np.meshgrid(x1,z1)
w0 = lam
E0 = 5
r = sqrt(x**2)
zR = pi * lam
w = w0 * sqrt(1 + (y / zR)**2)
Rz = y * (1 + (zR / y)**2)
E = (E0 * w0) / w
E = E * exp((-r**2 / w**2))
E = E * cos((2 * pi / lam) * (y + (r**2 / (2 * Rz))))
def field(x,y):
lam = 500*10**-9
k = (5 * lam) / lam * sqrt(1 + (y / (pi*lam))**2)
k *= exp(((-sqrt(x**2)**2 / (lam * sqrt(1 + (y / pi * lam)**2))**2)))
k *= cos((2 / lam) * (y + ((sqrt(x**2)**2 / (2 * y * (1 + (pi * lam / y)**2))))))
return k
#Density Plot
f = field(x,y)
plt.imshow(f)
plt.show()
#Surface Plot
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x,y,E,rstride=1,cstride=1)
plt.show