diff --git a/qiskit_metal/renderers/renderer_gds/airbridge.py b/qiskit_metal/renderers/renderer_gds/airbridge.py new file mode 100644 index 000000000..d56128111 --- /dev/null +++ b/qiskit_metal/renderers/renderer_gds/airbridge.py @@ -0,0 +1,79 @@ +from qiskit_metal import draw, Dict +from qiskit_metal.qlibrary.core.base import QComponent + + +class Airbridge_forGDS(QComponent): + """ + The base "Airbridge" inherits the "QComponent" class. + + NOTE TO USER: This component is designed for GDS export only. + This QComponent should NOT be rendered for EM simulation. + + Default Options: + * crossover_length: '22um' -- Distance between the two outer squares (aka bridge length). + Usually, this should be the same length as (cpw_width + 2 * cpw_gap) + * bridge_width: '7.5um' -- Width of bridge element + * inner_length: '8um' -- Length of inner square. + * outer_length: '11um' -- Length of outer square. + * square_layer: 30 -- GDS layer of inner squares. + * bridge_layer: 31 -- GDS layer of bridge + outer squares. + """ + + # Default drawing options + default_options = Dict(crossover_length='22um', + bridge_width='7.5um', + inner_length='8um', + outer_length='11um', + square_layer=30, + bridge_layer=31) + """Default drawing options""" + + # Name prefix of component, if user doesn't provide name + component_metadata = Dict(short_name='airbridge') + """Component metadata""" + + def make(self): + """Convert self.options into QGeometry.""" + # Parse options + p = self.parse_options() + crossover_length = p.crossover_length + bridge_width = p.bridge_width + inner_length = p.inner_length + outer_length = p.outer_length + + # Make the inner square structure + left_inside = draw.rectangle(inner_length, inner_length, 0, 0) + right_inside = draw.translate(left_inside, + crossover_length / 2 + outer_length / 2, + 0) + left_inside = draw.translate(left_inside, + -(crossover_length / 2 + outer_length / 2), + 0) + + inside_struct = draw.union(left_inside, right_inside) + + # Make the outer square structure + left_outside = draw.rectangle(outer_length, outer_length, 0, 0) + right_outside = draw.translate(left_outside, + crossover_length / 2 + outer_length / 2, + 0) + left_outside = draw.translate( + left_outside, -(crossover_length / 2 + outer_length / 2), 0) + + # Make the bridge structure + bridge = draw.rectangle(crossover_length, bridge_width, 0, 0) + bridge_struct = draw.union(bridge, left_outside, right_outside) + + ### Final adjustments to allow repositioning + final_design = [bridge_struct, inside_struct] + final_design = draw.rotate(final_design, p.orientation, origin=(0, 0)) + final_design = draw.translate(final_design, p.pos_x, p.pos_y) + bridge_struct, inside_struct = final_design + + ### Add everything as a QGeometry + self.add_qgeometry('poly', {'bridge_struct': bridge_struct}, + layer=p.bridge_layer, + subtract=False) + self.add_qgeometry('poly', {'inside_struct': inside_struct}, + layer=p.square_layer, + subtract=False) diff --git a/qiskit_metal/renderers/renderer_gds/gds_renderer.py b/qiskit_metal/renderers/renderer_gds/gds_renderer.py index c6e2e7670..5279526cb 100644 --- a/qiskit_metal/renderers/renderer_gds/gds_renderer.py +++ b/qiskit_metal/renderers/renderer_gds/gds_renderer.py @@ -33,6 +33,8 @@ import numpy as np from qiskit_metal.renderers.renderer_base import QRenderer +from qiskit_metal.renderers.renderer_gds.airbridge import Airbridge_forGDS +from qiskit_metal.renderers.renderer_gds.make_airbridge import Airbridging from qiskit_metal.renderers.renderer_gds.make_cheese import Cheesing from qiskit_metal.toolbox_metal.parsing import is_true from qiskit_metal import draw @@ -94,6 +96,13 @@ class QGDSRenderer(QRenderer): * junction_pad_overlap: '5um' * max_points: '199' * fabricate: 'False' + * airbridge: Dict + * geometry: Dict + qcomponent_base: Airbridge_forGDS + options: Dict(crossover_length='22um') + * bridge_pitch: '100um' + * bridge_minimum_spacing: '5um' + * datatype: '0' * cheese: Dict * datatype: '100' * shape: '0' @@ -212,6 +221,26 @@ class QGDSRenderer(QRenderer): # handle is 8191. max_points='199', + # Airbriding + airbridge=Dict( + # GDS datatype of airbridges. + datatype='0', + + # Setup geometrical style of airbridge + geometry=Dict( + # Skeleton of airbridge in QComponent form, + # meaning this is a child of QComponents. + qcomponent_base=Airbridge_forGDS, + # These options are plugged into the qcomponent_base. + # Think of it as calling qcomponent_base(design, name, options=options). + options=Dict(crossover_length='22um')), + # Spacing between centers of each airbridge. + bridge_pitch='100um', + + # Minimum spacing between each airbridge, + # this number usually comes from fabrication guidelines. + bridge_minimum_spacing='5um'), + # Cheesing is denoted by each chip and layer. cheese=Dict( #Cheesing is NOT completed @@ -288,7 +317,8 @@ class QGDSRenderer(QRenderer): element_table_data = dict( # Cell_name must exist in gds file with: path_filename - junction=dict(cell_name='my_other_junction')) + junction=dict(cell_name='my_other_junction'), + path=dict(make_airbridge=False)) """Element table data""" def __init__(self, @@ -1096,6 +1126,89 @@ def new_gds_library(self) -> gdspy.GdsLibrary: return self.lib + ### Start of Airbridging + + def _populate_airbridge(self): + """ + Main function to make airbridges. This is called in `self.export_to_gds()`. + """ + for chip_name in self.chip_info: + layers_in_chip = self.design.qgeometry.get_all_unique_layers( + chip_name) + + chip_box, status = self.design.get_x_y_for_chip(chip_name) + if status == 0: + minx, miny, maxx, maxy = chip_box + + # Right now this code assumes airbridges will look + # the same across all CPWs. If you want to change that, + # add an if/else statement here to check for custom behavior. + # You will also have to update the self.default_options. + self._make_uniform_airbridging_df(minx, miny, maxx, maxy, + chip_name) + + def _make_uniform_airbridging_df(self, minx: float, miny: float, + maxx: float, maxy: float, chip_name: str): + """ + Apply airbridges to all `path` elements which have + options.gds_make_airbridge = True. This is a + wrapper for Airbridging.make_uniform_airbridging_df(...). + + Args: + minx (float): Chip minimum x location. + miny (float): Chip minimum y location. + maxx (float): Chip maximum x location. + maxy (float): chip maximum y location. + chip_name (str): User defined chip name. + """ + # Warning / limitations + if (self.options.corners != 'circular bend'): + self.logger.warning( + 'Uniform airbridging is designed for `self.options.corners = "circular bend"`. You might experience unexpected behavior.' + ) + + # gdspy objects + top_cell = self.lib.cells[f'TOP_{chip_name}'] + lib_cell = self.lib.new_cell(f'TOP_{chip_name}_ab') + datatype = int(self.parse_value(self.options.airbridge.datatype)) + no_cheese_buffer = float(self.parse_value( + self.options.no_cheese.buffer)) + + # Airbridge Options + self.options.airbridge.qcomponent_base + self.options.airbridge.options + airbridging = Airbridging(design=self.design, + lib=self.lib, + minx=minx, + miny=miny, + maxx=maxx, + maxy=maxy, + chip_name=chip_name, + precision=self.options.precision) + airbridges_df = airbridging.make_uniform_airbridging_df( + custom_qcomponent=self.options.airbridge.geometry.qcomponent_base, + qcomponent_options=self.options.airbridge.geometry.options, + bridge_pitch=self.options.airbridge.bridge_pitch, + bridge_minimum_spacing=self.options.airbridge.bridge_minimum_spacing + ) + + # Get all MultiPolygons and render to gds file + for _, row in airbridges_df.iterrows(): + ab_component_multi_poly = row['MultiPoly'] + ab_component_layer = row['layer'] + airbridge_gds = self._multipolygon_to_gds( + multi_poly=ab_component_multi_poly, + layer=ab_component_layer, + data_type=datatype, + no_cheese_buffer=no_cheese_buffer) + + lib_cell.add(airbridge_gds) + top_cell.add(gdspy.CellReference(lib_cell)) + + ### End of Airbridging + + ### Start of Cheesing + def _check_cheese(self, chip: str, layer: int) -> int: """Examine the option for cheese_view_in_file. @@ -1481,6 +1594,8 @@ def _cheese_buffer_maker( return combo_shapely return None # Need explicitly to avoid lint warnings. + ### End of Cheesing + def _get_rectangle_points(self, chip_name: str) -> Tuple[list, list]: """There can be more than one chip in QGeometry. All chips export to one gds file. Each chip uses its own subtract rectangle. @@ -2162,6 +2277,10 @@ def export_to_gds(self, # Create self.lib and populate path and poly. self._populate_poly_path_for_export() + # Adds airbridges to CPWs w/ options.gds_make_airbridge = True + # Options for these airbridges are in self.options.airbridge + self._populate_airbridge() + # Add no-cheese MultiPolygon to # self.chip_info[chip_name][chip_layer]['no_cheese'], # if self.options requests the layer. diff --git a/qiskit_metal/renderers/renderer_gds/make_airbridge.py b/qiskit_metal/renderers/renderer_gds/make_airbridge.py new file mode 100644 index 000000000..fe40dc086 --- /dev/null +++ b/qiskit_metal/renderers/renderer_gds/make_airbridge.py @@ -0,0 +1,316 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" For GDS export, separate the logic for airbridging.""" + +import gdspy +import shapely +import numpy as np +import pandas as pd + +from qiskit_metal import draw + +from qiskit_metal.qlibrary.core import QComponent + + +class Airbridging: + """Logic for placing airbridges for QGDSRenderer.""" + + def __init__(self, design: 'QDesign', lib: gdspy.GdsLibrary, minx: float, + miny: float, maxx: float, maxy: float, chip_name: str, + precision: float): + """ + Initializes the Airbridging object. + + Args: + design (QDesign): The QDesign object associated with the design. + lib (gdspy.GdsLibrary): The GdsLibrary object to store GDS data. + minx (float): The minimum x-coordinate of the layout bounding box. + miny (float): The minimum y-coordinate of the layout bounding box. + maxx (float): The maximum x-coordinate of the layout bounding box. + maxy (float): The maximum y-coordinate of the layout bounding box. + chip_name (str): The name of the chip. + precision (float): Round position values to the closest integer multiple of this value. + """ + # Design variables + self.design = design + self.lib = lib + + # Chip setup variables + self.minx = minx + self.miny = miny + self.maxx = maxx + self.maxy = maxy + self.chip_name = chip_name + self.precision = precision + + @property + def cpws_with_ab(self) -> list[str]: + ''' + QGeometry of CPWs w/ airbridges + + Returns: + cpw_names (list[str]): QGeometry of CPWs w/ airbridges + ''' + cpw_names = [] + + path_qgeom = self.design.qgeometry.tables['path'] + cpws_df = path_qgeom[path_qgeom['gds_make_airbridge'] == True] + unique_id = set(list(cpws_df['component'])) + + for cpw_name, cpw_id in self.design.all_component_names_id(): + if cpw_id in unique_id: + cpw_names.append(cpw_name) + + return cpw_names + + def make_uniform_airbridging_df( + self, custom_qcomponent: 'QComponent', qcomponent_options: dict, + bridge_pitch: str, bridge_minimum_spacing: str) -> pd.DataFrame: + """ + Makes the uniform airbridging dataframe + + Args: + custom_qcomponent (QComponent): Airbridge class, child of QComponent. + qcomponent_options (dict): Options when calling airbridge. + These go in `custom_qcomponent(design, name, options=qcomponent_options)` + bridge_pitch (str): Length between airbridges. Measured from the center of the bridge structure. + bridge_minimum_spacing (str): Spacing between corner and first airbridge. + + Returns: + airbridge_df (pd.DataFrame): Has two columns: 'MultiPoly' and 'layer'. + Used in QGDSRenderer._make_uniform_airbridging_df + """ + bridge_pitch = self.design.parse_value(bridge_pitch) + bridge_minimum_spacing = self.design.parse_value(bridge_minimum_spacing) + + # Get shapley cutout of airbridges + ab_qgeom = self.extract_qgeom_from_unrendered_qcomp( + custom_qcomponent=custom_qcomponent, + qcomponent_options=qcomponent_options) + + # Place the airbridges + ab_df_list = [] + for cpw_name in self.cpws_with_ab: + ab_placement = self.find_uniform_ab_placement( + cpw_name=cpw_name, + bridge_pitch=bridge_pitch, + bridge_minimum_spacing=bridge_minimum_spacing) + airbridge_df_for_cpw = self.ab_placement_to_df( + ab_placement=ab_placement, ab_qgeom=ab_qgeom) + ab_df_list.append(airbridge_df_for_cpw) + + airbridge_df = pd.concat(ab_df_list) + + return airbridge_df + + def find_uniform_ab_placement( + self, cpw_name: str, bridge_pitch: float, + bridge_minimum_spacing: float) -> list[tuple[float, float, float]]: + ''' + Determines where to place the wirebonds given a CPW. + + Inputs: + cpw_name (str): Name of cpw to find airbridge placements. + bridge_pitch: (float) -- Spacing between the centers of each bridge. Units in mm. + bridge_minimum_spacing: (float) -- Minimum spacing from corners. Units in mm. + + Returns: + ab_placements (list[tuple[float, float, float]]): Where the airbridges should be placed for given `cpw_name`. + + Data structure is in the form of list of tuples + [(x0, y0, theta0), + (x1, y1, theta1), + ..., + (xn, yn, thetan))] + + - x (float): x position of airbridge. Units mm. + - y (float): y position of airbridge. Units mm. + - theta (float): Rotation of airbridge. Units degrees. + ''' + precision = self.design.parse_value(self.precision) + + target_cpw = self.design.components[cpw_name] + + points = target_cpw.get_points() + ab_placements = [] + points_theta = [] + + fillet = self.design.parse_value(target_cpw.options.fillet) + + ### Handles all the straight sections ### + for i in range(len(points) - 1): + # Set up parameters for this calculation + pos_i = points[i] + pos_f = points[i + 1] + + x0 = round(float(pos_i[0]) / precision) * precision + y0 = round(float(pos_i[1]) / precision) * precision + xf = round(float(pos_f[0]) / precision) * precision + yf = round(float(pos_f[1]) / precision) * precision + + dl = (xf - x0, yf - y0) + dx = dl[0] + dy = dl[1] + + theta = np.arctan2(dy, dx) + mag_dl = np.sqrt(dx**2 + dy**2) + lprime = mag_dl - 2 * bridge_minimum_spacing + + if fillet > bridge_minimum_spacing: + lprime = mag_dl - 2 * fillet + else: + lprime = mag_dl - 2 * bridge_minimum_spacing + n = 1 #refers to the number of bridges you've already placed + #Asking should I place another? If true place another one. + while (lprime) >= (n * bridge_pitch): + n += 1 + + mu_x = (xf + x0) / 2 + mu_y = (yf + y0) / 2 + + x = np.array([i * bridge_pitch * np.cos(theta) for i in range(n)]) + y = np.array([i * bridge_pitch * np.sin(theta) for i in range(n)]) + + x = (x - np.average(x)) + mu_x + y = (y - np.average(y)) + mu_y + + for i in range(n): + ab_placements.append((x[i], y[i], np.degrees(theta))) + + #This is for the corner points + points_theta.append(theta) + + ### This handles all the corner / turning sections ### + # First check to see if any turns exists + if (len(points) > 2): + corner_points = points_theta[1:-1] + for i in range(len(corner_points) + 1): + + # First check to see if we should + # even make an airbridge at this corner + pos_i = points[i] + pos_f = points[i + 1] + + x0 = round(float(pos_i[0]) / precision) * precision + y0 = round(float(pos_i[1]) / precision) * precision + xf = round(float(pos_f[0]) / precision) * precision + yf = round(float(pos_f[1]) / precision) * precision + + mag_dl = np.sqrt((xf - x0)**2 + (yf - y0)**2) + + if mag_dl < fillet or mag_dl < bridge_minimum_spacing: + continue + + # Now that we only have real turns + # let's find the center trace of to align the wirebonds + theta_f = points_theta[i + 1] + theta_i = points_theta[i] + + dx = np.cos(theta_i) - np.cos(theta_f) + dy = np.sin(theta_i) - np.sin(theta_f) + + theta = np.arctan2(dy, dx) + + distance_circle_box_x = fillet * (1 - np.abs(np.cos(theta))) + distance_circle_box_y = fillet * (1 - np.abs(np.sin(theta))) + + theta_avg = (theta_f + theta_i) / 2 + + x = points[i + 1][0] - distance_circle_box_x * np.sign( + np.cos(theta)) + y = points[i + 1][1] - distance_circle_box_y * np.sign( + np.sin(theta)) + + ab_placements.append((x, y, np.degrees(theta_avg))) + + # Removes airbridge at the start pin + ab_placements = ab_placements[1:] + + return ab_placements + + def ab_placement_to_df(self, ab_placement: list[tuple[float, float, float]], + ab_qgeom: pd.DataFrame) -> pd.DataFrame: + ''' + With a base airbridge shape, find the shapely data for placing all airbridges. + + Args: + ab_placement (list[tuple[float, float, float]]): Output from self.find_uniform_ab_placement + ab_qgeom (pd.DataFrame): QGeometry table of single airbridge. + + Return: + airbridge_df (pd.DataFrame): All airbridges placed. Has two columns: 'MultiPoly' and 'layer'. + ''' + shapley_data_all = [] + layer_data_all = [] + for _, component in ab_qgeom.iterrows(): + shapley_data = component['geometry'] + for x, y, theta in ab_placement: + # Extract shapely data, and move to proper spot + shapely_copy = draw.rotate(shapley_data, + theta + 90, + origin=(0, 0)) + shapely_copy = draw.translate(shapely_copy, x, y) + shapely_copy = shapely.geometry.MultiPolygon([shapely_copy]) + + # Extract layer info + layer = component['layer'] + + # Add data to DataFrame + shapley_data_all.append(shapely_copy) + layer_data_all.append(layer) + + airbridge_df = pd.DataFrame({ + 'MultiPoly': shapley_data_all, + 'layer': layer_data_all + }) + + return airbridge_df + + def extract_qgeom_from_unrendered_qcomp( + self, custom_qcomponent: 'QComponent', + qcomponent_options: dict) -> 'pd.DataFrame': + ''' + Extracts the qgeometry table from a child of QComponent. + + Args: + custom_qcomponent (QComponent): Class of QComponent, not called / instantiated. + qcomponent_options (dict): Geometrical options for cusotm_qcomponent. In structure of cusotm_qcomponent.default_options. + + Returns: + qgeom_table (pd.DataFrame) + ''' + # Chck you put in a QComponent w/ self.make() functionality + if not issubclass(custom_qcomponent, QComponent): + raise ValueError( + '`custom_qcomponent` must be a child of `QComponent`.') + if not hasattr(custom_qcomponent, 'make'): + raise AttributeError( + '`custom_qcomponent` must have `make()` method') + + # Make a name which won't interfer w/ other components + test_name = 'initial_name' + all_component_names = self.design.components.keys() + while test_name in all_component_names: + test_name += '1' + + # Temporarily render in QComponent + qcomponent_obj = custom_qcomponent(self.design, + test_name, + options=qcomponent_options) + # Extract shapley data + qgeom_table = qcomponent_obj.qgeometry_table('poly') + # Delete it + qcomponent_obj.delete() + + return qgeom_table diff --git a/qiskit_metal/tests/test_designs.py b/qiskit_metal/tests/test_designs.py index 3cdc312b0..d8f1e8038 100644 --- a/qiskit_metal/tests/test_designs.py +++ b/qiskit_metal/tests/test_designs.py @@ -503,7 +503,7 @@ def test_design_get_list_of_tables_in_metadata(self): result = q1._get_table_values_from_renderers(design) - self.assertEqual(len(result), 17) + self.assertEqual(len(result), 18) self.assertEqual(result['hfss_inductance'], '10nH') self.assertEqual(result['hfss_capacitance'], 0) self.assertEqual(result['hfss_resistance'], 0) @@ -517,6 +517,7 @@ def test_design_get_list_of_tables_in_metadata(self): self.assertEqual(result['q3d_wire_bonds'], False) self.assertEqual(result['aedt_hfss_inductance'], 10e-9) self.assertEqual(result['aedt_hfss_capacitance'], 0) + self.assertEqual(result['gds_make_airbridge'], False) if __name__ == '__main__': diff --git a/qiskit_metal/tests/test_renderers.py b/qiskit_metal/tests/test_renderers.py index b5d6a5d17..df2cb3b73 100644 --- a/qiskit_metal/tests/test_renderers.py +++ b/qiskit_metal/tests/test_renderers.py @@ -22,8 +22,9 @@ import unittest from unittest.mock import MagicMock import matplotlib.pyplot as _plt +import pandas as pd -from qiskit_metal import designs +from qiskit_metal import designs, Dict, draw from qiskit_metal.renderers import setup_default from qiskit_metal.renderers.renderer_ansys.ansys_renderer import QAnsysRenderer from qiskit_metal.renderers.renderer_ansys.q3d_renderer import QQ3DRenderer @@ -42,7 +43,11 @@ from qiskit_metal.qgeometries.qgeometries_handler import QGeometryTables from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket -from qiskit_metal import draw +from qiskit_metal.qlibrary.terminations.open_to_ground import OpenToGround +from qiskit_metal.qlibrary.tlines.mixed_path import RouteMixed +from qiskit_metal.renderers.renderer_gds.airbridge import Airbridge_forGDS + +from qiskit_metal.renderers.renderer_gds.make_airbridge import Airbridging class TestRenderers(unittest.TestCase): @@ -323,7 +328,7 @@ def test_renderer_gdsrenderer_options(self): renderer = QGDSRenderer(design) options = renderer.default_options - self.assertEqual(len(options), 17) + self.assertEqual(len(options), 18) self.assertEqual(options['short_segments_to_not_fillet'], 'True') self.assertEqual(options['check_short_segments_by_scaling_fillet'], '2.0') @@ -343,6 +348,18 @@ def test_renderer_gdsrenderer_options(self): self.assertEqual(options['fabricate'], 'False') + self.assertEqual(len(options['airbridge']), 4) + self.assertEqual(len(options['airbridge']['geometry']), 2) + + self.assertEqual(options['airbridge']['geometry']['qcomponent_base'], + Airbridge_forGDS) + self.assertEqual( + options['airbridge']['geometry']['options']['crossover_length'], + '22um') + self.assertEqual(options['airbridge']['bridge_pitch'], '100um') + self.assertEqual(options['airbridge']['bridge_minimum_spacing'], '5um') + self.assertEqual(options['airbridge']['datatype'], '0') + self.assertEqual(len(options['cheese']), 9) self.assertEqual(len(options['no_cheese']), 5) @@ -538,11 +555,15 @@ def test_renderer_gdsrenderer_high_level(self): self.assertEqual(renderer.name, 'gds') element_table_data = renderer.element_table_data - self.assertEqual(len(element_table_data), 1) + self.assertEqual(len(element_table_data), 2) + self.assertEqual(len(element_table_data['junction']), 1) self.assertEqual(element_table_data['junction']['cell_name'], 'my_other_junction') + self.assertEqual(len(element_table_data['path']), 1) + self.assertEqual(element_table_data['path']['make_airbridge'], False) + def test_renderer_gdsrenderer_update_units(self): """Test update_units in gds_renderer.py.""" design = designs.DesignPlanar() @@ -591,6 +612,55 @@ def test_renderer_mpl_interaction_disconnect(self): mpl.disconnect() self.assertEqual(mpl.figure, None) + def test_renderer_gds_check_uniform_airbridge(self): + """Tests uniform airbridge placement via Airbridging""" + design = designs.DesignPlanar() + open_start_options = Dict(pos_x='1000um', + pos_y='0um', + orientation='-90') + open_start_meander = OpenToGround(design, + 'Open_meander_start', + options=open_start_options) + open_end_options = Dict(pos_x='1200um', + pos_y='500um', + orientation='0', + termination_gap='10um') + open_end_meander = OpenToGround(design, + 'Open_meander_end', + options=open_end_options) + meander_options = Dict(pin_inputs=Dict( + start_pin=Dict(component='Open_meander_start', pin='open'), + end_pin=Dict(component='Open_meander_end', pin='open')), + fillet='49.99um', + gds_make_airbridge=True) + testMeander = RouteMixed(design, 'meanderTest', options=meander_options) + + airbridging = Airbridging(design=design, + lib=None, + minx=None, + miny=None, + maxx=None, + maxy=None, + chip_name=None, + precision=0.000001) + ab_placement_result = airbridging.find_uniform_ab_placement( + cpw_name='meanderTest', + bridge_pitch=0.3, # in mm + bridge_minimum_spacing=0.005) # in mm + ab_placement_check = [(1.0, 0.4, 90.0), (1.1, 0.5, 0.0), + (1.0146417320084844, 0.4853582679915155, 45.0)] + self.assertEqual(ab_placement_result, ab_placement_check) + + test_ab_qgeom = pd.DataFrame({ + 'geometry': [draw.Polygon([(0, 0), (1, 0), (0, 1)])], + 'layer': [1] + }) + df_result = airbridging.ab_placement_to_df( + ab_placement=ab_placement_result, ab_qgeom=test_ab_qgeom) + + self.assertIn('MultiPoly', df_result) + self.assertIn('layer', df_result) + def test_renderer_gds_check_cheese(self): """Test check_cheese in gds_renderer.py.""" design = designs.DesignPlanar() diff --git a/tutorials/3 Renderers/3.2 Export your design to GDS.ipynb b/tutorials/3 Renderers/3.2 Export your design to GDS.ipynb index 36fd7f85c..4ea357d64 100644 --- a/tutorials/3 Renderers/3.2 Export your design to GDS.ipynb +++ b/tutorials/3 Renderers/3.2 Export your design to GDS.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -8,6 +9,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -15,6 +17,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -37,6 +40,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -52,6 +56,7 @@ "import qiskit_metal as metal\n", "from qiskit_metal import designs, draw\n", "from qiskit_metal import MetalGUI, Dict, Headings\n", + "from qiskit_metal.qlibrary.core import QComponent\n", "from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket\n", "from qiskit_metal.qlibrary.qubits.transmon_cross import TransmonCross" ] @@ -117,6 +122,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -150,6 +156,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -191,7 +198,8 @@ " start_straight='0.13mm',\n", " end_straight='0.13mm'\n", " ),\n", - " total_length=length)\n", + " total_length=length,\n", + " gds_make_airbridge=True)\n", " myoptions.update(options)\n", " myoptions.meander.asymmetry = asymmetry\n", " myoptions.meander.lead_direction_inverted = 'true' if flip else 'false'\n", @@ -246,6 +254,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -256,6 +265,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -266,6 +276,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -284,6 +295,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -313,6 +325,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -392,6 +405,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -399,6 +413,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -415,6 +430,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -453,6 +469,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -472,6 +489,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -491,6 +509,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -535,6 +554,162 @@ "a_gds.options['tolerance'] = '0.00001'" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Headings.h1('Airbridges')" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Airbridges are extensions of the ground plane which loop over CPWs. They are meant to increase a CPWs capacitance to ground. Normally preparing the GDS file for airbridges would take someone a couple hours, but Qiskit Metal can renderer in airbridges automatically.\n", + "\n", + "To enable airbridges on a CPW:\n", + "```\n", + "cpw_options = dict(...,\n", + " gds_make_airbridge = True)\n", + "\n", + "CPW_QComponent(design, name, options=cpw_options)\n", + "```\n", + "This was also demonstated in the QComponent design at the beginning of this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Starting off, we can change the datatype of our airbridge\n", + "a_gds.options['airbridge']['datatype'] = 0" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Change the geometry of the airbridge\n", + "\n", + "To modify how an airbridge looks on the GDS file, we can create a custom `QComponent` (if you're unfamiliar w/ creating custom components, see [tutorials/2 From components to chip/D. How do I make my custom QComponent](https://github.com/qiskit-community/qiskit-metal/tree/main/tutorials/2%20From%20components%20to%20chip/D.%20How%20do%20I%20make%20my%20custom%20QComponent))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class MyCustomAirbridge(QComponent):\n", + " \"\"\"\n", + " The default airbridge has square poles. \n", + " Let's make an airbridge w/ circular poles.\n", + "\n", + " Args:\n", + " crossover_length:'25um' -- The length of the crossover in micrometers.\n", + " bridge_width: '7.5um' -- The width of the bridge in micrometers.\n", + " inner_radius: '4um' -- The radius of the inner circle in micrometers.\n", + " outer_radius: '5.5um' -- The radius of the outer circle in micrometers.\n", + " inner_layer: 30 -- The GDS layer number of the inner circle.\n", + " outer_layer: 31 -- The GDS layer number of the outer circle and bridge.\n", + " \"\"\"\n", + "\n", + " default_options = Dict(\n", + " crossover_length='25um'\n", + " bridge_width='7.5um'\n", + " inner_radius='4um',\n", + " outer_radius='5.5um',\n", + " inner_layer=30,\n", + " outer_layer=31\n", + " )\n", + "\n", + " def make(self):\n", + " # Parse options\n", + " p = self.p\n", + "\n", + " # Inner circle\n", + " crossover_length = p.crossover_length\n", + " bridge_width = p.bridge_width\n", + " inner_length = p.inner_length\n", + " outer_radius = p.outer_radius\n", + "\n", + " # Make the inner square structure\n", + " inner_circle = draw.shapely.geometry.Point(0, 0).buffer(inner_radius)\n", + " right_inside = draw.translate(inner_circle,\n", + " crossover_length / 2 + outer_radius,\n", + " 0)\n", + " left_inside = draw.translate(inner_circle,\n", + " -(crossover_length / 2 + outer_radius),\n", + " 0)\n", + "\n", + " inside_struct = draw.union(left_inside, right_inside)\n", + "\n", + " # Make the outer square structure\n", + " outer_circle = draw.shapely.geometry.Point(0, 0).buffer(outer_radius)\n", + " right_outside = draw.translate(outer_circle,\n", + " crossover_length / 2 + outer_radius,\n", + " 0)\n", + " left_outside = draw.translate(\n", + " outer_circle, -(crossover_length / 2 + outer_radius), 0)\n", + "\n", + " # Make the bridge structure\n", + " bridge = draw.rectangle(crossover_length, bridge_width, 0, 0)\n", + " bridge_struct = draw.union(bridge, left_outside, right_outside)\n", + "\n", + " ### Final adjustments to allow repositioning\n", + " final_design = [bridge_struct, inside_struct]\n", + " final_design = draw.rotate(final_design, p.orientation, origin=(0, 0))\n", + " final_design = draw.translate(final_design, p.pos_x, p.pos_y)\n", + " bridge_struct, inside_struct = final_design\n", + "\n", + " ### Add everything as a QGeometry\n", + " self.add_qgeometry('poly', {'bridge_struct': bridge_struct},\n", + " layer=p.outer_layer,\n", + " subtract=False)\n", + " self.add_qgeometry('poly', {'inside_struct': inside_struct},\n", + " layer=p.inner_layer,\n", + " subtract=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use this custom airbridge\n", + "a_gds.options['airbridge']['geometry']['qcomponent_base'] = MyCustomAirbridge\n", + "\n", + "# Modify the geometry from the default options\n", + "# Notice this is the same way we can modify QComponents!\n", + "a_gds.options['airbridge']['geometry']['options'] = Dict(\n", + " inner_radius='2um', # inner raidus\n", + " inner_layer=60, # refers to GDS layer\n", + " outer_layer=61 # refers to GDS layer\n", + ") \n", + "# You can play w/ other options! \n", + "# Or let them default to whatever is in your custom component." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Distance between airbridges, measured from center of bridge\n", + "a_gds.options.airbridge.bridge_pitch = '50um'\n", + "\n", + "# Ensures a minimum distance between the center of airbridges\n", + "a_gds.options.airbridge.bridge_minimum_spacing = '10um'" + ] + }, { "cell_type": "code", "execution_count": null, @@ -547,6 +722,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -560,6 +736,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -606,6 +783,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -640,6 +818,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -710,6 +889,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -752,6 +932,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -780,6 +961,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [