Python: is it possible to interpolate a matrix in a MATLAB style? - python

I have difficulty to interpolate a matrix/data frame in python.
Suppose we I have a matrix M = 3x4 and x = [1 3 5], y = [0.1 0.4 0.5 0.7]
This is my way to do interpolate and then plot in Matlab.
xq = 1:1:5;
yq = 0.1:0.1:1;
[xq,yq] = meshgrid(xq,yq);
zq = interp2(y,x,M,xq,yq);
figure
h=pcolor(xq,yq,zq)
set(h,'EdgeColor','none')
This is a possible way in Python
from scipy import interpolate
import numpy as np
def my_interp(X, Y, Z, x, y, spn=3):
xs,ys = map(np.array,(x,y))
z = np.zeros(xs.shape)
for i,(x,y) in enumerate(zip(xs,ys)):
# get the indices of the nearest x,y
xi = np.argmin(np.abs(X[0,:]-x))
yi = np.argmin(np.abs(Y[:,0]-y))
xlo = max(xi-spn, 0)
ylo = max(yi-spn, 0)
xhi = min(xi+spn, X[0,:].size)
yhi = min(yi+spn, Y[:,0].size)
# make slices of X,Y,Z that are only a few items wide
nX = X[xlo:xhi, ylo:yhi]
nY = Y[xlo:xhi, ylo:yhi]
nZ = Z[xlo:xhi, ylo:yhi]
intp = interpolate.interp2d(nX, nY, nZ)
z[i] = intp(x,y)[0]
return z
zq = my_interp(y, x, M, xq, yq)

As I noted in a comment, your code is 1:1 translatable to python using the necessary libraries. You need numpy for linspace/meshgrid, matplotlib.pyplot for pcolor(mesh), scipy.interpolate for griddata. I was going to say "interp2d is available but don't use it, but it turns out that you need extrapolation outside the convex hull of your input data, so griddata won't cut it. Here's a solution with interp2d, but take the results with a grain of salt:
import numpy as np
import scipy.interpolate as interp
import matplotlib.pyplot as plt
# input
xv = np.array([1, 3, 5])
yv = np.array([0.1, 0.4, 0.5, 0.7])
x,y = np.meshgrid(xv,yv)
M = np.random.rand(4,3)
xqv = np.arange(1,6)
yqv = np.arange(0.1,1.1,0.1)
xq,yq = np.meshgrid(xqv,yqv)
zqfun = interp.interp2d(x,y,M)
zq = zqfun(xqv,yqv)
plt.figure()
#h = plt.pcolor(xq,yq,zq)
h = plt.pcolormesh(xq,yq,zq) # <-- same thing but faster
Result(left) compared with your MATLAB original (right; after fixing the order x,y,M in interp2d):
You can see that the results differ at the sides, this is because MATLAB always throws away the last row and column of the data, while matplotlib doesn't.

Related

How to plot a function with a vector and matrix in python?

But function f is a problem because I don't know how to combine the mesh with the matrix, is there a smart way to solve this problem?
It looks like your code for g is very close to the one for f. You could just define your M matrix and include it in the matrix multiplication. See code below for more details:
import numpy as np
import matplotlib.pyplot as plt
def f_function(diagonal_values):
fig = plt.figure(figsize=(15,8))
data = np.linspace(-4, 4, 20)
x_1, x_2 = np.meshgrid(data, data, indexing="ij")
fx = np.zeros_like(x_1)
#Defining M
M=np.diag(diagonal_values)
print(M)
for i in range(data.shape[0]):
for j in range(data.shape[0]):
x = np.array([x_1[i,j], x_2[i,j]])
f = x.T # M # x
fx[i,j] = f
ax = fig.add_subplot(121, projection="3d")
surf = ax.plot_surface(x_1, x_2, fx)
ax.set_xlabel("x_1")
ax.set_ylabel("x_2")
ax.set_zlabel("f")
#Randomly picking diagonal values
diag_values=np.random.uniform(0,10,2)
print('Diagonal values: '+str(diag_values))
f_function(np.array(diag_values))
The output gives:
Diagonal values: [8.62030848 2.68367524]
And the plot:

How do you slice a meshgrid array of points in numpy so that the order is maintained?

I have a grid of points in numpy that I am trying to interpolate. The code that generates them is as follows:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.interpolate import Rbf
x = np.linspace(-1, 1, 100)[:, None] # inject trailing singleton for broadcasting
a, b = np.mgrid[0:1:0.1, 0:1:0.1]
#create dose distribution matrix
D = a.ravel() * x**2 + b.ravel() * x + 1
#perform SVD Decomposition
U, S, V = np.linalg.svd(D)
fig = plt.figure()
ax = plt.axes(projection="3d")
c0 = V[0]
#ax.scatter(a, b, c0)
#delete some points for testing interpolation
a_trng = a.ravel()
a_trng = np.append(a_trng[0:50], a_trng[70:100])
b_trng = b.ravel()
b_trng = np.append(b_trng[0:50], b_trng[70:100])
a_test = a_trng[50:70]
b_test = b_trng[50:70]
c0_trng = np.append(c0[0:50], c0[70:100])
c0_test = c0[50:70]
rbfi = Rbf(a_trng, b_trng, c0_trng)
ci = rbfi(a, b)
ax.scatter(a_trng, b_trng, c0_trng)
ax.scatter(a_test, b_test, c0_test)
I am trying to remove some points, the test points to create trng points for the interpolation. However, the ```test`` points are moved. How can I slice the meshgrid so that this doesn't occur?
I think you should use:
a_test = a.ravel()[50:70]
b_test = b.ravel()[50:70]

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

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

Create a 2D array that is the product of two functions

I am working on a visualization and trying to create a 2D array that is the product of a normalized Gaussian function on the X axis and a normalized exponential function on the Y axis (using Python).
I would use NumPy for this. You can use np.meshgrid to create the (X, Y) axes and use NumPy's vectorized functions to create the function on these coordinates. The array f below is your two-dimensional array, here containing the product of exp(-X/4) and exp(-((Y-2)/1.5)**2). (Substitute your own normalized functions here.)
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0,10,100)
y = np.linspace(0,5,100)
X, Y = np.meshgrid(x, y)
f = np.exp(-X/4.) * np.exp(-((Y-2)/1.5)**2)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(f)
plt.show()
If you can't or don't want to use NumPy, you'll have to loop by hand and use conventional math functions:
import math
dx, dy = 0.1, 0.05
nx, ny = 101, 101
f = [[None]*nx for i in range(ny)]
for ix in range(nx):
x = xmin + dx*ix
for iy in range(ny):
y = ymin + dy*iy
f[iy][ix] = math.exp(-x/4.) * math.exp(-((y-2)/1.5)**2)
I would use numpy for this, because numpy makes it very simple to do what you want. If you can't use it, then something like the following should work:
import math
def gauss(x, mu=0.0, sigma=1.0):
return 1.0 / math.sqrt(2.0*math.pi*sigma**2) * math.exp(-0.5*(x-mu)**2/sigma**2)
def exponential(x, lam=1.0):
return lam * math.exp(-lam * x)
# X values from -10 to 10 with 0.01 step size
xvals = [x * 0.01 for x in range(-1000, 1001)]
# Y values from 0 to 10 with 0.01 step size
yvals = [y * 0.01 for y in range(0, 1001)]
# Calculate your function at the grid points
f = [[gauss(x)*exponential(y) for x in xvals] for y in yvals]

Using numpy/scipy to calculate iso-surface from 3D array

I have a 3D numpy array that contains the values of a given function. I want to calculate a 2D iso-surface, or a set of iso-surfaces that represent certain values of this function.
In this particular case, each 1D column (column = myarray[i, j, :]) of the 3D array can be treated independently. So what I would like to know are the last index positions (2D array) where the function is equal to a certain value, say myvalue.
Some (slow) code to exemplify:
# myarray = 3D ndarray
import numpy as np
from scipy import interpolate
result = np.zeros(nx, ny)
z_values = np.arange(nz)
for i in range(nx):
for j in range(ny):
f = interpolate.interp1d(my_array[i, j], z_values)
result[i, j] = f(myvalue)
I know this can be sped up a bit with np.ndenumerate and other tricks, but was wondering if there is already a simpler way of doing this kind of iso-surface. I couldn't find anything in ndimage or other libraries. I know that mayavi2 and vtk have a lot of tools to deal with iso-surfaces, but my aim here is not visualisation -- I want to perform calculations on those iso-surface values, not display them. Plus, a lot of the iso-surface methods of vtk seem to involve polygons and the like, and what I need is just a 2D array of positions for each iso-surface value.
Using only numpy you can get a good solution using argsort, sort, take and the proper array manipulation. The function below uses a weighted average to compute the iso-surface:
def calc_iso_surface(my_array, my_value, zs, interp_order=6, power_parameter=0.5):
if interp_order < 1: interp_order = 1
from numpy import argsort, take, clip, zeros
dist = (my_array - my_value)**2
arg = argsort(dist,axis=2)
dist.sort(axis=2)
w_total = 0.
z = zeros(my_array.shape[:2], dtype=float)
for i in xrange(int(interp_order)):
zi = take(zs, arg[:,:,i])
valuei = dist[:,:,i]
wi = 1/valuei
clip(wi, 0, 1.e6, out=wi) # avoiding overflows
w_total += wi**power_parameter
z += zi*wi**power_parameter
z /= w_total
return z
This solution does not handle situations where there is more than one z corresponding to my_value. An application example to build the iso-surfaces below is given in the following code:
from numpy import meshgrid, sin, cos, pi, linspace
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
dx = 100; dy = 50; dz = 25
nx = 200; ny = 100; nz = 100
xs = linspace(0,dx,nx)
ys = linspace(0,dy,ny)
zs = linspace(0,dz,nz)
X,Y,Z = meshgrid( xs, ys, zs, dtype=float)
my_array = sin(0.3*pi+0.4*pi*X/dx)*sin(0.3*pi+0.4*pi*Y/dy)*(Z/dz)
fig = plt.figure()
ax = fig.gca(projection='3d')
z = calc_iso_surface( my_array, my_value=0.1, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='g')
z = calc_iso_surface( my_array, my_value=0.2, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='y')
z = calc_iso_surface( my_array, my_value=0.3, zs=zs, interp_order=6 )
ax.plot_surface(X[:,:,0], Y[:,:,0], z, cstride=4, rstride=4, color='b')
plt.ion()
plt.show()
You can also play with different interpolation functions. See below one example that takes the average of the two closest zs:
def calc_iso_surface_2(my_array, my_value, zs):
'''Takes the average of the two closest zs
'''
from numpy import argsort, take
dist = (my_array - my_value)**2
arg = argsort(dist,axis=2)
z0 = take(zs, arg[:,:,0])
z1 = take(zs, arg[:,:,1])
z = (z0+z1)/2
return z

Categories

Resources