Skip to content
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

add post init validators and check if structures terminate in PML #774

Merged
merged 1 commit into from
Mar 23, 2023
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 @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `SimulationData.plot_field` accepts new field componets and values, including the Poynting vector.
- `SimulationData.get_poynting_vector` for calculating the 3D Poynting vector at the Yee cell centers.
- Post init validation of Tidy3D components.

### Changed
- `export_matlib_to_file` in `material_library` exports material's full name in additional to abbreviation.
Expand Down
2 changes: 1 addition & 1 deletion requirements/basic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ h5py>=3.0.0
rich<12.6.0 # note: rich >= 12.6 adds double progressbars
matplotlib
shapely>=2.0
pydantic>=1.10.0
pydantic>=1.10.0,<2.0.0
PyYAML
dask
toml
Expand Down
45 changes: 40 additions & 5 deletions tests/test_components/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _test_monitor_size():
s.validate_pre_upload()


@pytest.mark.parametrize("freq, log_level", [(1.5, "warning"), (2.5, None), (3.5, "warning")])
@pytest.mark.parametrize("freq, log_level", [(1.5, "warning"), (2.5, "info"), (3.5, "warning")])
def test_monitor_medium_frequency_range(log_capture, freq, log_level):
# monitor frequency above or below a given medium's range should throw a warning

Expand All @@ -209,7 +209,7 @@ def test_monitor_medium_frequency_range(log_capture, freq, log_level):
assert_log_level(log_capture, log_level)


@pytest.mark.parametrize("fwidth, log_level", [(0.1, "warning"), (2, None)])
@pytest.mark.parametrize("fwidth, log_level", [(0.1, "warning"), (2, "info")])
def test_monitor_simulation_frequency_range(log_capture, fwidth, log_level):
# monitor frequency outside of the simulation's frequency range should throw a warning

Expand Down Expand Up @@ -559,7 +559,7 @@ def test_large_grid_size(log_capture, grid_size, log_level):
assert_log_level(log_capture, log_level)


@pytest.mark.parametrize("box_size,log_level", [(0.001, None), (9.9, "warning"), (20, None)])
@pytest.mark.parametrize("box_size,log_level", [(0.001, "info"), (9.9, "warning"), (20, "info")])
def test_sim_structure_gap(log_capture, box_size, log_level):
"""Make sure the gap between a structure and PML is not too small compared to lambda0."""
medium = td.Medium(permittivity=2)
Expand Down Expand Up @@ -844,7 +844,7 @@ def test_diffraction_medium():
@pytest.mark.parametrize(
"box_size,log_level",
[
((0.1, 0.1, 0.1), None),
((0.1, 0.1, 0.1), "info"),
((1, 0.1, 0.1), "warning"),
((0.1, 1, 0.1), "warning"),
((0.1, 0.1, 1), "warning"),
Expand All @@ -870,6 +870,42 @@ def test_sim_structure_extent(log_capture, box_size, log_level):
assert_log_level(log_capture, log_level)


@pytest.mark.parametrize(
"box_length,absorb_type,log_level",
[
(0, "PML", "info"),
(0.5, "PML", "info"),
(1, "PML", "info"),
(1.5, "PML", "warning"),
(1.5, "absorber", "info"),
(5.0, "PML", "info"),
],
)
def test_sim_validate_structure_bounds_pml(log_capture, box_length, absorb_type, log_level):
"""Make sure we warn if structure bounds are within the PML exactly to simulation edges."""

boundary = td.PML() if absorb_type == "PML" else td.Absorber()

src = td.UniformCurrentSource(
source_time=td.GaussianPulse(freq0=3e14, fwidth=1e13),
size=(0, 0, 0),
polarization="Ex",
)
box = td.Structure(
geometry=td.Box(size=(box_length, 0.5, 0.5), center=(0, 0, 0)),
medium=td.Medium(permittivity=2),
)
sim = td.Simulation(
size=(1, 1, 1),
structures=[box],
sources=[src],
run_time=1e-12,
boundary_spec=td.BoundarySpec.all_sides(boundary=boundary),
)

assert_log_level(log_capture, log_level)


def test_num_mediums():
"""Make sure we error if too many mediums supplied."""

Expand Down Expand Up @@ -1227,7 +1263,6 @@ def test_tfsf_structures_grid(log_capture):
)
],
)
sim.validate_pre_upload()

# TFSF box must not intersect a custom medium
Nx, Ny, Nz = 10, 9, 8
Expand Down
8 changes: 8 additions & 0 deletions tidy3d/components/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ class Tidy3dBaseModel(pydantic.BaseModel):
`Pydantic Models <https://pydantic-docs.helpmanual.io/usage/models/>`_
"""

def __init__(self, **kwargs):
"""Init method, includes post-init validators."""
super().__init__(**kwargs)
self._post_init_validators()

def _post_init_validators(self) -> None:
"""Call validators taking ``self`` that get run after init, implement in subclasses."""

def __init_subclass__(cls) -> None:
"""Things that are done to each of the models."""

Expand Down
113 changes: 73 additions & 40 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,79 @@ def _check_normalize_index(cls, val, values):

return val

""" Post-init validators """

def _post_init_validators(self) -> None:
"""Call validators taking z`self` that get run after init."""
self._validate_no_structures_pml()
self._validate_tfsf_nonuniform_grid()

def _validate_no_structures_pml(self) -> None:
"""Ensure no structures terminate / have bounds inside of PML."""

pml_thicks = self.pml_thicknesses
sim_bounds = self.bounds
bound_spec = self.boundary_spec.to_list
for i, structure in enumerate(self.structures):
geo_bounds = structure.geometry.bounds
for sim_bound, geo_bound, pml_thick, bound_dim in zip(
sim_bounds, geo_bounds, pml_thicks, bound_spec
):
for sim_pos, geo_pos, pml, pm_val, bound_edge in zip(
sim_bound, geo_bound, pml_thick, (-1, 1), bound_dim
):
sim_pos_pml = sim_pos + pm_val * pml
in_pml_plus = (pm_val > 0) and (sim_pos < geo_pos <= sim_pos_pml)
in_pml_mnus = (pm_val < 0) and (sim_pos > geo_pos >= sim_pos_pml)
tylerflex marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(bound_edge, Absorber) and (in_pml_plus or in_pml_mnus):
log.warning(
f"A bound of Simulation.structures[{i}] was detected as being within "
"the simulation PML. We recommend extending structures to infinity or "
"completely outside of the simulation PML to "
"avoid unexpected effects when the structures are not translationally "
"invariant within the PML. Skipping rest of structures."
)
return

def _validate_tfsf_nonuniform_grid(self) -> None:
"""Warn if the grid is nonuniform along the directions tangential to the injection plane,
inside the TFSF box.
"""
# if the grid is uniform in all directions, there's no need to proceed
if not (self.grid_spec.auto_grid_used or self.grid_spec.custom_grid_used):
return

for source in self.sources:
if not isinstance(source, TFSF):
continue

centers = self.grid.centers.to_list
sizes = self.grid.sizes.to_list
tfsf_bounds = source.bounds
_, plane_inds = source.pop_axis([0, 1, 2], axis=source.injection_axis)
grid_list = [self.grid_spec.grid_x, self.grid_spec.grid_y, self.grid_spec.grid_z]
for ind in plane_inds:
grid_type = grid_list[ind]
if isinstance(grid_type, UniformGrid):
continue

sizes_in_tfsf = [
size
for size, center in zip(sizes[ind], centers[ind])
if tfsf_bounds[0][ind] <= center <= tfsf_bounds[1][ind]
]

# check if all the grid sizes are sufficiently unequal
if not np.all(np.isclose(sizes_in_tfsf, sizes_in_tfsf[0])):
log.warning(
f"The grid is nonuniform along the '{'xyz'[ind]}' axis, which may lead "
"to sub-optimal cancellation of the incident field in the scattered-field "
"region for the total-field scattered-field (TFSF) source "
f"'{source.name}'. For best results, we recomended ensuring a uniform "
"grid in both directions tangential to the TFSF injection axis, "
f"'{'xyz'[source.injection_axis]}'. "
)

""" Pre submit validation (before web.upload()) """

def validate_pre_upload(self) -> None:
Expand All @@ -803,7 +876,6 @@ def validate_pre_upload(self) -> None:
self._validate_monitor_size()
self._validate_datasets_not_none()
self._validate_tfsf_structure_intersections()
tylerflex marked this conversation as resolved.
Show resolved Hide resolved
self._validate_tfsf_nonuniform_grid()
# self._validate_run_time()

def _validate_size(self) -> None:
Expand Down Expand Up @@ -936,45 +1008,6 @@ def _validate_tfsf_structure_intersections(self) -> None:
f" '{'xyz'[source.injection_axis]}'."
)

def _validate_tfsf_nonuniform_grid(self) -> None:
"""Warn if the grid is nonuniform along the directions tangential to the injection plane,
inside the TFSF box.
"""
# if the grid is uniform in all directions, there's no need to proceed
if not (self.grid_spec.auto_grid_used or self.grid_spec.custom_grid_used):
return

for source in self.sources:
if not isinstance(source, TFSF):
continue

centers = self.grid.centers.to_list
sizes = self.grid.sizes.to_list
tfsf_bounds = source.bounds
_, plane_inds = source.pop_axis([0, 1, 2], axis=source.injection_axis)
grid_list = [self.grid_spec.grid_x, self.grid_spec.grid_y, self.grid_spec.grid_z]
for ind in plane_inds:
grid_type = grid_list[ind]
if isinstance(grid_type, UniformGrid):
continue

sizes_in_tfsf = [
size
for size, center in zip(sizes[ind], centers[ind])
if tfsf_bounds[0][ind] <= center <= tfsf_bounds[1][ind]
]

# check if all the grid sizes are sufficiently unequal
if not np.all(np.isclose(sizes_in_tfsf, sizes_in_tfsf[0])):
log.warning(
f"The grid is nonuniform along the '{'xyz'[ind]}' axis, which may lead "
"to sub-optimal cancellation of the incident field in the scattered-field "
"region for the total-field scattered-field (TFSF) source "
f"'{source.name}'. For best results, we recomended ensuring a uniform "
"grid in both directions tangential to the TFSF injection axis, "
f"'{'xyz'[source.injection_axis]}'. "
)

""" Accounting """

@cached_property
Expand Down