Skip to content

Commit

Permalink
Add fixed radius rounding method force_rounded_corners
Browse files Browse the repository at this point in the history
Modify circular_capacitor to use the function

Add tests for `force_rounded_corners`
  • Loading branch information
jukkarabina committed Dec 20, 2024
1 parent 3e4eca3 commit fcea288
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import math
from kqcircuits.pya_resolver import pya
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
from kqcircuits.util.geometry_helper import circle_polygon, arc_points
from kqcircuits.util.geometry_helper import circle_polygon, arc_points, force_rounded_corners
from kqcircuits.elements.element import Element
from kqcircuits.elements.finger_capacitor_square import FingerCapacitorSquare, eval_a2, eval_b2

Expand Down Expand Up @@ -91,7 +91,7 @@ def build(self):
]
).to_itype(self.layout.dbu)
)
capacitor_neg.round_corners(5 / self.layout.dbu, 5 / self.layout.dbu, self.n // 2)
capacitor_neg = force_rounded_corners(capacitor_neg, 5 / self.layout.dbu, 5 / self.layout.dbu, self.n // 2)
self._add_waveguides(capacitor_neg, x_end, y_left, y_right)

# define the capacitor in the ground
Expand Down
61 changes: 61 additions & 0 deletions klayout_package/python/kqcircuits/util/geometry_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,3 +561,64 @@ def round_dpath_width(dpath: pya.DPath, dbu: float, precision: int = 2) -> pya.D
ipath = dpath.to_itype(dbu)
ipath.width -= ipath.width % precision
return ipath.to_dtype(dbu)


def force_rounded_corners(region: pya.Region, r_inner: float, r_outer: float, n: int) -> pya.Region:
"""Returns region with rounded corners by trying to force radius as given by r_inner and r_outer.
This function is useful when corner rounding is wanted next to curved segment. The point of curved segment that is
closest to the corner limits the radius produced by the klayout round_corners method. This function solves this
problem by removing the points next to the corner that prevent the full rounding radius from taking effect in the
round_corners method.
Please note that this function can't guarantee full radius in cases, where two corners are close to each other.
For example, if two 90 degree angles are closer than 2 * r distance apart, then the rounding radius is decreased.
Args:
region: Region whose corners need to be rounded
r_inner: Inner corner radius (in database units)
r_outer: Outer corner radius (in database units)
n: The number of points per circle
Returns:
Region with rounded corners
"""

corner_max_cos = np.cos(3 * np.pi / n) # consider point as corner if cos is below this

def process_points(pts: list[pya.Point]):
i0 = 0
while i0 < len(pts):
if len(pts) < 3:
return []
i1, i2, i3 = (i0 + 1) % len(pts), (i0 + 2) % len(pts), (i0 + 3) % len(pts)
p0, p1, p2, p3 = pts[i0 % len(pts)], pts[i1], pts[i2], pts[i3]
v0, v1, v2 = p1 - p0, p2 - p1, p3 - p2
l0, l1, l2 = v0.length(), v1.length(), v2.length()
cos0, cos1 = v0.sprod(v1) / (l0 * l1), v1.sprod(v2) / (l1 * l2)
if cos0 > corner_max_cos or cos1 > corner_max_cos: # do nothing between two corners
r0, r1 = r_inner if v0.vprod(v1) > 0 else r_outer, r_inner if v1.vprod(v2) > 0 else r_outer
cut0, cut1 = r0 * np.sqrt((1 - cos0) / (1 + cos0)), r1 * np.sqrt((1 - cos1) / (1 + cos1)) # r*tan(a/2)
if cut0 + cut1 > l1:
div, x0, x1 = v0.vprod(v2), v2.vprod(p0 - p3), v0.vprod(p0 - p3)
if x1 * div < 0 < x0 * div:
p_cross = p0 + x0 / div * v0
if p_cross not in (p0, p3):
pts[i1] = p_cross
pts = [p for i, p in enumerate(pts) if i != i2]
i0 -= 1 + int(i2 < i0)
continue
pts = [p for i, p in enumerate(pts) if i not in (i1, i2)]
i0 -= 1 + int(i1 < i0) + int(i2 < i0)
continue
i0 += 1
return pts

# Create new region and insert rounded shapes into it
result = pya.Region()
for polygon in region.each_merged():
poly = pya.Polygon(process_points(list(polygon.each_point_hull())))
for hole in range(polygon.holes()):
poly.insert_hole(process_points(list(polygon.each_point_hole(hole))))
result.insert(poly.round_corners(r_inner, r_outer, n))
return result
51 changes: 51 additions & 0 deletions tests/util/geometry_helper/test_force_rounded_corners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This code is part of KQCircuits
# Copyright (C) 2024 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).

from kqcircuits.pya_resolver import pya
from kqcircuits.util.geometry_helper import force_rounded_corners


def test_conserve_narrow_rectangle():
w, h, r, n = 1000, 10000, 5000, 100
region = pya.Region(pya.Box(-w, -h, w, h))
assert abs(force_rounded_corners(region, r, 0, n).area() - region.area()) < 100 # inner rounding
assert abs(force_rounded_corners(region, 0, r, n).area() - 39141292) < 100 # outer rounding


def test_half_sphere():
w, r, n = 20000, 5000, 100
region = pya.Region(pya.Box(-w, -w, w, w)).rounded_corners(w, w, n) - pya.Region(pya.Box(0, -w, w, w))
assert abs(force_rounded_corners(region, r, 0, n).area() - region.area()) < 100 # inner rounding
assert abs(force_rounded_corners(region, 0, r, n).area() - 611108787) < 100 # outer rounding


def test_half_sphere_hole():
d, w, r, n = 30000, 20000, 5000, 100
hole = pya.Region(pya.Box(-w, -w, w, w)).rounded_corners(w, w, n) - pya.Region(pya.Box(0, -w, w, w))
region = pya.Region(pya.Box(-d, -d, d, d)) - hole
assert abs(force_rounded_corners(region, r, 0, n).area() - 2988891213) < 100 # inner rounding
assert abs(force_rounded_corners(region, 0, r, n).area() - 2950038234) < 100 # outer rounding


def test_boat_shape():
w, d, r, n = 20000, 10000, 5000, 100
region = pya.Region(pya.Box(-w - d, -w, w - d, w)).rounded_corners(w, w, n) & pya.Region(
pya.Box(-w + d, -w, w + d, w)
).rounded_corners(w, w, n)
assert abs(force_rounded_corners(region, r, 0, n).area() - region.area()) < 100 # inner rounding
assert abs(force_rounded_corners(region, 0, r, n).area() - 486053824) < 100 # outer rounding

0 comments on commit fcea288

Please sign in to comment.