Skip to content

Commit 48bf882

Browse files
PR comments, small feature filter bug, fix for boundary conditions
1 parent ddbe4ae commit 48bf882

File tree

3 files changed

+180
-39
lines changed

3 files changed

+180
-39
lines changed

tidy3d/components/grid/corner_finder.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Find corners of structures on a 2D plane."""
22

3-
from typing import List, Literal, Optional, Tuple
3+
from typing import Any, List, Literal, Optional, Tuple
44

55
import numpy as np
66
import pydantic.v1 as pd
@@ -11,7 +11,7 @@
1111
from ..geometry.utils import merging_geometries_on_plane
1212
from ..medium import PEC, LossyMetalMedium
1313
from ..structure import Structure
14-
from ..types import ArrayFloat1D, ArrayFloat2D, Axis
14+
from ..types import ArrayFloat1D, ArrayFloat2D, Axis, Shapely
1515

1616
CORNER_ANGLE_THRESOLD = 0.1 * np.pi
1717

@@ -81,7 +81,9 @@ def _merged_pec_on_plane(
8181
normal_axis: Axis,
8282
coord: float,
8383
structure_list: List[Structure],
84-
) -> Tuple[ArrayFloat2D, ArrayFloat1D]:
84+
center: Tuple[float, float] = [0, 0, 0],
85+
size: Tuple[float, float, float] = [inf, inf, inf],
86+
) -> List[Tuple[Any, Shapely]]:
8587
"""On a 2D plane specified by axis = `normal_axis` and coordinate `coord`, merge geometries made of PEC.
8688
8789
Parameters
@@ -92,19 +94,23 @@ def _merged_pec_on_plane(
9294
Position of plane along the normal axis.
9395
structure_list : List[Structure]
9496
List of structures present in simulation.
97+
center : Tuple[float, float] = [0, 0, 0]
98+
Center of the 2D plane (coordinate along ``axis`` is ignored)
99+
size : Tuple[float, float, float] = [inf, inf, inf]
100+
Size of the 2D plane (size along ``axis`` is ignored)
95101
96102
Returns
97103
-------
98-
ArrayFloat2D
99-
Corner coordinates.
104+
List[Tuple[Any, Shapely]]
105+
List of shapes and their property value on the plane after merging.
100106
"""
101107

102108
# Construct plane
103-
center = [0, 0, 0]
104-
size = [inf, inf, inf]
105-
center[normal_axis] = coord
106-
size[normal_axis] = 0
107-
plane = Box(center=center, size=size)
109+
slice_center = list(center)
110+
slice_size = list(size)
111+
slice_center[normal_axis] = coord
112+
slice_size[normal_axis] = 0
113+
plane = Box(center=slice_center, size=slice_size)
108114

109115
# prepare geometry and medium list
110116
geometry_list = [structure.geometry for structure in structure_list]
@@ -143,8 +149,8 @@ def _corners_and_convexity(
143149
144150
Returns
145151
-------
146-
ArrayFloat2D
147-
Corner coordinates.
152+
Tuple[ArrayFloat2D, ArrayFloat1D]
153+
Corner coordinates and their convexity.
148154
"""
149155

150156
# merge geometries

tidy3d/components/grid/grid_spec.py

+147-24
Original file line numberDiff line numberDiff line change
@@ -1052,8 +1052,9 @@ class LayerRefinementSpec(Box):
10521052

10531053
dl_min_from_gap_width: bool = pd.Field(
10541054
True,
1055-
title="Set ``dl_min`` from Gap Width",
1056-
description="Take into account autodetected minimal PEC gap width when determining ``dl_min``.",
1055+
title="Set ``dl_min`` from Estimated Gap Width",
1056+
description="Take into account autodetected minimal PEC gap width when determining ``dl_min``. "
1057+
"This only applies if ``dl_min`` in ``AutoGrid`` specification is not set.",
10571058
)
10581059

10591060
@pd.validator("axis", always=True)
@@ -1554,7 +1555,7 @@ def _override_structures_along_axis(
15541555
return override_structures
15551556

15561557
def _find_vertical_intersections(
1557-
self, grid_x_coords, grid_y_coords, poly_vertices
1558+
self, grid_x_coords, grid_y_coords, poly_vertices, boundary
15581559
) -> Tuple[List[Tuple[int, int]], List[float]]:
15591560
"""Detect intersection points of single polygon and vertical grid lines."""
15601561

@@ -1587,7 +1588,9 @@ def _find_vertical_intersections(
15871588
continue
15881589

15891590
# sort vertices in ascending order to make treatmeant unifrom
1591+
reverse = False
15901592
if ind_beg > ind_end:
1593+
reverse = True
15911594
ind_beg, ind_end, v_beg, v_end = ind_end, ind_beg, v_end, v_beg
15921595

15931596
# x coordinates are simply x coordinates of intersected vertical grid lines
@@ -1623,6 +1626,12 @@ def _find_vertical_intersections(
16231626
grid_y_coords[cell_js + 1] - grid_y_coords[cell_js]
16241627
)
16251628

1629+
# preserve uniform ordering along perimeter of the polygon
1630+
if reverse:
1631+
cell_is = cell_is[::-1]
1632+
cell_js = cell_js[::-1]
1633+
dy = dy[::-1]
1634+
16261635
# record info
16271636
cells_ij.append(np.transpose([cell_is, cell_js]))
16281637
cells_dy.append(dy)
@@ -1665,39 +1674,55 @@ def _find_vertical_intersections(
16651674
points_to_duplicate_near_zero = cells_ij[close_to_zero]
16661675
points_to_duplicate_near_one = cells_ij[close_to_one]
16671676

1677+
# if we go beyond simulation domain boundary, either ignore
1678+
# or wrap periodically depending on boundary conditions
1679+
cells_ij_zero_side = points_to_duplicate_near_zero - np.array([0, 1])
1680+
cells_zero_side_out = cells_ij_zero_side[:, 1] == -1
1681+
if boundary[0] == "periodic":
1682+
cells_ij_zero_side[cells_zero_side_out, 1] = len(grid_y_coords) - 2
1683+
else:
1684+
cells_ij_zero_side = cells_ij_zero_side[cells_zero_side_out == 0]
1685+
1686+
cells_ij_one_side = points_to_duplicate_near_one + np.array([0, 1])
1687+
cells_one_side_out = cells_ij_one_side[:, 1] == len(grid_y_coords) - 1
1688+
if boundary[1] == "periodic":
1689+
cells_ij_one_side[cells_one_side_out, 1] = 0
1690+
else:
1691+
cells_ij_one_side = cells_ij_one_side[cells_one_side_out == 0]
1692+
16681693
cells_ij = np.concatenate(
16691694
[
16701695
cells_ij,
1671-
points_to_duplicate_near_zero - np.array([0, 1]),
1672-
points_to_duplicate_near_one + np.array([0, 1]),
1696+
cells_ij_zero_side,
1697+
cells_ij_one_side,
16731698
]
16741699
)
16751700
cells_dy = np.concatenate(
16761701
[
16771702
cells_dy,
1678-
np.ones(len(points_to_duplicate_near_zero)),
1679-
np.zeros(len(points_to_duplicate_near_one)),
1703+
np.ones(len(cells_ij_zero_side)),
1704+
np.zeros(len(cells_ij_one_side)),
16801705
]
16811706
)
16821707

16831708
return cells_ij, cells_dy
16841709

16851710
def _process_poly(
1686-
self, grid_x_coords, grid_y_coords, poly_vertices
1711+
self, grid_x_coords, grid_y_coords, poly_vertices, boundaries
16871712
) -> Tuple[List[Tuple[int, int]], List[float], List[Tuple[int, int]], List[float]]:
16881713
"""Detect intersection points of single polygon and grid lines."""
16891714

16901715
# find cells that contain intersections of vertical grid lines
16911716
# and relative locations of those intersections (along y axis)
16921717
v_cells_ij, v_cells_dy = self._find_vertical_intersections(
1693-
grid_x_coords, grid_y_coords, poly_vertices
1718+
grid_x_coords, grid_y_coords, poly_vertices, boundaries[1]
16941719
)
16951720

16961721
# find cells that contain intersections of horizontal grid lines
16971722
# and relative locations of those intersections (along x axis)
16981723
# reuse the same command but flip dimensions
16991724
h_cells_ij, h_cells_dx = self._find_vertical_intersections(
1700-
grid_y_coords, grid_x_coords, np.flip(poly_vertices, axis=1)
1725+
grid_y_coords, grid_x_coords, np.flip(poly_vertices, axis=1), boundaries[0]
17011726
)
17021727
if len(h_cells_ij) > 0:
17031728
# flip dimensions back
@@ -1706,7 +1731,7 @@ def _process_poly(
17061731
return v_cells_ij, v_cells_dy, h_cells_ij, h_cells_dx
17071732

17081733
def _process_slice(
1709-
self, x, y, merged_geos
1734+
self, x, y, merged_geos, boundaries
17101735
) -> Tuple[List[Tuple[int, int]], List[float], List[Tuple[int, int]], List[float]]:
17111736
"""Detect intersection points of geometries boundaries and grid lines."""
17121737

@@ -1720,6 +1745,27 @@ def _process_slice(
17201745
# relative locations of those intersections (along x axis)
17211746
h_cells_dx = []
17221747

1748+
# for PEC and PMC boundary - treat them as PEC structure
1749+
# so that gaps are resolved near boundaries if any
1750+
nx = len(x)
1751+
ny = len(y)
1752+
1753+
if boundaries[0][0] == "pec/pmc":
1754+
h_cells_ij.append(np.transpose([np.zeros(ny), np.arange(ny)]).astype(int))
1755+
h_cells_dx.append(np.zeros(ny))
1756+
1757+
if boundaries[0][1] == "pec/pmc":
1758+
h_cells_ij.append(np.transpose([(nx - 2) * np.ones(ny), np.arange(ny)]).astype(int))
1759+
h_cells_dx.append(np.ones(ny))
1760+
1761+
if boundaries[1][0] == "pec/pmc":
1762+
v_cells_ij.append(np.transpose([np.arange(nx), np.zeros(nx)]).astype(int))
1763+
v_cells_dy.append(np.zeros(nx, dtype=int))
1764+
1765+
if boundaries[1][1] == "pec/pmc":
1766+
v_cells_ij.append(np.transpose([np.arange(nx), (ny - 2) * np.ones(nx)]).astype(int))
1767+
v_cells_dy.append(np.ones(nx))
1768+
17231769
# loop over all shapes
17241770
for mat, shapes in merged_geos:
17251771
if not mat.is_pec:
@@ -1736,7 +1782,7 @@ def _process_slice(
17361782
# 1. relative locations of those intersections along y axis
17371783
# 2. cells that contain intersections of horizontal grid lines
17381784
# 3. relative locations of those intersections along x axis
1739-
data = self._process_poly(x, y, np.array(poly.exterior.coords)[:-1])
1785+
data = self._process_poly(x, y, np.array(poly.exterior.coords)[:-1], boundaries)
17401786

17411787
if len(data[0]) > 0:
17421788
v_cells_ij.append(data[0])
@@ -1748,7 +1794,7 @@ def _process_slice(
17481794

17491795
# in case the polygon has holes
17501796
for poly_inner in poly.interiors:
1751-
data = self._process_poly(x, y, np.array(poly_inner.coords)[:-1])
1797+
data = self._process_poly(x, y, np.array(poly_inner.coords)[:-1], boundaries)
17521798
if len(data[0]) > 0:
17531799
v_cells_ij.append(data[0])
17541800
v_cells_dy.append(data[1])
@@ -1845,27 +1891,82 @@ def _generate_horizontal_snapping_lines(
18451891

18461892
return snapping_lines_y, min_gap_width
18471893

1848-
def _resolve_gaps(self, structures: List, grid: Grid) -> Tuple[List[CoordinateOptional], float]:
1894+
def _resolve_gaps(
1895+
self, structures: List[Structure], grid: Grid, boundaries: Tuple, center, size
1896+
) -> Tuple[List[CoordinateOptional], float]:
18491897
"""Detect underresolved gaps and place snapping lines in them. Also return the detected minimal gap width."""
18501898

1851-
# get merged pec structures on plane
1852-
# note that we expect this function to also convert all LossyMetal's into PEC
1853-
plane_slice = CornerFinderSpec._merged_pec_on_plane(
1854-
coord=self.center_axis, normal_axis=self.axis, structure_list=structures
1855-
)
1856-
18571899
# get x and y coordinates of grid lines
18581900
_, tan_dims = Box.pop_axis([0, 1, 2], self.axis)
18591901
x = grid.boundaries.to_list[tan_dims[0]]
18601902
y = grid.boundaries.to_list[tan_dims[1]]
18611903

1904+
_, boundaries_tan = Box.pop_axis(boundaries, self.axis)
1905+
1906+
# restrict to the size of layer spec
1907+
rmin, rmax = self.bounds
1908+
_, rmin = Box.pop_axis(rmin, self.axis)
1909+
_, rmax = Box.pop_axis(rmax, self.axis)
1910+
1911+
new_coords = []
1912+
new_boundaries = []
1913+
for coord, cmin, cmax, bdry in zip([x, y], rmin, rmax, boundaries_tan):
1914+
if cmax <= coord[0] or cmin >= coord[-1]:
1915+
return [], inf
1916+
else:
1917+
if cmin < coord[0]:
1918+
ind_min = 0
1919+
else:
1920+
ind_min = max(0, np.argmax(coord >= cmin) - 1)
1921+
1922+
if cmax > coord[-1]:
1923+
ind_max = len(coord) - 1
1924+
else:
1925+
ind_max = np.argmax(coord >= cmax)
1926+
1927+
if ind_min >= ind_max:
1928+
return [], inf
1929+
1930+
new_coords.append(coord[ind_min : (ind_max + 1)])
1931+
# ignore boundary conditions if we are not touching them
1932+
new_boundaries.append(
1933+
[
1934+
None if ind_min > 0 else bdry[0],
1935+
None if ind_max < len(coord) - 1 else bdry[1],
1936+
]
1937+
)
1938+
1939+
x, y = new_coords
1940+
1941+
# restrict size of the plane where pec polygons are found in case of periodic boundary conditions
1942+
# this is to make sure gaps across periodic boundary conditions are resolved
1943+
# (if there is a PEC structure going into periodic boundary, now it will generate a grid line
1944+
# intersection next to that boundary and it will be propagated to the other side)
1945+
restricted_size_tan = [
1946+
s - GAP_MESHING_TOL / 2 if b[0] == "periodic" else inf
1947+
for b, s in zip(new_boundaries, size)
1948+
]
1949+
restricted_size = Box.unpop_axis(size[self.axis], restricted_size_tan, self.axis)
1950+
1951+
# get merged pec structures on plane
1952+
# note that we expect this function to also convert all LossyMetal's into PEC
1953+
plane_slice = CornerFinderSpec._merged_pec_on_plane(
1954+
coord=self.center_axis,
1955+
normal_axis=self.axis,
1956+
structure_list=structures,
1957+
center=center,
1958+
size=restricted_size,
1959+
)
1960+
18621961
# find intersections of pec polygons with grid lines
18631962
# specifically:
18641963
# 0. cells that contain intersections of vertical grid lines
18651964
# 1. relative locations of those intersections along y axis
18661965
# 2. cells that contain intersections of horizontal grid lines
18671966
# 3. relative locations of those intersections along x axis
1868-
v_cells_ij, v_cells_dy, h_cells_ij, h_cells_dx = self._process_slice(x, y, plane_slice)
1967+
v_cells_ij, v_cells_dy, h_cells_ij, h_cells_dx = self._process_slice(
1968+
x, y, plane_slice, new_boundaries
1969+
)
18691970

18701971
# generate horizontal snapping lines
18711972
snapping_lines_y, min_gap_width_along_y = self._generate_horizontal_snapping_lines(
@@ -2289,6 +2390,11 @@ def make_grid(
22892390
lumped_elements: List[LumpedElementType] = (),
22902391
internal_override_structures: List[MeshOverrideStructure] = None,
22912392
internal_snapping_points: List[CoordinateOptional] = None,
2393+
boundary_types: Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [
2394+
[None, None],
2395+
[None, None],
2396+
[None, None],
2397+
],
22922398
) -> Grid:
22932399
"""Make the entire simulation grid based on some simulation parameters.
22942400
@@ -2313,6 +2419,9 @@ def make_grid(
23132419
If `None`, recomputes internal override structures.
23142420
internal_snapping_points : List[CoordinateOptional]
23152421
If `None`, recomputes internal snapping points.
2422+
boundary_types : Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [[None, None], [None, None], [None, None]]
2423+
Type of boundary conditions along each dimension: "pec/pmc", "periodic", or
2424+
None for any other. This is relevant only for gap meshing.
23162425
23172426
Returns
23182427
-------
@@ -2343,6 +2452,11 @@ def _make_grid_and_snapping_lines(
23432452
lumped_elements: List[LumpedElementType] = (),
23442453
internal_override_structures: List[MeshOverrideStructure] = None,
23452454
internal_snapping_points: List[CoordinateOptional] = None,
2455+
boundary_types: Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [
2456+
[None, None],
2457+
[None, None],
2458+
[None, None],
2459+
],
23462460
) -> Tuple[Grid, List[CoordinateOptional]]:
23472461
"""Make the entire simulation grid based on some simulation parameters.
23482462
Also return snappiung point resulted from iterative gap meshing.
@@ -2368,11 +2482,14 @@ def _make_grid_and_snapping_lines(
23682482
If `None`, recomputes internal override structures.
23692483
internal_snapping_points : List[CoordinateOptional]
23702484
If `None`, recomputes internal snapping points.
2485+
boundary_types : Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [[None, None], [None, None], [None, None]]
2486+
Type of boundary conditions along each dimension: "pec/pmc", "periodic", or
2487+
None for any other. This is relevant only for gap meshing.
23712488
23722489
Returns
23732490
-------
2374-
Grid:
2375-
Entire simulation grid.
2491+
Tuple[Grid, List[CoordinateOptional]]:
2492+
Entire simulation grid and snapping points generated during iterative gap meshing.
23762493
"""
23772494

23782495
old_grid = self._make_grid_one_iteration(
@@ -2386,6 +2503,8 @@ def _make_grid_and_snapping_lines(
23862503
internal_snapping_points=internal_snapping_points,
23872504
)
23882505

2506+
sim_geometry = structures[0].geometry
2507+
23892508
snapping_lines = []
23902509
if len(self.layer_refinement_specs) > 0:
23912510
num_iters = max(
@@ -2398,7 +2517,11 @@ def _make_grid_and_snapping_lines(
23982517
for layer_spec in self.layer_refinement_specs:
23992518
if layer_spec.gap_meshing_iters > ind:
24002519
one_layer_snapping_lines, gap_width = layer_spec._resolve_gaps(
2401-
structures, old_grid
2520+
structures,
2521+
old_grid,
2522+
boundary_types,
2523+
center=sim_geometry.center,
2524+
size=sim_geometry.size,
24022525
)
24032526
new_snapping_lines = new_snapping_lines + one_layer_snapping_lines
24042527
if layer_spec.dl_min_from_gap_width:

0 commit comments

Comments
 (0)