Source code for kqcircuits.elements.spiral_capacitor

# 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 math import pi, cos, sqrt

from kqcircuits.elements.smooth_capacitor import SmoothCapacitor, unit_vector, segment_points
from kqcircuits.pya_resolver import pya
from kqcircuits.util.geometry_helper import get_angle
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
from kqcircuits.elements.element import Element


[docs] @add_parameters_from(SmoothCapacitor, "a2", "b2", "finger_width", "finger_gap", "ground_gap") class SpiralCapacitor(Element): """The PCell declaration for a spiral capacitor.""" finger_width2 = Param( pdt.TypeDouble, "Width of a finger on the right", -1, unit="μm", docstring="Non-physical value '-1' means that same width is used on both sides.", ) ground_gap2 = Param( pdt.TypeDouble, "Gap between ground and finger on the right", -1, unit="μm", docstring="Non-physical value '-1' means that same gap is used on both sides.", ) spiral_angle = Param(pdt.TypeDouble, "Angle of spiral in degrees", 180, unit="degrees") spiral_angle2 = Param( pdt.TypeDouble, "Angle of spiral in degrees on the right", -1, unit="degrees", docstring="Non-physical value '-1' means that same angle is used on both sides.", ) rotation = Param( pdt.TypeDouble, "Waveguide rotation in degrees", -1, unit="degrees", docstring="Non-physical value '-1' means that the waveguide points towards the spiral center.", ) rotation2 = Param( pdt.TypeDouble, "Waveguide rotation in degrees on the right", -1, unit="degrees", docstring="Non-physical value '-1' means that the same rotation is used on both sides.", )
[docs] def can_create_from_shape_impl(self): return self.shape.is_path()
[docs] def build(self): def spiral_segment(segment_index, spiral_angle, rotation): finger_width2 = self.finger_width if self.finger_width2 < 0 else self.finger_width2 width = (self.finger_width + finger_width2) / 2 + self.finger_gap angle = (segment_index % 2 + rotation / 180) * pi origin = pya.DPoint(-unit_vector(angle) * width / 2) turn = (spiral_angle / 180 - segment_index) * pi radius = (segment_index + 1) * width return origin, angle, turn, radius def spiral_position(spiral_angle): origin, angle, turn, radius = spiral_segment(int(spiral_angle / 180), spiral_angle, 0.0) position = origin + unit_vector(angle + turn) * radius return get_angle(position), position.abs() def spiral_region(spiral_angle, rotation, finger_r): pnts = [] stnp = [] segments = int(spiral_angle / 180 - 1e-3) for s in range(segments + 1): o, a, t, r = spiral_segment(s, spiral_angle if s == segments else 180 * (s + 1), rotation) v0 = unit_vector(a) v1 = unit_vector(a + t) if s == 0: pnts += segment_points(o + v0 * (r + finger_r), a - pi / 2, -pi, pi * finger_r, self.n) pnts += segment_points(o + v0 * (r - finger_r), a + pi / 2, t, t * (r - finger_r), self.n) stnp += segment_points(o + v1 * (r + finger_r), a + t - pi / 2, -t, t * (r + finger_r), self.n)[::-1] if s == segments: pnts += segment_points(o + v1 * (r - finger_r), a + t + pi / 2, -pi, pi * finger_r, self.n) return pya.Region(pya.DPolygon(pnts + stnp[::-1]).to_itype(self.layout.dbu)) def wg_region(p0, r0, p1, w1, l1=0): l0 = (p1 - p0).abs() dx = (p1 - p0) / l0 dy = pya.DVector(dx.y, -dx.x) div = r0 * r0 * l0 / (w1 * w1 + l0 * l0) rx = (1 - sqrt(1 - (r0 * r0 - w1 * w1) / (l0 * div))) * div ry = sqrt(r0 * r0 - rx * rx) pnts = [p1 - dy * w1, p0 + dx * rx - dy * ry, p0 + dx * rx + dy * ry, p1 + dy * w1] if l1 > 0: pnts += [p1 + dx * l1 + dy * w1, p1 + dx * l1 - dy * w1] return pya.Region(pya.DPolygon(pnts).to_itype(self.layout.dbu)) def port_position(position, direction, distance): return position + (distance - direction.sprod(position.to_v())) * direction # Process terms on the right a2 = self.a if self.a2 < 0 else self.a2 b2 = self.b if self.b2 < 0 else self.b2 finger_width2 = self.finger_width if self.finger_width2 < 0 else self.finger_width2 ground_gap2 = self.ground_gap if self.ground_gap2 < 0 else self.ground_gap2 spiral_angle2 = self.spiral_angle if self.spiral_angle2 < 0 else self.spiral_angle2 rotation2 = self.rotation if self.rotation2 < 0 else self.rotation2 # Find angles for spirals rot, length = spiral_position(self.spiral_angle - max(0, self.rotation - 90)) spiral_rotation = 180 - rot if self.rotation < 0 else 90 - self.spiral_angle + self.rotation rot2, length2 = spiral_position(spiral_angle2 - max(0, rotation2 - 90)) spiral_rotation2 = spiral_rotation - 180 rotation_out = spiral_rotation2 + (rot2 if rotation2 < 0 else 90 + spiral_angle2 - rotation2) # Create spiral regions finger_left = spiral_region(self.spiral_angle, spiral_rotation, self.finger_width / 2) finger_right = spiral_region(spiral_angle2, spiral_rotation2, finger_width2 / 2) # Find waveguide positions and directions pos = pya.DPoint(length * unit_vector((rot + spiral_rotation) / 180 * pi)) port_dir = pya.DVector(-1, 0) port_pos = port_position(pos, port_dir, length + self.finger_width / 2 + self.ground_gap) pos2 = pya.DPoint(length2 * unit_vector((rot2 + spiral_rotation2) / 180 * pi)) port_dir2 = unit_vector(rotation_out / 180 * pi) port_pos2 = port_position(pos2, port_dir2, length2 + finger_width2 / 2 + ground_gap2) # Create waveguide regions safe_scale = cos(pi / self.n) wg_left = wg_region(pos, self.finger_width / 2 * safe_scale, port_pos, self.a / 2, length) gap_left = wg_region(pos, (self.finger_width / 2 + self.ground_gap) * safe_scale, port_pos, self.a / 2 + self.b) wg_right = wg_region(pos2, finger_width2 / 2 * safe_scale, port_pos2, a2 / 2, length2) gap_right = wg_region(pos2, (finger_width2 / 2 + ground_gap2) * safe_scale, port_pos2, a2 / 2 + b2) # Combine gap and metal regions region_ground = ( finger_left.sized(self.ground_gap / self.layout.dbu, 5) + finger_right.sized(ground_gap2 / self.layout.dbu, 5) + gap_left + gap_right ) gap_region = region_ground - finger_left - finger_right - wg_left - wg_right # Create shapes into cell self.cell.shapes(self.get_layer("base_metal_gap_wo_grid")).insert(gap_region) # protection region_protection = region_ground.sized(self.margin / self.layout.dbu, 5) self.add_protection(region_protection) # Add size into annotation layer self.cell.shapes(self.get_layer("annotations")).insert(pya.DText(str(round(self.spiral_angle, 5)), 0, 0)) # Create ports self.add_port("a", port_pos, port_dir) self.add_port("b", port_pos2, port_dir2)
[docs] @classmethod def get_sim_ports(cls, simulation): return Element.left_and_right_waveguides(simulation)