Plot sphere with Julia and PyPlot - matplotlib

Recently I tried to plot a sphere using PyPlot/Julia and unfortunately it was harder than I thought.
Probably there's something wrong with points generation, but I can't figure out why my implementation didn't work. Although everything is fine with original python code.
I've tried to adapt demo2 from matplotlib surface plot doc as MWE:
using PyPlot
u = linspace(0,2*π,100);
v = linspace(0,π,100);
x = cos(u).*sin(v);
y = sin(u).*sin(v);
z = cos(v);
surf(x,y,z)
And I'm getting instead of
So, what's exactly wrong in my Julia implementation?

x, y and z should be matrices, not vectors -- otherwise you only have a curve drawn on the sphere, instead of the surface itself.
using PyPlot
n = 100
u = linspace(0,2*π,n);
v = linspace(0,π,n);
x = cos(u) * sin(v)';
y = sin(u) * sin(v)';
z = ones(n) * cos(v)';
# The rstride and cstride arguments default to 10
surf(x,y,z, rstride=4, cstride=4)
The curve initially drawn corresponds to the diagonal of those matrices.
plot( diag(x), diag(y), diag(z), color="yellow", linewidth=3 )

This no longer works in Julia 1.1.2 to draw the sphere. Use this instead
using PyPlot
n = 100
u = range(0,stop=2*π,length=n);
v = range(0,stop=π,length=n);
x = cos.(u) * sin.(v)';
y = sin.(u) * sin.(v)';
z = ones(n) * cos.(v)';
# The rstride and cstride arguments default to 10
surf(x,y,z, rstride=4, cstride=4)

Related

How to create an adjacency matrix from VTK / STL file?

I have a .vtk mesh with N points and F polygon (triangle) faces, and i'd like to build an N x N adjacency matrix to represent the connectivity between the points.
I've tried mesh.GetLines().GetData() however, this returns an empty array. I've also tried mesh.GetPolys().GetData() and this gives an flat array of 4 x F elements.
From inspecting the .vtk file, I know that each face is given as 3, point1, point2, point3 where I assume 3 indicates the faces are triangular. From here it is possible to create the adjacency matrix by iterating through the list, however I'd like to know there if there is any inbuilt VTK functions that can do the job for me.
I also have the mesh in .stl format, if that helps.
Thanks
It is possible to create the adj matrix by iterating over the vtk polys faces and adding a 1 in an empty matrix for every edge connection like so:
polygons = vtk_to_numpy(mesh.GetPolys().GetData())
adj = np.zeros((len(coords), len(coords)))
print('ADJACENCY MATRIX SHAPE: ',adj.shape)
for i in range(0, int(len(polygons)), 4):
line = polygons[i:i+4] # 3, point1, point2, point3
face = line[1:] # point1, point2, point3
n1, n2, n3 = face[0], face[1], face[2]
adj[n1,n2] = 1
adj[n2,n1] = 1
adj[n1,n3] = 1
adj[n3,n1] = 1
adj[n2,n3] = 1
adj[n3,n2] = 1
Altenatively, if using .stl files, this can be done with the trimesh and networkx packages, like so:
mesh = trimesh.load_mesh('mesh.stl')
adj = networkx.adjacency_matrix(trimesh.graph.vertex_adjacency_graph(mesh))

Solve motion equations for first ODE using scipy

I would like to solve motion first order ODE equations using scipy solve_ivp function. I can see that I'm doing something wrong because this should be an ellipse but I'm plotting only four points. Are you able to spot the mistake?
import math
import matplotlib.pyplot as plt
import numpy as np
import scipy.integrate
gim = 4*(math.pi**2)
x0 = 1 #x-position of the center or h
y0 = 0 #y-position of the center or k
vx0 = 0 #vx position
vy0 = 1.1* 2* math.pi #vy position
initial = [x0, y0, vx0, vy0] #initial state of the system
time = np.arange(0, 1000, 0.01) #period
def motion(t, Z):
dx = Z[2] # vx
dy = Z[3] # vy
dvx = -gim/(x**2+y**2)**(3/2) * x * Z[2]
dvy = -gim/(x**2+y**2)**(3/2) * y * Z[3]
return [dx, dy, dvx, dvy]
sol = scipy.integrate.solve_ivp(motion, t_span=time, y0= initial, method='RK45')
plt.plot(sol.y[0],sol.y[1],"x", label="Scipy RK45 solution")
plt.show()
The code should not be able to run from a fresh workspace. The variables x,y that are used in the gravitation formula are not declared anywhere. So insert the line x,y = Z[:2] or similar.
The gravitation formula usually does not contain the velocity components. Remove Z[2], Z[3].
Check again what the time span and evaluation times arguments expect. The time span takes the first two values from the array. So change to t_span=time[[0,-1]] to build the pair of first and last time value.
The second plot suffers from insufficient evaluation points, the line segments used are too large. With your time array that should not be a problem.

How to get the array corresponding exaclty to contourf?

I have a rather complicated two dimensional function that I represent with few levels using contourf. How do I get the array corresponding exactly to filled contours ?
For example :
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0,1,100)
y = np.linspace(0,1,100)
[X,Y] = np.meshgrid(x,y)
Z = X**2 + Y**2
plt.subplot(1,2,1)
plt.imshow(Z, origin = 'lower')
plt.subplot(1,2,2)
plt.contourf(Z,[0,1,2])
plt.show()
plt.savefig('test.png')
I'd like to have the array from the contourplot that returns constant values between the different contours.
So far I did some thresholding:
test = Z
test[test<1] = 0
test[test>=1] = 2
plt.contourf(X,Y,test,[0,1,2])
plt.savefig('test1.png')
But contourf does a much better job at interpolating things. Furthermore, thresholding 'by hand' becomes a bit long when I have multiple contours.
I guess that, because contourf does all the job, there is a way to get this from the contourf object ?
P.S : why does my first piece of code produces subplots of different sizes ?

Annotation box does not appear in matplotlib

The planned annotation box does not appear on my plot, however, I've tried a wide range of values for its coordinates.
What's wrong with that?!
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def f(s,t):
a = 0.7
b = 0.8
Iext= 0.5
tau = 12.5
v = s[0]
w = s[1]
dndt = v - np.power(v,3)/3 - w + Iext
dwdt = (v + a - b * w)/tau
return [dndt, dwdt]
t = np.linspace(0,200)
s0=[1,1]
s = odeint(f,s0,t)
plt.plot(t,s[:,0],'b-', linewidth=1.0)
plt.xlabel(r"$t(sec.)$")
plt.ylabel(r"$V (volt)$")
plt.legend([r"$V$"])
annotation_string = r"$I_{ext}=0.5$"
plt.text(15, 60, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
plt.show()
The coordinates to plt.text are data coordinates by default. This means in order to be present in the plot they should not exceed the data limits of your plot (here, ~0..200 in x direction, ~-2..2 in y direction).
Something like plt.text(10,1.8) should work.
The problem with that is that once the data limits change (because you plot something different or add another plot) the text item will be at a different position inside the canvas.
If this is undesired, you can specify the text in axes coordinates (ranging from 0 to 1 in both directions). In order to place the text always in the top left corner of the axes, independent on what you plot there, you can use e.g.
plt.text(0.03,0.97, annotation_string, bbox=dict(facecolor='red', alpha=0.5),
transform=plt.gca().transAxes, va = "top", ha="left")
Here the transform keyword tells the text to use Axes coordinates, and va = "top", ha="left" means, that the top left corner of the text should be the anchor point.
The annotation is appearing far above your plot because you have given a 'y' coordinate of 60, whereas your plot ends at '2' (upwards).
Change the second argument here:
plt.text(15, 60, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
It needs to be <=2 to show up on the plot itself. You may also want to change the x coorinate (from 15 to something less), so that it doesn't obscure your lines.
e.g.
plt.text(5, 1.5, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
Don't be alarmed by my (5,1.5) suggestion, I would then add the following line to the top of your script (beneath your imports):
rcParams['legend.loc'] = 'best'
This will choose a 'best fit' for your legend; in this case, top left (just above your annotation). Both look quite neat then, your choice though :)

inverse of FFT not the same as original function

I don't understand why the ifft(fft(myFunction)) is not the same as my function. It seems to be the same shape but a factor of 2 out (ignoring the constant y-offset). All the documentation I can see says there is some normalisation that fft doesn't do, but that ifft should take care of that. Here's some example code below - you can see where I've bodged the factor of 2 to give me the right answer. Thanks for any help - its driving me nuts.
import numpy as np
import scipy.fftpack as fftp
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
def fourier_series(x, y, wn, n=None):
# get FFT
myfft = fftp.fft(y, n)
# kill higher freqs above wavenumber wn
myfft[wn:] = 0
# make new series
y2 = fftp.ifft(myfft).real
# find constant y offset
myfft[1:]=0
c = fftp.ifft(myfft)[0]
# remove c, apply factor of 2 and re apply c
y2 = (y2-c)*2 + c
plt.figure(num=None)
plt.plot(x, y, x, y2)
plt.show()
if __name__=='__main__':
x = np.array([float(i) for i in range(0,360)])
y = np.sin(2*np.pi/360*x) + np.sin(2*2*np.pi/360*x) + 5
fourier_series(x, y, 3, 360)
You're removing half the spectrum when you do myfft[wn:] = 0. The negative frequencies are those in the top half of the array and are required.
You have a second fudge to get your results which is taking the real part to find y2: y2 = fftp.ifft(myfft).real (fftp.ifft(myfft) has a non-negligible imaginary part due to the asymmetry in the spectrum).
Fix it with myfft[wn:-wn] = 0 instead of myfft[wn:] = 0, and remove the fudges. So the fixed code looks something like:
import numpy as np
import scipy.fftpack as fftp
import matplotlib.pyplot as plt
def fourier_series(x, y, wn, n=None):
# get FFT
myfft = fftp.fft(y, n)
# kill higher freqs above wavenumber wn
myfft[wn:-wn] = 0
# make new series
y2 = fftp.ifft(myfft)
plt.figure(num=None)
plt.plot(x, y, x, y2)
plt.show()
if __name__=='__main__':
x = np.array([float(i) for i in range(0,360)])
y = np.sin(2*np.pi/360*x) + np.sin(2*2*np.pi/360*x) + 5
fourier_series(x, y, 3, 360)
It's really worth paying attention to the interim arrays that you are creating when trying to do signal processing. Invariably, there are clues as to what is going wrong that should direct you to the problem. In this case, you taking the real part masked the problem and made your task more difficult.
Just to add another quick point: Sometimes taking the real part of the resultant array is exactly the correct thing to do. It's often the case that you end up with an imaginary part to the signal output which is just down to numerical errors in the input to the inverse FFT. Typically this manifests itself as very small imaginary values, so taking the real part is basically the same array.
You are killing the negative frequencies between 0 and -wn.
I think what you mean to do is to set myfft to 0 for all frequencies outside [-wn, wn].
Change the following line:
myfft[wn:] = 0
to:
myfft[wn:-wn] = 0