Part 8: Lineage and Practical Migration Notes

Back to regularizeNd series home

Previous: Part 7

This part documents the historical lineage of regularizeNd and gives practical migration guidance for users coming from gridfit or RegularizeData3D.

Practical Goal

  1. How regularizeNd relates to earlier tools.
  2. Map common gridfit and RegularizeData3D patterns to regularizeNd equivalents.

Example Anchor

Primary migration examples in this part: regularizeNd/Examples/From gridfit/demo_from_gridfit.m rewrite and regularizeNd/Examples/From RegularizeData3D rewrite patterns.

gridfit example data: bluff_data.mat

Conceptual Lineage

regularizeNd is inspired by earlier regularized surface fitting tools in the MATLAB ecosystem. Two tools are particularly relevant as a conceptual starting point:

  • gridfit (John D’Errico) (D’Errico 2016) — a 2-D input surface fitting tool using a similar regularization philosophy. gridfit introduced many users to the idea of balancing data fidelity against smoothness on a grid.
  • RegularizeData3D (Jamal 2014) — extended the concept to cubic interpolation. Made the smoothness parameter reusable across problems by scaling the regularization relative to the fidelity term.

regularizeNd generalizes the approach to an arbitrary number of input dimensions, fixed some all zero rows in gridfit and RegularizeData3D,exposes the underlying matrices for constrained problems, and provides multiple solver options including iterative solvers for large systems.

Important framing: regularizeNd is inspired by this prior work and shares the regularization concept, but it is an independent implementation.

Migrating from gridfit

Conceptual mapping

gridfit concept regularizeNd equivalent
gx, gy (grid vectors) xGrid = {gx, gy}
'smoothness' property (default 1), scalar, vector smoothness positional arg (similar role, different scaling/defaults), scalar, vector
g = gridfit(x,y,z,gx,gy,s) g = regularizeNd([x,y], z, {gx,gy}, s)
'interp' property ('triangle', 'bilinear', 'nearest') 5th positional arg ( 'nearest', 'linear', 'cubic'). 'bilinear' = 'linear'
'regularizer' property ('gradient', 'diffusion'/'laplacian', 'springs') regularizeNd’s regularization is fixed to the gradient regularizer because the others don’t work well.
Output is in meshgrid format Output is in ndgrid format — transpose before plotting with surf

Minimal gridfit → regularizeNd rewrite

% --- gridfit (old) ---
% g = gridfit(x, y, z, gx, gy, smoothness);
% surf(gx, gy, g)      % gridfit output is meshgrid-formatted

% --- regularizeNd (new) ---
g = regularizeNd([x,y], z, {gx, gy}, smoothness);
surf(gx, gy, g')       % note the transpose: regularizeNd output is ndgrid-formatted

The transpose g' is the most common source of confusion. regularizeNd stores the output in ndgrid format (g(i,j) indexes gx(i), gy(j)), while MATLAB’s surf expects meshgrid format (g(j,i)).

Topographic data demo rewrite

Below is the first section of demo_from_gridfit.m rewritten in clean regularizeNd style. The original used bluff_data.mat (Two ravines on a hillside, scanned from a topographic map of upstate New York).

clc; clear; close all;
load bluff_data
x = bluff_data(:,1);
y = bluff_data(:,2);
z = bluff_data(:,3);

gx = 0:4:264;
gy = 0:4:400;
smoothness = 1e-5;

g = regularizeNd([x,y], z, {gx, gy}, smoothness);

figure
colormap(hot(256))
surf(gx, gy, g')        % transpose for surf
camlight right; lighting phong; shading interp
line(x, y, z, 'Marker', '.', 'MarkerSize', 4, 'LineStyle', 'none')
title('Topographic surface — regularizeNd rewrite of gridfit demo')
xlabel('x'); ylabel('y'); zlabel('z')

Migrating from RegularizeData3D

Conceptual mapping

RegularizeData3D concept regularizeNd equivalent
x, y scatter inputs First two columns of the input matrix
z (value) input Second positional arg (response vector)
xnodes, ynodes xGrid = {xnodes, ynodes}
smoothness scalar, vector smoothness scalar, vector
'interp' property ('triangle', 'bilinear', 'bicubic', 'nearest') 5th positional arg ('nearest', 'linear', 'cubic') 'bilinear' = 'linear'. 'bicubic' = 'cubic'
Output grid in meshgrid format Output in ndgrid format — transpose for surf

Minimal RegularizeData3D → regularizeNd rewrite

% --- RegularizeData3D (old) ---
% g = RegularizeData3D(x, y, z, xnodes, ynodes, ...
%                     'smoothness', smoothness, 'interp', 'bicubic');

% --- regularizeNd (new) ---
g = regularizeNd([x,y], z, {xnodes, ynodes}, smoothness, 'cubic');
% same ndgrid output format — surf(xnodes, ynodes, g') for display

The key structural change is that the separate x, y scatter inputs become the two columns of a single matrix argument.

Interpolation naming is not one-to-one: in practice, RegularizeData3D 'bicubic' aligns with regularizeNd 'cubic', and RegularizeData3D 'bilinear' align most closely with regularizeNd 'linear'.

Coarse vs. fine grid demo rewrite

This example is adapted from From RegularizeData3D/coarse_vs_fine_grids.m and demonstrates a key property: for the same smoothness value, the fitted surface is nearly identical regardless of grid resolution. Coarser grids are faster; finer grids give more evaluation points.

clc; clear; close all;
inputPoints = [
    1,  0.5, 0.5;
    1,    2,   1;
    1,  3.5, 0.5;
    3, 0.75, 0.5;
    3,  1.5,   1;
    3, 2.25, 0.5;
    3,    3,   1;
    3,    4, 0.5];

x12 = inputPoints(:, 1:2);
v   = inputPoints(:, 3);

xCoarse = 0:0.2:4;  yCoarse = 0:0.4:4;
xFine   = 0:0.02:4; yFine   = 0:0.04:4;
smoothness = 0.001;

zCoarse = regularizeNd(x12, v, {xCoarse, yCoarse}, smoothness, 'cubic');
zFine   = regularizeNd(x12, v, {xFine,   yFine  }, smoothness, 'cubic');

tiledlayout(1, 2, 'TileSpacing', 'compact', 'Padding', 'compact')

nexttile
surf(xCoarse, yCoarse, zCoarse')
title('Coarse grid'); xlabel('x'); ylabel('y'); zlabel('z')

nexttile
surf(xFine, yFine, zFine','EdgeColor', 'none')
title('Fine grid'); xlabel('x'); ylabel('y'); zlabel('z')

Both panels should show essentially the same surface shape. The fine grid has more evaluation resolution, not a fundamentally different result.

What regularizeNd Does Not Do

To be precise about scope:

  • It does not guarantee interpolation through every data point — it is a weighted least-squares fit. Use smoothness → 0 if you want near-interpolation behavior (at the cost of the smoother no longer regularizing effectively).
  • It does not enforce physical constraints automatically. Use regularizeNdMatrices + lsqlin or lsqConstrainedAlternative for that (see Part 6).
  • It does not currently support unstructured output grids. The output is always on a rectilinear ndgrid.

Quick Reference: Common Output Format Confusion

regularizeNd outputs in ndgrid format. Most MATLAB visualization functions expect meshgrid format. The relationship is:

% regularizeNd output: zGrid(i,j) ↔ xGrid{1}(i), xGrid{2}(j)
% surf/mesh/contourf expect: Z(j,i) for plotting against x, y vectors

surf(xGrid{1}, xGrid{2}, zGrid')   % 2-D case: transpose zGrid

For 3-D and higher, use permute or griddedInterpolant to handle the indexing convention explicitly.

Checklist

References

Deep-Dive: Regularizer Design and Derivative Order

References

D’Errico, John. 2016. Surface Fitting Using Gridfit. Released. https://www.mathworks.com/matlabcentral/fileexchange/8998-surface-fitting-using-gridfit.
Jamal. 2014. RegularizeData3D. Released. https://www.mathworks.com/matlabcentral/fileexchange/46223-regularizedata3d.