Skip to content

Commit 688f30f

Browse files
ABC boundary conditions
1 parent e8ec6e0 commit 688f30f

File tree

4 files changed

+710
-8
lines changed

4 files changed

+710
-8
lines changed

tests/test_components/test_boundaries.py

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

tidy3d/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
# boundary
9797
from .components.boundary import (
9898
PML,
99+
ABCBoundary,
99100
Absorber,
100101
AbsorberParams,
101102
BlochBoundary,
@@ -106,6 +107,7 @@
106107
DefaultAbsorberParameters,
107108
DefaultPMLParameters,
108109
DefaultStablePMLParameters,
110+
ModeABCBoundary,
109111
PECBoundary,
110112
Periodic,
111113
PMCBoundary,
@@ -534,6 +536,8 @@ def set_logging_level(level: str) -> None:
534536
"Periodic",
535537
"PECBoundary",
536538
"PMCBoundary",
539+
"ABCBoundary",
540+
"ModeABCBoundary",
537541
"PML",
538542
"StablePML",
539543
"Absorber",

0 commit comments

Comments
 (0)