Skip to content

Commit 7a56991

Browse files
committed
Introducing colocation matrices in tensorial mode solver
1 parent e5c8f08 commit 7a56991

File tree

4 files changed

+301
-45
lines changed

4 files changed

+301
-45
lines changed

tests/test_plugins/test_mode_solver.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,84 @@ def test_high_order_mode_normalization():
12371237
assert (values[: values.size // 3] > 0).all()
12381238

12391239

1240+
# Test that a mode solver with an angle and PEC symmetry does not produce unphysical modes
1241+
def test_mode_solver_angle_symmetry():
1242+
# Angle to test
1243+
theta = np.pi / 180
1244+
theta = np.pi / 6
1245+
1246+
# Other parameters
1247+
plane = td.Box(center=(0, 0, 0), size=(5, 0, 8))
1248+
sim_size = (10, 10, 10)
1249+
# sim_size = (4, 3, 3)
1250+
freq0 = td.C_0 / 1.0
1251+
1252+
# Make the default waveguide lossless to also test numerical loss artifact in the angled case
1253+
wg = WAVEGUIDE.updated_copy(medium=td.Medium(permittivity=4))
1254+
1255+
# ModeSource at an angle for visualization aid
1256+
src = td.ModeSource(
1257+
source_time=td.GaussianPulse(freq0=2e14, fwidth=1e13),
1258+
center=plane.center,
1259+
size=plane.size,
1260+
mode_spec=td.ModeSpec(num_modes=1, angle_theta=theta),
1261+
mode_index=0,
1262+
direction="+",
1263+
)
1264+
1265+
# Mode solver without angle
1266+
simulation = td.Simulation(
1267+
size=sim_size,
1268+
grid_spec=td.GridSpec.auto(wavelength=1.0, min_steps_per_wvl=10),
1269+
# grid_spec=td.GridSpec.uniform(dl=0.04),
1270+
structures=[wg],
1271+
run_time=1e-12,
1272+
symmetry=(0, 0, -1),
1273+
sources=[src],
1274+
# medium=td.Medium(permittivity=4.5),
1275+
)
1276+
1277+
# simulation = td.Simulation.from_file("/home/momchil/flexcompute/test_fdtd/lucas/FDTD_Setup_P1@0_v1.json")
1278+
# print(simulation.grid.num_cells)
1279+
# freq0 = simulation.sources[0].source_time.freq0
1280+
# plane = td.Box(center=simulation.monitors[1].center, size=simulation.monitors[1].size)
1281+
1282+
mode_spec = td.ModeSpec(num_modes=5)
1283+
ms = ModeSolver(
1284+
simulation=simulation,
1285+
plane=plane,
1286+
mode_spec=mode_spec,
1287+
freqs=[freq0],
1288+
)
1289+
modes_zero = ms.solve()
1290+
1291+
# Compute modes with angle
1292+
wg = wg.updated_copy(geometry=wg.geometry.rotated(angle=-theta, axis=[0, 0, 1]))
1293+
simulation = simulation.updated_copy(structures=[wg])
1294+
mode_spec = mode_spec.updated_copy(angle_theta=theta)
1295+
ms_angle = ms.updated_copy(mode_spec=mode_spec, simulation=simulation)
1296+
modes_angle = ms_angle.solve()
1297+
1298+
import matplotlib.pyplot as plt
1299+
1300+
fig, ax = plt.subplots(1, 3)
1301+
simulation.plot(x=0, ax=ax[0])
1302+
simulation.plot(y=0, ax=ax[1])
1303+
simulation.plot(z=0, ax=ax[2])
1304+
plt.show()
1305+
1306+
# Plot the mode_index = 0 mode for both
1307+
_, ax = plt.subplots(1, 2)
1308+
ms.plot_field("E", mode_index=0, ax=ax[0], robust=False)
1309+
ms_angle.plot_field("E", mode_index=0, ax=ax[1], robust=False)
1310+
plt.show()
1311+
1312+
print(modes_zero.n_complex.values)
1313+
print(modes_angle.n_complex.values)
1314+
1315+
assert np.allclose(modes_zero.n_complex.values, modes_angle.n_complex.values, atol=3e-2)
1316+
1317+
12401318
def test_gauge_robustness():
12411319
array = np.zeros((5, 5), dtype=float)
12421320
ij = np.arange(5)

tidy3d/components/mode/derivatives.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,113 @@ def s_value(
240240
kappa = kappa_min + (kappa_max - kappa_min) * step**order
241241
sigma = sigma_max * avg_speed / (ETA_0 * dl) * step**order
242242
return kappa + 1j * sigma / (omega * EPSILON_0)
243+
244+
245+
def make_cxf(dls, shape, pmc):
246+
"""Forward colocation in x."""
247+
import scipy.sparse as sp
248+
249+
Nx, Ny = shape
250+
if Nx == 1:
251+
return sp.csr_matrix((Ny, Ny))
252+
# Create matrix with [1,1,0,...,0], [0,1,1,...,0], etc pattern
253+
cxf = 0.5 * sp.csr_matrix(sp.diags([1, 1], [0, 1], shape=(Nx, Nx)))
254+
255+
# Apply boundary conditions before Kronecker product
256+
if not pmc:
257+
cxf[0, 0] = 0
258+
259+
return sp.kron(cxf, sp.eye(Ny))
260+
261+
262+
def make_cxb(dls, shape, pmc):
263+
"""Backward colocation in x."""
264+
import scipy.sparse as sp
265+
266+
Nx, Ny = shape
267+
if Nx == 1:
268+
return sp.csr_matrix((Ny, Ny))
269+
270+
# Calculate weights for each position
271+
p1 = dls[:-1] # First grid spacing
272+
p2 = dls[1:] # Second grid spacing
273+
274+
weights_curr = np.ones(Nx)
275+
weights_curr[1:] = p1 / (p1 + p2)
276+
277+
weights_prev = np.zeros(Nx)
278+
weights_prev[:-1] = p2 / (p1 + p2)
279+
280+
# Create the matrix using diagonals
281+
cxb = sp.diags([weights_curr, weights_prev], [0, -1], shape=(Nx, Nx))
282+
283+
# Apply boundary conditions before Kronecker product
284+
# The matrix is already set up for PEC (cxb[0, 0] = 1)
285+
if pmc:
286+
cxb[0, 0] = 0
287+
288+
return sp.kron(cxb, sp.eye(Ny))
289+
290+
291+
def make_cyf(dls, shape, pmc):
292+
"""Forward colocation in y."""
293+
import scipy.sparse as sp
294+
295+
Nx, Ny = shape
296+
if Ny == 1:
297+
return sp.csr_matrix((Nx, Nx))
298+
# Create matrix with [1,1,0,...,0], [0,1,1,...,0], etc pattern
299+
cyf = 0.5 * sp.csr_matrix(sp.diags([1, 1], [0, 1], shape=(Ny, Ny)))
300+
301+
# Apply boundary conditions before Kronecker product
302+
if not pmc:
303+
cyf[0, 0] = 0
304+
305+
return sp.kron(sp.eye(Nx), cyf)
306+
307+
308+
def make_cyb(dls, shape, pmc):
309+
"""Backward colocation in y."""
310+
import scipy.sparse as sp
311+
312+
Nx, Ny = shape
313+
if Ny == 1:
314+
return sp.csr_matrix((Nx, Nx))
315+
316+
# Calculate weights for each position
317+
p1 = dls[:-1] # First grid spacing
318+
p2 = dls[1:] # Second grid spacing
319+
320+
weights_curr = np.ones(Ny)
321+
weights_curr[1:] = p1 / (p1 + p2)
322+
323+
weights_prev = np.zeros(Ny)
324+
weights_prev[:-1] = p2 / (p1 + p2)
325+
326+
# Create the matrix using diagonals
327+
cyb = sp.diags([weights_curr, weights_prev], [0, -1], shape=(Ny, Ny))
328+
329+
# Apply boundary conditions before Kronecker product
330+
# The matrix is already set up for PEC (cyb[0, 0] = 1)
331+
if pmc:
332+
cyb[0, 0] = 0
333+
334+
return sp.kron(sp.eye(Nx), cyb)
335+
336+
337+
def create_c_matrices(shape, dls, dmin_pmc=(False, False)):
338+
"""Make the colocation matrices. If dmin_pmc is True, the matrices will be modified
339+
to implement PMC boundary conditions, otherwise they will implement PEC."""
340+
341+
dlf, _ = dls
342+
cxf = make_cxf(dlf[0], shape, dmin_pmc[0])
343+
cxb = make_cxb(dlf[0], shape, dmin_pmc[0]) # Note: uses primal grid spacings
344+
cyf = make_cyf(dlf[1], shape, dmin_pmc[1])
345+
cyb = make_cyb(dlf[1], shape, dmin_pmc[1]) # Note: uses primal grid spacings
346+
347+
# cxf = sp.eye(shape[0] * shape[1])
348+
# cxb = sp.eye(shape[0] * shape[1])
349+
# cyf = sp.eye(shape[0] * shape[1])
350+
# cyb = sp.eye(shape[0] * shape[1])
351+
352+
return (cxf, cxb, cyf, cyb)

0 commit comments

Comments
 (0)