def containerize(component: Component, function: Callable, **kwargs): """Returns a containerize component after applying a function. This is an alternative of using the @container decorator. However I recommend using the decorator when possible Args: component: to containerize function: that applies to component **kwargs: for the function .. code:: import pp def add_label(component, text='hi'): return component.add_label(text) c = pp.c.waveguide() cc = pp.containerize(c, function=add_label, text='hi') """ c = Component() component_type = f"{component.name}_{function.__name__}" name = get_component_name(component_type, **kwargs) if len(name) > MAX_NAME_LENGTH: c.name_long = name name = f"{component_type}_{hashlib.md5(name.encode()).hexdigest()[:8]}" c.name = name c << component function(c, **kwargs) return c
def rotate(component, angle=90): """ returns rotated component """ c = Component( settings=component.get_settings(), test_protocol=component.test_protocol, data_analysis_protocol=component.data_analysis_protocol, ) cr = c.add_ref(component) cr.rotate(angle) c.ports = cr.ports c.name = component.name + "_r" return c
def _arbitrary_straight_waveguide(length, windows): """ Args: length: length windows: [(y_start, y_stop, layer), ...] """ md5 = hashlib.md5() for e in windows: md5.update(str(e).encode()) component = Component() component.name = "ARB_SW_L{}_HASH{}".format(length, md5.hexdigest()) y_min, y_max, layer0 = windows[0] y_min, y_max = min(y_min, y_max), max(y_min, y_max) # Add one port on each side centered at y=0 for y_start, y_stop, layer in windows: w = abs(y_stop - y_start) y = (y_stop + y_start) / 2 _wg = hline(length=length, width=w, layer=layer).ref() _wg.movey(y) component.add(_wg) component.absorb(_wg) y_min = min(y_stop, y_start, y_min) y_max = max(y_stop, y_start, y_max) width = y_max - y_min component.add_port(name="W0", midpoint=[0, 0], width=width, orientation=180, layer=layer0) component.add_port(name="E0", midpoint=[length, 0], width=width, orientation=0, layer=layer0) return component
def test_connect_bundle(): xs_top = [-100, -90, -80, 0, 10, 20, 40, 50, 80, 90, 100, 105, 110, 115] pitch = 127.0 N = len(xs_top) xs_bottom = [(i - N / 2) * pitch for i in range(N)] top_ports = [ Port("top_{}".format(i), (xs_top[i], 0), 0.5, 270) for i in range(N) ] bottom_ports = [ Port("bottom_{}".format(i), (xs_bottom[i], -400), 0.5, 90) for i in range(N) ] top_cell = Component(name="connect_bundle") elements = connect_bundle(top_ports, bottom_ports) for e in elements: top_cell.add(e) top_cell.name = "connect_bundle" return top_cell
def component_sequence( sequence: List[str], string_to_device_in_out_ports: Dict[str, Union[Tuple[Component, str, str], Tuple[None, str, str]]], ports_map: Dict[str, Tuple[str, str]] = {}, input_port_name: str = "in", output_port_name: str = "out", start_orientation: float = 0.0, name_prefix: None = None, ) -> Component: """ This generates a component from a sequence and a dictionnary to interprete each symbol in the sequence. Args: sequence: a string or a list of symbols string_to_device_in_out_ports: maps symbols to (device, input, output) ports_map: (optional) extra port mapping using the convention {port_name: (alias_name, port_name)} Returns: component containing the sequence of sub-components instantiated and connected together in the sequence order """ # Remove all None devices from the sequence sequence = sequence[:] to_rm = [] for i, d in enumerate(sequence): _name_device, _ = _parse_component_name(d) _device, _, _ = string_to_device_in_out_ports[_name_device] if _device is None: to_rm += [i] while to_rm: sequence.pop(to_rm.pop()) # To generate unique aliases for each instance counters = { k: count(start=1) for k in string_to_device_in_out_ports.keys() } def _next_id(name): return "{}{}".format(name, next(counters[name])) component = Component() # Add first device and input port name_start_device, do_flip = _parse_component_name(sequence[0]) _input_device, input_port, prev_port = string_to_device_in_out_ports[ name_start_device] prev_device = component.add_ref(_input_device, alias=_next_id(name_start_device)) if do_flip: prev_device = _flip_ref(prev_device, input_port) prev_device.rotate(angle=start_orientation) component.add_port(name=input_port_name, port=prev_device.ports[input_port]) # Generate and connect all elements from the sequence for s in sequence[1:]: s, do_flip = _parse_component_name(s) _device, input_port, next_port = string_to_device_in_out_ports[s] device = component.add_ref(_device, alias=_next_id(s)) if do_flip: device = _flip_ref(device, input_port) device.connect(input_port, prev_device.ports[prev_port]) prev_device = device prev_port = next_port # Deal with edge case where the sequence contains only one component if len(sequence) == 1: device = prev_device next_port = prev_port # Add output port try: component.add_port(name=output_port_name, port=device.ports[next_port]) except BaseException: print(sequence) raise # Add any extra port specified in ports_map for name, (alias, alias_port_name) in ports_map.items(): component.add_port(name=name, port=component[alias].ports[alias_port_name]) if name_prefix is not None: _md5 = hashlib.md5() _md5.update(str(string_to_device_in_out_ports).encode()) _md5.update(str(sequence).encode()) component.name = "{}_{}".format(name_prefix, _md5.hexdigest()) return component
def add_io_optical(c, grating_coupler=grating_coupler_te, gc_port_name="W0", component_name=None, **kwargs): """ returns component with optical IO (tapers, south routes and grating_couplers) Args: component: to connect optical_io_spacing: SPACING_GC grating_coupler: grating coupler instance, function or list of functions bend_factory: bend_circular straight_factory: waveguide fanout_length: None, # if None, automatic calculation of fanout length max_y0_optical: None with_align_ports: True, adds loopback structures waveguide_separation=4.0 bend_radius: BEND_RADIUS list_port_labels: None, adds TM labels to port indices in this list connected_port_list_ids: None # only for type 0 optical routing nb_optical_ports_lines: 1 force_manhattan: False excluded_ports: grating_indices: None routing_waveguide: None routing_method: connect_strip gc_port_name: W0 optical_routing_type: None: autoselection, 0: no extension gc_rotation=-90 layer_label=LAYER.LABEL input_port_indexes=[0] component_name: for the label """ if not c.ports: return c cc = Component( settings=c.get_settings(), test_protocol=c.test_protocol, data_analysis_protocol=c.data_analysis_protocol, ) cc.function_name = "add_io_optical" if isinstance(grating_coupler, list): gc = grating_coupler[0] else: gc = grating_coupler gc = pp.call_if_func(gc) if "polarization" in gc.settings: gc.polarization = gc.settings["polarization"] cc.name = "{}_{}".format(c.name, gc.polarization) port_width_gc = list(gc.ports.values())[0].width port_width_component = list(c.ports.values())[0].width if port_width_component != port_width_gc: c = add_tapers( c, taper(length=10, width1=port_width_gc, width2=port_width_component)) elements, io_gratings_lines, _ = _get_optical_io_elements( component=c, grating_coupler=grating_coupler, gc_port_name=gc_port_name, component_name=component_name, **kwargs) if len(elements) == 0: return c cc.add(elements) for io_gratings in io_gratings_lines: cc.add(io_gratings) cc.add(c.ref()) cc.move(origin=io_gratings_lines[0][0].ports[gc_port_name], destination=(0, 0)) return cc
def import_gds( gdspath: Union[str, Path], cellname: None = None, flatten: bool = False, snap_to_grid_nm: Optional[int] = None, ) -> Component: """Returns a Componenent from a GDS file. Adapted from phidl/geometry.py Args: gdspath: path of GDS file cellname: cell of the name to import (None) imports top cell flatten: if True returns flattened (no hierarchy) snap_to_grid_nm: snap to different nm grid (does not snap if False) """ gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(gdspath) top_level_cells = gdsii_lib.top_level() cellnames = [c.name for c in top_level_cells] if cellname is not None: if cellname not in gdsii_lib.cells: raise ValueError( f"cell {cellname} is not in file {gdspath} with cells {cellnames}" ) topcell = gdsii_lib.cells[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: raise ValueError( f"import_gds() There are multiple top-level cells in {gdspath}, " f"you must specify `cellname` to select of one of them among {cellnames}" ) if not flatten: D_list = [] c2dmap = {} for cell in gdsii_lib.cells.values(): D = Component(name=cell.name) D.polygons = cell.polygons D.references = cell.references D.name = cell.name for label in cell.labels: rotation = label.rotation if rotation is None: rotation = 0 label_ref = D.add_label( text=label.text, position=np.asfarray(label.position), magnification=label.magnification, rotation=rotation * 180 / np.pi, layer=(label.layer, label.texttype), ) label_ref.anchor = label.anchor c2dmap.update({cell: D}) D_list += [D] for D in D_list: # First convert each reference so it points to the right Device converted_references = [] for e in D.references: ref_device = c2dmap[e.ref_cell] if isinstance(e, gdspy.CellReference): dr = DeviceReference( device=ref_device, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) elif isinstance(e, gdspy.CellArray): dr = CellArray( device=ref_device, columns=e.columns, rows=e.rows, spacing=e.spacing, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) D.references = converted_references # Next convert each Polygon # temp_polygons = list(D.polygons) # D.polygons = [] # for p in temp_polygons: # D.add_polygon(p) # Next convert each Polygon temp_polygons = list(D.polygons) D.polygons = [] for p in temp_polygons: if snap_to_grid_nm: points_on_grid = pp.drc.snap_to_grid( p.polygons[0], nm=snap_to_grid_nm ) p = gdspy.Polygon( points_on_grid, layer=p.layers[0], datatype=p.datatypes[0] ) D.add_polygon(p) topdevice = c2dmap[topcell] return topdevice if flatten: D = pp.Component() polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): D.add_polygon(polys, layer=layer_in_gds) return D