Skip to content

Commit 59f342e

Browse files
ABC boundary conditions
1 parent eaf1f0a commit 59f342e

File tree

4 files changed

+709
-7
lines changed

4 files changed

+709
-7
lines changed

tests/test_components/test_boundaries.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,274 @@ def test_num_layers_validator_warning(absorber_type, num_layers):
214214
_ = absorber_type(num_layers=num_layers)
215215
with AssertLogLevel("WARNING"):
216216
_ = absorber_type(num_layers=num_layers - 1)
217+
218+
219+
def test_abc_boundary():
220+
# check basic instance
221+
_ = td.ABCBoundary()
222+
223+
# check enforced perm (and conductivity)
224+
_ = td.ABCBoundary(permittivity=2)
225+
_ = td.ABCBoundary(permittivity=2, conductivity=0.1)
226+
227+
with pytest.raises(pydantic.ValidationError):
228+
_ = td.ABCBoundary(permittivity=0)
229+
230+
with pytest.raises(pydantic.ValidationError):
231+
_ = td.ABCBoundary(permittivity=2, conductivity=-0.1)
232+
233+
with pytest.raises(pydantic.ValidationError):
234+
_ = td.ABCBoundary(permittivity=None, conductivity=-0.1)
235+
236+
# test mode abc
237+
wvl_um = 1
238+
freq0 = td.C_0 / wvl_um
239+
mode_abc = td.ModeABCBoundary(
240+
plane=td.Box(size=(1, 1, 0)),
241+
mode_spec=td.ModeSpec(num_modes=2),
242+
mode_index=1,
243+
frequency=freq0,
244+
)
245+
246+
with pytest.raises(pydantic.ValidationError):
247+
_ = td.ModeABCBoundary(
248+
plane=td.Box(size=(1, 1, 0)),
249+
mode_spec=td.ModeSpec(num_modes=2),
250+
mode_index=1,
251+
frequency=-1,
252+
)
253+
254+
with pytest.raises(pydantic.ValidationError):
255+
_ = td.ModeABCBoundary(
256+
plane=td.Box(size=(1, 1, 0)),
257+
mode_spec=td.ModeSpec(num_modes=2),
258+
mode_index=-1,
259+
frequency=freq0,
260+
)
261+
262+
with pytest.raises(pydantic.ValidationError):
263+
_ = td.ModeABCBoundary(
264+
plane=td.Box(size=(1, 1, 1)),
265+
mode_spec=td.ModeSpec(num_modes=2),
266+
mode_index=0,
267+
frequency=freq0,
268+
)
269+
270+
# from mode source
271+
mode_source = td.ModeSource(
272+
size=(1, 1, 0),
273+
source_time=td.GaussianPulse(freq0=freq0, fwidth=0.2 * freq0),
274+
mode_spec=td.ModeSpec(num_modes=2),
275+
mode_index=1,
276+
direction="+",
277+
)
278+
mode_abc_from_source = td.ModeABCBoundary.from_source(mode_source)
279+
assert mode_abc == mode_abc_from_source
280+
281+
# from mode monitor
282+
mode_monitor = td.ModeMonitor(
283+
size=(1, 1, 0), mode_spec=td.ModeSpec(num_modes=2), freqs=[freq0], name="mnt"
284+
)
285+
mode_abc_from_monitor = td.ModeABCBoundary.from_monitor(
286+
mode_monitor, mode_index=1, frequency=freq0
287+
)
288+
assert mode_abc == mode_abc_from_monitor
289+
290+
# in Boundary
291+
_ = td.Boundary(
292+
minus=td.ABCBoundary(permittivity=3), plus=td.ModeABCBoundary(plane=td.Box(size=(1, 1, 0)))
293+
)
294+
_ = td.Boundary.abc(permittivity=3, conductivity=1e-5)
295+
abc_boundary = td.Boundary.mode_abc(
296+
plane=td.Box(size=(1, 1, 0)),
297+
mode_spec=td.ModeSpec(num_modes=2),
298+
mode_index=1,
299+
frequency=freq0,
300+
)
301+
abc_boundary_from_source = td.Boundary.mode_abc_from_source(mode_source)
302+
abc_boundary_from_monitor = td.Boundary.mode_abc_from_monitor(
303+
mode_monitor, mode_index=1, frequency=freq0
304+
)
305+
assert abc_boundary == abc_boundary_from_source
306+
assert abc_boundary == abc_boundary_from_monitor
307+
308+
with pytest.raises(pydantic.ValidationError):
309+
_ = td.Boundary(minus=td.Periodic(), plus=td.ABCBoundary())
310+
311+
with pytest.raises(pydantic.ValidationError):
312+
_ = td.Boundary(minus=td.Periodic(), plus=td.ModeABCBoundary(plane=td.Box(size=(1, 1, 0))))
313+
314+
# in Simulation
315+
_ = td.Simulation(
316+
center=[0, 0, 0],
317+
size=[1, 1, 1],
318+
grid_spec=td.GridSpec.auto(
319+
min_steps_per_wvl=10,
320+
wavelength=wvl_um,
321+
),
322+
sources=[],
323+
run_time=1e-20,
324+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
325+
)
326+
327+
# validate ABC medium is not anisotorpic
328+
with pytest.raises(pydantic.ValidationError):
329+
_ = td.Simulation(
330+
center=[0, 0, 0],
331+
size=[1, 1, 1],
332+
grid_spec=td.GridSpec.auto(
333+
min_steps_per_wvl=10,
334+
wavelength=wvl_um,
335+
),
336+
sources=[],
337+
medium=td.AnisotropicMedium(xx=td.Medium(), yy=td.Medium(), zz=td.Medium()),
338+
run_time=1e-20,
339+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
340+
)
341+
342+
# validate homogeneous medium when permittivity=None, that is, automatic detection
343+
box_crossing_boundary = td.Structure(
344+
geometry=td.Box(size=(0.3, 0.2, td.inf)),
345+
medium=td.Medium(permittivity=2),
346+
)
347+
# ok if ABC boundary is not crossed
348+
_ = td.Simulation(
349+
center=[0, 0, 0],
350+
size=[1, 1, 1],
351+
grid_spec=td.GridSpec.auto(
352+
min_steps_per_wvl=10,
353+
wavelength=wvl_um,
354+
),
355+
sources=[],
356+
structures=[box_crossing_boundary],
357+
run_time=1e-20,
358+
boundary_spec=td.BoundarySpec(
359+
x=td.Boundary.abc(),
360+
y=td.Boundary.abc(),
361+
z=td.Boundary.pml(),
362+
),
363+
)
364+
# or if we override manually
365+
_ = td.Simulation(
366+
center=[0, 0, 0],
367+
size=[1, 1, 1],
368+
grid_spec=td.GridSpec.auto(
369+
min_steps_per_wvl=10,
370+
wavelength=wvl_um,
371+
),
372+
sources=[],
373+
structures=[box_crossing_boundary],
374+
run_time=1e-20,
375+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary(permittivity=2)),
376+
)
377+
# not ok if ABC boudary is crossed
378+
with pytest.raises(pydantic.ValidationError):
379+
_ = td.Simulation(
380+
center=[0, 0, 0],
381+
size=[1, 1, 1],
382+
grid_spec=td.GridSpec.auto(
383+
min_steps_per_wvl=10,
384+
wavelength=wvl_um,
385+
),
386+
sources=[],
387+
structures=[box_crossing_boundary],
388+
run_time=1e-20,
389+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
390+
)
391+
# edge case when a structure exactly coincides with simulation domain
392+
_ = td.Simulation(
393+
center=[0, 0, 0],
394+
size=[1, 1, 1],
395+
grid_spec=td.GridSpec.auto(
396+
min_steps_per_wvl=10,
397+
wavelength=wvl_um,
398+
),
399+
sources=[],
400+
structures=[box_crossing_boundary.updated_copy(geometry=td.Box(size=(1, 1, 1)))],
401+
run_time=1e-20,
402+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
403+
)
404+
405+
# warning for possibly non-uniform custom medium
406+
with AssertLogLevel(
407+
"WARNING", contains_str="Nonuniform custom medium detected on an 'ABCBoundary'"
408+
):
409+
_ = td.Simulation(
410+
center=[0, 0, 0],
411+
size=[1, 1, 1],
412+
grid_spec=td.GridSpec.auto(
413+
min_steps_per_wvl=10,
414+
wavelength=wvl_um,
415+
),
416+
sources=[],
417+
medium=td.CustomMedium(
418+
permittivity=td.SpatialDataArray([[[2, 3]]], coords=dict(x=[0], y=[0], z=[0, 1]))
419+
),
420+
run_time=1e-20,
421+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
422+
)
423+
424+
# disallow ABC boundaries in zero dimensions
425+
with pytest.raises(pydantic.ValidationError):
426+
_ = td.Simulation(
427+
center=[0, 0, 0],
428+
size=[1, 1, 0],
429+
grid_spec=td.GridSpec.auto(
430+
min_steps_per_wvl=10,
431+
wavelength=wvl_um,
432+
),
433+
sources=[],
434+
structures=[box_crossing_boundary],
435+
run_time=1e-20,
436+
boundary_spec=td.BoundarySpec.all_sides(td.ABCBoundary()),
437+
)
438+
439+
# need to define frequence for ModeABCBoundary
440+
# manually
441+
_ = td.Simulation(
442+
center=[0, 0, 0],
443+
size=[1, 1, 1],
444+
grid_spec=td.GridSpec.auto(
445+
min_steps_per_wvl=10,
446+
wavelength=wvl_um,
447+
),
448+
sources=[],
449+
run_time=1e-20,
450+
boundary_spec=td.BoundarySpec.all_sides(
451+
td.ModeABCBoundary(plane=td.Box(size=(1, 1, 0)), frequency=freq0)
452+
),
453+
)
454+
# or at least one source
455+
_ = td.Simulation(
456+
center=[0, 0, 0],
457+
size=[1, 1, 1],
458+
grid_spec=td.GridSpec.auto(
459+
min_steps_per_wvl=10,
460+
wavelength=wvl_um,
461+
),
462+
sources=[mode_source],
463+
run_time=1e-20,
464+
boundary_spec=td.BoundarySpec.all_sides(td.ModeABCBoundary(plane=td.Box(size=(1, 1, 0)))),
465+
)
466+
# multiple sources with different central freqs is still ok, but show warning
467+
with AssertLogLevel(
468+
"WARNING", contains_str="The central frequency of the first source will be used"
469+
):
470+
_ = td.Simulation(
471+
center=[0, 0, 0],
472+
size=[1, 1, 1],
473+
grid_spec=td.GridSpec.auto(
474+
min_steps_per_wvl=10,
475+
wavelength=wvl_um,
476+
),
477+
sources=[
478+
mode_source,
479+
mode_source.updated_copy(
480+
source_time=td.GaussianPulse(freq0=2 * freq0, fwidth=0.2 * freq0)
481+
),
482+
],
483+
run_time=1e-20,
484+
boundary_spec=td.BoundarySpec.all_sides(
485+
td.ModeABCBoundary(plane=td.Box(size=(1, 1, 0)))
486+
),
487+
)

tidy3d/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
# boundary
104104
from .components.boundary import (
105105
PML,
106+
ABCBoundary,
106107
Absorber,
107108
AbsorberParams,
108109
BlochBoundary,
@@ -113,6 +114,7 @@
113114
DefaultAbsorberParameters,
114115
DefaultPMLParameters,
115116
DefaultStablePMLParameters,
117+
ModeABCBoundary,
116118
PECBoundary,
117119
Periodic,
118120
PMCBoundary,
@@ -713,4 +715,6 @@ def set_logging_level(level: str) -> None:
713715
"set_logging_console",
714716
"set_logging_file",
715717
"wavelengths",
718+
"ABCBoundary",
719+
"ModeABCBoundary",
716720
]

0 commit comments

Comments
 (0)