Skip to content

Commit bf0e9a1

Browse files
feat(meshing): disable polygon cleanup in corner finder
Corner finder: reduce the number of vertices for smoothly curved cross section Mesher and gap refinement: performance improvement in parse_structures
1 parent 2b6d21b commit bf0e9a1

File tree

9 files changed

+397
-70
lines changed

9 files changed

+397
-70
lines changed

tests/test_components/test_geometry.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,35 @@ def test_intersections_plane_inf():
236236
assert len(c.intersections_plane(y=0)) == 1
237237

238238

239+
@pytest.mark.parametrize("component", [BOX, CYLINDER, SPHERE, POLYSLAB, UNION, GROUP])
240+
@pytest.mark.parametrize("cleanup", [True, False])
241+
def test_intersections_plane_cleanup_param(component, cleanup):
242+
"""Test that cleanup parameter is accepted by all geometry types."""
243+
shapes = component.intersections_plane(z=0, cleanup=cleanup)
244+
assert isinstance(shapes, list)
245+
shapes_default = component.intersections_plane(z=0)
246+
# Both should return valid shapely objects
247+
for shape in shapes:
248+
assert hasattr(shape, "is_valid")
249+
for shape in shapes_default:
250+
assert hasattr(shape, "is_valid")
251+
252+
253+
@pytest.mark.parametrize("component", [SPHERE, CYLINDER])
254+
@pytest.mark.parametrize("quad_segs", [8, 50, 200])
255+
def test_intersections_plane_quad_segs(component, quad_segs):
256+
"""Test that quad_segs parameter controls discretization of circular shapes."""
257+
shapes = component.intersections_plane(z=0, quad_segs=quad_segs)
258+
assert len(shapes) > 0
259+
# For circular shapes, quad_segs should affect the number of vertices
260+
# Higher quad_segs should generally give more vertices
261+
shape = shapes[0]
262+
num_coords = len(shape.exterior.coords)
263+
assert num_coords > 4 * quad_segs, (
264+
f"Expected more than {4 * quad_segs} coords, got {num_coords}"
265+
)
266+
267+
239268
def test_center_not_inf_validate():
240269
with pytest.raises(pydantic.ValidationError):
241270
_ = td.Box(center=(td.inf, 0, 0))

tidy3d/components/geometry/base.py

Lines changed: 171 additions & 29 deletions
Large diffs are not rendered by default.

tidy3d/components/geometry/mesh.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,12 @@ def bounds(self) -> Bound:
530530
return self.trimesh.bounds
531531

532532
def intersections_tilted_plane(
533-
self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4
533+
self,
534+
normal: Coordinate,
535+
origin: Coordinate,
536+
to_2D: MatrixReal4x4,
537+
cleanup: bool = True,
538+
quad_segs: Optional[int] = None,
534539
) -> list[Shapely]:
535540
"""Return a list of shapely geometries at the plane specified by normal and origin.
536541
@@ -542,6 +547,10 @@ def intersections_tilted_plane(
542547
Vector defining the plane origin.
543548
to_2D : MatrixReal4x4
544549
Transformation matrix to apply to resulting shapes.
550+
cleanup : bool = True
551+
If True, removes extremely small features from each polygon's boundary.
552+
quad_segs : Optional[int] = None
553+
Number of segments used to discretize circular shapes. Not used for TriangleMesh.
545554
546555
Returns
547556
-------
@@ -557,7 +566,12 @@ def intersections_tilted_plane(
557566
return path.polygons_full
558567

559568
def intersections_plane(
560-
self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None
569+
self,
570+
x: Optional[float] = None,
571+
y: Optional[float] = None,
572+
z: Optional[float] = None,
573+
cleanup: bool = True,
574+
quad_segs: Optional[int] = None,
561575
) -> list[Shapely]:
562576
"""Returns list of shapely geometries at plane specified by one non-None value of x,y,z.
563577
@@ -569,6 +583,10 @@ def intersections_plane(
569583
Position of plane in y direction, only one of x,y,z can be specified to define plane.
570584
z : float = None
571585
Position of plane in z direction, only one of x,y,z can be specified to define plane.
586+
cleanup : bool = True
587+
If True, removes extremely small features from each polygon's boundary.
588+
quad_segs : Optional[int] = None
589+
Number of segments used to discretize circular shapes. Not used for TriangleMesh.
572590
573591
Returns
574592
-------
@@ -623,7 +641,7 @@ def intersections_plane(
623641
"Using bounding box instead."
624642
)
625643
log.warning(f"Error encountered: {e}")
626-
return self.bounding_box.intersections_plane(x=x, y=y, z=z)
644+
return self.bounding_box.intersections_plane(x=x, y=y, z=z, cleanup=cleanup)
627645

628646
def inside(
629647
self, x: np.ndarray[float], y: np.ndarray[float], z: np.ndarray[float]

tidy3d/components/geometry/polyslab.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,11 @@ def _move_axis_reverse(arr: NDArray) -> NDArray:
587587

588588
@verify_packages_import(["trimesh"])
589589
def _do_intersections_tilted_plane(
590-
self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4
590+
self,
591+
normal: Coordinate,
592+
origin: Coordinate,
593+
to_2D: MatrixReal4x4,
594+
quad_segs: Optional[int] = None,
591595
) -> list[Shapely]:
592596
"""Return a list of shapely geometries at the plane specified by normal and origin.
593597
@@ -599,6 +603,8 @@ def _do_intersections_tilted_plane(
599603
Vector defining the plane origin.
600604
to_2D : MatrixReal4x4
601605
Transformation matrix to apply to resulting shapes.
606+
quad_segs : Optional[int] = None
607+
Number of segments used to discretize circular shapes. Not used for PolySlab geometry.
602608
603609
Returns
604610
-------
@@ -641,7 +647,7 @@ def _do_intersections_tilted_plane(
641647
path, _ = section.to_2D(to_2D=to_2D)
642648
return path.polygons_full
643649

644-
def _intersections_normal(self, z: float) -> list[Shapely]:
650+
def _intersections_normal(self, z: float, quad_segs: Optional[int] = None) -> list[Shapely]:
645651
"""Find shapely geometries intersecting planar geometry with axis normal to slab.
646652
647653
Parameters
@@ -2567,7 +2573,12 @@ def _dilation_value_at_reference_to_coord(self, dilation: float) -> float:
25672573
return z_coord
25682574

25692575
def intersections_tilted_plane(
2570-
self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4
2576+
self,
2577+
normal: Coordinate,
2578+
origin: Coordinate,
2579+
to_2D: MatrixReal4x4,
2580+
cleanup: bool = True,
2581+
quad_segs: Optional[int] = None,
25712582
) -> list[Shapely]:
25722583
"""Return a list of shapely geometries at the plane specified by normal and origin.
25732584
@@ -2579,6 +2590,10 @@ def intersections_tilted_plane(
25792590
Vector defining the plane origin.
25802591
to_2D : MatrixReal4x4
25812592
Transformation matrix to apply to resulting shapes.
2593+
cleanup : bool = True
2594+
If True, removes extremely small features from each polygon's boundary.
2595+
quad_segs : Optional[int] = None
2596+
Number of segments used to discretize circular shapes. Not used for PolySlab.
25822597
25832598
Returns
25842599
-------
@@ -2592,7 +2607,9 @@ def intersections_tilted_plane(
25922607
[
25932608
base.Geometry.evaluate_inf_shape(shape)
25942609
for polyslab in self.sub_polyslabs
2595-
for shape in polyslab.intersections_tilted_plane(normal, origin, to_2D)
2610+
for shape in polyslab.intersections_tilted_plane(
2611+
normal, origin, to_2D, cleanup=cleanup, quad_segs=quad_segs
2612+
)
25962613
]
25972614
)
25982615
]

tidy3d/components/geometry/primitives.py

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
_N_SAMPLE_CURVE_SHAPELY = 40
2929

3030
# for shapely circular shapes discretization in visualization
31-
_N_SHAPELY_QUAD_SEGS = 200
31+
_N_SHAPELY_QUAD_SEGS_VISUALIZATION = 200
3232

3333
# Default number of points to discretize polyslab in `Cylinder.to_polyslab()`
3434
_N_PTS_CYLINDER_POLYSLAB = 51
@@ -71,7 +71,12 @@ def inside(
7171
return (dist_x**2 + dist_y**2 + dist_z**2) <= (self.radius**2)
7272

7373
def intersections_tilted_plane(
74-
self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4
74+
self,
75+
normal: Coordinate,
76+
origin: Coordinate,
77+
to_2D: MatrixReal4x4,
78+
cleanup: bool = True,
79+
quad_segs: Optional[int] = None,
7580
) -> list[Shapely]:
7681
"""Return a list of shapely geometries at the plane specified by normal and origin.
7782
@@ -83,6 +88,11 @@ def intersections_tilted_plane(
8388
Vector defining the plane origin.
8489
to_2D : MatrixReal4x4
8590
Transformation matrix to apply to resulting shapes.
91+
cleanup : bool = True
92+
If True, removes extremely small features from each polygon's boundary.
93+
quad_segs : Optional[int] = None
94+
Number of segments used to discretize circular shapes. If ``None``, uses
95+
``_N_SHAPELY_QUAD_SEGS_VISUALIZATION`` for high-quality visualization.
8696
8797
Returns
8898
-------
@@ -91,6 +101,9 @@ def intersections_tilted_plane(
91101
For more details refer to
92102
`Shapely's Documentation <https://shapely.readthedocs.io/en/stable/project.html>`_.
93103
"""
104+
if quad_segs is None:
105+
quad_segs = _N_SHAPELY_QUAD_SEGS_VISUALIZATION
106+
94107
normal = np.array(normal)
95108
unit_normal = normal / (np.sum(normal**2) ** 0.5)
96109
projection = np.dot(np.array(origin) - np.array(self.center), unit_normal)
@@ -106,13 +119,18 @@ def intersections_tilted_plane(
106119
u /= np.sum(u**2) ** 0.5
107120
v = np.cross(unit_normal, u)
108121

109-
angles = np.linspace(0, 2 * np.pi, _N_SHAPELY_QUAD_SEGS * 4 + 1)[:-1]
122+
angles = np.linspace(0, 2 * np.pi, quad_segs * 4 + 1)[:-1]
110123
circ = center + np.outer(np.cos(angles), radius * u) + np.outer(np.sin(angles), radius * v)
111124
vertices = np.dot(np.hstack((circ, np.ones((angles.size, 1)))), to_2D.T)
112125
return [shapely.Polygon(vertices[:, :2])]
113126

114127
def intersections_plane(
115-
self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None
128+
self,
129+
x: Optional[float] = None,
130+
y: Optional[float] = None,
131+
z: Optional[float] = None,
132+
cleanup: bool = True,
133+
quad_segs: Optional[int] = None,
116134
) -> list[BaseGeometry]:
117135
"""Returns shapely geometry at plane specified by one non None value of x,y,z.
118136
@@ -124,22 +142,30 @@ def intersections_plane(
124142
Position of plane in x direction, only one of x,y,z can be specified to define plane.
125143
z : float = None
126144
Position of plane in x direction, only one of x,y,z can be specified to define plane.
145+
cleanup : bool = True
146+
If True, removes extremely small features from each polygon's boundary.
147+
quad_segs : Optional[int] = None
148+
Number of segments used to discretize circular shapes. If ``None``, uses
149+
``_N_SHAPELY_QUAD_SEGS_VISUALIZATION`` for high-quality visualization.
127150
128151
Returns
129152
-------
130153
List[shapely.geometry.base.BaseGeometry]
131154
List of 2D shapes that intersect plane.
132155
For more details refer to
133-
`Shapely's Documentation <https://shapely.readthedocs.io/en/stable/project.html>`_.
156+
`Shapely's Documentation <https://shapely.readthedocs.io/en/stable/project.html>``.
134157
"""
158+
if quad_segs is None:
159+
quad_segs = _N_SHAPELY_QUAD_SEGS_VISUALIZATION
160+
135161
axis, position = self.parse_xyz_kwargs(x=x, y=y, z=z)
136162
if not self.intersects_axis_position(axis, position):
137163
return []
138164
z0, (x0, y0) = self.pop_axis(self.center, axis=axis)
139165
intersect_dist = self._intersect_dist(position, z0)
140166
if not intersect_dist:
141167
return []
142-
return [shapely.Point(x0, y0).buffer(0.5 * intersect_dist, quad_segs=_N_SHAPELY_QUAD_SEGS)]
168+
return [shapely.Point(x0, y0).buffer(0.5 * intersect_dist, quad_segs=quad_segs)]
143169

144170
@cached_property
145171
def bounds(self) -> Bound:
@@ -430,7 +456,11 @@ def _update_from_bounds(self, bounds: tuple[float, float], axis: Axis) -> Cylind
430456

431457
@verify_packages_import(["trimesh"])
432458
def _do_intersections_tilted_plane(
433-
self, normal: Coordinate, origin: Coordinate, to_2D: MatrixReal4x4
459+
self,
460+
normal: Coordinate,
461+
origin: Coordinate,
462+
to_2D: MatrixReal4x4,
463+
quad_segs: Optional[int] = None,
434464
) -> list[Shapely]:
435465
"""Return a list of shapely geometries at the plane specified by normal and origin.
436466
@@ -442,6 +472,9 @@ def _do_intersections_tilted_plane(
442472
Vector defining the plane origin.
443473
to_2D : MatrixReal4x4
444474
Transformation matrix to apply to resulting shapes.
475+
quad_segs : Optional[int] = None
476+
Number of segments used to discretize circular shapes. If ``None``, uses
477+
``_N_SHAPELY_QUAD_SEGS_VISUALIZATION`` for high-quality visualization.
445478
446479
Returns
447480
-------
@@ -452,6 +485,9 @@ def _do_intersections_tilted_plane(
452485
"""
453486
import trimesh
454487

488+
if quad_segs is None:
489+
quad_segs = _N_SHAPELY_QUAD_SEGS_VISUALIZATION
490+
455491
z0, (x0, y0) = self.pop_axis(self.center, self.axis)
456492
half_length = self.finite_length_axis / 2
457493

@@ -471,7 +507,7 @@ def _do_intersections_tilted_plane(
471507
r_bot = 0
472508
z_bot = z0 + self._radius_z(z0) / self._tanq
473509

474-
angles = np.linspace(0, 2 * np.pi, _N_SHAPELY_QUAD_SEGS * 4 + 1)
510+
angles = np.linspace(0, 2 * np.pi, quad_segs * 4 + 1)
475511

476512
if r_bot > 0:
477513
x_bot = x0 + r_bot * np.cos(angles)
@@ -525,13 +561,18 @@ def _do_intersections_tilted_plane(
525561
path, _ = section.to_2D(to_2D=to_2D)
526562
return path.polygons_full
527563

528-
def _intersections_normal(self, z: float) -> list[BaseGeometry]:
564+
def _intersections_normal(
565+
self, z: float, quad_segs: Optional[int] = None
566+
) -> list[BaseGeometry]:
529567
"""Find shapely geometries intersecting cylindrical geometry with axis normal to slab.
530568
531569
Parameters
532570
----------
533571
z : float
534572
Position along the axis normal to slab
573+
quad_segs : Optional[int] = None
574+
Number of segments used to discretize circular shapes. If ``None``, uses
575+
``_N_SHAPELY_QUAD_SEGS_VISUALIZATION`` for high-quality visualization.
535576
536577
Returns
537578
-------
@@ -540,6 +581,8 @@ def _intersections_normal(self, z: float) -> list[BaseGeometry]:
540581
For more details refer to
541582
`Shapely's Documentation <https://shapely.readthedocs.io/en/stable/project.html>`_.
542583
"""
584+
if quad_segs is None:
585+
quad_segs = _N_SHAPELY_QUAD_SEGS_VISUALIZATION
543586

544587
static_self = self.to_static()
545588

@@ -550,7 +593,7 @@ def _intersections_normal(self, z: float) -> list[BaseGeometry]:
550593
return []
551594

552595
_, (x0, y0) = self.pop_axis(static_self.center, axis=self.axis)
553-
return [shapely.Point(x0, y0).buffer(radius_offset, quad_segs=_N_SHAPELY_QUAD_SEGS)]
596+
return [shapely.Point(x0, y0).buffer(radius_offset, quad_segs=quad_segs)]
554597

555598
def _intersections_side(self, position: float, axis: int) -> list[BaseGeometry]:
556599
"""Find shapely geometries intersecting cylindrical geometry with axis orthogonal to length.

tidy3d/components/geometry/utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def merging_geometries_on_plane(
9393
plane: Box,
9494
property_list: list[Any],
9595
interior_disjoint_geometries: bool = False,
96+
cleanup: bool = True,
97+
quad_segs: Optional[int] = None,
9698
) -> list[tuple[Any, Shapely]]:
9799
"""Compute list of shapes on plane. Overlaps are removed or merged depending on
98100
provided property_list.
@@ -107,6 +109,11 @@ def merging_geometries_on_plane(
107109
Property value for each structure.
108110
interior_disjoint_geometries: bool = False
109111
If ``True``, geometries of different properties on the plane must not be overlapping.
112+
cleanup : bool = True
113+
If True, removes extremely small features from each polygon's boundary.
114+
quad_segs : Optional[int] = None
115+
Number of segments used to discretize circular shapes. If ``None``, uses
116+
high-quality visualization settings.
110117
111118
Returns
112119
-------
@@ -122,7 +129,7 @@ def merging_geometries_on_plane(
122129
shapes = []
123130
for geo, prop in zip(geometries, property_list):
124131
# get list of Shapely shapes that intersect at the plane
125-
shapes_plane = plane.intersections_with(geo)
132+
shapes_plane = plane.intersections_with(geo, cleanup=cleanup, quad_segs=quad_segs)
126133

127134
# Append each of them and their property information to the list of shapes
128135
for shape in shapes_plane:

tidy3d/components/grid/corner_finder.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
from tidy3d.constants import inf
1717

1818
CORNER_ANGLE_THRESOLD = 0.25 * np.pi
19+
# For shapely circular shapes discretization.
20+
N_SHAPELY_QUAD_SEGS = 8
21+
# whether to clean tiny features that sometimes occurs in shapely operations
22+
SHAPELY_CLEANUP = False
1923

2024

2125
class CornerFinderSpec(Tidy3dBaseModel):
@@ -132,7 +136,12 @@ def _merged_pec_on_plane(
132136
medium_list = [PEC for _ in geometry_list]
133137
# merge geometries
134138
merged_geos = merging_geometries_on_plane(
135-
geometry_list, plane, medium_list, interior_disjoint_geometries
139+
geometry_list,
140+
plane,
141+
medium_list,
142+
interior_disjoint_geometries,
143+
cleanup=SHAPELY_CLEANUP,
144+
quad_segs=N_SHAPELY_QUAD_SEGS,
136145
)
137146

138147
return merged_geos

0 commit comments

Comments
 (0)