def connect_frame(self, segments: int, constaxis=0, constval=0) -> list: """Generate the list of 2D coordinates comprising a CPW between startpin and endpin. Args: startpin (str): Name of startpin endpin (str): Name of endpin width (float): Width of CPW in mm segments (int): Number of segments in the CPW, not including leadin and leadout. Ranges from 1 to 3. leadstart (float): Length of first CPW segment originating at startpin leadend (float): Length of final CPW segment ending at endpin constaxis (int, optional): In the case of 3 segment CPWs, the constant axis of the line that both leadin and leadout must connect to. Example: If x = 3, the constant axis (x) is 0. Defaults to 0. constval (int, optional): In the case of 3 segment CPWs, the constant numerical value of the line that both leadin and leadout must connect to. Example: If x = 3, the constant value is 3. Defaults to 0. Returns: List: [np.array([x0, y0]), np.array([x1, y1]), np.array([x2, y2])] where xi, yi are vertices of CPW. """ start = self.head.get_tip() end = self.tail.get_tip() if segments == 1: # Straight across or up and down; no additional points necessary midcoords = [] elif segments == 2: # Choose between 2 diagonally opposing corners so that CPW doesn't trace back on itself corner1 = np.array([(start.position)[0], (end.position)[1]]) corner2 = np.array([(end.position)[0], (start.position)[1]]) startc1 = mao.dot(corner1 - (start.position), start.direction) endc1 = mao.dot(corner1 - (end.position), end.direction) startc2 = mao.dot(corner2 - (start.position), start.direction) endc2 = mao.dot(corner2 - (end.position), end.direction) # If both pins' normal vectors point towards one of the corners, pick that corner if (startc1 > 0) and (endc1 > 0): midcoords = [corner1] elif (startc2 > 0) and (endc2 > 0): midcoords = [corner2] elif min(startc1, endc1) >= 0: midcoords = [corner1] else: midcoords = [corner2] elif segments == 3: # Connect start and end to long segment at constaxis # 2 additional intermediate points needed to achieve this if constaxis == 0: # Middle segment lies on the line x = c for some constant c midcoord_start = np.array([constval, (start.position)[1]]) midcoord_end = np.array([constval, (end.position)[1]]) else: # Middle segment lies on the line y = d for some constant d midcoord_start = np.array([(start.position)[0], constval]) midcoord_end = np.array([(end.position)[0], constval]) midcoords = [midcoord_start, midcoord_end] startpts = [start.position] endpts = [end.position] return startpts + midcoords + endpts
def test_toolbox_metal_dot(self): """ Test functionality of dot in toolbox_metal.py. """ math_and_overrides.set_decimal_precision(3) my_array_1 = np.array([3, 4]) my_array_2 = np.array([12, 14]) self.assertEqual(math_and_overrides.dot(my_array_1, my_array_2), 92)
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 make(self): """Use user-specified parameters and geometric orientation of components to determine whether the CPW connecting the pins on either end requires 1, 2, or 3 segments. Preference given to shorter paths and paths that flow with the leadin and leadout directions rather than turning immediately at 90 deg. Keepout region along x and y directions specified for CPWs that wrap around outer perimeter of overall bounding box of both components. """ self.__pts = [] # list of 2D numpy arrays containing vertex locations p = self.p # parsed options w = p.trace_width leadstart = p.lead.start_straight leadend = p.lead.end_straight keepoutx = 0.2 keepouty = 0.2 # 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") n1 = meander_start_point.direction n2 = meander_end_point.direction m1 = meander_start_point.position m2 = meander_end_point.position component_start = p.pin_inputs['start_pin']['component'] component_end = p.pin_inputs['end_pin']['component'] # Coordinates of bounding box for each individual component minx1, miny1, maxx1, maxy1 = self.design.components[ component_start].qgeometry_bounds() minx2, miny2, maxx2, maxy2 = self.design.components[ component_end].qgeometry_bounds() # Coordinates of overall bounding box which includes both components minx, miny = min(minx1, minx2), min(miny1, miny2) maxx, maxy = max(maxx1, maxx2), max(maxy1, maxy2) # Normdot is dot product of normal vectors normdot = mao.dot(n1, n2) if normdot == -1: # Modify CPW endpoints to include mandatory w / 2 leadin plus user defined leadin m1ext = m1 + n1 * (w / 2 + leadstart) m2ext = m2 + n2 * (w / 2 + leadend) # Alignment is between displacement of modified positions (see above) and normal vector alignment = mao.dot((m2ext - m1ext) / norm(m2ext - m1ext), n1) if alignment == 1: # Normal vectors point directly at each other; no obstacles in between # Connect with single segment; generalizes to arbitrary angles with no snapping self.__pts = self.connect_frame(1) elif alignment > 0: # Displacement partially aligned with normal vectors # Connect with antisymmetric 3-segment CPW if n1[1] == 0: # Normal vectors horizontal if minx2 < maxx1: self.__pts = self.connect_frame( 3, 0, (minx2 + maxx1) / 2) else: self.__pts = self.connect_frame( 3, 0, (minx1 + maxx2) / 2) else: # Normal vectors vertical if miny2 < maxy1: self.__pts = self.connect_frame( 3, 1, (miny2 + maxy1) / 2) else: self.__pts = self.connect_frame( 3, 1, (miny1 + maxy2) / 2) elif alignment == 0: # Displacement orthogonal to normal vectors # Connect with 1 segment self.__pts = self.connect_frame(1) elif alignment < 0: # Normal vectors on opposite sides of squares if n1[1] == 0: # Normal vectors horizontal if maxy1 < miny2: self.__pts = self.connect_frame( 3, 1, (maxy1 + miny2) / 2) elif maxy2 < miny1: self.__pts = self.connect_frame( 3, 1, (maxy2 + miny1) / 2) else: # Gap running only vertically -> must wrap around with shorter of 2 possibilities # pts_top represents 3-segment CPW running along top edge of overall bounding box # pts_bott represents 3-segment CPW running along bottom edge of overall bounding box pts_top = self.connect_frame(3, 1, maxy + keepouty) pts_bott = self.connect_frame(3, 1, miny - keepouty) if self.totlength(pts_top) < self.totlength(pts_bott): self.__pts = pts_top else: self.__pts = pts_bott else: # Normal vectors vertical if maxx1 < minx2: self.__pts = self.connect_frame( 3, 0, (maxx1 + minx2) / 2) elif maxx2 < minx1: self.__pts = self.connect_frame( 3, 0, (maxx2 + minx1) / 2) else: # Gap running only horizontally -> must wrap around with shorter of 2 possibilities # pts_left represents 3-segment CPW running along left edge of overall bounding box # pts_right represents 3-segment CPW running along right edge of overall bounding box pts_left = self.connect_frame(3, 0, minx - keepoutx) pts_right = self.connect_frame(3, 0, maxx + keepoutx) if self.totlength(pts_left) < self.totlength( pts_right): self.__pts = pts_left else: self.__pts = pts_right elif normdot == 0: # Normal vectors perpendicular to each other if (m1[0] in [minx, maxx]) and (m2[1] in [miny, maxy]): # Both pins on perimeter of overall bounding box, but not at corner self.__pts = self.connect_frame(2) elif (m1[1] in [miny, maxy]) and (m2[0] in [minx, maxx]): # Both pins on perimeter of overall bounding box, but not at corner self.__pts = self.connect_frame(2) elif (m1[0] not in [minx, maxx]) and (m2[0] not in [ minx, maxx ]) and (m1[1] not in [miny, maxy]) and (m2[1] not in [miny, maxy]): # Neither pin lies on perimeter of overall bounding box # Always possible to connect with at most 2 segments if (m1[0] == m2[0]) or (m1[1] == m2[1]): # Connect directly with 1 segment self.__pts = self.connect_frame(1) else: # Connect with 2 segments self.__pts = self.connect_frame(2) elif (m1[0] in [minx, maxx]) or (m1[1] in [miny, maxy]): # Pin 1 lies on boundary of overall bounding box but pin 2 does not if m1[0] in [minx, maxx]: # Pin 1 on left or right boundary, pointing left or right, respectively if n2[1] > 0: # Pin 2 pointing up if miny1 > maxy2: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 1, maxy + keepouty) else: # Pin 2 pointing down if miny2 > maxy1: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 1, miny - keepouty) elif m1[1] in [miny, maxy]: # Pin 1 on bottom or top boundary, pointing down or up, respectively if n2[0] < 0: # Pin 2 pointing left if minx2 > maxx1: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 0, minx - keepoutx) else: # Pin 2 pointing right if minx1 > maxx2: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 0, maxx + keepoutx) elif (m2[0] in [minx, maxx]) or (m2[1] in [miny, maxy]): # Pin 2 lies on boundary of overall bounding box but pin 1 does not if m2[0] in [minx, maxx]: # Pin 2 on left or right boundary, pointing left or right, respectively if n1[1] > 0: # Pin 1 pointing up if miny2 > maxy1: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 1, maxy + keepouty) else: # Pin 1 pointing down if miny1 > maxy2: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 1, miny - keepouty) elif m2[1] in [miny, maxy]: # Pin 2 on bottom or top boundary, pointing down or up, respectively if n1[0] < 0: # Pin 1 pointing left if minx1 > maxx2: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 0, minx - keepoutx) else: # Pin 1 pointing right if minx2 > maxx1: self.__pts = self.connect_frame(2) else: self.__pts = self.connect_frame( 3, 0, maxx + keepoutx) else: # Normal vectors pointing in same direction if ((m1[0] == m2[0]) or (m1[1] == m2[1])) and (mao.dot(n1, m2 - m1) == 0): # Connect directly with 1 segment self.__pts = self.connect_frame(1) elif n1[1] == 0: # Normal vectors horizontal if (m1[1] > maxy2) or (m2[1] > maxy1): # Connect with 2 segments self.__pts = self.connect_frame(2) else: # Must wrap around with shorter of the 2 following possibilities: # pts_top represents 3-segment CPW running along top edge of overall bounding box # pts_bott represents 3-segment CPW running along bottom edge of overall bounding box pts_top = self.connect_frame(3, 1, maxy + keepouty) pts_bott = self.connect_frame(3, 1, miny - keepouty) if self.totlength(pts_top) < self.totlength(pts_bott): self.__pts = pts_top else: self.__pts = pts_bott else: # Normal vectors vertical if (m1[0] > maxx2) or (m2[0] > maxx1): # Connect with 2 segments self.__pts = self.connect_frame(2) else: # Must wrap around with shorter of the 2 following possibilities: # pts_left represents 3-segment CPW running along left edge of overall bounding box # pts_right represents 3-segment CPW running along right edge of overall bounding box pts_left = self.connect_frame(3, 0, minx - keepoutx) pts_right = self.connect_frame(3, 0, maxx + keepoutx) if self.totlength(pts_left) < self.totlength(pts_right): self.__pts = pts_left else: self.__pts = pts_right self.intermediate_pts = self.__pts # Make points into elements self.make_elements(self.get_points())
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
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
def connect_astar_or_simple(self, start_pt: QRoutePoint, end_pt: QRoutePoint) -> list: """Connect start and end via A* algo if connect_simple doesn't work. Args: start_direction (np.array): Vector indicating direction of starting point start (np.array): 2-D coordinates of first anchor end (np.array): 2-D coordinates of second anchor Returns: List of vertices of a CPW going from start to end Raises: QiskitMetalDesignError: If the connect_simple() has failed. """ start_direction = start_pt.direction start = start_pt.position end_direction = end_pt.direction end = end_pt.position step_size = self.parse_options().step_size starting_dist = sum( abs(end - start)) # Manhattan distance between start and end key_starting_point = (starting_dist, start[0], start[1]) pathmapper = {key_starting_point: [starting_dist, [start]]} # pathmapper maps tuple(total length of the path from self.start + Manhattan distance to destination, coordx, coordy) to [total length of # path from self.start, path] visited = set( ) # maintain record of points we've already visited to avoid self-intersections visited.add(tuple(start)) # TODO: add to visited all of the current points in the route, to prevent self intersecting priority_queue = list() # A* priority queue. Implemented as heap priority_queue.append(key_starting_point) # Elements in the heap are ordered by the following: # 1. The total length of the path from self.start + Manhattan distance to destination # 2. The x coordinate of the latest point # 3. The y coordinate of the latest point while priority_queue: tot_dist, x, y = heapq.heappop( priority_queue ) # tot_dist is the total length of the path from self.start + Manhattan distance to destination length_travelled, current_path = pathmapper[(tot_dist, x, y)] # Look in forward, left, and right directions a fixed distance away. # If the line segment connecting the current point and this next one does # not collide with any bounding boxes in design.components, add it to the # list of neighbors. neighbors = list() if len(current_path) == 1: # At starting point -> initial direction is start direction direction = start_direction else: # Beyond starting point -> look at vector difference of last 2 points along path direction = current_path[-1] - current_path[-2] # The dot product between direction and the vector connecting the current # point and a potential neighbor must be non-negative to avoid retracing. # Check if connect_simple works at each iteration of A* try: simple_path = self.connect_simple( QRoutePoint(np.array([x, y]), direction), QRoutePoint(end, end_direction)) except QiskitMetalDesignError: simple_path = None # try the pathfinder algorithm pass if simple_path is not None: current_path.extend(simple_path) return current_path for disp in [ np.array([0, 1]), np.array([0, -1]), np.array([1, 0]), np.array([-1, 0]) ]: # Unit displacement in 4 cardinal directions if mao.dot(disp, direction) >= 0: # Ignore backward direction curpt = current_path[-1] nextpt = curpt + step_size * disp if self.unobstructed([curpt, nextpt]): neighbors.append(nextpt) for neighbor in neighbors: if tuple(neighbor) not in visited: new_remaining_dist = sum(abs(end - neighbor)) new_length_travelled = length_travelled + step_size new_path = current_path + [neighbor] if new_remaining_dist < 10**-8: # Destination has been reached within acceptable error tolerance (errors due to rounding in Python) return new_path[:-1] + [ end ] # Replace last element of new_path with end since they're basically the same heapq.heappush(priority_queue, (new_length_travelled + new_remaining_dist, neighbor[0], neighbor[1])) pathmapper[(new_length_travelled + new_remaining_dist, neighbor[0], neighbor[1])] = [ new_length_travelled, new_path ] visited.add(tuple(neighbor)) return [ ] # Shouldn't actually reach here - if it fails, there's a convergence issue