Esempio n. 1
0
    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)
Esempio n. 3
0
    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")
Esempio n. 4
0
    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())
Esempio n. 5
0
    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
Esempio n. 6
0
    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
Esempio n. 7
0
    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