Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Added warning in `LayerRefinementSpec` when `dl_min_from_gaps` (derived from automatic gap refinement) is very small relative to the lateral grid size for identifying cases where excessive grid refinement may occur due to very small detected gaps.

### Fixed
- Fixed intermittent "API key not found" errors in parallel job launches by making configuration directory detection race-safe.

Expand Down
88 changes: 88 additions & 0 deletions tests/test_components/test_layerrefinement.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,3 +1000,91 @@ def test_gap_meshing_tiny_nearly_parallel():
# sim.plot_grid(y=0, ax=ax)
# plt.show()
assert sim.grid_info["min_grid_size"] > 0.05


def test_gap_meshing_dl_min_warning():
"""Test that warning is displayed when dl_min_from_gaps is very small relative to lateral grid size."""
from ..utils import AssertLogStr

wavelength = 1.0
# Create a very small gap that will trigger the warning
# For a grid with min_steps_per_wvl=10 and wavelength=1, the grid size will be around 0.1
# We need gap_width such that DL_MIN_FROM_GAPS_FRACTION * gap_width < GAP_REFINEMENT_WARNING_THRESH * min_lateral_grid_size
# gap_width < (0.1 * 0.1) / 0.45 ≈ 0.022
small_gap_width = 0.01 # Very small gap that will trigger warning

# Create two parallel PEC strips with a small gap between them
# Make structures smaller to avoid edge warnings (size 0.3 instead of 0.5 in y-direction)
strip1 = td.Structure(
geometry=td.Box(center=(-small_gap_width / 2 - 0.05, 0, 0), size=(0.1, 0.3, 0.2)),
medium=td.PECMedium(),
)

strip2 = td.Structure(
geometry=td.Box(center=(small_gap_width / 2 + 0.05, 0, 0), size=(0.1, 0.3, 0.2)),
medium=td.PECMedium(),
)

# Create layer refinement spec with gap meshing enabled
layer_spec = td.LayerRefinementSpec(
axis=2, # z-axis
size=(td.inf, td.inf, 0.2),
center=(0, 0, 0),
gap_meshing_iters=1,
dl_min_from_gap_width=True,
corner_snapping=False,
corner_refinement=None,
)

grid_spec = td.GridSpec.auto(
wavelength=wavelength,
min_steps_per_wvl=10, # This will create grid cells of ~0.1 size
layer_refinement_specs=[layer_spec],
)

# Test that warning IS displayed for very small gap
# Use AssertLogStr to filter out edge warnings and only check for our specific warning
with AssertLogStr("WARNING", contains_str="detected a very small gap width"):
sim = td.Simulation(
size=(1, 1, 0.2),
structures=[strip1, strip2],
grid_spec=grid_spec,
run_time=1e-15,
)

# Now test with a larger gap that should NOT trigger warning
# gap_width should be large enough: gap_width > (GAP_REFINEMENT_WARNING_THRESH * min_lateral_grid_size) / DL_MIN_FROM_GAPS_FRACTION
# For min_lateral_grid_size ~ 0.1: gap_width > (0.1 * 0.1) / 0.45 ≈ 0.022
large_gap_width = 0.05 # Large enough to not trigger warning

strip1_large = td.Structure(
geometry=td.Box(center=(-large_gap_width / 2 - 0.05, 0, 0), size=(0.1, 0.3, 0.2)),
medium=td.PECMedium(),
)

strip2_large = td.Structure(
geometry=td.Box(center=(large_gap_width / 2 + 0.05, 0, 0), size=(0.1, 0.3, 0.2)),
medium=td.PECMedium(),
)

# Test that warning is NOT displayed for larger gap
# Use AssertLogStr to exclude edge warnings and check that our specific warning is not present
with AssertLogStr("WARNING", excludes_str="detected a very small gap width"):
sim_large = td.Simulation(
size=(1, 1, 0.2),
structures=[strip1_large, strip2_large],
grid_spec=grid_spec,
run_time=1e-15,
)

# Test with dl_min_from_gap_width=False - should not warn even with small gap
layer_spec_no_gap = layer_spec.updated_copy(dl_min_from_gap_width=False)
grid_spec_no_gap = grid_spec.updated_copy(layer_refinement_specs=[layer_spec_no_gap])

with AssertLogStr("WARNING", excludes_str="detected a very small gap width"):
sim_no_gap = td.Simulation(
size=(1, 1, 0.2),
structures=[strip1, strip2],
grid_spec=grid_spec_no_gap,
run_time=1e-15,
)
46 changes: 45 additions & 1 deletion tidy3d/components/grid/grid_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
# Tolerance for distinguishing pec/grid intersections
GAP_MESHING_TOL = 1e-3

# Fraction of detected gap width used to set dl_min_from_gaps
DL_MIN_FROM_GAPS_FRACTION = 0.45

# Threshold for warning when dl_min_from_gaps is very small relative to lateral grid size
GAP_REFINEMENT_WARNING_THRESH = 0.1


class GridSpec1d(Tidy3dBaseModel, ABC):
"""Abstract base class, defines 1D grid generation specifications."""
Expand Down Expand Up @@ -3083,6 +3089,44 @@ def _make_grid_and_snapping_lines(
if layer_spec.dl_min_from_gap_width:
min_gap_width = min(min_gap_width, gap_width)

# Warn if dl_min_from_gaps would be very small relative to lateral grid size
if gap_width < inf:
# Get lateral dimensions (perpendicular to layer axis)
_, tan_dims = Box.pop_axis([0, 1, 2], layer_spec.axis)
dim_names = ["x", "y", "z"]

# Get grid sizes along lateral dimensions
grid_sizes = old_grid.sizes.to_dict
lateral_grid_sizes = [
grid_sizes[dim_names[tan_dims[0]]],
grid_sizes[dim_names[tan_dims[1]]],
]

# Find minimum grid size along lateral dimensions
min_lateral_grid_size = min(
min(sizes) if len(sizes) > 0 else inf
for sizes in lateral_grid_sizes
)

# Calculate dl_min_from_gaps for this layer spec
dl_min_from_gaps = DL_MIN_FROM_GAPS_FRACTION * gap_width

# Warn if dl_min_from_gaps is too small relative to lateral grid size
if (
min_lateral_grid_size < inf
and dl_min_from_gaps
< GAP_REFINEMENT_WARNING_THRESH * min_lateral_grid_size
):
log.warning(
f"'LayerRefinementSpec' (axis={layer_spec.axis}) detected a very small gap width "
f"({gap_width:.2e}), resulting in 'dl_min_from_gaps'={dl_min_from_gaps:.2e}. "
f"This is less than {GAP_REFINEMENT_WARNING_THRESH * 100:.0f}% of the smallest "
f"lateral grid size ({min_lateral_grid_size:.2e}). This may lead to "
"excessive grid refinement. Consider adjusting the geometry or grid "
"specification.",
log_once=True,
)

if len(new_snapping_lines) == 0:
log.info(
"Grid is no longer changing. "
Expand All @@ -3101,7 +3145,7 @@ def _make_grid_and_snapping_lines(
lumped_elements=lumped_elements,
internal_override_structures=internal_override_structures,
internal_snapping_points=snapping_lines + internal_snapping_points,
dl_min_from_gaps=0.45 * min_gap_width,
dl_min_from_gaps=DL_MIN_FROM_GAPS_FRACTION * min_gap_width,
structure_priority_mode=structure_priority_mode,
boundary_types=boundary_types,
parse_structures_interval_coords=parse_structures_interval_coords,
Expand Down
Loading