Skip to content

Automatically use periodic boundary on zero-size dimensions #2553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved performance of `tidy3d.web.delete_old()` for large folders.
- Upon initialization, an FDTD `Simulation` will now try to create all `ModeSolver` objects associated to `ModeSource`-s and `ModeMonitor`-s so they can be validated.
- `tidy3d.plugins.autograd.interpolate_spline()` and `tidy3d.plugins.autograd.add_at()` can now be called with keyword arguments during tracing.
- Zero-size dimensions automatically receive periodic boundary conditions instead of raising an error.

## [2.8.4] - 2025-05-15

Expand Down
6 changes: 0 additions & 6 deletions tests/test_components/test_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ def test_validation_from_simulation():
size=(0, 5, 5),
mode_spec=td.ModeSpec(angle_rotation=True, angle_theta=np.pi / 4),
freqs=[td.C_0],
boundary_spec=td.BoundarySpec.all_sides(td.Periodic()),
)

with pytest.raises(SetupError):
Expand All @@ -160,7 +159,6 @@ def test_validation_from_simulation():
size=(0, 5, 5),
mode_spec=td.ModeSpec(angle_rotation=True, angle_theta=np.pi / 4),
freqs=[td.C_0],
boundary_spec=td.BoundarySpec.all_sides(td.Periodic()),
)


Expand All @@ -169,16 +167,12 @@ def get_mode_sim():
permittivity_monitor = td.PermittivityMonitor(
size=(1, 1, 0), center=(0, 0, 0), name="eps", freqs=FS
)
boundary_spec = td.BoundarySpec(
x=td.Boundary.pml(), y=td.Boundary.periodic(), z=td.Boundary.pml()
)
sim = td.ModeSimulation(
size=SIZE_2D,
freqs=FS,
mode_spec=mode_spec,
grid_spec=td.GridSpec.auto(wavelength=td.C_0 / FS[0]),
monitors=[permittivity_monitor],
boundary_spec=boundary_spec,
)
return sim

Expand Down
23 changes: 13 additions & 10 deletions tests/test_components/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,16 +563,19 @@ def test_validate_zero_dim_boundaries():
pol_angle=0.0,
)

with pytest.raises(pydantic.ValidationError):
td.Simulation(
size=(1, 1, 0),
run_time=1e-12,
sources=[src],
boundary_spec=td.BoundarySpec(
x=td.Boundary.periodic(),
y=td.Boundary.periodic(),
z=td.Boundary.pml(),
),
with AssertLogLevel("WARNING", contains_str="Periodic"):
assert (
td.Simulation(
size=(1, 1, 0),
run_time=1e-12,
sources=[src],
boundary_spec=td.BoundarySpec(
x=td.Boundary.periodic(),
y=td.Boundary.periodic(),
z=td.Boundary.pml(),
),
).boundary_spec.z
== td.Boundary.periodic()
)

# zero-dim simulation with an absorbing boundary any other direction should not error
Expand Down
2 changes: 1 addition & 1 deletion tidy3d/components/eme/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1183,4 +1183,4 @@ def _cell_index_pairs(self) -> list[pd.NonNegativeInt]:
pairs = set(self.eme_grid_spec._cell_index_pairs)
return list(pairs)

_boundaries_for_zero_dims = validate_boundaries_for_zero_dims()
_boundaries_for_zero_dims = validate_boundaries_for_zero_dims(warn_on_change=False)
16 changes: 2 additions & 14 deletions tidy3d/components/mode/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pydantic.v1 as pd

from tidy3d.components.base import cached_property
from tidy3d.components.boundary import Boundary, BoundarySpec
from tidy3d.components.boundary import BoundarySpec
from tidy3d.components.geometry.base import Box
from tidy3d.components.grid.grid import Grid
from tidy3d.components.grid.grid_spec import GridSpec
Expand Down Expand Up @@ -213,18 +213,6 @@ def plane_in_sim_bounds(cls, val, values):
raise SetupError("'ModeSimulation.plane' must intersect 'ModeSimulation.geometry.")
return val

@pd.validator("boundary_spec", always=True)
def boundaries_for_zero_dims(cls, val, values):
"""Replace with periodic boundary along zero-size dimensions."""
boundaries = [val.x, val.y, val.z]
size = values.get("size")

for dim, size_dim in enumerate(size):
if size_dim == 0:
boundaries[dim] = Boundary.periodic()

return BoundarySpec(x=boundaries[0], y=boundaries[1], z=boundaries[2])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so this validator actually existed but it wasn't taking effect because _boundaries_for_zero_dims = validate_boundaries_for_zero_dims() was also included (and probably executed first?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think that's right


def _post_init_validators(self) -> None:
"""Call validators taking `self` that get run after init."""
ModeSolver._validate_mode_plane_radius(
Expand Down Expand Up @@ -524,4 +512,4 @@ def plot_pml_mode_plane(
def validate_pre_upload(self, source_required: bool = False):
self._mode_solver.validate_pre_upload(source_required=source_required)

_boundaries_for_zero_dims = validate_boundaries_for_zero_dims()
_boundaries_for_zero_dims = validate_boundaries_for_zero_dims(warn_on_change=False)
16 changes: 10 additions & 6 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
RF_FREQ_WARNING = 300e9


def validate_boundaries_for_zero_dims():
def validate_boundaries_for_zero_dims(warn_on_change: bool = True):
"""Error if absorbing boundaries, bloch boundaries, unmatching pec/pmc, or symmetry is used along a zero dimension."""

@pydantic.validator("boundary_spec", allow_reuse=True, always=True)
Expand All @@ -192,11 +192,15 @@ def boundaries_for_zero_dims(cls, val, values):
num_bloch_bdries = sum(isinstance(bnd, BlochBoundary) for bnd in boundary)

if num_absorbing_bdries > 0:
raise SetupError(
f"The simulation has zero size along the {axis} axis, so "
"using a PML or absorbing boundary along that axis is incorrect. "
f"Use either 'Periodic' or 'BlochBoundary' along {axis}."
)
pbc = Boundary(minus=Periodic(), plus=Periodic())
val = val.updated_copy(**{axis: pbc})
if warn_on_change:
log.warning(
f"The simulation has zero size along the {axis} axis, so "
"using a PML or absorbing boundary along that axis is incorrect. "
f"Use either 'Periodic' or 'BlochBoundary' along {axis}. "
"Using 'Periodic' boundary by default."
)

if num_bloch_bdries > 0:
raise SetupError(
Expand Down