def _merge_connections(list_of_points): a = [list_of_points[0]] a = a + [point[1:] for point in list_of_points[1:]] b = np.vstack(a) b = remove_identicals(b) b = remove_flat_angles(b) return b
def _generate_manhattan_bundle_waypoints( ports1: List[Port], ports2: List[Port], waypoints: Coordinates, separation: Optional[float] = None, **kwargs, ) -> Coordinates: """ Args: ports1: list of ports must face the same direction ports2: list of ports must face the same direction waypoints: from one point within the ports1 bank to another point within the ports2 bank separation: center to center, defaults to ports1 separation """ waypoints = remove_flat_angles(waypoints) way_segments = list(zip(waypoints, waypoints[1:])) offsets_start = get_ports_x_or_y_distances(ports1, waypoints[0]) start_angle = ports1[0].orientation if start_angle in [90, 270]: offsets_start = [-_d for _d in offsets_start] end_angle = ports2[0].orientation # if separation is defined, the offsets should increment from the reference port # in the same direction as the original offsets if separation and len(ports1) > 1: # the default case when we start with the reference port offsets_mid = [ np.sign(offsets_start[1]) * separation * i for i, o in enumerate(offsets_start) ] if offsets_start[0] == 0: # the default case, no action necessary pass elif offsets_start[-1] == 0: # if the reference port is last, reverse the whole list offsets_mid.reverse() else: raise ValueError("Expected offset = 0 at either start or end of route.") # if there is only one route, we should skip this step as it is irrelevant else: # separation defaults to ports1 separation offsets_mid = offsets_start def _displace_segment_copy(s, a, sh=1, sv=1): sign_seg = _segment_sign(s) if _is_horizontal(s): dp = (0, sh * sign_seg * a) elif _is_vertical(s): dp = (sv * sign_seg * a, 0) else: raise RouteError(f"Segment should be manhattan, got {s}") displaced_seg = [np.array(p) + dp for p in s] return displaced_seg def _displace_segment_copy_group1(s, a): return _displace_segment_copy(s, a, sh=1, sv=-1) def _intersection(s1, s2): if _is_horizontal(s1) and _is_vertical(s2): sh, sv = s1, s2 elif _is_horizontal(s2) and _is_vertical(s1): sh, sv = s2, s1 else: if _is_horizontal(s1): s1_dir = "h" elif _is_vertical(s1): s1_dir = "v" else: s1_dir = "u" if _is_horizontal(s2): s2_dir = "h" elif _is_vertical(s2): s2_dir = "v" else: s2_dir = "u" raise ValueError( "s1 / s2 should be h/v or v/h. Got \ {} {} {} {}".format( s1_dir, s2_dir, s1, s2 ) ) return sv[0][0], sh[0][1] routes = [] _make_segment = _displace_segment_copy_group1 for i, start_port in enumerate(ports1): route = [] prev_seg_sep = None for j, seg in enumerate(way_segments): if j == 0: start_point = start_port.midpoint seg_sep = offsets_start[i] else: seg_sep = offsets_mid[i] d_seg = _make_segment(seg, seg_sep) prev_seg = way_segments[j - 1] tmp_seg = _make_segment(prev_seg, prev_seg_sep) start_point = _intersection(d_seg, tmp_seg) route += [start_point] # If last point before the ports, adjust the separation to the end ports if j == len(way_segments) - 1: end_point = ports2[i].position route += [end_point] if end_angle in [0, 180]: route = snap_route_to_end_point_y(route, end_point[1]) else: route = snap_route_to_end_point_x(route, end_point[0]) prev_seg_sep = seg_sep routes += [route] return routes
def path_length_matched_points_modify_segment( list_of_waypoints, modify_segment_i, extra_length, ): if not isinstance(list_of_waypoints, list): raise ValueError("list_of_waypoints should be a list, got {}".format( type(list_of_waypoints))) list_of_waypoints = [ remove_flat_angles(waypoints) for waypoints in list_of_waypoints ] lengths = [path_length(waypoints) for waypoints in list_of_waypoints] L0 = max(lengths) N = len(list_of_waypoints[0]) # Find how many turns there are per path nb_turns = [len(waypoints) - 2 for waypoints in list_of_waypoints] # The paths have to have the same number of turns, otherwise this algo # cannot path length match if min(nb_turns) != max(nb_turns): raise ValueError( f"Number of turns in paths have to be identical got {nb_turns}") if modify_segment_i < 0: modify_segment_i = modify_segment_i + N + 1 list_new_waypoints = [] # For each list of waypoints, modify one segment in-place for i, waypoints in enumerate(list_of_waypoints): p_s0, p_s1, p_next = waypoints[modify_segment_i - 1:modify_segment_i + 2] p_s0 = np.array(p_s0) p_s1 = np.array(p_s1) L = lengths[i] # Path length compensation length dL = (L0 - L) / 2 # Additional fixed length dL = dL + extra_length # Modify the segment to accomodate for path length matching # Two cases: vertical or horizontal segment if _is_vertical(p_s0, p_s1): sx = np.sign(p_next[0] - p_s1[0]) dx = -sx * dL dp = (dx, 0) # Sequence of displacements to apply elif _is_horizontal(p_s0, p_s1): sy = np.sign(p_next[1] - p_s1[1]) dy = -sy * dL dp = (0, dy) waypoints[modify_segment_i - 1] = p_s0 + dp waypoints[modify_segment_i] = p_s1 + dp list_new_waypoints += [waypoints] return list_new_waypoints
def path_length_matched_points_add_waypoints( list_of_waypoints: List[ndarray], modify_segment_i: int = -2, bend: ComponentFactory = bend_euler, margin: float = 0.0, extra_length: float = 0.0, nb_loops: int = 1, cross_section: CrossSectionFactory = strip, **kwargs, ) -> List[ndarray]: """ Args: list_of_waypoints: a list of list_of_points: [[p1, p2, p3,...], [q1, q2, q3,...], ...] - the number of turns have to be identical (usually means same number of points. exception is if there are some flat angles) modify_segment_i: index of the segment which accomodates the new turns default is next to last segment bend: for bends margin: some extra space to budget for in addition to the bend radius in most cases, the default is fine extra_length: distance added to all path length compensation. Useful is we want to add space for extra taper on all branches nb_loops: number of extra loops added in the path cross_section: factory **kwargs: cross_section settings returns: another list of waypoints where: - the path_lenth of each waypoints list are identical - the number of turns are identical Several types of paths won't match correctly. We do not try to handle all the corner cases here. If the paths are not well behaved, the input list_of_waypoints needs to be modified. To have flexibility in the path length, we need to add 4 bends One path has to be converted in this way: .. code:: __ | | | | This length is adjusted to make all path with the same length | | ___| |___ """ if not isinstance(list_of_waypoints, list): raise ValueError( f"list_of_waypoints should be a list, got {type(list_of_waypoints)}" ) list_of_waypoints = [ remove_flat_angles(waypoints) for waypoints in list_of_waypoints ] lengths = [path_length(waypoints) for waypoints in list_of_waypoints] L0 = max(lengths) N = len(list_of_waypoints[0]) # Find how many turns there are per path nb_turns = [len(waypoints) - 2 for waypoints in list_of_waypoints] # The paths have to have the same number of turns, otherwise cannot path-length # match with this algorithm if min(nb_turns) != max(nb_turns): raise ValueError( f"Number of turns in paths have to be identical got {nb_turns}") # Get the points for the segment we need to modify bend90 = bend(cross_section=cross_section, **kwargs) a = margin + bend90.info.dy if modify_segment_i < 0: modify_segment_i = modify_segment_i + N + 1 list_new_waypoints = [] for i, waypoints in enumerate(list_of_waypoints): p_s0, p_s1, p_next = waypoints[modify_segment_i - 2:modify_segment_i + 1] p_s1 = np.array(p_s1) L = lengths[i] # Path length compensation length dL = (L0 - L) / (2 * nb_loops) # Additional fixed length dL = dL + extra_length # Generate a new sequence of points which will replace this segment # Two cases: vertical or horizontal segment if _is_vertical(p_s0, p_s1): sx = np.sign(p_next[0] - p_s1[0]) sy = np.sign(p_s1[1] - p_s0[1]) dx = sx * (2 * a + dL) dy = sy * 2 * a # First new point to insert q0 = p_s1 + (0, -2 * nb_loops * dy) # Sequence of displacements to apply seq = [(dx, 0), (0, dy), (-dx, 0), (0, dy)] * nb_loops seq.pop() # Remove last point to avoid flat angle with next point elif _is_horizontal(p_s0, p_s1): sy = np.sign(p_next[1] - p_s1[1]) sx = np.sign(p_s1[0] - p_s0[0]) dx = sx * 2 * a dy = sy * (2 * a + dL) # First new point to insert q0 = p_s1 + (-2 * dx * nb_loops, 0) # Sequence of displacements to apply seq = [(0, dy), (dx, 0), (0, -dy), (dx, 0)] * nb_loops seq.pop() # Remove last point to avoid flat angle with next point # Generate points to insert qs = [q0] for dp in seq: qs += [qs[-1] + dp] inserted_points = np.stack(qs, axis=0) waypoints = np.array(waypoints) # Insert the points new_points = np.vstack([ waypoints[:modify_segment_i - 1], inserted_points, waypoints[modify_segment_i - 1:], ]) list_new_waypoints += [new_points] return list_new_waypoints