Skip to content

Commit 90f9f8c

Browse files
jewettaijfcyaugenst-flex
authored andcommitted
enhance custom validator error messages with object name fields (when specified)
1 parent 6594a04 commit 90f9f8c

File tree

4 files changed

+78
-27
lines changed

4 files changed

+78
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Warnings are now generated (instead of errors) when instantiating `PML`, `StablePML`,
2323
or `Absorber` classes (or when invoking `pml()`, `stable_pml()`, or `absorber()` functions)
2424
with fewer layers than recommended.
25+
- Warnings and error messages originating from `Structure`, `Source`, or `Monitor` classes now refer to problematic objects by their user-supplied `name` attribute, alongside their index.
2526

2627
### Fixed
2728
- 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_simulation.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3648,3 +3648,45 @@ def test_create_sim_multiphysics_with_incompatibilities():
36483648
),
36493649
],
36503650
)
3651+
3652+
3653+
def test_messages_contain_object_names():
3654+
"""Make sure that errors and warnings contain the name of the object."""
3655+
# Note: This function currently tests for out-of-bounds errors and warnings.
3656+
# Create an empty simulation.
3657+
sim = td.Simulation(
3658+
size=(1, 1, 1),
3659+
grid_spec=td.GridSpec.auto(wavelength=4),
3660+
run_time=1e-12,
3661+
)
3662+
3663+
# Test 1) Create a structure lying outside the simulation boundary.
3664+
# Check that a warning message is generated containing the structure's `name`.
3665+
name = "structure_123"
3666+
structure = td.Structure(
3667+
name=name,
3668+
geometry=td.Box(center=(1.0, 0.0, 0.0), size=(0.5, 0.5, 0.5)),
3669+
medium=td.Medium(permittivity=2.0),
3670+
)
3671+
with AssertLogLevel("WARNING", contains_str=name):
3672+
_ = sim.updated_copy(structures=[structure])
3673+
3674+
# Test 2) Create a source lying outside the simulation boundary.
3675+
# Check that an error message is generated containing the source's `name`.
3676+
name = "source_123"
3677+
source = td.UniformCurrentSource(
3678+
name=name,
3679+
center=(0, -1.0, 0),
3680+
size=(1, 0, 0.5),
3681+
polarization="Ex",
3682+
source_time=td.GaussianPulse(freq0=100e14, fwidth=10e14),
3683+
)
3684+
with pytest.raises(pydantic.ValidationError, match=name) as e:
3685+
_ = sim.updated_copy(sources=[source])
3686+
3687+
# Test 3) Create a monitor lying outside the simulation boundary.
3688+
# Check that an error message is generated containing the monitor's `name`.
3689+
name = "monitor_123"
3690+
monitor = td.FieldMonitor(name=name, center=(-1.0, 0, 0), size=(0.5, 0, 1), freqs=[100e14])
3691+
with pytest.raises(pydantic.ValidationError, match=name) as e:
3692+
_ = sim.updated_copy(monitors=[monitor])

tidy3d/components/simulation.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
from .validators import (
118118
assert_objects_contained_in_sim_bounds,
119119
assert_objects_in_sim_bounds,
120+
named_obj_descr,
120121
validate_mode_objects_symmetry,
121122
)
122123
from .viz import (
@@ -2961,8 +2962,9 @@ def _validate_2d_geometry_has_2d_medium(cls, val, values):
29612962
for geom in flatten_groups(structure.geometry):
29622963
zero_dims = geom.zero_dims
29632964
if len(zero_dims) > 0:
2965+
obj_descr = named_obj_descr(structure, "structures", i)
29642966
consolidated_logger.warning(
2965-
f"Structure at 'structures[{i}]' has geometry with zero size along "
2967+
f"Structure: {obj_descr} has geometry with zero size along "
29662968
f"dimensions {zero_dims}, and with a medium that is not a 'Medium2D'. "
29672969
"This is probably not correct, since the resulting simulation will "
29682970
"depend on the details of the numerical grid. Consider either "
@@ -3018,10 +3020,11 @@ def _structures_not_close_pml(cls, val, values):
30183020

30193021
with log as consolidated_logger:
30203022

3021-
def warn(istruct, side):
3023+
def warn(structure, istruct, side):
30223024
"""Warning message for a structure too close to PML."""
3025+
obj_descr = named_obj_descr(structure, "structures", istruct)
30233026
consolidated_logger.warning(
3024-
f"Structure at structures[{istruct}] was detected as being less "
3027+
f"Structure: {obj_descr} was detected as being less "
30253028
f"than half of a central wavelength from a PML on side {side}. "
30263029
"To avoid inaccurate results or divergence, please increase gap between "
30273030
"any structures and PML or fully extend structure through the pml.",
@@ -3044,7 +3047,7 @@ def warn(istruct, side):
30443047
and struct_val > sim_val
30453048
and abs(sim_val - struct_val) < lambda0 / 2
30463049
):
3047-
warn(istruct, axis + "-min")
3050+
warn(structure, istruct, axis + "-min")
30483051

30493052
zipped = zip(["x", "y", "z"], sim_bound_max, struct_bound_max, boundaries)
30503053
for axis, sim_val, struct_val, boundary in zipped:
@@ -3056,7 +3059,7 @@ def warn(istruct, side):
30563059
and struct_val < sim_val
30573060
and abs(sim_val - struct_val) < lambda0 / 2
30583061
):
3059-
warn(istruct, axis + "-max")
3062+
warn(structure, istruct, axis + "-max")
30603063

30613064
return val
30623065

@@ -3095,20 +3098,20 @@ def _warn_monitor_mediums_frequency_range(cls, val, values):
30953098
medium_str = "The simulation background medium"
30963099
custom_loc = ["medium", "frequency_range"]
30973100
else:
3098-
medium_str = (
3099-
f"The medium associated with structures[{medium_index - 1}]"
3100-
)
3101+
medium_descr = named_obj_descr(medium, "mediums", medium_index)
3102+
medium_str = f"The medium associated with {medium_descr}"
31013103
custom_loc = [
31023104
"structures",
31033105
medium_index - 1,
31043106
"medium",
31053107
"frequency_range",
31063108
]
31073109

3110+
monitor_descr = named_obj_descr(monitor, "monitors", monitor_index)
31083111
consolidated_logger.warning(
31093112
f"{medium_str} has a frequency range: ({sci_fmin_med}, {sci_fmax_med}) "
3110-
"(Hz) that does not fully cover the frequencies contained in "
3111-
f"monitors[{monitor_index}]. "
3113+
"(Hz) that does not fully cover the frequencies contained "
3114+
f"in {monitor_descr}."
31123115
"This can cause inaccuracies in the recorded results.",
31133116
custom_loc=custom_loc,
31143117
)
@@ -3806,8 +3809,9 @@ def _validate_custom_source_time(self):
38063809
data_times = source.source_time.data_times
38073810
mint = np.min(data_times)
38083811
maxt = np.max(data_times)
3812+
obj_descr = named_obj_descr(source, "sources", idx)
38093813
log.warning(
3810-
f"'CustomSourceTime' at 'sources[{idx}]' is defined over a time range "
3814+
f"'CustomSourceTime': {obj_descr} is defined over a time range "
38113815
f"'({mint}, {maxt})' which does not include any of the 'Simulation' "
38123816
f"times '({0, run_time})'. The envelope will be constant extrapolated "
38133817
"from the first or last value in the 'CustomSourceTime', which may not "
@@ -3837,8 +3841,9 @@ def _validate_no_structures_pml(self) -> None:
38373841
if not isinstance(bound_edge, Absorber) and (in_pml_plus or in_pml_mnus):
38383842
warn = True
38393843
if warn:
3844+
obj_descr = named_obj_descr(structure, "structures", i)
38403845
consolidated_logger.warning(
3841-
f"A bound of Simulation.structures[{i}] was detected as being "
3846+
f"A bound of {obj_descr} was detected as being "
38423847
"within the simulation PML. We recommend extending structures to "
38433848
"infinity or completely outside of the simulation PML to avoid "
38443849
"unexpected effects when the structures are not translationally "
@@ -3956,8 +3961,9 @@ def _validate_nonlinear_specs(self) -> None:
39563961
if isinstance(monitor, AuxFieldTimeMonitor):
39573962
for aux_field in monitor.fields:
39583963
if aux_field not in self.aux_fields:
3964+
obj_descr = named_obj_descr(monitor, "monitors", i)
39593965
log.warning(
3960-
f"Monitor at 'monitors[{i}]' stores field '{aux_field}', "
3966+
f"Monitor: {obj_descr} stores field '{aux_field}', "
39613967
"which is not used by any of the nonlinear models present "
39623968
"in the mediums in the simulation. The resulting data "
39633969
"will be zero."

tidy3d/components/validators.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Optional
5+
from typing import Any, Optional
66

77
import numpy as np
88
import pydantic.v1 as pydantic
@@ -52,6 +52,14 @@
5252
MIN_FREQUENCY = 1e5
5353

5454

55+
def named_obj_descr(obj: Any, field_name: str, position_index: int) -> str:
56+
"""Generate a string describing a named object which can be used in error messages."""
57+
descr = f"simulation.{field_name}[{position_index}] (no `name` was specified)"
58+
if hasattr(obj, "name") and obj.name:
59+
descr = f"'{obj.name}' (simulation.{field_name}[{position_index}])"
60+
return descr
61+
62+
5563
def assert_line():
5664
"""makes sure a field's ``size`` attribute has exactly 2 zeros"""
5765

@@ -156,10 +164,10 @@ def check_symmetry(cls, val, values):
156164
and bounds_min[dim] < sim_center[dim]
157165
and geometric_object.center[dim] != sim_center[dim]
158166
):
167+
obj_descr = named_obj_descr(geometric_object, field_name, position_index)
159168
raise SetupError(
160-
f"{obj_type} at 'simulation.{field_name}[{position_index}]' "
161-
"in presence of symmetries must be in the main quadrant, "
162-
"or centered on the symmetry axis."
169+
f"{obj_type}: {obj_descr} in presence of symmetries must be in the main "
170+
"quadrant, or centered on the symmetry axis."
163171
)
164172

165173
return val
@@ -201,12 +209,9 @@ def objects_in_sim_bounds(cls, val, values):
201209
with log as consolidated_logger:
202210
for position_index, geometric_object in enumerate(val):
203211
if not sim_box.intersects(geometric_object.geometry, strict_inequality=strict_ineq):
204-
message = (
205-
f"'simulation.{field_name}[{position_index}]' "
206-
"is outside of the simulation domain."
207-
)
212+
obj_descr = named_obj_descr(geometric_object, field_name, position_index)
213+
message = f"{obj_descr} is outside of the simulation domain."
208214
custom_loc = [field_name, position_index]
209-
210215
if error:
211216
raise SetupError(message)
212217
consolidated_logger.warning(message, custom_loc=custom_loc)
@@ -245,12 +250,9 @@ def objects_contained_in_sim_bounds(cls, val, values):
245250
if not sim_box.contains(
246251
geometric_object.geometry, strict_inequality=geo_strict_ineq
247252
):
248-
message = (
249-
f"'simulation.{field_name}[{position_index}]' "
250-
"is not completely inside the simulation domain."
251-
)
253+
obj_descr = named_obj_descr(geometric_object, field_name, position_index)
254+
message = f"{obj_descr} is not completely inside the simulation domain."
252255
custom_loc = [field_name, position_index]
253-
254256
if error:
255257
raise SetupError(message)
256258
consolidated_logger.warning(message, custom_loc=custom_loc)

0 commit comments

Comments
 (0)