From dfae9d45af4551e38581136b3d397204f1a4ea35 Mon Sep 17 00:00:00 2001 From: Tilo Arens <tilo.arens@kit.edu> Date: Wed, 18 Jan 2023 09:15:12 +0100 Subject: [PATCH] Program files to produce the plots added to repository. --- src/indicator_func.m | 70 +++++++++++++++++++ src/operator_N.m | 148 +++++++++++++++++++++++++++++++++++++++++ src/operator_T_B.m | 118 ++++++++++++++++++++++++++++++++ src/plot_of_figure_1.m | 52 +++++++++++++++ src/plot_of_figure_2.m | 49 ++++++++++++++ src/plot_of_figure_3.m | 52 +++++++++++++++ 6 files changed, 489 insertions(+) create mode 100644 src/indicator_func.m create mode 100644 src/operator_N.m create mode 100644 src/operator_T_B.m create mode 100644 src/plot_of_figure_1.m create mode 100644 src/plot_of_figure_2.m create mode 100644 src/plot_of_figure_3.m diff --git a/src/indicator_func.m b/src/indicator_func.m new file mode 100644 index 0000000..e34c7cd --- /dev/null +++ b/src/indicator_func.m @@ -0,0 +1,70 @@ +function [indicat, Xi1, Xi2] = indicator_func(op_N, alpha, k_out, H, R, x_max, mesh_step, a, q_positive) +% +% Compute the indicator function for finding an obstacle for a transmission +% problem in a Neumann waveguide of height H. The wave number in the +% waveguide is k_out. +% +% The near field measurements, which are taken on vertikal lines at +-R, +% are contained in the matrix operator_N. +% +% The function creates a grid of points on [-x_max, xmax] x [0, H] with +% mesh size mesh_step. For each grid point, the operator T_B is computed for +% B equal to a square with lateral length 2a centered at the grid point. +% The number of negative eigenvalues of the matrix +% +% Re(N) - alpha T_B +% +% is computed. This the value of the indicator for this point. +% +% The parameter q_positive should be true for the case q > 0, it should be +% false for the case -1 < q <= 0. + +% threshold for negative eigenvalues. For now we define this as a constant. +if q_positive + delta = -2e-6; % for figure 1 + %delta = -2e-6; % for figure 2 +else + delta = 2e-6; % for figure 3 +end + +% obtain the number of source/receiver points from the dimension of +% operator_N +M = size(op_N, 1) / 2 - 1; + +% grid in x1 direction: x_max_true is the largest value such that the +% square centred at that x_1 coordinate is completely inside the rectangle +% [-x_max, x_max] x [0, H] +N_x1 = floor(2*(x_max-a) / mesh_step); +x_max_true = mesh_step * N_x1 / 2; +x1 = linspace(-x_max_true, x_max_true, N_x1+1); + +% grid in x2 direction. The centers of the first and last pixels are +% x2_offset away from the waveguide boundary. +bound_gap = 0.05; +N_x2 = floor((H-2*a-2*bound_gap) / mesh_step); +x2_offset = (H - N_x2*mesh_step) / 2; +x2 = linspace(x2_offset, H-x2_offset, N_x2+1); + +[Xi1, Xi2] = meshgrid(x1, x2); +indicat = zeros(size(Xi1)); + +real_N = (op_N + op_N') / 2; + +for j=1:N_x1+1 + fprintf('.'); + for l=1:N_x2+1 + T_B = operator_T_B(H, k_out, R, Xi1(l, j), Xi2(l, j), a, M); + the_op = real_N - alpha * T_B; + eigenvals = eig(the_op); + if q_positive + indicat(l, j) = sum(eigenvals < delta); + else + indicat(l, j) = sum(eigenvals > delta); + end + end +end +fprintf('\n\n'); + + +end + diff --git a/src/operator_N.m b/src/operator_N.m new file mode 100644 index 0000000..b41dc40 --- /dev/null +++ b/src/operator_N.m @@ -0,0 +1,148 @@ +function N_mat_ct = operator_N(H, curve, k_out, k_in, R, M, N, N_quad_ct, debug_mode) +% +% The function generates a discretization for the near field operator N for +% a penetrable obstacle in a waveguide with Neumann boundary conditions. +% +% The waveguide is \R \times (0, 'H'). The obstacle is given by the boundary +% curve 'curve' and should be a BIEPack.geom.ParamCurve instance. It must +% be contained in the waveguide. +% +% The numbers 'k_out' and 'k_in' are the wavenumbers outside and inside the +% obstacle, respectively. The positive number 'R' specifes the +% x_1-coordinate of the vertical lines containing the source and receiver +% points. +% +% The space L^2(C_R) is discretized using the basis induced by the Neumann +% eigenfunctions of the Laplacian on (0, H). If one of these are used as +% the density in the conjugated single layer potential, the series for the +% Green's function reduces to just one term. We use the first M+1 +% eigenfunctions, hence the returned matrix will be of size 2M+2 x 2M+2. +% +% To compute the coefficients of the scattered field with respect to the +% basis, the composite trapezoidal rule with M+1 points is used on each +% vertical line. As the field can be extended as an even function for +% negative x_2 to a 2H-periodic function, the rule is highly accurate. +% +% The parameter 'N' specifies the number of unknowns to use in the boundary +% integral equation method. +% +% The parameter debug_mode is optional and defaults to false. If it is +% true, some tests whether the boundary data is correct, are carried out. + +% set default value of parameters +if (nargin < 8) + N_quad_ct = 80; +end + +if (nargin < 9) + debug_mode = false; +end + +% shorten names of BIEPack classes +import BIEPack.* + +% allocate memory for the output and define source/receiver positions +%N_mat_gl = zeros(2*M+2, 2*M+2); +N_mat_ct = zeros(2*M+2, 2*M+2); + +% quadrature points on C_R and corresponding weights for evaluating scalar +% products with basis functions +y_quad_ct = linspace(0, H, N_quad_ct+1).'; +w_ct = H/N_quad_ct * ones(1, N_quad_ct+1); +w_ct([1, N_quad_ct+1]) = 0.5 * w_ct([1, N_quad_ct+1]); + +%[y_quad_gl, w_gl] = BIEPack.utils.quadGaussLegendre(40, 0, H); +%y_quad_gl = y_quad_gl.'; + +%y1_gl = [-R * ones(size(y_quad_gl)); R * ones(size(y_quad_gl))]; +%y2_gl = [y_quad_gl; y_quad_gl]; +y1_ct = [-R * ones(size(y_quad_ct)); R * ones(size(y_quad_ct))]; +y2_ct = [y_quad_ct; y_quad_ct]; + + +% define the mesh +mesh = meshes.ParamCurveMesh(curve, N); +space = spaces.TrigPol(mesh); + +% define all the integral operators +slop_ext = ops.HelmholtzNwgSLOp(space, space, k_out, H); +dlop_ext = ops.HelmholtzNwgDLOp(space, space, k_out, H); +adlop_ext = ops.HelmholtzNwgADLOp(space, space, k_out, H); +slop_int = ops.HelmholtzSLOp(space, space, k_in); +dlop_int = ops.HelmholtzDLOp(space, space, k_in); +adlop_int = ops.HelmholtzADLOp(space, space, k_in); +hsing_dif_op = ops.HelmholtzNwgHsingDifOp(space, space, k_out, k_in, H); +slpot = ops.HelmholtzNwgSLPot(space, space, k_out, H); +dlpot = ops.HelmholtzNwgDLPot(space, space, k_out, H); + +% Define the block operator A in the IE system (I - A) phi = psi in +% the Kress-Roach integral equation formulation for a transmission problem. +A = {(-1)*dlop_ext + dlop_int, slop_ext + (-1)*slop_int; + (-1)*hsing_dif_op, adlop_ext + (-1)*adlop_int}; + +% Set up the Nyström method for solving the IE system +nystroemMethod = methods.NystroemPeriodicLog(); +nystroemMethod.setOperators(A); + +if (debug_mode) + % Operators for checking that the boundary data is sensible. It + % corresponds to Cauchy data of a solution of the Helmholtz equation in + % the obstacle, so it should be in the kernel of the Calderon projector. + slop_ff_out = ops.HelmholtzSLOp(space, space, k_out); + dlop_ff_out = ops.HelmholtzDLOp(space, space, k_out); + calderon_dir = 2.0*dlop_ff_out; + calderon_neu = (-2.0)*slop_ff_out; + calderon_dir_mat = calderon_dir.getImplementation('NystroemPeriodicLog'); + calderon_neu_mat = calderon_neu.getImplementation('NystroemPeriodicLog'); + fprintf('Exterior wave number is %6.4f\n', k_out); +end + +% for each source point, assign right hand side and solve +import BIEPack.utils.PeriodicGreensFunc +for j=1:2*M+2 + mesh.evalDerivs(1); + + k_n = mod(j-1, M+1) * pi / H; + beta_n_conj = sqrt(k_out^2 - k_n^2)'; + + if j == 1 + ui_dirichlet = -0.5i / (sqrt(H)*beta_n_conj) * exp(-1i * beta_n_conj * (R + mesh.nodesX.')); + ui_neumann = -0.5 / sqrt(H) * exp(-1i * beta_n_conj * (R + mesh.nodesX.')) .* mesh.normalAtNodeX.'; + elseif 2 <= j && j <= M+1 + ui_dirichlet = -0.5i / (sqrt(H/2)*beta_n_conj) * exp(-1i * beta_n_conj * (R + mesh.nodesX.')) .* cos(k_n * mesh.nodesY.'); + ui_neumann = -0.5 / sqrt(H/2) * exp(-1i * beta_n_conj * (R + mesh.nodesX.')) ... + .* (cos(k_n * mesh.nodesY.') .* mesh.normalAtNodeX.' - 1i * k_n / beta_n_conj * sin(k_n * mesh.nodesY.') .* mesh.normalAtNodeY.'); + elseif j == M+2 + ui_dirichlet = -0.5i / (sqrt(H)*beta_n_conj) * exp(-1i * beta_n_conj * (R - mesh.nodesX.')); + ui_neumann = 0.5 / sqrt(H) * exp(-1i * beta_n_conj * (R - mesh.nodesX.')) .* mesh.normalAtNodeX.'; + else + ui_dirichlet = -0.5i / (sqrt(H/2)*beta_n_conj) * exp(-1i * beta_n_conj * (R - mesh.nodesX.')) .* cos(k_n * mesh.nodesY.'); + ui_neumann = 0.5 / sqrt(H/2) * exp(-1i * beta_n_conj * (R - mesh.nodesX.')) ... + .* (cos(k_n * mesh.nodesY.') .* mesh.normalAtNodeX.' + 1i * k_n / beta_n_conj * sin(k_n * mesh.nodesY.') .* mesh.normalAtNodeY.'); + end + + if (debug_mode) + % check that the boundary data is sensible. + residual = ui_dirichlet + calderon_dir_mat * ui_dirichlet + calderon_neu_mat * ui_neumann; + fprintf('mode = %2d, k_n = %6.4f, boundary data Calderon residual: %e\n', j, k_n, norm(residual)); + end + + nystroemMethod.setRhs({-ui_dirichlet; -ui_neumann}); + phi = nystroemMethod.apply; + + u_scat_ct = dlpot.evalPotential(y1_ct, y2_ct, phi(1:N)) - slpot.evalPotential(y1_ct, y2_ct, phi(N+1:2*N)); + + theta_l = 1/sqrt(H) * ones(size(y_quad_ct)); + N_mat_ct(1, j) = w_ct * (theta_l .* u_scat_ct(1:length(y_quad_ct))); + N_mat_ct(M+2, j) = w_ct * (theta_l .* u_scat_ct(length(y_quad_ct)+1:end)); + for l=2:M+1 + k_l = (l-1)*pi/H; + theta_l = sqrt(2/H) * cos(k_l * y_quad_ct); + N_mat_ct(l, j) = w_ct * (theta_l .* u_scat_ct(1:length(y_quad_ct))); + N_mat_ct(M+1+l, j) = w_ct * (theta_l .* u_scat_ct(length(y_quad_ct)+1:end)); + end +end + +% fprintf('Difference %e\n', norm(N_mat_gl - N_mat_ct)); + +end \ No newline at end of file diff --git a/src/operator_T_B.m b/src/operator_T_B.m new file mode 100644 index 0000000..3db3d2f --- /dev/null +++ b/src/operator_T_B.m @@ -0,0 +1,118 @@ +function T_B = operator_T_B(H, k_out, R, xi1, xi2, a, M) +% +% Set up the operator T_B where B is a square centered at (xi_1, xi_2) with +% lateral length 2a. The operator is discretized by using the basis in +% L^2(C_R) induced by the Neumann eigenfunctions of the Laplacian, +% +% \theta_n(t) = sqrt(2/H) cos( n pi / H * t ) for n \geq 1 +% \theta_0(t) = sqrt(1/H) +% +% The corresponding basis is +% +% g^1_n = ( theta_n, 0 ) , g^2_n = ( 0, theta_n ) +% +% where the first component function is defined on C_{-, R}, the second one +% on C_{+, R}. Fixing the number M of the highest eigen function used, the +% resulting matrix is of size 2M+2 x 2M+2 and contains the scalar products +% +% A^(pq)_{jn} = ( T_B g^q_n , g^p_j )_{L^2(0, H)} p,q = 1,2, j,n = 0,...,M. +% +% As the Green's function is also expanded in the theta_n, only one term in +% the double series in the expression of T_B remains. This still envolves +% an integral over two exponential functions. This is computed by a +% Gauss-Legendre quadrature rule. + +T_B = zeros(2*M+2, 2*M+2); + +k_n = (0:M) * pi / H; +beta_n = sqrt(k_out^2 - k_n.^2).'; +max_real_index = sum(imag(beta_n) == 0); + +% % The number of Gauss-Legendre points for quadratur with respect to y_1 +% N_gauss = 15; +% [y1, w] = BIEPack.utils.quadGaussLegendre(N_gauss, xi1-a, xi1+a); +% W = diag(w); +% +% pre_factors = 0.25 * k_out^2 ./ beta_n ./ beta_n' .* gamma_n_m_eval(M, k_n, xi2, a, H); +% exp_plus = exp(1i * beta_n * (R + y1)); +% exp_minus = exp(1i * beta_n * (R - y1)); +% +% T_B(1:M+1, 1:M+1) = exp_plus * W * exp_plus' .* pre_factors; +% T_B(1:M+1, M+2:end) = exp_plus * W * exp_minus' .* pre_factors; +% T_B(M+2:end, 1:M+1) = exp_minus * W * exp_plus' .* pre_factors; +% T_B(M+2:end, M+2:end) = exp_minus * W * exp_minus' .* pre_factors; + +one_vec = ones(size(beta_n)); +gamma_n_m = gamma_n_m_eval(M, k_n, xi2, a, H); +pre_factors = 0.25 * k_out^2 ./ beta_n ./ beta_n' .* gamma_n_m ... + .* (exp(1i * R * (beta_n * one_vec.' - one_vec * beta_n'))); + +% block nu = mu = 1 +denom = 1i * (beta_n * one_vec.' - one_vec * beta_n'); +exp_plus = exp(denom * (xi1 + a)); +exp_minus = exp(denom * (xi1 - a)); +T_B_block = (exp_plus - exp_minus) ./ denom .* pre_factors; +diag_indeces = (1:M+2:(M+2)*(max_real_index-1)+1).'; +T_B_block(diag_indeces) = 0.5 * a * k_out^2 ./ beta_n(1:max_real_index).^2 .* gamma_n_m(diag_indeces); +T_B(1:M+1, 1:M+1) = T_B_block; + +% block nu = 2, mu = 2 +denom = -1i * (beta_n * one_vec.' - one_vec * beta_n'); +exp_plus = exp(denom * (xi1 + a)); +exp_minus = exp(denom * (xi1 - a)); +T_B_block = (exp_plus - exp_minus) ./ denom .* pre_factors; +T_B_block(diag_indeces) = 0.5 * a * k_out^2 ./ beta_n(1:max_real_index).^2 .* gamma_n_m(diag_indeces); +T_B(M+2:end, M+2:end) = T_B_block; + +% block nu = 1, mu = 2 +denom = 1i * (beta_n * one_vec.' + one_vec * beta_n'); +exp_plus = exp(denom * (xi1 + a)); +exp_minus = exp(denom * (xi1 - a)); +T_B_block = (exp_plus - exp_minus) ./ denom .* pre_factors; +diag_indeces = ( (M+2)*max_real_index + (1:M+2:(M+2)*(M + 1 - max_real_index)) ).'; +T_B_block(diag_indeces) = 0.5 * a * k_out^2 ./ abs(beta_n(max_real_index+1:end)).^2 .* gamma_n_m(diag_indeces) ... + .* exp(-2 * R * abs(beta_n(max_real_index+1:end))); +T_B(1:M+1, M+2:end) = T_B_block; + +% block nu = 2, mu = 1 +denom = -1i * (beta_n * one_vec.' + one_vec * beta_n'); +exp_plus = exp(denom * (xi1 + a)); +exp_minus = exp(denom * (xi1 - a)); +T_B_block = (exp_plus - exp_minus) ./ denom .* pre_factors; +T_B_block(diag_indeces) = 0.5 * a * k_out^2 ./ abs(beta_n(max_real_index+1:end)).^2 .* gamma_n_m(diag_indeces) ... + .* exp(-2 * R * abs(beta_n(max_real_index+1:end))); +T_B(M+2:end, 1:M+1) = T_B_block; + +end + + +% The matrix (gamma_n^m)_{nm} contains the scalar products of two Neumann +% functions over the interval (\xi_2-a, \xi_2 + a) \subseteq (0, H). It is +% symmetric. +function g_n_m = gamma_n_m_eval(M, k_n, xi2, a, H) + +g_n_m = zeros(M+1, M+1); +k_n_nat = k_n(2:end); + +% entry for n = m = 0 +g_n_m(1, 1) = 2*a / H; + +% other entries for first row and first column +v = sqrt(2) ./ (H * k_n_nat) .* (sin(k_n_nat*(xi2+a)) - sin(k_n_nat*(xi2-a))); +g_n_m(1, 2:end) = v; +g_n_m(2:end, 1) = v.'; + +% assign the diagonal of the matrix except for first entry +g_n_m(M+3:M+2:end) = 2*a / H + (sin(2*k_n_nat*(xi2+a)) - sin(2*k_n_nat*(xi2-a))) ./ (2 * k_n_nat * H); + +% assign all the sub-diagonals +for l=1:M + k_diff = k_n(2+l:end) - k_n(2:end-l); + k_sum = k_n(2+l:end) + k_n(2:end-l); + v = sin(k_diff*(xi2+a)) / (k_diff * H) + sin(k_sum*(xi2+a)) / (k_sum * H) ... + - sin(k_diff*(xi2-a)) / (k_diff * H) - sin(k_sum*(xi2-a)) / (k_sum * H); + g_n_m((M+1)*(l+1)+2:M+2:(M+1)^2-l) = v; + g_n_m((M+2)+l+1:M+2:(M+1-l)*(M+1)) = v; +end + +end diff --git a/src/plot_of_figure_1.m b/src/plot_of_figure_1.m new file mode 100644 index 0000000..378ff23 --- /dev/null +++ b/src/plot_of_figure_1.m @@ -0,0 +1,52 @@ +function [] = plot_of_figure_1() + +H = 5; +R = 6; +x_max = 5; + +curve = BIEPack.geom.Ellipse([0; 0], 0.6, 0.2); +curve.rotate(pi/6); +curve.translate([-0.4; 2.6]); + +k_out = 6; +k_in = sqrt(2) * k_out; +M = 10; + +N_q = operator_N(H, curve, k_out, k_in, R, M, 512, 80, true); + +% Define the plots +a = 0.01; +%alpha = [0.1 0.2 0.5 0.75]; +alpha = [0.05 0.06 0.1 0.2]; +fig_nr = 1:length(alpha); +indicat = cell(1, length(fig_nr)); +%step = 0.1; +step = 0.05; +s = linspace(-pi, pi, 201); +obs_height = 25; + +% lambda = eig(N_q); +% figure(length(alpha)+1); +% semilogy(abs(lambda)); + +for j = 1:length(fig_nr) + [indicat{j}, Xi1, Xi2] = indicator_func(N_q, alpha(j), k_out, H, R, x_max, step, a, true); + + figure(fig_nr(j)) + colormap(parula); + surf(Xi1, Xi2, indicat{j}); + hold on + [x, y] = curve.eval(s, 0); + plot3(x, y, obs_height*ones(size(x)), 'w'); + hold off + view(2); + axis equal + axis([-5 5 0 H]); + shading interp + colorbar + set(gcf, 'Color', 'w'); + export_fig(sprintf('../figures/ellipse_central_k_6.0_alpha_%04.2f.png', alpha(j))); +end + +end + diff --git a/src/plot_of_figure_2.m b/src/plot_of_figure_2.m new file mode 100644 index 0000000..1caf0be --- /dev/null +++ b/src/plot_of_figure_2.m @@ -0,0 +1,49 @@ +function [] = plot_of_figure_2() + +H = 5; +R = 6; + +curve = BIEPack.geom.Circle([1.5; 3.5], 0.35); + +k_out = 11.0; +k_in = sqrt(2)*k_out; +M = 17; + +N_q = operator_N(H, curve, k_out, k_in, R, M, 512, 80, true); +%lambda = eig(N_q); + +% Define the plots +a = 0.01; +alpha = [0.01 0.025 0.0375 0.05]; +fig_nr = 1:length(alpha); +indicat = cell(1, length(fig_nr)); +x_max = 5; +%step = 0.1; +step = 0.05; +s = linspace(-pi, pi, 201); +obs_height = 25; + +% figure(length(alpha)+1); +% semilogy(abs(lambda)); + +for j = 1:length(fig_nr) + [indicat{j}, Xi1, Xi2] = indicator_func(N_q, alpha(j), k_out, H, R, x_max, step, a, true); + + figure(fig_nr(j)) + colormap(parula); + surf(Xi1, Xi2, indicat{j}); + hold on + [x, y] = curve.eval(s, 0); + plot3(x, y, obs_height*ones(size(x)), 'w'); + hold off + view(2); + axis equal + axis([-5 5 0 H]); + shading interp + colorbar + set(gcf, 'Color', 'w'); + export_fig(sprintf('../figures/circle_off_center_k_%4.2f_alpha_%4.2f.png', k_out, alpha(j))); +end + +end + diff --git a/src/plot_of_figure_3.m b/src/plot_of_figure_3.m new file mode 100644 index 0000000..ec9a9e6 --- /dev/null +++ b/src/plot_of_figure_3.m @@ -0,0 +1,52 @@ +function [] = plot_of_figure_3() + +H = 5; +R = 6; + +curve = BIEPack.geom.Ellipse([0; 0], 0.5, 0.2); +curve.rotate(pi/6); +curve.translate([-1; 2.5]); + +k_out = 6.0; +M = 9; + +%alpha = [-0.01 -0.008 -0.007 -0.006 ]; +alpha = [-0.01 -0.02 -0.03 -0.04]; +q = -0.5 * ones(size(alpha)); +fig_nr = 1:length(q); +a = 0.01; + +indicat = cell(1, length(fig_nr)); +x_max = 5; +step = 0.05; +s = linspace(-pi, pi, 201); +obs_height = 25; + +for j = 1:length(fig_nr) + + N_q = operator_N(H, curve, k_out, sqrt((1+ q(j)))*k_out, R, M, 512, true); + [indicat{j}, Xi1, Xi2] = indicator_func(N_q, alpha(j), k_out, H, R, x_max, step, a, false); + + figure(fig_nr(j)) + colormap(parula); + surf(Xi1, Xi2, indicat{j}); + hold on + [x, y] = curve.eval(s, 0); + plot3(x, y, obs_height*ones(size(x)), 'w'); + hold off + view(2); + axis equal + axis([-5 5 0 5]); + shading interp + colorbar + set(gcf, 'Color', 'w'); + export_fig(sprintf('../figures/ellipse_neg_q_alpha_%05.3f.png', -alpha(j))) +end + +% lambda = eig(N_q); +% figure(length(alpha)+1); +% semilogy(abs(lambda)); + + +end + -- GitLab