Source code for qiskit_metal.qlibrary.tlines.meandered

# -*- 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.

from typing import List, Tuple, Union

from numpy.linalg import norm

import numpy as np
from qiskit_metal import Dict
from qiskit_metal.toolbox_metal.parsing import is_true
from qiskit_metal.qlibrary.core import QRoute, QRoutePoint
from qiskit_metal.toolbox_metal import math_and_overrides as mao
from qiskit_metal.toolbox_metal.exceptions import QiskitMetalDesignError


[docs] class RouteMeander(QRoute): """Implements a simple CPW, with a single meander. The base `CPW meandered` class. Inherits `QRoute` class .. meta:: Route Meander 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: * meander: Dict * spacing: '200um' -- Minimum spacing between adjacent meander curves. Defaults to 200um. * asymmetry='0um' -- offset between the center-line of the meander and the center-line that stretches from the tip of lead-in to the x (or y) coordinate of the tip of the lead-out. Defaults to '0um'. * snap: 'true' * prevent_short_edges: 'true' """ component_metadata = Dict(short_name='cpw') """Component metadata""" default_options = Dict(meander=Dict(spacing='200um', asymmetry='0um'), snap='true', prevent_short_edges='true') """Default options""" TOOLTIP = """Implements a simple CPW, with a single meander."""
[docs] 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.""" # parsed options snap = is_true(self.p.snap) # 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 meander_start_point = self.set_lead("start") meander_end_point = self.set_lead("end") # approximate length needed for the meander self._length_segment = self.p.total_length - (self.head.length + self.tail.length) arc_pts = self.connect_meandered(meander_start_point, meander_end_point) self.intermediate_pts = arc_pts self.intermediate_pts = self.adjust_length( self.p.total_length - self.length, arc_pts, meander_start_point, meander_end_point) # Make points into elements self.make_elements(self.get_points())
[docs] def connect_meandered(self, start_pt: QRoutePoint, end_pt: QRoutePoint) -> np.ndarray: """Meanders using a fixed length and fixed spacing. Args: start_pt (QRoutePoint): QRoutePoint of the start end_pt (QRoutePoint): QRoutePoint of the end Returns: np.ndarray: Array of points Adjusts the width of the meander: * Includes the start but not the given end point * If it cannot meander just returns the initial start point """ ################################################################ # Setup # Parameters meander_opt = self.p.meander spacing = meander_opt.spacing # Horizontal spacing between meanders asymmetry = meander_opt.asymmetry snap = is_true(self.p.snap) # snap to xy grid prevent_short_edges = is_true(self.p.prevent_short_edges) # take care of anchors (do not have set directions) anchor_lead = 0 if end_pt.direction is None: # end_direction originates strictly from endpoint + leadout (NOT intermediate stopping anchors) self.assign_direction_to_anchor(start_pt, end_pt) anchor_lead = spacing # Meander length length_meander = self._length_segment if self.p.snap: # handle y distance length_meander -= 0 # (end.position - endm.position)[1] # Coordinate system (example: x to the right => sideways up) forward, sideways = self.get_unit_vectors(start_pt, end_pt, snap) # Calculate lengths and meander number dist = end_pt.position - start_pt.position if snap: length_direct = abs(norm(mao.dot( dist, forward))) # in the vertical direction length_sideways = abs(norm(mao.dot( dist, sideways))) # in the orthogonal direction else: length_direct = norm(dist) length_sideways = 0 # Breakup into sections meander_number = np.floor(length_direct / spacing) if meander_number < 1: self.logger.info(f'Zero meanders for {self.name}') return np.empty((0, 2), float) # The start and end points can have 4 directions each. Depending on the direction # there might be not enough space for all the meanders, thus here we adjust # meander_number w.r.t. what the start and end points "directionality" allows if mao.round( mao.dot(start_pt.direction, sideways) * mao.dot(end_pt.direction, sideways)) > 0 and (meander_number % 2) == 0: # even meander_number is no good if roots have same orientation (w.r.t sideway) meander_number -= 1 elif mao.round( mao.dot(start_pt.direction, sideways) * mao.dot(end_pt.direction, sideways)) < 0 and (meander_number % 2) == 1: # odd meander_number is no good if roots have opposite orientation (w.r.t sideway) meander_number -= 1 # should the first meander go sideways or counter sideways? start_meander_direction = mao.dot(start_pt.direction, sideways) end_meander_direction = mao.dot(end_pt.direction, sideways) if start_meander_direction > 0: # sideway direction first_meander_sideways = True elif start_meander_direction < 0: # opposite to sideway direction first_meander_sideways = False else: if end_meander_direction > 0: # sideway direction first_meander_sideways = ((meander_number % 2) == 1) elif end_meander_direction < 0: # opposite to sideway direction first_meander_sideways = ((meander_number % 2) == 0) else: # either direction is fine, so let's just pick one first_meander_sideways = True # length to distribute on the meanders (excess w.r.t a straight line between start and end) length_excess = (length_meander - length_direct - 2 * abs(asymmetry)) # how much meander offset from center-line is needed to accommodate the length_excess (perpendicular length) length_perp = max(0, length_excess / (meander_number * 2.)) # USES ROW Vectors # const vec. of unit normals middle_points = [forward] * int(meander_number + 1) # index so to multiply other column - creates a column vector scale_bys = spacing * np.arange(int(meander_number + 1))[:, None] # multiply each one in a linear chain fashion fwd middle_points = scale_bys * middle_points ''' middle_points = array([ [0. , 0. ], [0.2, 0. ], [0.4, 0. ], [0.6, 0. ], [0.8, 0. ], [1. , 0. ]]) ''' ################################################################ # Calculation # including start and end points - there is no overlap in points # root_pts = np.concatenate([middle_points, # end.position[None, :]], # convert to row vectors # axis=0) side_shift_vecs = np.array([sideways * length_perp] * len(middle_points)) asymmetry_vecs = np.array([sideways * asymmetry] * len(middle_points)) root_pts = middle_points + asymmetry_vecs top_pts = root_pts + side_shift_vecs bot_pts = root_pts - side_shift_vecs ################################################################ # Combine points # Meanest part of the meander # Add 2 for the lead and end points in the cpw from # pts will have to store properly alternated top_pts and bot_pts # it will also store right-most root_pts (end) # 2 points from top_pts and bot_pts will be dropped for a complete meander pts = np.zeros((len(top_pts) + len(bot_pts) + 1 - 2, 2)) # need to add the last root_pts in, because there could be a left-over non-meandered segment pts[-1, :] = root_pts[-1, :] idx_side1_meander, odd = self.get_index_for_side1_meander(len(root_pts)) idx_side2_meander = 2 + idx_side1_meander[:None if odd else -2] if first_meander_sideways: pts[idx_side1_meander, :] = top_pts[:-1 if odd else None] pts[idx_side2_meander, :] = bot_pts[1:None if odd else -1] else: pts[idx_side1_meander, :] = bot_pts[:-1 if odd else None] pts[idx_side2_meander, :] = top_pts[1:None if odd else -1] pts += start_pt.position # move to start position if snap: if ((mao.dot(start_pt.direction, end_pt.direction) < 0) and (mao.dot(forward, start_pt.direction) <= 0)): # pins are pointing opposite directions and diverging # the last root_pts need to be sideways aligned with the end.position point # and forward aligned with the previous meander point pts[-1, abs(forward[0])] = pts[-2, abs(forward[0])] pts[-1, abs(forward[0]) - 1] = end_pt.position[abs(forward[0]) - 1] else: # the last root_pts need to be forward aligned with the end.position point pts[-1, abs(forward[0])] = end_pt.position[abs(forward[0])] # and if the last root_pts ends outside the CPW amplitude on the side where the last meander is # then the last meander needs to be locked on it as well if (self.issideways(pts[-1], pts[-3], pts[-2]) and self.issideways(pts[-2], root_pts[0]+start_pt.position, root_pts[-1]+start_pt.position))\ or (not self.issideways(pts[-1], pts[-3], pts[-2]) and not self.issideways(pts[-2], root_pts[0]+start_pt.position, root_pts[-1]+start_pt.position)): pts[-2, abs(forward[0])] = end_pt.position[abs(forward[0])] pts[-3, abs(forward[0])] = end_pt.position[abs(forward[0])] if abs(asymmetry) > abs(length_perp): if not ((mao.dot(start_pt.direction, end_pt.direction) < 0) and (mao.dot(forward, start_pt.direction) <= 0)): # pins are "not" pointing opposite directions and diverging if start_meander_direction * asymmetry < 0: # sideway direction pts[0, abs(forward[0])] = start_pt.position[abs(forward[0])] pts[1, abs(forward[0])] = start_pt.position[abs(forward[0])] if end_meander_direction * asymmetry < 0: # opposite sideway direction pts[-2, abs(forward[0])] = end_pt.position[abs(forward[0])] pts[-3, abs(forward[0])] = end_pt.position[abs(forward[0])] # Adjust the meander to eliminate the terminating jog (dogleg) if prevent_short_edges: x2fillet = 2 * self.p.fillet # adjust the tail first # the meander algorithm adds a final point in line with the tail, to cope with left-over # this extra point needs to be moved or not, depending on the tail tip direction if abs(mao.dot(end_pt.direction, sideways)) > 0: skippoint = 0 else: skippoint = 1 if 0 < abs(mao.round(end_pt.position[0] - pts[-1, 0])) < x2fillet: pts[-1 - skippoint, 0 - skippoint] = end_pt.position[0 - skippoint] pts[-2 - skippoint, 0 - skippoint] = end_pt.position[0 - skippoint] if 0 < abs(mao.round(end_pt.position[1] - pts[-1, 1])) < x2fillet: pts[-1 - skippoint, 1 - skippoint] = end_pt.position[1 - skippoint] pts[-2 - skippoint, 1 - skippoint] = end_pt.position[1 - skippoint] # repeat for the start. here we do not have the extra point if 0 < abs(mao.round(start_pt.position[0] - pts[0, 0])) < x2fillet: pts[0, 0] = start_pt.position[0] pts[1, 0] = start_pt.position[0] if 0 < abs(mao.round(start_pt.position[1] - pts[0, 1])) < x2fillet: pts[0, 1] = start_pt.position[1] pts[1, 1] = start_pt.position[1] return pts
[docs] def adjust_length(self, delta_length, pts, start_pt: QRoutePoint, end_pt: QRoutePoint) -> np.ndarray: """Edits meander points to redistribute the length slacks accrued with the various local adjustments It should be run after self.pts_intermediate is completely defined Inputs are however specific to the one meander segment Assumption is that pts is always a sequence of paired points, each corresponds to one meander 180deg curve The pts is typically an odd count since the last point is typically used to anchor the left-over length, therefore this code supports both odd and even cases, separately. For even it assumes all points are in paired. Args: delta_length (delta_length): slack/excess length to distribute on the pts pts (np.array): intermediate points of meander. pairs, except last point (2,2,...,2,1) start_pt (QRoutePoint): QRoutePoint of the start end_pt (QRoutePoint): QRoutePoint of the end Returns: np.ndarray: Array of points """ # the adjustment length has to be computed in the main or in other method # considering entire route (Could include the corner fillet) if len(pts) <= 3: # not a meander return pts # is it an even or odd count of points? term_point = len(pts) % 2 # recompute direction snap = is_true(self.p.snap) # snap to xy grid forward, sideways = self.get_unit_vectors(start_pt, end_pt, snap) # recompute meander_sideways if mao.cross(pts[1] - pts[0], pts[2] - pts[1]) < 0: first_meander_sideways = True else: first_meander_sideways = False if mao.cross(pts[-2 - term_point] - pts[-1 - term_point], pts[-3 - term_point] - pts[-2 - term_point]) < 0: last_meander_sideways = False else: last_meander_sideways = True # which points need to receive the shift? # 1. initialize the shift vector to 1 (1 = will receive shift) adjustment_vector = np.ones(len(pts)) # 2. switch shift direction depending on sideways or not if first_meander_sideways: adjustment_vector[2::4] *= -1 adjustment_vector[3::4] *= -1 else: adjustment_vector[::4] *= -1 adjustment_vector[1::4] *= -1 # 3. suppress shift for points that can cause short edges # calculate thresholds for suppression of short edges (short edge = not long enough for set fillet) fillet_shift = sideways * self.p.fillet start_pt_adjusted_up = start_pt.position + fillet_shift start_pt_adjusted_down = start_pt.position - fillet_shift end_pt_adjusted_up = end_pt.position + fillet_shift end_pt_adjusted_down = end_pt.position - fillet_shift # if start_pt.position is below axes + shift - 2xfillet & first_meander_sideways if first_meander_sideways and not self.issideways( start_pt_adjusted_up, pts[0], pts[1]): pass # if start_pt.position is above axes - shift + 2xfillet & not first_meander_sideways elif not first_meander_sideways and self.issideways( start_pt_adjusted_down, pts[0], pts[1]): pass else: # else block first mender adjustment_vector[:2] = [0, 0] # if end_pt.position is below axes + shift - 2xfillet & last_meander_sideways if last_meander_sideways and not self.issideways( end_pt_adjusted_up, pts[-2 - term_point], pts[-1 - term_point]): pass # if end_pt.position is above axes - shift + 2xfillet & not last_meander_sideways elif not last_meander_sideways and self.issideways( end_pt_adjusted_down, pts[-2 - term_point], pts[-1 - term_point]): pass else: # else block last mender adjustment_vector[-2 - term_point:-term_point] = [0, 0] not_a_meander = 0 if term_point: # means that pts count is a odd number # thus needs to disable shift on the termination point... adjustment_vector[-1] = 0 # ...unless the last point is anchored to the last meander curve if start_pt.direction is not None and end_pt.direction is not None: if ((mao.dot(start_pt.direction, end_pt.direction) < 0) and (mao.dot(forward, start_pt.direction) <= 0)): # pins are pointing opposite directions and diverging, thus keep consistency adjustment_vector[-1] = adjustment_vector[-2] if adjustment_vector[-1]: # the point in between needs to be shifted, but it will not contribute to length change # therefore the total length distribution (next step) should ignore it. not_a_meander = 1 # Finally, divide the slack amongst all points... sideways_adjustment = sideways * ( delta_length / (np.count_nonzero(adjustment_vector) - not_a_meander)) pts = pts + sideways_adjustment[ np.newaxis, :] * adjustment_vector[:, np.newaxis] return pts
[docs] @staticmethod def get_index_for_side1_meander(num_root_pts: int): """Get the indices. Args: num_root_pts (list): List of points Returns: tuple: Tuple of indices """ num_2pts, odd = divmod(num_root_pts, 2) x = np.array(range(num_2pts), dtype=int) * 4 z = np.zeros(num_2pts * 2, dtype=int) z[::2] = x z[1::2] = x + 1 return z, odd
[docs] def issideways(self, point, seg_point_a, seg_point_b): return mao.cross(point - seg_point_a, seg_point_b - seg_point_a) < 0