Generate surface equation from x, y, z data - python

What is the most effective way of generating an equation for a surface where x, y and z are known? There seems to be many ways to interpolate spline by spline from a few data points, however, I have all the data points that represent a smooth surface and would still not have a single equation representing the whole surface. Each spline is fairly simple in that it rises and falls once.
I have generated an equation from a least squares example on Brandon Stafford's blog, but the resulting equation does not represent the more complicated form.
I realize the cross terms are missing. How can I add each cross term (xy, xy^2, x^2y, x^3y, x^3y^2, y^3x, y^3x^2) into the script? Once I have cross terms do I need to add degrees for them?
# Set up the canonnical least squares form
Ax = np.vander(X,degree)
Ay = np.vander(Y,degree)
A = np.hstack((Ax,Ay))
# Solve for the least squares estimate of current
(coeffs, residuals, rank, sing_vals) = np.linalg.lstsq(A, Z)
# Extract coefficients and create polynomials in x and y
xcoeffs = coeffs[0:degree]
ycoeffs = coeffs[degree:2 * degree]
fx = np.poly1d(xcoeffs)
fy = np.poly1d(ycoeffs)
print fx
print fy

Related

space varying vertical coordinates of model data-- writing netCDF file (xarray)

Consider using a numerical flow model to simulate a simple 1D advection-diffusion case, in hydraulic engineering, e.g.: evolution of salt concentration (Cs). The domain has no y-dimension, but only X and Z dimension, meaning that the flow is not depth-averaged and, for 1 timestep, I have the salt concentration:
Cs = Cs(x, z)
where
x: space coordinate (equally spaced vector), and z: vertical coordinate (non equally spaced vector).
or more in general, including time, :
Cs = Cs(x, z, t)
Now, x is constant over time and space (meaning that the x-grid is not "moving" slightly back and forth), but z (i.e. the vertical coordinate of each "Layer") is indeed adapting and oscillating up and down.
For each time-step, the model spits out the actual numerical value of the salt concentration, AND the vertical coordinate for each layer (i.e. at each given depth of the fluid). IN Python I could easily "merge" contour the salinity over x and z, by staggering each z on top of its previous level.
Therefore, cords matrix in Python has a number of lines that equals the number of vertical layers and a number of rows that equals the number of x grid points. By so doing, coordinates and Salinity matrices are consistent and may be plotted (contours).
My question now is:
In Python, I want to use x-array to generate/output a netcdf file to read that in with Paraview. I could get started and created a netCDF file, containing (for now) only 1 time-step, ingest that into Paraview and plot it.
However, my problem is that z = z(x), which means that each level height varies along the horzinotal coordinate. So far, I could only apply an equally spaced vertical vector to describe vertical coordinate, and that vector is not depending on x, so it is not varying along the horizontal coordinate.
How can I achieve my goal?
nx = np.shape(Xp)[1]
nz = Nvert+1
#X vector is linearly spaced, constant dx, and it is OK like that!
xmin = 0
xmax = 10
X = np.linspace(xmin, xmax, nx)
#Z is equally spaced too, by it should NOT be! might vary over vertical AND/or over X!!!
zmin = 0
zmax = 0.5
Z = np.linspace(zmin, zmax, nz)
# merge quantity "qname", at timestep 0, given Nvert vertical layers
q0 = merge_quant(Nvert, t[0], qname)
#create two matrix coordinates, xk and zk, being consistent is size with q0
xk, zk = merge_xnadz(Nvert, t[0])
#assign:
vals = q0
#preallocate data using xarray
ds = xr.Dataset(
{qname: (("z", "x"), vals)},
coords={
"x": X,
"z": Z #---this here should vary but is instead constant..?!?
}
)
#save to disk
ds.to_netcdf("Testout.nc")
How can I add another dimension? Could I just add the matrix of coordinates in xarray before saving to netCDF? That would really solve my problem.
Any help is appreciated.
Thank you!
Marco
Edit:
Two images, one for model results; and one obtained in PARAVIEW

Fourier transform or fit of sines and cosines to a 2D surface from discrete point cloud data

I have x,y,z data that define a surface (x and y position, z height).
The data is imperfect, in that it contains some noise, i.e. not every point lies precisely on the plane I wish to model, just very close to it.
I only have data within a triangular region, not the full x,y, plane.
Here is an example with z represented by colour:
In this example the data has been sampled in the centres of triangles on a mesh like this (each blue dot is a sample):
If it is necessary, the samples could be evenly spaced on an x,y grid, though a solution where this is not required is preferable.
I want to represent this data as a sum of sines and cosines in order to manipulate it mathematically. Ideally using as few terms as are needed to keep the error of the fit acceptably low.
If this were a square region I would take the 2D Fourier transform and discard higher frequency terms.
However I think this situation has two key differences that make this approach not viable:
Ideally I want to use samples at the points indicated by the blue dots in my grid above. I could instead use a regular x,y grid if there is no alternative, but this is not an ideal solution
I do not have data for the whole x,y, plane. The white areas in the first image above do not contain data that should be considered in the fit.
So in summary my question is thus:
Is there a way to extract coefficients for a best-fit of this data using a linear combination of sines and cosines?
Ideally using python.
My apologies if this is more of a mathematics question and stack overflow is not the correct place to post this!
EDIT: Here is an example dataset in python style [x,y,z] form - sorry it's huge but apparently I can't use pastebin?:
[[1.7500000000000001e-08, 1.0103629710818452e-08, 14939.866751020554],
[1.7500000000000001e-08, 2.0207259421636904e-08, 3563.2218207404617],
[8.7500000000000006e-09, 5.0518148554092277e-09, 24529.964593228644],
[2.625e-08, 5.0518148554092261e-09, 24529.961688158553],
[1.7500000000000001e-08, 5.0518148554092261e-09, 21956.74682671843],
[2.1874999999999999e-08, 1.2629537138523066e-08, 10818.190869824304],
[1.3125000000000003e-08, 1.2629537138523066e-08, 10818.186813746233],
[1.7500000000000001e-08, 2.5259074277046132e-08, 3008.9480862705223],
[1.3125e-08, 1.7681351993932294e-08, 5630.9978116591838],
[2.1874999999999999e-08, 1.768135199393229e-08, 5630.9969846863969],
[8.7500000000000006e-09, 1.0103629710818454e-08, 13498.380006002562],
[4.3750000000000003e-09, 2.5259074277046151e-09, 40376.866196753763],
[1.3125e-08, 2.5259074277046143e-09, 26503.432370909999],
[2.625e-08, 1.0103629710818452e-08, 13498.379635232159],
[2.1874999999999999e-08, 2.5259074277046139e-09, 26503.430698738041],
[3.0625000000000005e-08, 2.525907427704613e-09, 40376.867011915041],
[8.7500000000000006e-09, 1.2629537138523066e-08, 11900.832515759088],
[6.5625e-09, 8.8406759969661469e-09, 17422.002946526718],
[1.09375e-08, 8.8406759969661469e-09, 17275.788904632376],
[4.3750000000000003e-09, 5.0518148554092285e-09, 30222.756636780832],
[2.1875000000000001e-09, 1.2629537138523088e-09, 64247.241146490327],
[6.5625e-09, 1.2629537138523084e-09, 35176.652106572205],
[1.3125e-08, 5.0518148554092277e-09, 22623.574247287044],
[1.09375e-08, 1.2629537138523082e-09, 27617.700396641056],
[1.5312500000000002e-08, 1.2629537138523078e-09, 25316.907231576402],
[2.625e-08, 1.2629537138523066e-08, 11900.834523905782],
[2.4062500000000001e-08, 8.8406759969661469e-09, 17275.796410700641],
[2.8437500000000002e-08, 8.8406759969661452e-09, 17422.004617294893],
[2.1874999999999999e-08, 5.0518148554092269e-09, 22623.570035270699],
[1.96875e-08, 1.2629537138523076e-09, 25316.9042194055],
[2.4062500000000001e-08, 1.2629537138523071e-09, 27617.700160860692],
[3.0625000000000005e-08, 5.0518148554092261e-09, 30222.765972585737],
[2.8437500000000002e-08, 1.2629537138523069e-09, 35176.65151453446],
[3.2812500000000003e-08, 1.2629537138523065e-09, 64247.246775422129],
[2.1875000000000001e-09, 2.5259074277046151e-09, 46711.23463223876],
[1.0937500000000001e-09, 6.3147685692615553e-10, 101789.89315354674],
[3.28125e-09, 6.3147685692615543e-10, 52869.788364220134],
[3.2812500000000003e-08, 2.525907427704613e-09, 46711.229428833962],
[3.1718750000000001e-08, 6.3147685692615347e-10, 52869.79233902022],
[3.3906250000000006e-08, 6.3147685692615326e-10, 101789.92509671643],
[1.0937500000000001e-09, 1.2629537138523088e-09, 82527.848790063814],
[5.4687500000000004e-10, 3.1573842846307901e-10, 137060.87432327325],
[1.640625e-09, 3.157384284630789e-10, 71884.380087542726],
[3.3906250000000006e-08, 1.2629537138523065e-09, 82527.861035177877],
[3.3359375000000005e-08, 3.1573842846307673e-10, 71884.398689011548],
[3.4453125000000001e-08, 3.1573842846307663e-10, 137060.96214950032],
[4.3750000000000003e-09, 6.3147685692615347e-09, 18611.868317256733],
[3.28125e-09, 4.4203379984830751e-09, 27005.961455364879],
[5.4687499999999998e-09, 4.4203379984830751e-09, 28655.126635802204],
[3.0625000000000005e-08, 6.314768569261533e-09, 18611.869287539808],
[2.9531250000000002e-08, 4.4203379984830734e-09, 28655.119850641502],
[3.1718750000000001e-08, 4.4203379984830726e-09, 27005.959731047784]]
Nothing stops you from doing normal linear least squares with whatever basis you like. (You'll have to work out the periodicity you want, as mikuszefski said.) The lack of samples outside the triangle will naturally blind the method to the function's behavior out there. You probably want to weight the samples according to the area of their mesh cell, to avoid overfitting the corners.
Here some code that might help to fit periodic spikes. That also shows the use of the base x, x/2+ sqrt(3)/2 * y. The flat part can then be handled by low order Fourier. I hope that gives an idea. (BTW I agree with Davis Herring that area weighting is a good idea). For the fit, I guess, good initial guesses are crucial.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
def gauss(x,s):
return np.exp(-x**2/(2.*s**2))
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.15)
Y = np.arange(-5, 5, 0.15)
X, Y = np.meshgrid(X, Y)
kX=np.sin(X)
kY=np.sin(0.5*X+0.5*np.sqrt(3.)*Y)
R = np.sqrt(kX**2 + kY**2)
Z = gauss(R,.4)
#~ surf = ax.plot_wireframe(X, Y, Z, linewidth=1)
surf= ax.plot_surface(X, Y, Z, rstride=1, cstride=1,linewidth=0, antialiased=False)
plt.show()
Output:

Get the minimal surface solution of a 3D contour

I have a set of 3D points defining a 3D contour.
What I want to do is to obtain the minimal surface representation corresponding to this contour (see Minimal Surfaces in Wikipedia). Basically, this requires to solve a nonlinear partial differential equation.
In Matlab, this is almost straightforward using the pdenonlinfunction (see Matlab's documentation). An example of its usage for solving a minimal surface problem can be found here: Minimal Surface Problem on the Unit Disk.
I need to make such an implementation in Python, but up to know I haven't found any web resources on how to do this.
Can anyone point me any resources/examples of such implementation?
Thanks,
Miguel.
UPDATE
The 3D surface (ideally a triangular mesh representation) I want to find is bounded by this set of 3D points (as seen in this figure, the points lie in the best-fit plane):
Ok, so doing some research I found that this minimal surface problem is related with the solution of the Biharmonic Equation, and I also found that the Thin-plate spline is the fundamental solution to this equation.
So I think the approach would be to try to fit this sparse representation of the surface (given by the 3D contour of points) using thin-plate splines. I found this example in scipy.interpolate where scattered data (x,y,z format) is interpolated using thin-plate splines to obtain the ZI coordinates on a uniform grid (XI,YI).
Two questions arise:
Would thin-plate spline interpolation be the correct approach for the problem of computing the surface from the set of 3D contour points?
If so, how to perform thin-plate interpolation on scipy with a NON-UNIFORM grid?
Thanks again!
Miguel
UPDATE: IMPLEMENTATION IN MATLAB (BUT IT DOESN'T WORK ON SCIPY PYTHON)
I followed this example using Matlab's tpaps function and obtained the minimal surface fitted to my contour on a uniform grid. This is the result in Matlab (looks great!):
However I need to implement this in Python, so I'm using the package scipy.interpolate.Rbf and the thin-plate function. Here's the code in python (XYZ contains the 3D coordinates of each point in the contour):
GRID_POINTS = 25
x_min = XYZ[:,0].min()
x_max = XYZ[:,0].max()
y_min = XYZ[:,1].min()
y_max = XYZ[:,1].max()
xi = np.linspace(x_min, x_max, GRID_POINTS)
yi = np.linspace(y_min, y_max, GRID_POINTS)
XI, YI = np.meshgrid(xi, yi)
from scipy.interpolate import Rbf
rbf = Rbf(XYZ[:,0],XYZ[:,1],XYZ[:,2],function='thin-plate',smooth=0.0)
ZI = rbf(XI,YI)
However this is the result (quite different from that obtained in Matlab):
It's evident that scipy's result does not correspond to a minimal surface.
Is scipy.interpolate.Rbf + thin-plate doing as expected, why does it differ from Matlab's result?
You can use FEniCS:
from fenics import (
UnitSquareMesh,
FunctionSpace,
Expression,
interpolate,
assemble,
sqrt,
inner,
grad,
dx,
TrialFunction,
TestFunction,
Function,
solve,
DirichletBC,
DomainBoundary,
MPI,
XDMFFile,
)
# Create mesh and define function space
mesh = UnitSquareMesh(100, 100)
V = FunctionSpace(mesh, "Lagrange", 2)
# initial guess (its boundary values specify the Dirichlet boundary conditions)
# (larger coefficient in front of the sin term makes the problem "more nonlinear")
u0 = Expression("a*sin(2.5*pi*x[1])*x[0]", a=0.2, degree=5)
u = interpolate(u0, V)
print(
"initial surface area: {}".format(assemble(sqrt(1 + inner(grad(u), grad(u))) * dx))
)
# Define the linearized weak formulation for the Newton iteration
du = TrialFunction(V)
v = TestFunction(V)
q = (1 + inner(grad(u), grad(u))) ** (-0.5)
a = (
q * inner(grad(du), grad(v)) * dx
- q ** 3 * inner(grad(u), grad(du)) * inner(grad(u), grad(v)) * dx
)
L = -q * inner(grad(u), grad(v)) * dx
du = Function(V)
# Newton iteration
tol = 1.0e-5
maxiter = 30
for iter in range(maxiter):
# compute the Newton increment by solving the linearized problem;
# note that the increment has *homogeneous* Dirichlet boundary conditions
solve(a == L, du, DirichletBC(V, 0.0, DomainBoundary()))
u.vector()[:] += du.vector() # update the solution
eps = sqrt(
abs(assemble(inner(grad(du), grad(du)) * dx))
) # check increment size as convergence test
area = assemble(sqrt(1 + inner(grad(u), grad(u))) * dx)
print(
f"iteration{iter + 1:3d} H1 seminorm of delta: {eps:10.2e} area: {area:13.5e}"
)
if eps < tol:
break
if eps > tol:
print("no convergence after {} Newton iterations".format(iter + 1))
else:
print("convergence after {} Newton iterations".format(iter + 1))
with XDMFFile(MPI.comm_world, "out.xdmf") as xdmf_file:
xdmf_file.write(u)
(Modified from http://www-users.math.umn.edu/~arnold/8445/programs/minimalsurf-newton.py.)
Obviously Matlab and SciPy understand TPS in different ways. The Matlab implementation looks correct.
SciPy treats TPS the same way as others RBFs, so you could implement it correctly in Python yourself - it would be enough to form a matrix of the related linear equation system and solve it to receive TPS' coefficients.

Peak curvature in Scipy spline

How can I find the peak curvature of a spline fitted using scipy? (Actually, peak second differential would be enough)
I have calculated the tck values as follows, using my 1d xs and ys vectors:
tck = splrep(xs, ys, s=0)
I know I can evaluate the second differential at any x of my choice:
ddy = splev([x], tck, 2)
So I could loop over many values of x, calculate the curvature and take the maximum. But I would prefer to interpret the values in tck to get the coefficients of the individual cubic functions, and thus calculate the peak curvature directly. However, tck appears rather opaque - how can I extract the cubic function coefficients from it?
Just use the der keyword argument on splev function:
ddy = splev(X, tck, der=2)
and preferrably don't loop over many values of x, instead make a Nx1 array X containing every value you want to evaluate, so as to get back an array of values instead of individual values you'll have to put in a sequence anyway.
Also, it is extremely adviseable to PLOT your results as a way to debug it. If plots make sense, things are most likely working (and, if not, they surely are NOT working) as you expect.
EDIT: in case the interpolation using X gives just an approximate value and you want the TRUE maximum, you can use parabolic interpolation of the three points that define the maximum (the local interpolated maximum and its neighbors), considering the spline is locally smooth:
def parabolic_interpolation(p1, p2, p3):
x1, y1 = p1
x2, y2 = p2
x3, y3 = p3
denom = (x1-x2)*(x1-x3)*(x2-x3);
a = (x3*(y2-y1)+x2*(y1-y3)+x1*(y3-y2))/denom
b = (x3*x3*(y1-y2)+x2*x2*(y3-y1)+x1*x1*(y2-y3))/denom
c = (x2*x3*(x2-x3)*y1+x3*x1*(x3-x1)*y2+x1*x2*(x1-x2)*y3)/denom
xv = -b/(2*a)
yv = c-b**2/(4*a)
return (xv, yv) # coordinates of the vertex
Hope this helps!

2d random walk in python - drawing hypotenuse from distribution

I'm writing a simple 2d brownian motion simulator in Python. It's obviously easy to draw values for x displacement and y displacement from a distribution, but I have to set it up so that the 2d displacement (ie hypotenuse) is drawn from a distribution, and then translate this to new x and y coordinates. This is probably trivial and I'm just too far removed from trigonometry to remember how to do it correctly. Am I going to need to generate a value for the hypotenuse and then translate it into x and y displacements with sin and cos? (How do you do this correctly?)
This is best done by using polar coordinates (r, theta) for your distributions (where r is your "hypotenuse")), and then converting the result to (x, y), using x = r cos(theta) and y = r sin(theta). That is, select r from whatever distribution you like, and then select a theta, usually from a flat, 0 to 360 deg, distribution, and then convert these values to x and y.
Going the other way around (i.e., constructing correlated (x, y) distributions that gave a direction independent hypotenuse) would be very difficult.
If you have a hypotenuse in the form of a line segment, then you have two points. From two points in the form P0 = (x0, y0) P1 = (x1, y1) you can get the x and y displacements by subtracting x0 from x1 and y0 from y1.
If your hypotenuse is actually a vector in a polar coordinate plane, then yes, you'll have to take the sin of the angle and multiply it by the magnitude of the vector to get the y displacement and likewise with cos for the x displacement.

Categories