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'}))
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 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)
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