From d3a9017f570cbd94f970e093dcc6f83d602a6ce1 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Sat, 14 Mar 2026 16:56:16 -0400 Subject: [PATCH 01/14] Smaller 2D neighborhood --- README.md | 1 - devtools/README.md | 1 + devtools/test/test_intersecting_bins_2d.m | 12 ++++-- devtools/test/test_neighbor_template_2d.m | 24 ++++++----- pcfft/utils/intersecting_bins_2d.m | 51 +++++++++++++++-------- 5 files changed, 56 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 06a8fcd..4bb8779 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ This package involves solving many poorly conditioned least squares problems. Th ## Wishlist * Tighter neighboring boxes -* Continuous integration * Profiling, particularly on an old computer * FMM3DBIE demo * Matern kernel (3D) (The dense is too fast, but we win in memory?) diff --git a/devtools/README.md b/devtools/README.md index 698d0ac..843fffe 100644 --- a/devtools/README.md +++ b/devtools/README.md @@ -3,4 +3,5 @@ * `accuracy_tests/` contains tests for the accuracy of the entire PCFFT algorithm applied to 2D and 3D problems for a range of kernels. They can be thought of as integration tests. * `convergence_plots/` contains scripts which run various parts of the PCFFT algorithm, and record + plot the convergence of various quantities like error, nshell, nproxy, and dx with the requested tolerance. * `test/` are unit tests for the various helper functions in the PCFFT package. +* `timing_tests/` contain scripts that loop through problem sizes and compare the time required to solve Helmholtz BVPs using PCFFT, FLAM, and chunkIE+FMM. * `visual_checks/` are unit tests which don't assert anything, but are helpful for visually checking outputs of various parts of the code, i.e. grid point placement. \ No newline at end of file diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index 73aa838..751f53a 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -119,9 +119,13 @@ % expected_bin_0_intersecting_y = [-1, 0, 1]; disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins y: "); disp(bin_0_intersecting_y); +% In reality, we expect the unique sorted values of bin_0_intersecting_x +% to be [-1, 0, 1] expected_bin_0_intersecting = [-1 0 1]; -assert(all(bin_0_intersecting_x == expected_bin_0_intersecting)); -assert(all(bin_0_intersecting_y == expected_bin_0_intersecting)); +unique_bin_0_intersecting_x = unique(bin_0_intersecting_x); +unique_bin_0_intersecting_y = unique(bin_0_intersecting_y); +assert(all(unique_bin_0_intersecting_x == expected_bin_0_intersecting)); +assert(all(unique_bin_0_intersecting_y == expected_bin_0_intersecting)); % Figure shows that bin idx 5 intersects with 2, 4, 5, 8. % bin_idx 5 corresponds to (id_x, id_y) = (1, 2) @@ -135,8 +139,8 @@ expected_bin_5_intersecting_x = [0 1 2]; expected_bin_5_intersecting_y = [1 2 3]; -assert(all(bin_5_intersecting_x == expected_bin_5_intersecting_x)); -assert(all(bin_5_intersecting_y == expected_bin_5_intersecting_y)); +assert(all(unique(bin_5_intersecting_x) == expected_bin_5_intersecting_x)); +assert(all(unique(bin_5_intersecting_y) == expected_bin_5_intersecting_y)); close all; diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index c7e4f30..e6e207d 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -30,16 +30,20 @@ assert( bin_idx == 8); -n_x = sqrt(length(nbr_binids)); -expected_n_pts = n_x * grid_info.nbinpts + 2 * grid_info.rpad; -disp("test_neighbor_template_2d: expected_n_pts: "); -disp(expected_n_pts^2); -disp("test_neighbor_template_2d: nbr_gridpts size: "); -disp(size(nbr_gridpts)); -disp("test_neighbor_template_2d: nbr_grididxes size: "); -disp(size(nbr_grididxes)); -assert(size(nbr_gridpts, 2) == expected_n_pts^2); -assert(size(nbr_grididxes, 2) == expected_n_pts^2); +% OJM: deleting this assert statement because it was written when we computed +% the neighbor template as a rectangle, now we use a circle. + + +% n_x = sqrt(length(nbr_binids)); +% expected_n_pts = n_x * grid_info.nbinpts + 2 * grid_info.rpad; +% disp("test_neighbor_template_2d: expected_n_pts: "); +% disp(expected_n_pts^2); +% disp("test_neighbor_template_2d: nbr_gridpts size: "); +% disp(size(nbr_gridpts)); +% disp("test_neighbor_template_2d: nbr_grididxes size: "); +% disp(size(nbr_grididxes)); +% assert(size(nbr_gridpts, 2) == expected_n_pts^2); +% assert(size(nbr_grididxes, 2) == expected_n_pts^2); diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index 7ba10e4..6a8b6cd 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -19,26 +19,41 @@ id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_xs = id_x_min:id_x_max; - id_ys = id_y_min:id_y_max; + % id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % Compute the binids - binids = zeros(length(id_xs) * length(id_ys), 1 ); - for i = 1:length(id_xs) - for j = 1:length(id_ys) - % If it's an invalid binid, set it to -1 - if id_xs(i) < 0 || id_xs(i) >= grid_info.nbin(1) || ... - id_ys(j) < 0 || id_ys(j) >= grid_info.nbin(2) - - binids((i-1)*length(id_ys) + j) = -1; - else - binids((i-1)*length(id_ys) + j) = ... - id_xs(i) * N_y_bins + id_ys(j); - end - end + + % Radius in index space is 2 * radius / (nspread * dx). + rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + id_xs = []; + id_ys = []; + % Loop over idx_x + for idx_x = id_x_min:id_x_max + + % Compute max idx_y for this idx_x + dx_offset = idx_x - id_x; + idx_y_max = id_y + floor(rad^2 - dx_offset^2 ); + idx_y_min = id_y - floor(rad^2 - dx_offset^2 ); + + ny = idx_y_max - idx_y_min + 1; + id_xs = [id_xs, repmat(idx_x, 1, ny)]; + id_ys = [id_ys, idx_y_min:idx_y_max]; end + + % Compute the binids + binids = id_ys(:) + id_xs(:) * N_y_bins; + binids(id_xs < 0 | id_xs >= grid_info.nbin(1) | ... + id_ys < 0 | id_ys >= grid_info.nbin(2)) = -1; + % for i = 1:length(id_xs) + % % If it's an invalid binid, set it to -1 + % if id_xs(i) < 0 || id_xs(i) >= grid_info.nbin(1) || ... + % id_ys(i) < 0 || id_ys(i) >= grid_info.nbin(2) + % binids(i) = -1; + % else + % binids(i) = ... + % id_xs(i) * N_y_bins + id_ys(i); + % end + % end binids = binids.'; end \ No newline at end of file From ec71fac9b05900346584cf10e8b23177978d334e Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Sun, 15 Mar 2026 14:28:12 -0400 Subject: [PATCH 02/14] Need to test intersecting_bins_2d logic more throughly --- devtools/accuracy_tests/test_log_2D.m | 8 ++-- devtools/test/test_intersecting_bins_3d.m | 27 +++++++++++++ pcfft/get_addsub.m | 5 ++- pcfft/utils/intersecting_bins_3d.m | 49 ++++++++++++++++++----- 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 devtools/test/test_intersecting_bins_3d.m diff --git a/devtools/accuracy_tests/test_log_2D.m b/devtools/accuracy_tests/test_log_2D.m index edffea6..137ce7d 100644 --- a/devtools/accuracy_tests/test_log_2D.m +++ b/devtools/accuracy_tests/test_log_2D.m @@ -23,15 +23,17 @@ tol = 1e-10; n_nbr = 100; [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr); - +disp("test_log_2D: grid_info:"); +disp(grid_info); [A_spread_s, sort_info_s ]= get_spread(kern_0, [], src_info, ... grid_info, proxy_info); +disp("test_log_2D: A_spread_s size: " + int2str(size(A_spread_s))); [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); - +disp("test_log_2D: A_spread_t size: " + int2str(size(A_spread_t))); A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); - +disp("test_log_2D: A_addsub size: " + int2str(size(A_addsub))); k0hat = get_kernhat(kern_0,grid_info); evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); diff --git a/devtools/test/test_intersecting_bins_3d.m b/devtools/test/test_intersecting_bins_3d.m new file mode 100644 index 0000000..cfb13eb --- /dev/null +++ b/devtools/test/test_intersecting_bins_3d.m @@ -0,0 +1,27 @@ +% Test that intersecting_bins_3d returns a valid neighborhood for every bin. +addpath(genpath('../../pcfft')); + +dx = 0.25; +nbinpts = 3; +dim = 3; + +Lbd = [-1 1 + -1 1 + -1 1]; + +grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0); + +% Spoof a proxy_info with a reasonable radius. +proxy_info = struct; +proxy_info.radius = 0.5; + +N_bin = grid_info.nbin(1) * grid_info.nbin(2) * grid_info.nbin(3); + +for bin_idx = 0:(N_bin - 1) + [~, ~, ~, binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); + assert(length(binids) <= N_bin, ... + sprintf('bin_idx %d: neighborhood size %d exceeds N_bin %d', ... + bin_idx, length(binids), N_bin)); +end + +disp('test_intersecting_bins_3d: PASSED'); diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index e31d734..fe9b978 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -62,12 +62,15 @@ % Build a spreading template matrix for adjacent source points. % Then build a list of regular gridpoints that are in the intersecting bins if dim == 2 - [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); + [nbr_binids, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); [pts0, ctr_0, ~] = grid_pts_for_box_2d(nbr_bin_idx, grid_info); else [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_3d(grid_info, proxy_info); [pts0, ctr_0, ~] = grid_pts_for_box_3d(nbr_bin_idx, grid_info); end + disp("get_addsub: nbr_binids size: " + int2str(size(nbr_binids))); + disp("get_addsub: nbr_binids: "); + disp(nbr_binids); % pts0_centered = pts0 - ctr_0; nbr_info = struct('r', reg_neighbor_template_pts); diff --git a/pcfft/utils/intersecting_bins_3d.m b/pcfft/utils/intersecting_bins_3d.m index 9ff65d4..3ec9edf 100644 --- a/pcfft/utils/intersecting_bins_3d.m +++ b/pcfft/utils/intersecting_bins_3d.m @@ -21,21 +21,48 @@ id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_z_min = id_z - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_z_max = id_z + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % id_z_min = id_z - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % id_z_max = id_z + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + + % id_xs = id_x_min:id_x_max; + % id_ys = id_y_min:id_y_max; + % id_zs = id_z_min:id_z_max; + + % Radius in index space is 2 * radius / (nspread * dx). + rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + + id_xs = []; + id_ys = []; + id_zs = []; + for idx_x = id_x_min:id_x_max + % Compute max idx_y for this idx_x + dx_offset = idx_x - id_x; + idx_y_max = id_y + floor(rad^2 - dx_offset^2 ); + idx_y_min = id_y - floor(rad^2 - dx_offset^2 ); + + for idx_y = idx_y_min:idx_y_max + % Compute max idx_z for this idx_x, idx_y + dy_offset = idx_y - id_y; + idx_z_max = id_z + floor(rad^2 - dx_offset^2 - dy_offset^2 ); + idx_z_min = id_z - floor(rad^2 - dx_offset^2 - dy_offset^2 ); + + nz = idx_z_max - idx_z_min + 1; + id_xs = [id_xs, repmat(idx_x, 1, nz)]; + id_ys = [id_ys, repmat(idx_y, 1, nz)]; + id_zs = [id_zs, idx_z_min:idx_z_max]; + end + end + - id_xs = id_x_min:id_x_max; - id_ys = id_y_min:id_y_max; - id_zs = id_z_min:id_z_max; % Compute the binids - binids = id_zs(:) + id_ys(:).'*N_z_bins + reshape(id_xs,1,1,[]) * N_y_bins * N_z_bins; + binids = id_zs + id_ys * N_z_bins + id_xs * N_y_bins * N_z_bins; - binids(:,:,id_xs<0 | id_xs >= grid_info.nbin(1)) = []; - binids(:,id_ys<0 | id_ys >= grid_info.nbin(2),:) = []; - binids(id_zs<0 | id_zs >= grid_info.nbin(3),:,:) = []; + binids(id_xs<0 | id_xs >= grid_info.nbin(1)) = -1; + binids(id_ys<0 | id_ys >= grid_info.nbin(2)) = -1; + binids(id_zs<0 | id_zs >= grid_info.nbin(3)) = -1; binids = binids(:); From ba2504a62fbe4703bbadca6e11957539e0208ea3 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Mon, 16 Mar 2026 15:54:34 -0400 Subject: [PATCH 03/14] Need to finish abstract neighborhood --- README.md | 4 + .../test_abstract_neighbor_spreading_2D.m | 149 ++++++++++++++++++ devtools/test/test_get_addsub.m | 72 ++++----- devtools/test/test_intersecting_bins_2d.m | 57 ++++++- devtools/test/test_neighbor_template_2d.m | 38 +++-- devtools/test/test_neighbor_template_2d_1.m | 31 +++- pcfft/classes/GridInfo.m | 19 ++- pcfft/get_addsub.m | 15 +- pcfft/utils/abstract_neighbor_spreading_2D.m | 53 +++++++ pcfft/utils/neighbor_template_2d.m | 114 +++++++++++--- 10 files changed, 463 insertions(+), 89 deletions(-) create mode 100644 devtools/test/test_abstract_neighbor_spreading_2D.m create mode 100644 pcfft/utils/abstract_neighbor_spreading_2D.m diff --git a/README.md b/README.md index 4bb8779..84554dd 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,11 @@ make html open _build/html/index.html ``` +## How to trim the neighborhoods +1. In `grid_info`, make a member called `zero_bin` which has the binpts for a bin centered at 0. +2. Inside `neighbor_template_2d()`, generate `nbr_gridpts` by shifting `zero_bin` by `bin_center_2d(idx)`. +3. diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m new file mode 100644 index 0000000..e135d45 --- /dev/null +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -0,0 +1,149 @@ +% Tests for abstract_neighbor_spreading_2D. +addpath(genpath('../../pcfft')); + +%% test_0: +% Basic size and centering checks on a hand-constructed grid. +% +% dx=0.5, nbinpts=2, nspread=4, so each spreading box is 4x4=16 pts. + +dim = 2; +Lbd = [-1 1; -1 1]; +dx = 0.5; +nbinpts = 2; +nspread = 4; +grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); + +proxy_info = struct; +proxy_info.radius = 0.5; + +[box_pts, spreading_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + +% box_pts must be [2, nspread^2] +assert(all(size(box_pts) == [2, nspread^2]), ... + 'box_pts must be [2, nspread^2]'); + +% box_pts must be centered at the origin +box_center = mean(box_pts, 2); +assert(norm(box_center) < 1e-12, ... + 'box_pts must be centered at the origin'); + +% No duplicate points in spreading_template_pts +[~, uid] = unique(spreading_template_pts.', 'rows'); +assert(length(uid) == size(spreading_template_pts, 2), ... + 'spreading_template_pts must have no duplicate points'); + +% box_pts must be a subset of spreading_template_pts (central bin is included) +for i = 1:size(box_pts, 2) + dists = sqrt(sum((spreading_template_pts - box_pts(:, i)).^2, 1)); + assert(min(dists) < 1e-12, ... + sprintf('box_pts column %d not found in spreading_template_pts', i)); +end + +% spreading_template_pts must have at least nspread^2 points +assert(size(spreading_template_pts, 2) >= nspread^2, ... + 'spreading_template_pts must have at least nspread^2 points'); + +% Spacing between adjacent points must be dx everywhere +% (all points lie on the same dx-spaced grid as box_pts) +% x_vals = unique(round(spreading_template_pts(1, :) / dx)); +% y_vals = unique(round(spreading_template_pts(2, :) / dx)); +% x_spacings = diff(sort(x_vals)); +% y_spacings = diff(sort(y_vals)); +% disp("x_vals: "); +% disp(x_vals); +% disp("y_vals: "); +% disp(y_vals); +% disp("x_spacings: "); +% disp(x_spacings); +% disp("y_spacings: "); +% disp(y_spacings); +% assert(all(abs(x_spacings - 1) < 1e-10), ... +% 'x-coordinates of spreading_template_pts must be evenly spaced by dx'); +% assert(all(abs(y_spacings - 1) < 1e-10), ... +% 'y-coordinates of spreading_template_pts must be evenly spaced by dx'); + +%% test_1: +% Template size matches an interior bin from neighbor_template_2d. +% +% For a grid large enough that interior bins exist, the number of valid +% (in-bounds) points returned by neighbor_template_2d for an interior bin +% must equal size(spreading_template_pts, 2). + +rng(0); +n_src = 200; +n_targ = 150; +tol = 1e-8; + +scale = 4.0; + +src_info = struct; +src_info.r = (rand(2, n_src) - 0.5) * scale; +src_info.weights = rand(n_src, 1); + +targ_info = struct; +targ_info.r = (rand(2, n_targ) - 0.5) * scale; + +[grid_info_1, proxy_info_1] = get_grid(@log_kernel, src_info, targ_info, tol, 50); + +[box_pts_1, tmpl_pts_1] = abstract_neighbor_spreading_2D(grid_info_1, proxy_info_1); + +% check the number of interior bins +disp("test_1: grid_info_1.nbin: "); +disp(grid_info_1.nbin); + +% Radius in index space +rad = ceil(2 * proxy_info_1.radius / (grid_info_1.nspread * grid_info_1.dx)); + +disp("test_1: Radius in index space: " + int2str(rad)); +% Size check +assert(all(size(box_pts_1) == [2, grid_info_1.nspread^2]), ... + 'box_pts must be [2, nspread^2] for get_grid output'); + +% For every bin, count the number of in-bounds points that neighbor_template_2d +% returns. For interior bins this should equal size(tmpl_pts_1, 2). +n_template = size(tmpl_pts_1, 2); +ngridpts = grid_info_1.ngrid(1) * grid_info_1.ngrid(2); + +n_bins = grid_info_1.nbin(1) * grid_info_1.nbin(2); +found_interior = false; +for bin_idx = 0:(n_bins - 1) + [~, ~, nbr_grididxes, ~] = neighbor_template_2d(grid_info_1, proxy_info_1, bin_idx); + n_valid = sum(nbr_grididxes <= ngridpts); + % disp("test_1: For bin_idx " + int2str(bin_idx) + ", n_valid: " + int2str(n_valid) + ", n_template: " + int2str(n_template)); + if n_valid == n_template + found_interior = true; + break; + end +end + +assert(found_interior, ... + ['No bin found whose valid neighbor grid point count matches ' ... + 'abstract_neighbor_spreading_2D. Template may be over- or under-sized.']); + +%% test_2: +% Template covers the correct neighbor offsets. +% +% Each point in spreading_template_pts must lie within the spreading box of +% some bin offset (delta_x, delta_y) satisfying the same circular criterion +% used by intersecting_bins_2d. + +dx2 = grid_info.dx; +nsp2 = grid_info.nspread; +nbp2 = grid_info.nbinpts; +rad2 = ceil(2 * proxy_info.radius / (nsp2 * dx2)); +half = (nsp2 - 1) / 2 * dx2; % half-width of a spreading box + +for i = 1:size(spreading_template_pts, 2) + pt = spreading_template_pts(:, i); + % Find which bin offset this point belongs to + delta_x = round(pt(1) / (nbp2 * dx2)); + delta_y = round(pt(2) / (nbp2 * dx2)); + % Check that (delta_x, delta_y) satisfies the intersection criterion + assert(delta_x^2 + delta_y^2 <= rad2^2 + 1e-10, ... + sprintf('Template point (%g,%g) belongs to offset (%d,%d) outside neighborhood', ... + pt(1), pt(2), delta_x, delta_y)); + % Check that the point lies within that bin's spreading box + center = [delta_x; delta_y] * nbp2 * dx2; + assert(max(abs(pt - center)) <= half + 1e-10, ... + sprintf('Template point (%g,%g) not within its bin offset box', pt(1), pt(2))); +end diff --git a/devtools/test/test_get_addsub.m b/devtools/test/test_get_addsub.m index 60cc904..a78a203 100644 --- a/devtools/test/test_get_addsub.m +++ b/devtools/test/test_get_addsub.m @@ -55,55 +55,55 @@ %% 3D case % Makes sure get_addsub returns without error on a 2D input. -clear; -addpath(genpath('../../pcfft')); +% clear; +% addpath(genpath('../../pcfft')); -rad = 10.0; -tol = 1e-6; -dim = 3; +% rad = 10.0; +% tol = 1e-6; +% dim = 3; -k = @(s,t) one_over_r_kernel(s,t); +% k = @(s,t) one_over_r_kernel(s,t); -src_info_3d = struct; -n_src = 13; -rng(0); -src_info_3d.r = (rand(dim, n_src) - 0.5); -src_info_3d.weights = rand(n_src, 1); +% src_info_3d = struct; +% n_src = 13; +% rng(0); +% src_info_3d.r = (rand(dim, n_src) - 0.5); +% src_info_3d.weights = rand(n_src, 1); -targ_info_3d = struct; -ntarg = 17; -targ_info_3d.r = rand(dim, ntarg) - 0.5; +% targ_info_3d = struct; +% ntarg = 17; +% targ_info_3d.r = rand(dim, ntarg) - 0.5; -[grid_info_3d, proxy_info_3d] = get_grid(k, ... - src_info_3d, targ_info_3d, tol); +% [grid_info_3d, proxy_info_3d] = get_grid(k, ... +% src_info_3d, targ_info_3d, tol); -% disp("grid_info.ngrid: ") -% disp(grid_info.ngrid) -% disp("grid_info.dx: ") -% disp(grid_info.dx) -% disp(grid_info.r(:, 1:100)) +% % disp("grid_info.ngrid: ") +% % disp(grid_info.ngrid) +% % disp("grid_info.dx: ") +% % disp(grid_info.dx) +% % disp(grid_info.r(:, 1:100)) -% scatter(grid_info.r(1,:), grid_info.r(2,:), 'k.'); -% hold on; -% scatter(src_info_2d.r(1,:), src_info_2d.r(2,:), 'ro'); +% % scatter(grid_info.r(1,:), grid_info.r(2,:), 'k.'); +% % hold on; +% % scatter(src_info_2d.r(1,:), src_info_2d.r(2,:), 'ro'); -kern_0 = @(s,t) one_over_r_kernel(s,t); -kern = @(s,t) one_over_r_kernel(s,t); +% kern_0 = @(s,t) one_over_r_kernel(s,t); +% kern = @(s,t) one_over_r_kernel(s,t); -[A_spread_s, sort_info_s ]= get_spread(kern_0, kern, src_info_3d, ... -grid_info_3d, proxy_info_3d); +% [A_spread_s, sort_info_s ]= get_spread(kern_0, kern, src_info_3d, ... +% grid_info_3d, proxy_info_3d); -[A_spread_t, sort_info_t ]= get_spread(kern_0, kern, targ_info_3d, ... -grid_info_3d, proxy_info_3d); +% [A_spread_t, sort_info_t ]= get_spread(kern_0, kern, targ_info_3d, ... +% grid_info_3d, proxy_info_3d); -A_addsub = get_addsub(kern_0, kern, src_info_3d, targ_info_3d, ... - grid_info_3d, proxy_info_3d, sort_info_s, sort_info_t, A_spread_s, A_spread_t); +% A_addsub = get_addsub(kern_0, kern, src_info_3d, targ_info_3d, ... +% grid_info_3d, proxy_info_3d, sort_info_s, sort_info_t, A_spread_s, A_spread_t); -% Check that A_adsub has the correct size. -assert(all(size(A_addsub) == [ntarg n_src])); -assert(all(~isnan(A_addsub(:)))); -assert(all(~isinf(A_addsub(:)))); \ No newline at end of file +% % Check that A_adsub has the correct size. +% assert(all(size(A_addsub) == [ntarg n_src])); +% assert(all(~isnan(A_addsub(:)))); +% assert(all(~isinf(A_addsub(:)))); \ No newline at end of file diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index 751f53a..1907982 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -28,11 +28,11 @@ % valid bin_idxes should be between 0 and N_bin - 1 for bin_idx = 0:(N_bin - 1) - [idx_x, idx_y] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); - % disp("test_intersecting_bins_2d: For bin_idx " + int2str(bin_idx) + ... - % ", intersecting bins: "); - % disp(bin_idxes); + [idx_x, idx_y, binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); + % Once we remove invalid binids, there should be no repeats. + valid_binids = binids(binids >= 0); + assert(length(valid_binids) == length(unique(valid_binids))); end @@ -40,7 +40,7 @@ % Larger test case with visualization. Same setup as test_SortInfo_2d_1 -n_pts = 100000; +n_pts = 10000; L = 2.0; Lbd = [-1 1; -1 1]; @@ -145,4 +145,51 @@ close all; %% test_0c +% Test that the returned bins for each query bin satisfy the proxy-circle +% intersection condition + +rng(42); +n_src = 200; +n_targ = 150; +tol_0c = 1e-6; + +src_info_0c = struct; +src_info_0c.r = rand(2, n_src) - 0.5; +src_info_0c.weights = rand(n_src, 1); + +targ_info_0c = struct; +targ_info_0c.r = rand(2, n_targ) - 0.5; + +[grid_info_0c, proxy_info_0c] = get_grid(@log_kernel, src_info_0c, targ_info_0c, tol_0c); + +N_bin_0c = grid_info_0c.nbin(1) * grid_info_0c.nbin(2); +all_bins = 0:(N_bin_0c - 1); +tol_dist = 1e-10; + +for bin_idx = 0:(N_bin_0c - 1) + [~, ~, binids] = intersecting_bins_2d(bin_idx, grid_info_0c, proxy_info_0c); + c1 = bin_center(bin_idx, grid_info_0c); + valid_returned = binids(binids >= 0); + assert(length(valid_returned) == length(unique(valid_returned)), ... + sprintf('bin_idx %d: returned bins have repeats', bin_idx)); + + % First returned bins must intersect + for k = 1:length(valid_returned) + c2 = bin_center(valid_returned(k), grid_info_0c); + d = norm(c1 - c2); + assert(d <= 2 * proxy_info_0c.radius + tol_dist, ... + sprintf('bin %d -> bin %d dist=%.6f > 2*r=%.6f', ... + bin_idx, valid_returned(k), d, 2*proxy_info_0c.radius)); + end + + % non-returned valid bins must not intersect + non_returned = setdiff(all_bins, valid_returned); + for k = 1:length(non_returned) + c2 = bin_center(non_returned(k), grid_info_0c); + d = norm(c1 - c2); + assert(d > 2 * proxy_info_0c.radius - tol_dist, ... + sprintf('bin %d -> bin %d dist=%.6f <= 2*r=%.6f but not returned', ... + bin_idx, non_returned(k), d, 2*proxy_info_0c.radius)); + end +end diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index e6e207d..e92e464 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -75,15 +75,7 @@ % Create a GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx, ngrid grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); -% grid_info = struct; -% grid_info.nbin = nbin; -% grid_info.dx = dx; -% grid_info.Lbd = Lbd; -% grid_info.nspread = 2*nbinpts + 1; -% grid_info.nbinpts = nbinpts; -% pad = ceil((grid_info.nspread - nbinpts)/2); -% grid_info.offset = pad * dx - dx/2; -% grid_info.dx = dx; + % spoof the ProxyInfo object. Need radius proxy_info = struct; @@ -117,8 +109,10 @@ % plot(proxypts(1,:), proxypts(2,:), 'k-'); % end +BOX_IDX = 2; + % Figure shows that bin idx 0 only intersects with 0, 1, 3. -[nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, 0); +[nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX); % Now center at the center of bin idx 0. ctr = bin_center(bin_idx, grid_info); @@ -127,11 +121,13 @@ temp_at_4 = nbr_gridpts; % Plot the regular grid points and plot the template points overtop them. +% blue circles scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); % Plot the template points hold on; -scatter(temp_at_4(1,:), temp_at_4(2,:), 5, 'r', 'filled'); +% red dots are the template points before filtering out the invalid ones +% scatter(temp_at_4(1,:), temp_at_4(2,:), 5, 'r', 'filled'); % Plot text lables for the valid nbr idxes @@ -143,12 +139,18 @@ valid_temp_pts = temp_at_4(:, ~oob_idxes); % Plot the valid template points in green +% green dots are the valid template points after filtering out the invalid ones scatter(valid_temp_pts(1,:), valid_temp_pts(2,:), 20, 'g', 'filled'); % close all; for i = 1:size(valid_temp_pts, 2) pt = valid_temp_pts(:, i); diffs = grid_info.r - pt; + % disp("test_neighbor_template_2d: Checking pt " + int2str(i) + " at idx " + int2str(nbr_grididxes(i))); + % disp("pt: "); + % disp(pt); + % disp("diffs: "); + % disp(diffs); dists = sqrt(sum(diffs.^2, 1)); min_dist = min(dists); assert(min_dist < 1e-12); @@ -162,7 +164,7 @@ text(valid_temp_pts(1,:), valid_temp_pts(2,:), string(valid_nbr_idxes), 'Color', 'k'); % Plot the spreading box for bin idx 0 -[boxpts, boxctr, boxidxes] = grid_pts_for_box_2d(0, grid_info); +[boxpts, boxctr, boxidxes] = grid_pts_for_box_2d(BOX_IDX, grid_info); disp("test_neighbor_template_2d: boxidxes: "); disp(boxidxes); boxpts2 = grid_info.r(:, boxidxes); @@ -170,6 +172,8 @@ diffs = boxpts - boxpts2; dists = sqrt(sum(diffs.^2, 1)); assert(max(dists) < 1e-12); + +% magenta dots are the boxpts for bin idx 0 scatter(boxpts2(1,:), boxpts2(2,:), 20, 'm', 'filled'); @@ -245,13 +249,13 @@ valid_nbr_gridpts = nbr_gridpts(:, ~oob_idxes); % Make a new figure with the grid points and the template points for this bin idx - figure; - scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); - hold on; - scatter(valid_nbr_gridpts(1,:), valid_nbr_gridpts(2,:), 20, 'rx'); + % figure; + % scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); + % hold on; + % scatter(valid_nbr_gridpts(1,:), valid_nbr_gridpts(2,:), 20, 'rx'); title("Bin idx " + int2str(bin_idx)); xlabel("x"); ylabel("y"); end -close all; \ No newline at end of file +% close all; \ No newline at end of file diff --git a/devtools/test/test_neighbor_template_2d_1.m b/devtools/test/test_neighbor_template_2d_1.m index 23efa40..c28452e 100644 --- a/devtools/test/test_neighbor_template_2d_1.m +++ b/devtools/test/test_neighbor_template_2d_1.m @@ -113,8 +113,37 @@ a_1 = A_spread_s(:, source_idx); a_2 = A_spread_s(nbr_grididxes, source_idx); - disp("test_neighbor_template_2d: For bin_idx " + int2str(bin_idx) + ", norm of A_spread_s(:, source_idx): " + num2str(norm(a_1, "fro")) + ", norm of A_spread_s(nbr_grididxes, source_idx): " + num2str(norm(a_2, "fro"))); + % disp("test_neighbor_template_2d: For bin_idx " + int2str(bin_idx) + ", norm of A_spread_s(:, source_idx): " + num2str(norm(a_1, "fro")) + ", norm of A_spread_s(nbr_grididxes, source_idx): " + num2str(norm(a_2, "fro"))); % Assert norms are close. assert(norm(a_1, "fro") - norm(a_2, "fro") < 1e-12); +end + +%% Part 2: many pts + +n_src = 10000; +n_targ = 5017; +dim = 2; + +kern_0 = @(s,t) log_kernel(s,t); +src_info = struct; +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] +src_info.r = (rand(dim, n_src) - 0.5); +targ_info = struct; +targ_info.r = (rand(dim, n_targ) - 0.5); + +tol = 1e-10; +n_nbr = 100; +[grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr); + +% Loop through all of the boxes +for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2)-1 + [nbr_binids, nbr_gridpts, nbr_grididxes, ~] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + + % Assert that # binids returned = # grid pts returned = # grid idxes returned + assert(length(nbr_grididxes) == size(nbr_gridpts, 2)); + + % Assert that the grid points are within the valid range unless marked + % with a dummy idx. + end \ No newline at end of file diff --git a/pcfft/classes/GridInfo.m b/pcfft/classes/GridInfo.m index 1364d27..bf9dda5 100644 --- a/pcfft/classes/GridInfo.m +++ b/pcfft/classes/GridInfo.m @@ -36,6 +36,8 @@ % minimum coordinate value of the padded grid. % n_nbr : int % average number of near-field neighbours. + % zero_bin : array [dim, nbinpts^dim] + % Grid points of a spreading bin centered at the origin. properties ngrid @@ -51,6 +53,7 @@ rmax rmin n_nbr + zero_bin end methods function obj = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr) @@ -71,7 +74,7 @@ if dim == 2 % Create a regular grid with spacing dx starting at the xmin, ymin point - % specified by Lbd. + % specified by Lbd. xx = Lbd(1, 1) - offset + (0: ngrid(1) - 1) * dx; yy = Lbd(2, 1) - offset + (0: ngrid(2) - 1) * dx; [X, Y] = meshgrid(xx, yy); @@ -90,6 +93,19 @@ rmax = max(rgrid, [], 2); rmin = min(rgrid, [], 2); + if dim == 2 + zero_pts = dx * (0:nbinpts-1) - (nbinpts-1)/2 * dx; + [X, Y] = meshgrid(zero_pts, zero_pts); + zero_bin = [X(:).'; Y(:).']; + elseif dim == 3 + zero_pts = dx * (0:nbinpts-1) - (nbinpts-1)/2 * dx; + [X, Y, Z] = meshgrid(zero_pts, zero_pts, zero_pts); + X = permute(X,[3,1,2]); + Y = permute(Y,[3,1,2]); + Z = permute(Z,[3,1,2]); + zero_bin = [X(:).'; Y(:).'; Z(:).']; + end + obj.ngrid = ngrid; obj.Lbd = Lbd; obj.dx = dx; @@ -103,6 +119,7 @@ obj.rmax = rmax; obj.rmin = rmin; obj.n_nbr = n_nbr; + obj.zero_bin = zero_bin; end end end diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index fe9b978..0bef8fb 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -62,15 +62,16 @@ % Build a spreading template matrix for adjacent source points. % Then build a list of regular gridpoints that are in the intersecting bins if dim == 2 - [nbr_binids, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); - [pts0, ctr_0, ~] = grid_pts_for_box_2d(nbr_bin_idx, grid_info); + % [nbr_binids, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); + % [pts0, ctr_0, ~] = grid_pts_for_box_2d(nbr_bin_idx, grid_info); + [pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); else [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_3d(grid_info, proxy_info); [pts0, ctr_0, ~] = grid_pts_for_box_3d(nbr_bin_idx, grid_info); end - disp("get_addsub: nbr_binids size: " + int2str(size(nbr_binids))); - disp("get_addsub: nbr_binids: "); - disp(nbr_binids); + % disp("get_addsub: nbr_binids size: " + int2str(size(nbr_binids))); + % disp("get_addsub: nbr_binids: "); + % disp(nbr_binids); % pts0_centered = pts0 - ctr_0; nbr_info = struct('r', reg_neighbor_template_pts); @@ -204,6 +205,10 @@ A_spread_t_i = A_spread_t(reg_idxs_i, opdim(1)*(idx_ti_start-1)+1:opdim(1)*idx_ti_end); A_spread_s_j = A_spread_s(nbr_grididxes, source_idx_dof); % AKA_chunk = (A_spread_t_i.' * K_nbr2bin) * A_spread_s_j; + disp("get_addsub: size(A_spread_t_i): " + int2str(size(A_spread_t_i))); + disp("get_addsub: size(K_nbr2bin): " + int2str(size(K_nbr2bin))); + disp("get_addsub: size(A_spread_s_j): " + int2str(size(A_spread_s_j))); + AKA_chunk = A_spread_t_i.' * (K_nbr2bin * A_spread_s_j); Aloc = K_src_to_targ - AKA_chunk; diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m new file mode 100644 index 0000000..842590f --- /dev/null +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -0,0 +1,53 @@ +function [box_pts, spreading_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info) + % Constructs a fictitious spreading box centered at the origin and a + % spreading template around it, in abstract/relative coordinates. + % + % Unlike neighbor_template_2d, this function does not reference any + % specific bin index or global grid — the result represents the full + % interior neighborhood for any non-boundary bin. + % + % Inputs + % ------ + % grid_info : GridInfo + % Regular grid and bin structure. + % proxy_info : ProxyInfo (or equivalent struct with a .radius field) + % Proxy geometry used to determine which bins intersect. + % + % Outputs + % ------- + % box_pts : array [2, nspread^2] + % Coordinates of the spreading box centered at the origin. + % spreading_template_pts : array [2, n_template_pts] + % Coordinates of all unique spreading box points across every + % neighboring bin (including the central bin), in origin-relative + % coordinates. + + dx = grid_info.dx; + nspread = grid_info.nspread; + nbinpts = grid_info.nbinpts; + + % Spreading box centered at origin: nspread points per dimension. + box_1d = dx * (0:nspread-1) - (nspread-1)/2 * dx; + [X, Y] = meshgrid(box_1d, box_1d); + box_pts = [X(:).'; Y(:).']; % [2, nspread^2] + + % Neighborhood radius in bin-index units — same formula as + % intersecting_bins_2d (line 28). + rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + + % Collect spreading box points for every neighboring bin offset. + all_pts = zeros(2, 0); + for delta_x = -rad : rad + delta_y_max = floor(rad^2 - delta_x^2); + for delta_y = -delta_y_max : delta_y_max + shift = [delta_x; delta_y] * nbinpts * dx; + all_pts = [all_pts, box_pts + shift]; + end + end + + % Deduplicate while preserving insertion order. + disp("abstract_neighbor_spreading_2D: Number of points before deduplication: " + int2str(size(all_pts, 2))); + [~, uid] = unique(all_pts.', 'rows', 'stable'); + spreading_template_pts = all_pts(:, uid); + disp("abstract_neighbor_spreading_2D: Number of points after deduplication: " + int2str(size(spreading_template_pts, 2))); +end diff --git a/pcfft/utils/neighbor_template_2d.m b/pcfft/utils/neighbor_template_2d.m index 03818ae..94b1c6b 100644 --- a/pcfft/utils/neighbor_template_2d.m +++ b/pcfft/utils/neighbor_template_2d.m @@ -1,11 +1,46 @@ function [nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, bin_idx) + % neighbor_template_2d Neighbor bin template for 2D spreading. + % + % Computes the set of neighboring bins and their spreading grid points for a + % given source bin. When called without bin_idx, selects an interior reference + % bin whose full neighborhood lies within the grid. + % + % The result serves as a reusable template: for any interior bin, the neighbor + % structure is the same up to a fixed offset. + % + % Inputs + % ------ + % grid_info : GridInfo + % Regular grid and bin structure. + % proxy_info : ProxyInfo (or equivalent) + % Proxy geometry used to determine which bins intersect. + % bin_idx : int, optional + % Linear index of the source bin. If omitted, a suitable interior bin is + % chosen automatically. + % + % Outputs + % ------- + % nbr_binids : array [1, n_nbr] + % Linear bin indices of the neighboring bins. -1 entries mark invalid bins. + % nbr_gridpts : array [2, n_nbr_pts] + % Coordinates of unique spreading grid points across all neighbor bins. + % Out-of-bounds points are retained with a dummy index. + % nbr_grididxes : array [1, n_nbr_pts] + % Linear grid indices for each column of nbr_gridpts. Out-of-bounds points + % are assigned dummy_idx = ngrid(1)*ngrid(2) + 1. + % bin_idx : int + % The source bin index used. + if nargin == 2 % First, find how many bins are intersecting. [int_idx_x, int_idx_y] = intersecting_bins_2d(0, grid_info, proxy_info); + % Filter the invalid ones + int_idx_x = unique(int_idx_x(int_idx_x >= 0)); + int_idx_y = unique(int_idx_y(int_idx_y >= 0)); % Now find a bin s.t. all of the intersecting bins have idx >= 0. - offset_x = ceil((length(int_idx_x) - 1) / 2); - offset_y = ceil((length(int_idx_y) - 1) / 2); + offset_x = length(int_idx_x) - 1; + offset_y = length(int_idx_y) - 1; bin_idx = offset_x * grid_info.nbin(2) + offset_y; end @@ -25,36 +60,67 @@ ngrid = grid_info.ngrid; rmax = grid_info.rmax; rmin = grid_info.rmin; + nspread = grid_info.nspread; + + % Preallocate arrays for nbr_gridpts and nbr_grididxes. + nbr_gridpts = zeros(2, nspread^2 * length(nbr_binids)) * NaN; + nbr_grididxes = zeros(1, nspread^2 * length(nbr_binids)) * NaN; + + for i = 1:length(nbr_binids) + % if nbr_binids(i) == -1 + % continue; + % end + [pts, ~, row_idxes] = grid_pts_for_box_2d(nbr_binids(i), grid_info); + start_idx = (i-1) * nspread^2 + 1; + end_idx = start_idx + nspread^2 - 1; + nbr_gridpts(:, start_idx:end_idx) = pts; + nbr_grididxes(start_idx:end_idx) = row_idxes; + end + + % Use unique and setdiff to find the unique grid idxes and the corresponding + % grid pts. + % TODO + [~, unique_idx] = unique(nbr_grididxes); + nbr_grididxes = nbr_grididxes(unique_idx); + nbr_gridpts = nbr_gridpts(:, unique_idx); + + % Remove all NaNs + nbr_grididxes = nbr_grididxes(~isnan(nbr_grididxes)); + nbr_gridpts = nbr_gridpts(:, ~isnan(nbr_grididxes)); + + % % Build the nbr_gridpts % % npts = number of regular grid points across the side of the neighbor template - nx = size(int_idx_x, 2); - npts = nx * nbinpts + 2 * rpad; - minxnbr = min(int_idx_x); % Minimum x bin index of the neighbor bins - minynbr = min(int_idx_y); % Minimum y bin index of the neighbor bins - - nbr_xpts = Lbd(1) - offset + minxnbr * nbinpts * dx + dx * (0:npts-1); - nbr_ypts = Lbd(2) - offset + minynbr * nbinpts * dx + dx * (0:npts-1); - [X, Y] = meshgrid(nbr_xpts, nbr_ypts); - nbr_gridpts = [X(:).'; Y(:).']; - - % % Build the nbr_grididxes. This logic is copied from grid_pts_for_box_2d - % and npts is used instead of nspread. - x_positions = minxnbr * nbinpts +1 : minxnbr * nbinpts + npts ; - y_positions = minynbr * nbinpts +1 : minynbr * nbinpts + npts ; - - % Mark out-of-bounds grid points with a dummy index + % nx = size(int_idx_x, 2); + % npts = nx * nbinpts + 2 * rpad; + % minxnbr = min(int_idx_x); % Minimum x bin index of the neighbor bins + % minynbr = min(int_idx_y); % Minimum y bin index of the neighbor bins + + % nbr_xpts = Lbd(1) - offset + minxnbr * nbinpts * dx + dx * (0:npts-1); + % nbr_ypts = Lbd(2) - offset + minynbr * nbinpts * dx + dx * (0:npts-1); + % [X, Y] = meshgrid(nbr_xpts, nbr_ypts); + % nbr_gridpts = [X(:).'; Y(:).']; + + % % % Build the nbr_grididxes. This logic is copied from grid_pts_for_box_2d + % % and npts is used instead of nspread. + % x_positions = minxnbr * nbinpts +1 : minxnbr * nbinpts + npts ; + % y_positions = minynbr * nbinpts +1 : minynbr * nbinpts + npts ; + + % % Mark out-of-bounds grid points with a dummy index ngridpts = grid_info.ngrid(1) * grid_info.ngrid(2); dummy_idx = ngridpts + 1; - nbr_grididxes = y_positions(:) + (x_positions(:)-1).' * ngrid(2); + % nbr_grididxes = y_positions(:) + (x_positions(:)-1).' * ngrid(2); - % Mark the row_idxes corresponding to out-of-bounds grid points with a dummy - % Might need a tiny bit of margin here + % % Mark the row_idxes corresponding to out-of-bounds grid points with a dummy + % % Might need a tiny bit of margin here margin = 0.1 * dx; - nbr_grididxes(nbr_ypts rmax(2) + margin,:) = dummy_idx; - nbr_grididxes(:,nbr_xpts rmax(1) + margin) = dummy_idx; + % Check the y points + nbr_grididxes(nbr_gridpts(2,:) < rmin(2) - margin | nbr_gridpts(2,:) > rmax(2) + margin,:) = dummy_idx; + % Check the x points + nbr_grididxes(:,nbr_gridpts(1,:) < rmin(1) - margin | nbr_gridpts(1,:) > rmax(1) + margin) = dummy_idx; - nbr_grididxes = nbr_grididxes(:).'; + % nbr_grididxes = nbr_grididxes(:).'; end \ No newline at end of file From 182b422ff380c41e74b2f56bf1187f5b6b074ca6 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Tue, 17 Mar 2026 12:00:33 -0400 Subject: [PATCH 04/14] Updates to neighborhood radius logic. Want to pull in get_grid manual halfside optionality --- .../test_abstract_neighbor_spreading_2D.m | 35 ++++++------- devtools/test/test_intersecting_bins_2d.m | 52 +++++++++++-------- pcfft/utils/abstract_neighbor_spreading_2D.m | 8 +-- pcfft/utils/intersecting_bins_2d.m | 17 +++--- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m index e135d45..7371701 100644 --- a/devtools/test/test_abstract_neighbor_spreading_2D.m +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -43,24 +43,6 @@ assert(size(spreading_template_pts, 2) >= nspread^2, ... 'spreading_template_pts must have at least nspread^2 points'); -% Spacing between adjacent points must be dx everywhere -% (all points lie on the same dx-spaced grid as box_pts) -% x_vals = unique(round(spreading_template_pts(1, :) / dx)); -% y_vals = unique(round(spreading_template_pts(2, :) / dx)); -% x_spacings = diff(sort(x_vals)); -% y_spacings = diff(sort(y_vals)); -% disp("x_vals: "); -% disp(x_vals); -% disp("y_vals: "); -% disp(y_vals); -% disp("x_spacings: "); -% disp(x_spacings); -% disp("y_spacings: "); -% disp(y_spacings); -% assert(all(abs(x_spacings - 1) < 1e-10), ... -% 'x-coordinates of spreading_template_pts must be evenly spaced by dx'); -% assert(all(abs(y_spacings - 1) < 1e-10), ... -% 'y-coordinates of spreading_template_pts must be evenly spaced by dx'); %% test_1: % Template size matches an interior bin from neighbor_template_2d. @@ -93,12 +75,25 @@ % Radius in index space rad = ceil(2 * proxy_info_1.radius / (grid_info_1.nspread * grid_info_1.dx)); - disp("test_1: Radius in index space: " + int2str(rad)); + % Size check assert(all(size(box_pts_1) == [2, grid_info_1.nspread^2]), ... 'box_pts must be [2, nspread^2] for get_grid output'); +% Plot the points +figure; +subplot(1, 2, 1); +scatter(src_info.r(1, :), src_info.r(2, :), 'b.'); +hold on; +scatter(targ_info.r(1, :), targ_info.r(2, :), 'r.'); +title('Sources and Targets'); +axis equal; +subplot(1, 2, 2); +scatter(tmpl_pts_1(1, :), tmpl_pts_1(2, :), 'k.'); +title('Spreading Template Points'); +axis equal; + % For every bin, count the number of in-bounds points that neighbor_template_2d % returns. For interior bins this should equal size(tmpl_pts_1, 2). n_template = size(tmpl_pts_1, 2); @@ -109,7 +104,7 @@ for bin_idx = 0:(n_bins - 1) [~, ~, nbr_grididxes, ~] = neighbor_template_2d(grid_info_1, proxy_info_1, bin_idx); n_valid = sum(nbr_grididxes <= ngridpts); - % disp("test_1: For bin_idx " + int2str(bin_idx) + ", n_valid: " + int2str(n_valid) + ", n_template: " + int2str(n_template)); + disp("test_1: For bin_idx " + int2str(bin_idx) + ", nbr_grididxes: " + int2str(size(nbr_grididxes)) + ", n_valid: " + int2str(n_valid) + ", n_template: " + int2str(n_template)); if n_valid == n_template found_interior = true; break; diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index 1907982..ad9823a 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -47,21 +47,30 @@ % r points live on [-1, 1] x [-1, 1] rng(0); r = (rand(2, n_pts) - 0.5) * L; -r(2,:) = r(2,:); + +% Get grid and proxy info +tol = 1e-6; +[grid_info, proxy_info] = get_grid(@log_kernel, ... + struct('r', r), ... + struct('r', r), ... + tol, n_pts * 10); + % dx = 0.25, so the grid points are at -dx = 0.25; -ngrid = [9 9]; -% When we set nbinpts = 3, we expect -% x bins and y bins [-1, -0.25], [-0.25, 0.5], [0.5, 1.] -nbinpts = 3; -nbin = [3 3]; -N_bin = nbin(1) * nbin(2); - -% Generate the GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx -grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0); +% dx = 0.25; +% ngrid = [9 9]; +% % When we set nbinpts = 3, we expect +% % x bins and y bins [-1, -0.25], [-0.25, 0.5], [0.5, 1.] +% nbinpts = 3; +% nbin = [3 3]; +% N_bin = nbin(1) * nbin(2); + +% % Generate the GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx +% grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0); disp("test_intersecting_bins_2d: grid_info:"); disp(grid_info); +disp("test_intersecting_bins_2d: grid_info.nbin:"); +disp(grid_info.nbin); % Test the third return value is correct. @@ -70,19 +79,16 @@ % This should = [0 1 2 3 4 5 6 7 8 expected_bin_4_intersecting_binids = [0 1 2 3 4 5 6 7 8]; -disp("test_intersecting_bins_2d: For bin_idx 4, intersecting binids: "); -disp(bin_4_intersecting_binids); +% disp("test_intersecting_bins_2d: For bin_idx 4, intersecting binids: "); +% disp(bin_4_intersecting_binids); valid_bins = bin_4_intersecting_binids >= 0 & bin_4_intersecting_binids < N_bin; valid_bins = bin_4_intersecting_binids(valid_bins); -disp("test_intersecting_bins_2d: For bin_idx 4, valid intersecting binids: "); -disp(valid_bins); -assert(all(valid_bins == expected_bin_4_intersecting_binids)); +% disp("test_intersecting_bins_2d: For bin_idx 4, valid intersecting binids: "); +% disp(valid_bins); +% assert(all(valid_bins == expected_bin_4_intersecting_binids)); -% spoof the ProxyInfo object. Need radius -proxy_info = struct; -proxy_info.radius = 0.5; -[sort_info] = SortInfo(struct('r', r), dx, Lbd, nbin, nbinpts); +[sort_info] = SortInfo(struct('r', r), grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); r_srt = sort_info.r_srt; binid_srt = sort_info.binid_srt; ptid_srt = sort_info.ptid_srt; @@ -100,7 +106,7 @@ % Draw an x at the center of each bin hold on; -N_bins = nbin(1) * nbin(2); +N_bins = grid_info.nbin(1) * grid_info.nbin(2); for bin_idx = 0:(N_bins - 1) center = bin_center(bin_idx, grid_info); scatter(center(1), center(2), 100, 'x'); @@ -149,8 +155,8 @@ % intersection condition rng(42); -n_src = 200; -n_targ = 150; +n_src = 5000; +n_targ = 1500; tol_0c = 1e-6; src_info_0c = struct; diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index 842590f..fe3e3d3 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -33,12 +33,12 @@ % Neighborhood radius in bin-index units — same formula as % intersecting_bins_2d (line 28). - rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + rad = ceil(2 * proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); for delta_x = -rad : rad - delta_y_max = floor(rad^2 - delta_x^2); + delta_y_max = floor(sqrt(rad^2 - delta_x^2)); for delta_y = -delta_y_max : delta_y_max shift = [delta_x; delta_y] * nbinpts * dx; all_pts = [all_pts, box_pts + shift]; @@ -46,8 +46,8 @@ end % Deduplicate while preserving insertion order. - disp("abstract_neighbor_spreading_2D: Number of points before deduplication: " + int2str(size(all_pts, 2))); - [~, uid] = unique(all_pts.', 'rows', 'stable'); + disp("abstract_neighbor_spreading_2D: Number of points before deduplication: " + int2str(size(all_pts))); + [~, uid] = unique(all_pts.', 'rows'); spreading_template_pts = all_pts(:, uid); disp("abstract_neighbor_spreading_2D: Number of points after deduplication: " + int2str(size(spreading_template_pts, 2))); end diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index 6a8b6cd..9708203 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -15,17 +15,14 @@ % disp("intersecting_bins_2d: For bin_idx " + int2str(bin_idx) + ... % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); - % Which bins are within 2 * radius / (nspread * dx) ? - id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - + % Radius in index space + rad = ceil(2 * proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); - % id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + % Which bins are within 2 * radius / (nspread * dx) ? + id_x_min = id_x - rad; + id_x_max = id_x + rad; - % Radius in index space is 2 * radius / (nspread * dx). - rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); id_xs = []; id_ys = []; % Loop over idx_x @@ -33,8 +30,8 @@ % Compute max idx_y for this idx_x dx_offset = idx_x - id_x; - idx_y_max = id_y + floor(rad^2 - dx_offset^2 ); - idx_y_min = id_y - floor(rad^2 - dx_offset^2 ); + idx_y_max = id_y + ceil(sqrt(rad^2 - dx_offset^2)); + idx_y_min = id_y - ceil(sqrt(rad^2 - dx_offset^2)); ny = idx_y_max - idx_y_min + 1; id_xs = [id_xs, repmat(idx_x, 1, ny)]; From 1fb7f1b6865c22dcb3a754d7d198bc71e2a52025 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Tue, 17 Mar 2026 17:55:32 -0400 Subject: [PATCH 05/14] 2d neighborhood looks OK but still issue with get_addsub --- .../get_addsub_log_2D_manypts.m | 3 +- .../get_addsub_log_2D_threepts.m | 10 +- .../test_abstract_neighbor_spreading_2D.m | 176 ++++++++++++------ devtools/test/test_intersecting_bins_2d.m | 150 +++++++-------- devtools/test/test_neighbor_template_2d.m | 28 ++- devtools/test/test_neighbor_template_2d_1.m | 19 +- pcfft/classes/GridInfo.m | 5 + pcfft/get_addsub.m | 14 +- pcfft/utils/abstract_neighbor_spreading_2D.m | 56 +++--- pcfft/utils/neighbor_template_2d.m | 131 +++---------- 10 files changed, 303 insertions(+), 289 deletions(-) diff --git a/devtools/convergence_plots/get_addsub_log_2D_manypts.m b/devtools/convergence_plots/get_addsub_log_2D_manypts.m index 70e1015..5224190 100644 --- a/devtools/convergence_plots/get_addsub_log_2D_manypts.m +++ b/devtools/convergence_plots/get_addsub_log_2D_manypts.m @@ -21,7 +21,8 @@ -tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08]; +% tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08]; +tol_vals = [1e-05 1e-06 1e-07 1e-08]; n_tol_vals = size(tol_vals, 2); error_vals = zeros(n_tol_vals, 1); % dx_vals = zeros(n_tol_vals, 1); diff --git a/devtools/convergence_plots/get_addsub_log_2D_threepts.m b/devtools/convergence_plots/get_addsub_log_2D_threepts.m index a034d6d..c4ac831 100644 --- a/devtools/convergence_plots/get_addsub_log_2D_threepts.m +++ b/devtools/convergence_plots/get_addsub_log_2D_threepts.m @@ -28,7 +28,7 @@ K_src_to_target = log_kernel(struct('r',source_pts), struct('r',target_pts)); target_vals = K_src_to_target * src_weights; -n_nbr = 3; % 10000 points / 500 is approximately 20 boxes +n_nbr = 5; % 10000 points / 500 is approximately 20 boxes src_info = struct; src_info.r = source_pts; @@ -65,6 +65,10 @@ [A_spread_t, sort_info_t] = get_spread(k, k, targ_info, ... grid_info, proxy_info); +assert(all(~isnan(A_spread_s(:)))); +assert(all(~isinf(A_spread_s(:)))); +assert(all(~isnan(A_spread_t(:)))); +assert(all(~isinf(A_spread_t(:)))); A_addsub = get_addsub(k, k, src_info, targ_info, grid_info, ... proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); @@ -72,6 +76,10 @@ % A_addsub = A_add - A_sub; K_grid2grid = log_kernel(grid_info, grid_info); +% Put zeros on the diagonal of K_grid2grid +for i = 1:size(K_grid2grid, 1) + K_grid2grid(i, i) = 0; +end term1 = A_addsub * src_weights; disp("main: term1: ") diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m index 7371701..263f05f 100644 --- a/devtools/test/test_abstract_neighbor_spreading_2D.m +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -22,10 +22,11 @@ assert(all(size(box_pts) == [2, nspread^2]), ... 'box_pts must be [2, nspread^2]'); -% box_pts must be centered at the origin +% box_pts must be centered at center_bin's spreading box center +[~, expected_box_center] = grid_pts_for_box_2d(grid_info.center_bin, grid_info); box_center = mean(box_pts, 2); -assert(norm(box_center) < 1e-12, ... - 'box_pts must be centered at the origin'); +assert(norm(box_center - expected_box_center) < 1e-10, ... + 'box_pts must be centered at center_bin spreading box center'); % No duplicate points in spreading_template_pts [~, uid] = unique(spreading_template_pts.', 'rows'); @@ -33,8 +34,9 @@ 'spreading_template_pts must have no duplicate points'); % box_pts must be a subset of spreading_template_pts (central bin is included) +% spreading_template_pts is relative to box_center, so compare box_pts - box_center for i = 1:size(box_pts, 2) - dists = sqrt(sum((spreading_template_pts - box_pts(:, i)).^2, 1)); + dists = sqrt(sum((spreading_template_pts - (box_pts(:, i) - box_center)).^2, 1)); assert(min(dists) < 1e-12, ... sprintf('box_pts column %d not found in spreading_template_pts', i)); end @@ -43,6 +45,12 @@ assert(size(spreading_template_pts, 2) >= nspread^2, ... 'spreading_template_pts must have at least nspread^2 points'); +% spreading_template_pts must be separated by about dx +dists = sqrt(sum(diff(spreading_template_pts, 1, 2).^2, 1)); +min_dist = min(dists); +assert(min_dist + 1e-12 >= dx, ... + sprintf('Minimum distance between spreading_template_pts is %g, less than dx=%g', min_dist, dx)); + %% test_1: % Template size matches an interior bin from neighbor_template_2d. @@ -52,22 +60,22 @@ % must equal size(spreading_template_pts, 2). rng(0); -n_src = 200; -n_targ = 150; +n_src = 2000; tol = 1e-8; scale = 4.0; src_info = struct; src_info.r = (rand(2, n_src) - 0.5) * scale; -src_info.weights = rand(n_src, 1); -targ_info = struct; -targ_info.r = (rand(2, n_targ) - 0.5) * scale; -[grid_info_1, proxy_info_1] = get_grid(@log_kernel, src_info, targ_info, tol, 50); +[grid_info_1, proxy_info_1] = get_grid(@log_kernel, src_info, src_info, tol); -[box_pts_1, tmpl_pts_1] = abstract_neighbor_spreading_2D(grid_info_1, proxy_info_1); +[box_pts_1, tmpl_pts_1, tmpl_idxes_1] = abstract_neighbor_spreading_2D(grid_info_1, proxy_info_1); +box_ctr = bin_center(grid_info_1.center_bin, grid_info_1); +disp("test_1: box center: "); +disp(box_ctr); +tmpl_pts_1 = tmpl_pts_1 + box_ctr; % Shift template points to be centered at the box center % check the number of interior bins disp("test_1: grid_info_1.nbin: "); @@ -81,39 +89,87 @@ assert(all(size(box_pts_1) == [2, grid_info_1.nspread^2]), ... 'box_pts must be [2, nspread^2] for get_grid output'); -% Plot the points -figure; -subplot(1, 2, 1); -scatter(src_info.r(1, :), src_info.r(2, :), 'b.'); +% Check we are indexing the correct points. +keep_bool = tmpl_idxes_1(1,:) > 0 & tmpl_idxes_1(1,:) <= grid_info_1.ngrid(1) & ... + tmpl_idxes_1(2,:) > 0 & tmpl_idxes_1(2,:) <= grid_info_1.ngrid(2); + +temp_pts = tmpl_pts_1(:, keep_bool); +grid_idxes = (tmpl_idxes_1(1,keep_bool) - 1) * grid_info_1.ngrid(2) + tmpl_idxes_1(2,keep_bool); +grid_pts_idxed = grid_info_1.r(:, grid_idxes); + +for i = 1:size(temp_pts, 2) + dist_x = abs((grid_pts_idxed(1, i) - temp_pts(1, i))); + dist_x_dx = dist_x / grid_info_1.dx; + dist_y = abs((grid_pts_idxed(2, i) - temp_pts(2, i))); + dist_y_dx = dist_y / grid_info_1.dx; + assert(dist_x < 1e-10, ... + sprintf('Index %g, computed idx %g, grid point (%g,%g) does not match template point (%g,%g) in x. In units of dx, its %g', ... + i, grid_idxes(i), grid_pts_idxed(1, i), grid_pts_idxed(2, i), temp_pts(1, i), temp_pts(2, i), dist_x_dx)); + assert(dist_y < 1e-10, ... + sprintf('Index %g, computed idx %g, grid point (%g,%g) does not match template point (%g,%g) in y. In units of dx, its %g', ... + i, grid_idxes(i), grid_pts_idxed(1, i), grid_pts_idxed(2, i), temp_pts(1, i), temp_pts(2, i), dist_y_dx)); +end + +% Figure 1: spreading template in black and box points in red +figure(1); +scatter(tmpl_pts_1(1, :), tmpl_pts_1(2, :), 'kx'); hold on; -scatter(targ_info.r(1, :), targ_info.r(2, :), 'r.'); -title('Sources and Targets'); -axis equal; -subplot(1, 2, 2); -scatter(tmpl_pts_1(1, :), tmpl_pts_1(2, :), 'k.'); -title('Spreading Template Points'); +scatter(box_pts_1(1, :), box_pts_1(2, :), 'r.'); + + +% Figure 2: source points colored by bin +sort_info = SortInfo(src_info, grid_info_1.dx, grid_info_1.Lbd, grid_info_1.nbin, grid_info_1.nbinpts); +figure(2); +scatter(sort_info.r_srt(1,:), sort_info.r_srt(2,:), 20, sort_info.binid_srt, 'filled'); +colormap('parula'); +colorbar; +bin0_ctr = bin_center(0, grid_info_1); +hold on; +% Center the spreading template at the center of bin 0 for visualization +scatter(tmpl_pts_1(1, :) + bin0_ctr(1), tmpl_pts_1(2, :) + bin0_ctr(2), 'kx'); +% Also add the spreading box +scatter(box_pts_1(1, :) + bin0_ctr(1), box_pts_1(2, :) + bin0_ctr(2), 'ro'); +% Also add the proxy ring for bin 0 +proxypts = get_ring_points(100, proxy_info_1.radius, bin0_ctr); +plot(proxypts(1,:), proxypts(2,:) , 'k-'); +title('Sources colored by bin, with spreading template and box for bin 0'); axis equal; -% For every bin, count the number of in-bounds points that neighbor_template_2d -% returns. For interior bins this should equal size(tmpl_pts_1, 2). -n_template = size(tmpl_pts_1, 2); -ngridpts = grid_info_1.ngrid(1) * grid_info_1.ngrid(2); - -n_bins = grid_info_1.nbin(1) * grid_info_1.nbin(2); -found_interior = false; -for bin_idx = 0:(n_bins - 1) - [~, ~, nbr_grididxes, ~] = neighbor_template_2d(grid_info_1, proxy_info_1, bin_idx); - n_valid = sum(nbr_grididxes <= ngridpts); - disp("test_1: For bin_idx " + int2str(bin_idx) + ", nbr_grididxes: " + int2str(size(nbr_grididxes)) + ", n_valid: " + int2str(n_valid) + ", n_template: " + int2str(n_template)); - if n_valid == n_template - found_interior = true; - break; - end +% Also the neighborhood returned by neighbor_template_2d for bin 0. Plot the +% nbr_grididxes as text on the figure. +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info_1, proxy_info_1, 0); +nbr_gridpts = nbr_gridpts(:, nbr_grididxes <= grid_info_1.ngrid(1) * grid_info_1.ngrid(2) & nbr_grididxes > 0); +% text(nbr_gridpts(1, :), nbr_gridpts(2, :), arrayfun(@(idx) int2str(idx), nbr_grididxes(nbr_grididxes <= grid_info_1.ngrid(1) * grid_info_1.ngrid(2) & nbr_grididxes > 0), 'UniformOutput', false), 'FontSize', 8); + +for i = 1:length(nbr_binids) + center = bin_center(nbr_binids(i), grid_info_1); + % Plot the center of the neighboring bin as text on the figure, with the bin id as the text. + text(center(1), center(2), int2str(nbr_binids(i)), 'FontSize', 8); end -assert(found_interior, ... - ['No bin found whose valid neighbor grid point count matches ' ... - 'abstract_neighbor_spreading_2D. Template may be over- or under-sized.']); +% scatter(nbr_gridpts(1, :), nbr_gridpts(2, :), 'm.'); + + +% For every bin, count the number of in-bounds points that neighbor_template_2d +% returns. For interior bins this should equal size(tmpl_pts_1, 2). +% n_template = size(tmpl_pts_1, 2); +% ngridpts = grid_info_1.ngrid(1) * grid_info_1.ngrid(2); + +% n_bins = grid_info_1.nbin(1) * grid_info_1.nbin(2); +% found_interior = false; +% for bin_idx = 0:(n_bins - 1) +% [~, ~, nbr_grididxes, ~] = neighbor_template_2d(grid_info_1, proxy_info_1, bin_idx); +% n_valid = sum(nbr_grididxes <= ngridpts); +% disp("test_1: For bin_idx " + int2str(bin_idx) + ", nbr_grididxes: " + int2str(size(nbr_grididxes)) + ", n_valid: " + int2str(n_valid) + ", n_template: " + int2str(n_template)); +% if n_valid == n_template +% found_interior = true; +% break; +% end +% end + +% assert(found_interior, ... +% ['No bin found whose valid neighbor grid point count matches ' ... +% 'abstract_neighbor_spreading_2D. Template may be over- or under-sized.']); %% test_2: % Template covers the correct neighbor offsets. @@ -122,23 +178,25 @@ % some bin offset (delta_x, delta_y) satisfying the same circular criterion % used by intersecting_bins_2d. -dx2 = grid_info.dx; -nsp2 = grid_info.nspread; -nbp2 = grid_info.nbinpts; -rad2 = ceil(2 * proxy_info.radius / (nsp2 * dx2)); -half = (nsp2 - 1) / 2 * dx2; % half-width of a spreading box - -for i = 1:size(spreading_template_pts, 2) - pt = spreading_template_pts(:, i); - % Find which bin offset this point belongs to - delta_x = round(pt(1) / (nbp2 * dx2)); - delta_y = round(pt(2) / (nbp2 * dx2)); - % Check that (delta_x, delta_y) satisfies the intersection criterion - assert(delta_x^2 + delta_y^2 <= rad2^2 + 1e-10, ... - sprintf('Template point (%g,%g) belongs to offset (%d,%d) outside neighborhood', ... - pt(1), pt(2), delta_x, delta_y)); - % Check that the point lies within that bin's spreading box - center = [delta_x; delta_y] * nbp2 * dx2; - assert(max(abs(pt - center)) <= half + 1e-10, ... - sprintf('Template point (%g,%g) not within its bin offset box', pt(1), pt(2))); -end + + +% dx2 = grid_info.dx; +% nsp2 = grid_info.nspread; +% nbp2 = grid_info.nbinpts; +% rad2 = ceil(2 * proxy_info.radius / (nsp2 * dx2)); +% half = (nsp2 - 1) / 2 * dx2; % half-width of a spreading box + +% for i = 1:size(spreading_template_pts, 2) +% pt = spreading_template_pts(:, i); +% % Find which bin offset this point belongs to +% delta_x = round(pt(1) / (nbp2 * dx2)); +% delta_y = round(pt(2) / (nbp2 * dx2)); +% % Check that (delta_x, delta_y) satisfies the intersection criterion +% assert(delta_x^2 + delta_y^2 <= rad2^2 + 1e-10, ... +% sprintf('Template point (%g,%g) belongs to offset (%d,%d) outside neighborhood', ... +% pt(1), pt(2), delta_x, delta_y)); +% % Check that the point lies within that bin's spreading box +% center = [delta_x; delta_y] * nbp2 * dx2; +% assert(max(abs(pt - center)) <= half + 1e-10, ... +% sprintf('Template point (%g,%g) not within its bin offset box', pt(1), pt(2))); +% end diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index ad9823a..11aca65 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -22,6 +22,8 @@ [grid_info, proxy_info] = get_grid(@log_kernel, ... src_info_2d, targ_info_2d, tol); + + N_bin = grid_info.nbin(1) * grid_info.nbin(2); % disp("test_intersecting_bins_2d: N_bin = " + int2str(N_bin)); @@ -47,26 +49,44 @@ % r points live on [-1, 1] x [-1, 1] rng(0); r = (rand(2, n_pts) - 0.5) * L; +src_info_0b = struct('r', r); -% Get grid and proxy info +% Get grid and proxy info. The halfside is tuned so that the proxy shells of +% opposing corners do not intersect, see the figure generated. tol = 1e-6; [grid_info, proxy_info] = get_grid(@log_kernel, ... - struct('r', r), ... - struct('r', r), ... - tol, n_pts * 10); - - -% dx = 0.25, so the grid points are at -% dx = 0.25; -% ngrid = [9 9]; -% % When we set nbinpts = 3, we expect -% % x bins and y bins [-1, -0.25], [-0.25, 0.5], [0.5, 1.] -% nbinpts = 3; -% nbin = [3 3]; -% N_bin = nbin(1) * nbin(2); - -% % Generate the GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx -% grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0); + src_info_0b, ... + src_info_0b, ... + tol, 1000, struct('halfside', 0.41)); + +sort_info = SortInfo(src_info_0b, grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); +N_bins = grid_info.nbin(1) * grid_info.nbin(2); + +% Plot the sorted points and color by the bin +% to make sure the bin assignment looks correct +scatter(sort_info.r_srt(1,:), sort_info.r_srt(2,:), 20, sort_info.binid_srt, 'filled'); +colormap('parula'); +colorbar; + +% Draw an x at the center of each bin +hold on; + +bin_penultimate = (grid_info.nbin(1) - 1) * grid_info.nbin(2) - 2; + +% Draw proxy rings for the first and last bin. +for bin_idx = [0 bin_penultimate N_bins - 1] + center = bin_center(bin_idx, grid_info); + scatter(center(1), center(2), 100, 'x'); + + % Draw the proxy ring + proxypts = get_ring_points(100, proxy_info.radius, center); + plot(proxypts(1,:), proxypts(2,:), 'k-'); +end + + +% close all; + + disp("test_intersecting_bins_2d: grid_info:"); disp(grid_info); disp("test_intersecting_bins_2d: grid_info.nbin:"); @@ -74,18 +94,18 @@ % Test the third return value is correct. -[~, ~, bin_4_intersecting_binids] = ... - intersecting_bins_2d(4, grid_info, proxy_info); +[~, ~, bin_0_intersecting_binids] = ... + intersecting_bins_2d(0, grid_info, proxy_info); -% This should = [0 1 2 3 4 5 6 7 8 -expected_bin_4_intersecting_binids = [0 1 2 3 4 5 6 7 8]; +% This should = [0 1 2 3 4 5 6 7 8 ... Nbin-1] +expected_bin_0_intersecting_binids = 0:(N_bins - 1); % disp("test_intersecting_bins_2d: For bin_idx 4, intersecting binids: "); % disp(bin_4_intersecting_binids); -valid_bins = bin_4_intersecting_binids >= 0 & bin_4_intersecting_binids < N_bin; -valid_bins = bin_4_intersecting_binids(valid_bins); +valid_bins = bin_0_intersecting_binids >= 0 & bin_0_intersecting_binids < N_bins; +valid_bins = bin_0_intersecting_binids(valid_bins); % disp("test_intersecting_bins_2d: For bin_idx 4, valid intersecting binids: "); % disp(valid_bins); -% assert(all(valid_bins == expected_bin_4_intersecting_binids)); +assert(all(valid_bins == expected_bin_0_intersecting_binids)); [sort_info] = SortInfo(struct('r', r), grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); @@ -97,78 +117,36 @@ assert(all(size(r_srt) == size(r))); assert(all(size(r, 2) == size(binid_srt, 2))); +% Figure shows that bin idx 0 intersercts with all of the bins in the first col. +[bin_0_intersecting_x, bin_0_intersecting_y, ~] = intersecting_bins_2d(0, grid_info, proxy_info); -% Plot the sorted points and color by the bin -% to make sure the bin assignment looks correct -scatter(r_srt(1,:), r_srt(2,:), 20, binid_srt, 'filled'); -colormap('parula'); -colorbar; +% Expect bin_0_intersecting_x = [0, 1, ..., Nbin(1) - 1] +disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins x: "); +disp(bin_0_intersecting_x); +unique_bin_0_intersecting_x = unique(bin_0_intersecting_x(bin_0_intersecting_x>=0 & bin_0_intersecting_x < grid_info.nbin(1))); +expected_bin_0_intersecting_x = 0:(grid_info.nbin(1) - 1); +disp("test_intersecting_bins_2d: For bin_idx 0, unique intersecting bins x: "); +disp(unique_bin_0_intersecting_x); +disp("test_intersecting_bins_2d: For bin_idx 0, expected intersecting bins x: "); +disp(expected_bin_0_intersecting_x); +assert(all(unique_bin_0_intersecting_x == expected_bin_0_intersecting_x)); -% Draw an x at the center of each bin -hold on; -N_bins = grid_info.nbin(1) * grid_info.nbin(2); -for bin_idx = 0:(N_bins - 1) - center = bin_center(bin_idx, grid_info); - scatter(center(1), center(2), 100, 'x'); +% Same for y +unique_bin_0_intersecting_y = unique(bin_0_intersecting_y(bin_0_intersecting_y>=0 & bin_0_intersecting_y < grid_info.nbin(2))); +expected_bin_0_intersecting_y = 0:(grid_info.nbin(2) - 1); +assert(all(unique_bin_0_intersecting_y == expected_bin_0_intersecting_y)); - % Draw the proxy ring - proxypts = get_ring_points(100, proxy_info.radius, center); - plot(proxypts(1,:), proxypts(2,:), 'k-'); -end -% Figure shows that bin idx 0 only intersects with 0, 1, 3. -[bin_0_intersecting_x, bin_0_intersecting_y] = intersecting_bins_2d(0, grid_info, proxy_info); -% Expect bin_0_intersecting_x = [-1, 0, 1] -disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins x: "); -disp(bin_0_intersecting_x); -% expected_bin_0_intersecting_y = [-1, 0, 1]; -disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins y: "); -disp(bin_0_intersecting_y); -% In reality, we expect the unique sorted values of bin_0_intersecting_x -% to be [-1, 0, 1] -expected_bin_0_intersecting = [-1 0 1]; -unique_bin_0_intersecting_x = unique(bin_0_intersecting_x); -unique_bin_0_intersecting_y = unique(bin_0_intersecting_y); -assert(all(unique_bin_0_intersecting_x == expected_bin_0_intersecting)); -assert(all(unique_bin_0_intersecting_y == expected_bin_0_intersecting)); - -% Figure shows that bin idx 5 intersects with 2, 4, 5, 8. -% bin_idx 5 corresponds to (id_x, id_y) = (1, 2) -% So we expect intersecting bins to be -% id_x in [0, 1, 2] -% id_y in [1, 2, 3] -[bin_5_intersecting_x, bin_5_intersecting_y] = intersecting_bins_2d(5, grid_info, proxy_info); -disp("test_intersecting_bins_2d: For bin_idx 4, intersecting bins: "); -disp(bin_5_intersecting_x); -disp(bin_5_intersecting_y); -expected_bin_5_intersecting_x = [0 1 2]; -expected_bin_5_intersecting_y = [1 2 3]; - -assert(all(unique(bin_5_intersecting_x) == expected_bin_5_intersecting_x)); -assert(all(unique(bin_5_intersecting_y) == expected_bin_5_intersecting_y)); - -close all; %% test_0c % Test that the returned bins for each query bin satisfy the proxy-circle -% intersection condition - -rng(42); -n_src = 5000; -n_targ = 1500; -tol_0c = 1e-6; - -src_info_0c = struct; -src_info_0c.r = rand(2, n_src) - 0.5; -src_info_0c.weights = rand(n_src, 1); - -targ_info_0c = struct; -targ_info_0c.r = rand(2, n_targ) - 0.5; +% Use grid_info and proxy_info from test_0b -[grid_info_0c, proxy_info_0c] = get_grid(@log_kernel, src_info_0c, targ_info_0c, tol_0c); +grid_info_0c = grid_info; +proxy_info_0c = proxy_info; -N_bin_0c = grid_info_0c.nbin(1) * grid_info_0c.nbin(2); +N_bin_0c = grid_info.nbin(1) * grid_info.nbin(2); all_bins = 0:(N_bin_0c - 1); tol_dist = 1e-10; diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index e92e464..ea9f99b 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -26,9 +26,27 @@ % disp("test_intersecting_bins_2d: N_bin = " + int2str(N_bin)); -[nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, 8); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, 8); -assert( bin_idx == 8); + +% nbr_gridpts should be the same size as nbr_grididxes +assert(size(nbr_gridpts, 2) == size(nbr_grididxes, 2)); + +% nbr_grididxes should either be in the range [1, ngrid(1)*ngrid(2)] or be equal to dummy_idx = ngrid(1)*ngrid(2) + 1 +dummy_idx = grid_info.ngrid(1) * grid_info.ngrid(2) + 1; +assert(all(nbr_grididxes >= 1 & nbr_grididxes <= dummy_idx)); + +% valid nbr_grididxes should correctly index nbr_gridpts +keep_bool = nbr_grididxes ~= dummy_idx; +valid_grididxes = nbr_grididxes(keep_bool); +valid_gridpts = nbr_gridpts(:, keep_bool); +for i = 1:size(valid_grididxes, 2) + idx = valid_grididxes(i); + pt = valid_gridpts(:, i); + grid_pt = grid_info.r(:, idx); + dist = norm(pt - grid_pt); + assert(dist < 1e-12); +end % OJM: deleting this assert statement because it was written when we computed % the neighbor template as a rectangle, now we use a circle. @@ -112,10 +130,10 @@ BOX_IDX = 2; % Figure shows that bin idx 0 only intersects with 0, 1, 3. -[nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX); % Now center at the center of bin idx 0. -ctr = bin_center(bin_idx, grid_info); +ctr = bin_center(BOX_IDX, grid_info); disp("test_neighbor_template_2d: Centering template at bin idx 4, ctr: "); disp(ctr); temp_at_4 = nbr_gridpts; @@ -238,7 +256,7 @@ n_bins = grid_info.nbin(1) * grid_info.nbin(2); for bin_idx = 0:(n_bins - 1) - [nbr_binids, nbr_gridpts, nbr_grididxes, ~] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); % disp("test_neighbor_template_2d: Checking bin idx " + int2str(bin_idx)); % disp("nbr_grididxes: "); % disp(nbr_grididxes); diff --git a/devtools/test/test_neighbor_template_2d_1.m b/devtools/test/test_neighbor_template_2d_1.m index c28452e..62858a9 100644 --- a/devtools/test/test_neighbor_template_2d_1.m +++ b/devtools/test/test_neighbor_template_2d_1.m @@ -90,7 +90,7 @@ % indexing the rows of A_spread_s with the nbr_grididxes gets all of the relevant entries for a given bin_idx. for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2)-1 - [nbr_binids, nbr_gridpts, nbr_grididxes, ~] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); % Get the source points in the neighbor bins source_idx = []; @@ -138,8 +138,21 @@ % Loop through all of the boxes for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2)-1 - [nbr_binids, nbr_gridpts, nbr_grididxes, ~] = neighbor_template_2d(grid_info, proxy_info, bin_idx); - + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + + valid_idxes = nbr_grididxes <= grid_info.ngrid(1) * grid_info.ngrid(2); + valid_nbr_gridpts = nbr_gridpts(:, valid_idxes); + valid_nbr_grididxes = nbr_grididxes(valid_idxes); + + % Assert that the valid grid points match the expected grid points + for i = 1:size(valid_nbr_grididxes, 2) + idx = valid_nbr_grididxes(i); + pt = valid_nbr_gridpts(:, i); + grid_pt = grid_info.r(:, idx); + dist = norm(pt - grid_pt); + assert(dist < 1e-12); + end + % Assert that # binids returned = # grid pts returned = # grid idxes returned assert(length(nbr_grididxes) == size(nbr_gridpts, 2)); diff --git a/pcfft/classes/GridInfo.m b/pcfft/classes/GridInfo.m index bf9dda5..c576b89 100644 --- a/pcfft/classes/GridInfo.m +++ b/pcfft/classes/GridInfo.m @@ -38,6 +38,9 @@ % average number of near-field neighbours. % zero_bin : array [dim, nbinpts^dim] % Grid points of a spreading bin centered at the origin. + % center_bin : int + % Linear index of a bin approximately at the center of the grid. + % Computed as floor(nbin(1)/2) * nbin(2) + floor(nbin(2)/2). properties ngrid @@ -54,6 +57,7 @@ rmin n_nbr zero_bin + center_bin end methods function obj = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr) @@ -120,6 +124,7 @@ obj.rmin = rmin; obj.n_nbr = n_nbr; obj.zero_bin = zero_bin; + obj.center_bin = floor(n_bin(1)/2) * n_bin(2) + floor(n_bin(2)/2); end end end diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index 565e7c6..d61bd9c 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -65,6 +65,8 @@ % [nbr_binids, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); % [pts0, ctr_0, ~] = grid_pts_for_box_2d(nbr_bin_idx, grid_info); [pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + box_center = bin_center(grid_info.center_bin, grid_info); + reg_neighbor_template_pts = reg_neighbor_template_pts - box_center; else [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_3d(grid_info, proxy_info); [pts0, ctr_0, ~] = grid_pts_for_box_3d(nbr_bin_idx, grid_info); @@ -75,8 +77,8 @@ % pts0_centered = pts0 - ctr_0; nbr_info = struct('r', reg_neighbor_template_pts); - bin_info = struct('r', pts0); - K_nbr2bin = kern_0(nbr_info, bin_info); + box_info = struct('r', pts0); + K_nbr2bin = kern_0(nbr_info, box_info); r = 0; for i = 1:dim r = r + (reg_neighbor_template_pts(i,:) - pts0(i,:).').^2; @@ -141,7 +143,7 @@ % Build the spreading template if dim == 2 - [nbr_binids, ~, nbr_grididxes, ~] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, ~, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); nbr_binids(nbr_binids==-1) =[]; else [nbr_binids, nbr_grididxes] = neighbor_bins_3d(grid_info, proxy_info, bin_idx); @@ -206,9 +208,9 @@ A_spread_t_i = A_spread_t(reg_idxs_i, opdim(1)*(idx_ti_start-1)+1:opdim(1)*idx_ti_end); A_spread_s_j = A_spread_s(nbr_grididxes, source_idx_dof); % AKA_chunk = (A_spread_t_i.' * K_nbr2bin) * A_spread_s_j; - disp("get_addsub: size(A_spread_t_i): " + int2str(size(A_spread_t_i))); - disp("get_addsub: size(K_nbr2bin): " + int2str(size(K_nbr2bin))); - disp("get_addsub: size(A_spread_s_j): " + int2str(size(A_spread_s_j))); + % disp("get_addsub: size(A_spread_t_i): " + int2str(size(A_spread_t_i))); + % disp("get_addsub: size(K_nbr2bin): " + int2str(size(K_nbr2bin))); + % disp("get_addsub: size(A_spread_s_j): " + int2str(size(A_spread_s_j))); AKA_chunk = A_spread_t_i.' * (K_nbr2bin * A_spread_s_j); diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index fe3e3d3..2480a5b 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -1,10 +1,6 @@ -function [box_pts, spreading_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info) - % Constructs a fictitious spreading box centered at the origin and a - % spreading template around it, in abstract/relative coordinates. - % - % Unlike neighbor_template_2d, this function does not reference any - % specific bin index or global grid — the result represents the full - % interior neighborhood for any non-boundary bin. +function [box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info) + % Constructs a spreading box for grid_info.center_bin and a spreading + % template covering all neighboring bins, in center_bin-relative coordinates. % % Inputs % ------ @@ -15,39 +11,43 @@ % % Outputs % ------- - % box_pts : array [2, nspread^2] - % Coordinates of the spreading box centered at the origin. - % spreading_template_pts : array [2, n_template_pts] + % box_pts : array [2, nspread^2] + % Coordinates of the spreading box for grid_info.center_bin. + % spreading_template_pts : array [2, n_template_pts] % Coordinates of all unique spreading box points across every - % neighboring bin (including the central bin), in origin-relative - % coordinates. - - dx = grid_info.dx; - nspread = grid_info.nspread; - nbinpts = grid_info.nbinpts; + % neighboring bin (including center_bin), centered at the center of + % box_pts. + % spreading_template_idxes : array [2, n_template_pts] + % Grid indices of the spreading template points. Row 1 contains + % 0-indexed x-grid positions; row 2 contains 0-indexed y-grid positions. + % For a target bin, shift these indices by the bin offset to obtain the + % corresponding global grid positions. - % Spreading box centered at origin: nspread points per dimension. - box_1d = dx * (0:nspread-1) - (nspread-1)/2 * dx; - [X, Y] = meshgrid(box_1d, box_1d); - box_pts = [X(:).'; Y(:).']; % [2, nspread^2] + % Step 2a: spreading box for center_bin + [box_pts, box_center] = grid_pts_for_box_2d(grid_info.center_bin, grid_info); - % Neighborhood radius in bin-index units — same formula as - % intersecting_bins_2d (line 28). + % Neighborhood radius in bin-index units rad = ceil(2 * proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); for delta_x = -rad : rad - delta_y_max = floor(sqrt(rad^2 - delta_x^2)); + delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); for delta_y = -delta_y_max : delta_y_max - shift = [delta_x; delta_y] * nbinpts * dx; + shift = [delta_x; delta_y] * grid_info.nbinpts * grid_info.dx; all_pts = [all_pts, box_pts + shift]; end end - % Deduplicate while preserving insertion order. - disp("abstract_neighbor_spreading_2D: Number of points before deduplication: " + int2str(size(all_pts))); + % Deduplicate [~, uid] = unique(all_pts.', 'rows'); - spreading_template_pts = all_pts(:, uid); - disp("abstract_neighbor_spreading_2D: Number of points after deduplication: " + int2str(size(spreading_template_pts, 2))); + all_unique_pts = all_pts(:, uid); + + % Step 2b: center relative to box_center + spreading_template_pts = all_unique_pts - box_center; + + % Step 2c: 0-indexed grid positions for each template point + x_idxes = round((all_unique_pts(1,:) - (grid_info.rmin(1))) / grid_info.dx)+1; + y_idxes = round((all_unique_pts(2,:) - (grid_info.rmin(2))) / grid_info.dx)+1; + spreading_template_idxes = [x_idxes; y_idxes]; end diff --git a/pcfft/utils/neighbor_template_2d.m b/pcfft/utils/neighbor_template_2d.m index 94b1c6b..aa1ab3f 100644 --- a/pcfft/utils/neighbor_template_2d.m +++ b/pcfft/utils/neighbor_template_2d.m @@ -1,12 +1,9 @@ -function [nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_2d(grid_info, proxy_info, bin_idx) +function [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx) % neighbor_template_2d Neighbor bin template for 2D spreading. % % Computes the set of neighboring bins and their spreading grid points for a - % given source bin. When called without bin_idx, selects an interior reference - % bin whose full neighborhood lies within the grid. - % - % The result serves as a reusable template: for any interior bin, the neighbor - % structure is the same up to a fixed offset. + % given source bin by shifting the abstract spreading template built for + % grid_info.center_bin. % % Inputs % ------ @@ -14,113 +11,47 @@ % Regular grid and bin structure. % proxy_info : ProxyInfo (or equivalent) % Proxy geometry used to determine which bins intersect. - % bin_idx : int, optional - % Linear index of the source bin. If omitted, a suitable interior bin is - % chosen automatically. + % bin_idx : int + % Linear index of the source bin. % % Outputs % ------- % nbr_binids : array [1, n_nbr] % Linear bin indices of the neighboring bins. -1 entries mark invalid bins. % nbr_gridpts : array [2, n_nbr_pts] - % Coordinates of unique spreading grid points across all neighbor bins. + % Coordinates of spreading grid points across all neighbor bins. % Out-of-bounds points are retained with a dummy index. % nbr_grididxes : array [1, n_nbr_pts] % Linear grid indices for each column of nbr_gridpts. Out-of-bounds points % are assigned dummy_idx = ngrid(1)*ngrid(2) + 1. - % bin_idx : int - % The source bin index used. + % Get the abstract spreading template built for center_bin. + [~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); - if nargin == 2 - % First, find how many bins are intersecting. - [int_idx_x, int_idx_y] = intersecting_bins_2d(0, grid_info, proxy_info); - % Filter the invalid ones - int_idx_x = unique(int_idx_x(int_idx_x >= 0)); - int_idx_y = unique(int_idx_y(int_idx_y >= 0)); - % Now find a bin s.t. all of the intersecting bins have idx >= 0. - offset_x = length(int_idx_x) - 1; - offset_y = length(int_idx_y) - 1; - bin_idx = offset_x * grid_info.nbin(2) + offset_y; - end + % Get neighboring bin IDs (needed by callers such as get_addsub). + [~, ~, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); - [int_idx_x, int_idx_y, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); - + % Compute the grid-index shift from center_bin to bin_idx. + cx = floor(grid_info.center_bin / grid_info.nbin(2)); + cy = mod(grid_info.center_bin, grid_info.nbin(2)); + bx = floor(bin_idx / grid_info.nbin(2)); + by = mod(bin_idx, grid_info.nbin(2)); + delta_ix = (bx - cx) * grid_info.nbinpts; + delta_iy = (by - cy) * grid_info.nbinpts; - % disp("neighbor_template_2d: For bin_idx " + int2str(bin_idx) + ... - % ", ind_idx_x: "); - % disp(ind_idx_x); + % Shift template indices to the target bin. + shifted_idxes = tmpl_idxes + [delta_ix; delta_iy]; - % Info from grid_info that will be used later - rpad = grid_info.rpad; - nbinpts = grid_info.nbinpts; - Lbd = grid_info.Lbd; - dx = grid_info.dx; - offset = grid_info.offset; + % Determine in-bounds mask and compute linear grid indices. ngrid = grid_info.ngrid; - rmax = grid_info.rmax; - rmin = grid_info.rmin; - nspread = grid_info.nspread; - - % Preallocate arrays for nbr_gridpts and nbr_grididxes. - nbr_gridpts = zeros(2, nspread^2 * length(nbr_binids)) * NaN; - nbr_grididxes = zeros(1, nspread^2 * length(nbr_binids)) * NaN; - - for i = 1:length(nbr_binids) - % if nbr_binids(i) == -1 - % continue; - % end - [pts, ~, row_idxes] = grid_pts_for_box_2d(nbr_binids(i), grid_info); - start_idx = (i-1) * nspread^2 + 1; - end_idx = start_idx + nspread^2 - 1; - nbr_gridpts(:, start_idx:end_idx) = pts; - nbr_grididxes(start_idx:end_idx) = row_idxes; - end - - % Use unique and setdiff to find the unique grid idxes and the corresponding - % grid pts. - % TODO - [~, unique_idx] = unique(nbr_grididxes); - nbr_grididxes = nbr_grididxes(unique_idx); - nbr_gridpts = nbr_gridpts(:, unique_idx); - - % Remove all NaNs - nbr_grididxes = nbr_grididxes(~isnan(nbr_grididxes)); - nbr_gridpts = nbr_gridpts(:, ~isnan(nbr_grididxes)); - - - - % % Build the nbr_gridpts - % % npts = number of regular grid points across the side of the neighbor template - % nx = size(int_idx_x, 2); - % npts = nx * nbinpts + 2 * rpad; - % minxnbr = min(int_idx_x); % Minimum x bin index of the neighbor bins - % minynbr = min(int_idx_y); % Minimum y bin index of the neighbor bins - - % nbr_xpts = Lbd(1) - offset + minxnbr * nbinpts * dx + dx * (0:npts-1); - % nbr_ypts = Lbd(2) - offset + minynbr * nbinpts * dx + dx * (0:npts-1); - % [X, Y] = meshgrid(nbr_xpts, nbr_ypts); - % nbr_gridpts = [X(:).'; Y(:).']; - - % % % Build the nbr_grididxes. This logic is copied from grid_pts_for_box_2d - % % and npts is used instead of nspread. - % x_positions = minxnbr * nbinpts +1 : minxnbr * nbinpts + npts ; - % y_positions = minynbr * nbinpts +1 : minynbr * nbinpts + npts ; - - % % Mark out-of-bounds grid points with a dummy index - ngridpts = grid_info.ngrid(1) * grid_info.ngrid(2); - dummy_idx = ngridpts + 1; - - % nbr_grididxes = y_positions(:) + (x_positions(:)-1).' * ngrid(2); - - % % Mark the row_idxes corresponding to out-of-bounds grid points with a dummy - % % Might need a tiny bit of margin here - margin = 0.1 * dx; - - % Check the y points - nbr_grididxes(nbr_gridpts(2,:) < rmin(2) - margin | nbr_gridpts(2,:) > rmax(2) + margin,:) = dummy_idx; - % Check the x points - nbr_grididxes(:,nbr_gridpts(1,:) < rmin(1) - margin | nbr_gridpts(1,:) > rmax(1) + margin) = dummy_idx; - - % nbr_grididxes = nbr_grididxes(:).'; -end \ No newline at end of file + in_bounds = shifted_idxes(1,:) >= 1 & shifted_idxes(1,:) <= ngrid(1) & ... + shifted_idxes(2,:) >= 1 & shifted_idxes(2,:) <= ngrid(2); + dummy_idx = ngrid(1) * ngrid(2) + 1; + nbr_grididxes = (shifted_idxes(1,:) - 1) * ngrid(2) + shifted_idxes(2,:); + nbr_grididxes(~in_bounds) = dummy_idx; + + % Compute physical coordinates: tmpl_pts are offsets from center_bin's box + % center, so adding bin_idx's box center gives absolute coordinates. + [~, bin_center] = grid_pts_for_box_2d(bin_idx, grid_info); + nbr_gridpts = tmpl_pts + bin_center; +end From 9dedaeffd24ba9850d7d44e4a4094800cb8cbeee Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Tue, 7 Apr 2026 14:49:26 -0400 Subject: [PATCH 06/14] I think get_addsub now works in 2D --- .../get_addsub_log_2D_threepts.m | 26 +++- .../test_abstract_neighbor_spreading_2D.m | 8 +- devtools/test/test_get_addsub_1.m | 72 ++++++++++ devtools/test/test_get_addsub_2.m | 126 ++++++++++++++++++ devtools/test/test_neighbor_template_2d.m | 23 +++- pcfft/get_addsub.m | 19 +-- pcfft/utils/abstract_neighbor_spreading_2D.m | 10 +- pcfft/utils/neighbor_template_2d.m | 22 ++- 8 files changed, 274 insertions(+), 32 deletions(-) create mode 100644 devtools/test/test_get_addsub_1.m create mode 100644 devtools/test/test_get_addsub_2.m diff --git a/devtools/convergence_plots/get_addsub_log_2D_threepts.m b/devtools/convergence_plots/get_addsub_log_2D_threepts.m index c4ac831..ed28732 100644 --- a/devtools/convergence_plots/get_addsub_log_2D_threepts.m +++ b/devtools/convergence_plots/get_addsub_log_2D_threepts.m @@ -45,16 +45,14 @@ % In this test, there are 2 source points and 3 target points. % Here are the dense interactions: % s(1) -> t(1) : both in box 0 -% s(2) -> t(2) : both in box 5 -% s(1) -> t(3) : s(1) in box 0, t(3) in box 7. These are near. +% s(2) -> t(2) : both in box 119 +% s(1) -> t(3) : s(1) in box 0, t(3) in box 307. These are near. % So here are the interactions which are NOT dense: % s(1) -> t(2) % 0 and 5 are not near % s(2) -> t(1) % 5 and 0 are not near % s(2) -> t(3) % 5 and 7 are not near - - tol = 1e-08; [grid_info, proxy_info] = get_grid(k, src_info, targ_info, tol, n_nbr); @@ -90,6 +88,8 @@ % disp(term2) AKA = A_spread_t.' * K_grid2grid * A_spread_s; +disp("main: AKA: ") +disp( AKA) term3 = AKA * src_weights; disp("main: term3: ") @@ -114,6 +114,24 @@ % disp("main: AKA: ") % disp(full(AKA)) +% Plot the source points with blue dots +figure(1); +scatter(source_pts(1,:), source_pts(2,:), 100, 'b.'); +hold on; +% Plot the target points with red dots +scatter(target_pts(1,:), target_pts(2,:), 100, 'r.'); + +% Plot the index of each bin at its center +for i = 0:grid_info.nbin(1) * grid_info.nbin(2) - 1 + bin_ctr = bin_center(i, grid_info); + text(bin_ctr(1), bin_ctr(2), num2str(i), 'HorizontalAlignment', 'center'); +end + +% Draw a circle of radius 2 * proxy rad around center of box 0 +box0_ctr = bin_center(0, grid_info); +ring = get_ring_points(100, 2 * proxy_info.radius, box0_ctr); +plot(ring(1,:), ring(2,:), 'k--'); + assert(errors_at_target < tol); diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m index 263f05f..ef1191e 100644 --- a/devtools/test/test_abstract_neighbor_spreading_2D.m +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -16,7 +16,7 @@ proxy_info = struct; proxy_info.radius = 0.5; -[box_pts, spreading_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +[box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); % box_pts must be [2, nspread^2] assert(all(size(box_pts) == [2, nspread^2]), ... @@ -52,6 +52,12 @@ sprintf('Minimum distance between spreading_template_pts is %g, less than dx=%g', min_dist, dx)); + +% All indices must be unique +[~, uid] = unique(spreading_template_idxes.', 'rows'); +assert(length(uid) == size(spreading_template_idxes, 2), ... + 'spreading_template_idxes must have no duplicate indices'); + %% test_1: % Template size matches an interior bin from neighbor_template_2d. % diff --git a/devtools/test/test_get_addsub_1.m b/devtools/test/test_get_addsub_1.m new file mode 100644 index 0000000..61a567c --- /dev/null +++ b/devtools/test/test_get_addsub_1.m @@ -0,0 +1,72 @@ +% This tests the K_nbr2bin object constructed in get_addsub(). +addpath(genpath('../../pcfft')); + +k = @(s,t) log_kernel(s,t); +tol = 1e-8; +rng(0); +src_info.r = rand(2,50) - 0.5; +[grid_info, proxy_info] = get_grid(k, src_info, src_info, tol); + +[pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +box_center = bin_center(grid_info.center_bin, grid_info); +pts0_abs = pts0; % pts0 is already absolute (before subtraction in get_addsub) + +% K_nbr2bin as built in get_addsub (relative coords, center bin) +pts0_rel = pts0 - box_center; +K_nbr2bin = k(struct('r', reg_neighbor_template_pts), struct('r', pts0_rel)); +K_nbr2bin_r = 0; +for d = 1:2 + K_nbr2bin_r = K_nbr2bin_r + (reg_neighbor_template_pts(d,:) - pts0_rel(d,:).').^2; +end +K_nbr2bin(K_nbr2bin_r < 1e-14) = 0; + +% For each non-boundary bin, compute K directly from absolute grid coords +% and compare to K_nbr2bin +for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2) - 1 + [~, ~, reg_idxs_i] = grid_pts_for_box_2d(bin_idx, grid_info); + [~, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + + % Only consider in-bounds template points + dummy = grid_info.ngrid(1)*grid_info.ngrid(2) + 1; + valid = nbr_grididxes ~= dummy; + + % Absolute grid coordinates + box_abs = grid_info.r(:, reg_idxs_i); % [2 x nbox] + tmpl_abs = grid_info.r(:, nbr_grididxes(valid)); % [2 x nvalid] + + % Direct K (absolute coords) + K_direct = k(struct('r', tmpl_abs), struct('r', box_abs)); + r2 = 0; + for d = 1:2 + r2 = r2 + (tmpl_abs(d,:) - box_abs(d,:).').^2; + end + K_direct(r2 < 1e-14) = 0; + + % K_nbr2bin restricted to valid columns + K_approx = K_nbr2bin(:, valid); + + err = max(abs(K_direct(:) - K_approx(:))); + assert(err < 1e-10, sprintf('bin %d: K ordering mismatch, err=%g', bin_idx, err)); +end + +%% test_2 + +bin_idx = grid_info.center_bin; +[~, tmpl_pts_abs, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +box_ctr = bin_center(grid_info.center_bin, grid_info); +tmpl_pts_abs = tmpl_pts_abs + box_ctr; % shift from relative to absolute + +[~, ~, nbr_grididxes_c] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + +% For valid points, check that nbr_grididxes index the same physical points +% as tmpl_idxes +dummy = grid_info.ngrid(1)*grid_info.ngrid(2) + 1; +valid = nbr_grididxes_c ~= dummy; +in_bounds = tmpl_idxes(1,:) >= 1 & tmpl_idxes(1,:) <= grid_info.ngrid(1) & ... + tmpl_idxes(2,:) >= 1 & tmpl_idxes(2,:) <= grid_info.ngrid(2); + +linear_from_tmpl = (tmpl_idxes(1,in_bounds)-1)*grid_info.ngrid(2) + tmpl_idxes(2,in_bounds); +assert(isequal(sort(nbr_grididxes_c(valid)), sort(linear_from_tmpl)), ... + 'center_bin: nbr_grididxes set does not match tmpl_idxes set'); +assert(isequal(nbr_grididxes_c(valid), linear_from_tmpl), ... + 'center_bin: nbr_grididxes ORDER does not match tmpl_idxes order'); \ No newline at end of file diff --git a/devtools/test/test_get_addsub_2.m b/devtools/test/test_get_addsub_2.m new file mode 100644 index 0000000..40ace15 --- /dev/null +++ b/devtools/test/test_get_addsub_2.m @@ -0,0 +1,126 @@ +addpath(genpath("../../pcfft")); +close all; +clear; + + +rad = 2.0; + + +% Set up two source points +rng(4); +n_src = 400; +source_pts = rand(2,n_src) - 0.5; + +% target points are close but not exactly = source points +n_targ = 317; +target_pts = rand(2,n_targ) - 0.5; +disp(size(target_pts)); + + +src_weights = zeros(n_src,1); +src_weights(1) = 1.0; +src_weights = src_weights(:); + +% Define the kernel +k = @(s,t) log_kernel(s,t); + +K_src_to_target = log_kernel(struct('r',source_pts), struct('r',target_pts)); + +target_vals = K_src_to_target * src_weights; +n_nbr = 100; % 10000 points / 500 is approximately 20 boxes + +src_info = struct; +src_info.r = source_pts; +targ_info = struct; +targ_info.r = target_pts; + + +tol = 1e-08; + +[grid_info, proxy_info] = get_grid(k, src_info, targ_info, tol, n_nbr); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Plot the bins, sources, and targets. + +% Plot the source points with blue dots +figure(1); +scatter(source_pts(1,:), source_pts(2,:), 100, 'b.'); +hold on; +% Plot the target points with red dots +scatter(target_pts(1,:), target_pts(2,:), 100, 'r.'); + +% Plot the index of each bin at its center +for i = 0:grid_info.nbin(1) * grid_info.nbin(2) - 1 + bin_ctr = bin_center(i, grid_info); + text(bin_ctr(1), bin_ctr(2), num2str(i), 'HorizontalAlignment', 'center'); +end + +% Draw a circle of radius 2 * proxy rad around center of box 0 +box0_ctr = bin_center(0, grid_info); +ring = get_ring_points(100, 2 * proxy_info.radius, box0_ctr); +plot(ring(1,:), ring(2,:), 'k--'); +% Plot the reg gridpoints with black x's +scatter(grid_info.r(1,:), grid_info.r(2,:), 100, 'kx'); + + +[A_spread_s, sort_info_s] = get_spread(k, k, src_info, ... + grid_info, proxy_info); + +[A_spread_t, sort_info_t] = get_spread(k, k, targ_info, ... + grid_info, proxy_info); + +assert(all(~isnan(A_spread_s(:)))); +assert(all(~isinf(A_spread_s(:)))); +assert(all(~isnan(A_spread_t(:)))); +assert(all(~isinf(A_spread_t(:)))); + +A_addsub = get_addsub(k, k, src_info, targ_info, grid_info, ... + proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); + +% % A_addsub = A_add - A_sub; + +% K_grid2grid = log_kernel(grid_info, grid_info); +% % Put zeros on the diagonal of K_grid2grid +% for i = 1:size(K_grid2grid, 1) +% K_grid2grid(i, i) = 0; +% end + +% term1 = A_addsub * src_weights; +% disp("main: term1: ") +% disp(term1) + +% % term2 = A_sub * src_weights; +% % disp("main: term2: ") +% % disp(term2) + +% AKA = A_spread_t.' * K_grid2grid * A_spread_s; +% disp("main: AKA: ") +% disp( AKA) + +% term3 = AKA * src_weights; +% disp("main: term3: ") +% disp(term3) + +% evals_approx = term1 + term3; + +% disp("main: evals_approx: ") +% disp(evals_approx) +% disp("main: target_vals: ") +% disp(target_vals) + +% errors_at_target = max(abs(evals_approx - target_vals)) / max(abs(target_vals)); +% disp("errors_at_target: " + num2str(errors_at_target)); + + +% % Print out A_add, A_sub, and AKA for debugging +% disp("main: A_addsub: ") +% disp(full(A_addsub)) +% % disp("main: A_sub: ") +% % disp(full(A_sub)) +% % disp("main: AKA: ") +% % disp(full(AKA)) + + +% assert(errors_at_target < tol); + diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index ea9f99b..d0d8852 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -36,14 +36,33 @@ dummy_idx = grid_info.ngrid(1) * grid_info.ngrid(2) + 1; assert(all(nbr_grididxes >= 1 & nbr_grididxes <= dummy_idx)); +% Make a figure with the grid points labeled by their index. +scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); +hold on; +text(grid_info.r(1,:), grid_info.r(2,:), string(1:size(grid_info.r, 2)), 'Color', 'k'); +% Plot nbr_gridpts in red +scatter(nbr_gridpts(1,:), nbr_gridpts(2,:), 20, 'r', 'filled'); +title("Grid points with their linear indices"); + % valid nbr_grididxes should correctly index nbr_gridpts keep_bool = nbr_grididxes ~= dummy_idx; valid_grididxes = nbr_grididxes(keep_bool); valid_gridpts = nbr_gridpts(:, keep_bool); + + +% Plot the text of valid_grididxes over the valid_gridpts +text(valid_gridpts(1,:), valid_gridpts(2,:), string(valid_grididxes), 'Color', 'g'); + + for i = 1:size(valid_grididxes, 2) idx = valid_grididxes(i); pt = valid_gridpts(:, i); grid_pt = grid_info.r(:, idx); + disp("test_neighbor_template_2d: Checking pt " + int2str(i) + " at idx " + int2str(idx)); + disp("test_neighbor_template_2d: pt: "); + disp(pt); + disp("test_neighbor_template_2d: grid_pt: "); + disp(grid_pt); dist = norm(pt - grid_pt); assert(dist < 1e-12); end @@ -127,7 +146,7 @@ % plot(proxypts(1,:), proxypts(2,:), 'k-'); % end -BOX_IDX = 2; +BOX_IDX = 1; % Figure shows that bin idx 0 only intersects with 0, 1, 3. [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX); @@ -209,7 +228,7 @@ end -% close all; +close all; %% test_0c % Check that for a given A_spread_s matrix, indexing it with the diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index d61bd9c..b412a09 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -62,19 +62,14 @@ % Build a spreading template matrix for adjacent source points. % Then build a list of regular gridpoints that are in the intersecting bins if dim == 2 - % [nbr_binids, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_2d(grid_info, proxy_info); - % [pts0, ctr_0, ~] = grid_pts_for_box_2d(nbr_bin_idx, grid_info); [pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); box_center = bin_center(grid_info.center_bin, grid_info); - reg_neighbor_template_pts = reg_neighbor_template_pts - box_center; + pts0 = pts0 - box_center; else [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_3d(grid_info, proxy_info); [pts0, ctr_0, ~] = grid_pts_for_box_3d(nbr_bin_idx, grid_info); end - % disp("get_addsub: nbr_binids size: " + int2str(size(nbr_binids))); - % disp("get_addsub: nbr_binids: "); - % disp(nbr_binids); - % pts0_centered = pts0 - ctr_0; + nbr_info = struct('r', reg_neighbor_template_pts); box_info = struct('r', pts0); @@ -194,6 +189,7 @@ % part. K_src_to_targ = kern_st(src_pts_in_j, ... targ_info_in_i); + % Zero out the diagonal entries. r = 0; for k = 1:dim r = r + (src_pts_in_j.r(k,:) - targ_info_in_i.r(k,:).').^2; @@ -207,18 +203,9 @@ % "sub" part. A_spread_t_i = A_spread_t(reg_idxs_i, opdim(1)*(idx_ti_start-1)+1:opdim(1)*idx_ti_end); A_spread_s_j = A_spread_s(nbr_grididxes, source_idx_dof); - % AKA_chunk = (A_spread_t_i.' * K_nbr2bin) * A_spread_s_j; - % disp("get_addsub: size(A_spread_t_i): " + int2str(size(A_spread_t_i))); - % disp("get_addsub: size(K_nbr2bin): " + int2str(size(K_nbr2bin))); - % disp("get_addsub: size(A_spread_s_j): " + int2str(size(A_spread_s_j))); AKA_chunk = A_spread_t_i.' * (K_nbr2bin * A_spread_s_j); - % A_spread_t_i = full(A_spread_t(reg_idxs_i, opdim(1)*(idx_ti_start-1)+1:opdim(1)*idx_ti_end)); - % A_spread_s_j = full(A_spread_s(nbr_grididxes, source_idx_dof)); - % % AKA_chunk = (A_spread_t_i.' * K_nbr2bin) * A_spread_s_j; - % AKA_chunk = A_spread_t_i.' * (K_nbr2bin * A_spread_s_j); - Aloc = K_src_to_targ - AKA_chunk; % Update COO arrays. diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index 2480a5b..7fd9128 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -40,8 +40,9 @@ end % Deduplicate - [~, uid] = unique(all_pts.', 'rows'); - all_unique_pts = all_pts(:, uid); + % [~, uid] = unique(all_pts.', 'rows'); + % all_unique_pts = all_pts(:, uid); + all_unique_pts = all_pts; % Step 2b: center relative to box_center spreading_template_pts = all_unique_pts - box_center; @@ -50,4 +51,9 @@ x_idxes = round((all_unique_pts(1,:) - (grid_info.rmin(1))) / grid_info.dx)+1; y_idxes = round((all_unique_pts(2,:) - (grid_info.rmin(2))) / grid_info.dx)+1; spreading_template_idxes = [x_idxes; y_idxes]; + + % Step 2d: De-duplicate indices + [~, uid] = unique(spreading_template_idxes.', 'rows'); + spreading_template_pts = spreading_template_pts(:, uid); + spreading_template_idxes = spreading_template_idxes(:, uid); end diff --git a/pcfft/utils/neighbor_template_2d.m b/pcfft/utils/neighbor_template_2d.m index aa1ab3f..ea13228 100644 --- a/pcfft/utils/neighbor_template_2d.m +++ b/pcfft/utils/neighbor_template_2d.m @@ -26,19 +26,28 @@ % are assigned dummy_idx = ngrid(1)*ngrid(2) + 1. % Get the abstract spreading template built for center_bin. + % These are centered at the center of center_bin. [~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); - % Get neighboring bin IDs (needed by callers such as get_addsub). [~, ~, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); % Compute the grid-index shift from center_bin to bin_idx. - cx = floor(grid_info.center_bin / grid_info.nbin(2)); + % cx, xy are the 2D bin coordinates of center_bin cy = mod(grid_info.center_bin, grid_info.nbin(2)); - bx = floor(bin_idx / grid_info.nbin(2)); + cx = floor((grid_info.center_bin - cy) / grid_info.nbin(2)); + + % bx, by are the 2D bin coordinates of bin_idx by = mod(bin_idx, grid_info.nbin(2)); + bx = floor((bin_idx - by )/ grid_info.nbin(2)); + delta_ix = (bx - cx) * grid_info.nbinpts; delta_iy = (by - cy) * grid_info.nbinpts; + % disp("neighbor_template_2d: bin_idx: " + int2str(bin_idx) + ... + % ", center_bin: " + int2str(grid_info.center_bin) + ... + % ", delta_ix: " + int2str(delta_ix) + ... + % ", delta_iy: " + int2str(delta_iy)); + % Shift template indices to the target bin. shifted_idxes = tmpl_idxes + [delta_ix; delta_iy]; @@ -50,8 +59,7 @@ nbr_grididxes = (shifted_idxes(1,:) - 1) * ngrid(2) + shifted_idxes(2,:); nbr_grididxes(~in_bounds) = dummy_idx; - % Compute physical coordinates: tmpl_pts are offsets from center_bin's box - % center, so adding bin_idx's box center gives absolute coordinates. - [~, bin_center] = grid_pts_for_box_2d(bin_idx, grid_info); - nbr_gridpts = tmpl_pts + bin_center; + % Compute physical coordinates + ctr = bin_center(bin_idx, grid_info); + nbr_gridpts = tmpl_pts + ctr; end From d5daa504088cb3a3567a053ea38cdf9c1dcbf457 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Wed, 8 Apr 2026 09:25:16 -0400 Subject: [PATCH 07/14] Now code is fast but I'm unsure about neighbor radius --- devtools/accuracy_tests/test_log_2D.m | 5 + devtools/accuracy_tests/test_log_3D.m | 3 + devtools/accuracy_tests/test_one_over_r_3D.m | 3 + .../test_abstract_neighbor_spreading_2D.m | 7 +- devtools/test/test_get_addsub_1.m | 10 +- devtools/test/test_intersecting_bins_2d.m | 190 +++++++++--------- devtools/test/test_neighbor_template_2d.m | 10 +- devtools/test/test_neighbor_template_2d_1.m | 8 +- pcfft/get_addsub.m | 4 +- pcfft/utils/abstract_neighbor_spreading_2D.m | 2 +- pcfft/utils/intersecting_bins_2d.m | 4 +- pcfft/utils/neighbor_template_2d.m | 6 +- pcfft/utils/old_intersecting_bins_2d.m | 44 ++++ 13 files changed, 180 insertions(+), 116 deletions(-) create mode 100644 pcfft/utils/old_intersecting_bins_2d.m diff --git a/devtools/accuracy_tests/test_log_2D.m b/devtools/accuracy_tests/test_log_2D.m index 137ce7d..5748f10 100644 --- a/devtools/accuracy_tests/test_log_2D.m +++ b/devtools/accuracy_tests/test_log_2D.m @@ -2,6 +2,7 @@ close all; clear; + % Set up random source and target points rng(1); n_src = 5000; @@ -31,9 +32,13 @@ [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); disp("test_log_2D: A_spread_t size: " + int2str(size(A_spread_t))); + +tic; A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); +t_addsub = toc disp("test_log_2D: A_addsub size: " + int2str(size(A_addsub))); + k0hat = get_kernhat(kern_0,grid_info); evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); diff --git a/devtools/accuracy_tests/test_log_3D.m b/devtools/accuracy_tests/test_log_3D.m index cd9db9f..232beb3 100644 --- a/devtools/accuracy_tests/test_log_3D.m +++ b/devtools/accuracy_tests/test_log_3D.m @@ -2,6 +2,9 @@ close all; clear; +% Raise an error. This test is not implemented yet. +error("Test not implemented"); + % Set up random source and target points rng(1); n_src = 500; diff --git a/devtools/accuracy_tests/test_one_over_r_3D.m b/devtools/accuracy_tests/test_one_over_r_3D.m index 2e6e7ba..52cb72e 100644 --- a/devtools/accuracy_tests/test_one_over_r_3D.m +++ b/devtools/accuracy_tests/test_one_over_r_3D.m @@ -2,6 +2,9 @@ close all; clear; +% Raise an error. This test is not implemented yet. +error("Test not implemented"); + % Set up random source and target points rng(1); n_src = 500; diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m index ef1191e..f373fe8 100644 --- a/devtools/test/test_abstract_neighbor_spreading_2D.m +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -77,11 +77,12 @@ [grid_info_1, proxy_info_1] = get_grid(@log_kernel, src_info, src_info, tol); -[box_pts_1, tmpl_pts_1, tmpl_idxes_1] = abstract_neighbor_spreading_2D(grid_info_1, proxy_info_1); +[box_pts_1, tmpl_pts_1_original, tmpl_idxes_1_original] = abstract_neighbor_spreading_2D(grid_info_1, proxy_info_1); box_ctr = bin_center(grid_info_1.center_bin, grid_info_1); disp("test_1: box center: "); disp(box_ctr); -tmpl_pts_1 = tmpl_pts_1 + box_ctr; % Shift template points to be centered at the box center +tmpl_pts_1 = tmpl_pts_1_original + box_ctr; % Shift template points to be centered at the box center +tmpl_idxes_1 = tmpl_idxes_1_original; % check the number of interior bins disp("test_1: grid_info_1.nbin: "); @@ -143,7 +144,7 @@ % Also the neighborhood returned by neighbor_template_2d for bin 0. Plot the % nbr_grididxes as text on the figure. -[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info_1, proxy_info_1, 0); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info_1, proxy_info_1, 0, tmpl_pts_1_original, tmpl_idxes_1_original); nbr_gridpts = nbr_gridpts(:, nbr_grididxes <= grid_info_1.ngrid(1) * grid_info_1.ngrid(2) & nbr_grididxes > 0); % text(nbr_gridpts(1, :), nbr_gridpts(2, :), arrayfun(@(idx) int2str(idx), nbr_grididxes(nbr_grididxes <= grid_info_1.ngrid(1) * grid_info_1.ngrid(2) & nbr_grididxes > 0), 'UniformOutput', false), 'FontSize', 8); diff --git a/devtools/test/test_get_addsub_1.m b/devtools/test/test_get_addsub_1.m index 61a567c..e785c2d 100644 --- a/devtools/test/test_get_addsub_1.m +++ b/devtools/test/test_get_addsub_1.m @@ -7,7 +7,7 @@ src_info.r = rand(2,50) - 0.5; [grid_info, proxy_info] = get_grid(k, src_info, src_info, tol); -[pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +[pts0, reg_neighbor_template_pts, reg_neighbor_template_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); box_center = bin_center(grid_info.center_bin, grid_info); pts0_abs = pts0; % pts0 is already absolute (before subtraction in get_addsub) @@ -24,7 +24,7 @@ % and compare to K_nbr2bin for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2) - 1 [~, ~, reg_idxs_i] = grid_pts_for_box_2d(bin_idx, grid_info); - [~, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [~, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, reg_neighbor_template_pts, reg_neighbor_template_idxes); % Only consider in-bounds template points dummy = grid_info.ngrid(1)*grid_info.ngrid(2) + 1; @@ -52,11 +52,11 @@ %% test_2 bin_idx = grid_info.center_bin; -[~, tmpl_pts_abs, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +[~, tmpl_pts_abs_1, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); box_ctr = bin_center(grid_info.center_bin, grid_info); -tmpl_pts_abs = tmpl_pts_abs + box_ctr; % shift from relative to absolute +tmpl_pts_abs = tmpl_pts_abs_1 + box_ctr; % shift from relative to absolute -[~, ~, nbr_grididxes_c] = neighbor_template_2d(grid_info, proxy_info, bin_idx); +[~, ~, nbr_grididxes_c] = neighbor_template_2d(grid_info, proxy_info, bin_idx, tmpl_pts_abs_1, tmpl_idxes); % For valid points, check that nbr_grididxes index the same physical points % as tmpl_idxes diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index 11aca65..9c679eb 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -40,101 +40,101 @@ %% test_0b % Larger test case with visualization. Same setup as test_SortInfo_2d_1 - - -n_pts = 10000; -L = 2.0; -Lbd = [-1 1; - -1 1]; -% r points live on [-1, 1] x [-1, 1] -rng(0); -r = (rand(2, n_pts) - 0.5) * L; -src_info_0b = struct('r', r); - -% Get grid and proxy info. The halfside is tuned so that the proxy shells of -% opposing corners do not intersect, see the figure generated. -tol = 1e-6; -[grid_info, proxy_info] = get_grid(@log_kernel, ... - src_info_0b, ... - src_info_0b, ... - tol, 1000, struct('halfside', 0.41)); - -sort_info = SortInfo(src_info_0b, grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); -N_bins = grid_info.nbin(1) * grid_info.nbin(2); - -% Plot the sorted points and color by the bin -% to make sure the bin assignment looks correct -scatter(sort_info.r_srt(1,:), sort_info.r_srt(2,:), 20, sort_info.binid_srt, 'filled'); -colormap('parula'); -colorbar; - -% Draw an x at the center of each bin -hold on; - -bin_penultimate = (grid_info.nbin(1) - 1) * grid_info.nbin(2) - 2; - -% Draw proxy rings for the first and last bin. -for bin_idx = [0 bin_penultimate N_bins - 1] - center = bin_center(bin_idx, grid_info); - scatter(center(1), center(2), 100, 'x'); - - % Draw the proxy ring - proxypts = get_ring_points(100, proxy_info.radius, center); - plot(proxypts(1,:), proxypts(2,:), 'k-'); -end - - -% close all; - - -disp("test_intersecting_bins_2d: grid_info:"); -disp(grid_info); -disp("test_intersecting_bins_2d: grid_info.nbin:"); -disp(grid_info.nbin); - -% Test the third return value is correct. - -[~, ~, bin_0_intersecting_binids] = ... - intersecting_bins_2d(0, grid_info, proxy_info); - -% This should = [0 1 2 3 4 5 6 7 8 ... Nbin-1] -expected_bin_0_intersecting_binids = 0:(N_bins - 1); -% disp("test_intersecting_bins_2d: For bin_idx 4, intersecting binids: "); -% disp(bin_4_intersecting_binids); -valid_bins = bin_0_intersecting_binids >= 0 & bin_0_intersecting_binids < N_bins; -valid_bins = bin_0_intersecting_binids(valid_bins); -% disp("test_intersecting_bins_2d: For bin_idx 4, valid intersecting binids: "); -% disp(valid_bins); -assert(all(valid_bins == expected_bin_0_intersecting_binids)); - - -[sort_info] = SortInfo(struct('r', r), grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); -r_srt = sort_info.r_srt; -binid_srt = sort_info.binid_srt; -ptid_srt = sort_info.ptid_srt; -id_start = sort_info.id_start; -c = 1:n_pts; -assert(all(size(r_srt) == size(r))); -assert(all(size(r, 2) == size(binid_srt, 2))); - -% Figure shows that bin idx 0 intersercts with all of the bins in the first col. -[bin_0_intersecting_x, bin_0_intersecting_y, ~] = intersecting_bins_2d(0, grid_info, proxy_info); - -% Expect bin_0_intersecting_x = [0, 1, ..., Nbin(1) - 1] -disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins x: "); -disp(bin_0_intersecting_x); -unique_bin_0_intersecting_x = unique(bin_0_intersecting_x(bin_0_intersecting_x>=0 & bin_0_intersecting_x < grid_info.nbin(1))); -expected_bin_0_intersecting_x = 0:(grid_info.nbin(1) - 1); -disp("test_intersecting_bins_2d: For bin_idx 0, unique intersecting bins x: "); -disp(unique_bin_0_intersecting_x); -disp("test_intersecting_bins_2d: For bin_idx 0, expected intersecting bins x: "); -disp(expected_bin_0_intersecting_x); -assert(all(unique_bin_0_intersecting_x == expected_bin_0_intersecting_x)); - -% Same for y -unique_bin_0_intersecting_y = unique(bin_0_intersecting_y(bin_0_intersecting_y>=0 & bin_0_intersecting_y < grid_info.nbin(2))); -expected_bin_0_intersecting_y = 0:(grid_info.nbin(2) - 1); -assert(all(unique_bin_0_intersecting_y == expected_bin_0_intersecting_y)); +% TODO: Re-write this test once the nearness radius is figured out. + +% n_pts = 10000; +% L = 2.0; +% Lbd = [-1 1; +% -1 1]; +% % r points live on [-1, 1] x [-1, 1] +% rng(0); +% r = (rand(2, n_pts) - 0.5) * L; +% src_info_0b = struct('r', r); + +% % Get grid and proxy info. The halfside is tuned so that the proxy shells of +% % opposing corners do not intersect, see the figure generated. +% tol = 1e-6; +% [grid_info, proxy_info] = get_grid(@log_kernel, ... +% src_info_0b, ... +% src_info_0b, ... +% tol, 1000, struct('halfside', 0.41)); + +% sort_info = SortInfo(src_info_0b, grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); +% N_bins = grid_info.nbin(1) * grid_info.nbin(2); + +% % Plot the sorted points and color by the bin +% % to make sure the bin assignment looks correct +% scatter(sort_info.r_srt(1,:), sort_info.r_srt(2,:), 20, sort_info.binid_srt, 'filled'); +% colormap('parula'); +% colorbar; + +% % Draw an x at the center of each bin +% hold on; + +% bin_penultimate = (grid_info.nbin(1) - 1) * grid_info.nbin(2) - 2; + +% % Draw proxy rings for the first and last bin. +% for bin_idx = [0 bin_penultimate N_bins - 1] +% center = bin_center(bin_idx, grid_info); +% scatter(center(1), center(2), 100, 'x'); + +% % Draw the proxy ring +% proxypts = get_ring_points(100, proxy_info.radius, center); +% plot(proxypts(1,:), proxypts(2,:), 'k-'); +% end + + +% % close all; + + +% disp("test_intersecting_bins_2d: grid_info:"); +% disp(grid_info); +% disp("test_intersecting_bins_2d: grid_info.nbin:"); +% disp(grid_info.nbin); + +% % Test the third return value is correct. + +% [~, ~, bin_0_intersecting_binids] = ... +% intersecting_bins_2d(0, grid_info, proxy_info); + +% % This should = [0 1 2 3 4 5 6 7 8 ... Nbin-1] +% expected_bin_0_intersecting_binids = 0:(N_bins - 1); +% % disp("test_intersecting_bins_2d: For bin_idx 4, intersecting binids: "); +% % disp(bin_4_intersecting_binids); +% valid_bins = bin_0_intersecting_binids >= 0 & bin_0_intersecting_binids < N_bins; +% valid_bins = bin_0_intersecting_binids(valid_bins); +% % disp("test_intersecting_bins_2d: For bin_idx 4, valid intersecting binids: "); +% % disp(valid_bins); +% assert(all(valid_bins == expected_bin_0_intersecting_binids)); + + +% [sort_info] = SortInfo(struct('r', r), grid_info.dx, grid_info.Lbd, grid_info.nbin, grid_info.nbinpts); +% r_srt = sort_info.r_srt; +% binid_srt = sort_info.binid_srt; +% ptid_srt = sort_info.ptid_srt; +% id_start = sort_info.id_start; +% c = 1:n_pts; +% assert(all(size(r_srt) == size(r))); +% assert(all(size(r, 2) == size(binid_srt, 2))); + +% % Figure shows that bin idx 0 intersercts with all of the bins in the first col. +% [bin_0_intersecting_x, bin_0_intersecting_y, ~] = intersecting_bins_2d(0, grid_info, proxy_info); + +% % Expect bin_0_intersecting_x = [0, 1, ..., Nbin(1) - 1] +% disp("test_intersecting_bins_2d: For bin_idx 0, intersecting bins x: "); +% disp(bin_0_intersecting_x); +% unique_bin_0_intersecting_x = unique(bin_0_intersecting_x(bin_0_intersecting_x>=0 & bin_0_intersecting_x < grid_info.nbin(1))); +% expected_bin_0_intersecting_x = 0:(grid_info.nbin(1) - 1); +% disp("test_intersecting_bins_2d: For bin_idx 0, unique intersecting bins x: "); +% disp(unique_bin_0_intersecting_x); +% disp("test_intersecting_bins_2d: For bin_idx 0, expected intersecting bins x: "); +% disp(expected_bin_0_intersecting_x); +% assert(all(unique_bin_0_intersecting_x == expected_bin_0_intersecting_x)); + +% % Same for y +% unique_bin_0_intersecting_y = unique(bin_0_intersecting_y(bin_0_intersecting_y>=0 & bin_0_intersecting_y < grid_info.nbin(2))); +% expected_bin_0_intersecting_y = 0:(grid_info.nbin(2) - 1); +% assert(all(unique_bin_0_intersecting_y == expected_bin_0_intersecting_y)); diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index d0d8852..fd1ee14 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -26,7 +26,8 @@ % disp("test_intersecting_bins_2d: N_bin = " + int2str(N_bin)); -[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, 8); +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, 8, tmpl_pts, tmpl_idxes); % nbr_gridpts should be the same size as nbr_grididxes @@ -149,7 +150,8 @@ BOX_IDX = 1; % Figure shows that bin idx 0 only intersects with 0, 1, 3. -[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX); +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, BOX_IDX, tmpl_pts, tmpl_idxes); % Now center at the center of bin idx 0. ctr = bin_center(BOX_IDX, grid_info); @@ -274,8 +276,10 @@ n_bins = grid_info.nbin(1) * grid_info.nbin(2); +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + for bin_idx = 0:(n_bins - 1) - [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, tmpl_pts, tmpl_idxes); % disp("test_neighbor_template_2d: Checking bin idx " + int2str(bin_idx)); % disp("nbr_grididxes: "); % disp(nbr_grididxes); diff --git a/devtools/test/test_neighbor_template_2d_1.m b/devtools/test/test_neighbor_template_2d_1.m index 62858a9..1891164 100644 --- a/devtools/test/test_neighbor_template_2d_1.m +++ b/devtools/test/test_neighbor_template_2d_1.m @@ -89,9 +89,10 @@ % For each bin, get the neighbor bin idxes and assert that % indexing the rows of A_spread_s with the nbr_grididxes gets all of the relevant entries for a given bin_idx. +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2)-1 - [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); - + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, tmpl_pts, tmpl_idxes); + % Get the source points in the neighbor bins source_idx = []; for j = 1:length(nbr_binids) @@ -137,8 +138,9 @@ [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr); % Loop through all of the boxes +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); for bin_idx = 0 : grid_info.nbin(1)*grid_info.nbin(2)-1 - [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, tmpl_pts, tmpl_idxes); valid_idxes = nbr_grididxes <= grid_info.ngrid(1) * grid_info.ngrid(2); valid_nbr_gridpts = nbr_gridpts(:, valid_idxes); diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index b412a09..4c3e90b 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -62,7 +62,7 @@ % Build a spreading template matrix for adjacent source points. % Then build a list of regular gridpoints that are in the intersecting bins if dim == 2 - [pts0, reg_neighbor_template_pts] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + [pts0, reg_neighbor_template_pts, template_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); box_center = bin_center(grid_info.center_bin, grid_info); pts0 = pts0 - box_center; else @@ -138,7 +138,7 @@ % Build the spreading template if dim == 2 - [nbr_binids, ~, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx); + [nbr_binids, ~, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, reg_neighbor_template_pts, template_idxes); nbr_binids(nbr_binids==-1) =[]; else [nbr_binids, nbr_grididxes] = neighbor_bins_3d(grid_info, proxy_info, bin_idx); diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index 7fd9128..7f94fd2 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -27,7 +27,7 @@ [box_pts, box_center] = grid_pts_for_box_2d(grid_info.center_bin, grid_info); % Neighborhood radius in bin-index units - rad = ceil(2 * proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); + rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index 9708203..495e610 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -16,9 +16,9 @@ % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); % Radius in index space - rad = ceil(2 * proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); + rad = ceil(2 *proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % Which bins are within 2 * radius / (nspread * dx) ? + % Which bins are within 2 * radius / (bin_width) ? id_x_min = id_x - rad; id_x_max = id_x + rad; diff --git a/pcfft/utils/neighbor_template_2d.m b/pcfft/utils/neighbor_template_2d.m index ea13228..5d93355 100644 --- a/pcfft/utils/neighbor_template_2d.m +++ b/pcfft/utils/neighbor_template_2d.m @@ -1,4 +1,4 @@ -function [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx) +function [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, template_pts, template_idxes) % neighbor_template_2d Neighbor bin template for 2D spreading. % % Computes the set of neighboring bins and their spreading grid points for a @@ -27,7 +27,9 @@ % Get the abstract spreading template built for center_bin. % These are centered at the center of center_bin. - [~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + % [~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); + tmpl_pts = template_pts; + tmpl_idxes = template_idxes; [~, ~, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); diff --git a/pcfft/utils/old_intersecting_bins_2d.m b/pcfft/utils/old_intersecting_bins_2d.m new file mode 100644 index 0000000..cad1fae --- /dev/null +++ b/pcfft/utils/old_intersecting_bins_2d.m @@ -0,0 +1,44 @@ +function [id_xs, id_ys, binids] = old_intersecting_bins_2d(bin_idx, grid_info, ... + proxy_info) + % Given a set of bins which are described by grid_info, and a set of proxy + % surfaces which are described by proxy_info, return id_xs and id_ys. The + % product of these two sets of bins is the set of intersecting bin idxes. + % + % This function may return invalid bin idxes in the first two return values, + % i.e. < 0 or >= grid_info.nbin(d). In the third return value, these invalid + % bin idxes are set to -1. + % We say that two bins intersect if their proxy surfaces intersect at all. + + N_y_bins = grid_info.nbin(2); + id_y = mod(bin_idx, N_y_bins); + id_x = floor((bin_idx - id_y)/N_y_bins); + % disp("intersecting_bins_2d: For bin_idx " + int2str(bin_idx) + ... + % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); + + % Which bins are within 2 * radius / (nspread * dx) ? + id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + + id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + + id_xs = id_x_min:id_x_max; + id_ys = id_y_min:id_y_max; + + % Compute the binids + binids = zeros(length(id_xs) * length(id_ys), 1 ); + for i = 1:length(id_xs) + for j = 1:length(id_ys) + % If it's an invalid binid, set it to -1 + if id_xs(i) < 0 || id_xs(i) >= grid_info.nbin(1) || ... + id_ys(j) < 0 || id_ys(j) >= grid_info.nbin(2) + + binids((i-1)*length(id_ys) + j) = -1; + else + binids((i-1)*length(id_ys) + j) = ... + id_xs(i) * N_y_bins + id_ys(j); + end + end + end + binids = binids.'; +end \ No newline at end of file From 8b6ffd6c0b81dbfb2a2ed1be73ea9b360b7ef43e Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Wed, 8 Apr 2026 17:14:52 -0400 Subject: [PATCH 08/14] Updates to convergence and accuracy checks for 2D problems --- devtools/accuracy_tests/test_log_2D.m | 1 + devtools/accuracy_tests/test_one_over_r_2D.m | 1 + devtools/accuracy_tests/test_r4log_2D.m | 3 + .../convergence_plots/end_to_end_log_2D.m | 81 ++++++++++++++++++ .../end_to_end_one_over_r_2D.m | 82 +++++++++++++++++++ pcfft/utils/abstract_neighbor_spreading_2D.m | 2 +- pcfft/utils/intersecting_bins_2d.m | 2 +- 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 devtools/convergence_plots/end_to_end_log_2D.m create mode 100644 devtools/convergence_plots/end_to_end_one_over_r_2D.m diff --git a/devtools/accuracy_tests/test_log_2D.m b/devtools/accuracy_tests/test_log_2D.m index 5748f10..866db1c 100644 --- a/devtools/accuracy_tests/test_log_2D.m +++ b/devtools/accuracy_tests/test_log_2D.m @@ -46,4 +46,5 @@ diffs = abs(evals_approx - target_vals); rel_linf_error = max(diffs) / max(abs(target_vals)); +disp("test_log_2D: tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); assert(rel_linf_error < tol); \ No newline at end of file diff --git a/devtools/accuracy_tests/test_one_over_r_2D.m b/devtools/accuracy_tests/test_one_over_r_2D.m index d658b85..95394a5 100644 --- a/devtools/accuracy_tests/test_one_over_r_2D.m +++ b/devtools/accuracy_tests/test_one_over_r_2D.m @@ -38,5 +38,6 @@ % Compute relative L infinity error diffs = abs(evals_approx - target_vals); rel_linf_error = max(diffs) / max(abs(target_vals)); +disp("test_one_over_r_2D: tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); assert(rel_linf_error < tol); \ No newline at end of file diff --git a/devtools/accuracy_tests/test_r4log_2D.m b/devtools/accuracy_tests/test_r4log_2D.m index d3b17be..e0dc8be 100644 --- a/devtools/accuracy_tests/test_r4log_2D.m +++ b/devtools/accuracy_tests/test_r4log_2D.m @@ -44,6 +44,9 @@ diffs = abs(evals_approx - target_vals); rel_linf_error = max(diffs) / max(abs(target_vals)); +disp("test_r4log_2D: tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); + + assert(rel_linf_error < tol); function k_evals = r4log_kernel(src_pts,target_pts) diff --git a/devtools/convergence_plots/end_to_end_log_2D.m b/devtools/convergence_plots/end_to_end_log_2D.m new file mode 100644 index 0000000..26042e1 --- /dev/null +++ b/devtools/convergence_plots/end_to_end_log_2D.m @@ -0,0 +1,81 @@ +addpath(genpath("../../pcfft")); +close all; +clear; + + +% Set up random source and target points +rng(1); +n_src = 500; +n_targ = 517; +dim = 2; + +kern_0 = @(s,t) log_kernel(s,t); +src_info = struct; +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] +src_info.r = (rand(dim, n_src) - 0.5); +targ_info = struct; +targ_info.r = (rand(dim, n_targ) - 0.5); + +% Source weights are random uniform in [0, 1] +mu = rand(n_src, 1); +K_exact = kern_0(src_info, targ_info); +target_vals = K_exact * mu; + +n_nbr = 10; + +% Loop through different tolerances and record errors and timings +tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08 1e-09 1e-10]; +n_tol_vals = size(tol_vals, 2); +error_vals = zeros(n_tol_vals, 1); +dx_vals = zeros(n_tol_vals, 1); +nbinpts_vals = zeros(n_tol_vals, 1); +nproxy_vals = zeros(n_tol_vals, 1); + +for i = 1:n_tol_vals + tol = tol_vals(i); + [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr); + % disp(grid_info); + [A_spread_s, sort_info_s ]= get_spread(kern_0, [], src_info, ... + grid_info, proxy_info); + [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... + grid_info, proxy_info); + + A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); + + k0hat = get_kernhat(kern_0,grid_info); + evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); + + % Compute relative L infinity error + diffs = abs(evals_approx - target_vals); + rel_linf_error = max(diffs) / max(abs(target_vals)); + + % Save error and grid info. + error_vals(i) = rel_linf_error; + dx_vals(i) = grid_info.dx; + nbinpts_vals(i) = grid_info.nbinpts; + nproxy_vals(i) = proxy_info.nproxy; + + disp("tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); + +end + +figure(1); +clf; +subplot(2,1,1); +plot(tol_vals(:), error_vals(:), '.-') +hold on +plot(tol_vals(:), tol_vals(:), '--') +xscale('log') +ylabel("Observed error") +yscale('log') +xlabel("Tolerance") +grid on; +subplot(2,1,2); +plot(tol_vals(:), nproxy_vals(:), '.-'); +hold on; +plot(tol_vals(:), nbinpts_vals(:), '.-'); +% plot(tol_vals(:), nspread_vals(:), '.-'); +legend("nproxy", "nbinpts"); +grid on; +xscale('log'); \ No newline at end of file diff --git a/devtools/convergence_plots/end_to_end_one_over_r_2D.m b/devtools/convergence_plots/end_to_end_one_over_r_2D.m new file mode 100644 index 0000000..21dd61b --- /dev/null +++ b/devtools/convergence_plots/end_to_end_one_over_r_2D.m @@ -0,0 +1,82 @@ +addpath(genpath("../../pcfft")); +close all; +clear; + + +% Set up random source and target points +rng(1); +n_src = 500; +n_targ = 517; +dim = 2; + +kern_0 = @(s,t) one_over_r_kernel2D(s,t); +src_info = struct; +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] +src_info.r = (rand(dim, n_src) - 0.5); +targ_info = struct; +targ_info.r = (rand(dim, n_targ) - 0.5); + +% Source weights are random uniform in [0, 1] +mu = rand(n_src, 1); +K_exact = kern_0(src_info, targ_info); +target_vals = K_exact * mu; + +n_nbr = 10; + +% Loop through different tolerances and record errors and timings +tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08 1e-09 1e-10 1e-11 1e-12]; +n_tol_vals = size(tol_vals, 2); +error_vals = zeros(n_tol_vals, 1); +dx_vals = zeros(n_tol_vals, 1); +nbinpts_vals = zeros(n_tol_vals, 1); +nproxy_vals = zeros(n_tol_vals, 1); + +for i = 1:n_tol_vals + tol = tol_vals(i); + opts = struct('multi_shells', true); + [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr, opts); + % disp(grid_info); + [A_spread_s, sort_info_s ]= get_spread(kern_0, [], src_info, ... + grid_info, proxy_info); + [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... + grid_info, proxy_info); + + A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); + + k0hat = get_kernhat(kern_0,grid_info); + evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); + + % Compute relative L infinity error + diffs = abs(evals_approx - target_vals); + rel_linf_error = max(diffs) / max(abs(target_vals)); + + % Save error and grid info. + error_vals(i) = rel_linf_error; + dx_vals(i) = grid_info.dx; + nbinpts_vals(i) = grid_info.nbinpts; + nproxy_vals(i) = proxy_info.nproxy; + + disp("tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); + +end + +figure(1); +clf; +subplot(2,1,1); +plot(tol_vals(:), error_vals(:), '.-') +hold on +plot(tol_vals(:), tol_vals(:), '--') +xscale('log') +ylabel("Observed error") +yscale('log') +xlabel("Tolerance") +grid on; +subplot(2,1,2); +plot(tol_vals(:), nproxy_vals(:), '.-'); +hold on; +plot(tol_vals(:), nbinpts_vals(:), '.-'); +% plot(tol_vals(:), nspread_vals(:), '.-'); +legend("nproxy", "nbinpts"); +grid on; +xscale('log'); \ No newline at end of file diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index 7f94fd2..e293084 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -27,7 +27,7 @@ [box_pts, box_center] = grid_pts_for_box_2d(grid_info.center_bin, grid_info); % Neighborhood radius in bin-index units - rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + rad = interaction_radius(proxy_info, grid_info); % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index 495e610..bdef182 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -16,7 +16,7 @@ % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); % Radius in index space - rad = ceil(2 *proxy_info.radius / (grid_info.nspread * grid_info.dx)); + rad = interaction_radius(proxy_info, grid_info); % Which bins are within 2 * radius / (bin_width) ? id_x_min = id_x - rad; From 78caceac0151e23a2bf60fbef62b62fdf93f9f11 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Thu, 9 Apr 2026 14:35:21 -0400 Subject: [PATCH 09/14] 3D looks like it's working --- devtools/accuracy_tests/test_log_3D.m | 2 - devtools/accuracy_tests/test_one_over_r_3D.m | 2 - .../test_abstract_neighbor_spreading_3D.m | 91 +++++++++++++++++ devtools/test/test_bin_center_0.m | 1 + devtools/test/test_get_addsub.m | 72 +++++++------- devtools/test/test_neighbor_template_3D.m | 82 ++++++++++++++++ pcfft/get_addsub.m | 13 ++- pcfft/utils/abstract_neighbor_spreading_2D.m | 10 +- pcfft/utils/abstract_neighbor_spreading_3D.m | 55 +++++++++++ pcfft/utils/bin_center.m | 21 +++- pcfft/utils/interaction_radius.m | 16 +++ pcfft/utils/intersecting_bins_2d.m | 22 +---- pcfft/utils/intersecting_bins_3d.m | 41 +------- pcfft/utils/neighbor_offsets_2d.m | 24 +++++ pcfft/utils/neighbor_offsets_3d.m | 29 ++++++ pcfft/utils/neighbor_template_3d.m | 98 ++++++++----------- pcfft/utils/old_intersecting_bins_2d.m | 44 --------- 17 files changed, 411 insertions(+), 212 deletions(-) create mode 100644 devtools/test/test_abstract_neighbor_spreading_3D.m create mode 100644 devtools/test/test_neighbor_template_3D.m create mode 100644 pcfft/utils/abstract_neighbor_spreading_3D.m create mode 100644 pcfft/utils/interaction_radius.m create mode 100644 pcfft/utils/neighbor_offsets_2d.m create mode 100644 pcfft/utils/neighbor_offsets_3d.m delete mode 100644 pcfft/utils/old_intersecting_bins_2d.m diff --git a/devtools/accuracy_tests/test_log_3D.m b/devtools/accuracy_tests/test_log_3D.m index 232beb3..49273b0 100644 --- a/devtools/accuracy_tests/test_log_3D.m +++ b/devtools/accuracy_tests/test_log_3D.m @@ -2,8 +2,6 @@ close all; clear; -% Raise an error. This test is not implemented yet. -error("Test not implemented"); % Set up random source and target points rng(1); diff --git a/devtools/accuracy_tests/test_one_over_r_3D.m b/devtools/accuracy_tests/test_one_over_r_3D.m index 52cb72e..09178d4 100644 --- a/devtools/accuracy_tests/test_one_over_r_3D.m +++ b/devtools/accuracy_tests/test_one_over_r_3D.m @@ -2,8 +2,6 @@ close all; clear; -% Raise an error. This test is not implemented yet. -error("Test not implemented"); % Set up random source and target points rng(1); diff --git a/devtools/test/test_abstract_neighbor_spreading_3D.m b/devtools/test/test_abstract_neighbor_spreading_3D.m new file mode 100644 index 0000000..bc3803f --- /dev/null +++ b/devtools/test/test_abstract_neighbor_spreading_3D.m @@ -0,0 +1,91 @@ +% Tests for abstract_neighbor_spreading_2D. +addpath(genpath('../../pcfft')); + +%% test_0: +% Plot a basic spreading template to visually check it. + +dim = 3; +Lbd = [-1 1; -1 1; -1 1]; + +nsrc = 1000; +ntarg = 1000; +rng(0); +src_info = struct; +src_info.r = (rand(dim, nsrc) - 0.5) * 2; +targ_info = struct; +targ_info.r = (rand(dim, ntarg) - 0.5) * 2; + +[grid_info, proxy_info] = get_grid(@one_over_r_kernel, src_info, targ_info, 1e-6); + +[box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info); + +% Scatter plot spreading_template_pts +scatter3(spreading_template_pts(1, :), spreading_template_pts(2, :), spreading_template_pts(3, :), 'filled'); + +% Also scatter the gridpoints in a different color +hold on; +scatter3(grid_info.r(1, :), grid_info.r(2, :), grid_info.r(3, :), 'k.'); +xlabel('X'); +ylabel('Y'); +zlabel('Z'); +title('Spreading Template Points'); + +%% test_1: +% Basic size and centering checks on a hand-constructed grid. +% +% dx=0.5, nbinpts=2, nspread=4, so each spreading box is 4x4=16 pts. + +dim = 3; +Lbd = [-1 1; -1 1; -1 1]; +dx = 0.5; +nbinpts = 2; +nspread = 4; +grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); + +proxy_info = struct; +proxy_info.radius = 0.5; + +[box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info); + + + + +% box_pts must be [3, nspread^3] +assert(all(size(box_pts) == [3, nspread^3]), ... + 'box_pts must be [3, nspread^3]'); + +% box_pts must be centered at center_bin's spreading box center +[~, expected_box_center] = grid_pts_for_box_3d(grid_info.center_bin, grid_info); +box_center = mean(box_pts, 2); +assert(norm(box_center - expected_box_center) < 1e-10, ... + 'box_pts must be centered at center_bin spreading box center'); + +% No duplicate points in spreading_template_pts +[~, uid] = unique(spreading_template_pts.', 'rows'); +assert(length(uid) == size(spreading_template_pts, 2), ... + 'spreading_template_pts must have no duplicate points'); + +% box_pts must be a subset of spreading_template_pts (central bin is included) +% spreading_template_pts is relative to box_center, so compare box_pts - box_center +for i = 1:size(box_pts, 2) + dists = sqrt(sum((spreading_template_pts - (box_pts(:, i) - box_center)).^2, 1)); + assert(min(dists) < 1e-12, ... + sprintf('box_pts column %d not found in spreading_template_pts', i)); +end + +% spreading_template_pts must have at least nspread^3 points +assert(size(spreading_template_pts, 2) >= nspread^3, ... + 'spreading_template_pts must have at least nspread^3 points'); + +% spreading_template_pts must be separated by about dx +dists = sqrt(sum(diff(spreading_template_pts, 1, 2).^2, 1)); +min_dist = min(dists); +assert(min_dist + 1e-12 >= dx, ... + sprintf('Minimum distance between spreading_template_pts is %g, less than dx=%g', min_dist, dx)); + + + +% All indices must be unique +[~, uid] = unique(spreading_template_idxes.', 'rows'); +assert(length(uid) == size(spreading_template_idxes, 2), ... + 'spreading_template_idxes must have no duplicate indices'); \ No newline at end of file diff --git a/devtools/test/test_bin_center_0.m b/devtools/test/test_bin_center_0.m index 9ae6bfe..15e58c3 100644 --- a/devtools/test/test_bin_center_0.m +++ b/devtools/test/test_bin_center_0.m @@ -26,6 +26,7 @@ pad = ceil((grid_info.nspread - nbinpts)/2); grid_info.offset = pad * dx - dx/2; grid_info.dx = dx; +grid_info.dim = 2; % [a, b, c] = grid_pts_for_box_2d(0, grid_info); diff --git a/devtools/test/test_get_addsub.m b/devtools/test/test_get_addsub.m index a78a203..60cc904 100644 --- a/devtools/test/test_get_addsub.m +++ b/devtools/test/test_get_addsub.m @@ -55,55 +55,55 @@ %% 3D case % Makes sure get_addsub returns without error on a 2D input. -% clear; -% addpath(genpath('../../pcfft')); +clear; +addpath(genpath('../../pcfft')); -% rad = 10.0; -% tol = 1e-6; -% dim = 3; +rad = 10.0; +tol = 1e-6; +dim = 3; -% k = @(s,t) one_over_r_kernel(s,t); +k = @(s,t) one_over_r_kernel(s,t); -% src_info_3d = struct; -% n_src = 13; -% rng(0); -% src_info_3d.r = (rand(dim, n_src) - 0.5); -% src_info_3d.weights = rand(n_src, 1); +src_info_3d = struct; +n_src = 13; +rng(0); +src_info_3d.r = (rand(dim, n_src) - 0.5); +src_info_3d.weights = rand(n_src, 1); -% targ_info_3d = struct; -% ntarg = 17; -% targ_info_3d.r = rand(dim, ntarg) - 0.5; +targ_info_3d = struct; +ntarg = 17; +targ_info_3d.r = rand(dim, ntarg) - 0.5; -% [grid_info_3d, proxy_info_3d] = get_grid(k, ... -% src_info_3d, targ_info_3d, tol); +[grid_info_3d, proxy_info_3d] = get_grid(k, ... + src_info_3d, targ_info_3d, tol); -% % disp("grid_info.ngrid: ") -% % disp(grid_info.ngrid) -% % disp("grid_info.dx: ") -% % disp(grid_info.dx) -% % disp(grid_info.r(:, 1:100)) +% disp("grid_info.ngrid: ") +% disp(grid_info.ngrid) +% disp("grid_info.dx: ") +% disp(grid_info.dx) +% disp(grid_info.r(:, 1:100)) -% % scatter(grid_info.r(1,:), grid_info.r(2,:), 'k.'); -% % hold on; -% % scatter(src_info_2d.r(1,:), src_info_2d.r(2,:), 'ro'); +% scatter(grid_info.r(1,:), grid_info.r(2,:), 'k.'); +% hold on; +% scatter(src_info_2d.r(1,:), src_info_2d.r(2,:), 'ro'); -% kern_0 = @(s,t) one_over_r_kernel(s,t); -% kern = @(s,t) one_over_r_kernel(s,t); +kern_0 = @(s,t) one_over_r_kernel(s,t); +kern = @(s,t) one_over_r_kernel(s,t); -% [A_spread_s, sort_info_s ]= get_spread(kern_0, kern, src_info_3d, ... -% grid_info_3d, proxy_info_3d); +[A_spread_s, sort_info_s ]= get_spread(kern_0, kern, src_info_3d, ... +grid_info_3d, proxy_info_3d); -% [A_spread_t, sort_info_t ]= get_spread(kern_0, kern, targ_info_3d, ... -% grid_info_3d, proxy_info_3d); +[A_spread_t, sort_info_t ]= get_spread(kern_0, kern, targ_info_3d, ... +grid_info_3d, proxy_info_3d); -% A_addsub = get_addsub(kern_0, kern, src_info_3d, targ_info_3d, ... -% grid_info_3d, proxy_info_3d, sort_info_s, sort_info_t, A_spread_s, A_spread_t); +A_addsub = get_addsub(kern_0, kern, src_info_3d, targ_info_3d, ... + grid_info_3d, proxy_info_3d, sort_info_s, sort_info_t, A_spread_s, A_spread_t); -% % Check that A_adsub has the correct size. -% assert(all(size(A_addsub) == [ntarg n_src])); -% assert(all(~isnan(A_addsub(:)))); -% assert(all(~isinf(A_addsub(:)))); \ No newline at end of file +% Check that A_adsub has the correct size. +assert(all(size(A_addsub) == [ntarg n_src])); +assert(all(~isnan(A_addsub(:)))); +assert(all(~isinf(A_addsub(:)))); \ No newline at end of file diff --git a/devtools/test/test_neighbor_template_3D.m b/devtools/test/test_neighbor_template_3D.m new file mode 100644 index 0000000..9263113 --- /dev/null +++ b/devtools/test/test_neighbor_template_3D.m @@ -0,0 +1,82 @@ +% Makes sure get_addsub returns without error on a 2D input. +addpath(genpath('../../pcfft')); + +close all; + +tol = 1e-6; +dim = 2; + +k = @(s,t) log_kernel(s,t); + +src_info_2d = struct; +n_src = 1037; +rng(0); +src_info_2d.r = (rand(3, n_src) - 0.5); +src_info_2d.weights = rand(n_src, 1); + +targ_info_2d = struct; +ntarg = 173; +targ_info_2d.r = rand(3, ntarg) - 0.5; + + +[grid_info, proxy_info] = get_grid(@one_over_r_kernel, ... + src_info_2d, targ_info_2d, tol); + +N_bin = grid_info.nbin(1) * grid_info.nbin(2); + +% disp("test_intersecting_bins_2d: N_bin = " + int2str(N_bin)); + +[~, tmpl_pts, tmpl_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info); + +disp("test: tmpl_pts size: " + int2str(size(tmpl_pts))); +disp("test: tmpl_idxes size: " + int2str(size(tmpl_idxes))); +[nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_3d(grid_info, proxy_info, 8, tmpl_pts, tmpl_idxes); + + +% nbr_grididxes should either be in the range [1, ngrid(1)*ngrid(2)] or be equal to dummy_idx = ngrid(1)*ngrid(2) + 1 +dummy_idx = grid_info.ngrid(1) * grid_info.ngrid(2) * grid_info.ngrid(3) + 1; +assert(all(nbr_grididxes >= 1 & nbr_grididxes <= dummy_idx)); + +% valid nbr_grididxes should correctly index nbr_gridpts +keep_bool = nbr_grididxes ~= dummy_idx; +valid_grididxes = nbr_grididxes(keep_bool); +valid_gridpts = nbr_gridpts(:, keep_bool); + + +% Scatter plot the valid nbr_gridpts +scatter3(grid_info.r(1,:), grid_info.r(2,:), grid_info.r(3,:), 10, 'b'); +hold on; +scatter3(valid_gridpts(1,:), valid_gridpts(2,:), valid_gridpts(3,:), 20, 'r', 'filled'); + +% Plot the text of nbr_binids at the center of each bin +for i = 1:size(nbr_binids, 2) + bin_id = nbr_binids(i); + ctr = bin_center(bin_id, grid_info); + text(ctr(1), ctr(2), ctr(3), string(bin_id), 'Color', 'k'); +end + +title("Grid points with their linear indices"); + +N_bins = grid_info.nbin(1) * grid_info.nbin(2) * grid_info.nbin(3); +for bin_idx = 0:N_bins-1 + [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_3d(grid_info, proxy_info, bin_idx, tmpl_pts, tmpl_idxes); + assert(all(nbr_grididxes >= 1 & nbr_grididxes <= dummy_idx)); + + keep_bool = nbr_grididxes ~= dummy_idx; + valid_grididxes = nbr_grididxes(keep_bool); + + % Assert valid_grididxes are unique + assert(length(unique(valid_grididxes)) == length(valid_grididxes)); + + % Assert valid_grididxes correctly index valid_gridpts + valid_gridpts = nbr_gridpts(:, keep_bool); + for i = 1:size(valid_grididxes, 2) + idx = valid_grididxes(i); + pt = valid_gridpts(:, i); + grid_pt = grid_info.r(:, idx); + dist = norm(pt - grid_pt); + assert(dist < 1e-12); + end +end + +close all; \ No newline at end of file diff --git a/pcfft/get_addsub.m b/pcfft/get_addsub.m index 4c3e90b..f0daecf 100644 --- a/pcfft/get_addsub.m +++ b/pcfft/get_addsub.m @@ -66,8 +66,9 @@ box_center = bin_center(grid_info.center_bin, grid_info); pts0 = pts0 - box_center; else - [~, reg_neighbor_template_pts, ~, nbr_bin_idx] = neighbor_template_3d(grid_info, proxy_info); - [pts0, ctr_0, ~] = grid_pts_for_box_3d(nbr_bin_idx, grid_info); + [pts0, reg_neighbor_template_pts, template_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info); + box_center = bin_center(grid_info.center_bin, grid_info); + pts0 = pts0 - box_center; end nbr_info = struct('r', reg_neighbor_template_pts); @@ -96,6 +97,7 @@ % Add 1 row of zeros to A_spread_s to handle empty bins A_spread_s = [A_spread_s; sparse(1, opdim(2)*N_src)]; + % disp("get_addsub: size(A_spread_s) after adding dummy row: " + int2str(size(A_spread_s))); % dummy_idx = n_gridpts + 1; % TODO: correct formula for number of corrections @@ -120,7 +122,7 @@ % indexes of the regular grid points for spreading bin i, so we can % correctly index A_spread_t. if dim == 2 - [~, ctr_i, reg_idxs_i] = grid_pts_for_box_2d(bin_idx, grid_info); + [~, ~, reg_idxs_i] = grid_pts_for_box_2d(bin_idx, grid_info); else [reg_idxs_i] = grid_ids_for_box_3d(bin_idx, grid_info); end @@ -141,7 +143,8 @@ [nbr_binids, ~, nbr_grididxes] = neighbor_template_2d(grid_info, proxy_info, bin_idx, reg_neighbor_template_pts, template_idxes); nbr_binids(nbr_binids==-1) =[]; else - [nbr_binids, nbr_grididxes] = neighbor_bins_3d(grid_info, proxy_info, bin_idx); + [nbr_binids, ~, nbr_grididxes] = neighbor_template_3d(grid_info, proxy_info, bin_idx, reg_neighbor_template_pts, template_idxes); + nbr_binids(nbr_binids==-1) =[]; end % Loop through all of the neighbor bins and fill in the local source points. @@ -202,6 +205,8 @@ % Update A_sub with approximated near-field interactions. This is the % "sub" part. A_spread_t_i = A_spread_t(reg_idxs_i, opdim(1)*(idx_ti_start-1)+1:opdim(1)*idx_ti_end); + % disp("get_addsub: nbr_grididxes min: " + int2str(min(nbr_grididxes)) + ", max: " + int2str(max(nbr_grididxes)) + ", length: " + int2str(length(nbr_grididxes))); + % disp(nbr_grididxes); A_spread_s_j = A_spread_s(nbr_grididxes, source_idx_dof); AKA_chunk = A_spread_t_i.' * (K_nbr2bin * A_spread_s_j); diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index e293084..281f12f 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -31,12 +31,10 @@ % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); - for delta_x = -rad : rad - delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); - for delta_y = -delta_y_max : delta_y_max - shift = [delta_x; delta_y] * grid_info.nbinpts * grid_info.dx; - all_pts = [all_pts, box_pts + shift]; - end + offsets = neighbor_offsets_2d(rad); + for k = 1 : size(offsets, 2) + shift = offsets(:, k) * grid_info.nbinpts * grid_info.dx; + all_pts = [all_pts, box_pts + shift]; end % Deduplicate diff --git a/pcfft/utils/abstract_neighbor_spreading_3D.m b/pcfft/utils/abstract_neighbor_spreading_3D.m new file mode 100644 index 0000000..06e9eb9 --- /dev/null +++ b/pcfft/utils/abstract_neighbor_spreading_3D.m @@ -0,0 +1,55 @@ +function [box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info) + % Constructs a spreading box for grid_info.center_bin and a spreading + % template covering all neighboring bins, in center_bin-relative coordinates. + % + % Inputs + % ------ + % grid_info : GridInfo + % Regular grid and bin structure. + % proxy_info : ProxyInfo (or equivalent struct with a .radius field) + % Proxy geometry used to determine which bins intersect. + % + % Outputs + % ------- + % box_pts : array [3, nspread^2] + % Coordinates of the spreading box for grid_info.center_bin. + % spreading_template_pts : array [3, n_template_pts] + % Coordinates of all unique spreading box points across every + % neighboring bin (including center_bin), centered at the center of + % box_pts. + % spreading_template_idxes : array [3, n_template_pts] + % Grid indices of the spreading template points. Row 1 contains + % 0-indexed x-grid positions; row 2 contains 0-indexed y-grid positions. + % Row 3 contains 0-indexed z-grid positions. For a target bin, shift + % these indices by the bin offset to obtain the corresponding global + % grid positions. + + + % First, get the spreading box for the center bin. + [box_pts, box_center] = grid_pts_for_box_3d(grid_info.center_bin, grid_info); + + % Neighbor radius in bin-index units + rad = interaction_radius(proxy_info, grid_info); + + % Collect spreading box points for every neighboring bin offset. + all_pts = zeros(3, 0); + offsets = neighbor_offsets_3d(rad); + for k = 1 : size(offsets, 2) + shift = offsets(:, k) * grid_info.nbinpts * grid_info.dx; + all_pts = [all_pts, box_pts + shift]; + end + + % Center relative to box_center + spreading_template_pts = all_pts - box_center; + + % Get 0-indexed grid positions for each template points + x_idxes = round((all_pts(1,:) - (grid_info.rmin(1))) / grid_info.dx)+1; + y_idxes = round((all_pts(2,:) - (grid_info.rmin(2))) / grid_info.dx)+1; + z_idxes = round((all_pts(3,:) - (grid_info.rmin(3))) / grid_info.dx)+1; + spreading_template_idxes = [x_idxes; y_idxes; z_idxes]; + + % Deduplicate indices + [spreading_template_idxes, unique_idx] = unique(spreading_template_idxes.', 'rows'); + spreading_template_pts = spreading_template_pts(:, unique_idx); + +end \ No newline at end of file diff --git a/pcfft/utils/bin_center.m b/pcfft/utils/bin_center.m index fe8d909..4eb7447 100644 --- a/pcfft/utils/bin_center.m +++ b/pcfft/utils/bin_center.m @@ -15,9 +15,15 @@ nbinpts = grid_info.nbinpts; offset = grid_info.offset; - % Compute integer bin ids (consistent with grid_pts_for_box_2d) - id_y = mod(bin_idx, N_y_bins); - id_x = floor((bin_idx - id_y) / N_y_bins); + if grid_info.dim == 2 + id_y = mod(bin_idx, N_y_bins); + id_x = floor((bin_idx - id_y) / N_y_bins); + else + N_z_bins = grid_info.nbin(3); + id_z = mod(bin_idx, N_z_bins); + id_y = mod(floor(bin_idx / N_z_bins), N_y_bins); + id_x = floor(bin_idx / (N_y_bins * N_z_bins)); + end % Compute the first and last gridpoints and find their average x_first = Lbd(1) - offset + id_x * dx * nbinpts + 0 * dx; @@ -26,5 +32,12 @@ y_first = Lbd(2) - offset + id_y * dx * nbinpts + 0 * dx; y_last = Lbd(2) - offset + id_y * dx * nbinpts + (nspread-1) * dx; - center = [(x_first + x_last) / 2; (y_first + y_last) / 2]; + if grid_info.dim == 2 + center = [(x_first + x_last) / 2; (y_first + y_last) / 2]; + else + z_first = Lbd(3) - offset + id_z * dx * nbinpts + 0 * dx; + z_last = Lbd(3) - offset + id_z * dx * nbinpts + (nspread-1) * dx; + + center = [(x_first + x_last) / 2; (y_first + y_last) / 2; (z_first + z_last) / 2]; + end end diff --git a/pcfft/utils/interaction_radius.m b/pcfft/utils/interaction_radius.m new file mode 100644 index 0000000..b505dae --- /dev/null +++ b/pcfft/utils/interaction_radius.m @@ -0,0 +1,16 @@ +function rad = interaction_radius(proxy_info, grid_info) + % Returns the interaction radius in bin-index units, which is used to + % determine which neighboring bins to spread to. + % + % proxy_info : struct with fields + % radius : scalar + % The radius of the proxy points in physical units. + % + % grid_info : struct with fields + % nspread : scalar + % The number of points in the spreading box along one dimension. + % dx : scalar + % The grid spacing in physical units. + + rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); +end \ No newline at end of file diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index bdef182..59415bb 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -18,25 +18,9 @@ % Radius in index space rad = interaction_radius(proxy_info, grid_info); - % Which bins are within 2 * radius / (bin_width) ? - id_x_min = id_x - rad; - id_x_max = id_x + rad; - - - id_xs = []; - id_ys = []; - % Loop over idx_x - for idx_x = id_x_min:id_x_max - - % Compute max idx_y for this idx_x - dx_offset = idx_x - id_x; - idx_y_max = id_y + ceil(sqrt(rad^2 - dx_offset^2)); - idx_y_min = id_y - ceil(sqrt(rad^2 - dx_offset^2)); - - ny = idx_y_max - idx_y_min + 1; - id_xs = [id_xs, repmat(idx_x, 1, ny)]; - id_ys = [id_ys, idx_y_min:idx_y_max]; - end + offsets = neighbor_offsets_2d(rad); + id_xs = id_x + offsets(1, :); + id_ys = id_y + offsets(2, :); % Compute the binids binids = id_ys(:) + id_xs(:) * N_y_bins; diff --git a/pcfft/utils/intersecting_bins_3d.m b/pcfft/utils/intersecting_bins_3d.m index 3ec9edf..91d38f7 100644 --- a/pcfft/utils/intersecting_bins_3d.m +++ b/pcfft/utils/intersecting_bins_3d.m @@ -17,43 +17,12 @@ % disp("intersecting_bins_3d: For bin_idx " + int2str(bin_idx) + ... % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); - % Which bins are within 2 * radius / (nspread * dx) ? - id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + rad = interaction_radius(proxy_info, grid_info); - % id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % id_z_min = id_z - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - % id_z_max = id_z + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - - % id_xs = id_x_min:id_x_max; - % id_ys = id_y_min:id_y_max; - % id_zs = id_z_min:id_z_max; - - % Radius in index space is 2 * radius / (nspread * dx). - rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - - id_xs = []; - id_ys = []; - id_zs = []; - for idx_x = id_x_min:id_x_max - % Compute max idx_y for this idx_x - dx_offset = idx_x - id_x; - idx_y_max = id_y + floor(rad^2 - dx_offset^2 ); - idx_y_min = id_y - floor(rad^2 - dx_offset^2 ); - - for idx_y = idx_y_min:idx_y_max - % Compute max idx_z for this idx_x, idx_y - dy_offset = idx_y - id_y; - idx_z_max = id_z + floor(rad^2 - dx_offset^2 - dy_offset^2 ); - idx_z_min = id_z - floor(rad^2 - dx_offset^2 - dy_offset^2 ); - - nz = idx_z_max - idx_z_min + 1; - id_xs = [id_xs, repmat(idx_x, 1, nz)]; - id_ys = [id_ys, repmat(idx_y, 1, nz)]; - id_zs = [id_zs, idx_z_min:idx_z_max]; - end - end + offsets = neighbor_offsets_3d(rad); + id_xs = id_x + offsets(1, :); + id_ys = id_y + offsets(2, :); + id_zs = id_z + offsets(3, :); diff --git a/pcfft/utils/neighbor_offsets_2d.m b/pcfft/utils/neighbor_offsets_2d.m new file mode 100644 index 0000000..ef53b25 --- /dev/null +++ b/pcfft/utils/neighbor_offsets_2d.m @@ -0,0 +1,24 @@ +function offsets = neighbor_offsets_2d(rad) + % neighbor_offsets_2d Integer bin offsets within a circular disk. + % + % Returns all integer (delta_x, delta_y) pairs whose Euclidean distance + % from the origin is at most rad (using ceil rounding on the boundary). + % + % Input + % ----- + % rad : scalar + % Neighborhood radius in bin-index units. + % + % Output + % ------ + % offsets : array [2, n] + % Each column is one (delta_x; delta_y) offset. + + offsets = zeros(2, 0); + for delta_x = -rad : rad + delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); + for delta_y = -delta_y_max : delta_y_max + offsets = [offsets, [delta_x; delta_y]]; + end + end +end diff --git a/pcfft/utils/neighbor_offsets_3d.m b/pcfft/utils/neighbor_offsets_3d.m new file mode 100644 index 0000000..b7fd349 --- /dev/null +++ b/pcfft/utils/neighbor_offsets_3d.m @@ -0,0 +1,29 @@ +function offsets = neighbor_offsets_3d(rad) + % neighbor_offsets_3d Integer bin offsets within a spherical ball. + % + % Returns all integer (delta_x, delta_y, delta_z) triples whose Euclidean + % distance from the origin is at most rad (using ceil rounding on the boundary). + % + % Input + % ----- + % rad : scalar + % Neighborhood radius in bin-index units. + % + % Output + % ------ + % offsets : array [3, n] + % Each column is one (delta_x; delta_y; delta_z) offset. + + offsets = zeros(3, 0); + for delta_x = -rad : rad + delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); + for delta_y = -delta_y_max : delta_y_max + r2 = rad^2 - delta_x^2 - delta_y^2; + if r2 < 0; continue; end + delta_z_max = ceil(sqrt(r2)); + for delta_z = -delta_z_max : delta_z_max + offsets = [offsets, [delta_x; delta_y; delta_z]]; + end + end + end +end diff --git a/pcfft/utils/neighbor_template_3d.m b/pcfft/utils/neighbor_template_3d.m index 01b5759..6ed4582 100644 --- a/pcfft/utils/neighbor_template_3d.m +++ b/pcfft/utils/neighbor_template_3d.m @@ -1,68 +1,48 @@ -function [nbr_binids, nbr_gridpts, nbr_grididxes, bin_idx] = neighbor_template_3d(grid_info, proxy_info, bin_idx) +function [nbr_binids, nbr_gridpts, nbr_grididxes] = neighbor_template_3d(grid_info, proxy_info, bin_idx, template_pts, template_idxes) - if nargin == 2 - % First, find how many bins are intersecting. - [int_idx_x, int_idx_y, int_idx_z] = intersecting_bins_3d(0, grid_info, proxy_info); - % Now find a bin s.t. all of the intersecting bins have idx >= 0. - offset_x = ceil((length(int_idx_x) - 1) / 2); - offset_y = ceil((length(int_idx_y) - 1) / 2); - offset_z = ceil((length(int_idx_z) - 1) / 2); - bin_idx = offset_x * grid_info.nbin(2) * grid_info.nbin(3) + offset_y * grid_info.nbin(3) + offset_z; - end - [int_idx_x, int_idx_y, int_idx_z, nbr_binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); - - - % disp("neighbor_template_2d: For bin_idx " + int2str(bin_idx) + ... - % ", ind_idx_x: "); - % disp(ind_idx_x); - - % Info from grid_info that will be used later - rpad = grid_info.rpad; - nbinpts = grid_info.nbinpts; - Lbd = grid_info.Lbd; - dx = grid_info.dx; - offset = grid_info.offset; - ngrid = grid_info.ngrid; - rmax = grid_info.rmax; - rmin = grid_info.rmin; - - % % Build the nbr_gridpts - nx = size(int_idx_x, 2); - npts = nx * nbinpts + 2 * rpad; - minxnbr = min(int_idx_x); % Minimum x bin index of the neighbor bins - minynbr = min(int_idx_y); % Minimum y bin index of the neighbor bins - minznbr = min(int_idx_z); % Minimum z bin index of the neighbor bins - nbr_xpts = Lbd(1) - offset + minxnbr * nbinpts * dx + dx * (0:npts-1); - nbr_ypts = Lbd(2) - offset + minynbr * nbinpts * dx + dx * (0:npts-1); - nbr_zpts = Lbd(3) - offset + minznbr * nbinpts * dx + dx * (0:npts-1); - [X, Y, Z] = meshgrid(nbr_xpts, nbr_ypts, nbr_zpts); - X = permute(X,[3,1,2]); - Y = permute(Y,[3,1,2]); - Z = permute(Z,[3,1,2]); - nbr_gridpts = [X(:).'; Y(:).'; Z(:).']; + [~, ~, ~,nbr_binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); + % Compute the grid-index shift from center_bin to bin_idx. + % cx, cy, cz are the 3D bin coordinates of center_bin + cz = mod(grid_info.center_bin, grid_info.nbin(3)); + cy = mod(floor(grid_info.center_bin / grid_info.nbin(3)), grid_info.nbin(2)); + cx = floor(grid_info.center_bin / (grid_info.nbin(2) * grid_info.nbin(3))); - % % Build the nbr_grididxes. This logic is copied from grid_pts_for_box_3d - % and npts is used instead of nspread. - x_positions = minxnbr * nbinpts +1 : minxnbr * nbinpts + npts ; - y_positions = minynbr * nbinpts +1 : minynbr * nbinpts + npts ; - z_positions = minznbr * nbinpts +1 : minznbr * nbinpts + npts ; - % Mark out-of-bounds grid points with a dummy index - ngridpts = grid_info.ngrid(1) * grid_info.ngrid(2) * grid_info.ngrid(3); - dummy_idx = ngridpts + 1; + % bz, by, bx are the 3D bin coordinates of bin_idx + bz = mod(bin_idx, grid_info.nbin(3)); + by = mod(floor(bin_idx / grid_info.nbin(3)), grid_info.nbin(2)); + bx = floor(bin_idx / (grid_info.nbin(2) * grid_info.nbin(3))); - nbr_grididxes = (z_positions(:)) + (y_positions(:)-1).'*ngrid(3) + reshape(x_positions-1,1,1,[]) * ngrid(2) * ngrid(3); + delta_ix = (bx - cx) * grid_info.nbinpts; + delta_iy = (by - cy) * grid_info.nbinpts; + delta_iz = (bz - cz) * grid_info.nbinpts; - % Mark the row_idxes corresponding to out-of-bounds grid points with a dummy - % Might need a tiny bit of margin here - margin = 0.1 * dx; - - nbr_grididxes(nbr_zpts rmax(3) + margin,:,:) = dummy_idx; - nbr_grididxes(:,nbr_ypts rmax(2) + margin,:) = dummy_idx; - nbr_grididxes(:,:,nbr_xpts rmax(1) + margin) = dummy_idx; - - nbr_grididxes = nbr_grididxes(:).'; + shift = [delta_ix; delta_iy; delta_iz]; + shifted_idxes = (template_idxes.' + shift); + % disp("neighbor_template_3d: size(template_idxes): " + int2str(size(template_idxes)) + ... + % ", size(shift): " + int2str(size(shift)) + ... + % ", size(shifted_idxes): " + int2str(size(shifted_idxes))); + + % Only keep in-bounds grid points. + ngrid = grid_info.ngrid; + in_bounds = shifted_idxes(1,:) >= 1 & shifted_idxes(1,:) <= ngrid(1) & ... + shifted_idxes(2,:) >= 1 & shifted_idxes(2,:) <= ngrid(2) & ... + shifted_idxes(3,:) >= 1 & shifted_idxes(3,:) <= ngrid(3); + nbr_grididxes = (shifted_idxes(1,:) - 1) * ngrid(2) * ngrid(3) + (shifted_idxes(2,:) - 1) * ngrid(3) + shifted_idxes(3,:); + % disp("neighbor_template_3d: size(nbr_grididxes): " + int2str(size(nbr_grididxes)) + ... + % ", size(in_bounds): " + int2str(size(in_bounds))); + % Set out-of-bounds grid points to a dummy index. + dummy_idx = ngrid(1) * ngrid(2) * ngrid(3) + 1; + nbr_grididxes(~in_bounds) = dummy_idx; + + % Compute physical coordinates + ctr = bin_center(bin_idx, grid_info); + nbr_gridpts = template_pts + ctr; + + % disp("neighbor_template_3d: size(nbr_grididxes): " + int2str(size(nbr_grididxes)) + ... + % ", size(nbr_gridpts): " + int2str(size(nbr_gridpts)) + ... + % ", size(nbr_binids): " + int2str(size(nbr_binids))); end \ No newline at end of file diff --git a/pcfft/utils/old_intersecting_bins_2d.m b/pcfft/utils/old_intersecting_bins_2d.m deleted file mode 100644 index cad1fae..0000000 --- a/pcfft/utils/old_intersecting_bins_2d.m +++ /dev/null @@ -1,44 +0,0 @@ -function [id_xs, id_ys, binids] = old_intersecting_bins_2d(bin_idx, grid_info, ... - proxy_info) - % Given a set of bins which are described by grid_info, and a set of proxy - % surfaces which are described by proxy_info, return id_xs and id_ys. The - % product of these two sets of bins is the set of intersecting bin idxes. - % - % This function may return invalid bin idxes in the first two return values, - % i.e. < 0 or >= grid_info.nbin(d). In the third return value, these invalid - % bin idxes are set to -1. - % We say that two bins intersect if their proxy surfaces intersect at all. - - N_y_bins = grid_info.nbin(2); - id_y = mod(bin_idx, N_y_bins); - id_x = floor((bin_idx - id_y)/N_y_bins); - % disp("intersecting_bins_2d: For bin_idx " + int2str(bin_idx) + ... - % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); - - % Which bins are within 2 * radius / (nspread * dx) ? - id_x_min = id_x - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_x_max = id_x + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - - id_y_min = id_y - ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - id_y_max = id_y + ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); - - id_xs = id_x_min:id_x_max; - id_ys = id_y_min:id_y_max; - - % Compute the binids - binids = zeros(length(id_xs) * length(id_ys), 1 ); - for i = 1:length(id_xs) - for j = 1:length(id_ys) - % If it's an invalid binid, set it to -1 - if id_xs(i) < 0 || id_xs(i) >= grid_info.nbin(1) || ... - id_ys(j) < 0 || id_ys(j) >= grid_info.nbin(2) - - binids((i-1)*length(id_ys) + j) = -1; - else - binids((i-1)*length(id_ys) + j) = ... - id_xs(i) * N_y_bins + id_ys(j); - end - end - end - binids = binids.'; -end \ No newline at end of file From 93bea0329a8c9e331f601989dfbbef28a140c0d6 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Thu, 9 Apr 2026 16:00:00 -0400 Subject: [PATCH 10/14] Various optimizations --- devtools/accuracy_tests/test_one_over_r_3D.m | 7 +++---- pcfft/classes/GridInfo.m | 15 +++++++++++++ pcfft/get_spread.m | 15 ++++++------- pcfft/utils/bin_center.m | 13 +++++------- pcfft/utils/grid_ids_for_box_3d.m | 10 ++++----- pcfft/utils/grid_pts_for_box_3d.m | 22 +++++--------------- pcfft/utils/neighbor_offsets_2d.m | 5 ++--- pcfft/utils/neighbor_offsets_3d.m | 5 ++--- 8 files changed, 45 insertions(+), 47 deletions(-) diff --git a/devtools/accuracy_tests/test_one_over_r_3D.m b/devtools/accuracy_tests/test_one_over_r_3D.m index 09178d4..12c883b 100644 --- a/devtools/accuracy_tests/test_one_over_r_3D.m +++ b/devtools/accuracy_tests/test_one_over_r_3D.m @@ -5,17 +5,16 @@ % Set up random source and target points rng(1); -n_src = 500; -n_targ = 517; +n_src = 5000; +n_targ = 5017; dim = 3; kern_0 = @(s,t) one_over_r_kernel(s,t); src_info = struct; -% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5] +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0run.5] src_info.r = (rand(dim, n_src) - 0.5); targ_info = struct; targ_info.r = (rand(dim, n_targ) - 0.5); - % Source weights are random uniform in [0, 1] mu = rand(n_src, 1); K_exact = kern_0(src_info, targ_info); diff --git a/pcfft/classes/GridInfo.m b/pcfft/classes/GridInfo.m index c576b89..cd511a2 100644 --- a/pcfft/classes/GridInfo.m +++ b/pcfft/classes/GridInfo.m @@ -38,6 +38,8 @@ % average number of near-field neighbours. % zero_bin : array [dim, nbinpts^dim] % Grid points of a spreading bin centered at the origin. + % zero_box : array [dim, nspread^dim] + % Grid points of a spreading box centered at the origin. % center_bin : int % Linear index of a bin approximately at the center of the grid. % Computed as floor(nbin(1)/2) * nbin(2) + floor(nbin(2)/2). @@ -57,6 +59,7 @@ rmin n_nbr zero_bin + zero_box center_bin end methods @@ -101,6 +104,10 @@ zero_pts = dx * (0:nbinpts-1) - (nbinpts-1)/2 * dx; [X, Y] = meshgrid(zero_pts, zero_pts); zero_bin = [X(:).'; Y(:).']; + + zero_pts_box = dx * (0:nspread-1) - (nspread-1)/2 * dx; + [X, Y] = meshgrid(zero_pts_box, zero_pts_box); + zero_box = [X(:).'; Y(:).']; elseif dim == 3 zero_pts = dx * (0:nbinpts-1) - (nbinpts-1)/2 * dx; [X, Y, Z] = meshgrid(zero_pts, zero_pts, zero_pts); @@ -108,6 +115,13 @@ Y = permute(Y,[3,1,2]); Z = permute(Z,[3,1,2]); zero_bin = [X(:).'; Y(:).'; Z(:).']; + + zero_pts_box = dx * (0:nspread-1) - (nspread-1)/2 * dx; + [X, Y, Z] = meshgrid(zero_pts_box, zero_pts_box, zero_pts_box); + X = permute(X,[3,1,2]); + Y = permute(Y,[3,1,2]); + Z = permute(Z,[3,1,2]); + zero_box = [X(:).'; Y(:).'; Z(:).']; end obj.ngrid = ngrid; @@ -124,6 +138,7 @@ obj.rmin = rmin; obj.n_nbr = n_nbr; obj.zero_bin = zero_bin; + obj.zero_box = zero_box; obj.center_bin = floor(n_bin(1)/2) * n_bin(2) + floor(n_bin(2)/2); end end diff --git a/pcfft/get_spread.m b/pcfft/get_spread.m index 3116cb7..08e47e8 100644 --- a/pcfft/get_spread.m +++ b/pcfft/get_spread.m @@ -95,11 +95,12 @@ idx_end = id_start(i+1) - 1; % Get the regular grid points and centers of bin i - if dim == 2 - [pts_i, center_i, row_idxes_i] = grid_pts_for_box_2d(i-1, grid_info); - else - [pts_i, center_i, row_idxes_i] = grid_pts_for_box_3d(i-1, grid_info); - end + % if dim == 2 + % [pts_i, center_i, row_idxes_i] = grid_pts_for_box_2d(i-1, grid_info); + % else + % [pts_i, center_i, row_idxes_i] = grid_pts_for_box_3d(i-1, grid_info); + % end + center_i = bin_center(i-1, grid_info); % disp("get_spread: In bin " + int2str(i)) % disp("get_spread: idx_start " + int2str(idx_start)) % disp("get_spread: idx_end " + int2str(idx_end)) @@ -134,9 +135,9 @@ % Remember, we 0-indexed the bin IDs for i = 0:size(id_start,2) - 2 if dim == 2 - [pts_i, center_i, row_idxes_i] = grid_pts_for_box_2d(i, grid_info); + [~, ~, row_idxes_i] = grid_pts_for_box_2d(i, grid_info); else - [pts_i, center_i, row_idxes_i] = grid_pts_for_box_3d(i, grid_info); + row_idxes_i = grid_ids_for_box_3d(i, grid_info); end idx_start = opdim*(id_start(i+1)-1) + 1; diff --git a/pcfft/utils/bin_center.m b/pcfft/utils/bin_center.m index 4eb7447..ca416e4 100644 --- a/pcfft/utils/bin_center.m +++ b/pcfft/utils/bin_center.m @@ -26,18 +26,15 @@ end % Compute the first and last gridpoints and find their average - x_first = Lbd(1) - offset + id_x * dx * nbinpts + 0 * dx; - x_last = Lbd(1) - offset + id_x * dx * nbinpts + (nspread-1) * dx; - y_first = Lbd(2) - offset + id_y * dx * nbinpts + 0 * dx; - y_last = Lbd(2) - offset + id_y * dx * nbinpts + (nspread-1) * dx; + x_center = Lbd(1) - offset + id_x * dx * nbinpts + (nspread-1)/2 * dx; + y_center = Lbd(2) - offset + id_y * dx * nbinpts + (nspread-1)/2 * dx; if grid_info.dim == 2 - center = [(x_first + x_last) / 2; (y_first + y_last) / 2]; + center = [x_center; y_center]; else - z_first = Lbd(3) - offset + id_z * dx * nbinpts + 0 * dx; - z_last = Lbd(3) - offset + id_z * dx * nbinpts + (nspread-1) * dx; + z_center = Lbd(3) - offset + id_z * dx * nbinpts + (nspread-1)/2 * dx; - center = [(x_first + x_last) / 2; (y_first + y_last) / 2; (z_first + z_last) / 2]; + center = [x_center; y_center; z_center]; end end diff --git a/pcfft/utils/grid_ids_for_box_3d.m b/pcfft/utils/grid_ids_for_box_3d.m index 1bbdfdb..dfc5ab8 100644 --- a/pcfft/utils/grid_ids_for_box_3d.m +++ b/pcfft/utils/grid_ids_for_box_3d.m @@ -15,8 +15,6 @@ % bin_idx = id_x * N_y_bins * N_z_bins + id_y * N_z_bins + id_z; - dx = grid_info.dx; - Lbd = grid_info.Lbd; ngrid = grid_info.ngrid; N_y_bins = grid_info.nbin(2); @@ -27,10 +25,12 @@ id_z = mod(bin_idx, N_z_bins); id_y = mod(floor(bin_idx / N_z_bins), N_y_bins); id_x = floor(bin_idx / (N_y_bins * N_z_bins)); - % Compute the row indices. - % First, we know that the xpts are in position + + + % Compute the row indices. + % First, we know that the xpts are in position % (id_x - 1) * nbinpts + 1: id_x * nbinpts - % and the ypts are in position + % and the ypts are in position % (id_y - 1) * nbinpts + 1: id_y * nbinpts % and the zpts are in position % (id_z - 1) * nbinpts + 1: id_z * nbinpts diff --git a/pcfft/utils/grid_pts_for_box_3d.m b/pcfft/utils/grid_pts_for_box_3d.m index d0c8843..b09e53c 100644 --- a/pcfft/utils/grid_pts_for_box_3d.m +++ b/pcfft/utils/grid_pts_for_box_3d.m @@ -17,36 +17,24 @@ % bin_idx = id_x * N_y_bins * N_z_bins + id_y * N_z_bins + id_z; - dx = grid_info.dx; - Lbd = grid_info.Lbd; ngrid = grid_info.ngrid; N_y_bins = grid_info.nbin(2); N_z_bins = grid_info.nbin(3); nspread = grid_info.nspread; nbinpts = grid_info.nbinpts; - offset = grid_info.offset; id_z = mod(bin_idx, N_z_bins); id_y = mod(floor(bin_idx / N_z_bins), N_y_bins); id_x = floor(bin_idx / (N_y_bins * N_z_bins)); - % Find the grid points corresponding to id_x, id_y, and id_z - xpts = Lbd(1) - offset + id_x * dx * nbinpts + dx * (0:nspread-1); - ypts = Lbd(2) - offset + id_y * dx * nbinpts + dx * (0:nspread-1); - zpts = Lbd(3) - offset + id_z * dx * nbinpts + dx * (0:nspread-1); - [X,Y,Z] = meshgrid(xpts, ypts, zpts); - X = permute(X,[3,1,2]); - Y = permute(Y,[3,1,2]); - Z = permute(Z,[3,1,2]); - pts = [X(:).'; Y(:).'; Z(:).']; + center = bin_center(bin_idx, grid_info); + pts = grid_info.zero_box + center; - center = [(xpts(1) + xpts(end)) / 2; (ypts(1) + ypts(end)) / 2; (zpts(1) + zpts(end)) / 2]; - - % Compute the row indices. - % First, we know that the xpts are in position + % Compute the row indices. + % First, we know that the xpts are in position % (id_x - 1) * nbinpts + 1: id_x * nbinpts - % and the ypts are in position + % and the ypts are in position % (id_y - 1) * nbinpts + 1: id_y * nbinpts % and the zpts are in position % (id_z - 1) * nbinpts + 1: id_z * nbinpts diff --git a/pcfft/utils/neighbor_offsets_2d.m b/pcfft/utils/neighbor_offsets_2d.m index ef53b25..d6cec14 100644 --- a/pcfft/utils/neighbor_offsets_2d.m +++ b/pcfft/utils/neighbor_offsets_2d.m @@ -17,8 +17,7 @@ offsets = zeros(2, 0); for delta_x = -rad : rad delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); - for delta_y = -delta_y_max : delta_y_max - offsets = [offsets, [delta_x; delta_y]]; - end + delta_ys = -delta_y_max : delta_y_max; + offsets = [offsets, [delta_x * ones(1, numel(delta_ys)); delta_ys]]; end end diff --git a/pcfft/utils/neighbor_offsets_3d.m b/pcfft/utils/neighbor_offsets_3d.m index b7fd349..7315ac3 100644 --- a/pcfft/utils/neighbor_offsets_3d.m +++ b/pcfft/utils/neighbor_offsets_3d.m @@ -21,9 +21,8 @@ r2 = rad^2 - delta_x^2 - delta_y^2; if r2 < 0; continue; end delta_z_max = ceil(sqrt(r2)); - for delta_z = -delta_z_max : delta_z_max - offsets = [offsets, [delta_x; delta_y; delta_z]]; - end + delta_zs = -delta_z_max : delta_z_max; + offsets = [offsets, [delta_x * ones(1, numel(delta_zs)); delta_y * ones(1, numel(delta_zs)); delta_zs]]; end end end From 5aba4d3692f1813ac3f17cf32c7b8958c28e066f Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Thu, 9 Apr 2026 16:30:46 -0400 Subject: [PATCH 11/14] Changed interaction radius def and added end_to_end convergence plots for 3D --- .../convergence_plots/end_to_end_log_3D.m | 82 +++++++++++++++++++ .../end_to_end_one_over_r_3D.m | 82 +++++++++++++++++++ pcfft/utils/dx_nproxy.m | 1 + pcfft/utils/interaction_radius.m | 2 +- 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 devtools/convergence_plots/end_to_end_log_3D.m create mode 100644 devtools/convergence_plots/end_to_end_one_over_r_3D.m diff --git a/devtools/convergence_plots/end_to_end_log_3D.m b/devtools/convergence_plots/end_to_end_log_3D.m new file mode 100644 index 0000000..a21862e --- /dev/null +++ b/devtools/convergence_plots/end_to_end_log_3D.m @@ -0,0 +1,82 @@ +addpath(genpath("../../pcfft")); +close all; +clear; + + +% Set up random source and target points +rng(1); +n_src = 500; +n_targ = 517; +dim = 3; + +kern_0 = @(s,t) log_kernel3D(s,t); +src_info = struct; +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5] +src_info.r = (rand(dim, n_src) - 0.5); +targ_info = struct; +targ_info.r = (rand(dim, n_targ) - 0.5); + +% Source weights are random uniform in [0, 1] +mu = rand(n_src, 1); +K_exact = kern_0(src_info, targ_info); +target_vals = K_exact * mu; + +n_nbr = 100; + +% Loop through different tolerances and record errors and timings +tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 ]; +n_tol_vals = size(tol_vals, 2); +error_vals = zeros(n_tol_vals, 1); +dx_vals = zeros(n_tol_vals, 1); +nbinpts_vals = zeros(n_tol_vals, 1); +nproxy_vals = zeros(n_tol_vals, 1); + +for i = 1:n_tol_vals + tol = tol_vals(i); + opts = struct('multi_shells', true); + [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr, opts); + % disp(grid_info); + [A_spread_s, sort_info_s ]= get_spread(kern_0, [], src_info, ... + grid_info, proxy_info); + [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... + grid_info, proxy_info); + + A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); + + k0hat = get_kernhat(kern_0,grid_info); + evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); + + % Compute relative L infinity error + diffs = abs(evals_approx - target_vals); + rel_linf_error = max(diffs) / max(abs(target_vals)); + + % Save error and grid info. + error_vals(i) = rel_linf_error; + dx_vals(i) = grid_info.dx; + nbinpts_vals(i) = grid_info.nbinpts; + nproxy_vals(i) = proxy_info.nproxy; + + disp("tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); + +end + +figure(1); +clf; +subplot(2,1,1); +plot(tol_vals(:), error_vals(:), '.-') +hold on +plot(tol_vals(:), tol_vals(:), '--') +xscale('log') +ylabel("Observed error") +yscale('log') +xlabel("Tolerance") +grid on; +subplot(2,1,2); +plot(tol_vals(:), nproxy_vals(:), '.-'); +hold on; +plot(tol_vals(:), nbinpts_vals(:), '.-'); +% plot(tol_vals(:), nspread_vals(:), '.-'); +legend("nproxy", "nbinpts"); +grid on; +xscale('log'); \ No newline at end of file diff --git a/devtools/convergence_plots/end_to_end_one_over_r_3D.m b/devtools/convergence_plots/end_to_end_one_over_r_3D.m new file mode 100644 index 0000000..adc3a59 --- /dev/null +++ b/devtools/convergence_plots/end_to_end_one_over_r_3D.m @@ -0,0 +1,82 @@ +addpath(genpath("../../pcfft")); +close all; +clear; + + +% Set up random source and target points +rng(1); +n_src = 500; +n_targ = 517; +dim = 3; + +kern_0 = @(s,t) one_over_r_kernel(s,t); +src_info = struct; +% Source and target points are random in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5] +src_info.r = (rand(dim, n_src) - 0.5); +targ_info = struct; +targ_info.r = (rand(dim, n_targ) - 0.5); + +% Source weights are random uniform in [0, 1] +mu = rand(n_src, 1); +K_exact = kern_0(src_info, targ_info); +target_vals = K_exact * mu; + +n_nbr = 10; + +% Loop through different tolerances and record errors and timings +tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08]; +n_tol_vals = size(tol_vals, 2); +error_vals = zeros(n_tol_vals, 1); +dx_vals = zeros(n_tol_vals, 1); +nbinpts_vals = zeros(n_tol_vals, 1); +nproxy_vals = zeros(n_tol_vals, 1); + +for i = 1:n_tol_vals + tol = tol_vals(i); + opts = struct('multi_shells', false); + [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr, opts); + % disp(grid_info); + [A_spread_s, sort_info_s ]= get_spread(kern_0, [], src_info, ... + grid_info, proxy_info); + [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... + grid_info, proxy_info); + + A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); + + k0hat = get_kernhat(kern_0,grid_info); + evals_approx = pcfft_apply(mu,A_spread_s,A_spread_t,A_addsub,k0hat); + + % Compute relative L infinity error + diffs = abs(evals_approx - target_vals); + rel_linf_error = max(diffs) / max(abs(target_vals)); + + % Save error and grid info. + error_vals(i) = rel_linf_error; + dx_vals(i) = grid_info.dx; + nbinpts_vals(i) = grid_info.nbinpts; + nproxy_vals(i) = proxy_info.nproxy; + + disp("tol: " + num2str(tol) + ", rel linf error: " + num2str(rel_linf_error)); + +end + +figure(1); +clf; +subplot(2,1,1); +plot(tol_vals(:), error_vals(:), '.-') +hold on +plot(tol_vals(:), tol_vals(:), '--') +xscale('log') +ylabel("Observed error") +yscale('log') +xlabel("Tolerance") +grid on; +subplot(2,1,2); +plot(tol_vals(:), nproxy_vals(:), '.-'); +hold on; +plot(tol_vals(:), nbinpts_vals(:), '.-'); +% plot(tol_vals(:), nspread_vals(:), '.-'); +legend("nproxy", "nbinpts"); +grid on; +xscale('log'); \ No newline at end of file diff --git a/pcfft/utils/dx_nproxy.m b/pcfft/utils/dx_nproxy.m index ffb0767..9f33b1f 100644 --- a/pcfft/utils/dx_nproxy.m +++ b/pcfft/utils/dx_nproxy.m @@ -168,6 +168,7 @@ nspread = nspread + 1; if mod(nspread,4) == 0 nshell = nshell + 1; + % disp("dx_nproxy: Increasing nshell to " + int2str(nshell) + ", nspread is " + int2str(nspread)); end nproxy = nspread^2; proxy_pts0 = get_sphere_points(nproxy, 1); diff --git a/pcfft/utils/interaction_radius.m b/pcfft/utils/interaction_radius.m index b505dae..ab462e7 100644 --- a/pcfft/utils/interaction_radius.m +++ b/pcfft/utils/interaction_radius.m @@ -12,5 +12,5 @@ % dx : scalar % The grid spacing in physical units. - rad = ceil(2 * proxy_info.radius / (grid_info.nspread * grid_info.dx)); + rad = ceil( proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); end \ No newline at end of file From 3a604666f8ade261d3022282d58183c1147fddfe Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Wed, 15 Apr 2026 12:06:40 -0400 Subject: [PATCH 12/14] Precomputing neighbor offsets --- Makefile | 5 + devtools/accuracy_tests/test_one_over_r_3D.m | 4 +- .../test_abstract_neighbor_spreading_2D.m | 6 +- .../test_abstract_neighbor_spreading_3D.m | 10 +- devtools/test/test_intersecting_bins_2d.m | 4 +- devtools/test/test_intersecting_bins_3d.m | 8 +- devtools/test/test_neighbor_template_2d.m | 12 +- notes/main.pdf | Bin 219247 -> 246733 bytes notes/main.tex | 140 ++++++++++++++---- pcfft/classes/GridInfo.m | 21 ++- pcfft/get_grid.m | 2 +- pcfft/utils/abstract_neighbor_spreading_2D.m | 4 +- pcfft/utils/abstract_neighbor_spreading_3D.m | 3 +- pcfft/utils/interaction_radius.m | 5 +- pcfft/utils/intersecting_bins_2d.m | 8 +- pcfft/utils/intersecting_bins_3d.m | 8 +- pcfft/utils/neighbor_bins_3d.m | 4 +- pcfft/utils/neighbor_template_2d.m | 2 +- pcfft/utils/neighbor_template_3d.m | 2 +- 19 files changed, 183 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index b0dac80..2496050 100644 --- a/Makefile +++ b/Makefile @@ -7,4 +7,9 @@ spread-notes: bibtex main.aux pdflatex main.tex pdflatex main.tex + open main.pdf + +quick: + cd ${NOTES_DIR} + pdflatex main.tex open main.pdf \ No newline at end of file diff --git a/devtools/accuracy_tests/test_one_over_r_3D.m b/devtools/accuracy_tests/test_one_over_r_3D.m index 100bc19..bb417e5 100644 --- a/devtools/accuracy_tests/test_one_over_r_3D.m +++ b/devtools/accuracy_tests/test_one_over_r_3D.m @@ -5,8 +5,8 @@ % Set up random source and target points rng(1); -n_src = 5000; -n_targ = 5017; +n_src = 1000; +n_targ = 1017; dim = 3; kern_0 = @(s,t) one_over_r_kernel(s,t); diff --git a/devtools/test/test_abstract_neighbor_spreading_2D.m b/devtools/test/test_abstract_neighbor_spreading_2D.m index f373fe8..9a67e9b 100644 --- a/devtools/test/test_abstract_neighbor_spreading_2D.m +++ b/devtools/test/test_abstract_neighbor_spreading_2D.m @@ -11,10 +11,12 @@ dx = 0.5; nbinpts = 2; nspread = 4; -grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); proxy_info = struct; proxy_info.radius = 0.5; +grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1, proxy_info.radius); + + [box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_2D(grid_info, proxy_info); @@ -154,6 +156,8 @@ text(center(1), center(2), int2str(nbr_binids(i)), 'FontSize', 8); end +close all; + % scatter(nbr_gridpts(1, :), nbr_gridpts(2, :), 'm.'); diff --git a/devtools/test/test_abstract_neighbor_spreading_3D.m b/devtools/test/test_abstract_neighbor_spreading_3D.m index bc3803f..ff1cf98 100644 --- a/devtools/test/test_abstract_neighbor_spreading_3D.m +++ b/devtools/test/test_abstract_neighbor_spreading_3D.m @@ -40,11 +40,13 @@ dx = 0.5; nbinpts = 2; nspread = 4; -grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); - proxy_info = struct; proxy_info.radius = 0.5; +grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1, proxy_info.radius); + + + [box_pts, spreading_template_pts, spreading_template_idxes] = abstract_neighbor_spreading_3D(grid_info, proxy_info); @@ -88,4 +90,6 @@ % All indices must be unique [~, uid] = unique(spreading_template_idxes.', 'rows'); assert(length(uid) == size(spreading_template_idxes, 2), ... - 'spreading_template_idxes must have no duplicate indices'); \ No newline at end of file + 'spreading_template_idxes must have no duplicate indices'); + +close all; \ No newline at end of file diff --git a/devtools/test/test_intersecting_bins_2d.m b/devtools/test/test_intersecting_bins_2d.m index 9c679eb..3fafef9 100644 --- a/devtools/test/test_intersecting_bins_2d.m +++ b/devtools/test/test_intersecting_bins_2d.m @@ -30,7 +30,7 @@ % valid bin_idxes should be between 0 and N_bin - 1 for bin_idx = 0:(N_bin - 1) - [idx_x, idx_y, binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); + [idx_x, idx_y, binids] = intersecting_bins_2d(bin_idx, grid_info); % Once we remove invalid binids, there should be no repeats. valid_binids = binids(binids >= 0); @@ -151,7 +151,7 @@ tol_dist = 1e-10; for bin_idx = 0:(N_bin_0c - 1) - [~, ~, binids] = intersecting_bins_2d(bin_idx, grid_info_0c, proxy_info_0c); + [~, ~, binids] = intersecting_bins_2d(bin_idx, grid_info_0c); c1 = bin_center(bin_idx, grid_info_0c); valid_returned = binids(binids >= 0); assert(length(valid_returned) == length(unique(valid_returned)), ... diff --git a/devtools/test/test_intersecting_bins_3d.m b/devtools/test/test_intersecting_bins_3d.m index cfb13eb..52de656 100644 --- a/devtools/test/test_intersecting_bins_3d.m +++ b/devtools/test/test_intersecting_bins_3d.m @@ -9,16 +9,18 @@ -1 1 -1 1]; -grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0); - % Spoof a proxy_info with a reasonable radius. proxy_info = struct; proxy_info.radius = 0.5; +grid_info = GridInfo(Lbd, dx, 2*nbinpts + 1, nbinpts, dim, 0, proxy_info.radius); + + + N_bin = grid_info.nbin(1) * grid_info.nbin(2) * grid_info.nbin(3); for bin_idx = 0:(N_bin - 1) - [~, ~, ~, binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); + [~, ~, ~, binids] = intersecting_bins_3d(bin_idx, grid_info); assert(length(binids) <= N_bin, ... sprintf('bin_idx %d: neighborhood size %d exceeds N_bin %d', ... bin_idx, length(binids), N_bin)); diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index fd1ee14..3e9d353 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -111,14 +111,16 @@ nbin = [3 3]; nspread = 2 * nbinpts ; -% Create a GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx, ngrid -grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1); - - % spoof the ProxyInfo object. Need radius proxy_info = struct; proxy_info.radius = 0.5; +% Create a GridInfo object. Need nbin, dx, Lbd, nspread, nbinpts, offset, dx, ngrid +grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, -1, proxy_info.radius); + + + + [sort_info] = SortInfo(struct('r', r), dx, Lbd, nbin, nbinpts); r_srt = sort_info.r_srt; binid_srt = sort_info.binid_srt; @@ -299,4 +301,4 @@ ylabel("y"); end -% close all; \ No newline at end of file +close all; \ No newline at end of file diff --git a/notes/main.pdf b/notes/main.pdf index f7bb64fbb8a539f0d715db1f0a83b223b8fdd212..cbe35a62da56dcd42ec01f13fd1afd4b8c81c202 100644 GIT binary patch delta 188120 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 AtpET3 delta 161780 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 @DqOrgOR+S=Mp{v~XD34>;$ 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 diff --git a/notes/main.tex b/notes/main.tex index 960ffe3..7779494 100644 --- a/notes/main.tex +++ b/notes/main.tex @@ -249,50 +249,132 @@ \subsection{Constructing the regular grid} \label{fig:padding} \end{figure} -\subsection{Computing the spreading matrix} +\section{Computing the spreading matrix} The spreading matrix $\boldsymbol{A}$ maps from weights on the source points to equivalent weights on the regular grid. This matrix is constructed in \texttt{get\_spread()}. -\subsection{Computing the addsub matrix} -\newcommand{\A}{\boldsymbol{A}} -\newcommand{\K}{\boldsymbol{K}} -\newcommand{\bmu}{\boldsymbol{\mu}} -Let's look at an N-body sum that we are trying to approximate. Suppose there are target points $\{x_i\}_{i=1,...}$ and source points $\{y_i\}_{j=1,...}$. -\begin{align} - u_i &= \sum_{j=1}^N K(x_i - y_j) \mu_j \label{eq:full_sum} \\ - &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \mu_j + \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j. -\end{align} -Ideally, we'd want to use the source point spreading matrix $\A^{(\text{src})}$ computed in the previous step to approximate the far interactions as -\begin{align*} - \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j \approx \sum_{k} K(x_i - z_k)\A^{(\text{src})}_{k, :} \bmu, -\end{align*} -where $z_k$ are the grid points. Let $\tilde{\boldsymbol{K}}$ be the kernel matrix from regular grid points to themselves and let $\A^{(\text{targ})}$ be the spreading matrix for the target points. We would then compute -\begin{align} - \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j = \left( \A^{(\text{targ})}\right)^\top \tilde{\boldsymbol{K}} \A^{(\text{src})} \boldsymbol{\mu}. -\end{align} +\todo{Describe the indexing of the spreading bins.} + +\todo{Describe the \texttt{SortInfo} object and data it contains.} + +\section{Computing the addsub matrix} + +\subsection{Defining the neighborhood} -However, the right hand sum contains all of the interactions, not just far interactions. So we need to compute a matrix $\boldsymbol{B}$ which approximates: +For a spreading bin $j$, the neighboring bins are all such bins whose associated spreading boxes intersect the ball bounded by the proxy radius. + +If we wanted to just include all bins which intersect the ball bounded by the proxy radius, we would use this cutoff in bin index space: \begin{align*} - \boldsymbol{B}_{i,:} \boldsymbol{\mu} &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \boldsymbol{\mu}_j - \sum_{k} \left( \A^{(\text{targ})}_{k, \text{Near}(i)} \right)^\top K(x_i - z_k)\A^{(\text{src})}_{k, \text{Near}(i)} \boldsymbol{\mu}_{\text{Near}(i)} \\ - % &= \boldsymbol{A}^{(\text{add})}_{i,:} \boldsymbol{\mu} - \boldsymbol{A}^{(\text{sub})}_{i,:}\boldsymbol{\mu} + \texttt{idx\_rad} = \lceil \frac{\texttt{proxy\_rad}}{\texttt{dx} \times \texttt{nbinpts}} \rceil. \end{align*} -where $\boldsymbol{A}^{(\text{add})}$ maps from source points to target points, and -$\boldsymbol{B}_{i,j}$ is equal to $K(x_i - y_j) - \left( \left( \A^{(\text{targ})} \right)^\top \tilde{\K} \A^{(\text{src})} \right)_{i,j}$ if $j\in \text{Near}(x_i)$ and zero otherwise. -Then we can evaluate \eqref{eq:full_sum} as: +To make sure we are accounting for the overlap of the spreading boxes, we will inflate by the max size of the margin: \begin{align*} - u_i &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \mu_j + \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j \\ - &= \boldsymbol{B}_{i,:} \boldsymbol{\mu} + \left( \A^{(\text{targ})}\right)^\top \tilde{\boldsymbol{K}} \A^{(\text{src})} \boldsymbol{\mu} + \texttt{idx\_rad} = \lceil \frac{\texttt{proxy\_rad} + \sqrt{d} \times \texttt{pad} \times \texttt{dx}}{\texttt{dx} \times \texttt{nbinpts}} \rceil. \end{align*} -where $\tilde{\boldsymbol{K}}$ is the kernel matrix from regular grid points to themselves. -Steps for computing this matrix: + + +\newcommand{\A}{\boldsymbol{A}} +\newcommand{\K}{\boldsymbol{K}} +\newcommand{\bmu}{\boldsymbol{\mu}} +% Let's look at an N-body sum that we are trying to approximate. Suppose there are target points $\{x_i\}_{i=1,...}$ and source points $\{y_i\}_{j=1,...}$. +% \begin{align} +% u_i &= \sum_{j=1}^N K(x_i - y_j) \mu_j \label{eq:full_sum} \\ +% &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \mu_j + \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j. +% \end{align} +% Ideally, we'd want to use the source point spreading matrix $\A^{(\text{src})}$ computed in the previous step to approximate the far interactions as +% \begin{align*} +% \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j \approx \sum_{k} K(x_i - z_k)\A^{(\text{src})}_{k, :} \bmu, +% \end{align*} +% where $z_k$ are the grid points. Let $\tilde{\boldsymbol{K}}$ be the kernel matrix from regular grid points to themselves and let $\A^{(\text{targ})}$ be the spreading matrix for the target points. We would then compute +% \begin{align} +% \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j = \left( \A^{(\text{targ})}\right)^\top \tilde{\boldsymbol{K}} \A^{(\text{src})} \boldsymbol{\mu}. +% \end{align} + + + +% However, the right hand sum contains all of the interactions, not just far interactions. So we need to compute a matrix $\boldsymbol{B}$ which approximates: +% \begin{align*} +% \boldsymbol{B}_{i,:} \boldsymbol{\mu} &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \boldsymbol{\mu}_j - \sum_{k} \left( \A^{(\text{targ})}_{k, \text{Near}(i)} \right)^\top K(x_i - z_k)\A^{(\text{src})}_{k, \text{Near}(i)} \boldsymbol{\mu}_{\text{Near}(i)} \\ +% % &= \boldsymbol{A}^{(\text{add})}_{i,:} \boldsymbol{\mu} - \boldsymbol{A}^{(\text{sub})}_{i,:}\boldsymbol{\mu} +% \end{align*} +% where $\boldsymbol{A}^{(\text{add})}$ maps from source points to target points, and +% $\boldsymbol{B}_{i,j}$ is equal to $K(x_i - y_j) - \left( \left( \A^{(\text{targ})} \right)^\top \tilde{\K} \A^{(\text{src})} \right)_{i,j}$ if $j\in \text{Near}(x_i)$ and zero otherwise. +% Then we can evaluate \eqref{eq:full_sum} as: +% \begin{align*} +% u_i &= \sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \mu_j + \sum_{j \not \in \text{Near}(x_i)} K(x_i - y_j) \mu_j \\ +% &= \boldsymbol{B}_{i,:} \boldsymbol{\mu} + \left( \A^{(\text{targ})}\right)^\top \tilde{\boldsymbol{K}} \A^{(\text{src})} \boldsymbol{\mu} +% \end{align*} +% where $\tilde{\boldsymbol{K}}$ is the kernel matrix from regular grid points to themselves. + +\subsection{Constructing the matrix} + +See the paper draft for the definition of the mathematical object we are trying to construct. + +The algorithm takes these steps \todo{Update this} \begin{itemize} + \item Form a "spreading template", centered at bin $i$, which is a list of bins $j$ such that $j \in \text{Near}(i)$. + \item Use this "spreading template" to form a kernel matrix $K_{\text{bin2temp}}$, which has columns indexing the regular gridpoints of spreading bin $i$ and rows indexing the regular gridpoints of the spreading template bins. \item For each bin $i$, return a list of each bin $j$ where the proxy circle of $i$ intersects with the proxy circle of $j$. \item Compute the exact near-field interactions $\sum_{j \in \text{Near}(x_i)} K(x_i - y_j) \mu_j$. \item Compute the contribution of the $\left( \A^{(\text{targ})}\right)^\top \tilde{\boldsymbol{K}} \A^{(\text{src})} \boldsymbol{\mu}$ term via indexing. \end{itemize} -\subsection{Derivatives} +\begin{figure} +\centering +% Parameters: change these two to adjust the drawing easily +\begin{tikzpicture} + % Simpler approach: set x and y to binSize (with units) so all coordinates + % in the picture can be integers. proxyRadius remains a simple numeric + % multiplier (in units of bins). + \def\binSize{2cm} % side length of one bin + \def\proxyRadius{1.1} % radius in multiples of binSize + \def\ibin{3} % 0-based column index of highlighted bin + \def\jbin{4} % 0-based row index of highlighted bin + + % Use scaled coordinate axes so that (1,1) is one bin. + \begin{scope}[x=\binSize,y=\binSize] + % Draw 7 x 8 grid of unit squares (each square is one bin) + \foreach \i in {0,...,6} { + \foreach \j in {0,...,7} { + \draw[black!50] (\i,\j) rectangle ++(1,1); + } + } + + % Center coordinate of bin i in scaled units + \coordinate (binCenter) at ({\ibin+0.5},{\jbin+0.5}); + + % Highlight bin i and label it (label slightly below center) + \filldraw[fill=orange!20, draw=black] (\ibin,\jbin) rectangle ++(1,1); + \node[font=\small] at ({\ibin+0.5},{\jbin+0.35}) {\textbf{bin} $(i_x, i_y)$}; + + % Green 5x5 box around bin i (now a 5x5 neighborhood) + \draw[green!60!black, very thick, rounded corners] (\ibin-2,\jbin-2) rectangle ++(5,5); + + % Dotted proxy circle around bin center. Radius is proxyRadius (in bins) + \draw[blue!70, dotted, thick] (binCenter) circle (\proxyRadius); + + % Proxy circles at bottom-center and bottom-right bins inside the 5x5 neighborhood + % Bottom row of the 5x5 neighborhood is at jbin-2. Bottom-center bin center: + \coordinate (bottomCenter) at ({\ibin+0.5},{\jbin-1.5}); + % Bottom-right bin center is at ibin+2.5, jbin-1.5 + \coordinate (bottomRight) at ({\ibin+2.5},{\jbin-1.5}); + \draw[blue!70, dotted, thick] (bottomCenter) circle (\proxyRadius); + \draw[blue!70, dotted, thick] (bottomRight) circle (\proxyRadius); + + \node[font=\small] at (bottomCenter) {\textbf{bin} $(i_x, i_y-2)$}; + \node[font=\small] at (bottomRight) {\textbf{bin} $(i_x-2, i_y-2)$}; + + % Arrow showing radius (in scaled units so text is easy) + \draw[<->, thin] (binCenter) -- ++(\proxyRadius,0) node[midway, above, font=\footnotesize] {Proxy radius}; + \end{scope} +\end{tikzpicture} + +\caption{A schematic of the spreading template. The spreading template is always a square, found by taking the $x$ and $y$ bin indices which are within $2 \frac{\texttt{proxy\_rad}}{\texttt{bin\_width}}$ of $(i_x, i_y)$.} +\label{fig:spreading_template} +\end{figure} + +\section{Derivatives} If we want to dipoles instead of point charges, we change the spreading matrix \begin{align*} diff --git a/pcfft/classes/GridInfo.m b/pcfft/classes/GridInfo.m index cd511a2..025d2c1 100644 --- a/pcfft/classes/GridInfo.m +++ b/pcfft/classes/GridInfo.m @@ -43,6 +43,11 @@ % center_bin : int % Linear index of a bin approximately at the center of the grid. % Computed as floor(nbin(1)/2) * nbin(2) + floor(nbin(2)/2). + % nbr_offsets : array [n_nbr, 1] + % Used for indexing neighboring bins. n_nbr is the number of spreading bins + % which are treated directly. + % idx_cutoff : int + % The number of bins in each direction which are treated directly. properties ngrid @@ -61,11 +66,11 @@ zero_bin zero_box center_bin + nbr_offsets + idx_cutoff end methods - function obj = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr) - - + function obj = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr, proxy_rad) bin_sidelen = dx * nbinpts; @@ -124,6 +129,14 @@ zero_box = [X(:).'; Y(:).'; Z(:).']; end + % Precompute a few things which depend on the proxy radius. + idx_cutoff = interaction_radius(struct('radius', proxy_rad), struct('nbinpts', nbinpts, 'dx', dx, 'rpad', pad, 'dim', dim)); + if dim == 2 + nbr_offsets = neighbor_offsets_2d(idx_cutoff); + elseif dim == 3 + nbr_offsets = neighbor_offsets_3d(idx_cutoff); + end + obj.ngrid = ngrid; obj.Lbd = Lbd; obj.dx = dx; @@ -140,6 +153,8 @@ obj.zero_bin = zero_bin; obj.zero_box = zero_box; obj.center_bin = floor(n_bin(1)/2) * n_bin(2) + floor(n_bin(2)/2); + obj.nbr_offsets = nbr_offsets; + obj.idx_cutoff = idx_cutoff; end end end diff --git a/pcfft/get_grid.m b/pcfft/get_grid.m index 56058c7..ed72b8c 100644 --- a/pcfft/get_grid.m +++ b/pcfft/get_grid.m @@ -80,6 +80,6 @@ [dx, nspread, nbinpts, proxy_info] = dx_nproxy(kernel, dim, tol, halfside, crad, multi_shells, proxy_der); - grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr); + grid_info = GridInfo(Lbd, dx, nspread, nbinpts, dim, n_nbr, proxy_info.radius); end diff --git a/pcfft/utils/abstract_neighbor_spreading_2D.m b/pcfft/utils/abstract_neighbor_spreading_2D.m index 281f12f..b2b6700 100644 --- a/pcfft/utils/abstract_neighbor_spreading_2D.m +++ b/pcfft/utils/abstract_neighbor_spreading_2D.m @@ -27,11 +27,11 @@ [box_pts, box_center] = grid_pts_for_box_2d(grid_info.center_bin, grid_info); % Neighborhood radius in bin-index units - rad = interaction_radius(proxy_info, grid_info); + % rad = interaction_radius(proxy_info, grid_info); % Collect spreading box points for every neighboring bin offset. all_pts = zeros(2, 0); - offsets = neighbor_offsets_2d(rad); + offsets = grid_info.nbr_offsets; for k = 1 : size(offsets, 2) shift = offsets(:, k) * grid_info.nbinpts * grid_info.dx; all_pts = [all_pts, box_pts + shift]; diff --git a/pcfft/utils/abstract_neighbor_spreading_3D.m b/pcfft/utils/abstract_neighbor_spreading_3D.m index 06e9eb9..9eec543 100644 --- a/pcfft/utils/abstract_neighbor_spreading_3D.m +++ b/pcfft/utils/abstract_neighbor_spreading_3D.m @@ -33,7 +33,8 @@ % Collect spreading box points for every neighboring bin offset. all_pts = zeros(3, 0); - offsets = neighbor_offsets_3d(rad); + % offsets = neighbor_offsets_3d(rad); + offsets = grid_info.nbr_offsets; for k = 1 : size(offsets, 2) shift = offsets(:, k) * grid_info.nbinpts * grid_info.dx; all_pts = [all_pts, box_pts + shift]; diff --git a/pcfft/utils/interaction_radius.m b/pcfft/utils/interaction_radius.m index ab462e7..19c3c85 100644 --- a/pcfft/utils/interaction_radius.m +++ b/pcfft/utils/interaction_radius.m @@ -12,5 +12,8 @@ % dx : scalar % The grid spacing in physical units. - rad = ceil( proxy_info.radius / (grid_info.nbinpts * grid_info.dx)); + % inflation = sqrt(grid_info.dim) * grid_info.rpad * grid_info.dx; + inflation = 0; + + rad = ceil( (proxy_info.radius + inflation)/(grid_info.nbinpts * grid_info.dx)); end \ No newline at end of file diff --git a/pcfft/utils/intersecting_bins_2d.m b/pcfft/utils/intersecting_bins_2d.m index 59415bb..836f00e 100644 --- a/pcfft/utils/intersecting_bins_2d.m +++ b/pcfft/utils/intersecting_bins_2d.m @@ -1,5 +1,4 @@ -function [id_xs, id_ys, binids] = intersecting_bins_2d(bin_idx, grid_info, ... - proxy_info) +function [id_xs, id_ys, binids] = intersecting_bins_2d(bin_idx, grid_info) % Given a set of bins which are described by grid_info, and a set of proxy % surfaces which are described by proxy_info, return id_xs and id_ys. The % product of these two sets of bins is the set of intersecting bin idxes. @@ -16,9 +15,10 @@ % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); % Radius in index space - rad = interaction_radius(proxy_info, grid_info); + % rad = interaction_radius(proxy_info, grid_info); - offsets = neighbor_offsets_2d(rad); + % offsets = neighbor_offsets_2d(rad); + offsets = grid_info.nbr_offsets; id_xs = id_x + offsets(1, :); id_ys = id_y + offsets(2, :); diff --git a/pcfft/utils/intersecting_bins_3d.m b/pcfft/utils/intersecting_bins_3d.m index 91d38f7..6245d9f 100644 --- a/pcfft/utils/intersecting_bins_3d.m +++ b/pcfft/utils/intersecting_bins_3d.m @@ -1,5 +1,4 @@ -function [id_xs, id_ys, id_zs, binids] = intersecting_bins_3d(bin_idx, grid_info, ... - proxy_info) +function [id_xs, id_ys, id_zs, binids] = intersecting_bins_3d(bin_idx, grid_info) % Given a set of bins which are described by grid_info, and a set of proxy % surfaces which are described by proxy_info, return id_xs and id_ys. The % product of these two sets of bins is the set of intersecting bin idxes. @@ -17,9 +16,10 @@ % disp("intersecting_bins_3d: For bin_idx " + int2str(bin_idx) + ... % ", id_x: " + int2str(id_x) + ", id_y: " + int2str(id_y)); - rad = interaction_radius(proxy_info, grid_info); + % rad = interaction_radius(proxy_info, grid_info); - offsets = neighbor_offsets_3d(rad); + % offsets = neighbor_offsets_3d(rad); + offsets = grid_info.nbr_offsets; id_xs = id_x + offsets(1, :); id_ys = id_y + offsets(2, :); id_zs = id_z + offsets(3, :); diff --git a/pcfft/utils/neighbor_bins_3d.m b/pcfft/utils/neighbor_bins_3d.m index f1ee597..1ecd6c6 100644 --- a/pcfft/utils/neighbor_bins_3d.m +++ b/pcfft/utils/neighbor_bins_3d.m @@ -1,6 +1,6 @@ -function [nbr_binids, nbr_grididxes] = neighbor_bins_3d(grid_info, proxy_info, bin_idx) +function [nbr_binids, nbr_grididxes] = neighbor_bins_3d(grid_info, bin_idx) - [int_idx_x, int_idx_y, int_idx_z, nbr_binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); + [int_idx_x, int_idx_y, int_idx_z, nbr_binids] = intersecting_bins_3d(bin_idx, grid_info); % disp("neighbor_template_2d: For bin_idx " + int2str(bin_idx) + ... diff --git a/pcfft/utils/neighbor_template_2d.m b/pcfft/utils/neighbor_template_2d.m index 5d93355..333a906 100644 --- a/pcfft/utils/neighbor_template_2d.m +++ b/pcfft/utils/neighbor_template_2d.m @@ -31,7 +31,7 @@ tmpl_pts = template_pts; tmpl_idxes = template_idxes; - [~, ~, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info, proxy_info); + [~, ~, nbr_binids] = intersecting_bins_2d(bin_idx, grid_info); % Compute the grid-index shift from center_bin to bin_idx. % cx, xy are the 2D bin coordinates of center_bin diff --git a/pcfft/utils/neighbor_template_3d.m b/pcfft/utils/neighbor_template_3d.m index 6ed4582..f834333 100644 --- a/pcfft/utils/neighbor_template_3d.m +++ b/pcfft/utils/neighbor_template_3d.m @@ -2,7 +2,7 @@ - [~, ~, ~,nbr_binids] = intersecting_bins_3d(bin_idx, grid_info, proxy_info); + [~, ~, ~,nbr_binids] = intersecting_bins_3d(bin_idx, grid_info); % Compute the grid-index shift from center_bin to bin_idx. % cx, cy, cz are the 3D bin coordinates of center_bin cz = mod(grid_info.center_bin, grid_info.nbin(3)); From 9d8e42c3e222f49421494ef231eb12779355e959 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Wed, 15 Apr 2026 12:24:27 -0400 Subject: [PATCH 13/14] Set interaction radius and fixed failing test --- devtools/accuracy_tests/test_log_2D.m | 2 +- devtools/test/test_get_addsub_2.m | 3 ++- docs/usage.rst | 4 ++-- docs/usage_normal_der.rst | 4 ++-- pcfft/utils/interaction_radius.m | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/devtools/accuracy_tests/test_log_2D.m b/devtools/accuracy_tests/test_log_2D.m index 297addb..8314e05 100644 --- a/devtools/accuracy_tests/test_log_2D.m +++ b/devtools/accuracy_tests/test_log_2D.m @@ -22,7 +22,7 @@ target_vals = K_exact * mu; tol = 1e-10; -n_nbr = 100; +n_nbr = 500; [grid_info, proxy_info] = get_grid(kern_0, src_info, targ_info, tol, n_nbr); disp("test_log_2D: grid_info:"); disp(grid_info); diff --git a/devtools/test/test_get_addsub_2.m b/devtools/test/test_get_addsub_2.m index 40ace15..61cb09d 100644 --- a/devtools/test/test_get_addsub_2.m +++ b/devtools/test/test_get_addsub_2.m @@ -75,7 +75,7 @@ assert(all(~isnan(A_spread_t(:)))); assert(all(~isinf(A_spread_t(:)))); -A_addsub = get_addsub(k, k, src_info, targ_info, grid_info, ... +A_addsub = get_addsub(k, k, grid_info, ... proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); % % A_addsub = A_add - A_sub; @@ -124,3 +124,4 @@ % assert(errors_at_target < tol); +close all; diff --git a/docs/usage.rst b/docs/usage.rst index 33388db..d14715f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -65,8 +65,8 @@ The matrices ``A_spread_src`` and ``A_spread_targ`` take care of the far-field i .. code:: matlab - A_addsub = get_addsub(@kern, [], src_info, targ_info, grid_info, ... - proxy_info, srt_info_src, srt_info_targ, ... + A_addsub = get_addsub(@kern, [], grid_info, proxy_info, ... + srt_info_src, srt_info_targ, ... A_spread_src, A_spread_targ); Finally, we can evaluate the sum by calling :func:`get_kernhat` and :func:`pcfft_apply`: diff --git a/docs/usage_normal_der.rst b/docs/usage_normal_der.rst index d352be2..471f902 100644 --- a/docs/usage_normal_der.rst +++ b/docs/usage_normal_der.rst @@ -90,8 +90,8 @@ When we call this function, we need to provide the free-space kernel and the ker .. code:: matlab - A_addsub = get_addsub(@kern, @kern_s, src_info, targ_info, grid_info, ... - proxy_info, srt_info_src, srt_info_targ, ... + A_addsub = get_addsub(@kern, @kern_s, grid_info, proxy_info, ... + srt_info_src, srt_info_targ, ... A_spread_src, A_spread_targ); Now that we have finished our precomputations, we can evaluate the sum by calling :func:`pcfft_apply`: diff --git a/pcfft/utils/interaction_radius.m b/pcfft/utils/interaction_radius.m index 19c3c85..399b728 100644 --- a/pcfft/utils/interaction_radius.m +++ b/pcfft/utils/interaction_radius.m @@ -12,8 +12,8 @@ % dx : scalar % The grid spacing in physical units. - % inflation = sqrt(grid_info.dim) * grid_info.rpad * grid_info.dx; - inflation = 0; + inflation = sqrt(grid_info.dim) * grid_info.rpad * grid_info.dx; + % inflation = 0; rad = ceil( (proxy_info.radius + inflation)/(grid_info.nbinpts * grid_info.dx)); end \ No newline at end of file From f6cfbf40a0761334b124eac8b659e021f273d946 Mon Sep 17 00:00:00 2001 From: Owen Melia Date: Wed, 15 Apr 2026 13:56:28 -0400 Subject: [PATCH 14/14] Visually corrected interaction radius and fixed failing tests --- .../convergence_plots/end_to_end_log_2D.m | 2 +- .../convergence_plots/end_to_end_log_3D.m | 2 +- .../end_to_end_one_over_r_2D.m | 2 +- .../end_to_end_one_over_r_3D.m | 4 +- devtools/test/test_intersecting_bins_3d.m | 7 +- devtools/test/test_neighbor_template_2d.m | 25 +++---- devtools/test/test_neighbor_template_2d_1.m | 69 ++++++++++++++++++- pcfft/utils/interaction_radius.m | 4 +- pcfft/utils/neighbor_offsets_2d.m | 2 +- pcfft/utils/neighbor_offsets_3d.m | 4 +- 10 files changed, 90 insertions(+), 31 deletions(-) diff --git a/devtools/convergence_plots/end_to_end_log_2D.m b/devtools/convergence_plots/end_to_end_log_2D.m index 26042e1..b359339 100644 --- a/devtools/convergence_plots/end_to_end_log_2D.m +++ b/devtools/convergence_plots/end_to_end_log_2D.m @@ -40,7 +40,7 @@ [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); - A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + A_addsub = get_addsub(kern_0, [], ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); k0hat = get_kernhat(kern_0,grid_info); diff --git a/devtools/convergence_plots/end_to_end_log_3D.m b/devtools/convergence_plots/end_to_end_log_3D.m index a21862e..6d358e4 100644 --- a/devtools/convergence_plots/end_to_end_log_3D.m +++ b/devtools/convergence_plots/end_to_end_log_3D.m @@ -41,7 +41,7 @@ [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); - A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + A_addsub = get_addsub(kern_0, [], ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); k0hat = get_kernhat(kern_0,grid_info); diff --git a/devtools/convergence_plots/end_to_end_one_over_r_2D.m b/devtools/convergence_plots/end_to_end_one_over_r_2D.m index 21dd61b..c5ee60c 100644 --- a/devtools/convergence_plots/end_to_end_one_over_r_2D.m +++ b/devtools/convergence_plots/end_to_end_one_over_r_2D.m @@ -41,7 +41,7 @@ [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); - A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + A_addsub = get_addsub(kern_0, [], ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); k0hat = get_kernhat(kern_0,grid_info); diff --git a/devtools/convergence_plots/end_to_end_one_over_r_3D.m b/devtools/convergence_plots/end_to_end_one_over_r_3D.m index adc3a59..76706ae 100644 --- a/devtools/convergence_plots/end_to_end_one_over_r_3D.m +++ b/devtools/convergence_plots/end_to_end_one_over_r_3D.m @@ -21,7 +21,7 @@ K_exact = kern_0(src_info, targ_info); target_vals = K_exact * mu; -n_nbr = 10; +n_nbr = 50; % Loop through different tolerances and record errors and timings tol_vals = [1e-02 1e-03 1e-04 1e-05 1e-06 1e-07 1e-08]; @@ -41,7 +41,7 @@ [A_spread_t, sort_info_t ]= get_spread(kern_0, [], targ_info, ... grid_info, proxy_info); - A_addsub = get_addsub(kern_0, [], src_info, targ_info, ... + A_addsub = get_addsub(kern_0, [], ... grid_info, proxy_info, sort_info_s, sort_info_t, A_spread_s, A_spread_t); k0hat = get_kernhat(kern_0,grid_info); diff --git a/devtools/test/test_intersecting_bins_3d.m b/devtools/test/test_intersecting_bins_3d.m index 52de656..607b47a 100644 --- a/devtools/test/test_intersecting_bins_3d.m +++ b/devtools/test/test_intersecting_bins_3d.m @@ -21,9 +21,10 @@ for bin_idx = 0:(N_bin - 1) [~, ~, ~, binids] = intersecting_bins_3d(bin_idx, grid_info); - assert(length(binids) <= N_bin, ... + + valid_binids = binids(binids >= 0); + assert(length(valid_binids) <= N_bin, ... sprintf('bin_idx %d: neighborhood size %d exceeds N_bin %d', ... - bin_idx, length(binids), N_bin)); + bin_idx, length(valid_binids), N_bin)); end -disp('test_intersecting_bins_3d: PASSED'); diff --git a/devtools/test/test_neighbor_template_2d.m b/devtools/test/test_neighbor_template_2d.m index 3e9d353..45cb8b0 100644 --- a/devtools/test/test_neighbor_template_2d.m +++ b/devtools/test/test_neighbor_template_2d.m @@ -38,6 +38,7 @@ assert(all(nbr_grididxes >= 1 & nbr_grididxes <= dummy_idx)); % Make a figure with the grid points labeled by their index. +figure(1); scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); hold on; text(grid_info.r(1,:), grid_info.r(2,:), string(1:size(grid_info.r, 2)), 'Color', 'k'); @@ -68,26 +69,14 @@ assert(dist < 1e-12); end -% OJM: deleting this assert statement because it was written when we computed -% the neighbor template as a rectangle, now we use a circle. -% n_x = sqrt(length(nbr_binids)); -% expected_n_pts = n_x * grid_info.nbinpts + 2 * grid_info.rpad; -% disp("test_neighbor_template_2d: expected_n_pts: "); -% disp(expected_n_pts^2); -% disp("test_neighbor_template_2d: nbr_gridpts size: "); -% disp(size(nbr_gridpts)); -% disp("test_neighbor_template_2d: nbr_grididxes size: "); -% disp(size(nbr_grididxes)); -% assert(size(nbr_gridpts, 2) == expected_n_pts^2); -% assert(size(nbr_grididxes, 2) == expected_n_pts^2); - %% test_0b % Larger test case with visualization. Same setup as test_SortInfo_2d_1 +figure(2); n_pts = 100000; L = 2.0; @@ -232,13 +221,15 @@ end -close all; +% close all; %% test_0c % Check that for a given A_spread_s matrix, indexing it with the % nbr_grididxes gets all of the relevant entries for a given bin_idx. rad = 2.0; +figure(3); + % Set up two source points rng(4); n_src = 2; @@ -296,9 +287,9 @@ % scatter(grid_info.r(1,:), grid_info.r(2,:), 10, 'b'); % hold on; % scatter(valid_nbr_gridpts(1,:), valid_nbr_gridpts(2,:), 20, 'rx'); - title("Bin idx " + int2str(bin_idx)); - xlabel("x"); - ylabel("y"); + % title("Bin idx " + int2str(bin_idx)); + % xlabel("x"); + % ylabel("y"); end close all; \ No newline at end of file diff --git a/devtools/test/test_neighbor_template_2d_1.m b/devtools/test/test_neighbor_template_2d_1.m index 1891164..fd12de0 100644 --- a/devtools/test/test_neighbor_template_2d_1.m +++ b/devtools/test/test_neighbor_template_2d_1.m @@ -158,7 +158,72 @@ % Assert that # binids returned = # grid pts returned = # grid idxes returned assert(length(nbr_grididxes) == size(nbr_gridpts, 2)); - % Assert that the grid points are within the valid range unless marked + % Assert that the grid points are within the valid range unless marked % with a dummy idx. -end \ No newline at end of file +end + +%% Part 3: Plot neighbor template and proxy circle for a particular bin + +n_src_3 = 10000; +n_targ_3 = 5017; +dim_3 = 2; + +kern_3 = @(s,t) log_kernel(s,t); +src_info_3 = struct; +src_info_3.r = (rand(dim_3, n_src_3) - 0.5); +targ_info_3 = struct; +targ_info_3.r = (rand(dim_3, n_targ_3) - 0.5); + +tol_3 = 1e-8; +n_nbr_3 = 100; +[grid_info_3, proxy_info_3] = get_grid(kern_3, src_info_3, targ_info_3, tol_3, n_nbr_3); + +% Use the center bin as the bin to plot +plot_bin_idx = grid_info_3.center_bin; + +% Compute the neighbor template for the chosen bin +[~, tmpl_pts_3, tmpl_idxes_3] = abstract_neighbor_spreading_2D(grid_info_3, proxy_info_3); +[~, nbr_gridpts_3, nbr_grididxes_3] = neighbor_template_2d(grid_info_3, proxy_info_3, plot_bin_idx, tmpl_pts_3, tmpl_idxes_3); + +% Discard out-of-bounds dummy points +valid_mask_3 = nbr_grididxes_3 <= grid_info_3.ngrid(1) * grid_info_3.ngrid(2); +valid_nbr_gridpts_3 = nbr_gridpts_3(:, valid_mask_3); + +% Center of the chosen bin and proxy radius +bin_ctr_3 = bin_center(plot_bin_idx, grid_info_3); +proxy_rad_3 = proxy_info_3.radius; + +% Collect all bin centers +n_bins_3 = grid_info_3.nbin(1) * grid_info_3.nbin(2); +all_bin_ctrs_3 = zeros(2, n_bins_3); +for bi = 0 : n_bins_3 - 1 + all_bin_ctrs_3(:, bi+1) = bin_center(bi, grid_info_3); +end + +% Proxy circle via get_ring_points +proxy_circle_3 = get_ring_points(300, proxy_rad_3, bin_ctr_3); + + +% Second proxy circle inflated by the amount used in interaction_radius +inflation_3 = sqrt(grid_info_3.dim) * grid_info_3.rpad * grid_info_3.dx; +proxy_circle_inflated_3 = get_ring_points(300, proxy_rad_3 + inflation_3, bin_ctr_3); + +figure; +% Regular grid points first, in black +plot(grid_info_3.r(1,:), grid_info_3.r(2,:), 'k.', 'MarkerSize', 3); +hold on; +% Neighbor template grid points in red +plot(valid_nbr_gridpts_3(1,:), valid_nbr_gridpts_3(2,:), 'ro', 'MarkerSize', 6, 'LineWidth', 1.5); +% All bin centers as stars +plot(all_bin_ctrs_3(1,:), all_bin_ctrs_3(2,:), 'g*', 'MarkerSize', 6, 'LineWidth', 1); +% Chosen bin center highlighted +plot(bin_ctr_3(1), bin_ctr_3(2), 'b*', 'MarkerSize', 12, 'LineWidth', 2); +% Proxy circle +plot([proxy_circle_3(1,:), proxy_circle_3(1,1)], [proxy_circle_3(2,:), proxy_circle_3(2,1)], 'b-', 'LineWidth', 2); +plot([proxy_circle_inflated_3(1,:), proxy_circle_inflated_3(1,1)], [proxy_circle_inflated_3(2,:), proxy_circle_inflated_3(2,1)], 'b--', 'LineWidth', 2); +hold off; +axis equal; +title(sprintf('Neighbor template for bin %d', plot_bin_idx)); +legend('Regular grid pts', 'Neighbor template pts', 'All bin centers', 'Chosen bin center', 'Proxy circle', 'Location', 'best'); +close all; \ No newline at end of file diff --git a/pcfft/utils/interaction_radius.m b/pcfft/utils/interaction_radius.m index 399b728..a44e128 100644 --- a/pcfft/utils/interaction_radius.m +++ b/pcfft/utils/interaction_radius.m @@ -12,8 +12,10 @@ % dx : scalar % The grid spacing in physical units. + + % This inflation amount is validated by inspecting the figure generated by + % test_neighbor_template_2d_1 part 3. inflation = sqrt(grid_info.dim) * grid_info.rpad * grid_info.dx; - % inflation = 0; rad = ceil( (proxy_info.radius + inflation)/(grid_info.nbinpts * grid_info.dx)); end \ No newline at end of file diff --git a/pcfft/utils/neighbor_offsets_2d.m b/pcfft/utils/neighbor_offsets_2d.m index d6cec14..580443a 100644 --- a/pcfft/utils/neighbor_offsets_2d.m +++ b/pcfft/utils/neighbor_offsets_2d.m @@ -16,7 +16,7 @@ offsets = zeros(2, 0); for delta_x = -rad : rad - delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); + delta_y_max = floor(sqrt(rad^2 - delta_x^2)); delta_ys = -delta_y_max : delta_y_max; offsets = [offsets, [delta_x * ones(1, numel(delta_ys)); delta_ys]]; end diff --git a/pcfft/utils/neighbor_offsets_3d.m b/pcfft/utils/neighbor_offsets_3d.m index 7315ac3..76e401b 100644 --- a/pcfft/utils/neighbor_offsets_3d.m +++ b/pcfft/utils/neighbor_offsets_3d.m @@ -16,11 +16,11 @@ offsets = zeros(3, 0); for delta_x = -rad : rad - delta_y_max = ceil(sqrt(rad^2 - delta_x^2)); + delta_y_max = floor(sqrt(rad^2 - delta_x^2)); for delta_y = -delta_y_max : delta_y_max r2 = rad^2 - delta_x^2 - delta_y^2; if r2 < 0; continue; end - delta_z_max = ceil(sqrt(r2)); + delta_z_max = floor(sqrt(r2)); delta_zs = -delta_z_max : delta_z_max; offsets = [offsets, [delta_x * ones(1, numel(delta_zs)); delta_y * ones(1, numel(delta_zs)); delta_zs]]; end