How to rotate a cylinder without causing a 'sheared' appearance - python

I have plotted a 'tear drop' shaped cylinder in matplotlib. To obtain the tear drop shape I plotted a normal cylinder from theta = 0 to theta = pi and an ellipse from theta = pi to theta = 2pi. However I am now trying to 'spin' the cylinder around it's axis which here is given conveniently by the z-axis.
I tried using the rotation matrix for rotating around the z-axis which Wikipedia gives as:
However when I try to rotate through -pi/3 radians, the cylinder becomes very disfigured.
Is there anyway to prevent this from happening?
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(0,2*pi, 1200)
Z = np.linspace(0,5,1000+600)
Z,theta = np.meshgrid(Z, theta)
X = []
Y = []
R = 0.003
#calculate the x and y values
for i in theta:
cnt = 0
tempX = []
tempY = []
for j in i:
#circle
if(i[0]<=pi):
tempX.append(R*cos(j))
tempY.append(R*sin(j))
cnt+=1
#ellipse
else:
tempX.append(R*cos(j))
tempY.append(0.006*sin(j))
X.append(tempX)
Y.append(tempY)
X1 = np.array(X)
Y1 = np.array(Y)
#rotate around the Z axis
a = -pi/3
for i in range(len(X)):
X1[i] = cos(a)*X1[i]-sin(a)*Y1[i]
Y1[i] = sin(a)*X1[i]+cos(a)*Y1[i]
#plot
ax.plot_surface(X1,Y1,Z,linewidth = 0, shade = True, alpha = 0.3)
ax.set_xlim(-0.01,0.01)
ax.set_ylim(-0.01, 0.01)
azimuth = 173
elevation = 52
ax.view_init(elevation, azimuth)
plt.show()

Your rotating is flawed: To calculate Y1[i] you need the old value of X1[i], but you already updated it. You can try something like
X1[i], Y1[i] = cos(a)*X1[i]-sin(a)*Y1[i], sin(a)*X1[i]+cos(a)*Y1[i]
if you want to make the matrix multiplication a bit more obvious (and fix the bug) you could also do the following (please doublecheck that the matrix is correct and that the multiplication is in the right order, I did not test this):
rotation_matrix = np.array([[cos(a), -sin(a)], [sin(a), cos(a)]])
x, y = zip(*[(x,y) # rotation_matrix for x,y in zip(x,y)])
the # is new in 3.5 and for numpy array it's defined to be the matrix multiplication. If you are on a version below 3.5 you can use np.dot.
The zip(*...) is necessary to get a pair of lists instead of a list of pairs. See also this answer

Related

Plotting arrows perpendicular to coordinates

I have a plot like this, plotting a semicircle with x and y
I want to add arrows at each point like so (ignore the horrible paint job):
Is there an easy way to add arrows perpendicular to the plot?
Current code:
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
y = k + np.sqrt(r**2 - (x-h)**2)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
PERPENDICULAR TO THE TANGENT OF THE CURVE I'M SORRY I FORGOT TO ADD THIS
A point in space has no idea what "perpendicular" means, but assuming your y is some function of x that has a derivate, you can think of the derivate of the function at some point to be the tangent of the curve at that point, and to get a perpendicular vector you just need to rotate the vector counter-clockwise 90 degrees:
x1, y1 = -y0, x0
We know that these points come from a circle. So given three points we can easily find the center using basic geometry notions. If you need a refresher, take a look here.
For this particular case, the center is at the origin. Knowing the center coordinates, the normal at each point is just the vector from the center to the point itself. Since the center is the origin, the normals' components are just given by the coordinates of the points themselves.
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
center = np.array([0.0, 0.0])
plt.scatter(x, y)
plt.quiver(x, y, x, y, width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
If you are in a hurry and you do not have time to implement equations, you could use the scikit-spatial library in the following way:
from skspatial.objects import Circle, Vector, Points
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
points = Points(np.vstack((x, y)).T)
circle = Circle.best_fit(np.vstack((x, y)).T)
center = circle.point
normals = np.array([Vector.from_points(center, point) for point in points])
plt.scatter(x, y)
plt.quiver(x, y, normals[:, 0], normals[:, 1], width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
Postulate of blunova's and simon's answers is correct, generally speaking: points have no normal, but curve have; so you need to rely on what you know your curve is. Either, as blunova described it, by the knowledge that it is a circle, and computing those normal with ad-hoc computation from that knowledge.
Or, as I am about to describe, using the function f such as y=f(x). and using knowledge on what is the normal to such a (x,f(x)) chart.
Here is your code, written with such a function f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
def f(x):
return k + np.sqrt(r**2 - (x-h)**2)
y=f(x)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
So, all I did here is rewriting your line y=... in the form of a function.
From there, it is possible to compute the normal to each point of the chart (x,f(x)).
The tangent to a point (x,f(x)) is well known: it is vector (1,f'(x)), where f'(x) is the derivative of f. So, normal to that is (-f'(x), 1).
Divided by √(f'(x)²+1) to normalize this vector.
So, just use that as entry to quiver.
First compute a derivative of your function
dx=0.001
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
Then, just
plt.quiver(x, f(x), -fprime(x), 1)
Or, to have all vector normalized
plt.quiver(x, f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
(note that fprime and the normalization part are all vectorizable operation, so it works with x being a arange)
All together
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return k+ np.sqrt(r**2 - (x-h)**2)
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-4,4)
plt.show()
That is almost an exact copy of your code, but for the quiver line, and with the addition of fprime.
One other slight change, specific to your curve, is that I changed x range to ensure the computability of fprime (if first x is x0, then fprime need f(x0-dx) which does not exist because of sqrt. Likewise for x1. So, first x is x0+dx, and last is x1-dx, which is visually the same)
That is the main advantage of this solution over blunova's: it is your code, essentially. And would work if you change f, without assuming that f is a circle. All that is assume is that f is derivable (and if it were not, you could not define what those normal are anyway).
For example, if you want to do the same with a parabola instead, just change f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return x**2
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-2,5)
plt.show()
All I changed here is the f formula. Not need for a new reasoning to compute the normal.
Last remark: an even more accurate version (not forcing the approximate computation of fprime with a dx) would be to use sympy to define f, and then compute the real, symbolic, derivative of f. But that doesn't seem necessary for your case.

Creating a structured non-rectangular mesh in python

I am trying to model the temperature distribution in a rocket nozzle, I am doing this with the heat equation solved using finite difference method. In order to apply this method I need my mesh to fit the geometry of my modelled rocket engine. I have trouble doing this as the meshgrid function seems to only work for rectangular mesh's.
I tried using the code bellow, and want the mesh to only go up to the red line, but cannot find a way of doing this.
import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits import mplot3d
# Grid spacing size
Dx = 0.1
L = 10
L_T = 2
D_T = 1
D_C = 2
D_E = 5
maxtime = 10
# forming the x grid spacing
x = np.arange(0,L,Dx)
y = np.zeros(len(x))
# factor to scale loop so have integers
m = 10
# gradient of the engine line
M = (D_C-D_T)/(L_T)
#inital height of engine is at combustion point.
y[0] = D_C
for i in range(1,int(L_T*m),int(Dx*m)):
y[i] = y[i-1] - M*Dx
#Y[i] = np.arange(0,y[i])
for i in range(int(L_T*m),int(L*m),int(Dx*m)):
y[i] = y[i-1] + M*Dx
# set meshgrids
Xg , Yg = np.meshgrid(x,y)
pl.scatter(Xg,Yg)
pl.plot(x,y,c='red')

scipy fft returns null imaginary part

First of all, I apologize for being an absolute beginner in both python and signal processing.
I am trying to simulate an impulse signal (or a delta function) propagating along spatial x-axis over time. Then, I would like to perform Fourier Transformation on amplitude vs x-axis for each time and then amplitude vs t-axis for each point in space. The problem I'm facing is that the Fourier coefficients are all real valued. If I "implot" the imaginary part over spatial and temporal axis, you can see, all of these are shown to be zero. However, my understanding was that, the impulse signal at t = 0, x = 0, should have null imaginary coefficient. But after that, for all the other t and/or x's, there should be a real valued imaginary coefficient.
Please refer to this site http://madebyevan.com/dft/ where one can interactively make waveforms and observe the Fourier Transformation. In the f(x) box, please put "spike(x-0)", "spike(x-1)" etc. to simulate my problem and expected result.
I have tried the following code using scipy.fftpack. There are some extra lines to analyze the impulse signal travelling in x axis and x-t plane.
import numpy as np
from numpy import pi
import matplotlib.pyplot as plt
from scipy import signal
import math
import scipy.fftpack
from scipy import ndimage
L = 10
k = np.pi/L
w = np.pi*2
n = 5
# Number of samplepoints
Nx = 1000
Nt = 500
# sample spacing
l = 1.0/Nx
T = 1.0/Nt
x = np.linspace(0, Nx*l*L, Nx)
t = np.linspace(0, Nt*T*L, Nt)
x = np.round(x,2)
t = np.round(t,2)
# function to produce impulse
def gw(xx, tt):
if xx == tt:
kk = 1
else:
kk = 0
return (kk)
fig = plt.figure()
yg = np.array([gw(i, j) for j in t for i in x])
YG = yg.reshape(Nt, Nx)
# how impulse propagate in x-t plane
plt.imshow(YG, interpolation='bilinear',aspect='auto')
plt.colorbar();
# how impulse propagate in x-axis for t = 2 and t = 100
fig, ax = plt.subplots()
ax.plot(x, YG[2,:], x, YG[100,:])
plt.show()
# FFT in x-axis at each point in time
yxf = np.zeros((Nt, Nx))
for i in range(Nt):
yx = YG[i,:]
yxf[i,:] = scipy.fftpack.fft(yx)
plt.imshow(np.imag(yxf[:,:Nx]), interpolation='bilinear',aspect='auto')
plt.colorbar();
plt.show()
# FFT in t-axis at each point in space
ytf = np.zeros((Nt, Nx))
for i in range(Nx):
yt = YG[:,i]
ytf[:,i] = scipy.fftpack.fft(yt)
plt.imshow(np.imag(ytf[:Nt,:]), interpolation='bilinear',aspect='auto')
plt.colorbar();
plt.show()

Colormap by vector direction in python using quiver

I am trying to colormap vectors by their direction using quiver, in python 2.7. I read in my data from a text file, get the angle of each vector and normalize so that everything falls between [0,1]. However, when I go to plot the color it comes out that the same color indicates two different directions.
Also, it might be relevant that I'm not plotting the data on a mesh but as points with velocity vectors. Here's my code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as col
import sys
data = np.loadtxt("" + str(sys.argv[1]) + "")
x_dat = data[:,0]
y_dat = data[:,1]
vx_dat = data[:,2]
vy_dat = data[:,3]
rad = np.arctan(vy_dat/vx_dat) * 2
theta = np.degrees(rad)
for i in range(len(theta)):
if theta[i] < 0:
theta[i] += 360
theta[i] /= 360
I realize I don't need to convert to degrees. Then I normalize my vectors:
N = np.array([])
for i in range(len(vx_dat)):
N = np.append(N,np.sqrt(vx_dat[i]**2 + vy_dat[i]**2))
vx_dat[i] = vx_dat[i]/N[i]
vy_dat[i] = vy_dat[i]/N[i]
And finally, I plot it:
q = plt.quiver(x_dat, y_dat, vx_dat, vy_dat, theta, units='dots', angles='xy', cmap = 'Blues')
Where 'theta' should map the color for each vector based on direction. However here's what I get out (I zoomed in so it'd be easier to see):
How can I fix this so that each direction gets a unique color?
As suggested use the np.arctan2(V,U) function to calculate your colour value. To return a unique colour use a different colour map, 'Blues' can only return different shades of blue. A cyclic colour map like 'hsv' is more suitable. Try the following:
q = plt.quiver(x_dat, y_dat, vx_dat, vy_dat, np.arctan2(vy_dat, vx_dat), units='dots', angles='xy', cmap = 'hsv')

Mayavi: interpolate face colors in triangular_mesh

I have pieced together the
following code to plot a triangular mesh with the colors specified by an
additional scalar function:
#! /usr/bin/env python
import numpy as np
from mayavi import mlab
# Create cone
n = 8
t = np.linspace(-np.pi, np.pi, n)
z = np.exp(1j*t)
x = z.real.copy()
y = z.imag.copy()
z = np.zeros_like(x)
triangles = [(0, i, i+1) for i in range(n)]
x = np.r_[0, x]
y = np.r_[0, y]
z = np.r_[1, z]
t = np.r_[0, t]
# These are the scalar values for each triangle
f = np.mean(t[np.array(triangles)], axis=1)
# Plot it
mesh = mlab.triangular_mesh(x, y, z, triangles,
representation='wireframe',
opacity=0)
cell_data = mesh.mlab_source.dataset.cell_data
cell_data.scalars = f
cell_data.scalars.name = 'Cell data'
cell_data.update()
mesh2 = mlab.pipeline.set_active_attribute(mesh,
cell_scalars='Cell data')
mlab.pipeline.surface(mesh2)
mlab.show()
This works reasonably well. However, instead of having every triangle
with a uniform color and sharp transitions between the triangles, I'd
much rather have a smooth interpolation over the entire surface.
Is there a way to do that?
I think you want to use point data instead of cell data. With cell data, a single scalar value is not localized to any point. It is assigned to the entire face. It looks like you just want to assign the t data to the vertices instead. The default rendering of point scalars will smoothly interpolate across each face.
point_data = mesh.mlab_source.dataset.point_data
point_data.scalars = t
point_data.scalars.name = 'Point data'
point_data.update()
mesh2 = mlab.pipeline.set_active_attribute(mesh,
point_scalars='Point data')

Categories

Resources