Source code for squadds.components.airbridge.airbridge_generator

from decimal import Decimal

import numpy as np
from qiskit_metal import Dict

from .airbridge import Airbridge


[docs] class AirbridgeGenerator(): ''' The Airbrdige_Generator class generates a set of airbridges given a CPW. NOTE TO USER: * These QComponents should not be rendered into Ansys for simulation, instead these are to be exported to GDS for fabrication. * I recommend calling this right before you export to GDS, as this process tends to slow down Metal's GUI. * Input: * design: (qiskit_metal.design) * target_comps: (list of QComponent) -- which CPW do you want to generate airbridges on? * TODO: if target_cpw == None, default to all CPWs * crossover_length: (float, in units mm) -- The length of the bridge (Defaults to 27um) Parameters for airbridge placement: * min_spacing: (float, in units mm) -- Bridge Minimum Spacing (Defaults to 5um) * pitch: (float, in units mm) -- Bridge Pitch, spacing between the centers of each bridge (Defaults to 70um) ''' def __init__(self, design, target_comps = [], crossover_length=[0.027], min_spacing=0.005, pitch=0.070, add_curved_ab=True): ''' Places all airbridges for a specific CPW given a minimum spacing and pitch. ''' self.design = design self.target_comps = target_comps self.crossover_length = crossover_length # in units of mm self.min_spacing = min_spacing # in units of mm self.pitch = pitch # in units of mm self.add_curved_ab = add_curved_ab self.coordinates = [] # Places the airbridges self.airbridges = [] counter = 0 if (len(target_comps) == 0): # If we get default target_cpws, get all CPWs in design pass else: # Use the specified CPWs in the given list for i, target_cpw in enumerate(self.target_comps): coordinates = self.find_ab_placement(target_cpw) self.coordinates.append(coordinates) for coord in coordinates: try: name = "AB_{}".format(counter) airbridge_qcomponent = self.make_single_ab(coord, name, crossover_length[i]) self.airbridges.append(airbridge_qcomponent) counter += 1 except: pass self.cleanup()
[docs] def make_single_ab(self,coord,name,crossover_length=0.027): ''' Tells Qiskit to add a single airbridge into your design Input: * coord (tuple) -- formatted (x,y,orientation,modulated crossover_length) * name (string) -- Name this airbridge. Note: if you're making multiple, you'll want to increment the name * crossover_length (float) -- The length of the bridge (Defaults to 27um) ''' x = coord[0] y = coord[1] orientation = coord[2] + 90 options = Dict(pos_x = x, pos_y = y, orientation = orientation, crossover_length = crossover_length) if coord[3] != None: options.crossover_length = coord[3] try: airbridge_qcomponent = Airbridge(self.design, name, options=options) return airbridge_qcomponent except Exception as e: self.logger.error(f"Error generating airbridge '{name}': {str(e)}") return None
[docs] def get_all_airbridges(self): """ Returns all airbridges generated by the AirbridgeGenerator. Returns: list: A list of all airbridges generated by the AirbridgeGenerator. """ return self.airbridges
[docs] def validate_airbridge(self, airbridge): """ Validates the generated airbridge to ensure it fits properly within the desired component. Args: airbridge (Airbridge): The generated airbridge component to validate. Raises: ValueError: If the airbridge does not meet the validation criteria. """ # Implement validation logic based on your specific requirements # For example, check the airbridge's dimensions, position, and overlap with other components # Raise a ValueError if the airbridge does not meet the validation criteria couplers = self.get_non_overlap_zones() for coupler in couplers: try: if self.is_overlapping_with_coupler(airbridge, coupler): self.design.delete_component(airbridge.name) return except: pass else: continue
[docs] def get_corner_bounds(self, corner_point, fillet): """ Calculates the bounding box of a corner based on the corner point and fillet. Args: corner_point (tuple): The (x, y) coordinates of the corner point. fillet (float): The fillet radius of the corner. Returns: tuple: The bounding box of the corner in the format (min_x, min_y, max_x, max_y). """ x, y = corner_point return (x - fillet, y - fillet, x + fillet, y + fillet)
[docs] def find_ab_placement(self, target_cpw, precision=12): ''' Determines where to place the wirebonds given a CPW Inputs: * precision: (int) -- How precise did you define your CPWs? This parameter is meant to take care of floating point errors. References # of decimal points relative to millimeters. Outputs: Where the airbridges should be placed given set up Data structure is in the form of list of tuples [(x0, y0, theta0, new_crossover_length0), (x1, y1, theta1, new_crossover_length1), ..., (x_n, y_n, theta_n, new_crossover_length_n)] Units: - x, y, new_crossover_length are in mm - theta is in degrees ''' 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(pos_i[0],precision) y0 = round(pos_i[1],precision) xf = round(pos_f[0],precision) yf = round(pos_f[1],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 * self.min_spacing # Now implement logic to uphold the design rules ## Determine what min_spacing should be. It must be >= 0.005 (5um) if fillet > self.min_spacing: lprime = mag_dl - 2 * fillet else: lprime = mag_dl - 2 * self.min_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 * self.pitch): n += 1 mu_x = (xf + x0)/2 mu_y = (yf + y0)/2 x = np.array([i * self.pitch * np.cos(theta) for i in range(n)]) y = np.array([i * self.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), None)) #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 self.add_curved_ab: 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(pos_i[0],precision) y0 = round(pos_i[1],precision) xf = round(pos_f[0],precision) yf = round(pos_f[1],precision) mag_dl = np.sqrt((xf-x0)**2 + (yf-y0)**2) if mag_dl < fillet or mag_dl < self.min_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), None)) else: pass return ab_placements
[docs] def calculate_component_bounding_boxes(self): """ Calculates the bounding boxes of all QComponents in the design. Returns: A dictionary of component names and their bounding boxes in the format {component_name: (min_x, max_x, min_y, max_y)} """ bounding_boxes = {} for qcomp in self.design.components.values(): bbox = qcomp.qgeometry_bounds() bounding_boxes[qcomp.name] = bbox return bounding_boxes
[docs] def get_non_overlap_zones(self): """ Determines if an airbridge is too close to the cavity. """ forbidden_qcomponents = ["CapNInterdigitalTee", "CoupledLineTee"] qcomps = [] for qcomp in self.design.components.values(): if qcomp.class_name.split(".")[-1] in forbidden_qcomponents: qcomps.append(qcomp) return qcomps
[docs] def get_coupler_bbox(self): """ Determines if an airbridge is too close to the cavity. """ forbidden_qcomponents = ["CapNInterdigitalTee", "CoupledLineTee"] bounding_boxes = {} for qcomp in self.design.components.values(): if qcomp.class_name.split(".")[-1] in forbidden_qcomponents: bbox = qcomp.qgeometry_bounds() bounding_boxes[qcomp.name] = bbox return bounding_boxes
[docs] def is_overlapping(self, component1, component2): # Get the bounding boxes of the components bbox1 = component1.qgeometry_bounds() bbox2 = component2.qgeometry_bounds() # Check if the bounding boxes intersect if ( bbox1[0] <= bbox2[2] and bbox1[2] >= bbox2[0] and bbox1[1] <= bbox2[3] and bbox1[3] >= bbox2[1] ): return True else: return False
[docs] def is_overlapping_with_coupler(self, component1, coupler): # Get the bounding boxes of the components bbox1 = component1.qgeometry_bounds() bbox2 = coupler.qgeometry_bounds() # Get the coupling length of the components coupling_length = self.design.parse_value(coupler.options.coupling_length) # Check if the bounding boxes intersect if ( bbox1[0] <= bbox2[2] and bbox1[2] >= bbox2[0] and bbox1[1] <= (bbox2[3] - coupling_length/2) and bbox1[3] >= (bbox2[1] + coupling_length/2) ): return True else: return False
[docs] def is_close_to_components(self, x, y, margin, bounding_boxes): """ Determines if a point is too close to any component's bounding box. Args: x (float): The x-coordinate of the point to check. y (float): The y-coordinate of the point to check. margin (float): The safety margin to maintain from any component. bounding_boxes (dict): The bounding boxes of all components. Returns: bool: True if the point is too close, False otherwise. """ print(bounding_boxes) for bbox in bounding_boxes.values(): min_x, max_x, min_y, max_y = bbox if (min_x - margin) <= x <= (max_x + margin) and (min_y - margin) <= y <= (max_y + margin): return True return False
[docs] def get_all_coordinates(self): """ Generates all airbridge coordinates for the target CPWs. """ coordinates = [] for target_cpw in self.target_comps: coordinates = self.find_ab_placement(target_cpw) coordinates.append(coordinates) return coordinates
[docs] def clear_all(self): """ Clears all airbridges from the design. """ for airbridge in self.airbridges: self.design.delete_component(airbridge.name)
[docs] def render_all(self): """ Renders all airbridges in the design. """ pass
[docs] def cleanup(self): """ Cleans up any resources used by the AirbridgeGenerator. """ airbridges = self.get_all_airbridges() for airbridge in airbridges: self.validate_airbridge(airbridge)