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)