-
Notifications
You must be signed in to change notification settings - Fork 16
/
gridfit_jg.m
1078 lines (980 loc) · 36.1 KB
/
gridfit_jg.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function [zgrid,xgrid,ygrid] = gridfit_jg(x,y,z,xnodes,ynodes,varargin)
% gridfit: estimates a surface on a 2d grid, based on scattered data
% Replicates are allowed. All methods extrapolate to the grid
% boundaries. Gridfit uses a modified ridge estimator to
% generate the surface, where the bias is toward smoothness.
%
% Gridfit is not an interpolant. Its goal is a smooth surface
% that approximates your data, but allows you to control the
% amount of smoothing.
%
% usage #1: zgrid = gridfit(x,y,z,xnodes,ynodes);
% usage #2: [zgrid,xgrid,ygrid] = gridfit(x,y,z,xnodes,ynodes);
% usage #3: zgrid = gridfit(x,y,z,xnodes,ynodes,prop,val,prop,val,...);
%
% Arguments: (input)
% x,y,z - vectors of equal lengths, containing arbitrary scattered data
% The only constraint on x and y is they cannot ALL fall on a
% single line in the x-y plane. Replicate points will be treated
% in a least squares sense.
%
% ANY points containing a NaN are ignored in the estimation
%
% xnodes - vector defining the nodes in the grid in the independent
% variable (x). xnodes need not be equally spaced. xnodes
% must completely span the data. If they do not, then the
% 'extend' property is applied, adjusting the first and last
% nodes to be extended as necessary. See below for a complete
% description of the 'extend' property.
%
% If xnodes is a scalar integer, then it specifies the number
% of equally spaced nodes between the min and max of the data.
%
% ynodes - vector defining the nodes in the grid in the independent
% variable (y). ynodes need not be equally spaced.
%
% If ynodes is a scalar integer, then it specifies the number
% of equally spaced nodes between the min and max of the data.
%
% Also see the extend property.
%
% Additional arguments follow in the form of property/value pairs.
% Valid properties are:
% 'smoothness', 'interp', 'regularizer', 'solver', 'maxiter'
% 'extend', 'tilesize', 'overlap'
%
% Any UNAMBIGUOUS shortening (even down to a single letter) is
% valid for property names. All properties have default values,
% chosen (I hope) to give a reasonable result out of the box.
%
% 'smoothness' - scalar or vector of length 2 - determines the
% eventual smoothness of the estimated surface. A larger
% value here means the surface will be smoother. Smoothness
% must be a non-negative real number.
%
% If this parameter is a vector of length 2, then it defines
% the relative smoothing to be associated with the x and y
% variables. This allows the user to apply a different amount
% of smoothing in the x dimension compared to the y dimension.
%
% Note: the problem is normalized in advance so that a
% smoothness of 1 MAY generate reasonable results. If you
% find the result is too smooth, then use a smaller value
% for this parameter. Likewise, bumpy surfaces suggest use
% of a larger value. (Sometimes, use of an iterative solver
% with too small a limit on the maximum number of iterations
% will result in non-convergence.)
%
% DEFAULT: 1
%
%
% 'interp' - character, denotes the interpolation scheme used
% to interpolate the data.
%
% DEFAULT: 'triangle'
%
% 'bilinear' - use bilinear interpolation within the grid
% (also known as tensor product linear interpolation)
%
% 'triangle' - split each cell in the grid into a triangle,
% then linear interpolation inside each triangle
%
% 'nearest' - nearest neighbor interpolation. This will
% rarely be a good choice, but I included it
% as an option for completeness.
%
%
% 'regularizer' - character flag, denotes the regularization
% paradignm to be used. There are currently three options.
%
% DEFAULT: 'gradient'
%
% 'diffusion' or 'laplacian' - uses a finite difference
% approximation to the Laplacian operator (i.e, del^2).
%
% We can think of the surface as a plate, wherein the
% bending rigidity of the plate is specified by the user
% as a number relative to the importance of fidelity to
% the data. A stiffer plate will result in a smoother
% surface overall, but fit the data less well. I've
% modeled a simple plate using the Laplacian, del^2. (A
% projected enhancement is to do a better job with the
% plate equations.)
%
% We can also view the regularizer as a diffusion problem,
% where the relative thermal conductivity is supplied.
% Here interpolation is seen as a problem of finding the
% steady temperature profile in an object, given a set of
% points held at a fixed temperature. Extrapolation will
% be linear. Both paradigms are appropriate for a Laplacian
% regularizer.
%
% 'gradient' - attempts to ensure the gradient is as smooth
% as possible everywhere. Its subtly different from the
% 'diffusion' option, in that here the directional
% derivatives are biased to be smooth across cell
% boundaries in the grid.
%
% The gradient option uncouples the terms in the Laplacian.
% Think of it as two coupled PDEs instead of one PDE. Why
% are they different at all? The terms in the Laplacian
% can balance each other.
%
% 'springs' - uses a spring model connecting nodes to each
% other, as well as connecting data points to the nodes
% in the grid. This choice will cause any extrapolation
% to be as constant as possible.
%
% Here the smoothing parameter is the relative stiffness
% of the springs connecting the nodes to each other compared
% to the stiffness of a spting connecting the lattice to
% each data point. Since all springs have a rest length
% (length at which the spring has zero potential energy)
% of zero, any extrapolation will be minimized.
%
% Note: The 'springs' regularizer tends to drag the surface
% towards the mean of all the data, so too large a smoothing
% parameter may be a problem.
%
%
% 'solver' - character flag - denotes the solver used for the
% resulting linear system. Different solvers will have
% different solution times depending upon the specific
% problem to be solved. Up to a certain size grid, the
% direct \ solver will often be speedy, until memory
% swaps causes problems.
%
% What solver should you use? Problems with a significant
% amount of extrapolation should avoid lsqr. \ may be
% best numerically for small smoothnesss parameters and
% high extents of extrapolation.
%
% Large numbers of points will slow down the direct
% \, but when applied to the normal equations, \ can be
% quite fast. Since the equations generated by these
% methods will tend to be well conditioned, the normal
% equations are not a bad choice of method to use. Beware
% when a small smoothing parameter is used, since this will
% make the equations less well conditioned.
%
% DEFAULT: 'normal'
%
% '\' - uses matlab's backslash operator to solve the sparse
% system. 'backslash' is an alternate name.
%
% 'symmlq' - uses matlab's iterative symmlq solver
%
% 'lsqr' - uses matlab's iterative lsqr solver
%
% 'normal' - uses \ to solve the normal equations.
%
%
% 'maxiter' - only applies to iterative solvers - defines the
% maximum number of iterations for an iterative solver
%
% DEFAULT: min(10000,length(xnodes)*length(ynodes))
%
%
% 'extend' - character flag - controls whether the first and last
% nodes in each dimension are allowed to be adjusted to
% bound the data, and whether the user will be warned if
% this was deemed necessary to happen.
%
% DEFAULT: 'warning'
%
% 'warning' - Adjust the first and/or last node in
% x or y if the nodes do not FULLY contain
% the data. Issue a warning message to this
% effect, telling the amount of adjustment
% applied.
%
% 'never' - Issue an error message when the nodes do
% not absolutely contain the data.
%
% 'always' - automatically adjust the first and last
% nodes in each dimension if necessary.
% No warning is given when this option is set.
%
%
% 'tilesize' - grids which are simply too large to solve for
% in one single estimation step can be built as a set
% of tiles. For example, a 1000x1000 grid will require
% the estimation of 1e6 unknowns. This is likely to
% require more memory (and time) than you have available.
% But if your data is dense enough, then you can model
% it locally using smaller tiles of the grid.
%
% My recommendation for a reasonable tilesize is
% roughly 100 to 200. Tiles of this size take only
% a few seconds to solve normally, so the entire grid
% can be modeled in a finite amount of time. The minimum
% tilesize can never be less than 3, although even this
% size tile is so small as to be ridiculous.
%
% If your data is so sparse than some tiles contain
% insufficient data to model, then those tiles will
% be left as NaNs.
%
% DEFAULT: inf
%
%
% 'overlap' - Tiles in a grid have some overlap, so they
% can minimize any problems along the edge of a tile.
% In this overlapped region, the grid is built using a
% bi-linear combination of the overlapping tiles.
%
% The overlap is specified as a fraction of the tile
% size, so an overlap of 0.20 means there will be a 20%
% overlap of successive tiles. I do allow a zero overlap,
% but it must be no more than 1/2.
%
% 0 <= overlap <= 0.5
%
% Overlap is ignored if the tilesize is greater than the
% number of nodes in both directions.
%
% DEFAULT: 0.20
%
%
% 'autoscale' - Some data may have widely different scales on
% the respective x and y axes. If this happens, then
% the regularization may experience difficulties.
%
% autoscale = 'on' will cause gridfit to scale the x
% and y node intervals to a unit length. This should
% improve the regularization procedure. The scaling is
% purely internal.
%
% autoscale = 'off' will disable automatic scaling
%
% DEFAULT: 'on'
%
%
% Arguments: (output)
% zgrid - (nx,ny) array containing the fitted surface
%
% xgrid, ygrid - as returned by meshgrid(xnodes,ynodes)
%
%
% Speed considerations:
% Remember that gridfit must solve a LARGE system of linear
% equations. There will be as many unknowns as the total
% number of nodes in the final lattice. While these equations
% may be sparse, solving a system of 10000 equations may take
% a second or so. Very large problems may benefit from the
% iterative solvers or from tiling.
%
%
% Example usage:
%
% x = rand(100,1);
% y = rand(100,1);
% z = exp(x+2*y);
% xnodes = 0:.1:1;
% ynodes = 0:.1:1;
%
% g = gridfit(x,y,z,xnodes,ynodes);
%
% Note: this is equivalent to the following call:
%
% g = gridfit(x,y,z,xnodes,ynodes, ...
% 'smooth',1, ...
% 'interp','triangle', ...
% 'solver','normal', ...
% 'regularizer','gradient', ...
% 'extend','warning', ...
% 'tilesize',inf);
%
%
% Author: John D'Errico
% e-mail address: [email protected]
% Release: 2.0
% Release date: 5/23/06
%
% Modified by Ge Jin to releaze del^4 option
% Date: 03/2012
% set defaults
params.smoothness = 1;
params.interp = 'triangle';
params.regularizer = 'gradient';
params.solver = 'normal';
params.maxiter = [];
params.extend = 'warning';
params.tilesize = inf;
params.overlap = 0.20;
params.mask = [];
params.autoscale = 'on';
params.xscale = 1;
params.yscale = 1;
% was the params struct supplied?
if ~isempty(varargin)
if isstruct(varargin{1})
% params is only supplied if its a call from tiled_gridfit
params = varargin{1};
if length(varargin)>1
% check for any overrides
params = parse_pv_pairs(params,varargin{2:end});
end
else
% check for any overrides of the defaults
params = parse_pv_pairs(params,varargin);
end
end
% check the parameters for acceptability
params = check_params(params);
% ensure all of x,y,z,xnodes,ynodes are column vectors,
% also drop any NaN data
x=x(:);
y=y(:);
z=z(:);
k = isnan(x) | isnan(y) | isnan(z);
if any(k)
x(k)=[];
y(k)=[];
z(k)=[];
end
xmin = min(x);
xmax = max(x);
ymin = min(y);
ymax = max(y);
% did they supply a scalar for the nodes?
if length(xnodes)==1
xnodes = linspace(xmin,xmax,xnodes)';
xnodes(end) = xmax; % make sure it hits the max
end
if length(ynodes)==1
ynodes = linspace(ymin,ymax,ynodes)';
ynodes(end) = ymax; % make sure it hits the max
end
xnodes=xnodes(:);
ynodes=ynodes(:);
dx = diff(xnodes);
dy = diff(ynodes);
nx = length(xnodes);
ny = length(ynodes);
ngrid = nx*ny;
% set the scaling if autoscale was on
if strcmpi(params.autoscale,'on')
params.xscale = mean(dx);
params.yscale = mean(dy);
params.autoscale = 'off';
end
% check to see if any tiling is necessary
if (params.tilesize < max(nx,ny))
% split it into smaller tiles. compute zgrid and ygrid
% at the very end if requested
zgrid = tiled_gridfit(x,y,z,xnodes,ynodes,params);
else
% its a single tile.
% mask must be either an empty array, or a boolean
% aray of the same size as the final grid.
nmask = size(params.mask);
if ~isempty(params.mask) && ((nmask(2)~=nx) || (nmask(1)~=ny))
if ((nmask(2)==ny) || (nmask(1)==nx))
error 'Mask array is probably transposed from proper orientation.'
else
error 'Mask array must be the same size as the final grid.'
end
end
if ~isempty(params.mask)
params.maskflag = 1;
else
params.maskflag = 0;
end
% default for maxiter?
if isempty(params.maxiter)
params.maxiter = min(10000,nx*ny);
end
% check lengths of the data
n = length(x);
if (length(y)~=n) || (length(z)~=n)
error 'Data vectors are incompatible in size.'
end
if n<3
error 'Insufficient data for surface estimation.'
end
% verify the nodes are distinct
if any(diff(xnodes)<=0) || any(diff(ynodes)<=0)
error 'xnodes and ynodes must be monotone increasing'
end
% do we need to tweak the first or last node in x or y?
if xmin<xnodes(1)
switch params.extend
case 'always'
xnodes(1) = xmin;
case 'warning'
warning('GRIDFIT:extend',['xnodes(1) was decreased by: ',num2str(xnodes(1)-xmin),', new node = ',num2str(xmin)])
xnodes(1) = xmin;
case 'never'
error(['Some x (',num2str(xmin),') falls below xnodes(1) by: ',num2str(xnodes(1)-xmin)])
end
end
if xmax>xnodes(end)
switch params.extend
case 'always'
xnodes(end) = xmax;
case 'warning'
warning('GRIDFIT:extend',['xnodes(end) was increased by: ',num2str(xmax-xnodes(end)),', new node = ',num2str(xmax)])
xnodes(end) = xmax;
case 'never'
error(['Some x (',num2str(xmax),') falls above xnodes(end) by: ',num2str(xmax-xnodes(end))])
end
end
if ymin<ynodes(1)
switch params.extend
case 'always'
ynodes(1) = ymin;
case 'warning'
warning('GRIDFIT:extend',['ynodes(1) was decreased by: ',num2str(ynodes(1)-ymin),', new node = ',num2str(ymin)])
ynodes(1) = ymin;
case 'never'
error(['Some y (',num2str(ymin),') falls below ynodes(1) by: ',num2str(ynodes(1)-ymin)])
end
end
if ymax>ynodes(end)
switch params.extend
case 'always'
ynodes(end) = ymax;
case 'warning'
warning('GRIDFIT:extend',['ynodes(end) was increased by: ',num2str(ymax-ynodes(end)),', new node = ',num2str(ymax)])
ynodes(end) = ymax;
case 'never'
error(['Some y (',num2str(ymax),') falls above ynodes(end) by: ',num2str(ymax-ynodes(end))])
end
end
% determine which cell in the array each point lies in
[junk,indx] = histc(x,xnodes); %#ok
[junk,indy] = histc(y,ynodes); %#ok
% any point falling at the last node is taken to be
% inside the last cell in x or y.
k=(indx==nx);
indx(k)=indx(k)-1;
k=(indy==ny);
indy(k)=indy(k)-1;
ind = indy + ny*(indx-1);
% Do we have a mask to apply?
if params.maskflag
% if we do, then we need to ensure that every
% cell with at least one data point also has at
% least all of its corners unmasked.
params.mask(ind) = 1;
params.mask(ind+1) = 1;
params.mask(ind+ny) = 1;
params.mask(ind+ny+1) = 1;
end
% interpolation equations for each point
tx = min(1,max(0,(x - xnodes(indx))./dx(indx)));
ty = min(1,max(0,(y - ynodes(indy))./dy(indy)));
% Future enhancement: add cubic interpolant
switch params.interp
case 'triangle'
% linear interpolation inside each triangle
k = (tx > ty);
L = ones(n,1);
L(k) = ny;
t1 = min(tx,ty);
t2 = max(tx,ty);
A = sparse(repmat((1:n)',1,3),[ind,ind+ny+1,ind+L], ...
[1-t2,t1,t2-t1],n,ngrid);
case 'nearest'
% nearest neighbor interpolation in a cell
k = round(1-ty) + round(1-tx)*ny;
A = sparse((1:n)',ind+k,ones(n,1),n,ngrid);
case 'bilinear'
% bilinear interpolation in a cell
A = sparse(repmat((1:n)',1,4),[ind,ind+1,ind+ny,ind+ny+1], ...
[(1-tx).*(1-ty), (1-tx).*ty, tx.*(1-ty), tx.*ty], ...
n,ngrid);
end
rhs = z;
% do we have relative smoothing parameters?
if numel(params.smoothness) == 1
% it was scalar, so treat both dimensions equally
smoothparam = params.smoothness;
xyRelativeStiffness = [1;1];
else
% It was a vector, so anisotropy reigns.
% I've already checked that the vector was of length 2
smoothparam = sqrt(prod(params.smoothness));
xyRelativeStiffness = params.smoothness(:)./smoothparam;
end
% Build regularizer. Add del^4 regularizer one day.
switch params.regularizer
case 'springs'
% zero "rest length" springs
[i,j] = meshgrid(1:nx,1:(ny-1));
ind = j(:) + ny*(i(:)-1);
m = nx*(ny-1);
stiffness = 1./(dy/params.yscale);
Areg = sparse(repmat((1:m)',1,2),[ind,ind+1], ...
xyRelativeStiffness(2)*stiffness(j(:))*[-1 1], ...
m,ngrid);
[i,j] = meshgrid(1:(nx-1),1:ny);
ind = j(:) + ny*(i(:)-1);
m = (nx-1)*ny;
stiffness = 1./(dx/params.xscale);
Areg = [Areg;sparse(repmat((1:m)',1,2),[ind,ind+ny], ...
xyRelativeStiffness(1)*stiffness(i(:))*[-1 1],m,ngrid)];
[i,j] = meshgrid(1:(nx-1),1:(ny-1));
ind = j(:) + ny*(i(:)-1);
m = (nx-1)*(ny-1);
stiffness = 1./sqrt((dx(i(:))/params.xscale/xyRelativeStiffness(1)).^2 + ...
(dy(j(:))/params.yscale/xyRelativeStiffness(2)).^2);
Areg = [Areg;sparse(repmat((1:m)',1,2),[ind,ind+ny+1], ...
stiffness*[-1 1],m,ngrid)];
Areg = [Areg;sparse(repmat((1:m)',1,2),[ind+1,ind+ny], ...
stiffness*[-1 1],m,ngrid)];
case {'diffusion' 'laplacian'}
% thermal diffusion using Laplacian (del^2)
[i,j] = meshgrid(1:nx,2:(ny-1));
ind = j(:) + ny*(i(:)-1);
dy1 = dy(j(:)-1)/params.yscale;
dy2 = dy(j(:))/params.yscale;
Areg = sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ...
xyRelativeStiffness(2)*[-2./(dy1.*(dy1+dy2)), ...
2./(dy1.*dy2), -2./(dy2.*(dy1+dy2))],ngrid,ngrid);
[i,j] = meshgrid(2:(nx-1),1:ny);
ind = j(:) + ny*(i(:)-1);
dx1 = dx(i(:)-1)/params.xscale;
dx2 = dx(i(:))/params.xscale;
Areg = Areg + sparse(repmat(ind,1,3),[ind-ny,ind,ind+ny], ...
xyRelativeStiffness(1)*[-2./(dx1.*(dx1+dx2)), ...
2./(dx1.*dx2), -2./(dx2.*(dx1+dx2))],ngrid,ngrid);
case {'del4'}
% del^4 regulation, only accurate for even spacing!!
[i,j] = meshgrid(1:nx,2:(ny-1));
ind = j(:) + ny*(i(:)-1);
dy1 = dy(j(:)-1)/params.yscale;
dy2 = dy(j(:))/params.yscale;
Areg2 = sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ...
xyRelativeStiffness(2)*[-2./(dy1.*(dy1+dy2)), ...
2./(dy1.*dy2), -2./(dy2.*(dy1+dy2))],ngrid,ngrid);
[i,j] = meshgrid(2:(nx-1),1:ny);
ind = j(:) + ny*(i(:)-1);
dx1 = dx(i(:)-1)/params.xscale;
dx2 = dx(i(:))/params.xscale;
Areg2 = Areg2 + sparse(repmat(ind,1,3),[ind-ny,ind,ind+ny], ...
xyRelativeStiffness(1)*[-2./(dx1.*(dx1+dx2)), ...
2./(dx1.*dx2), -2./(dx2.*(dx1+dx2))],ngrid,ngrid);
[i,j] = meshgrid(1:nx,3:(ny-3));
ind = j(:) + ny*(i(:)-1);
dy_2 = dy(j(:)-2)/params.yscale;
dy_1 = dy(j(:)-1)/params.yscale;
dy0 = dy(j(:))/params.yscale;
dy1 = dy(j(:)+1)/params.yscale;
dy2 = dy(j(:)+2)/params.yscale;
Areg = sparse(repmat(ind,1,5),[ind-2,ind-1,ind,ind+1,ind+2], ...
xyRelativeStiffness(2)*[2./(dy_2.*(dy_2+dy_1)), ...
-8./(dy_1.*(dy_1+dy0)), 6./(dy0.*dy1), ...
-8./(dy1.*(dy1+dy0)), 2./(dy2.*(dy1+dy2))],ngrid,ngrid);
[i,j] = meshgrid(3:(nx-3),1:ny);
ind = j(:) + ny*(i(:)-1);
dx_2 = dx(i(:)-2)/params.xscale;
dx_1 = dx(i(:)-1)/params.xscale;
dx0 = dx(i(:))/params.xscale;
dx1 = dx(i(:)+1)/params.xscale;
dx2 = dx(i(:)+2)/params.xscale;
Areg = Areg + sparse(repmat(ind,1,5),[ind-2*ny,ind-ny,ind,ind+ny,ind+2*ny], ...
xyRelativeStiffness(1)*[2./(dx_2.*(dx_2+dx_1)), ...
-8./(dx_1.*(dx_1+dx0)), 6./(dx0.*dx1), ...
-8./(dx1.*(dx1+dx0)), 2./(dx2.*(dx1+dx2))],ngrid,ngrid);
Areg = [Areg;10*Areg2];
% Areg = Areg2;
case 'gradient'
% Subtly different from the Laplacian. A point for future
% enhancement is to do it better for the triangle interpolation
% case.
[i,j] = meshgrid(1:nx,2:(ny-1));
ind = j(:) + ny*(i(:)-1);
dy1 = dy(j(:)-1)/params.yscale;
dy2 = dy(j(:))/params.yscale;
Areg = sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ...
xyRelativeStiffness(2)*[-2./(dy1.*(dy1+dy2)), ...
2./(dy1.*dy2), -2./(dy2.*(dy1+dy2))],ngrid,ngrid);
[i,j] = meshgrid(2:(nx-1),1:ny);
ind = j(:) + ny*(i(:)-1);
dx1 = dx(i(:)-1)/params.xscale;
dx2 = dx(i(:))/params.xscale;
Areg = [Areg;sparse(repmat(ind,1,3),[ind-ny,ind,ind+ny], ...
xyRelativeStiffness(1)*[-2./(dx1.*(dx1+dx2)), ...
2./(dx1.*dx2), -2./(dx2.*(dx1+dx2))],ngrid,ngrid)];
end
nreg = size(Areg,1);
% Append the regularizer to the interpolation equations,
% scaling the problem first. Use the 1-norm for speed.
NA = norm(A,1);
NR = norm(Areg,1);
A = [A;Areg*(smoothparam*NA/NR)];
rhs = [rhs;zeros(nreg,1)];
% do we have a mask to apply?
if params.maskflag
unmasked = find(params.mask);
end
% solve the full system, with regularizer attached
switch params.solver
case {'\' 'backslash'}
if params.maskflag
% there is a mask to use
zgrid=nan(ny,nx);
zgrid(unmasked) = A(:,unmasked)\rhs;
else
% no mask
zgrid = reshape(A\rhs,ny,nx);
end
case 'normal'
% The normal equations, solved with \. Can be faster
% for huge numbers of data points, but reasonably
% sized grids. The regularizer makes A well conditioned
% so the normal equations are not a terribly bad thing
% here.
if params.maskflag
% there is a mask to use
Aunmasked = A(:,unmasked);
zgrid=nan(ny,nx);
zgrid(unmasked) = (Aunmasked'*Aunmasked)\(Aunmasked'*rhs);
else
zgrid = reshape((A'*A)\(A'*rhs),ny,nx);
end
case 'symmlq'
% iterative solver - symmlq - requires a symmetric matrix,
% so use it to solve the normal equations. No preconditioner.
tol = abs(max(z)-min(z))*1.e-13;
if params.maskflag
% there is a mask to use
zgrid=nan(ny,nx);
[zgrid(unmasked),flag] = symmlq(A(:,unmasked)'*A(:,unmasked), ...
A(:,unmasked)'*rhs,tol,params.maxiter);
else
[zgrid,flag] = symmlq(A'*A,A'*rhs,tol,params.maxiter);
zgrid = reshape(zgrid,ny,nx);
end
% display a warning if convergence problems
switch flag
case 0
% no problems with convergence
case 1
% SYMMLQ iterated MAXIT times but did not converge.
warning('GRIDFIT:solver',['Symmlq performed ',num2str(params.maxiter), ...
' iterations but did not converge.'])
case 3
% SYMMLQ stagnated, successive iterates were the same
warning('GRIDFIT:solver','Symmlq stagnated without apparent convergence.')
otherwise
warning('GRIDFIT:solver',['One of the scalar quantities calculated in',...
' symmlq was too small or too large to continue computing.'])
end
case 'lsqr'
% iterative solver - lsqr. No preconditioner here.
tol = abs(max(z)-min(z))*1.e-13;
if params.maskflag
% there is a mask to use
zgrid=nan(ny,nx);
[zgrid(unmasked),flag] = lsqr(A(:,unmasked),rhs,tol,params.maxiter);
else
[zgrid,flag] = lsqr(A,rhs,tol,params.maxiter);
zgrid = reshape(zgrid,ny,nx);
end
% display a warning if convergence problems
switch flag
case 0
% no problems with convergence
case 1
% lsqr iterated MAXIT times but did not converge.
warning('GRIDFIT:solver',['Lsqr performed ', ...
num2str(params.maxiter),' iterations but did not converge.'])
case 3
% lsqr stagnated, successive iterates were the same
warning('GRIDFIT:solver','Lsqr stagnated without apparent convergence.')
case 4
warning('GRIDFIT:solver',['One of the scalar quantities calculated in',...
' LSQR was too small or too large to continue computing.'])
end
end % switch params.solver
end % if params.tilesize...
% only generate xgrid and ygrid if requested.
if nargout>1
[xgrid,ygrid]=meshgrid(xnodes,ynodes);
end
% ============================================
% End of main function - gridfit
% ============================================
% ============================================
% subfunction - parse_pv_pairs
% ============================================
function params=parse_pv_pairs(params,pv_pairs)
% parse_pv_pairs: parses sets of property value pairs, allows defaults
% usage: params=parse_pv_pairs(default_params,pv_pairs)
%
% arguments: (input)
% default_params - structure, with one field for every potential
% property/value pair. Each field will contain the default
% value for that property. If no default is supplied for a
% given property, then that field must be empty.
%
% pv_array - cell array of property/value pairs.
% Case is ignored when comparing properties to the list
% of field names. Also, any unambiguous shortening of a
% field/property name is allowed.
%
% arguments: (output)
% params - parameter struct that reflects any updated property/value
% pairs in the pv_array.
%
% Example usage:
% First, set default values for the parameters. Assume we
% have four parameters that we wish to use optionally in
% the function examplefun.
%
% - 'viscosity', which will have a default value of 1
% - 'volume', which will default to 1
% - 'pie' - which will have default value 3.141592653589793
% - 'description' - a text field, left empty by default
%
% The first argument to examplefun is one which will always be
% supplied.
%
% function examplefun(dummyarg1,varargin)
% params.Viscosity = 1;
% params.Volume = 1;
% params.Pie = 3.141592653589793
%
% params.Description = '';
% params=parse_pv_pairs(params,varargin);
% params
%
% Use examplefun, overriding the defaults for 'pie', 'viscosity'
% and 'description'. The 'volume' parameter is left at its default.
%
% examplefun(rand(10),'vis',10,'pie',3,'Description','Hello world')
%
% params =
% Viscosity: 10
% Volume: 1
% Pie: 3
% Description: 'Hello world'
%
% Note that capitalization was ignored, and the property 'viscosity'
% was truncated as supplied. Also note that the order the pairs were
% supplied was arbitrary.
npv = length(pv_pairs);
n = npv/2;
if n~=floor(n)
error 'Property/value pairs must come in PAIRS.'
end
if n<=0
% just return the defaults
return
end
if ~isstruct(params)
error 'No structure for defaults was supplied'
end
% there was at least one pv pair. process any supplied
propnames = fieldnames(params);
lpropnames = lower(propnames);
for i=1:n
p_i = lower(pv_pairs{2*i-1});
v_i = pv_pairs{2*i};
ind = strmatch(p_i,lpropnames,'exact');
if isempty(ind)
ind = find(strncmp(p_i,lpropnames,length(p_i)));
if isempty(ind)
error(['No matching property found for: ',pv_pairs{2*i-1}])
elseif length(ind)>1
error(['Ambiguous property name: ',pv_pairs{2*i-1}])
end
end
p_i = propnames{ind};
% override the corresponding default in params
params = setfield(params,p_i,v_i); %#ok
end
% ============================================
% subfunction - check_params
% ============================================
function params = check_params(params)
% check the parameters for acceptability
% smoothness == 1 by default
if isempty(params.smoothness)
params.smoothness = 1;
else
if (numel(params.smoothness)>2) || any(params.smoothness<=0)
error 'Smoothness must be scalar (or length 2 vector), real, finite, and positive.'
end
end
% regularizer - must be one of 4 options - the second and
% third are actually synonyms.
valid = {'springs', 'diffusion', 'laplacian', 'gradient','del4'};
if isempty(params.regularizer)
params.regularizer = 'diffusion';
end
ind = find(strncmpi(params.regularizer,valid,length(params.regularizer)));
if (length(ind)==1)
params.regularizer = valid{ind};
else
error(['Invalid regularization method: ',params.regularizer])
end
% interp must be one of:
% 'bilinear', 'nearest', or 'triangle'
% but accept any shortening thereof.
valid = {'bilinear', 'nearest', 'triangle'};
if isempty(params.interp)
params.interp = 'triangle';
end
ind = find(strncmpi(params.interp,valid,length(params.interp)));
if (length(ind)==1)
params.interp = valid{ind};
else
error(['Invalid interpolation method: ',params.interp])
end
% solver must be one of:
% 'backslash', '\', 'symmlq', 'lsqr', or 'normal'
% but accept any shortening thereof.
valid = {'backslash', '\', 'symmlq', 'lsqr', 'normal'};
if isempty(params.solver)
params.solver = '\';
end
ind = find(strncmpi(params.solver,valid,length(params.solver)));
if (length(ind)==1)
params.solver = valid{ind};
else
error(['Invalid solver option: ',params.solver])
end
% extend must be one of:
% 'never', 'warning', 'always'
% but accept any shortening thereof.
valid = {'never', 'warning', 'always'};
if isempty(params.extend)
params.extend = 'warning';
end
ind = find(strncmpi(params.extend,valid,length(params.extend)));
if (length(ind)==1)
params.extend = valid{ind};
else
error(['Invalid extend option: ',params.extend])
end
% tilesize == inf by default
if isempty(params.tilesize)
params.tilesize = inf;
elseif (length(params.tilesize)>1) || (params.tilesize<3)
error 'Tilesize must be scalar and > 0.'
end
% overlap == 0.20 by default
if isempty(params.overlap)
params.overlap = 0.20;
elseif (length(params.overlap)>1) || (params.overlap<0) || (params.overlap>0.5)
error 'Overlap must be scalar and 0 < overlap < 1.'
end
% ============================================
% subfunction - tiled_gridfit
% ============================================
function zgrid=tiled_gridfit(x,y,z,xnodes,ynodes,params)
% tiled_gridfit: a tiled version of gridfit, continuous across tile boundaries
% usage: [zgrid,xgrid,ygrid]=tiled_gridfit(x,y,z,xnodes,ynodes,params)
%
% Tiled_gridfit is used when the total grid is far too large
% to model using a single call to gridfit. While gridfit may take
% only a second or so to build a 100x100 grid, a 2000x2000 grid
% will probably not run at all due to memory problems.
%
% Tiles in the grid with insufficient data (<4 points) will be
% filled with NaNs. Avoid use of too small tiles, especially
% if your data has holes in it that may encompass an entire tile.
%
% A mask may also be applied, in which case tiled_gridfit will
% subdivide the mask into tiles. Note that any boolean mask
% provided is assumed to be the size of the complete grid.
%
% Tiled_gridfit may not be fast on huge grids, but it should run
% as long as you use a reasonable tilesize. 8-)
% Note that we have already verified all parameters in check_params
% Matrix elements in a square tile
tilesize = params.tilesize;
% Size of overlap in terms of matrix elements. Overlaps
% of purely zero cause problems, so force at least two
% elements to overlap.
overlap = max(2,floor(tilesize*params.overlap));
% reset the tilesize for each particular tile to be inf, so
% we will never see a recursive call to tiled_gridfit
Tparams = params;
Tparams.tilesize = inf;
nx = length(xnodes);
ny = length(ynodes);
zgrid = zeros(ny,nx);
% linear ramp for the bilinear interpolation
rampfun = inline('(t-t(1))/(t(end)-t(1))','t');
% loop over each tile in the grid
h = waitbar(0,'Relax and have a cup of JAVA. Its my treat.');
warncount = 0;
xtind = 1:min(nx,tilesize);
while ~isempty(xtind) && (xtind(1)<=nx)
xinterp = ones(1,length(xtind));
if (xtind(1) ~= 1)
xinterp(1:overlap) = rampfun(xnodes(xtind(1:overlap)));
end
if (xtind(end) ~= nx)
xinterp((end-overlap+1):end) = 1-rampfun(xnodes(xtind((end-overlap+1):end)));
end
ytind = 1:min(ny,tilesize);
while ~isempty(ytind) && (ytind(1)<=ny)
% update the waitbar
waitbar((xtind(end)-tilesize)/nx + tilesize*ytind(end)/ny/nx)