I'm using interpolate.interp2d() to fit a 2-D spline over a function. How can I get the first derivative of the spline w.r.t. each of the dependent variables? Here is my code so far, Z are the descrete points on a mesh-grid that I have
from scipy import interpolate
YY, XX = np.meshgrid(Y, X)
f = interpolate.interp2d(AA, XX, Z, kind='cubic')
So, I need df/dx and df/dy. Note also that my Y-grid is not evenly spaced. I guess I can numerically differentiate Z and then fit a new spline, but it seemed like too much hassle. Is there an easier way?
You can differentiate the output of interp2d by using the function bisplev on the tck property of the interpolant with the optional arguments dx and dy.
If you've got some meshed data which you've interpolated:
X = np.arange(5.)
Y = np.arange(6., 11)
Y[0] = 4 # Demonstrate an irregular mesh
YY, XX = np.meshgrid(Y, X)
Z = np.sin(XX*2*np.pi/5 + YY*YY*2*np.pi/11)
f = sp.interpolate.interp2d(XX, YY, Z, kind='cubic')
xt = np.linspace(X.min(), X.max())
yt = np.linspace(Y.min(), Y.max())
then you can access the appropriate structure for bisplev as f.tck: the partial derivative of f with respect to x can be evaluated as
Z_x = sp.interpolate.bisplev(xt, yt, f.tck, dx=1, dy=0)
Edit: From this answer, it looks like the result of interp2d can itself take the optional arguments of dx and dy:
Z_x = f(xt, yt, dx=1, dy=0)
Related
I have several points on the unit sphere that are distributed according to the algorithm described in https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf (and implemented in the code below). On each of these points, I have a value that in my particular case represents 1 minus a small error. The errors are in [0, 0.1] if this is important, so my values are in [0.9, 1].
Sadly, computing the errors is a costly process and I cannot do this for as many points as I want. Still, I want my plots to look like I am plotting something "continuous".
So I want to fit an interpolation function to my data, to be able to sample as many points as I want.
After a little bit of research I found scipy.interpolate.SmoothSphereBivariateSpline which seems to do exactly what I want. But I cannot make it work properly.
Question: what can I use to interpolate (spline, linear interpolation, anything would be fine for the moment) my data on the unit sphere? An answer can be either "you misused scipy.interpolation, here is the correct way to do this" or "this other function is better suited to your problem".
Sample code that should be executable with numpy and scipy installed:
import typing as ty
import numpy
import scipy.interpolate
def get_equidistant_points(N: int) -> ty.List[numpy.ndarray]:
"""Generate approximately n points evenly distributed accros the 3-d sphere.
This function tries to find approximately n points (might be a little less
or more) that are evenly distributed accros the 3-dimensional unit sphere.
The algorithm used is described in
https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf.
"""
# Unit sphere
r = 1
points: ty.List[numpy.ndarray] = list()
a = 4 * numpy.pi * r ** 2 / N
d = numpy.sqrt(a)
m_v = int(numpy.round(numpy.pi / d))
d_v = numpy.pi / m_v
d_phi = a / d_v
for m in range(m_v):
v = numpy.pi * (m + 0.5) / m_v
m_phi = int(numpy.round(2 * numpy.pi * numpy.sin(v) / d_phi))
for n in range(m_phi):
phi = 2 * numpy.pi * n / m_phi
points.append(
numpy.array(
[
numpy.sin(v) * numpy.cos(phi),
numpy.sin(v) * numpy.sin(phi),
numpy.cos(v),
]
)
)
return points
def cartesian2spherical(x: float, y: float, z: float) -> numpy.ndarray:
r = numpy.linalg.norm([x, y, z])
theta = numpy.arccos(z / r)
phi = numpy.arctan2(y, x)
return numpy.array([r, theta, phi])
n = 100
points = get_equidistant_points(n)
# Random here, but costly in real life.
errors = numpy.random.rand(len(points)) / 10
# Change everything to spherical to use the interpolator from scipy.
ideal_spherical_points = numpy.array([cartesian2spherical(*point) for point in points])
r_interp = 1 - errors
theta_interp = ideal_spherical_points[:, 1]
phi_interp = ideal_spherical_points[:, 2]
# Change phi coordinate from [-pi, pi] to [0, 2pi] to please scipy.
phi_interp[phi_interp < 0] += 2 * numpy.pi
# Create the interpolator.
interpolator = scipy.interpolate.SmoothSphereBivariateSpline(
theta_interp, phi_interp, r_interp
)
# Creating the finer theta and phi values for the final plot
theta = numpy.linspace(0, numpy.pi, 100, endpoint=True)
phi = numpy.linspace(0, numpy.pi * 2, 100, endpoint=True)
# Creating the coordinate grid for the unit sphere.
X = numpy.outer(numpy.sin(theta), numpy.cos(phi))
Y = numpy.outer(numpy.sin(theta), numpy.sin(phi))
Z = numpy.outer(numpy.cos(theta), numpy.ones(100))
thetas, phis = numpy.meshgrid(theta, phi)
heatmap = interpolator(thetas, phis)
Issue with the code above:
With the code as-is, I have a
ValueError: The required storage space exceeds the available storage space: nxest or nyest too small, or s too small. The weighted least-squares spline corresponds to the current set of knots.
that is raised when initialising the interpolator instance.
The issue above seems to say that I should change the value of s that is one on the parameters of scipy.interpolate.SmoothSphereBivariateSpline. I tested different values of s ranging from 0.0001 to 100000, the code above always raise, either the exception described above or:
ValueError: Error code returned by bispev: 10
Edit: I am including my findings here. They can't really be considered as a solution, that is why I am editing and not posting as an answer.
With more research I found this question Using Radial Basis Functions to Interpolate a Function on a Sphere. The author has exactly the same problem as me and use a different interpolator: scipy.interpolate.Rbf. I changed the above code by replacing the interpolator and plotting:
# Create the interpolator.
interpolator = scipy.interpolate.Rbf(theta_interp, phi_interp, r_interp)
# Creating the finer theta and phi values for the final plot
plot_points = 100
theta = numpy.linspace(0, numpy.pi, plot_points, endpoint=True)
phi = numpy.linspace(0, numpy.pi * 2, plot_points, endpoint=True)
# Creating the coordinate grid for the unit sphere.
X = numpy.outer(numpy.sin(theta), numpy.cos(phi))
Y = numpy.outer(numpy.sin(theta), numpy.sin(phi))
Z = numpy.outer(numpy.cos(theta), numpy.ones(plot_points))
thetas, phis = numpy.meshgrid(theta, phi)
heatmap = interpolator(thetas, phis)
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
colormap = cm.inferno
normaliser = mpl.colors.Normalize(vmin=numpy.min(heatmap), vmax=1)
scalar_mappable = cm.ScalarMappable(cmap=colormap, norm=normaliser)
scalar_mappable.set_array([])
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(
X,
Y,
Z,
facecolors=colormap(normaliser(heatmap)),
alpha=0.7,
cmap=colormap,
)
plt.colorbar(scalar_mappable)
plt.show()
This code runs smoothly and gives the following result:
The interpolation seems OK except on one line that is discontinuous, just like in the question that led me to this class. One of the answer give the idea of using a different distance, more adapted the the spherical coordinates: the Haversine distance.
def haversine(x1, x2):
theta1, phi1 = x1
theta2, phi2 = x2
return 2 * numpy.arcsin(
numpy.sqrt(
numpy.sin((theta2 - theta1) / 2) ** 2
+ numpy.cos(theta1) * numpy.cos(theta2) * numpy.sin((phi2 - phi1) / 2) ** 2
)
)
# Create the interpolator.
interpolator = scipy.interpolate.Rbf(theta_interp, phi_interp, r_interp, norm=haversine)
which, when executed, gives a warning:
LinAlgWarning: Ill-conditioned matrix (rcond=1.33262e-19): result may not be accurate.
self.nodes = linalg.solve(self.A, self.di)
and a result that is not at all the one expected: the interpolated function have values that may go up to -1 which is clearly wrong.
You can use Cartesian coordinate instead of Spherical coordinate.
The default norm parameter ('euclidean') used by Rbf is sufficient
# interpolation
x, y, z = numpy.array(points).T
interpolator = scipy.interpolate.Rbf(x, y, z, r_interp)
# predict
heatmap = interpolator(X, Y, Z)
Here the result:
ax.plot_surface(
X, Y, Z,
rstride=1, cstride=1,
# or rcount=50, ccount=50,
facecolors=colormap(normaliser(heatmap)),
cmap=colormap,
alpha=0.7, shade=False
)
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
You can also use a cosine distance if you want (norm parameter):
def cosine(XA, XB):
if XA.ndim == 1:
XA = numpy.expand_dims(XA, axis=0)
if XB.ndim == 1:
XB = numpy.expand_dims(XB, axis=0)
return scipy.spatial.distance.cosine(XA, XB)
In order to better see the differences,
I stacked the two images, substracted them and inverted the layer.
I want to be able to find the intersection between a line and a three-dimensional surface.
Mathematically, I have done this by taking the following steps:
Define the (x, y, z) coordinates of the line in a parametric manner. e.g. (x, y, z) = (1+t, 2+3t, 1-t)
Define the surface as a function. e.g. z = f(x, y)
Substitute the values of x, y, and z from the line into the surface function.
By solving, I would be able to get the intersection of the surface and the line
I want to know if there is a method for doing this in Python. I am also open to suggestions on more simple ways to solving for the intersection.
You can use the following code:
import numpy as np
import scipy as sc
import scipy.optimize
from matplotlib import pyplot as plt
def f(x, y):
""" Function of the surface"""
# example equation
z = x**2 + y**2 -10
return z
p0 = np.array([1, 2, 1]) # starting point for the line
direction = np.array( [1, 3, -1]) # direction vector
def line_func(t):
"""Function of the straight line.
:param t: curve-parameter of the line
:returns xyz-value as array"""
return p0 + t*direction
def target_func(t):
"""Function that will be minimized by fmin
:param t: curve parameter of the straight line
:returns: (z_line(t) - z_surface(t))**2 – this is zero
at intersection points"""
p_line = line_func(t)
z_surface = f(*p_line[:2])
return np.sum((p_line[2] - z_surface)**2)
t_opt = sc.optimize.fmin(target_func, x0=-10)
intersection_point = line_func(t_opt)
The main idea is to reformulate the algebraic equation point_of_line = point_of_surface (condition for intersection) into a minimization problem: |point_of_line - point_of_surface| → min. Due to the representation of the surface as z_surface = f(x, y) it is convenient to calculate the distance for a given t-value only on basis of the z-values. This is done in target_func(t). And then the optimal t-value is found by fmin.
The correctness and plausibility of the result can be checked with some plotting:
from mpl_toolkits.mplot3d import Axes3D
ax = plt.subplot(projection='3d')
X = np.linspace(-5, 5, 10)
Y = np.linspace(-5, 5, 10)
tt = np.linspace(-5, 5, 100)
XX, YY = np.meshgrid(X, Y)
ZZ = f(XX, YY)
ax.plot_wireframe(XX, YY, ZZ, zorder=0)
LL = np.array([line_func(t) for t in tt])
ax.plot(*LL.T, color="orange", zorder=10)
ax.plot([x], [y], [z], "o", color="red", ms=10, zorder=20)
Note that this combination of wire frame and line plots does not handle well, which part of the orange line should be below the blue wire lines of the surface.
Also note, that for this type of problem there might be any number of solutions from 0 up to +∞. This depends on the actual surface. fmin finds an local optimum, this might be a global optimum with target_func(t_opt)=0 or it might not. Changing the initial guess x0 might change which local optimum fmin finds.
I'm looking for a way to get the value of a 2d point (x,y) given a list of points with known value (x,y,value) by interpolating the values of nearby points based on distance.
The points coordinates and values are floats and they are not in a grid or any other kind of pattern.
Maybe you 're looking for scipy's interpolate:
from scipy import interpolate
'''
Interpolate over a 2-D grid.
x, y and z are arrays of values used to approximate some function f: z = f(x, y).
This class returns a function whose call method uses spline interpolation
to find the value of new points.
'''
x = np.arange(-5.01, 5.01, 0.25)
y = np.arange(-5.01, 5.01, 0.25)
xx, yy = np.meshgrid(x, y)
z = np.sin(xx**2+yy**2)
f = interpolate.interp2d(x, y, z, kind='cubic')
# for new points
xnew = np.arange(-5.01, 5.01, 1e-2)
ynew = np.arange(-5.01, 5.01, 1e-2)
znew = f(xnew, ynew)
For a homework problem we are asked to
Write a program that computes all the eigenvalues of the matrix A, using the Rayleigh Quotient iteration to find each eigenvalue.
i. Report the initial guess you used for each eigenvalue. Plot the error at the n iteration against the error at the n+1 iteration for each eigenvalue (in log log).
ii. Compute the Rayleigh quotient for x sampled on a discretization of the unit sphere (use spherical coordinates). Plot the result, for example with mesh (Θ,ϕ,r(Θ,ϕ)). Explain the number and location of the critical points.
I have completed the first part of the problem, but I am not sure I understand how to complete the second. After some research I have found various ways (Here and Here) to plot spherical coordinates in Python which has provided me some clues. Borrowing from those links and a few other sources I have
# The matrix for completness
A = np.matrix([[4,3,4], [3,1,3], [4,3,4]])
A = scipy.linalg.hessenberg(A)
num_points = 50
theta, phi = np.linspace(0, 2 * np.pi, num_points), np.linspace(0, np.pi, num_points)
x = np.sin(phi) * np.cos(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(phi)
xyz = np.stack((x, y, z), axis=1)
rqs = np.array([np.dot(x, np.dot(A, np.conj(x).T)).item(0) for _,x in enumerate(xyz)])
In the first chunk of code (Θ,ϕ) are created and translated to Cartesian coordinates, effectively a discritization of the unit circle in the R^3. This allows me then to create the x vectors (xyz above) used to calculate the Rayleigh Quotients (rqs above) at each discritization point, the r(Θ,ϕ) of the spherical coordinates.
Though, now that I have the radial distance I am not sure how to again recreate x, y, z properly for a meshgrid to plot as a surface. This might be out of the realm of StackOverflow and more for Math.Stack, but I am also not sure if this plot should end up being a "warped plane" or a "warped sphere".
In this SO answer, linked above too, I think the answer lies though. In the chunk of code
theta, phi = np.linspace(0, 2 * np.pi, 40), np.linspace(0, np.pi, 40)
THETA, PHI = np.meshgrid(theta, phi)
R = np.cos(PHI**2)
X = R * np.sin(PHI) * np.cos(THETA)
Y = R * np.sin(PHI) * np.sin(THETA)
Z = R * np.cos(PHI)
R here I assume refers to the radial distance, though, this R is in a mesh when x, y, z are calculated. I have tried to reshape rqs above to the same dimension, but the values of the rqs do not line up with the subsequent grid and as a result produces obviously wrong plots.
I almost need a way to tie in the creation of the meshgrid with the calculation of the x. But the calculation seems to complex to be applied directly to the meshgrid..
How can I produce the spherical coordinates based plot given the radial distances?
EDIT: After some more searching I found this MatLab code which produces the desired plot, though I would still like to reproduce this in Python. I would like to say this MatLab code provides an overview of how to implement this in Python, but it seems to be some very old and esoteric code. Here is the plot(s) it produces
I don't know about the math or the Rayleigh Quotient but from what I gather, you want to calculate rqs as a function of the points of the unit sphere. For that, I would recommend to use meshgrid to generate value pairs for all Θ and ϕ. Then, since your matrix formula is defined for cartesian rather than spherical coordinates, I would convert my mesh to that and insert into the formula.
Then, finally, the result can be illustrated, using plot_surface on the unit sphere, where the (scaled) RQS data are used for facecolor:
import numpy as np
import scipy.linalg
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
# The matrix for completness
A = np.matrix([[4,3,4], [3,1,3], [4,3,4]])
A = scipy.linalg.hessenberg(A)
num_points = 25
theta = np.linspace(0, 2 * np.pi, num_points)
phi = np.linspace(0, np.pi, num_points)
THETA, PHI = np.meshgrid(theta, phi)
X = np.sin(PHI) * np.cos(THETA)
Y = np.sin(PHI) * np.sin(THETA)
Z = np.cos(PHI)
# Calculate RQS for points on unit sphere:
RQS = np.empty(PHI.shape)
for theta_pos, phi_pos in itertools.product(range(num_points), range(num_points)):
x = np.array([X[theta_pos, phi_pos],
Y[theta_pos, phi_pos],
Z[theta_pos, phi_pos]])
RQS[theta_pos, phi_pos] = np.dot(x, np.dot(A, np.conj(x).T))
# normalize in range 0..1
maxRQS = abs(RQS).max()
N = (RQS+maxRQS)/(2*maxRQS)
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1,
facecolors=cm.jet(N),
linewidth=0, antialiased=False, shade=False)
plt.show()
This gives the following result, which seems to agree with theMatlab contours plot in the OP.
Note that there may be better ways (vectorized) to convert the X, Y, and Z meshes to vectors, but the main execution time seems to be in the plottting anyway, so I won't spend the time trying to figure out exactly how.
If I want to interpolate the data below:
from scipy.interpolate import RectBivariateSpline, interp2d
import numpy as np
x1 = np.linspace(0,5,10)
y1 = np.linspace(0,20,20)
xx, yy = np.meshgrid(x1, y1)
z = np.sin(xx**2+yy**2)
with interp2d this works:
f = interp2d(x1, y1, z, kind='cubic')
however if I use RectBivariateSpline with the same x1, y1 parameters:
f = RectBivariateSpline(x1, y1, z)
I get this error:
TypeError Traceback (most recent call last)
<ipython-input-9-3da046e1ebe0> in <module>()
----> 1 f = RectBivariateSpline(x, y, z)
C:\...\Local\Continuum\Anaconda\lib\site-packages\scipy\interpolate\fitpack2.pyc in __init__(self, x, y, z, bbox, kx, ky, s)
958 raise TypeError('y must be strictly ascending')
959 if not x.size == z.shape[0]:
--> 960 raise TypeError('x dimension of z must have same number of '
961 'elements as x')
962 if not y.size == z.shape[1]:
TypeError: x dimension of z must have same number of elements as x
I'd have to switch the sizes of x, y like this to have it work:
x2 = np.linspace(0,5,20)
y2 = np.linspace(0,20,10)
f = RectBivariateSpline(x2, y2, z)
Is there a reason for this behavior - or something I am not understanding?
Well, the reason is that the parameters to the two functions are, as you have noted, different. Yes, this makes it really hard to just switch out one for the other, as I well know.
Why? In general it was a clear design decision to break backward compatibility with the new object-oriented spline functions, or at least not worry about it. Certainly, for large grid sizes there is significant space savings with not having to pass x and y as 2D objects. Frankly, I have found in my code that once this initial barrier is overcome, I'm much happier using the spline objects. For example, with the UnivariateSpline object, getting the derivative(s) is easy, as is the integral.
It would appear that, going forward, the SciPy folks will focus on the new objects, so you might contemplate just moving to them now. They are the same base functionality, and have additional methods that provide nice benefits.
EDIT - clarify what 'broke' between the two.
From the SciPy manual on interp2d you get the code snippet:
from scipy import interpolate
x = np.arange(-5.01, 5.01, 0.25)
y = np.arange(-5.01, 5.01, 0.25)
xx, yy = np.meshgrid(x, y)
z = np.sin(xx**2+yy**2)
f = interpolate.interp2d(x, y, z, kind=’cubic’)
This can be, unfortunately, potentially misleading since both x and y are the same length, so z will be a square matrix. So, lets play with this a bit:
x = np.linspace(0,5,11)
y = np.linspace(0,20,21) # note different lengths
z = x[None,:].T + y*y # need broadcasting
xx,yy = np.meshgrid(x,y) # this is from the interp2d example to compare
zz = xx + yy*yy
These now have different shapes: shape(z) is (11,21) and shape(zz) is (21,11). In fact, they are the transpose of each other, z == zz.T. Once you realize this, it all becomes clearer - going from interp2d to RectBivariateSpline swapped the expected axes. Pick one instantiation of the splines (I've opted for the newer ones), and you have picked a particular set of axes to keep clear in your head. To mix them together, a simple transpose will work as well, but can get to be a headache when you go back through your code a month or more from now.