class MyQComponent3(QComponent): """Demonstration2 - Straight segment with variable width/length""" default_options = Dict(width='0.5mm', height='0.1mm', pos_x='0mm', pos_y='0mm', layer='1') """Default drawing options""" # Name prefix of component + import of renderer-specific default_options component_metadata = Dict( short_name='Trace', _qgeometry_table_path='False', #wirebonds _qgeometry_table_poly='True', _qgeometry_table_junction='False') #gds imports and analysis inputs """Component metadata""" def make(self): """calculates the geometries of the QComponent""" p = self.parse_options() # short-handle alias. Options interpreter rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) self.add_qgeometry('poly', {'my_polygon': rect}, layer=p.layer, subtract=False) self.add_pin('in', rect.exterior.coords[:-3:-1], p.height)
class ShortToGround(QComponent): """A sample_shapes short to ground termination. Functions as a pin for auto CPW drawing. Inherits `QComponent` class. Default Options: * width: '10um' -- The width of the 'cpw' terminating to ground (this is merely for the purpose of generating a value to pass to the pin) Values (unless noted) are strings with units included, (e.g., '30um') """ component_metadata = Dict(short_name='term') """Component metadata""" default_options = Dict(width='10um') """Default connector options""" TOOLTIP = """A basic short to ground termination""" def make(self): """Build the component.""" p = self.p # p for parsed parameters. Access to the parsed options. port_line = draw.LineString([(0, -p.width / 2), (0, p.width / 2)]) # Rotates and translates the connector polygons (and temporary port_line) port_line = draw.rotate(port_line, p.orientation, origin=(0, 0)) port_line = draw.translate(port_line, p.pos_x, p.pos_y) port_points = list(draw.shapely.geometry.shape(port_line).coords) #Generates the pin self.add_pin('short', port_points, p.width)
class MyQComponent(QComponent): """Use this class as a template for your components.""" # Edit these to define your own tempate options for creation # Default drawing options default_options = Dict(width='500um', height='300um', pos_x='0um', pos_y='0um', rotation='0', layer='1') """Default drawing options""" # Name prefix of component, if user doesn't provide name component_metadata = Dict(short_name='component') """Component metadata""" # DO NOT DIRECTLY INSTANTIATE USING make() Use build() def make(self): """Convert self.options into QGeometry.""" p = self.parse_options() # Parse the string options into numbers # EDIT HERE - Replace the following with your code # Create some raw geometry # Use autocompletion for the `draw.` module (use tab key) rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) rect = draw.rotate(rect, p.rotation) geom = {'my_polygon': rect} self.add_qgeometry('poly', geom, layer=p.layer, subtract=False)
def test_component_get_nested_dict_item(self): """ Test the functionality of get_nested_dict_item in _parsed_dynamic_attrs.py. """ # Setup expected test results my_dict = Dict(aa=Dict(x1={'dda': '34fF'}, y1='Y', z='10um'), bb=Dict(x2=5, y2='YYYsdg', z='100um'), cc='100nm') # Generate actual result data _test_a = _parsed_dynamic_attrs.get_nested_dict_item(my_dict, ['cc']) _test_b = _parsed_dynamic_attrs.get_nested_dict_item( my_dict, ['aa', 'x1', 'dda']) _test_c = _parsed_dynamic_attrs.get_nested_dict_item(my_dict, ['aa']) _test_d = _parsed_dynamic_attrs.get_nested_dict_item( my_dict, ['empty']) # Test all elements of the result data against expected data self.assertEqual('100nm', _test_a) self.assertEqual('34fF', _test_b) self.assertEqual({ 'x1': { 'dda': '34fF' }, 'y1': 'Y', 'z': '10um' }, _test_c) self.assertEqual({}, _test_d)
class MyQComponent(QComponent): """ This class is a template Use this class as a blueprint to put together for your components - have fun .. meta:: My QComponent """ # Edit these to define your own template options for creation # Default drawing options default_options = Dict(width='500um', height='300um') """Default drawing options""" # Name prefix of component, if user doesn't provide name component_metadata = Dict(short_name='component') """Component metadata""" def make(self): """Convert self.options into QGeometry.""" p = self.parse_options() # Parse the string options into numbers # EDIT HERE - Replace the following with your code # Create some raw geometry # Use autocompletion for the `draw.` module (use tab key) rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) rect = draw.rotate(rect, p.orientation) geom = {'my_polygon': rect} self.add_qgeometry('poly', geom, layer=p.layer, subtract=False)
class SmileyFace(QComponent): """TEST COMPONENT It is for fun only. Can view a smiley face. Can make it wink or frown. .. image:: SmileyFace.png .. meta:: Smiley Face :) Default Options: * happy: True * wink: False * orientation: 0 """ component_metadata = Dict(short_name='Smile') """Component metadata""" default_options = Dict(happy=True, wink=False, orientation=0) """Default connector options""" TOOLTIP = """TEST COMPONENT It is for fun only""" def make(self): """Build the component.""" face = draw.shapely.geometry.Point(0, 0).buffer(1) eye = draw.shapely.geometry.Point(0, 0).buffer(0.2) eye_l = draw.translate(eye, -0.4, 0.4) eye_r = draw.translate(eye, 0.4, 0.4) smile = draw.shapely.geometry.Point(0, 0).buffer(0.8) cut_sq = draw.shapely.geometry.box(-1, -0.3, 1, 1) smile = draw.subtract(smile, cut_sq) frown = draw.rotate(smile, 180) frown = draw.translate(frown, 0, 0.3) frown = draw.subtract(frown, draw.shapely.geometry.Point(0, -0.8).buffer(0.7)) face = draw.subtract(face, eye_l) if self.p.happy: face = draw.subtract(face, smile) else: face = draw.subtract(face, frown) if self.p.wink: face = draw.subtract( face, draw.shapely.geometry.LineString([(0.2, 0.4), (0.6, 0.4)]).buffer(0.02)) else: face = draw.subtract(face, eye_r) face = draw.rotate(face, self.p.orientation, origin=(0, 0)) self.add_qgeometry('poly', {'Smiley': face})
class OpenToGround(QComponent): """A sample_shapes open to ground termination. Functions as a pin for auto drawing. Inherits `QComponent` class. .. image:: OpenToGround.png Default Options: * width: '10um' -- The width of the 'cpw' terminating to ground (this is merely for the purpose of generating a value to pass to the pin) * gap: '6um' -- The gap of the 'cpw' * termination_gap: '6um' -- The length of dielectric from the end of the cpw center trace to the ground. * pos_x: '0um' -- The x position of the ground termination. * pos_y: '0um' -- The y position of the ground termination. * rotation: '0' -- The direction of the termination. 0 degrees is +x, following a counter-clockwise rotation (eg. 90 is +y) * chip: 'main' -- The chip the pin should be on. * layer: '1' -- Layer the pin is on. Does not have any practical impact to the short. Values (unless noted) are strings with units included, (e.g., '30um') """ component_metadata = Dict(short_name='term', _qgeometry_table_poly='True') """Component metadata""" default_options = Dict(width='10um', gap='6um', termination_gap='6um', pos_x='0um', pos_y='0um', orientation='0', chip='main', layer='1') """Default connector options""" TOOLTIP = """A basic open to ground termination. """ def make(self): """Build the component.""" p = self.p # p for parsed parameters. Access to the parsed options. port_line = draw.LineString([(0, -p.width / 2), (0, p.width / 2)]) open_termination = draw.box(0, -(p.width / 2 + p.gap), p.termination_gap, (p.width / 2 + p.gap)) # Rotates and translates the connector polygons (and temporary port_line) polys = [open_termination, port_line] polys = draw.rotate(polys, p.orientation, origin=(0, 0)) polys = draw.translate(polys, p.pos_x, p.pos_y) [open_termination, port_line] = polys # Subtracts out ground plane on the layer its on self.add_qgeometry('poly', {'open_to_ground': open_termination}, subtract=True, layer=p.layer) # Generates the pins self.add_pin('open', port_line.coords, p.width)
class NGon(QComponent): """A n-gon polygon. Eg. n = 3 : triangle, n = infinity : circle Inherits `QComponent` class. Default Options: Convention: Values (unless noted) are strings with units included, (e.g., '30um') * n: '3' -- Number of sides of the polygon * radius: '30um' -- The radius of the circle given n=infinity * pos_x: '0um' -- The x position of the ground termination. * pos_y '0um' -- The y position of the ground termination. * rotation: '0' -- The direction of the termination. 0 degrees is +x, following a counter-clockwise rotation (eg. 90 is +y) * subtract: 'False' * helper: 'False' * chip: 'main' -- The chip the pin should be on. * layer: '1' -- Layer the pin is on. Does not have any practical impact to the short. """ default_options = Dict( n='3', radius='30um', pos_x='0um', pos_y='0um', rotation='0', subtract='False', helper='False', chip='main', layer='1', ) """Default drawing options""" def make(self): """ The make function implements the logic that creates the geoemtry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc. """ p = self.p # p for parsed parameters. Access to the parsed options. n = int(p.n) # Create the geometry # Generates a list of points n_polygon = [(p.radius * np.cos(2 * np.pi * x / n), p.radius * np.sin(2 * np.pi * x / n)) for x in range(n)] # Converts said list into a shapely polygon n_polygon = draw.Polygon(n_polygon) n_polygon = draw.rotate(n_polygon, p.rotation, origin=(0, 0)) n_polygon = draw.translate(n_polygon, p.pos_x, p.pos_y) ############################################## # add qgeometry self.add_qgeometry('poly', {'n_polygon': n_polygon}, subtract=p.subtract, helper=p.helper, layer=p.layer, chip=p.chip)
def _add_route_specific_options(self, options): """Enriches the default_options to support different types of route styles. Args: options (dict): User options that will override the defaults Return: A modified options dictionary Raises: Exception: Unsupported route type """ if self.type == "ROUTE": # all the defaults are fine as-is None elif self.type == "CPW": # add the variable to define the space between the route and the ground plane cpw_options = Dict(trace_gap='cpw_gap') if options: if "trace_gap" not in options: # user did not pass the trace_gap, so add it options.update(cpw_options) else: # user did not pass custom options, so create it to add trace_gap options["options"] = cpw_options else: raise Exception("Unsupported Route type: " + self.type + " The only supported types are CPW and route") return options
class RouteStraight(QRoute): """ Draw a straight Route connecting two pins. .. meta:: Route Straight """ component_metadata = Dict(short_name='cpw') """Component metadata""" TOOLTIP = """Draw a straight Route connecting two pins.""" def make(self): """The make function implements the logic that creates the geometry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc.""" # Set the CPW pins and add the points/directions to the lead-in/out arrays self.set_pin("start") self.set_pin("end") # Align the lead-in/out to the input options set from the user self.set_lead("start") self.set_lead("end") # Make points into elements self.make_elements(self.get_points())
class RectangleHollow(QComponent): """A single configurable square. Inherits QComponent class. Default Options: * width: '500um' * height: '300um' * pos_x: '0um' * pos_y: '0um' * rotation: '0' * subtract: 'False' * helper: 'False' * chip: 'main' * layer: '1' * inner: Dict * height: '100um' * offset_x: '40um' * offset_y: '-20um' * rotation: '15' """ default_options = Dict(width='500um', height='300um', pos_x='0um', pos_y='0um', rotation='0', subtract='False', helper='False', chip='main', layer='1', inner=dict(width='250um', height='100um', offset_x='40um', offset_y='-20um', rotation='15')) """Default drawing options""" def make(self): """The make function implements the logic that creates the geoemtry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc.""" p = self.p # p for parsed parameters. Access to the parsed options. # create the geometry rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) rec1 = draw.rectangle(p.inner.width, p.inner.height, p.pos_x + p.inner.offset_x, p.pos_y + p.inner.offset_y) rec1 = draw.rotate(rec1, p.inner.rotation) rect = draw.subtract(rect, rec1) rect = draw.rotate(rect, p.rotation) # add qgeometry self.add_qgeometry('poly', {'rect': rect}, subtract=p.subtract, helper=p.helper, layer=p.layer, chip=p.chip)
class ShortToGround(QComponent): """A sample_shapes short to ground termination. Functions as a pin for auto CPW drawing. Inherits `QComponent` class. Default Options: * width: '10um' -- The width of the 'cpw' terminating to ground (this is merely for the purpose of generating a value to pass to the pin) * pos_x: '0um' -- The x position of the ground termination. * pos_y: '0um' -- The y position of the ground termination. * rotation: '0' -- The direction of the termination. 0 degrees is +x, following a counter-clockwise rotation (eg. 90 is +y) * chip: 'main' -- The chip the pin should be on. * layer: '1' -- Layer the pin is on. Does not have any practical impact to the short. Values (unless noted) are strings with units included, (e.g., '30um') """ component_metadata = Dict(short_name='term') """Component metadata""" default_options = Dict(width='10um', pos_x='0um', pos_y='0um', orientation='0', chip='main', layer='1') """Default connector options""" TOOLTIP = """A basic short to ground termination""" def make(self): """Build the component.""" p = self.p # p for parsed parameters. Access to the parsed options. port_line = draw.LineString([(0, -p.width / 2), (0, p.width / 2)]) # Rotates and translates the connector polygons (and temporary port_line) port_line = draw.rotate(port_line, p.orientation, origin=(0, 0)) port_line = draw.translate(port_line, p.pos_x, p.pos_y) port_points = list(draw.shapely.geometry.shape(port_line).coords) #Generates the pin self.add_pin('short', port_points, p.width)
def get_chip_names(self) -> Dict: """ Returns a dict of unique chip names for ALL tables within QGeometry. In another words, for every "path" table, "poly" table ... etc, this method will search for unique chip names and return a dict of unique chip names from QGeometry table. Returns: Dict: dict with key of chip names and value of empty dict to hold things for renderers. """ chip_names = Dict() for table_name in self.design.qgeometry.get_element_types(): table = self.design.qgeometry.tables[table_name] names = table['chip'].unique().tolist() chip_names += names unique_list = list(set(chip_names)) unique_dict = Dict() for chip in unique_list: unique_dict[chip] = Dict() return unique_dict
def _init_metadata(cls) -> Dict: """Initialize default metadata dictionary. Returns: Dict: default metadata dictionary """ now = datetime.now() # current date and time return Dict(design_name='my_design', notes='', time_created=now.strftime("%m/%d/%Y, %H:%M:%S"))
class NGon(QComponent): """A n-gon polygon. Eg. n = 3 : triangle, n = infinity : circle Inherits `QComponent` class. .. image:: NGon.png .. meta:: N-Gon Polygon Default Options: Convention: Values (unless noted) are strings with units included, (e.g., '30um') * n: '3' -- Number of sides of the polygon * radius: '30um' -- The radius of the circle given n=infinity * subtract: 'False' * helper: 'False' """ default_options = Dict(n='3', radius='30um', subtract='False', helper='False') """Default drawing options""" TOOLTIP = """A n-gon polygon""" def make(self): """The make function implements the logic that creates the geoemtry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc.""" p = self.p # p for parsed parameters. Access to the parsed options. n = int(p.n) # Create the geometry # Generates a list of points n_polygon = [(p.radius * np.cos(2 * np.pi * x / n), p.radius * np.sin(2 * np.pi * x / n)) for x in range(n)] # Converts said list into a shapely polygon n_polygon = draw.Polygon(n_polygon) n_polygon = draw.rotate(n_polygon, p.orientation, origin=(0, 0)) n_polygon = draw.translate(n_polygon, p.pos_x, p.pos_y) ############################################## # add qgeometry self.add_qgeometry('poly', {'n_polygon': n_polygon}, subtract=p.subtract, helper=p.helper, layer=p.layer, chip=p.chip)
class Rectangle(QComponent): """A single configurable square. Inherits QComponent class. The class will add default_options class Dict to QComponent class before calling make. .. image:: Rectangle.png Default Options: * width: '500um' * height: '300um' * pos_x: '0um' * pos_y: '0um' * rotation: '0' * subtract: 'False' * helper: 'False' * chip: 'main' * layer: '1' """ default_options = Dict(width='500um', height='300um', pos_x='0um', pos_y='0um', rotation='0', subtract='False', helper='False', chip='main', layer='1') """Default drawing options""" TOOLTIP = """A single configurable square""" def make(self): """The make function implements the logic that creates the geoemtry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc.""" p = self.p # p for parsed parameters. Access to the parsed options. # create the geometry rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) rect = draw.rotate(rect, p.rotation) ############################################## # add qgeometry self.add_qgeometry('poly', {'rectangle': rect}, subtract=p.subtract, helper=p.helper, layer=p.layer, chip=p.chip)
def __init__(self, *args, **kwargs): """Creates a new Analysis object with a setup derived from the default_setup Dict. """ super().__init__(*args, **kwargs) self._setup, self._supported_data_labels = self._gather_from_all_children( ) # first self.clear_data() self._variables = Dict() # Keep a reference to Sweeper class. self._sweeper = None
def rename_variable(self, old_key: str, new_key: str): """Renames a variable in the variables dictionary. Preserves order. Args: old_key (str): Previous variable name new_key (str): New variable name """ keys = list(self._variables.keys()) values = list(self._variables.values()) keys[keys.index(old_key)] = new_key self._variables = Dict(zip(keys, values))
def test_qlibrary_base_get_template_options(self): """Test get_template_options in base.py.""" design = designs.DesignPlanar() expected = Dict(pos_x='0.0um', pos_y='0.0um', orientation='0.0', chip='main', layer='1') self.assertEqual(QComponent.get_template_options(design), expected) expected.update(connection_pads={}, _default_connection_pads={}) self.assertEqual(BaseQubit.get_template_options(design), expected)
class MyQComponent4(QComponent): """Demonstration3 - Straight segment with variable width/length""" default_options = Dict(width='0.5mm', height='0.1mm', gap='0.02mm', pos_x='0mm', pos_y='0mm', layer='1') """Default drawing options""" # Name prefix of component + import of renderer-specific default_options component_metadata = Dict( short_name='Trace', _qgeometry_table_path='True', #wirebonds _qgeometry_table_poly='False', _qgeometry_table_junction='False') #gds """Component metadata""" def make(self): """calculates the geometries of the QComponent""" p = self.parse_options() line = draw.LineString([(-p.width / 2, 0), (p.width / 2, 0)]) line = draw.translate(line, p.pos_x, p.pos_y) self.add_qgeometry('path', {'trace': line}, width=p.height, layer=p.layer, subtract=False) line2 = draw.LineString([((-p.width / 2) - 2 * p.gap, 0), ((p.width / 2) + 2 * p.gap, 0)]) line2 = draw.translate(line2, p.pos_x, p.pos_y) self.add_qgeometry('path', {'cut': line2}, width=p.height + 2 * p.gap, layer=p.layer, subtract=True) self.add_pin('in', line.coords[::-1], p.height, input_as_norm=True)
def __init__(self, design: 'QDesign' = None, renderer_name: str = None): """Initialize the Lumped Oscillator Model analysis. Args: design (QDesign): Pointer to the main qiskit-metal design. Used to access the QRenderer. Defaults to None. renderer_name (str, optional): Which renderer to use. Valid entries: 'q3d'. Defaults to None. """ # QAnalysis are expected to either run simulation or use pre-saved sim outputs # we use a Dict() to store the sim outputs previously saved. Its key names need # to match those found in the correspondent simulation class. self.sim = Dict() if renderer_name is None else LumpedElementsSim( design, renderer_name) super().__init__()
def clear_data(self, data_name: Union[str, list] = None): """Clear data. Can optionally specify one or more labels to delete those labels and data. Args: data_name (Union[str, list], optional): Can list specific labels to clean. Defaults to None. """ if data_name is None: self._variables = Dict() else: if isinstance(data_name, str): self._variables[data_name] = None else: for name in data_name: if name in self._variables[name]: del self._variables[name]
def run_sim( # pylint: disable=arguments-differ self, name: str = None, components: Union[list, None] = None, open_terminations: Union[list, None] = None, box_plus_buffer: bool = True) -> Tuple[str, str]: """Executes the capacitance matrix extraction. First it makes sure the tool is running. Then it does the necessary to render the design. Finally it runs the setup defined in this class. So you need to modify the setup ahead. You can modify the setup by using the methods defined in the QAnalysis super-class. After this method concludes you can inspect the output using this class properties. Args: name (str): reference name for the components selection. If None, it will use the design.name. Defaults to None. components (Union[list, None], optional): List of components to render. Defaults to None. open_terminations (Union[list, None], optional): List of tuples of pins that are open. Defaults to None. box_plus_buffer (bool, optional): Either calculate a bounding box based on the location of rendered geometries or use chip size from design class. Defaults to True. Returns: (str, str): Name of the design and name of the setup. """ # save input variables to run(). This line must be the first in the method if components is not None: argm = dict(locals()) del argm['self'] self.save_run_args(**argm) # wipe data from the previous run (if any) self.clear_data() if not self.renderer_initialized: self._initialize_renderer() renderer_design_name = self._render(name=name, solution_type='capacitive', selection=components, open_pins=open_terminations, box_plus_buffer=box_plus_buffer, vars_to_initialize=Dict()) self._analyze() return renderer_design_name, self.sim_setup_name
def populate_all_sweep(self, all_sweep: Dict, item: str, option_name: str): """Populate the Dict passed in all_sweep from QAnalysis. Args: all_sweep (Dict): Reference to hold each item which corresponds to option_name. item (str): The value of each item that we want to sweep in option_name. option_name (str): The option of QComponent that we want to sweep. """ sweep_values = Dict() sweep_values['option_name'] = option_name sweep_values['variables'] = self.parent._variables if hasattr(self.parent, 'sim'): sweep_values['sim_variables'] = self.parent.sim._variables all_sweep[item] = sweep_values
class MyQComponent2(QComponent): """Demonstration2 - Straight segment with variable width/length""" # Your knobs to modify the cell behavior default_options = Dict(width='0.5mm', height='0.1mm', pos_x='0mm', pos_y='0mm', layer='1') """Default drawing options""" def make(self): """calculates the geometries of the QComponent""" p = self.parse_options( ) # short-handle alias for the options interpreter rect = draw.rectangle(p.width, p.height, p.pos_x, p.pos_y) self.add_qgeometry('poly', {'my_polygon': rect}, layer=p.layer, subtract=False) self.add_pin('in', rect.exterior.coords[:-3:-1], p.height)
def _gather_from_all_children(cls): """From the QAnalysis core class, traverse the child classes to gather the default_setup and data_labels for each child class. For default_setup: If the same key is found twice, the youngest child will be picked. Returns: (dict, set): dict = setup, set = supported_data_labels """ setup_from_children = Dict() labels_from_children = set() parents = inspect.getmro(cls) # len-2: base.py is not expected to have default_setup and data_labels. for child in parents[len(parents) - 2::-1]: # The template default options are in the attribute `default_setup`. if hasattr(child, 'default_setup'): setup_from_children.update(deepcopy(child.default_setup)) # The data_labels are in the attribute `data_labels`. if hasattr(child, 'data_labels'): labels_from_children.update(child.data_labels) return setup_from_children, labels_from_children
class CapNInterdigital(QComponent): """Generates a two pin (+) structure comprised of a north CPW transmission line, and a south transmission line, coupled together via a finger capacitor. Such a structure can be used, as an example, for generating CPW resonators. (0,0) represents the center position of the component. Setting finger length to 0 gives a simple gap capacitor. The width of the gap capacitor is found via. (cap_width * finger_count + * cap_gap * (finger_count-1)). Inherits QComponent class. :: (0,0) N + ^ | | | | --|-----|-- | | | | | |-----|-----| | | | | + .. image:: CapNInterdigital.png Options: * north_width: '10um' -- The width of the 'north' portion of the CPW transmission line * north_gap: '6um' -- The dielectric gap of the 'north' portion of the CPW transmission line * south_width: '10um' -- The width of the 'south' portion of the CPW transmission line * south_gap: '6um' -- The dielectric gap of the 'south' portion of the CPW transmission line (also for the capacitor gap to ground) * cap_width: '10um' -- The width of the finger capacitor metal (and islands) * cap_gap: '6um' -- The width of dielectric for the capacitive coupling/fingers * cap_gap_ground: '6um' -- Width of the dielectric between the capacitor and ground * finger_length: '20um' -- The depth of the finger islands of the capacitor * finger_count: '5' -- Number of fingers in the capacitor * cap_distance: '50um' -- Distance of the north point of the capacitor from the north pin """ component_metadata = Dict(short_name='cpw', _qgeometry_table_poly='True', _qgeometry_table_path='True') """Component metadata""" #Currently setting the primary CPW length based on the coupling_length #May want it to be it's own value that the user can control? default_options = Dict(north_width='10um', north_gap='6um', south_width='10um', south_gap='6um', cap_width='10um', cap_gap='6um', cap_gap_ground='6um', finger_length='20um', finger_count='5', cap_distance='50um') """Default connector options""" TOOLTIP = """Generates a two pin (+) structure comprised of a north CPW transmission line, and a south transmission line, coupled together via a finger capacitor.""" def make(self): """Build the component.""" p = self.p N = int(p.finger_count) #Finger Capacitor cap_box = draw.rectangle(N * p.cap_width + (N - 1) * p.cap_gap, p.cap_gap + 2 * p.cap_width + p.finger_length, 0, 0) make_cut_list = [] make_cut_list.append([0, (p.finger_length) / 2]) make_cut_list.append([(p.cap_width) + (p.cap_gap / 2), (p.finger_length) / 2]) flip = -1 for i in range(1, N): make_cut_list.append([ i * (p.cap_width) + (2 * i - 1) * (p.cap_gap / 2), flip * (p.finger_length) / 2 ]) make_cut_list.append([ (i + 1) * (p.cap_width) + (2 * i + 1) * (p.cap_gap / 2), flip * (p.finger_length) / 2 ]) flip = flip * -1 cap_cut = draw.LineString(make_cut_list).buffer(p.cap_gap / 2, cap_style=2, join_style=2) cap_cut = draw.translate(cap_cut, -(N * p.cap_width + (N - 1) * p.cap_gap) / 2, 0) cap_body = draw.subtract(cap_box, cap_cut) cap_body = draw.translate( cap_body, 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) / 2) cap_etch = draw.rectangle( N * p.cap_width + (N - 1) * p.cap_gap + 2 * p.cap_gap_ground, p.cap_gap + 2 * p.cap_width + p.finger_length + 2 * p.cap_gap_ground, 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) / 2) #CPW north_cpw = draw.LineString([[0, 0], [0, -p.cap_distance]]) south_cpw = draw.LineString( [[ 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) ], [ 0, -2 * p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) ]]) #Rotate and Translate c_items = [north_cpw, south_cpw, cap_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) [north_cpw, south_cpw, cap_body, cap_etch] = c_items #Add to qgeometry tables self.add_qgeometry('path', {'north_cpw': north_cpw}, width=p.north_width, layer=p.layer) self.add_qgeometry('path', {'north_cpw_sub': north_cpw}, width=p.north_width + 2 * p.north_gap, layer=p.layer, subtract=True) self.add_qgeometry('path', {'south_cpw': south_cpw}, width=p.south_width, layer=p.layer) self.add_qgeometry('path', {'south_cpw_sub': south_cpw}, width=p.south_width + 2 * p.south_gap, layer=p.layer, subtract=True) self.add_qgeometry('poly', {'cap_body': cap_body}, layer=p.layer) self.add_qgeometry('poly', {'cap_etch': cap_etch}, layer=p.layer, subtract=True) #Add pins north_pin_list = north_cpw.coords south_pin_list = south_cpw.coords self.add_pin('north_end', points=np.array(north_pin_list[::-1]), width=p.north_width, input_as_norm=True) self.add_pin('south_end', points=np.array(south_pin_list), width=p.south_width, input_as_norm=True)
class TransmonPocket6(BaseQubit): """Transmon pocket with 6 connection pads. Inherits `BaseQubit` class Description: Create a standard pocket transmon qubit for a ground plane. Can have variable number of connection pads, up to 6. Options: Convention: Values (unless noted) are strings with units included, (e.g., '30um') Pocket: * pos_x / pos_y - where the center of the pocket should be located on chip (where the 'junction' is) * pad_gap - the distance between the two charge islands, which is also the resulting 'length' of the pseudo junction * inductor_width - width of the pseudo junction between the two charge islands (if in doubt, make the same as pad_gap). Really just for simulating in HFSS / other EM software * pad_width - the width (x-axis) of the charge island pads * pad_height - the size (y-axis) of the charge island pads * pocket_width - size of the pocket (cut out in ground) along x-axis * pocket_height - size of the pocket (cut out in ground) along y-axis * orientation - degree of qubit rotation Connector lines: * pad_gap - space between the connector pad and the charge island it is nearest to * pad_width - width (x-axis) of the connector pad * pad_height - height (y-axis) of the connector pad * pad_cpw_shift - shift the connector pad cpw line by this much away from qubit * pad_cpw_extent - how long should the pad be - edge that is parallel to pocket * cpw_width - center trace width of the CPW line * cpw_gap - dielectric gap width of the CPW line * cpw_extend - depth the connector line extends into ground (past the pocket edge) * pocket_extent - How deep into the pocket should we penetrate with the cpw connector (into the ground plane) * pocket_rise - How far up or down relative to the center of the transmon should we elevate the cpw connection point on the ground plane * loc_W / H - which 'quadrant' of the pocket the connector is set to, +/- 1 (check if diagram is correct) Sketch: Below is a sketch of the qubit :: -1 0 _________________________________ -1 |______ ____ _|_ __________| Y | |____| |___| |____| | ^ | __________________ | | | | island | | |-----> X | |__________________| | | | | | pocket x | | _________|________ | | | | | | |__________________| | | ______ | |_______|______| | |________________________________| +1 +1 .. image:: QComponent_Qubit_Transmon_Pocket.png """ #_img = 'transmon_pocket1.png' # Default drawing options default_options = Dict( chip='main', pos_x='0um', pos_y='0um', pad_gap='30um', inductor_width='20um', pad_width='455um', pad_height='90um', pocket_width='650um', pocket_height='650um', # 90 has dipole aligned along the +X axis, # while 0 has dipole aligned along the +Y axis orientation='0', _default_connection_pads=Dict( pad_gap='15um', pad_width='125um', pad_height='30um', pad_cpw_shift='0um', pad_cpw_extent='25um', cpw_width='10um', cpw_gap='6um', # : cpw_extend: how far into the ground to extend the CPW line from the coupling pads cpw_extend='100um', pocket_extent='5um', pocket_rise='0um', loc_W='+1', # width location only +-1 or 0 )) """Default drawing options""" component_metadata = Dict(short_name='Pocket', _qgeometry_table_path='True', _qgeometry_table_poly='True', _qgeometry_table_junction='True') """Component metadata""" def make(self): """Define the way the options are turned into QGeometry. The make function implements the logic that creates the geoemtry (poly, path, etc.) from the qcomponent.options dictionary of parameters, and the adds them to the design, using qcomponent.add_qgeometry(...), adding in extra needed information, such as layer, subtract, etc. """ self.make_pocket() self.make_connection_pads() def make_pocket(self): """Makes standard transmon in a pocket.""" # self.p allows us to directly access parsed values (string -> numbers) form the user option p = self.p # since we will reuse these options, parse them once and define them as variables pad_width = p.pad_width pad_height = p.pad_height pad_gap = p.pad_gap # make the pads as rectangles (shapely polygons) pad = draw.rectangle(pad_width, pad_height) pad_top = draw.translate(pad, 0, +(pad_height + pad_gap) / 2.) pad_bot = draw.translate(pad, 0, -(pad_height + pad_gap) / 2.) rect_jj = draw.LineString([(0, -pad_gap / 2), (0, +pad_gap / 2)]) # the draw.rectangle representing the josephson junction # rect_jj = draw.rectangle(p.inductor_width, pad_gap) rect_pk = draw.rectangle(p.pocket_width, p.pocket_height) # Rotate and translate all qgeometry as needed. # Done with utility functions in Metal 'draw_utility' for easy rotation/translation # NOTE: Should modify so rotate/translate accepts qgeometry, would allow for # smoother implementation. polys = [rect_jj, pad_top, pad_bot, rect_pk] polys = draw.rotate(polys, p.orientation, origin=(0, 0)) polys = draw.translate(polys, p.pos_x, p.pos_y) [rect_jj, pad_top, pad_bot, rect_pk] = polys # Use the geometry to create Metal qgeometry self.add_qgeometry('poly', dict(pad_top=pad_top, pad_bot=pad_bot)) self.add_qgeometry('poly', dict(rect_pk=rect_pk), subtract=True) # self.add_qgeometry('poly', dict( # rect_jj=rect_jj), helper=True) self.add_qgeometry('junction', dict(rect_jj=rect_jj), width=p.inductor_width) def make_connection_pads(self): """Makes standard transmon in a pocket.""" for name in self.options.connection_pads: self.make_connection_pad(name) def make_connection_pad(self, name: str): """Makes n individual connector. Args: name (str) : Name of the connector """ # self.p allows us to directly access parsed values (string -> numbers) form the user option p = self.p pc = self.p.connection_pads[name] # parser on connector options # define commonly used variables once cpw_width = pc.cpw_width cpw_extend = pc.cpw_extend pad_width = pc.pad_width pad_height = pc.pad_height pad_cpw_shift = pc.pad_cpw_shift pocket_rise = pc.pocket_rise pocket_extent = pc.pocket_extent loc_W = float(pc.loc_W) loc_W, loc_H = float(pc.loc_W), float(pc.loc_H) if float(loc_W) not in [-1., +1., 0] or float(loc_H) not in [-1., +1.]: self.logger.info( 'Warning: Did you mean to define a transmon qubit with loc_W and' ' loc_H that are not +1, -1, or 0? Are you sure you want to do this?' ) # Define the geometry # Connector pad if float(loc_W) != 0: connector_pad = draw.rectangle(pad_width, pad_height, -pad_width / 2, pad_height / 2) # Connector CPW wire connector_wire_path = draw.wkt.loads(f"""LINESTRING (\ 0 {pad_cpw_shift+cpw_width/2}, \ {pc.pad_cpw_extent} {pad_cpw_shift+cpw_width/2}, \ {(p.pocket_width-p.pad_width)/2-pocket_extent} {pad_cpw_shift+cpw_width/2+pocket_rise}, \ {(p.pocket_width-p.pad_width)/2+cpw_extend} {pad_cpw_shift+cpw_width/2+pocket_rise}\ )""") else: connector_pad = draw.rectangle(pad_width, pad_height, 0, pad_height / 2) connector_wire_path = draw.LineString( [[0, pad_height], [ 0, (p.pocket_width / 2 - p.pad_height - p.pad_gap / 2 - pc.pad_gap) + cpw_extend ]]) # Position the connector, rotate and translate objects = [connector_pad, connector_wire_path] if loc_W == 0: loc_Woff = 1 else: loc_Woff = loc_W objects = draw.scale(objects, loc_Woff, loc_H, origin=(0, 0)) objects = draw.translate( objects, loc_W * (p.pad_width) / 2., loc_H * (p.pad_height + p.pad_gap / 2 + pc.pad_gap)) objects = draw.rotate_position(objects, p.orientation, [p.pos_x, p.pos_y]) [connector_pad, connector_wire_path] = objects self.add_qgeometry('poly', {f'{name}_connector_pad': connector_pad}) self.add_qgeometry('path', {f'{name}_wire': connector_wire_path}, width=cpw_width) self.add_qgeometry('path', {f'{name}_wire_sub': connector_wire_path}, width=cpw_width + 2 * pc.cpw_gap, subtract=True) ############################################################ # add pins points = np.array(connector_wire_path.coords) self.add_pin(name, points=points[-2:], width=cpw_width, input_as_norm=True)
class RouteAnchors(QRoute): """Creates and connects a series of anchors through which the Route passes. QRoute Default Options: * pin_inputs: Dict * start_pin: Dict -- Component and pin string pair. Define which pin to start from * component: '' -- Name of component to start from, which has a pin * pin: '' -- Name of pin used for pin_start * end_pin=Dict -- Component and pin string pair. Define which pin to start from * component: '' -- Name of component to end on, which has a pin * pin: '' -- Name of pin used for pin_end * fillet: '0' * lead: Dict * start_straight: '0mm' -- Lead-in, defined as the straight segment extension from start_pin. Defaults to 0.1um. * end_straight: '0mm' -- Lead-out, defined as the straight segment extension from end_pin. Defaults to 0.1um. * start_jogged_extension: '' -- Lead-in, jogged extension of lead-in. Described as list of tuples * end_jogged_extension: '' -- Lead-out, jogged extension of lead-out. Described as list of tuples * total_length: '7mm' * trace_width: 'cpw_width' -- Defines the width of the line. Defaults to 'cpw_width'. Default Options: * anchors: OrderedDict -- Intermediate anchors only; doesn't include endpoints * advanced: Dict * avoid_collision: 'false' -- true/false, defines if the route needs to avoid collisions. Defaults to 'false'. """ component_metadata = Dict(short_name='cpw') """Component metadata""" default_options = Dict( anchors=OrderedDict( ), # Intermediate anchors only; doesn't include endpoints # Example: {1: np.array([x1, y1]), 2: np.array([x2, y2])} # startpin -> startpin + leadin -> anchors -> endpin + leadout -> endpin advanced=Dict(avoid_collision='false')) """Default options""" TOOLTIP = """Creates and connects a series of anchors through which the Route passes.""" from shapely.ops import cascaded_union from matplotlib import pyplot as plt import geopandas as gpd from shapely.geometry import CAP_STYLE, JOIN_STYLE def unobstructed_close_up(self, segment: list, component_name: str) -> bool: """Checks whether the given component's perimeter intersects or overlaps a given segment. Args: segment (list): 2 vertices, in the form [np.array([x0, y0]), np.array([x1, y1])] component_name (str): Alphanumeric component name Returns: bool: True is no obstacles """ # transform path to polygons paths_converted = [] paths = self.design.components[component_name].qgeometry_table('path') for _, row in paths.iterrows(): paths_converted.append(row['geometry'].buffer( row['width'] / 2, cap_style=CAP_STYLE.flat)) # merge all the polygons polygons = self.design.components[component_name].qgeometry_list( 'poly') boundary = gpd.GeoSeries(cascaded_union(polygons + paths_converted)) boundary_coords = list(boundary.geometry.exterior[0].coords) if any( intersecting(segment[0], segment[1], boundary_coords[i], boundary_coords[i + 1]) for i in range(len(boundary_coords) - 1)): # At least 1 intersection with the actual component contour; do not proceed! return False # All clear, no intersections return True def unobstructed(self, segment: list) -> bool: """Check that no component's bounding box in self.design intersects or overlaps a given segment. Args: segment (list): 2 vertices, in the form [np.array([x0, y0]), np.array([x1, y1])] Returns: bool: True is no obstacles """ # assumes rectangular bounding boxes for component in self.design.components: if component == self.name: continue xmin, ymin, xmax, ymax = self.design.components[ component].qgeometry_bounds() # p, q, r, s are corner coordinates of each bounding box p, q, r, s = [ np.array([xmin, ymin]), np.array([xmin, ymax]), np.array([xmax, ymin]), np.array([xmax, ymax]) ] if any( intersecting(segment[0], segment[1], k, l) for k, l in [(p, q), (p, r), (r, s), (q, s)]): # At least 1 intersection with the component bounding box. Check the actual contour. if not self.unobstructed_close_up(segment, component): # At least 1 intersection with the actual component contour; do not proceed! return False # All clear, no intersections return True def connect_simple(self, start_pt: QRoutePoint, end_pt: QRoutePoint) -> np.ndarray: """Try connecting start and end with single or 2-segment/S-shaped CPWs if possible. Args: start_pt (QRoutePoint): QRoutePoint of the start end_pt (QRoutePoint): QRoutePoint of the end Returns: List of vertices of a CPW going from start to end Raises: QiskitMetalDesignError: If the connect_simple() has failed. """ avoid_collision = self.parse_options().advanced.avoid_collision start_direction = start_pt.direction start = start_pt.position end_direction = end_pt.direction end = end_pt.position # end_direction originates strictly from endpoint + leadout (NOT intermediate stopping anchors) self.assign_direction_to_anchor(start_pt, end_pt) stop_direction = end_pt.direction if (start[0] == end[0]) or (start[1] == end[1]): # Matching x or y coordinates -> check if endpoints can be connected with a single segment if mao.dot(start_direction, end - start) >= 0: # Start direction and end - start for CPW must not be anti-aligned if (end_direction is None) or (mao.dot(end - start, end_direction) <= 0): # If leadout + end has been reached, the single segment CPW must not be aligned with its direction return np.empty((0, 2), float) else: # If the endpoints don't share a common x or y value: # designate them as 2 corners of an axis aligned rectangle # and check if both start and end directions are aligned with # the displacement vectors between start/end and # either of the 2 remaining corners ("perfect alignment"). corner1 = np.array([start[0], end[1]]) # x coordinate matches with start corner2 = np.array([end[0], start[1]]) # x coordinate matches with end if avoid_collision: # Check for collisions at the outset to avoid repeat work startc1end = bool( self.unobstructed([start, corner1]) and self.unobstructed([corner1, end])) startc2end = bool( self.unobstructed([start, corner2]) and self.unobstructed([corner2, end])) else: startc1end = startc2end = True if (mao.dot(start_direction, corner1 - start) > 0) and startc1end: # corner1 is "in front of" the start_pt if (end_direction is None) or (mao.dot(end_direction, corner1 - end) >= 0): # corner1 is also "in front of" the end_pt return np.expand_dims(corner1, axis=0) elif (mao.dot(start_direction, corner2 - start) > 0) and startc2end: # corner2 is "in front of" the start_pt if (end_direction is None) or (mao.dot(end_direction, corner2 - end) >= 0): # corner2 is also "in front of" the end_pt return np.expand_dims(corner2, axis=0) # In notation below, corners 3 and 4 correspond to # the ends of the segment bisecting the longer rectangle formed by start and end # while the segment formed by corners 5 and 6 bisect the shorter rectangle if stop_direction[ 0]: # "Wide" rectangle -> vertical middle segment is more natural corner3 = np.array([(start[0] + end[0]) / 2, start[1]]) corner4 = np.array([(start[0] + end[0]) / 2, end[1]]) corner5 = np.array([start[0], (start[1] + end[1]) / 2]) corner6 = np.array([end[0], (start[1] + end[1]) / 2]) else: # "Tall" rectangle -> horizontal middle segment is more natural corner3 = np.array([start[0], (start[1] + end[1]) / 2]) corner4 = np.array([end[0], (start[1] + end[1]) / 2]) corner5 = np.array([(start[0] + end[0]) / 2, start[1]]) corner6 = np.array([(start[0] + end[0]) / 2, end[1]]) if avoid_collision: startc3c4end = bool( self.unobstructed([start, corner3]) and self.unobstructed([corner3, corner4]) and self.unobstructed([corner4, end])) startc5c6end = bool( self.unobstructed([start, corner5]) and self.unobstructed([corner5, corner6]) and self.unobstructed([corner6, end])) else: startc3c4end = startc5c6end = True if (mao.dot(start_direction, stop_direction) < 0) and (mao.dot( start_direction, corner3 - start) > 0) and startc3c4end: if (end_direction is None) or (mao.dot(end_direction, corner4 - end) > 0): # Perfectly aligned S-shaped CPW return np.vstack((corner3, corner4)) # Relax constraints and check if imperfect 2-segment or S-segment works, # where "imperfect" means 1 or more dot products of directions # between successive segments is 0; otherwise return an empty list if (mao.dot(start_direction, corner1 - start) >= 0) and startc1end: if (end_direction is None) or (mao.dot(end_direction, corner1 - end) >= 0): return np.expand_dims(corner1, axis=0) if (mao.dot(start_direction, corner2 - start) >= 0) and startc2end: if (end_direction is None) or (mao.dot(end_direction, corner2 - end) >= 0): return np.expand_dims(corner2, axis=0) if (mao.dot(start_direction, corner3 - start) >= 0) and startc3c4end: if (end_direction is None) or (mao.dot(end_direction, corner4 - end) >= 0): return np.vstack((corner3, corner4)) if (mao.dot(start_direction, corner5 - start) >= 0) and startc5c6end: if (end_direction is None) or (mao.dot(end_direction, corner6 - end) >= 0): return np.vstack((corner5, corner6)) raise QiskitMetalDesignError( "connect_simple() has failed. This might be due to one of two reasons. " f"1. Either one of the start point {start} or the end point {end} " "provided are inside the bounding box of another QComponent. " "Please move the point, or setup a \"lead\" to exit the QComponent area. " "2. none of the 4 routing possibilities of this algorithm " "(^|_, ^^|, __|, _|^) can complete. Please use Pathfinder instead") def free_manhattan_length_anchors(self): """Computes the free-flight manhattan distance between start_pt and end_pt passing through all of the given anchor points. Returns: float: Total length connecting all points in order """ anchors = self.parse_options().anchors reference = [self.head.get_tip().position] reference.extend(list(anchors.values())) reference.append(self.tail.get_tip().position) length = 0 for i in range(1, len(reference)): length += abs(reference[i][0] - reference[i - 1][0]) + abs(reference[i][1] - reference[i - 1][1]) return length def trim_pts(self): """Crops the sequence of points to concatenate. For example, if a segment between two anchors has no points, then the segment is eliminated (only anchor points will do). Modified directly the self.intermediate_pts, thus nothing is returned. """ if isinstance(self.intermediate_pts, Mapping): keys_to_delete = set() for key, value in self.intermediate_pts.items(): if value is None: keys_to_delete.add(key) try: # value is a list if not value: keys_to_delete.add(key) except ValueError: # value is a numpy if not value.size: keys_to_delete.add(key) for key in keys_to_delete: del self.intermediate_pts[key] def make(self): """Generates path from start pin to end pin.""" p = self.parse_options() anchors = p.anchors # Set the CPW pins and add the points/directions to the lead-in/out arrays self.set_pin("start") self.set_pin("end") # Align the lead-in/out to the input options set from the user start_point = self.set_lead("start") end_point = self.set_lead("end") self.intermediate_pts = OrderedDict() for arc_num, coord in anchors.items(): arc_pts = self.connect_simple(self.get_tip(), QRoutePoint(coord)) if arc_pts is None: self.intermediate_pts[arc_num] = [coord] else: self.intermediate_pts[arc_num] = np.concatenate( [arc_pts, [coord]], axis=0) arc_pts = self.connect_simple(self.get_tip(), end_point) if arc_pts is not None: self.intermediate_pts[len(anchors)] = np.array(arc_pts) # concatenate all points, transforming the dictionary into a single numpy array self.trim_pts() self.intermediate_pts = np.concatenate(list( self.intermediate_pts.values()), axis=0) # Make points into elements self.make_elements(self.get_points())
class CPWTFingerCap(QComponent): """Generates a three pin (+) structure comprised of a primary two pin CPW transmission line, and a secondary one pin neighboring CPW transmission line that is capacitively coupled to the primary. Such a structure can be used, as an example, for generating CPW resonator hangars off of a transmission line. (0,0) represents the center position of the component. Setting finger length to 0 gives a simple gap capacitor. Inherits QComponent class. :: (0,0) +--------------------------+ | --|-----|-- | | | | | |-----|-----| | | | | + Options: * prime_width: '10um' -- The width of the trace of the two pin CPW transmission line * prime_gap: '6um' -- The dielectric gap of the two pin CPW transmission line * second_width: '10um' -- The width of the trace of the one pin CPW transmission line * second_gap: '6um' -- The dielectric gap of the one pin CPW transmission line (also for the capacitor) * cap_gap: '6um' -- The width of dielectric for the capacitive coupling (default same as second_gap) * cap_width: '10um' -- The width of the finger capacitor (default same as second) * finger_length: '20um' -- The depth of the charge islands of the capacitor * finger_count: '5' -- Number of fingers in the capacitor * cap_distance: '50um' -- Distance of capacitor from center transmission line * pos_x/_y: '0um' -- The x/y position of the center of the primary transmission line. * rotation: '0' -- The direction of the primary transmission line. 0 degrees is +x, following a counter-clockwise rotation (eg. 90 is +y) * chip: 'main' -- The chip the capacitor should be on. * layer: '1' -- Layer the capacitor is on. """ component_metadata = Dict(short_name='cpw', _qgeometry_table_poly='True', _qgeometry_table_path='True') """Component metadata""" #Currently setting the primary CPW length based on the coupling_length #May want it to be it's own value that the user can control? default_options = Dict(prime_width='10um', prime_gap='6um', second_width='10um', second_gap='6um', cap_gap='6um', cap_width='10um', finger_length='20um', finger_count='5', cap_distance='50um', pos_x='0um', pos_y='0um', orientation='0', chip='main', layer='1') """Default connector options""" def make(self): """Build the component.""" p = self.p N = int(p.finger_count) prime_cpw_length = p.cap_width * 2 * N #Primary CPW prime_cpw = draw.LineString([[-prime_cpw_length / 2, 0], [prime_cpw_length / 2, 0]]) #Finger Capacitor cap_box = draw.rectangle(N * p.cap_width + (N - 1) * p.cap_gap, p.cap_gap + 2 * p.cap_width + p.finger_length, 0, 0) make_cut_list = [] make_cut_list.append([0, (p.finger_length) / 2]) make_cut_list.append([(p.cap_width) + (p.cap_gap / 2), (p.finger_length) / 2]) flip = -1 for i in range(1, N): make_cut_list.append([ i * (p.cap_width) + (2 * i - 1) * (p.cap_gap / 2), flip * (p.finger_length) / 2 ]) make_cut_list.append([ (i + 1) * (p.cap_width) + (2 * i + 1) * (p.cap_gap / 2), flip * (p.finger_length) / 2 ]) flip = flip * -1 cap_cut = draw.LineString(make_cut_list).buffer(p.cap_gap / 2, cap_style=2, join_style=2) cap_cut = draw.translate(cap_cut, -(N * p.cap_width + (N - 1) * p.cap_gap) / 2, 0) cap_body = draw.subtract(cap_box, cap_cut) cap_body = draw.translate( cap_body, 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) / 2) cap_etch = draw.rectangle( N * p.cap_width + (N - 1) * p.cap_gap + 2 * p.second_gap, p.cap_gap + 2 * p.cap_width + p.finger_length + 2 * p.second_gap, 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) / 2) #Secondary CPW second_cpw_top = draw.LineString([[0, -p.prime_width / 2], [0, -p.cap_distance]]) second_cpw_bottom = draw.LineString( [[ 0, -p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) ], [ 0, -2 * p.cap_distance - (p.cap_gap + 2 * p.cap_width + p.finger_length) ]]) #Rotate and Translate c_items = [ prime_cpw, second_cpw_top, second_cpw_bottom, cap_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) [prime_cpw, second_cpw_top, second_cpw_bottom, cap_body, cap_etch] = c_items #Add to qgeometry tables self.add_qgeometry('path', {'prime_cpw': prime_cpw}, width=p.prime_width, layer=p.layer) self.add_qgeometry('path', {'prime_cpw_sub': prime_cpw}, width=p.prime_width + 2 * p.prime_gap, subtract=True, layer=p.layer) self.add_qgeometry('path', { 'second_cpw_top': second_cpw_top, 'second_cpw_bottom': second_cpw_bottom }, width=p.second_width, layer=p.layer) self.add_qgeometry('path', { 'second_cpw_top_sub': second_cpw_top, 'second_cpw_bottom_sub': second_cpw_bottom }, width=p.second_width + 2 * p.second_gap, subtract=True, layer=p.layer) self.add_qgeometry('poly', {'cap_body': cap_body}, layer=p.layer) self.add_qgeometry('poly', {'cap_etch': cap_etch}, layer=p.layer, subtract=True) #Add pins prime_pin_list = prime_cpw.coords second_pin_list = second_cpw_bottom.coords self.add_pin('prime_start', points=np.array(prime_pin_list[::-1]), width=p.prime_width, input_as_norm=True) self.add_pin('prime_end', points=np.array(prime_pin_list), width=p.prime_width, input_as_norm=True) self.add_pin('second_end', points=np.array(second_pin_list), width=p.second_width, input_as_norm=True)