def test_toolbox_metal_is_true(self):
        """
        Test is_true in toolbox_metal.py.
        """
        self.assertTrue(parsing.is_true('true'))
        self.assertTrue(parsing.is_true('True'))
        self.assertTrue(parsing.is_true('TRUE'))
        self.assertTrue(parsing.is_true('1'))
        self.assertTrue(parsing.is_true('t'))
        self.assertTrue(parsing.is_true('y'))
        self.assertTrue(parsing.is_true('Y'))
        self.assertTrue(parsing.is_true('YES'))
        self.assertTrue(parsing.is_true('yes'))
        self.assertTrue(parsing.is_true('yeah'))
        self.assertTrue(parsing.is_true(True))
        self.assertTrue(parsing.is_true(1))
        self.assertTrue(parsing.is_true(1.0))

        self.assertFalse(parsing.is_true('false'))
        self.assertFalse(parsing.is_true('False'))
        self.assertFalse(parsing.is_true('FALSE'))
        self.assertFalse(parsing.is_true('0'))
        self.assertFalse(parsing.is_true('f'))
        self.assertFalse(parsing.is_true(False))
        self.assertFalse(parsing.is_true(78))
        self.assertFalse(parsing.is_true('gibberish'))
        self.assertFalse(parsing.is_true({'key': 'val'}))
示例#2
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
示例#3
0
    def add_q3d_setup(self,
                      name: str = None,
                      freq_ghz: float = None,
                      save_fields: bool = None,
                      enabled: bool = None,
                      max_passes: int = None,
                      min_passes: int = None,
                      min_converged_passes: int = None,
                      percent_error: float = None,
                      percent_refinement: int = None,
                      auto_increase_solution_order: bool = None,
                      solution_order: str = None,
                      solver_type: str = None,
                      *args,
                      **kwargs):
        """Create a solution setup in Ansys Q3D. If user does not provide
        arguments, they will be obtained from q3d_options dict.

        Args:
            name (str, optional): Name of solution setup. Defaults to None.
            freq_ghz (float, optional): Frequency in GHz. Defaults to None.
            save_fields (bool, optional): Whether or not to save fields. Defaults to None.
            enabled (bool, optional): Whether or not setup is enabled. Defaults to None.
            max_passes (int, optional): Maximum number of passes. Defaults to None.
            min_passes (int, optional): Minimum number of passes. Defaults to None.
            min_converged_passes (int, optional): Minimum number of converged passes. Defaults to None.
            percent_error (float, optional): Error tolerance as a percentage. Defaults to None.
            percent_refinement (int, optional): Refinement as a percentage. Defaults to None.
            auto_increase_solution_order (bool, optional): Whether or not to increase solution order automatically. Defaults to None.
            solution_order (str, optional): Solution order. Defaults to None.
            solver_type (str, optional): Solver type. Defaults to None.
        """
        su = self.default_setup.q3d

        if not name:
            name = self.parse_value(su['name'])
        if not freq_ghz:
            freq_ghz = float(self.parse_value(su['freq_ghz']))
        if not save_fields:
            save_fields = is_true(su['save_fields'])
        if not enabled:
            enabled = is_true(su['enabled'])
        if not max_passes:
            max_passes = int(self.parse_value(su['max_passes']))
        if not min_passes:
            min_passes = int(self.parse_value(su['min_passes']))
        if not min_converged_passes:
            min_converged_passes = int(
                self.parse_value(su['min_converged_passes']))
        if not percent_error:
            percent_error = float(self.parse_value(su['percent_error']))
        if not percent_refinement:
            percent_refinement = int(self.parse_value(su['percent_refinement']))
        if not auto_increase_solution_order:
            auto_increase_solution_order = is_true(
                su['auto_increase_solution_order'])
        if not solution_order:
            solution_order = self.parse_value(su['solution_order'])
        if not solver_type:
            solver_type = self.parse_value(su['solver_type'])

        if self.pinfo:
            if self.pinfo.design:
                return self.pinfo.design.create_q3d_setup(
                    freq_ghz=freq_ghz,
                    name=name,
                    save_fields=save_fields,
                    enabled=enabled,
                    max_passes=max_passes,
                    min_passes=min_passes,
                    min_converged_passes=min_converged_passes,
                    percent_error=percent_error,
                    percent_refinement=percent_refinement,
                    auto_increase_solution_order=auto_increase_solution_order,
                    solution_order=solution_order,
                    solver_type=solver_type)
示例#4
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