Skip to content

Commit 290484a

Browse files
committed
add a MicrowaveModeSpec for RF specific mode information
add a ImpedanceSpec for controlling how characteristic impedance is calculated from modes
1 parent da73c2b commit 290484a

33 files changed

+4732
-771
lines changed

schemas/EMESimulation.json

Lines changed: 483 additions & 0 deletions
Large diffs are not rendered by default.

schemas/ModeSimulation.json

Lines changed: 478 additions & 0 deletions
Large diffs are not rendered by default.

schemas/Simulation.json

Lines changed: 478 additions & 0 deletions
Large diffs are not rendered by default.

schemas/TerminalComponentModeler.json

Lines changed: 523 additions & 0 deletions
Large diffs are not rendered by default.

tests/test_components/test_geometry.py

Lines changed: 192 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
import pytest
1313
import shapely
1414
import trimesh
15+
from shapely.geometry import (
16+
GeometryCollection,
17+
LineString,
18+
MultiLineString,
19+
MultiPoint,
20+
MultiPolygon,
21+
Point,
22+
Polygon,
23+
)
1524

1625
import tidy3d as td
1726
from tidy3d.compat import _shapely_is_older_than
@@ -22,6 +31,7 @@
2231
SnapLocation,
2332
SnappingSpec,
2433
flatten_groups,
34+
flatten_shapely_geometries,
2535
snap_box_to_grid,
2636
traverse_geometries,
2737
)
@@ -1137,7 +1147,14 @@ def test_subdivide():
11371147
@pytest.mark.parametrize("snap_location", [SnapLocation.Boundary, SnapLocation.Center])
11381148
@pytest.mark.parametrize(
11391149
"snap_behavior",
1140-
[SnapBehavior.Off, SnapBehavior.Closest, SnapBehavior.Expand, SnapBehavior.Contract],
1150+
[
1151+
SnapBehavior.Off,
1152+
SnapBehavior.Closest,
1153+
SnapBehavior.Expand,
1154+
SnapBehavior.Contract,
1155+
SnapBehavior.StrictExpand,
1156+
SnapBehavior.StrictContract,
1157+
],
11411158
)
11421159
def test_snap_box_to_grid(snap_location, snap_behavior):
11431160
""" "Test that all combinations of SnappingSpec correctly modify a test box without error."""
@@ -1158,12 +1175,78 @@ def test_snap_box_to_grid(snap_location, snap_behavior):
11581175
new_box = snap_box_to_grid(grid, box, snap_spec)
11591176

11601177
if snap_behavior != SnapBehavior.Off and snap_location == SnapLocation.Boundary:
1161-
# Check that the box boundary slightly off from 0.1 was correctly snapped to 0.1
1162-
assert math.isclose(new_box.bounds[0][1], xyz[1])
1163-
# Check that the box boundary slightly off from 0.3 was correctly snapped to 0.3
1164-
assert math.isclose(new_box.bounds[1][1], xyz[3])
1165-
# Check that the box boundary outside the grid was snapped to the smallest grid coordinate
1166-
assert math.isclose(new_box.bounds[0][2], xyz[0])
1178+
# Strict behaviors have different snapping rules, so skip these specific assertions
1179+
if snap_behavior not in (SnapBehavior.StrictExpand, SnapBehavior.StrictContract):
1180+
# Check that the box boundary slightly off from 0.1 was correctly snapped to 0.1
1181+
assert math.isclose(new_box.bounds[0][1], xyz[1])
1182+
# Check that the box boundary slightly off from 0.3 was correctly snapped to 0.3
1183+
assert math.isclose(new_box.bounds[1][1], xyz[3])
1184+
# Check that the box boundary outside the grid was snapped to the smallest grid coordinate
1185+
assert math.isclose(new_box.bounds[0][2], xyz[0])
1186+
1187+
1188+
def test_snap_box_to_grid_strict_behaviors():
1189+
"""Test StrictExpand and StrictContract behaviors specifically."""
1190+
xyz = np.linspace(0, 1, 11) # Grid points at 0.0, 0.1, 0.2, ..., 1.0
1191+
coords = td.Coords(x=xyz, y=xyz, z=xyz)
1192+
grid = td.Grid(boundaries=coords)
1193+
1194+
# Test StrictExpand: should always move endpoints outwards, even if coincident
1195+
box_coincident = td.Box(
1196+
center=(0.1, 0.2, 0.3), size=(0, 0, 0)
1197+
) # Centered exactly on grid points
1198+
snap_spec_strict_expand = SnappingSpec(
1199+
location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.StrictExpand] * 3
1200+
)
1201+
1202+
expanded_box = snap_box_to_grid(grid, box_coincident, snap_spec_strict_expand)
1203+
1204+
# StrictExpand should move bounds outwards even when already on grid
1205+
assert expanded_box.bounds[0][0] < 0.1 # Left bound moved left from 0.1
1206+
assert expanded_box.bounds[1][0] > 0.1 # Right bound moved right from 0.1
1207+
assert expanded_box.bounds[0][1] < 0.2 # Bottom bound moved down from 0.2
1208+
assert expanded_box.bounds[1][1] > 0.2 # Top bound moved up from 0.2
1209+
1210+
# Test StrictContract: should always move endpoints inwards, even if coincident
1211+
box_large = td.Box(center=(0.5, 0.5, 0.5), size=(0.4, 0.4, 0.4)) # Spans multiple grid cells
1212+
snap_spec_strict_contract = SnappingSpec(
1213+
location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.StrictContract] * 3
1214+
)
1215+
1216+
contracted_box = snap_box_to_grid(grid, box_large, snap_spec_strict_contract)
1217+
1218+
# StrictContract should make the box smaller than the original
1219+
assert contracted_box.size[0] < box_large.size[0]
1220+
assert contracted_box.size[1] < box_large.size[1]
1221+
assert contracted_box.size[2] < box_large.size[2]
1222+
1223+
# Test edge case: box coincident with grid boundaries
1224+
box_on_grid = td.Box(
1225+
center=(0.15, 0.25, 0.35), size=(0.1, 0.1, 0.1)
1226+
) # Boundaries at 0.1,0.2 and 0.2,0.3
1227+
1228+
# Regular Expand shouldn't change a box already coincident with grid
1229+
snap_spec_regular_expand = SnappingSpec(
1230+
location=[SnapLocation.Boundary] * 3, behavior=[SnapBehavior.Expand] * 3
1231+
)
1232+
regular_expanded = snap_box_to_grid(grid, box_on_grid, snap_spec_regular_expand)
1233+
assert np.allclose(regular_expanded.bounds, box_on_grid.bounds) # Should be unchanged
1234+
1235+
# StrictExpand should still expand even when coincident
1236+
strict_expanded = snap_box_to_grid(grid, box_on_grid, snap_spec_strict_expand)
1237+
assert not np.allclose(strict_expanded.bounds, box_on_grid.bounds) # Should be changed
1238+
assert strict_expanded.size[0] > box_on_grid.size[0] # Should be larger
1239+
1240+
# Test with margin parameter for strict behaviors
1241+
snap_spec_strict_expand_margin = SnappingSpec(
1242+
location=[SnapLocation.Boundary] * 3,
1243+
behavior=[SnapBehavior.StrictExpand] * 3,
1244+
margin=(1, 1, 1), # Consider 1 additional grid point when expanding
1245+
)
1246+
1247+
margin_expanded = snap_box_to_grid(grid, box_coincident, snap_spec_strict_expand_margin)
1248+
# With margin=1, should expand even further than without margin
1249+
assert margin_expanded.size[0] >= expanded_box.size[0]
11671250

11681251

11691252
def test_triangulation_with_collinear_vertices():
@@ -1431,3 +1514,105 @@ def test_trim_dims_and_bounds_edge():
14311514
assert np.all(np.array(expected_trimmed_bounds) == np.array(trimmed_bounds)), (
14321515
"Unexpected trimmed bounds"
14331516
)
1517+
1518+
1519+
def test_flatten_shapely_geometries():
1520+
"""Test the flatten_shapely_geometries utility function comprehensively."""
1521+
# Test 1: Single polygon (should be wrapped in list and returned)
1522+
single_polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
1523+
result = flatten_shapely_geometries(single_polygon)
1524+
assert len(result) == 1
1525+
assert result[0] == single_polygon
1526+
1527+
# Test 2: List of polygons (should return as-is)
1528+
poly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
1529+
poly2 = Polygon([(2, 0), (3, 0), (3, 1), (2, 1)])
1530+
polygon_list = [poly1, poly2]
1531+
result = flatten_shapely_geometries(polygon_list)
1532+
assert len(result) == 2
1533+
assert result == polygon_list
1534+
1535+
# Test 3: MultiPolygon (should be flattened)
1536+
multi_polygon = MultiPolygon([poly1, poly2])
1537+
result = flatten_shapely_geometries(multi_polygon)
1538+
assert len(result) == 2
1539+
assert result[0] == poly1
1540+
assert result[1] == poly2
1541+
1542+
# Test 4: Empty geometries (should be filtered out)
1543+
empty_polygon = Polygon()
1544+
mixed_list = [poly1, empty_polygon, poly2]
1545+
result = flatten_shapely_geometries(mixed_list)
1546+
assert len(result) == 2
1547+
assert empty_polygon not in result
1548+
1549+
# Test 5: GeometryCollection (should be recursively flattened)
1550+
line = LineString([(0, 0), (1, 1)])
1551+
point = Point(0, 0)
1552+
collection = GeometryCollection([poly1, line, point, poly2])
1553+
result = flatten_shapely_geometries(collection)
1554+
assert len(result) == 2 # Only polygons kept by default
1555+
assert poly1 in result
1556+
assert poly2 in result
1557+
1558+
# Test 6: Custom keep_types parameter
1559+
result_with_lines = flatten_shapely_geometries(collection, keep_types=(Polygon, LineString))
1560+
assert len(result_with_lines) == 3 # 2 polygons + 1 line
1561+
assert poly1 in result_with_lines
1562+
assert poly2 in result_with_lines
1563+
assert line in result_with_lines
1564+
1565+
# Test 7: Nested collections and multi-geometries
1566+
line1 = LineString([(0, 0), (1, 1)])
1567+
line2 = LineString([(2, 2), (3, 3)])
1568+
multi_line = MultiLineString([line1, line2])
1569+
nested_collection = GeometryCollection(
1570+
[
1571+
collection, # Contains poly1, line, point, poly2
1572+
multi_line,
1573+
poly1,
1574+
]
1575+
)
1576+
result = flatten_shapely_geometries(nested_collection)
1577+
assert len(result) == 3 # poly1 (from collection), poly2 (from collection), poly1 (direct)
1578+
1579+
# Test 8: MultiPoint (should be handled)
1580+
point1 = Point(0, 0)
1581+
point2 = Point(1, 1)
1582+
multi_point = MultiPoint([point1, point2])
1583+
result = flatten_shapely_geometries(multi_point, keep_types=(Point,))
1584+
assert len(result) == 2
1585+
assert point1 in result
1586+
assert point2 in result
1587+
1588+
# Test 9: MultiLineString (should be handled)
1589+
result = flatten_shapely_geometries(multi_line, keep_types=(LineString,))
1590+
assert len(result) == 2
1591+
assert line1 in result
1592+
assert line2 in result
1593+
1594+
# Test 10: Mixed empty and non-empty geometries
1595+
empty_multi = MultiPolygon([])
1596+
mixed_with_empty = [poly1, empty_multi, empty_polygon, poly2]
1597+
result = flatten_shapely_geometries(mixed_with_empty)
1598+
assert len(result) == 2
1599+
assert poly1 in result
1600+
assert poly2 in result
1601+
1602+
# Test 11: Deeply nested structure
1603+
inner_collection = GeometryCollection([poly1, line])
1604+
outer_multi = MultiPolygon([poly2])
1605+
deep_collection = GeometryCollection([inner_collection, outer_multi])
1606+
result = flatten_shapely_geometries(deep_collection)
1607+
assert len(result) == 2
1608+
assert poly1 in result
1609+
assert poly2 in result
1610+
1611+
# Test 12: All geometry types filtered out
1612+
points_and_lines = GeometryCollection([Point(0, 0), LineString([(0, 0), (1, 1)])])
1613+
result = flatten_shapely_geometries(points_and_lines) # Default keeps only Polygons
1614+
assert len(result) == 0
1615+
1616+
# Test 13: Edge case - single empty geometry
1617+
result = flatten_shapely_geometries(empty_polygon)
1618+
assert len(result) == 0

0 commit comments

Comments
 (0)