import numpy as np
from qiskit_metal import Dict, draw
from qiskit_metal.qlibrary.core import QComponent
from shapely import set_precision
from shapely.affinity import rotate, translate
from shapely.errors import GEOSException
from shapely.geometry import LineString, Polygon
[docs]
class GeneralizedCapNInterdigital(QComponent):
@staticmethod
def _clean_geometry(geom, precision=1e-6):
"""Repair potentially invalid polygons before/after precision snapping."""
if geom is None:
return geom
# First pass: resolve obvious self-intersections.
geom = geom.buffer(0)
try:
geom = set_precision(geom, precision)
except GEOSException:
# Fallback path for borderline-topology cases that fail on snap.
geom = geom.buffer(0)
geom = set_precision(geom, precision)
return geom.buffer(0)
default_options = Dict(
north_cpw_length="20um",
north_cpw_width="10um",
north_cpw_gap="6um",
north_cpw_radius="0um",
north_cpw_etch_radius="0um",
north_cpw_xpos_offset="0um",
north_cpw_maintain_gap=True,
#
south_cpw_length="20um",
south_cpw_width="10um",
south_cpw_gap="6um",
south_cpw_radius="0um",
south_cpw_etch_radius="0um",
south_cpw_xpos_offset="0um",
south_cpw_maintain_gap=True,
#
north_spine_width="10um",
north_west_spine_radius="0um",
north_east_spine_radius="0um",
north_west_spine_etch_radius="0um",
north_east_spine_etch_radius="0um",
#
south_spine_width="10um",
south_west_spine_radius="0um",
south_east_spine_radius="0um",
south_west_spine_etch_radius="0um",
south_east_spine_etch_radius="0um",
#
north_gap_ground="6um",
south_gap_ground="6um",
east_gap_ground="6um",
west_gap_ground="6um",
#
finger_length="20um",
finger_width="10um",
finger_gap_north_south="6um",
finger_gap_east_west="6um",
finger_radius="0um",
finger_etch_radius="0um",
finger_edge_gap_radius="0um",
finger_count="5",
)
[docs]
def make(self):
# self._install_hfss_scalar_safe_polyline_patch()
p = self.p
# north body is rotated 180 degrees, so for building it, the x-directions need to be flipped
p.north_cpw_xpos_offset = -p.north_cpw_xpos_offset
self._initialize_calculated_variables()
num_fingers = int(p.finger_count)
is_finger_count_even = num_fingers % 2 == 0
if is_finger_count_even:
finger_count_south = finger_count_north = num_fingers // 2
else:
finger_count_south = num_fingers // 2 + 1
finger_count_north = num_fingers // 2
assert p.finger_length >= 0.0, "finger_length must be greater than or equal to 0"
if p.finger_length > 0.0:
assert p.finger_count > 0, "finger_count must be greater than 0 if finger_length is greater than 0"
assert p.finger_radius <= p.finger_width / 2, (
f"finger_radius must be <= half the finger_width (= {p.finger_width / 2 * 1000} um)"
)
assert (p.finger_length - (p.finger_radius + p.finger_etch_radius)) >= -1e-6, (
"Must have finger_length >= finger_radius + finger_etch_radius in order to render"
)
assert (p.north_cpw_length - p.north_cpw_radius) >= 0.0, (
"north_cpw_length must be larger than or equal to north_cpw_radius in order to render"
)
assert (p.south_cpw_length - p.south_cpw_radius) >= 0.0, (
"south_cpw_length must be larger than or equal to south_cpw_radius in order to render"
)
y_translation_offset = -self.cap_height - p.north_cpw_length
cap_south_body = self._make_cap_body(is_body_south=True, finger_count=finger_count_south)
cap_south_body = translate(cap_south_body, 0, y_translation_offset)
cap_south_body = self._clean_geometry(cap_south_body)
cap_north_body = self._make_cap_body(
is_body_south=False, finger_count=finger_count_north, gap_first=False if is_finger_count_even else True
)
cap_north_body = rotate(cap_north_body, 180, origin=(0, 0))
cap_north_body = translate(cap_north_body, 0, self.cap_height + y_translation_offset)
cap_north_body = self._clean_geometry(cap_north_body)
# switch it back for etch
p.north_cpw_xpos_offset = -p.north_cpw_xpos_offset
self._initialize_calculated_variables()
cap_etch = self._make_cap_etch()
cap_etch = translate(
cap_etch,
self.etch_width / 2 - p.west_gap_ground - self.cap_width / 2,
-p.south_gap_ground + y_translation_offset,
)
cap_etch = self._clean_geometry(cap_etch)
c_items = [cap_north_body, cap_south_body, cap_etch]
c_items = draw.rotate(c_items, p.orientation, origin=(0, 0))
c_items = draw.translate(c_items, p.pos_x, p.pos_y)
[cap_north_body, cap_south_body, cap_etch] = c_items
self.add_qgeometry("poly", {"cap_etch": cap_etch}, layer=p.layer, subtract=True)
self.add_qgeometry("poly", {"cap_north_body": cap_north_body}, layer=p.layer)
self.add_qgeometry("poly", {"cap_south_body": cap_south_body}, layer=p.layer)
south_cpw_edge_coords = np.array(
[
(self.south_cpw_left_edge_x, -p.south_cpw_length + y_translation_offset),
(self.south_cpw_right_edge_x, -p.south_cpw_length + y_translation_offset),
]
)
north_cpw_edge_coords = np.array(
[
(self.north_cpw_left_edge_x, self.cap_height + p.north_cpw_length + y_translation_offset),
(self.north_cpw_right_edge_x, self.cap_height + p.north_cpw_length + y_translation_offset),
]
)
cpw_edge_lines = [
LineString(south_cpw_edge_coords),
LineString(north_cpw_edge_coords),
]
cpw_edge_lines = draw.rotate(cpw_edge_lines, p.orientation, origin=(0, 0))
cpw_edge_lines = draw.translate(cpw_edge_lines, p.pos_x, p.pos_y)
south_cpw_edge_coords = np.array(cpw_edge_lines[0].coords)
north_cpw_edge_coords = np.array(cpw_edge_lines[1].coords)
self.add_pin(
"south_end",
points=south_cpw_edge_coords[::-1],
width=p.south_cpw_width,
gap=p.south_cpw_gap,
)
self.add_pin(
"north_end",
points=north_cpw_edge_coords,
width=p.north_cpw_width,
gap=p.north_cpw_gap,
)
def _initialize_calculated_variables(self):
p = self.p
self.cap_width = p.finger_count * p.finger_width + (p.finger_count - 1) * p.finger_gap_east_west
self.cap_height = p.south_spine_width + p.finger_length + p.finger_gap_north_south + p.north_spine_width
self.south_cpw_left_edge_x = p.south_cpw_xpos_offset - p.south_cpw_width / 2
self.south_cpw_right_edge_x = p.south_cpw_xpos_offset + p.south_cpw_width / 2
self.north_cpw_left_edge_x = p.north_cpw_xpos_offset - p.north_cpw_width / 2
self.north_cpw_right_edge_x = p.north_cpw_xpos_offset + p.north_cpw_width / 2
self.etch_width = self.cap_width + p.west_gap_ground + p.east_gap_ground
self.etch_height = self.cap_height + p.north_gap_ground + p.south_gap_ground
self.etch_left_edge_x = -self.cap_width / 2 - p.west_gap_ground
self.etch_right_edge_x = self.cap_width / 2 + p.east_gap_ground
self.finger_gap_width = p.finger_width + 2 * p.finger_gap_east_west
def _get_rounded_corner_coords(
self, r, quadrant, corner_coords, clockwise=False, start_angle=None, end_angle=None, num_points=16
):
if r < 1e-6:
return [corner_coords]
x = corner_coords[0]
y = corner_coords[1]
if quadrant == "ne":
angle_start = 0
angle_end = np.pi / 2
if clockwise:
angle_start, angle_end = angle_end, angle_start
x = x - r
y = y - r
elif quadrant == "nw":
angle_start = np.pi / 2
angle_end = np.pi
if clockwise:
angle_start, angle_end = angle_end, angle_start
x = x + r
y = y - r
elif quadrant == "sw":
angle_start = -np.pi
angle_end = -np.pi / 2
if clockwise:
angle_start, angle_end = angle_end, angle_start
x = x + r
y = y + r
elif quadrant == "se":
angle_start = -np.pi / 2
angle_end = 0
if clockwise:
angle_start, angle_end = angle_end, angle_start
x = x - r
y = y + r
if start_angle is None:
start_angle = angle_start
if end_angle is None:
end_angle = angle_end
theta = np.linspace(start_angle, end_angle, num_points)
x_vals = r * np.cos(theta) + x
y_vals = r * np.sin(theta) + y
return [(float(xv), float(yv)) for xv, yv in zip(x_vals, y_vals)]
def _get_partial_corner_info(self, cpw_center, spine_center, cpw_radius, spine_radius, y_int_val=0.0):
x1, y1 = cpw_center
x2, y2 = spine_center
R1 = cpw_radius
R2 = spine_radius
dx, dy = x2 - x1, y2 - y1
d = np.sqrt(dx**2 + dy**2)
c = (R1 + R2) / d
s = np.sqrt(d**2 - (R1 + R2) ** 2) / d
nx1 = c * (dx / d) - s * (dy / d)
ny1 = c * (dy / d) + s * (dx / d)
nx2 = c * (dx / d) + s * (dy / d)
ny2 = c * (dy / d) - s * (dx / d)
pt1x_circ1, pt1y_circ1 = x1 + R1 * nx1, y1 + R1 * ny1
pt1x_circ2, pt1y_circ2 = x2 - R2 * nx1, y2 - R2 * ny1
pt2x_circ1, pt2y_circ1 = x1 + R1 * nx2, y1 + R1 * ny2
pt2x_circ2, pt2y_circ2 = x2 - R2 * nx2, y2 - R2 * ny2
if abs(pt1y_circ1 - y_int_val) < 1e-6:
pt_circ1 = np.array([pt2x_circ1, pt2y_circ1])
pt_circ2 = np.array([pt2x_circ2, pt2y_circ2])
else:
pt_circ1 = np.array([pt1x_circ1, pt1y_circ1])
pt_circ2 = np.array([pt1x_circ2, pt1y_circ2])
numerator = (pt_circ1[0] * pt_circ2[1] - pt_circ2[0] * pt_circ1[1]) + y_int_val * (pt_circ2[0] - pt_circ1[0])
denominator = pt_circ2[1] - pt_circ1[1]
int_point = np.array([numerator / denominator, y_int_val])
xc1, yc1 = cpw_center
xt1, yt1 = pt_circ1
dx = xt1 - xc1
dy = yt1 - yc1
angle1 = np.arctan2(dy, dx)
xc2, yc2 = spine_center
xt2, yt2 = pt_circ2
dx = xt2 - xc2
dy = yt2 - yc2
angle2 = np.arctan2(dy, dx)
info_dict = {
"int_point": int_point.tolist(),
"pt_circ1": pt_circ1.tolist(),
"pt_circ2": pt_circ2.tolist(),
"cpw_angle": angle1,
"spine_angle": angle2,
}
return info_dict
def _get_finger_coords(self, start_coord, exclude_left_ledge=False, exclude_right_ledge=False):
p = self.p
length = p.finger_length
width = p.finger_width
rf = p.finger_radius
re = p.finger_etch_radius
gap_width = self.finger_gap_width
start_x = start_coord[0]
start_y = start_coord[1]
coords = []
x = start_x
y = start_y
if exclude_left_ledge:
y = y + length
coords.extend(self._get_rounded_corner_coords(rf, "nw", (x, y), clockwise=True))
else:
x = x + gap_width / 2
coords.extend(self._get_rounded_corner_coords(re, "se", (x, y)))
y = y + length
coords.extend(self._get_rounded_corner_coords(rf, "nw", (x, y), clockwise=True))
x = x + width
coords.extend(self._get_rounded_corner_coords(rf, "ne", (x, y), clockwise=True))
if not exclude_right_ledge:
y = y - length
coords.extend(self._get_rounded_corner_coords(re, "sw", (x, y)))
return coords, x, y
def _spine_ledge_geometry(self, side: str, y: float) -> dict:
"""Circle/fillet centers for the spine ledge at the outer finger gap."""
p = self.p
if side == "left":
circ_center_x = -self.cap_width / 2 - p.finger_gap_east_west + p.finger_etch_radius
fillet_center_x = -self.cap_width / 2 + p.finger_edge_gap_radius
else:
circ_center_x = self.cap_width / 2 + p.finger_gap_east_west - p.finger_etch_radius
fillet_center_x = self.cap_width / 2 - p.finger_edge_gap_radius
circ_center_y = y + p.finger_etch_radius
radicand = (p.finger_etch_radius + p.finger_edge_gap_radius) ** 2 - (fillet_center_x - circ_center_x) ** 2
fillet_center_y = None
fillet_upper_y = None
circ_lower_y = circ_center_y - p.finger_etch_radius
if radicand >= 0:
fillet_center_y = circ_center_y - np.sqrt(radicand)
fillet_upper_y = fillet_center_y + p.finger_edge_gap_radius
return {
"circ_center_x": circ_center_x,
"circ_center_y": circ_center_y,
"fillet_center_x": fillet_center_x,
"fillet_center_y": fillet_center_y,
"fillet_upper_y": fillet_upper_y,
"circ_lower_y": circ_lower_y,
"radicand": radicand,
}
def _spine_ledge_use_simplified(self, side: str, y: float) -> bool:
"""Use simplified spine-ledge corners when fillet and etch circles overlap.
Simplified path when the fillet top is at or above the etch-circle bottom
(within 1e-6 mm). Otherwise use tangent fillet + etch-arc construction.
"""
geom = self._spine_ledge_geometry(side, y)
if geom["radicand"] < 0:
return True
return (geom["circ_lower_y"] - geom["fillet_upper_y"]) <= 1e-6
def _append_spine_ledge_left_simplified(self, cap_body_coords: list, y: float) -> None:
cap_body_coords.extend(
self._get_rounded_corner_coords(
self.p.finger_edge_gap_radius,
"nw",
(-self.cap_width / 2, y),
start_angle=np.pi,
end_angle=np.pi / 2,
clockwise=True,
)
)
def _append_spine_ledge_right_simplified(self, cap_body_coords: list, y: float) -> None:
cap_body_coords.extend(
self._get_rounded_corner_coords(
self.p.finger_edge_gap_radius,
"ne",
(self.cap_width / 2, y),
start_angle=np.pi / 2,
end_angle=0,
clockwise=True,
)
)
def _append_spine_ledge_left_tangent(self, cap_body_coords: list, y: float) -> None:
p = self.p
geom = self._spine_ledge_geometry("left", y)
circ_center_x = geom["circ_center_x"]
circ_center_y = geom["circ_center_y"]
fillet_center_x = geom["fillet_center_x"]
fillet_center_y = geom["fillet_center_y"]
fillet_angle_end = np.arctan2(circ_center_y - fillet_center_y, circ_center_x - fillet_center_x)
theta_start = np.arctan2(fillet_center_y - circ_center_y, fillet_center_x - circ_center_x)
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_edge_gap_radius,
"nw",
(-self.cap_width / 2, fillet_center_y + p.finger_edge_gap_radius),
start_angle=np.pi,
end_angle=fillet_angle_end,
)
)
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_etch_radius,
"sw",
(circ_center_x - p.finger_etch_radius, y),
start_angle=theta_start,
end_angle=-np.pi / 2,
)
)
def _append_spine_ledge_right_tangent(self, cap_body_coords: list, y: float) -> None:
p = self.p
geom = self._spine_ledge_geometry("right", y)
circ_center_x = geom["circ_center_x"]
circ_center_y = geom["circ_center_y"]
fillet_center_x = geom["fillet_center_x"]
fillet_center_y = geom["fillet_center_y"]
fillet_angle_start = np.arctan2(circ_center_y - fillet_center_y, circ_center_x - fillet_center_x)
theta_end = np.arctan2(fillet_center_y - circ_center_y, fillet_center_x - circ_center_x)
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_etch_radius,
"se",
(circ_center_x + p.finger_etch_radius, y),
start_angle=-np.pi / 2,
end_angle=theta_end,
)
)
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_edge_gap_radius,
"ne",
(self.cap_width / 2, fillet_center_y + p.finger_edge_gap_radius),
start_angle=fillet_angle_start,
end_angle=0,
)
)
def _make_cap_body(self, is_body_south, finger_count, gap_first=False):
p = self.p
if is_body_south:
spine_width = p.south_spine_width
west_spine_radius = p.south_west_spine_radius
east_spine_radius = p.south_east_spine_radius
cpw_width = p.south_cpw_width
cpw_length = p.south_cpw_length
cpw_radius = p.south_cpw_radius
cpw_xpos = p.south_cpw_xpos_offset
cpw_left_edge_x = self.south_cpw_left_edge_x
cpw_right_edge_x = self.south_cpw_right_edge_x
dir_text = "south_"
else:
spine_width = p.north_spine_width
west_spine_radius = (
p.north_east_spine_radius
) # Direction flipped since north body will be rotated 180 degrees
east_spine_radius = p.north_west_spine_radius
cpw_width = p.north_cpw_width
cpw_length = p.north_cpw_length
cpw_radius = p.north_cpw_radius
cpw_xpos = p.north_cpw_xpos_offset
cpw_left_edge_x = self.north_cpw_left_edge_x
cpw_right_edge_x = self.north_cpw_right_edge_x
dir_text = "north_"
assert cpw_length >= cpw_radius, (
f"{dir_text}cpw_length must be larger than or equal to {dir_text}cpw_radius in order to render"
)
cap_body_coords = []
# (0,0) is south spine bottom edge horizontally center
# LEFT: CPW AND SPINE
if cpw_length > 0.0:
x = cpw_xpos
y = -cpw_length
# cpw left corner
x = x - cpw_width / 2
cap_body_coords.append((x, y))
# cpw spine left connection curve
left_edge_x = -self.cap_width / 2
y = 0
wr_partial_valid = (left_edge_x < cpw_left_edge_x) and (
(cpw_left_edge_x - cpw_radius) < (left_edge_x + west_spine_radius)
)
wl_partial_valid = (left_edge_x > cpw_left_edge_x) and (
(cpw_left_edge_x + cpw_radius) > (left_edge_x - west_spine_radius)
)
w_equality_valid = np.abs(left_edge_x - cpw_left_edge_x) < 1e-6
if not any([wr_partial_valid, wl_partial_valid, w_equality_valid]):
# full pi/2 rotations for both curves
if left_edge_x < cpw_left_edge_x: # cpw fully connected to spine
cap_body_coords.extend(self._get_rounded_corner_coords(cpw_radius, "ne", (x, y)))
x = left_edge_x
cap_body_coords.extend(
self._get_rounded_corner_coords(west_spine_radius, "sw", (x, y), clockwise=True)
)
else: # cpw sticking out to the left of the cap body
cap_body_coords.extend(self._get_rounded_corner_coords(cpw_radius, "nw", (x, y), clockwise=True))
x = left_edge_x
cap_body_coords.extend(self._get_rounded_corner_coords(west_spine_radius, "se", (x, y)))
else:
if w_equality_valid: # straight connection from cpw to spine
cap_body_coords.append((x, y))
elif wr_partial_valid: # ne cpw curve -> tilted line -> sw spine curve
spine_circ_center = np.array([left_edge_x + west_spine_radius, west_spine_radius])
cpw_circ_center = np.array([cpw_left_edge_x - cpw_radius, -cpw_radius])
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, cpw_radius, west_spine_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
cap_body_coords.extend(
self._get_rounded_corner_coords(
cpw_radius, "ne", (x, y), start_angle=0, end_angle=cpw_end_angle
)
)
cap_body_coords.append(partial_info["int_point"])
x = left_edge_x
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
cap_body_coords.extend(
self._get_rounded_corner_coords(
west_spine_radius,
"sw",
(x, y),
clockwise=True,
start_angle=spine_start_angle,
end_angle=-np.pi,
)
)
elif wl_partial_valid: # nw cpw curve -> tilted line -> se spine curve
assert left_edge_x < cpw_right_edge_x, (
f"{dir_text}cpw must be connected to capacitor (exceeds {left_edge_x} mm). Reduce offset magnitude"
)
spine_circ_center = np.array([left_edge_x - west_spine_radius, west_spine_radius])
cpw_circ_center = np.array([cpw_left_edge_x + cpw_radius, -cpw_radius])
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, cpw_radius, west_spine_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
cap_body_coords.extend(
self._get_rounded_corner_coords(
cpw_radius, "nw", (x, y), start_angle=np.pi, end_angle=cpw_end_angle
)
)
cap_body_coords.append(partial_info["int_point"])
x = -self.cap_width / 2
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
cap_body_coords.extend(
self._get_rounded_corner_coords(
west_spine_radius, "se", (x, y), clockwise=True, start_angle=spine_start_angle, end_angle=0
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
else:
# no cpw, just spine
x = -self.cap_width / 2
y = 0
cap_body_coords.extend(self._get_rounded_corner_coords(west_spine_radius, "sw", (x, y), clockwise=True))
# FINGERS
y = y + spine_width
if p.finger_length > 0.0:
if gap_first:
if self._spine_ledge_use_simplified("left", y):
self._append_spine_ledge_left_simplified(cap_body_coords, y)
else:
self._append_spine_ledge_left_tangent(cap_body_coords, y)
x = x + p.finger_width + p.finger_gap_east_west - self.finger_gap_width / 2
for i in range(finger_count):
coords, x, y = self._get_finger_coords(
[x, y],
exclude_left_ledge=True if i == 0 and not gap_first else False,
exclude_right_ledge=True
if i == finger_count - 1 and not gap_first and p.finger_count % 2 == 1
else False,
)
if i != finger_count - 1:
x = x + self.finger_gap_width / 2
cap_body_coords.extend(coords)
if p.finger_count % 2 == 0 or gap_first:
if self._spine_ledge_use_simplified("right", y):
self._append_spine_ledge_right_simplified(cap_body_coords, y)
else:
self._append_spine_ledge_right_tangent(cap_body_coords, y)
else:
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_edge_gap_radius,
"nw",
(x, y),
clockwise=True,
)
)
x = x + self.cap_width
cap_body_coords.extend(
self._get_rounded_corner_coords(
p.finger_edge_gap_radius,
"ne",
(x, y),
clockwise=True,
)
)
# RIGHT: CPW AND SPINE
right_cpw_spine_coords = []
if cpw_length > 0.0:
x = cpw_xpos
y = -cpw_length
# cpw left corner
x = x + cpw_width / 2
right_cpw_spine_coords.append((x, y))
# cpw spine left connection curve
right_edge_x = self.cap_width / 2
y = 0
er_partial_valid = (right_edge_x < cpw_right_edge_x) and (
(cpw_right_edge_x - cpw_radius) < (right_edge_x + east_spine_radius)
)
el_partial_valid = (right_edge_x > cpw_right_edge_x) and (
(cpw_right_edge_x + cpw_radius) > (right_edge_x - east_spine_radius)
)
e_equality_valid = np.abs(right_edge_x - cpw_right_edge_x) < 1e-6
if not any([er_partial_valid, el_partial_valid, e_equality_valid]):
# full pi/2 rotations for both curves
if right_edge_x > cpw_right_edge_x: # cpw fully connected to spine
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(cpw_radius, "nw", (x, y), clockwise=True)
)
x = right_edge_x
right_cpw_spine_coords.extend(self._get_rounded_corner_coords(east_spine_radius, "se", (x, y)))
else: # cpw sticking out to the left of the cap body
right_cpw_spine_coords.extend(self._get_rounded_corner_coords(cpw_radius, "ne", (x, y)))
x = right_edge_x
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(east_spine_radius, "sw", (x, y), clockwise=True)
)
else:
if e_equality_valid: # straight connection from cpw to spine
right_cpw_spine_coords.append((x, y))
elif el_partial_valid: # nw cpw curve -> tilted line -> se spine curve
spine_circ_center = np.array([right_edge_x - east_spine_radius, east_spine_radius])
cpw_circ_center = np.array([cpw_right_edge_x + cpw_radius, -cpw_radius])
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, cpw_radius, east_spine_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(
cpw_radius, "nw", (x, y), start_angle=np.pi, end_angle=cpw_end_angle, clockwise=True
)
)
right_cpw_spine_coords.append(partial_info["int_point"])
x = right_edge_x
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(
east_spine_radius, "se", (x, y), start_angle=spine_start_angle, end_angle=0
)
)
elif er_partial_valid: # ne cpw curve -> tilted line -> sw spine curve
assert right_edge_x > cpw_left_edge_x, (
f"{dir_text}cpw must be connected to capacitor (exceeds {right_edge_x} mm). Reduce offset magnitude"
)
spine_circ_center = np.array([right_edge_x + east_spine_radius, east_spine_radius])
cpw_circ_center = np.array([cpw_right_edge_x - cpw_radius, -cpw_radius])
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, cpw_radius, east_spine_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(
cpw_radius, "ne", (x, y), start_angle=0, end_angle=cpw_end_angle
)
)
right_cpw_spine_coords.append(partial_info["int_point"])
x = right_edge_x
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
right_cpw_spine_coords.extend(
self._get_rounded_corner_coords(
east_spine_radius,
"sw",
(x, y),
clockwise=True,
start_angle=spine_start_angle,
end_angle=-np.pi,
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
else:
# no cpw, just spine
x = self.cap_width / 2
y = 0
right_cpw_spine_coords.extend(self._get_rounded_corner_coords(cpw_radius, "nw", (x, y)))
cap_body_coords.extend(right_cpw_spine_coords[::-1])
cap_body = Polygon(cap_body_coords)
return cap_body
def _make_cap_etch(self):
p = self.p
cap_etch_coords = []
south_cpw_etch_left_edge_x = self.south_cpw_left_edge_x - p.south_cpw_gap
south_cpw_etch_right_edge_x = self.south_cpw_right_edge_x + p.south_cpw_gap
north_cpw_etch_left_edge_x = self.north_cpw_left_edge_x - p.north_cpw_gap
north_cpw_etch_right_edge_x = self.north_cpw_right_edge_x + p.north_cpw_gap
south_cpw_etch_length = p.south_cpw_length - p.south_gap_ground
north_cpw_etch_length = p.north_cpw_length - p.north_gap_ground
# SOUTH WEST CORNER
if p.south_cpw_length > p.south_gap_ground:
x = south_cpw_etch_left_edge_x
y = -south_cpw_etch_length
cap_etch_coords.append((x, y))
y = 0
swr_partial_valid = (self.etch_left_edge_x < south_cpw_etch_left_edge_x) and (
(south_cpw_etch_left_edge_x - p.south_cpw_etch_radius)
< (self.etch_left_edge_x + p.south_west_spine_etch_radius)
)
swl_partial_valid = (self.etch_left_edge_x > south_cpw_etch_left_edge_x) and (
(south_cpw_etch_left_edge_x + p.south_cpw_etch_radius)
> (self.etch_left_edge_x - p.south_west_spine_etch_radius)
)
sw_equality_valid = np.abs(self.etch_left_edge_x - south_cpw_etch_left_edge_x) < 1e-6
if not any([swr_partial_valid, swl_partial_valid, sw_equality_valid]):
# fill pi/2 rotations for both curves
if self.etch_left_edge_x < south_cpw_etch_left_edge_x: # cpw fully connected to spine
cap_etch_coords.extend(self._get_rounded_corner_coords(p.south_cpw_etch_radius, "ne", (x, y)))
x = self.etch_left_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_west_spine_etch_radius, "sw", (x, y), clockwise=True)
)
else: # cpw sticking out to the left of the cap body
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_cpw_etch_radius, "nw", (x, y), clockwise=True)
)
x = self.etch_left_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_west_spine_etch_radius, "se", (x, y))
)
else:
if sw_equality_valid: # straight connection from cpw to spine
cap_etch_coords.append((x, y))
elif swr_partial_valid: # ne cpw curve -> tilted line -> sw spine curve
spine_circ_center = np.array(
[self.etch_left_edge_x + p.south_west_spine_etch_radius, p.south_west_spine_etch_radius]
)
cpw_circ_center = np.array(
[south_cpw_etch_left_edge_x - p.south_cpw_etch_radius, -p.south_cpw_etch_radius]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, p.south_cpw_etch_radius, p.south_west_spine_etch_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.south_cpw_etch_radius, "ne", (x, y), start_angle=0, end_angle=cpw_end_angle
)
)
cap_etch_coords.append(partial_info["int_point"])
x = self.etch_left_edge_x
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.south_west_spine_etch_radius,
"sw",
(x, y),
clockwise=True,
start_angle=spine_start_angle,
end_angle=-np.pi,
)
)
elif swl_partial_valid: # nw cpw curve -> tilted line -> se spine curve
assert self.etch_left_edge_x < south_cpw_etch_right_edge_x, (
f"south_cpw must be connected to capacitor (exceeds {self.etch_left_edge_x} mm). Reduce offset magnitude"
)
spine_circ_center = np.array(
[self.etch_left_edge_x - p.south_west_spine_etch_radius, p.south_west_spine_etch_radius]
)
cpw_circ_center = np.array(
[south_cpw_etch_left_edge_x + p.south_cpw_etch_radius, -p.south_cpw_etch_radius]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, p.south_cpw_etch_radius, p.south_west_spine_etch_radius
)
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.south_cpw_etch_radius, "nw", (x, y), start_angle=np.pi, end_angle=cpw_end_angle
)
)
cap_etch_coords.append(partial_info["int_point"])
x = -self.cap_width / 2
spine_start_angle = partial_info["spine_angle"]
assert spine_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.south_west_spine_etch_radius,
"se",
(x, y),
clockwise=True,
start_angle=spine_start_angle,
end_angle=0,
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
elif p.south_cpw_length > 0.0 and (p.south_cpw_length < p.south_gap_ground):
south_cpw_etch_left_edge_x = self.south_cpw_left_edge_x - p.south_cpw_gap
if south_cpw_etch_left_edge_x < self.etch_width / 2:
raise NotImplementedError
elif p.south_cpw_length < 1e-6:
raise NotImplementedError
# NORTH WEST CORNER
if p.south_cpw_length > p.south_gap_ground:
x = self.etch_left_edge_x
y = self.etch_height
nwr_partial_valid = (self.etch_left_edge_x < north_cpw_etch_left_edge_x) and (
(north_cpw_etch_left_edge_x - p.north_cpw_etch_radius)
< (self.etch_left_edge_x + p.north_west_spine_etch_radius)
)
nwl_partial_valid = (self.etch_left_edge_x > north_cpw_etch_left_edge_x) and (
(north_cpw_etch_left_edge_x + p.north_cpw_etch_radius)
> (self.etch_left_edge_x - p.north_west_spine_etch_radius)
)
nw_equality_valid = np.abs(self.etch_left_edge_x - north_cpw_etch_left_edge_x) < 1e-6
if not any([nwr_partial_valid, nwl_partial_valid, nw_equality_valid]):
# fill pi/2 rotations for both curves
if self.etch_right_edge_x > south_cpw_etch_right_edge_x: # cpw fully connected to spine
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_west_spine_etch_radius, "nw", (x, y), clockwise=True)
)
x = north_cpw_etch_left_edge_x
cap_etch_coords.extend(self._get_rounded_corner_coords(p.north_cpw_etch_radius, "se", (x, y)))
else: # cpw sticking out to the left of the cap body
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_west_spine_etch_radius, "ne", (x, y))
)
x = north_cpw_etch_left_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_cpw_etch_radius, "sw", (x, y), clockwise=True)
)
else:
if nw_equality_valid: # straight connection from cpw to spine
cap_etch_coords.append((x, y))
elif nwr_partial_valid: # nw spine curve -> tilted line -> se cpw curve
raise NotImplementedError
spine_circ_center = np.array(
[
self.etch_left_edge_x + p.north_west_spine_etch_radius,
self.etch_height - p.north_west_spine_etch_radius,
]
)
cpw_circ_center = np.array(
[
north_cpw_etch_left_edge_x - p.north_cpw_etch_radius,
self.etch_height + p.north_cpw_etch_radius,
]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center,
spine_circ_center,
p.north_cpw_etch_radius,
p.north_west_spine_etch_radius,
y_int_val=self.etch_height,
)
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_west_spine_etch_radius,
"nw",
(x, y),
clockwise=True,
start_angle=np.pi,
end_angle=spine_end_angle,
)
)
cap_etch_coords.append(partial_info["int_point"])
x = north_cpw_etch_left_edge_x
cpw_start_angle = partial_info["cpw_angle"]
assert cpw_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius, "se", (x, y), start_angle=cpw_start_angle, end_angle=0
)
)
elif nwl_partial_valid: # ne spine curve -> tilted line -> sw cpw curve
raise NotImplementedError
assert self.etch_left_edge_x < south_cpw_etch_right_edge_x, (
f"south_cpw must be connected to capacitor (exceeds {self.etch_left_edge_x} mm). Reduce offset magnitude"
)
y_int_val = self.etch_height - p.north_gap_ground
spine_circ_center = np.array(
[self.etch_left_edge_x - p.north_west_spine_etch_radius, y_int_val - p.north_west_spine_radius]
)
cpw_circ_center = np.array(
[north_cpw_etch_left_edge_x + p.north_cpw_etch_radius, y_int_val + p.north_cpw_radius]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center,
spine_circ_center,
p.north_cpw_etch_radius,
p.north_west_spine_etch_radius,
y_int_val=y_int_val,
)
y = y_int_val
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_west_spine_etch_radius, "ne", (x, y), start_angle=0, end_angle=spine_end_angle
)
)
cap_etch_coords.append(partial_info["int_point"])
x = north_cpw_etch_left_edge_x
cpw_start_angle = partial_info["cpw_angle"]
assert cpw_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius,
"sw",
(x, y),
start_angle=cpw_start_angle,
end_angle=-np.pi,
clockwise=True,
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
x = north_cpw_etch_left_edge_x
y = self.etch_height + north_cpw_etch_length
cap_etch_coords.append((x, y))
elif p.south_cpw_length > 0.0 and (p.south_cpw_length < p.south_gap_ground):
south_cpw_etch_left_edge_x = self.south_cpw_left_edge_x - p.south_cpw_gap
if south_cpw_etch_left_edge_x < self.etch_width / 2:
raise NotImplementedError
elif p.south_cpw_length < 1e-6:
raise NotImplementedError
# NORTH EAST CORNER
if p.north_cpw_length > p.north_gap_ground:
x = north_cpw_etch_right_edge_x
y = self.etch_height + north_cpw_etch_length
cap_etch_coords.append((x, y))
y = self.etch_height
ner_partial_valid = (self.etch_right_edge_x < north_cpw_etch_right_edge_x) and (
(north_cpw_etch_right_edge_x - p.north_cpw_etch_radius)
> (self.etch_left_edge_x + p.north_east_spine_etch_radius)
)
nel_partial_valid = (self.etch_right_edge_x > north_cpw_etch_right_edge_x) and (
(north_cpw_etch_right_edge_x + p.north_cpw_etch_radius)
< (self.etch_left_edge_x - p.north_east_spine_etch_radius)
)
ne_equality_valid = np.abs(self.etch_right_edge_x - north_cpw_etch_right_edge_x) < 1e-6
if not any([ner_partial_valid, nel_partial_valid, ne_equality_valid]):
# fill pi/2 rotations for both curves
if self.etch_right_edge_x > north_cpw_etch_right_edge_x: # cpw fully connected to spine
cap_etch_coords.extend(self._get_rounded_corner_coords(p.north_cpw_etch_radius, "sw", (x, y)))
x = self.etch_right_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_east_spine_etch_radius, "ne", (x, y), clockwise=True)
)
else: # cpw sticking out to the left of the cap body
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_cpw_etch_radius, "se", (x, y), clockwise=True)
)
x = self.etch_right_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.north_east_spine_etch_radius, "nw", (x, y))
)
else:
if ne_equality_valid: # straight connection from cpw to spine
cap_etch_coords.append((x, y))
elif nel_partial_valid: # sw cpw curve -> tilted line -> ne spine curve
raise NotImplementedError
spine_circ_center = np.array(
[
self.etch_right_edge_x - p.north_east_spine_etch_radius,
self.etch_height - p.north_east_spine_etch_radius,
]
)
cpw_circ_center = np.array(
[
north_cpw_etch_right_edge_x + p.north_cpw_etch_radius,
self.etch_height + p.north_cpw_etch_radius,
]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, p.north_cpw_etch_radius, p.north_east_spine_etch_radius
)
x = north_cpw_etch_right_edge_x
cpw_end_angle = partial_info["cpw_angle"]
assert cpw_end_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius, "se", (x, y), start_angle=cpw_start_angle, end_angle=0
)
)
cap_etch_coords.append(partial_info["int_point"])
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_east_spine_etch_radius,
"nw",
(x, y),
clockwise=True,
start_angle=np.pi,
end_angle=spine_end_angle,
)
)
elif ner_partial_valid: # ne spine curve -> tilted line -> sw cpw curve
raise NotImplementedError
assert self.etch_right_edge_x < south_cpw_etch_right_edge_x, (
f"south_cpw must be connected to capacitor (exceeds {self.etch_left_edge_x} mm). Reduce offset magnitude"
)
spine_circ_center = np.array(
[self.etch_right_edge_x - p.north_east_spine_etch_radius, p.north_east_spine_etch_radius]
)
cpw_circ_center = np.array(
[north_cpw_etch_right_edge_x + p.north_cpw_etch_radius, -p.north_cpw_etch_radius]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center, spine_circ_center, p.north_cpw_etch_radius, p.north_east_spine_etch_radius
)
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_east_spine_etch_radius,
"ne",
(x, y),
clockwise=True,
start_angle=0,
end_angle=spine_end_angle,
)
)
cap_etch_coords.append(partial_info["int_point"])
cpw_start_angle = partial_info["cpw_angle"]
assert cpw_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius, "sw", (x, y), start_angle=cpw_start_angle, end_angle=-np.pi
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
elif p.north_cpw_length > 0.0 and (p.north_cpw_length < p.north_gap_ground):
north_cpw_etch_left_edge_x = self.north_cpw_left_edge_x - p.north_cpw_gap
if north_cpw_etch_left_edge_x < self.etch_width / 2:
raise NotImplementedError
elif p.north_cpw_length < 1e-6:
raise NotImplementedError
# SOUTH EAST CORNER
if p.north_cpw_length > p.north_gap_ground:
x = self.etch_right_edge_x
y = 0
ser_partial_valid = (self.etch_right_edge_x < south_cpw_etch_right_edge_x) and (
(south_cpw_etch_right_edge_x - p.south_cpw_etch_radius)
< (self.etch_right_edge_x + p.south_east_spine_etch_radius)
)
sel_partial_valid = (self.etch_right_edge_x > south_cpw_etch_right_edge_x) and (
(south_cpw_etch_right_edge_x + p.south_cpw_etch_radius)
> (self.etch_right_edge_x - p.south_east_spine_etch_radius)
)
se_equality_valid = np.abs(self.etch_right_edge_x - south_cpw_etch_right_edge_x) < 1e-6
if not any([ser_partial_valid, sel_partial_valid, se_equality_valid]):
# fill pi/2 rotations for both curves
if self.etch_left_edge_x < north_cpw_etch_left_edge_x: # cpw fully connected to spine
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_east_spine_etch_radius, "se", (x, y), clockwise=True)
)
x = south_cpw_etch_right_edge_x
cap_etch_coords.extend(self._get_rounded_corner_coords(p.south_cpw_etch_radius, "nw", (x, y)))
else: # cpw sticking out to the left of the cap body
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_east_spine_etch_radius, "sw", (x, y))
)
x = south_cpw_etch_right_edge_x
cap_etch_coords.extend(
self._get_rounded_corner_coords(p.south_cpw_etch_radius, "ne", (x, y), clockwise=True)
)
else:
if se_equality_valid: # straight connection from cpw to spine
cap_etch_coords.append((x, y))
elif ser_partial_valid: # nw spine curve -> tilted line -> se cpw curve
raise NotImplementedError
spine_circ_center = np.array(
[
self.etch_left_edge_x + p.north_west_spine_etch_radius,
self.etch_height - p.north_west_spine_etch_radius,
]
)
cpw_circ_center = np.array(
[
north_cpw_etch_left_edge_x - p.north_cpw_etch_radius,
self.etch_height + p.north_cpw_etch_radius,
]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center,
spine_circ_center,
p.north_cpw_etch_radius,
p.north_west_spine_etch_radius,
y_int_val=self.etch_height,
)
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_west_spine_etch_radius,
"nw",
(x, y),
clockwise=True,
start_angle=np.pi,
end_angle=spine_end_angle,
)
)
cap_etch_coords.append(partial_info["int_point"])
x = north_cpw_etch_left_edge_x
cpw_start_angle = partial_info["cpw_angle"]
assert cpw_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius, "se", (x, y), start_angle=cpw_start_angle, end_angle=0
)
)
elif sel_partial_valid: # ne spine curve -> tilted line -> sw cpw curve
raise NotImplementedError
assert self.etch_left_edge_x < south_cpw_etch_right_edge_x, (
f"south_cpw must be connected to capacitor (exceeds {self.etch_left_edge_x} mm). Reduce offset magnitude"
)
y_int_val = self.etch_height - p.north_gap_ground
spine_circ_center = np.array(
[self.etch_left_edge_x - p.north_west_spine_etch_radius, y_int_val - p.north_west_spine_radius]
)
cpw_circ_center = np.array(
[north_cpw_etch_left_edge_x + p.north_cpw_etch_radius, y_int_val + p.north_cpw_radius]
)
partial_info = self._get_partial_corner_info(
cpw_circ_center,
spine_circ_center,
p.north_cpw_etch_radius,
p.north_west_spine_etch_radius,
y_int_val=y_int_val,
)
y = y_int_val
spine_end_angle = partial_info["spine_angle"]
assert spine_end_angle >= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_west_spine_etch_radius, "ne", (x, y), start_angle=0, end_angle=spine_end_angle
)
)
cap_etch_coords.append(partial_info["int_point"])
x = north_cpw_etch_left_edge_x
cpw_start_angle = partial_info["cpw_angle"]
assert cpw_start_angle <= 0.0, "DEBUG"
cap_etch_coords.extend(
self._get_rounded_corner_coords(
p.north_cpw_etch_radius,
"sw",
(x, y),
start_angle=cpw_start_angle,
end_angle=-np.pi,
clockwise=True,
)
)
else:
raise RuntimeError("Should not reach this point. Code logic issue")
x = south_cpw_etch_right_edge_x
y = -south_cpw_etch_length
cap_etch_coords.append((x, y))
elif p.north_cpw_length > 0.0 and (p.north_cpw_length < p.north_gap_ground):
north_cpw_etch_left_edge_x = self.north_cpw_left_edge_x - p.north_cpw_gap
if north_cpw_etch_left_edge_x < self.etch_width / 2:
raise NotImplementedError
elif p.north_cpw_length < 1e-6:
raise NotImplementedError
cap_etch = Polygon(cap_etch_coords)
return cap_etch