Source code for kqcircuits.simulations.epr.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 kqcircuits.pya_resolver import pya
from kqcircuits.simulations.epr.utils import in_gui, EPRTarget
from kqcircuits.simulations.partition_region import PartitionRegion
from kqcircuits.elements.finger_capacitor_square import eval_a2, eval_b2

vertical_dimension = 1.0
metal_edge_dimension = 1.0
waveguide_margin = 10.0
waveguide_length_scale = 5


[docs] def partition_regions(simulation: EPRTarget, prefix: str = "") -> list[PartitionRegion]: """ Returns partition regions. Doesn't work in general with non-symmetric spiral capacitors. """ rr = simulation.finger_width / 2 + simulation.ground_gap rr /= simulation.layout.dbu # Use modified SmoothCapacitor geometry to obtain partition regions on fingers finger_gaps = pya.Region(simulation.cell.begin_shapes_rec(simulation.get_layer("ground_grid_avoidance"))).sized( (-simulation.margin - simulation.ground_gap - simulation.finger_width / 2) / simulation.layout.dbu, 5 ) ra = simulation.a / 2 + simulation.b + waveguide_margin port_a = simulation.refpoints["port_a"] dir_a = waveguide_length_scale * (simulation.refpoints["port_a_corner"] - port_a) cross_a = ra / dir_a.abs() * pya.DVector(dir_a.y, -dir_a.x) wg_region = pya.Region( pya.DPolygon( [ port_a - cross_a, port_a - cross_a + dir_a, port_a + cross_a + dir_a, port_a + cross_a, ] ).to_itype(simulation.layout.dbu) ) rb = eval_a2(simulation) / 2 + eval_b2(simulation) + waveguide_margin port_b = simulation.refpoints["port_b"] dir_b = waveguide_length_scale * (simulation.refpoints["port_b_corner"] - port_b) cross_b = rb / dir_b.abs() * pya.DVector(dir_b.y, -dir_b.x) wg_region += pya.Region( pya.DPolygon( [ port_b - cross_b, port_b - cross_b + dir_b, port_b + cross_b + dir_b, port_b + cross_b, ] ).to_itype(simulation.layout.dbu) ) result = [ PartitionRegion( name=f"{prefix}fingergmer", face=simulation.face_ids[0], metal_edge_dimensions=metal_edge_dimension, region=finger_gaps, vertical_dimensions=vertical_dimension, visualise=True, ), PartitionRegion( name=f"{prefix}waveguides", face=simulation.face_ids[0], region=wg_region, visualise=True, ), PartitionRegion( name=f"{prefix}groundgmer", face=simulation.face_ids[0], metal_edge_dimensions=metal_edge_dimension, vertical_dimensions=vertical_dimension, visualise=True, ), ] return result
[docs] def correction_cuts(simulation: EPRTarget, prefix: str = "") -> dict[str, dict]: """ Returns correction cuts. Doesn't work in general with non-symmetric spiral capacitors. """ base_rf = simulation.refpoints["base"] z_me = 0 if not in_gui(simulation): if len(simulation.face_stack) > 1: z_me = -simulation.substrate_height[1] - simulation.chip_distance - 2 * simulation.metal_height gaps = pya.Region(simulation.cell.begin_shapes_rec(simulation.get_layer("base_metal_gap_wo_grid"))) top_most = None center_most = None for polygon in gaps.each(): d_poly = polygon.to_dtype(simulation.layout.dbu) points = [(e.p1 + e.p2) / 2 for e in d_poly.each_edge()] + list(d_poly.each_point_hull()) for p in points: if top_most is None or p.y > top_most.y: top_most = p if center_most is None or p.distance(base_rf) < center_most.distance(base_rf): center_most = p center_dir = 2 * (center_most - base_rf) center_gap_len = center_dir.abs() center_dir /= center_gap_len half_cut_len = 25.0 result = { f"{prefix}fingergmer": { "p1": center_most + simulation.finger_width * 0.9 * center_dir, "p2": center_most - center_dir * (center_gap_len + simulation.finger_width * 0.9), "metal_edges": [ {"x": simulation.finger_width * 0.9, "z": z_me}, {"x": center_gap_len + simulation.finger_width * 0.9, "z": z_me}, ], }, f"{prefix}groundgmer": { "p1": top_most + pya.DPoint(0, half_cut_len), "p2": top_most - pya.DPoint(0, simulation.ground_gap + simulation.finger_width * 0.9), "metal_edges": [ {"x": half_cut_len, "z": z_me}, {"x": half_cut_len + simulation.ground_gap, "z": z_me}, ], }, } return result