Skip to content

Commit 0ebf00a

Browse files
authored
In boundary.py, num_layers can now be >= 1, but a warning message is generated if num_layers < 6. (#2606)
All the tests passed. I took the liberty to press the shiny "Squash and merge" button. I hope that was okay.
1 parent 04c5e3b commit 0ebf00a

File tree

3 files changed

+74
-9
lines changed

3 files changed

+74
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- `GaussianBeam` and `AstigmaticGaussianBeam` default `num_freqs` reset to 1 (it was set to 3 in v2.8.0) and a warning is issued for a broadband, angled beam for which `num_freqs` may not be sufficiently large.
1717
- Set the maximum `num_freqs` to 20 for all broadband sources (we have been warning about the introduction of this hard limit for a while).
1818
- Significantly improved performance of the `tidy3d.plugins.autograd.grey_dilation` morphological operation and its gradient calculation. The new implementation is orders of magnitude faster, especially for large arrays and kernel sizes.
19+
- Warnings are now generated (instead of errors) when instantiating `PML`, `StablePML`,
20+
or `Absorber` classes (or when invoking `pml()`, `stable_pml()`, or `absorber()` functions)
21+
with fewer layers than recommended.
1922

2023
### Fixed
2124
- Arrow lengths are now scaled consistently in the X and Y directions, and their lengths no longer exceed the height of the plot window.

tests/test_components/test_boundaries.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
import tidy3d as td
99
from tidy3d.components.boundary import (
10+
MIN_NUM_ABSORBER_LAYERS,
11+
MIN_NUM_PML_LAYERS,
12+
MIN_NUM_STABLE_PML_LAYERS,
1013
PML,
1114
Absorber,
1215
BlochBoundary,
@@ -73,7 +76,7 @@ def test_boundaryedge_types(plane_wave_dir):
7376

7477

7578
def test_boundary_validators():
76-
"""Test the validators in class `Boundary`"""
79+
"""Test the validators in class ``Boundary``"""
7780

7881
bloch = BlochBoundary(bloch_vec=1)
7982
pec = PECBoundary()
@@ -91,20 +94,20 @@ def test_boundary_validators():
9194

9295
@pytest.mark.parametrize("boundary, log_level", [(PMCBoundary(), None), (Periodic(), "WARNING")])
9396
def test_boundary_validator_warnings(boundary, log_level):
94-
"""Test the validators in class `Boundary` which should show a warning but not an error"""
97+
"""Test the validators in class ``Boundary`` which should show a warning but not an error"""
9598
with AssertLogLevel(log_level):
9699
_ = Boundary(plus=PECBoundary(), minus=boundary)
97100

98101

99102
@pytest.mark.parametrize("boundary, log_level", [(PMCBoundary(), None), (Periodic(), "WARNING")])
100103
def test_boundary_validator_warnings_switched(boundary, log_level):
101-
"""Test the validators in class `Boundary` which should show a warning but not an error"""
104+
"""Test the validators in class ``Boundary`` which should show a warning but not an error"""
102105
with AssertLogLevel(log_level):
103106
_ = Boundary(minus=PECBoundary(), plus=boundary)
104107

105108

106109
def test_boundary():
107-
"""Test that the various classmethods and combinations for Boundary work correctly."""
110+
"""Test that the various classmethods and combinations for ``Boundary`` to work correctly."""
108111

109112
# periodic
110113
boundary = Boundary.periodic()
@@ -144,7 +147,7 @@ def test_boundary():
144147

145148

146149
def test_boundaryspec_classmethods():
147-
"""Test that the classmethods for BoundarySpec work correctly."""
150+
"""Test that the classmethods for ``BoundarySpec`` work correctly."""
148151

149152
# pml
150153
boundary_spec = BoundarySpec.pml(x=False, y=True, z=True)
@@ -188,3 +191,26 @@ def test_boundaryspec_classmethods():
188191
assert all(
189192
isinstance(boundary, PML) for boundary_dim in boundaries for boundary in boundary_dim
190193
)
194+
195+
196+
@pytest.mark.parametrize("absorber_type", [PML, StablePML, Absorber])
197+
def test_num_layers_validator(absorber_type):
198+
"""Test the Field validators that enforce ``num_layers>0``."""
199+
with pytest.raises(pydantic.ValidationError):
200+
_ = absorber_type(num_layers=0)
201+
202+
203+
@pytest.mark.parametrize(
204+
"absorber_type, num_layers",
205+
[
206+
(PML, MIN_NUM_PML_LAYERS),
207+
(StablePML, MIN_NUM_STABLE_PML_LAYERS),
208+
(Absorber, MIN_NUM_ABSORBER_LAYERS),
209+
],
210+
)
211+
def test_num_layers_validator_warning(absorber_type, num_layers):
212+
"""Test the validators in ``PML`` which should display a warning not an error."""
213+
with AssertLogLevel(None):
214+
_ = absorber_type(num_layers=num_layers)
215+
with AssertLogLevel("WARNING"):
216+
_ = absorber_type(num_layers=num_layers - 1)

tidy3d/components/boundary.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@
1818
from .types import TYPE_TAG_STR, Axis, Complex
1919

2020
MIN_NUM_PML_LAYERS = 6
21+
MIN_NUM_STABLE_PML_LAYERS = 6
22+
MIN_NUM_ABSORBER_LAYERS = 6
23+
24+
25+
def warn_num_layers_factory(min_num_layers: int, descr: str):
26+
"""Several similar classes defined have a ``num_layers`` data member, and they generate
27+
similar warning messages when ``num_layers`` is too small. This function creates a pydantic
28+
validator which can be shared with all of these classes to create these warning messages."""
29+
30+
@pd.validator("num_layers", allow_reuse=True, always=True)
31+
def _warn_num_layers(cls, val):
32+
if val < min_num_layers:
33+
cls_name = cls.__name__
34+
log.warning(
35+
f"A {descr} ({cls_name}) boundary was created with {val} layers. "
36+
f"{cls_name}s with less than {min_num_layers} layers are highly subject to "
37+
"unwanted numerical artifacts. We strongly recommend "
38+
"increasing the number of layers. For more details refer to: "
39+
f"'https://docs.flexcompute.com/projects/tidy3d/en/latest/api/_autosummary/tidy3d.{cls_name}.html'.",
40+
log_once=True,
41+
)
42+
return val
43+
44+
return _warn_num_layers
2145

2246

2347
class BoundaryEdge(ABC, Tidy3dBaseModel):
@@ -267,7 +291,7 @@ class AbsorberSpec(BoundaryEdge):
267291
...,
268292
title="Number of Layers",
269293
description="Number of layers of standard PML.",
270-
ge=MIN_NUM_PML_LAYERS,
294+
ge=1,
271295
)
272296
parameters: AbsorberParams = pd.Field(
273297
...,
@@ -384,7 +408,7 @@ class PML(AbsorberSpec):
384408
12,
385409
title="Number of Layers",
386410
description="Number of layers of standard PML.",
387-
ge=MIN_NUM_PML_LAYERS,
411+
ge=1,
388412
)
389413

390414
parameters: PMLParams = pd.Field(
@@ -393,6 +417,10 @@ class PML(AbsorberSpec):
393417
description="Parameters of the complex frequency-shifted absorption poles.",
394418
)
395419

420+
_warn_num_layers = warn_num_layers_factory(
421+
min_num_layers=MIN_NUM_PML_LAYERS, descr="perfectly-matched layer"
422+
)
423+
396424

397425
class StablePML(AbsorberSpec):
398426
"""Specifies a 'stable' PML along a single dimension.
@@ -422,7 +450,7 @@ class StablePML(AbsorberSpec):
422450
40,
423451
title="Number of Layers",
424452
description="Number of layers of 'stable' PML.",
425-
ge=MIN_NUM_PML_LAYERS,
453+
ge=1,
426454
)
427455

428456
parameters: PMLParams = pd.Field(
@@ -431,6 +459,10 @@ class StablePML(AbsorberSpec):
431459
description="'Stable' parameters of the complex frequency-shifted absorption poles.",
432460
)
433461

462+
_warn_num_layers = warn_num_layers_factory(
463+
min_num_layers=MIN_NUM_STABLE_PML_LAYERS, descr="stable perfectly-matched layer"
464+
)
465+
434466

435467
class Absorber(AbsorberSpec):
436468
"""Specifies an adiabatic absorber along a single dimension.
@@ -475,7 +507,7 @@ class Absorber(AbsorberSpec):
475507
40,
476508
title="Number of Layers",
477509
description="Number of layers of absorber to add to + and - boundaries.",
478-
ge=MIN_NUM_PML_LAYERS,
510+
ge=1,
479511
)
480512

481513
parameters: AbsorberParams = pd.Field(
@@ -484,6 +516,10 @@ class Absorber(AbsorberSpec):
484516
description="Adiabatic absorber parameters.",
485517
)
486518

519+
_warn_num_layers = warn_num_layers_factory(
520+
min_num_layers=MIN_NUM_ABSORBER_LAYERS, descr="absorber"
521+
)
522+
487523

488524
# pml types allowed in simulation init
489525
PMLTypes = Union[PML, StablePML, Absorber, None]

0 commit comments

Comments
 (0)