I found this great question with some concise code that, with a couple of tweaks, fits a 3D polynomial surface onto a set of points of in space.
Python 3D polynomial surface fit, order dependent
My version is below.
Ultimately, I've realized that I need to fit a surface over time, i.e. I need to solve for a 4 dimensional surface, and I've struggled with it.
I came up with a very hacky and computationally intensive work-around. I create a surface for each time interval. Then I create a grid of points and find the Z value for each point on each surface. So now I have a bunch of x,y points and each one has a list of z values that need to flow smoothly from one interval to the next. So we do a regression on the z values. Now that the z-flow is smooth, I refit a surface for each time interval based on the x,y points and whatever their smoothed Z value is for the relevant time interval.
Its what it sounds like. Clunky and suboptimal. The resulting surfaces flow more smoothly and still perform okay but there's gotta be a way to cut out the middle man and solve for that 4th dimension directly in the fitSurface function.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import itertools
# Start black magic
def xy_powers(order):
powers = itertools.product(range(order + 1), range(order + 1))
return [tup for tup in powers if sum(tup) <= order]
def fitSurface(x, y, z, order):
ncols = (order + 1)**2
G = np.zeros((x.size, ncols))
ij = xy_powers(order)
for k, (i,j) in enumerate(ij):
G[:,k] = x**i * y**j
m, _, _, _ = np.linalg.lstsq(G, z, rcond=None)
return m
def getZValuesForXYInputs(surface, order, x, y):
order = int(np.sqrt(len(surface))) - 1
ij = xy_powers(order)
z = np.zeros_like(x)
for a, (i,j) in zip(surface, ij):
z += a * x**i * y**j
return z
# End black magic
def showRender_3D(x_raw, y_raw, z_raw, xx, yy, zz):
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x_raw, y_raw, z_raw, color='red', zorder=0)
ax.plot_surface(xx, yy, zz, zorder=10, alpha=0.4)
ax.set_xlabel('X data')
ax.set_ylabel('Y data')
ax.set_zlabel('Z data')
plt.show()
def main(order):
# Make generic data
numdata = 100
x = np.random.random(numdata)
y = np.random.random(numdata)
z = x**2 + y**2 + 3*x**3 + y + np.random.random(numdata)
t = np.random.randint(1, 4, numdata) # Break the data into
# Fit the surface
m = fitSurface(x, y, z, order)
# Sample the surface at regular points so we can more easily plot the surface
nx, ny = 40, 40
xx, yy = np.meshgrid(np.linspace(x.min(), x.max(), nx),
np.linspace(y.min(), y.max(), ny))
zz = getZValuesForXYInputs(m, order, xx, yy)
# Plot it
showRender_3D(x, y, z, xx, yy, zz)
orderForSurfaceFit = 3
main(orderForSurfaceFit)