From 4076fd15ea376b62d20d2db728a84f3f0684e039 Mon Sep 17 00:00:00 2001 From: Casey Wojcik Date: Fri, 6 Jun 2025 12:26:42 -0700 Subject: [PATCH] Automatically use periodic boundary on zero-size dimensions --- CHANGELOG.md | 1 + tests/test_components/test_mode.py | 6 ------ tests/test_components/test_simulation.py | 23 +++++++++++++---------- tidy3d/components/eme/simulation.py | 2 +- tidy3d/components/mode/simulation.py | 16 ++-------------- tidy3d/components/simulation.py | 16 ++++++++++------ 6 files changed, 27 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae1c0eb1..8fbe2fc4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/tests/test_components/test_mode.py b/tests/test_components/test_mode.py index 8c5cd4377..4c03fbc28 100644 --- a/tests/test_components/test_mode.py +++ b/tests/test_components/test_mode.py @@ -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): @@ -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()), ) @@ -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 diff --git a/tests/test_components/test_simulation.py b/tests/test_components/test_simulation.py index 44b54323d..999ca4a00 100644 --- a/tests/test_components/test_simulation.py +++ b/tests/test_components/test_simulation.py @@ -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 diff --git a/tidy3d/components/eme/simulation.py b/tidy3d/components/eme/simulation.py index 3f2a6b0d6..7ddaff66b 100644 --- a/tidy3d/components/eme/simulation.py +++ b/tidy3d/components/eme/simulation.py @@ -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) diff --git a/tidy3d/components/mode/simulation.py b/tidy3d/components/mode/simulation.py index dd94682c8..2d737d683 100644 --- a/tidy3d/components/mode/simulation.py +++ b/tidy3d/components/mode/simulation.py @@ -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 @@ -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]) - def _post_init_validators(self) -> None: """Call validators taking `self` that get run after init.""" ModeSolver._validate_mode_plane_radius( @@ -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) diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index 9373e15bf..852159f30 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -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) @@ -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(