Skip to content

Commit 284f98f

Browse files
committed
fix(rf): validate auto path integrals against all conductors and the mode plane bounds
1 parent fb43388 commit 284f98f

File tree

3 files changed

+107
-7
lines changed

3 files changed

+107
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
### Fixed
15+
- Fixed `AutoImpedanceSpec` validation to check path intersections against all conductors, not just filtered ones, as well as the mode plane bounds.
1516

1617
## [2.10.0] - 2025-12-18
1718

tests/test_components/test_microwave.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,87 @@ def test_mode_plane_analyzer_errors():
708708
)
709709

710710

711+
@pytest.mark.parametrize("include_ground_plane", [True, False])
712+
def test_mode_plane_analyzer_coarse_grid_errors(include_ground_plane):
713+
"""Test that coarse grids produce appropriate errors for auto path generation.
714+
715+
With a coarse grid the snapped bounding box around the signal trace can:
716+
- Intersect a ground plane (if present) that was filtered from path generation
717+
- Extend outside the mode plane bounds (if no ground plane blocks it first)
718+
"""
719+
width = 3 * mm
720+
metal_thickness = 0.1 * mm
721+
coarse_dl = 1.0 * mm
722+
723+
sim_size = (10 * mm, 10 * mm, 10 * mm)
724+
sim_center = (0, 0, sim_size[2] / 2)
725+
726+
# Signal trace near bottom of mode plane
727+
signal_trace = td.Structure(
728+
geometry=td.Box(
729+
center=(0, 0, 0.3 * mm),
730+
size=(td.inf, width, metal_thickness),
731+
),
732+
medium=td.PEC,
733+
)
734+
735+
structures = [signal_trace]
736+
737+
if include_ground_plane:
738+
# Ground plane at z=0, extending infinitely in y to touch mode plane y-boundaries
739+
# This will be filtered out, but should still be checked for path intersections
740+
ground_plane = td.Structure(
741+
geometry=td.Box(
742+
center=(0, 0, 0),
743+
size=(td.inf, td.inf, metal_thickness),
744+
),
745+
medium=td.PEC,
746+
)
747+
structures.append(ground_plane)
748+
# Mode plane spans full simulation for ground plane case
749+
mode_plane_size = (0, sim_size[1], sim_size[2])
750+
mode_plane_center = (0, 0, sim_center[2])
751+
else:
752+
# Small mode plane that doesn't span full sim, so bounding box extends outside
753+
mode_plane_size = (0, sim_size[1], 1.0 * mm)
754+
mode_plane_center = (0, 0, 0.5 * mm)
755+
756+
sim = td.Simulation(
757+
center=sim_center,
758+
size=sim_size,
759+
grid_spec=td.GridSpec.uniform(dl=coarse_dl),
760+
structures=structures,
761+
sources=[],
762+
run_time=1e-12,
763+
)
764+
765+
mode_plane = td.Box(center=mode_plane_center, size=mode_plane_size)
766+
path_spec_gen = ModePlaneAnalyzer(
767+
center=mode_plane.center,
768+
size=mode_plane.size,
769+
field_data_colocated=False,
770+
)
771+
772+
if include_ground_plane:
773+
# Should raise SetupError because auto-generated path intersects the ground plane
774+
with pytest.raises(SetupError, match="intersect with a conductor"):
775+
path_spec_gen.get_conductor_bounding_boxes(
776+
sim.structures,
777+
sim.grid,
778+
sim.symmetry,
779+
sim.bounding_box,
780+
)
781+
else:
782+
# Should raise SetupError because bounding box extends outside mode plane
783+
with pytest.raises(SetupError, match="extends outside the mode solving plane"):
784+
path_spec_gen.get_conductor_bounding_boxes(
785+
sim.structures,
786+
sim.grid,
787+
sim.symmetry,
788+
sim.bounding_box,
789+
)
790+
791+
711792
@pytest.mark.parametrize("colocate", [False, True])
712793
@pytest.mark.parametrize("tline_type", ["microstrip", "cpw", "coax"])
713794
def test_mode_plane_analyzer_canonical_shapes(colocate, tline_type):

tidy3d/components/microwave/path_integrals/mode_plane_analyzer.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,15 @@ def bounding_box_from_shapely(geom: Shapely) -> Box:
209209
min_b_3d, max_b_3d = self._get_mode_limits(grid, mode_symmetry_3d)
210210

211211
intersection_plane = Box.from_bounds(min_b_3d, max_b_3d)
212-
conductor_shapely = self._get_isolated_conductors_as_shapely(intersection_plane, structures)
212+
isolated_conductor_shapely = self._get_isolated_conductors_as_shapely(
213+
intersection_plane, structures
214+
)
213215

214-
conductor_shapely = self._filter_conductors_touching_sim_bounds(
215-
(min_b_3d, max_b_3d), mode_symmetry_3d, conductor_shapely
216+
filtered_conductor_shapely = self._filter_conductors_touching_sim_bounds(
217+
(min_b_3d, max_b_3d), mode_symmetry_3d, isolated_conductor_shapely
216218
)
217219

218-
if len(conductor_shapely) < 1:
220+
if len(filtered_conductor_shapely) < 1:
219221
raise SetupError(
220222
"No valid isolated conductors were found in the mode plane. Please ensure that a 'Structure' "
221223
"with a medium of type 'PEC' or 'LossyMetalMedium' intersects the mode plane and is not touching "
@@ -228,23 +230,39 @@ def bounding_box_from_shapely(geom: Shapely) -> Box:
228230
snap_spec = self._snap_spec
229231

230232
bounding_boxes = []
231-
for shape in conductor_shapely:
233+
for shape in filtered_conductor_shapely:
232234
box = bounding_box_from_shapely(shape)
233235
boxes = self._apply_symmetries(symmetry, sim_box.center, box)
234236
for box in boxes:
235237
box_snapped = snap_box_to_grid(grid, box, snap_spec)
236238
bounding_boxes.append(box_snapped)
237239

240+
# TODO Improve these checks once FXC-4112-PEC-boundary-position-not-respected-by-ModeSolver is merged
238241
for bounding_box in bounding_boxes:
239-
if self._check_box_intersects_with_conductors(conductor_shapely, bounding_box):
242+
if self._check_box_intersects_with_conductors(isolated_conductor_shapely, bounding_box):
240243
raise SetupError(
241244
"Failed to automatically generate path specification because a generated path "
242245
"specification was found to intersect with a conductor. There is currently limited "
243246
"support for complex conductor geometries, so please provide an explicit current "
244247
"path specification through a 'CustomImpedanceSpec'. Alternatively, enforce a "
245248
"smaller grid around the conductors in the mode plane, which may resolve the issue."
246249
)
247-
return bounding_boxes, conductor_shapely
250+
251+
# Check that bounding boxes don't extend outside the original mode plane bounds
252+
mode_plane_min, mode_plane_max = self.bounds
253+
for bounding_box in bounding_boxes:
254+
box_min, box_max = bounding_box.bounds
255+
if any(box_min[i] < mode_plane_min[i] for i in range(3)) or any(
256+
box_max[i] > mode_plane_max[i] for i in range(3)
257+
):
258+
raise SetupError(
259+
"Failed to automatically generate path specification because a generated path "
260+
"specification extends outside the mode solving plane bounds. This issue can be fixed "
261+
"by enlarging the mode solving plane and ensuring that there is a buffer of at "
262+
"least 2 grid cells between the mode solving plane bounds and the nearest conductors."
263+
)
264+
265+
return bounding_boxes, filtered_conductor_shapely
248266

249267
def _check_box_intersects_with_conductors(
250268
self, shapely_list: list[Shapely], bounding_box: Box

0 commit comments

Comments
 (0)