@@ -1553,7 +1553,9 @@ def _override_structures_along_axis(
1553
1553
override_structures += refinement_structures
1554
1554
return override_structures
1555
1555
1556
- def _find_vertical_intersections (self , grid_x_coords , grid_y_coords , poly_vertices ) -> Tuple [List [Tuple [int , int ]], List [float ]]:
1556
+ def _find_vertical_intersections (
1557
+ self , grid_x_coords , grid_y_coords , poly_vertices
1558
+ ) -> Tuple [List [Tuple [int , int ]], List [float ]]:
1557
1559
"""Detect intersection points of single polygon and vertical grid lines."""
1558
1560
1559
1561
# indices of cells that contain intersection with grid lines (left edge of a cell)
@@ -1599,7 +1601,9 @@ def _find_vertical_intersections(self, grid_x_coords, grid_y_coords, poly_vertic
1599
1601
# however, some of the vertical lines might be crossed
1600
1602
# outside of computational domain
1601
1603
# so we need to see which ones are actually inside along y axis
1602
- inds_inside_grid = np .logical_and (intersections_y >= y [0 ], intersections_y <= y [- 1 ])
1604
+ inds_inside_grid = np .logical_and (
1605
+ intersections_y >= grid_y_coords [0 ], intersections_y <= grid_y_coords [- 1 ]
1606
+ )
1603
1607
1604
1608
intersections_y = intersections_y [inds_inside_grid ]
1605
1609
@@ -1615,60 +1619,94 @@ def _find_vertical_intersections(self, grid_x_coords, grid_y_coords, poly_vertic
1615
1619
1616
1620
# find local dy, that is, the distance between the intersection point
1617
1621
# and the bottom edge of the cell
1618
- dy = (intersections_y - grid_y_coords [cell_js ]) / (grid_y_coords [cell_js + 1 ] - grid_y_coords [cell_js ])
1622
+ dy = (intersections_y - grid_y_coords [cell_js ]) / (
1623
+ grid_y_coords [cell_js + 1 ] - grid_y_coords [cell_js ]
1624
+ )
1619
1625
1620
1626
# record info
1621
1627
cells_ij .append (np .transpose ([cell_is , cell_js ]))
1622
1628
cells_dy .append (dy )
1623
1629
1624
1630
if len (cells_ij ) > 0 :
1625
1631
cells_ij = np .concatenate (cells_ij )
1626
-
1627
- if len (cells_dy ) > 0 :
1628
1632
cells_dy = np .concatenate (cells_dy )
1629
1633
1634
+ # Filter from re-entering subcell features. That is, we discard any consecutive
1635
+ # intersections if they are crossing the same edge. This happens, for example,
1636
+ # when a tiny feature pokes through an edge. This helps not to set dl_min
1637
+ # to a very low value, and take into account only actual gaps and strips.
1638
+
1639
+ # To do that we use the fact that intersection points are recorded and stored
1640
+ # in the order as they appear along the border of the polygon.
1641
+
1642
+ # first we calculate linearized indices of edges (cells) they cross
1630
1643
linear_index = cells_ij [:, 0 ] * len (grid_y_coords ) + cells_ij [:, 1 ]
1631
1644
1645
+ # then look at the differences with next and previous neighbors
1632
1646
fwd_diff = linear_index - np .roll (linear_index , - 1 )
1633
- bwd_diff = linear_index - np .roll (linear_index , 1 )
1647
+ bwd_diff = np .roll (fwd_diff , 1 )
1634
1648
1649
+ # an intersection point is not a part of a "re-entering subcell feature"
1650
+ # if it doesn't cross the same edges as its neighbors
1635
1651
valid = np .logical_and (fwd_diff != 0 , bwd_diff != 0 )
1636
1652
1637
1653
cells_dy = cells_dy [valid ]
1638
1654
cells_ij = cells_ij [valid ]
1639
1655
1640
- # duplicate crossings that are too close to cell boundaries
1641
- close_zero = cells_dy < GAP_MESHING_TOL
1642
- close_one = (1.0 - cells_dy ) < GAP_MESHING_TOL
1643
-
1644
- duplicate_ij_zero = cells_ij [close_zero ]
1645
- duplicate_ij_one = cells_ij [close_one ]
1646
-
1647
- cells_ij = np .concatenate ([cells_ij , duplicate_ij_zero - np .array ([0 , 1 ]), duplicate_ij_one + np .array ([0 , 1 ])])
1648
- cells_dy = np .concatenate ([cells_dy , (1.0 - GAP_MESHING_TOL / 10 ) * np .ones (len (duplicate_ij_zero )), GAP_MESHING_TOL / 10 * np .ones (len (duplicate_ij_one ))])
1656
+ # Now we are duplicating intersection points very close to cell boundaries
1657
+ # to corresponding adjacent cells. Basically, if we have a line crossing
1658
+ # very close to a grid node, we consider that it crosses edges on both sides
1659
+ # from that node. That is, this serves as a tolerance allowance.
1660
+ # Note that duplicated intersections and their originals will be snapped to
1661
+ # cell boundaries during quantization later.
1662
+ close_to_zero = cells_dy < GAP_MESHING_TOL
1663
+ close_to_one = (1.0 - cells_dy ) < GAP_MESHING_TOL
1664
+
1665
+ points_to_duplicate_near_zero = cells_ij [close_to_zero ]
1666
+ points_to_duplicate_near_one = cells_ij [close_to_one ]
1667
+
1668
+ cells_ij = np .concatenate (
1669
+ [
1670
+ cells_ij ,
1671
+ points_to_duplicate_near_zero - np .array ([0 , 1 ]),
1672
+ points_to_duplicate_near_one + np .array ([0 , 1 ]),
1673
+ ]
1674
+ )
1675
+ cells_dy = np .concatenate (
1676
+ [
1677
+ cells_dy ,
1678
+ np .ones (len (points_to_duplicate_near_zero )),
1679
+ np .zeros (len (points_to_duplicate_near_one )),
1680
+ ]
1681
+ )
1649
1682
1650
1683
return cells_ij , cells_dy
1651
1684
1652
1685
def _process_poly (
1653
- self , grid_x_coords , grid_y_coords , poly_vertices
1686
+ self , grid_x_coords , grid_y_coords , poly_vertices
1654
1687
) -> Tuple [List [Tuple [int , int ]], List [float ], List [Tuple [int , int ]], List [float ]]:
1655
1688
"""Detect intersection points of single polygon and grid lines."""
1656
1689
1657
1690
# find cells that contain intersections of vertical grid lines
1658
1691
# and relative locations of those intersections (along y axis)
1659
- v_cells_ij , v_cells_dy = self ._find_vertical_intersections (grid_x_coords , grid_y_coords , poly_vertices )
1692
+ v_cells_ij , v_cells_dy = self ._find_vertical_intersections (
1693
+ grid_x_coords , grid_y_coords , poly_vertices
1694
+ )
1660
1695
1661
1696
# find cells that contain intersections of horizontal grid lines
1662
1697
# and relative locations of those intersections (along x axis)
1663
1698
# reuse the same command but flip dimensions
1664
- h_cells_ij , h_cells_dx = self ._find_vertical_intersections (grid_y_coords , grid_x_coords , np .flip (poly_vertices , axis = 1 ))
1699
+ h_cells_ij , h_cells_dx = self ._find_vertical_intersections (
1700
+ grid_y_coords , grid_x_coords , np .flip (poly_vertices , axis = 1 )
1701
+ )
1665
1702
if len (h_cells_ij ) > 0 :
1703
+ # flip dimensions back
1666
1704
h_cells_ij = np .roll (h_cells_ij , axis = 1 , shift = 1 )
1667
1705
1668
1706
return v_cells_ij , v_cells_dy , h_cells_ij , h_cells_dx
1669
1707
1670
1708
def _process_slice (
1671
- self , x , y , merged_geos
1709
+ self , x , y , merged_geos
1672
1710
) -> Tuple [List [Tuple [int , int ]], List [float ], List [Tuple [int , int ]], List [float ]]:
1673
1711
"""Detect intersection points of geometries boundaries and grid lines."""
1674
1712
@@ -1685,6 +1723,8 @@ def _process_slice(
1685
1723
# loop over all shapes
1686
1724
for mat , shapes in merged_geos :
1687
1725
if not mat .is_pec :
1726
+ # note that we expect LossyMetal's converted into PEC in merged_geos
1727
+ # that is why we are not checking for that separately
1688
1728
continue
1689
1729
polygon_list = ClipOperation .to_polygon_list (shapes )
1690
1730
for poly in polygon_list :
@@ -1726,12 +1766,12 @@ def _process_slice(
1726
1766
h_cells_dx = np .concatenate (h_cells_dx )
1727
1767
1728
1768
return v_cells_ij , v_cells_dy , h_cells_ij , h_cells_dx
1729
-
1769
+
1730
1770
def _generate_horizontal_snapping_lines (
1731
- self , grid_y_coords , intersected_cells_ij , relative_vert_disp
1771
+ self , grid_y_coords , intersected_cells_ij , relative_vert_disp
1732
1772
) -> Tuple [List [CoordinateOptional ], float ]:
1733
1773
"""Convert a list of intersections of vertical grid lines, given as coordinates of cells
1734
- and relative vertical displacement inside each cell, into locations of snapping lines that
1774
+ and relative vertical displacement inside each cell, into locations of snapping lines that
1735
1775
resolve thin gaps and strips.
1736
1776
"""
1737
1777
min_gap_width = inf
@@ -1740,9 +1780,11 @@ def _generate_horizontal_snapping_lines(
1740
1780
if len (intersected_cells_ij ) > 0 :
1741
1781
# quantize intersection locations
1742
1782
relative_vert_disp = np .round (relative_vert_disp / GAP_MESHING_TOL ).astype (int )
1743
- cell_linear_inds = intersected_cells_ij [:, 0 ] * len (grid_y_coords ) + intersected_cells_ij [:, 1 ]
1783
+ cell_linear_inds = (
1784
+ intersected_cells_ij [:, 0 ] * len (grid_y_coords ) + intersected_cells_ij [:, 1 ]
1785
+ )
1744
1786
cell_linear_inds_and_disps = np .transpose ([cell_linear_inds , relative_vert_disp ])
1745
- # remove duplicates
1787
+ # remove duplicates
1746
1788
cell_linear_inds_and_disps_unique = np .unique (cell_linear_inds_and_disps , axis = 0 )
1747
1789
1748
1790
# count intersections of vertical grid lines in each cell
@@ -1767,31 +1809,47 @@ def _generate_horizontal_snapping_lines(
1767
1809
j_selection = cell_linear_inds_unique_j == ind_j
1768
1810
# and number intersections in each of them
1769
1811
counts_j = counts [j_selection ]
1770
-
1812
+
1771
1813
# find cell with max intersections
1772
1814
max_count_el = np .argmax (counts_j )
1773
1815
max_count = counts_j [max_count_el ]
1774
1816
if max_count > 1 :
1775
1817
# get its linear index
1776
1818
target_cell_linear_ind = cell_linear_inds_unique [j_selection ][max_count_el ]
1777
1819
# look up relative positions of intersections in that cells
1778
- target_disps = np .sort (cell_linear_inds_and_disps_unique [cell_linear_inds_and_disps_unique [:, 0 ] == target_cell_linear_ind , 1 ])
1820
+ target_disps = np .sort (
1821
+ cell_linear_inds_and_disps_unique [
1822
+ cell_linear_inds_and_disps_unique [:, 0 ] == target_cell_linear_ind , 1
1823
+ ]
1824
+ )
1779
1825
1780
1826
# place a snapping line between any two neighboring intersections (in relative units)
1781
- relative_snap_lines_pos = 0.5 * (target_disps [1 :] + target_disps [:- 1 ]) * GAP_MESHING_TOL
1827
+ relative_snap_lines_pos = (
1828
+ 0.5 * (target_disps [1 :] + target_disps [:- 1 ]) * GAP_MESHING_TOL
1829
+ )
1782
1830
# convert relative positions to absolute ones
1783
- snapping_lines_y += [grid_y_coords [ind_j ] + rel_pos * (grid_y_coords [ind_j + 1 ] - grid_y_coords [ind_j ]) for rel_pos in relative_snap_lines_pos ]
1831
+ snapping_lines_y += [
1832
+ grid_y_coords [ind_j ]
1833
+ + rel_pos * (grid_y_coords [ind_j + 1 ] - grid_y_coords [ind_j ])
1834
+ for rel_pos in relative_snap_lines_pos
1835
+ ]
1784
1836
1785
1837
# compute minimal gap/strip width
1786
- min_gap_width_current = np .min (target_disps [1 :] - target_disps [:- 1 ]) * GAP_MESHING_TOL
1787
- min_gap_width = min (min_gap_width , min_gap_width_current * (grid_y_coords [ind_j + 1 ] - grid_y_coords [ind_j ]))
1838
+ min_gap_width_current = (
1839
+ np .min (target_disps [1 :] - target_disps [:- 1 ]) * GAP_MESHING_TOL
1840
+ )
1841
+ min_gap_width = min (
1842
+ min_gap_width ,
1843
+ min_gap_width_current * (grid_y_coords [ind_j + 1 ] - grid_y_coords [ind_j ]),
1844
+ )
1788
1845
1789
1846
return snapping_lines_y , min_gap_width
1790
1847
1791
1848
def _resolve_gaps (self , structures : List , grid : Grid ) -> Tuple [List [CoordinateOptional ], float ]:
1792
1849
"""Detect underresolved gaps and place snapping lines in them. Also return the detected minimal gap width."""
1793
1850
1794
1851
# get merged pec structures on plane
1852
+ # note that we expect this function to also convert all LossyMetal's into PEC
1795
1853
plane_slice = CornerFinderSpec ._merged_pec_on_plane (
1796
1854
coord = self .center_axis , normal_axis = self .axis , structure_list = structures
1797
1855
)
@@ -1810,16 +1868,28 @@ def _resolve_gaps(self, structures: List, grid: Grid) -> Tuple[List[CoordinateOp
1810
1868
v_cells_ij , v_cells_dy , h_cells_ij , h_cells_dx = self ._process_slice (x , y , plane_slice )
1811
1869
1812
1870
# generate horizontal snapping lines
1813
- snapping_lines_y , min_gap_width_along_y = self ._generate_horizontal_snapping_lines (y , v_cells_ij , v_cells_dy )
1871
+ snapping_lines_y , min_gap_width_along_y = self ._generate_horizontal_snapping_lines (
1872
+ y , v_cells_ij , v_cells_dy
1873
+ )
1874
+ detected_gap_width = min_gap_width_along_y
1814
1875
1815
1876
# generate vertical snapping lines
1816
- snapping_lines_x , min_gap_width_along_x = self ._generate_horizontal_snapping_lines (x , np .roll (h_cells_ij , shift = 1 , axis = 1 ), h_cells_dx )
1877
+ if len (h_cells_ij ) > 0 : # check, otherwise np.roll fails
1878
+ snapping_lines_x , min_gap_width_along_x = self ._generate_horizontal_snapping_lines (
1879
+ x , np .roll (h_cells_ij , shift = 1 , axis = 1 ), h_cells_dx
1880
+ )
1817
1881
1818
- detected_gap_width = min (min_gap_width_along_y , min_gap_width_along_x )
1882
+ detected_gap_width = min (detected_gap_width , min_gap_width_along_x )
1883
+ else :
1884
+ snapping_lines_x = []
1819
1885
1820
1886
# convert snapping lines' coordinates into 3d coordinates
1821
- snapping_lines_y_3d = [Box .unpop_axis (Y , (None , None ), axis = tan_dims [1 ]) for Y in snapping_lines_y ]
1822
- snapping_lines_x_3d = [Box .unpop_axis (X , (None , None ), axis = tan_dims [0 ]) for X in snapping_lines_x ]
1887
+ snapping_lines_y_3d = [
1888
+ Box .unpop_axis (Y , (None , None ), axis = tan_dims [1 ]) for Y in snapping_lines_y
1889
+ ]
1890
+ snapping_lines_x_3d = [
1891
+ Box .unpop_axis (X , (None , None ), axis = tan_dims [0 ]) for X in snapping_lines_x
1892
+ ]
1823
1893
1824
1894
return snapping_lines_x_3d + snapping_lines_y_3d , detected_gap_width
1825
1895
@@ -2405,7 +2475,7 @@ def _make_grid_one_iteration(
2405
2475
If `None`, recomputes internal snapping points.
2406
2476
dl_min_from_gaps : pd.PositiveFloat
2407
2477
Minimal grid size computed based on autodetected gaps.
2408
-
2478
+
2409
2479
2410
2480
Returns
2411
2481
-------
0 commit comments