Neumann boundary condition in a diagonal matrix

11 vues (au cours des 30 derniers jours)
Hugo
Hugo le 12 Sep 2025
Modifié(e) : Torsten le 16 Sep 2025
I am trying to increase the accuracy of my mode solver by reducing the size of the calculation space, to do that I apply Dirichlet and Neumann on the borders. Assuming the symmetry, solving for only the upper left side (picture below) gives me the whole field. So I apply Dirichlet on the right and top side and Neumann on the bottom and right side.
I solve with eigs the following system : where is a vector describing the whole field and Φ is a symmetric matrix having the form :
Where b is defined as (d is the distance between two points) and a is defined as where n(xi,yi) is the value of n at the x,y position described by ψ(i). The equation used to write the system is :
Defining Neumann Boundary condition as :
Rewriting the above equation for the bottom and left part of the field I evidence the fact that it only changes the coefficient a by a coefficient , so what I did is adding this coefficient to the matrix coefficient when I know it is a boundary, leading to :
  • if i modulo m is 0 ( for i=1,...,l*m and i=j ) it means I am on the bottom of the field so I add to the previous definition of Φ
  • if i>=l*(m-1) it means I am on the right boundary of the field so I add to the previous definition of Φ
The addition of this two condition supposedly take into account what happens on the bottom right corner adding . But there must be an issue whith the way I coded it or defined my boundaries since I do not get the same results as when I study the full field (and it is not just an accuracy issue). I plotted the difference between expected results and what I actually got below.
The code I used is as follow :
%%Function topology for DCF design
clear all
clc
format long
%stepIndex---------------------------------------------------------
sizeX=10e-6;
sizeY=10e-6;
l=100;
m=100;
x=-linspace(0,sizeX,l);
y=linspace(0,sizeY,m);
d=abs(x(1)-x(2))
[xx,yy]=meshgrid(x,y);
n=zeros(size(xx));
Phi=zeros(m*l);
%%%%%%%%%%%%%%%%%%%%%%%%Iterate over wl%%%%%%%%%%%%%%%%%%%%%%%%%%
%Basic physical parameters-----------------------------------------
lambdac=1030e-9;
dlambda=0.5e-9;
rcoreIni=5e-6;
rclad=125e-6;
nbg=1;
neff=[];
lambdac0=1000e-9;
lambdacmax=1600e-9;
psi=[];
for lambda0=lambdac0:50e-9:lambdacmax%lambdac-dlambda:dlambda:lambdac+dlambda
%n_SiO2=interp1(A(:,1)*1e-9,A(:,9),lambda0,'spline'); %-replaced because the Sellmeier filed is not attached and the issue is generalized to every wavelength
wl=lambdac0;
A1=0.6961663;
A2=0.4079426;
A3=0.8974794;
L1=0.0684043e-6;
L2=0.1162414e-6;
L3=9.896161e-6;
n_SiO2=sqrt(1+(A1*wl^2)/(wl^2-L1^2)+(A2*wl^2)/(wl^2-L2^2)+(A3*wl^2)/(wl^2-L3^2));
k0=2*pi/lambda0;
lambda0
%Initial design guess----------------------------------------------
nmin=n_SiO2;
nmax=sqrt(0.14^2+n_SiO2^2);
nicore=nmax;%n_SiO2+0.36*n_SiO2;%n_SiO2+0.38;
niclad=nmin;%n_SiO2;
n=n*0+nbg;
n(xx.^2+yy.^2<rclad^2)=niclad;
n(xx.^2+yy.^2<rcoreIni^2)=nicore;
figure(1);
surf(xx,yy,n,'EdgeColor','none');axis tight
%transform n-----------------------------------------------------------
ni=[];
for iii=1:1:length(n)
ni=[ni;n(iii,:).'];
end
for ii=1:1:l*m
Phi(ii,ii)=-4/d^2+ni(ii)^2*k0^2;
if(ii-1>=1 && ii+1<=l*m)
Phi(ii-1,ii)=1/d^2;
Phi(ii,ii+1)=1/d^2;
Phi(ii+1,ii)=1/d^2;
Phi(ii,ii-1)=1/d^2;
end
if(ii-m>=1)
Phi(ii,ii-m)=1/d^2;
end
if(ii+m<=l*m)
Phi(ii,ii+m)=1/d^2;
end
if(ii>=l*(m-1))%right hand side
Phi(ii,ii)=Phi(ii,ii)+1/d^2;
end
if(mod(ii,m)==0)%bottom side
Phi(ii,ii)=Phi(ii,ii)+1/d^2;
end
end
%%Eigenvalues calc--------------------------------------------------
Phi=sparse(Phi);
%%solving
opts.maxit = 1000;
opts.tol = 1e-9;
[eigenvector,eigenvalues,flag]=eigs(Phi,10,(1.45)^2*k0^2,opts);
beta2=diag(eigenvalues);
Psi=eigenvector;
neff=[neff max(sqrt(beta2)./k0)];
psi=[psi Psi(:,1)];
end
%mode field visual-------------------------------------------------------
for iii=1:1:5
Pssi=[];
for jj=1:1:l
Pssi=[Pssi; Psi((jj-1)*l+1:jj*l,iii).'];
end
figure(iii+2);
surf(xx,yy,abs(Pssi).^2,'EdgeColor','none');view(2);
end
lm=lambdac0:50e-9:lambdacmax;
figure(2)
hold on
plot(lm,neff);
hold off
Am I wrong code wise, theory wise or both??
Thanks a lot!

Réponses (3)

Torsten
Torsten le 12 Sep 2025
Modifié(e) : Torsten le 12 Sep 2025
The Laplace operator is discretized as
(u(x+h,y) - 2*u(x,y) + u(x-h,y))/h^2 + (u(x,y+h) - 2*u(x,y) + u(x,y-h))/h^2
If y = 0 and x < 0 (i.e. at the bottom line excuding x=0) and you want to set du/dy = 0 as boundary condition, we have u(x,y+h) = u(x,y-h).
Thus we get for the Laplace operator
(u(x+h,y) - 2*u(x,y) + u(x-h,y))/h^2 + (2*u(x,y+h) - 2*u(x,y) )/h^2
If y > 0 and x = 0 (i.e. at the right line excluding y = 0) and you want to set du/dx = 0 as boundary condition, we have u(x+h,y) = u(x-h,y).
Thus we get for the Laplace operator
( - 2*u(x,y) + 2*u(x-h,y))/h^2 + (u(x,y+h) - 2*u(x,y) + u(x,y-h))/h^2
If x=y=0 and you want to set du/dx = du/dy = 0 as boundary condition, we have u(x,y+h) = u(x,y-h) and u(x+h,y) = u(x-h,y).
Thus we get for the Laplace operator
( - 2*u(x,y) + 2*u(x-h,y))/h^2 + (2*u(x,y+h) - 2*u(x,y) )/h^2
As you can see, the diagonal part of the matrix Phi remains unchanged while the upper resp. lower diagonals change - in contrast to what you coded.
But I'm surprised about your problem formulation. Usually, you need an overdetermined problem to get lambda as a free parameter that can be solved for.
See e.g.
Here, you need three boundary conditions to treat "lambda" as a free eigenvalue.
  2 commentaires
Hugo
Hugo le 12 Sep 2025
I do not get why the way I defined it does not work?
So from my point of view I can do both :
(u(x+h,y) - 2*u(x,y) + u(x-h,y))/h^2 + (2*u(x,y+h) - 2*u(x,y) )/h^2
And
(u(x+h,y) - 2*u(x,y) + u(x-h,y))/h^2 + (2*u(x,y+h) - u(x,y) )/h^2
For y = 0 and x < 0.
As for your question about the problem formulation, I must confess I have not enough knowledge to answer it, I am basing myself of an article and many books already confirmed the method. I do not know much about bvp I am using an eigensolver from my understanding it is not a boundary value problem (but I may be very wrong about that...) I just define boundaries to reduce the size (the physic one not the matrix one) of the calculation domain and when I am not doing so I do not even bother defining boudaries as I consider the studied field to be almost 0 at the borders of the domain.
A picture of what I am trying to do, is reducing the calculation window assuming the studied field is perfectly symmetric. N stands for "at this border the field is equal" D for "the field is null". In the end the eigenvalue problem gives a lot of solution but I know from physics that the highest real value is the only one I care for. I don't know if that helps with problem formulation.
Torsten
Torsten le 12 Sep 2025
Modifié(e) : Torsten le 12 Sep 2025
If you want to approximate d^2u/dy^2 at y = 0 and x<0, you can also use
d^2u/dy^2 @(x,0) = d/dy (du/dy) @(x,0) ~ (du/dy(x,h/2) - du/dy(x,0))/(h/2) ~ ((u(x,h)-u(x,0))/h - 0)/(h/2) = (2*u(x,h)-2*u(x,0))/h^2
and if you want to approximate d^2u/dx^2 at x = 0 and y>0, you can also use
d^2u/dx^2 @(0,y) = d/dx (du/dx) @(0,y) ~ (du/dx(0,y) - du/dx(-h/2,y))/(h/2) ~ (0 - (u(0,y)-u(-h,y))/h)/(h/2) = (-2*u(0,y)+2*u(-h,y))/h^2
and again you see that my result from above is reproduced, i.e. the upper resp. lower diagonals are changed, not the diagonal itself.
I don't know how you want to approximate d^2u/dy^2 at y = 0 and x<0 resp. d^2u/dx^2 at x=0, y>0 and Neumann boundary condition with your approach.

Connectez-vous pour commenter.


Umar
Umar le 12 Sep 2025

Hi @Hugo,

Thanks for sharing your code and the detailed explanation of your approach. I see you're trying to increase the accuracy of your mode solver by reducing the simulation space through symmetry, which is a smart move. Let's go over your comments and figure out the issue.

1. Boundary Condition Setup: Dirichlet and Neumann on borders

You mentioned that you're applying Dirichlet boundary conditions on the top and right sides, and Neumann on the bottom and left sides. This is a valid approach for reducing the domain using symmetry.

However, I noticed something in your code that could be causing issues. It looks like you’re adding `1/d^2` to the diagonal terms for both the right and bottom edges, which isn’t how * Neumann boundary conditions* are typically applied in finite difference methods. Instead of modifying the diagonal, we need to modify the off-diagonal terms (the neighboring values) to mirror the values across the boundary.

For example, at the bottom edge (y = 0), a Neumann condition means that the value at the bottom should be the same as the value at the point just above it. So, we don’t touch the diagonal; we adjust the terms that represent neighbors.

2. Neumann Boundary Condition Misapplication

You wrote that you're adjusting the diagonal at the boundaries by adding `1/d^2`. The issue here is that Neumann conditions (i.e., zero derivative at the boundary) should be applied by doubling the off-diagonal terms that correspond to the boundary's neighbors, not modifying the diagonal term itself.

Here’s how the correction should look:

Instead of this:

if(ii>=l*(m-1))  % Right side
  Phi(ii,ii) = Phi(ii,ii) + 1/d^2;
end
if(mod(ii,m)==0) % Bottom side
  Phi(ii,ii) = Phi(ii,ii) + 1/d^2;
end

You should be doing this:

if(mod(ii, m) == 0)  % Bottom edge
  if ii + m <= l * m
      Phi(ii + m, ii) = 2 / d^2;  % mirror in y-direction
  end
end
if(ii >= l * (m - 1))  % Right edge
  if ii - 1 >= 1
      Phi(ii - 1, ii) = 2 / d^2;  % mirror in x-direction
  end
end

This will correctly implement the Neumann boundary conditions and give you the right behavior for your mode solver near the edges.

3. Why the Full Field Results Differ

You mentioned that your reduced domain simulation doesn't match the full-field solution, and it's not just an *accuracy issue*. That’s because applying the Neumann condition incorrectly near the boundaries — by adjusting the diagonal instead of the off-diagonal neighbors — introduces errors in the way the field is being calculated. Once you correct this, you should get results that match those from the full-field simulation more closely.

4. Corner Treatment

For the bottom-right corner, where Neumann conditions from both axes meet, you’ll need to mirror the neighbors in both x and y directions. This means adjusting the off-diagonal elements in both directions, without touching the diagonal.

This is how we ensure that the boundary condition is correctly enforced in the corner, maintaining the zero derivative across both boundaries.

5. Additional Thoughts on the Eigenvalue Formulation

@Torsten also raised an insightful theoretical point: when you're solving an eigenvalue problem like this, you need to make sure the boundary conditions are applied correctly to fully define the problem. If the reduced domain doesn’t have sufficient boundary conditions, the eigenvalue solver could behave unpredictably, as there might be too many unknowns or conflicting constraints.

Once the boundary conditions are correctly implemented, you should see much more stable results for the eigenvalue problem.

So, once you update your boundary conditions as I’ve outlined (modifying the off-diagonal terms instead of the diagonal for Neumann BCs), your reduced-domain simulation should align better with the full-field solution. This change will also resolve the physics issue introduced by incorrectly applied Neumann BCs.

Let me know if you'd like further clarification from us or need help with testing the change.

  3 commentaires
Torsten
Torsten le 12 Sep 2025
Modifié(e) : Torsten le 12 Sep 2025
For your last point, I'd understand if phi_(p-1,q) = phi_(p+1,q) or phi_(p,q) = phi_(p+1,q) were used to implement dhi/dx = 0.
phi_(p-1,q) = phi_(p,q) doesn't make sense to me: a parabola through (-h,phi0), (0,phi0) and (h,phi1) doesn't have slope = 0 in x = 0 - at least if phi0 ~= phi1.
Umar
Umar le 12 Sep 2025

Hi @Hugo,

Let me answer your questions directly, building on the important point that @Torsten just made.

Corner Elements: You're right - no special code needed. Corners handle themselves automatically.

The Key Issue @Torsten Identified: Torsten pointed out something crucial about your book equation. When you set the outside point equal to the boundary point, you don't actually get zero slope.

Think of it this way: if you have three points in a row with values five, five, eight, the line is clearly going upward from the first point to the third point. The slope is not zero.

For true zero slope at the middle point, you need the line to go up then back down, like eight, five, eight. This means the outside point must equal the inside point, not the boundary point.

Your Coordinate System Issue: You mentioned your upper left and bottom right are reversed compared to how you define i and j. This is probably causing you to apply the wrong boundary conditions to the wrong edges. This coordinate confusion might actually be a bigger problem than the theoretical issue.

Why Your Results Are Wrong: As @Torsten correctly showed, your book equation doesn't create zero slope conditions. Plus, if your coordinates are flipped, you're applying whatever conditions you have to the wrong boundaries entirely.

What to Do Next: Before changing any code: 1. Fix your coordinate system first - make sure you know which physical edges your boundary conditions actually hit 2. Check your book again - what does it actually call this boundary condition? 3. Verify if you really need zero slope conditions for your waveguide symmetry, or something else

Don't keep trying random fixes. Let's solve the right problem first.

Thanks to @Torsten for catching the fundamental mathematical issue that clarifies everything.

Connectez-vous pour commenter.


Torsten
Torsten le 14 Sep 2025
Modifié(e) : Torsten le 14 Sep 2025
For fun, I computed eigenvalues and eigenfunctions for the full region with boundary condition u = 0 and for the quarter region with boundary condition u = 0 on the outer edges and grad(u) = 0 on the inner edges. It turns out that eigenvalues and eigenfunctions are not identical (except for the smallest eigenvalue in magnitude). When you look at the eigenfunctions for the full region, the reason is simple: they are not symmetrical with respect to the assumed axes of symmetry.
Since I assume the situation will be similar for your (even more complicated) case, a simplification of the problem by only computing a quarter of the region doesn't seem possible.
  3 commentaires
Torsten
Torsten le 15 Sep 2025
Modifié(e) : Torsten le 15 Sep 2025
The eigenvalues of the problem
-(d^2u/dx^2 + d^2u/dy^2) = lambda*u
on
0 <= x <= a, 0 <= y <= b
with boundary conditions
du/dx = 0 for x = 0, du/dy = 0 for y = 0, and u = 0 for x = a and y = b
are given by
lambda_nm = pi^2 * ((2*n+1)^2/(2*a)^2 + (2*m+1)^2/(2*b)^2) (n = 0,1,2,... ; m = 0,1,2,...)
the eigenvalues of the problem
-(d^2u/dx^2 + d^2u/dy^2) = lambda*u
on
-a <= x <= a, -b <= y <= b
with boundary conditions
u = 0 for x = -a, x = a, y = -b, y = b
are given by
lambda_nm = pi^2 * (n^2/(2*a)^2 + m^2/(2*b)^2) (n = 1,2,3,... ; m = 1,2,3,...)
Thus for this simple case, the eigenvalues of the "quarter" problem are also eigenvalues of the "full" problem, but not vice versa.
(Source: Andrei D. Polyanin: Handbook of linear partial differential equations for engineers and scientists)
I don't know how this generalizes to your problem which is more complicated.
Umar
Umar le 16 Sep 2025
Modifié(e) : Torsten le 16 Sep 2025
Hi @Hugo,
First, let me say that I am as humble and helpful as Jesus Christ was. Second, I have great respect for @Torsten and others over here. Following up, I reran the calculations in MATLAB to directly compare the full-domain solver with the quarter-domain solver that assumes symmetry. The results came out as:
* Full domain effective index: 1.44167819
* Quarter domain effective index: 1.44644773
* Relative error: about 0.3%
This confirms what Torsten already showed analytically — the quarter-domain solver introduces a bias whenever the mode does not strictly follow Cartesian mirror symmetry.
Here’s what the plots show:
1. Fiber cross-section with the high-index core in the center and lower-index cladding around it.
2. Full-domain mode intensity, which serves as the reference solution.
3. Quarter-domain reconstructed mode, which only matches if the mode satisfies symmetry.
4. Difference map showing pixel-by-pixel error; bright spots appear near the core edges where symmetry assumptions break down.
5. Horizontal and vertical cross-sections comparing the two solutions. They line up in the center but diverge toward the tails.
6. Contour plot of the full solution, which is nearly circular, highlighting the true cylindrical symmetry. The quarter-domain solver instead enforces mirror planes, which explains the mismatch.
*Script (mode\_solver\_fixed.m):*
mode_solver_fixed()
Computing solutions... Starting eigenvalue iteration... Converged in 28 iterations Found eigenvalue: 34153325287950.144531 (target was 34548750473740.472656) Starting eigenvalue iteration... Converged in 9 iterations Found eigenvalue: 34379679686807.175781 (target was 34548750473740.472656) Results Comparison: Full domain n_eff: 1.44167819 Quarter domain n_eff: 1.44644773 Relative error: 3.31e-03
Plotting completed. Check figures for mode comparison.
ans =
1.441678185548988
function [neff, psi] = mode_solver_fixed()
% Fixed mode solver for optical fibers without MATLAB toolboxes
% Uses simple inverse iteration with proper conditioning
clear all; clc; format long;
% Reduced problem size for better performance
sizeX = 10e-6;
sizeY = 10e-6;
l = 50; % Reduced from 100 for better performance
m = 50;
x = linspace(0, sizeX, l);
y = linspace(0, sizeY, m);
d = abs(x(2) - x(1));
[xx, yy] = meshgrid(x, y);
% Fiber parameters
lambda0 = 1550e-9;
k0 = 2*pi/lambda0;
rcoreIni = 5e-6;
rclad = 125e-6;
% Sellmeier equation for silica
wl = lambda0;
A1 = 0.6961663; A2 = 0.4079426; A3 = 0.8974794;
L1 = 0.0684043e-6; L2 = 0.1162414e-6; L3 = 9.896161e-6;
n_SiO2 = sqrt(1 + (A1*wl^2)/(wl^2-L1^2) + (A2*wl^2)/(wl^2-L2^2) + (A3*wl^2)/(wl^2-L3^2));
% Create refractive index profile
nmin = n_SiO2;
nmax = sqrt(0.14^2 + n_SiO2^2);
n = ones(size(xx)) * 1.0; % background
n(xx.^2 + yy.^2 < rclad^2) = nmin;
n(xx.^2 + yy.^2 < rcoreIni^2) = nmax;
% Display structure
figure(1);
surf(xx*1e6, yy*1e6, n, 'EdgeColor', 'none');
axis tight; colorbar;
xlabel('x (μm)'); ylabel('y (μm)'); title('Refractive Index Profile');
% Solve both full and quarter domain
fprintf('Computing solutions...\n');
[neff_full, psi_full] = solve_full_domain(n, k0, d, l, m);
[neff_quarter, psi_quarter] = solve_quarter_domain(n, k0, d, l, m);
% Compare results
fprintf('\nResults Comparison:\n');
fprintf('Full domain n_eff: %.8f\n', neff_full);
fprintf('Quarter domain n_eff: %.8f\n', neff_quarter);
fprintf('Relative error: %.2e\n', abs(neff_full - neff_quarter)/neff_full);
% Plot results
plot_comparison(xx, yy, psi_full, psi_quarter);
neff = neff_full;
psi = psi_full;
end
function [neff, psi] = solve_full_domain(n, k0, d, l, m)
% Solve on full domain using robust method
% Build matrix efficiently
ni = reshape(n', [], 1);
N = l * m;
% Pre-allocate arrays for efficiency
max_entries = N * 5; % Estimate: each point has up to 5 entries (diagonal + 4 neighbors)
i_idx = zeros(max_entries, 1);
j_idx = zeros(max_entries, 1);
vals = zeros(max_entries, 1);
entry_count = 0;
for idx = 1:N
[row, col] = ind2sub([m, l], idx);
% Diagonal element
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = idx;
vals(entry_count) = -4/d^2 + ni(idx)^2 * k0^2;
% Off-diagonal elements - neighbors
if col > 1 % Left neighbor
neighbor_idx = sub2ind([m, l], row, col-1);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = neighbor_idx;
vals(entry_count) = 1/d^2;
end
if col < l % Right neighbor
neighbor_idx = sub2ind([m, l], row, col+1);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = neighbor_idx;
vals(entry_count) = 1/d^2;
end
if row > 1 % Bottom neighbor
neighbor_idx = sub2ind([m, l], row-1, col);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = neighbor_idx;
vals(entry_count) = 1/d^2;
end
if row < m % Top neighbor
neighbor_idx = sub2ind([m, l], row+1, col);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = neighbor_idx;
vals(entry_count) = 1/d^2;
end
end
% Trim arrays to actual size
i_idx = i_idx(1:entry_count);
j_idx = j_idx(1:entry_count);
vals = vals(1:entry_count);
A = sparse(i_idx, j_idx, vals, N, N);
% Use simple eigenvalue solver with better conditioning
target = (1.45 * k0)^2;
[psi_vec, lambda] = simple_eigensolve(A, target);
psi = reshape(psi_vec, m, l);
neff = sqrt(lambda) / k0;
end
function [neff, psi] = solve_quarter_domain(n, k0, d, l, m)
% Solve quarter domain with corrected boundary conditions
l_q = ceil(l/2);
m_q = ceil(m/2);
n_quarter = n(1:m_q, 1:l_q);
ni = reshape(n_quarter', [], 1);
N = l_q * m_q;
% Pre-allocate arrays
max_entries = N * 5;
i_idx = zeros(max_entries, 1);
j_idx = zeros(max_entries, 1);
vals = zeros(max_entries, 1);
entry_count = 0;
for idx = 1:N
[row, col] = ind2sub([m_q, l_q], idx);
% Start with standard diagonal term
diag_val = -4/d^2 + ni(idx)^2 * k0^2;
% Handle boundary conditions properly
is_left = (col == 1);
is_bottom = (row == 1);
is_right = (col == l_q);
is_top = (row == m_q);
% Add diagonal term
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = idx;
vals(entry_count) = diag_val;
% Only add off-diagonal terms if not on Dirichlet boundaries
if ~is_right && ~is_top
% Left neighbor
if ~is_left
j_left = sub2ind([m_q, l_q], row, col-1);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = j_left;
vals(entry_count) = 1/d^2;
end
% Right neighbor
if ~is_right
j_right = sub2ind([m_q, l_q], row, col+1);
if is_left
weight = 2/d^2; % Double for Neumann boundary condition
else
weight = 1/d^2; % Normal interior point
end
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = j_right;
vals(entry_count) = weight;
end
% Bottom neighbor
if ~is_bottom
j_bottom = sub2ind([m_q, l_q], row-1, col);
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = j_bottom;
vals(entry_count) = 1/d^2;
end
% Top neighbor
if ~is_top
j_top = sub2ind([m_q, l_q], row+1, col);
if is_bottom
weight = 2/d^2; % Double for Neumann boundary condition
else
weight = 1/d^2; % Normal interior point
end
entry_count = entry_count + 1;
i_idx(entry_count) = idx;
j_idx(entry_count) = j_top;
vals(entry_count) = weight;
end
end
end
% Trim arrays to actual size
i_idx = i_idx(1:entry_count);
j_idx = j_idx(1:entry_count);
vals = vals(1:entry_count);
A = sparse(i_idx, j_idx, vals, N, N);
% Solve eigenvalue problem
target = (1.45 * k0)^2;
[psi_vec, lambda] = simple_eigensolve(A, target);
% Reconstruct full domain by symmetry
psi_quarter = reshape(psi_vec, m_q, l_q);
psi = zeros(m, l);
% Fill quadrants with proper indexing
psi(1:m_q, 1:l_q) = psi_quarter;
% Mirror to other quadrants if needed
if l_q < l
% Right side - mirror horizontally (exclude boundary to avoid duplication)
n_cols_to_fill = l - l_q;
n_cols_available = l_q - 1; % Exclude the boundary column
if n_cols_available > 0
cols_to_fill = l_q+1:l;
% Take only as many columns as we have available and needed
n_cols_to_use = min(n_cols_to_fill, n_cols_available);
cols_to_mirror = l_q-1:-1:l_q-n_cols_to_use;
psi(1:m_q, l_q+1:l_q+n_cols_to_use) = psi_quarter(:, cols_to_mirror);
end
end
if m_q < m
% Bottom side - mirror vertically (exclude boundary to avoid duplication)
n_rows_to_fill = m - m_q;
n_rows_available = m_q - 1; % Exclude the boundary row
if n_rows_available > 0
rows_to_fill = m_q+1:m;
% Take only as many rows as we have available and needed
n_rows_to_use = min(n_rows_to_fill, n_rows_available);
rows_to_mirror = m_q-1:-1:m_q-n_rows_to_use;
psi(m_q+1:m_q+n_rows_to_use, 1:l_q) = psi_quarter(rows_to_mirror, :);
end
end
if l_q < l && m_q < m
% Bottom-right corner - mirror both ways
n_rows_available = m_q - 1;
n_cols_available = l_q - 1;
n_rows_to_fill = m - m_q;
n_cols_to_fill = l - l_q;
if n_rows_available > 0 && n_cols_available > 0
n_rows_to_use = min(n_rows_to_fill, n_rows_available);
n_cols_to_use = min(n_cols_to_fill, n_cols_available);
rows_to_mirror = m_q-1:-1:m_q-n_rows_to_use;
cols_to_mirror = l_q-1:-1:l_q-n_cols_to_use;
psi(m_q+1:m_q+n_rows_to_use, l_q+1:l_q+n_cols_to_use) = ...
psi_quarter(rows_to_mirror, cols_to_mirror);
end
end
neff = sqrt(lambda) / k0;
end
function [v, lambda] = simple_eigensolve(A, target)
% Simple eigenvalue solver using inverse iteration
N = size(A, 1);
max_iter = 50;
tol = 1e-6;
% Shift matrix
shift = target;
B = A - shift * speye(N);
% Better initial guess - Gaussian-like distribution centered in domain
grid_size = round(sqrt(N));
[I, J] = meshgrid(1:grid_size, 1:grid_size);
center = (grid_size + 1) / 2;
gaussian_2d = exp(-((I - center).^2 + (J - center).^2) / (grid_size/4)^2);
v = reshape(gaussian_2d, [], 1);
% Ensure we don't have more elements than N
if length(v) > N
v = v(1:N);
elseif length(v) < N
v = [v; zeros(N - length(v), 1)];
end
v = v / norm(v);
fprintf('Starting eigenvalue iteration...\n');
for iter = 1:max_iter
v_old = v;
% Solve B * v_new = v_old directly
try
v_new = B \ v_old;
catch ME
fprintf('Direct solve failed: %s\n', ME.message);
fprintf('Trying LU decomposition...\n');
try
[L, U, P] = lu(B);
v_new = U \ (L \ (P * v_old));
catch ME2
fprintf('LU decomposition failed: %s\n', ME2.message);
error('Matrix solve failed - matrix may be singular');
end
end
% Check for numerical issues
if any(~isfinite(v_new))
error('Eigenvalue iteration produced non-finite values');
end
% Normalize
v_norm = norm(v_new);
if v_norm < 1e-12
error('Eigenvector became zero - matrix may be singular');
end
v = v_new / v_norm;
% Check convergence
conv_check = min(norm(v - v_old), norm(v + v_old));
if conv_check < tol
fprintf('Converged in %d iterations\n', iter);
break;
end
if iter == max_iter
fprintf('Warning: Maximum iterations reached, convergence = %.2e\n',conv_check);
end
end
% Compute final eigenvalue using Rayleigh quotient
lambda = real((v' * A * v) / (v' * v));
fprintf('Found eigenvalue: %.6f (target was %.6f)\n', lambda, target);
end
function plot_comparison(xx, yy, psi_full, psi_quarter)
% Plot comparison between methods
figure(2);
set(gcf, 'Position', [100, 100, 1200, 800]);
subplot(2,3,1);
imagesc(xx(1,:)*1e6, yy(:,1)*1e6, abs(psi_full).^2);
axis equal tight; colorbar;
title('Full Domain |ψ|²');
xlabel('x (μm)'); ylabel('y (μm)');
subplot(2,3,2);
imagesc(xx(1,:)*1e6, yy(:,1)*1e6, abs(psi_quarter).^2);
axis equal tight; colorbar;
title('Quarter Domain |ψ|²');
xlabel('x (μm)'); ylabel('y (μm)');
subplot(2,3,3);
diff_field = abs(abs(psi_full).^2 - abs(psi_quarter).^2);
imagesc(xx(1,:)*1e6, yy(:,1)*1e6, diff_field);
axis equal tight; colorbar;
title('Difference |Full - Quarter|');
xlabel('x (μm)'); ylabel('y (μm)');
% Line plots
subplot(2,3,4);
mid_row = round(size(xx,1)/2);
plot(xx(1,:)*1e6, abs(psi_full(mid_row,:)).^2, 'b-', 'LineWidth', 2);
hold on;
plot(xx(1,:)*1e6, abs(psi_quarter(mid_row,:)).^2, 'r--', 'LineWidth', 2);
xlabel('x (μm)'); ylabel('|ψ|²');
title('Horizontal Cross-section');
legend('Full', 'Quarter', 'Location', 'best');
grid on;
subplot(2,3,5);
mid_col = round(size(yy,2)/2);
plot(yy(:,1)*1e6, abs(psi_full(:,mid_col)).^2, 'b-', 'LineWidth', 2);
hold on;
plot(yy(:,1)*1e6, abs(psi_quarter(:,mid_col)).^2, 'r--', 'LineWidth', 2);
xlabel('y (μm)'); ylabel('|ψ|²');
title('Vertical Cross-section');
legend('Full', 'Quarter', 'Location', 'best');
grid on;
subplot(2,3,6);
contour(xx*1e6, yy*1e6, abs(psi_full).^2, 10);
axis equal tight;
title('Full Domain Contours');
xlabel('x (μm)'); ylabel('y (μm)');
fprintf('\nPlotting completed. Check figures for mode comparison.\n');
end
Please see attached plots.
*Explanation of script:*
* Defines the fiber refractive index profile using the Sellmeier equation for silica.
* Solves the eigenmodes on both the **full 2D domain** and the **quarter domain** with symmetry boundary conditions.
* Uses inverse iteration with a Gaussian initial guess to compute the effective index and field distribution.
* Compares results by reporting effective indices, computing relative error, and plotting.
* Generates six plots: structure, full vs quarter domain modes, error map, cross-sections, and contours.
In short, the script backs up Torsten’s theoretical reasoning with numerical evidence: the quarter-domain solver can be useful, but it only gives exact results if the eigenmode itself respects Cartesian mirror symmetry. For the step-index fiber, that condition is not generally met, so the full-domain solver is the reliable choice.

Connectez-vous pour commenter.

Catégories

En savoir plus sur Creating and Concatenating Matrices dans Help Center et File Exchange

Produits


Version

R2024a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by