def add_grating_couplers( component, grating_coupler=grating_coupler_te, layer_label=pp.LAYER.LABEL, gc_port_name="W0", get_input_labels_function=get_input_labels, ): """ returns component with grating ports and labels on each port """ component = pp.call_if_func(component) c = pp.Component(name=component.name + "_c") c.add_ref(component) grating_coupler = pp.call_if_func(grating_coupler) io_gratings = [] for i, port in enumerate(component.ports.values()): gc_ref = grating_coupler.ref() gc_ref.connect(list(gc_ref.ports.values())[0], port) io_gratings.append(gc_ref) c.add(gc_ref) labels = get_input_labels_function( io_gratings, list(component.ports.values()), component_name=component.name, layer_label=layer_label, gc_port_name=gc_port_name, ) c.add(labels) return c
def ring_double_siepic( wg_width=0.5, gap=0.2, length_x=4, bend_radius=5, length_y=2, coupler=siepic.ebeam_dc_halfring_straight, waveguide=siepic.ebeam_wg_integral_1550, ): """ double bus ring made of two couplers (ct: top, cb: bottom) connected with two vertical waveguides (wyl: left, wyr: right) .. code:: --==ct==-- | | wl wr length_y | | --==cb==-- gap length_x .. plot:: :include-source: import pp c = pp.c.ring(wg_width=0.5, gap=0.2, length_x=4, bend_radius=5, length_y=2) pp.plotgds(c) .. plot:: :include-source: import simphony.library.gdsfactory as cl c = cl.ring() cl.sweep_simulation(c) """ waveguide = pp.call_if_func(waveguide) coupler = pp.call_if_func(coupler) # Create the circuit, add all individual instances circuit = Subcircuit("mzi") circuit.add([(coupler, "ct"), (coupler, "cb"), (waveguide, "wl"), (waveguide, "wr")]) # Circuits can be connected using the elements' string names: circuit.connect_many([ ("cb", "2", "wl", "n1"), ("wl", "n2", "ct", "n4"), ("ct", "2", "wr", "n2"), ("wr", "n1", "cb", "n4"), ]) circuit.elements["cb"].pins["n4"] = "input" circuit.elements["cb"].pins["n3"] = "output" circuit.elements["ct"].pins["n3"] = "drop" circuit.elements["ct"].pins["n4"] = "cdrop" return circuit
def test_ring_single_bus( coupler90_factory=pp.c.coupler90, cpl_straight_factory=pp.c.coupler_straight, straight_factory=pp.c.waveguide, bend90_factory=pp.c.bend_circular, length_y=2.0, length_x=4.0, gap=0.2, wg_width=0.5, bend_radius=5, ): """ single bus ring """ c = pp.Component() # define subcells coupler90 = pp.call_if_func(coupler90_factory, gap=gap, width=wg_width, bend_radius=bend_radius) waveguide_x = pp.call_if_func(straight_factory, length=length_x, width=wg_width) waveguide_y = pp.call_if_func(straight_factory, length=length_y, width=wg_width) bend = pp.call_if_func(bend90_factory, width=wg_width, radius=bend_radius) coupler_straight = pp.call_if_func(cpl_straight_factory, gap=gap, length=length_x, width=wg_width) # add references to subcells cbl = c << coupler90 cbr = c << coupler90 cs = c << coupler_straight wyl = c << waveguide_y wyr = c << waveguide_y wx = c << waveguide_x btl = c << bend btr = c << bend # connect references wyr.connect(port="E0", destination=cbr.ports["N0"]) cs.connect(port="E0", destination=cbr.ports["W0"]) cbl.reflect(p1=(0, coupler90.y), p2=(1, coupler90.y)) cbl.connect(port="W0", destination=cs.ports["W0"]) wyl.connect(port="E0", destination=cbl.ports["N0"]) btl.connect(port="N0", destination=wyl.ports["W0"]) btr.connect(port="W0", destination=wyr.ports["W0"]) wx.connect(port="W0", destination=btl.ports["W0"]) c.add_port("W0", port=cbl.ports["E0"]) c.add_port("E0", port=cbr.ports["E0"]) assert c return c
def ring_double_siepic( wg_width=0.5, gap=0.2, length_x=4, bend_radius=5, length_y=2, coupler=siepic.ebeam_dc_halfring_straight, waveguide=siepic.ebeam_wg_integral_1550, terminator=siepic.ebeam_terminator_te1550, ): """ double bus ring made of two couplers (ct: top, cb: bottom) connected with two vertical waveguides (wyl: left, wyr: right) .. code:: --==ct==-- | | wl wr length_y | | --==cb==-- gap length_x drop n1 _ _ n3 cdrop \______/ ______ in n2 _/ \_n4 | | n1 | | n3 \______/ ______ in n2 _/ \_n4 output """ waveguide = pp.call_if_func(waveguide) coupler = pp.call_if_func(coupler) # Create the circuit, add all individual instances circuit = Subcircuit("mzi") circuit.add([(coupler, "ct"), (coupler, "cb"), (waveguide, "wl"), (waveguide, "wr")]) # Circuits can be connected using the elements' string names: circuit.connect_many([ ("cb", "n1", "wl", "n1"), ("wl", "n2", "ct", "n2"), ("ct", "n4", "wr", "n1"), ("wr", "n2", "cb", "n3"), ]) circuit.elements["cb"].pins["n2"] = "input" circuit.elements["cb"].pins["n4"] = "output" circuit.elements["ct"].pins["n1"] = "drop" circuit.elements["ct"].pins["n3"] = "cdrop" return circuit
def ring_double_sipann( wg_width=0.5, gap=0.2, length_x=4, bend_radius=5, length_y=2, coupler=siepic.ebeam_dc_halfring_straight, waveguide=siepic.ebeam_wg_integral_1550, terminator=ebeam.ebeam_terminator_te1550, ): """ double bus ring made of two couplers (ct: top, cb: bottom) connected with two vertical waveguides (wyl: left, wyr: right) .. code:: --==ct==-- | | wl wr length_y | | --==cb==-- gap length_x .. plot:: :include-source: import pp c = pp.c.ring(wg_width=0.5, gap=0.2, length_x=4, bend_radius=5, length_y=2) pp.plotgds(c) .. plot:: :include-source: import simphony.library.gdsfactory as cl c = cl.ring() cl.sweep_simulation(c) """ waveguide = pp.call_if_func(waveguide) half_ring = pp.call_if_func(coupler) term = pp.call_if_func(coupler) circuit = Subcircuit() circuit.add([(half_ring, "input"), (half_ring, "output"), (term, "terminator")]) circuit.elements["input"].pins = ("pass", "midb", "in", "midt") circuit.elements["output"].pins = ("out", "midt", "term", "midb") circuit.connect_many([ ("input", "midb", "output", "midb"), ("input", "midt", "output", "midt"), ("terminator", "n1", "output", "term"), ]) return circuit
def add_grating_couplers( component, get_route_factory=route_fiber_single, optical_io_spacing=50, min_input2output_spacing=200, optical_routing_type=2, bend_factory=pp.c.bend_circular, grating_coupler=pp.c.grating_coupler_te, straight_factory=pp.c.waveguide, with_align_ports=True, layer_label=pp.LAYER.LABEL, ): """ returns component with grating ports and labels on each port can add align_ports reference structure """ component = pp.call_if_func(component) grating_coupler = pp.call_if_func(grating_coupler) c = pp.routing.add_fiber_array( component, optical_io_spacing=optical_io_spacing, bend_factory=bend_factory, straight_factory=straight_factory, grating_coupler=grating_coupler, get_route_factory=get_route_factory, optical_routing_type=optical_routing_type, min_input2output_spacing=min_input2output_spacing, ) if with_align_ports: gc_port_name = list(grating_coupler.ports.keys())[0] gci = c << grating_coupler gco = c << grating_coupler length = c.ysize - 2 * grating_coupler.xsize wg = c << straight_factory(length=length) wg.rotate(90) wg.xmin = c.xmax + optical_io_spacing - grating_coupler.ysize / 2 wg.ymin = c.ymin + grating_coupler.xsize gci.connect(gc_port_name, wg.ports["W0"]) gco.connect(gc_port_name, wg.ports["E0"]) port = wg.ports["E0"] label = get_optical_text(port, grating_coupler, 0, component_name=f"loopback_{component.name}") c.add_label(label, position=port.midpoint, layer=layer_label) port = wg.ports["W0"] label = get_optical_text(port, grating_coupler, 1, component_name=f"loopback_{component.name}") c.add_label(label, position=port.midpoint, layer=layer_label) return c
def coupler_ring( coupler90: Callable = coupler90, coupler: Callable = coupler_straight, length_x: float = 4.0, gap: float = 0.2, wg_width: float = 0.5, bend_radius: float = 5.0, ) -> Component: """ coupler for half a ring .. code:: N0 N1 | | \ / \ / ---=========--- W0 length_x E0 .. plot:: :include-source: import pp c = pp.c.coupler_ring(length_x=20, bend_radius=5.0, gap=0.3, wg_width=0.45) pp.plotgds(c) """ c = pp.Component() assert_on_2nm_grid(gap) # define subcells coupler90 = pp.call_if_func(coupler90, gap=gap, width=wg_width, bend_radius=bend_radius) coupler_straight = pp.call_if_func(coupler, gap=gap, length=length_x, width=wg_width) # add references to subcells cbl = c << coupler90 cbr = c << coupler90 cs = c << coupler_straight # connect references cs.connect(port="E0", destination=cbr.ports["W0"]) cbl.reflect(p1=(0, coupler90.y), p2=(1, coupler90.y)) cbl.connect(port="W0", destination=cs.ports["W0"]) c.add_port("W0", port=cbl.ports["E0"]) c.add_port("N0", port=cbl.ports["N0"]) c.add_port("E0", port=cbr.ports["E0"]) c.add_port("N1", port=cbr.ports["N0"]) return c
def loop_mirror_with_delay(loop_mirror=loop_mirror, spiral=spiral_external_io): """ delay = 13e-12 # delay = length/speed # length=delay*speed 13e-12*3e8/4.2*1e6 """ c = pp.Component() lm = c << pp.call_if_func(loop_mirror) s = c << pp.call_if_func(spiral_external_io) lm.connect("W0", s.ports["input"]) return c
def add_taper_elements(component, taper=taper): """returns ports and taper elements for a component""" taper = pp.call_if_func(taper) ports = [] elements = [] c = pp.Component() for port_name, port in component.ports.copy().items(): if port.port_type == "optical": taper_ref = c << pp.call_if_func(taper) taper_ref.connect(taper_ref.ports["2"].name, port) elements.append(taper_ref) ports.append(taper_ref.ports["1"]) return ports, elements
def add_tapers(component, taper=taper, suffix="t", port_type="optical"): """returns component optical tapers for component """ taper = pp.call_if_func(taper) c = pp.Component(name=f"{component.name}_{suffix}") for port_name, port in component.ports.copy().items(): if port.port_type == port_type: taper_ref = c << pp.call_if_func(taper) taper_ref.connect(taper_ref.ports["2"].name, port) c.add_port(name=port_name, port=taper_ref.ports["1"]) else: c.add_port(name=port_name, port=port) c.add_ref(component) return c
def loop_mirror(component=mmi1x2, bend90=bend_euler90): c = pp.Component() component = pp.call_if_func(component) bend90 = pp.call_if_func(bend90) cref = c.add_ref(component) elements = route_manhattan( cref.ports["E0"], cref.ports["E1"], bend90=bend90, straight_factory=pp.c.waveguide, ) c.add(elements) c.add_port(name="W0", port=cref.ports["W0"]) c.absorb(cref) return c
def cavity( component: Component, coupler: Component = coupler, length: float = 0.1, gap: float = 0.2, wg_width: float = 0.5, ) -> Component: """ creates a cavity from a coupler and a mirror it will connect the W0 port of the mirror to both E1 and W1 ports of the coupler creating a resonant cavity Args: component: mirror coupler: coupler factory length: coupler length gap: coupler gap wg_width: coupler wg_width .. code:: ml (mirror left) mr (mirror right) | | |W0 - W1__ __E1 - W0| | \ / | \ / ---=========--- W0 length E0 .. plot:: :include-source: import pp c = pp.c.cavity(component=pp.c.dbr()) pp.plotgds(c) """ mirror = pp.call_if_func(component) coupler = pp.call_if_func(coupler, length=length, gap=gap, wg_width=wg_width) c = pp.Component() cr = c << coupler ml = c << mirror mr = c << mirror ml.connect("W0", destination=cr.ports["W1"]) mr.connect("W0", destination=cr.ports["E1"]) c.add_port("W0", port=cr.ports["W0"]) c.add_port("E0", port=cr.ports["E0"]) return c
def add_grating_couplers( component: Component, grating_coupler=grating_coupler_te, layer_label=pp.LAYER.LABEL, gc_port_name: str = "W0", get_input_labels_function=get_input_labels, ): """Return component with grating couplers and labels.""" cnew = Component(name=component.name + "_c") cnew.add_ref(component) grating_coupler = pp.call_if_func(grating_coupler) io_gratings = [] for port in component.ports.values(): gc_ref = grating_coupler.ref() gc_ref.connect(list(gc_ref.ports.values())[0], port) io_gratings.append(gc_ref) cnew.add(gc_ref) labels = get_input_labels_function( io_gratings, list(component.ports.values()), component_name=component.name, layer_label=layer_label, gc_port_name=gc_port_name, ) cnew.add(labels) return cnew
def add_gc(circuit, gc=siepic.ebeam_gc_te1550): """ add input and output gratings Args: circuit: needs to have `input` and `output` pins gc: grating coupler """ c = Subcircuit(f"{circuit}_gc") gc = pp.call_if_func(gc) c.add([ (gc, "gci"), (gc, "gco"), (circuit, "circuit"), ]) c.connect_many([ ("gci", "n1", "circuit", "input"), ("gco", "n1", "circuit", "output"), ]) # c.elements["circuit"].pins["input"] = "input_circuit" # c.elements["circuit"].pins["output"] = "output_circuit" c.elements["gci"].pins["n2"] = "input" c.elements["gco"].pins["n2"] = "output" return c
def add_gc(circuit, gc=gc1550te, cpi="input", cpo="output", gpi="port 1", gpo="port 2"): """ add input and output gratings Args: circuit: needs to have `input` and `output` pins gc: grating coupler cpi: circuit pin input name cpo: circuit pin output name gpi: grating pin input name gpo: grating pin output name .. code:: _______ | | gpi-> gpo--|cpi cpo|--gpo <-gpi |_______| """ gc = pp.call_if_func(gc) c = Subcircuit(f"{circuit.name}_{gc.name}") c.add([(gc, "gci"), (gc, "gco"), (circuit, "circuit")]) c.connect_many([("gci", gpo, "circuit", cpi), ("gco", gpo, "circuit", cpo)]) c.elements["gci"].pins[gpi] = "input" c.elements["gco"].pins[gpi] = "output" return c
def crossing(arm: Callable = crossing_arm) -> Component: """waveguide crossing .. plot:: :include-source: import pp c = pp.c.crossing() pp.plotgds(c) """ cx = pp.Component() arm = pp.call_if_func(arm) arm_h = arm.ref() arm_v = arm.ref(rotation=90) port_id = 0 for c in [arm_h, arm_v]: cx.add(c) cx.absorb(c) for p in c.ports.values(): cx.add_port(name="{}".format(port_id), port=p) port_id += 1 cx = pp.port.rename_ports_by_orientation(cx) return cx
def grating_coupler_elliptical2( wgt=wg_strip, theta=np.pi / 4.0, length=30.0, taper_length=10.0, period=1.0, dutycycle=0.7, ridge=True, ridge_layers=(2, 0), teeth_list=None, port=(0, 0), direction="EAST", polarization="te", wavelength=1550, **kwargs ): """ Grating coupler Args: waveguide_template: object or function port (tuple): Cartesian coordinate of the input port direction (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) theta (float): Angle of the waveguide. Defaults to pi/4.0 length (float): Length of the total grating coupler region, measured from the output port. Defaults to 30.0 taper_length (float): Length of the taper before the grating coupler. Defaults to 10.0 period (float): Grating period. Defaults to 1.0 dutycycle (float): dutycycle, determines the size of the 'gap' by dutycycle=(period-gap)/period. Defaults to 0.7 ridge (boolean): If True, adds another layer to the grating coupler that can be used for partial etched gratings ridge_layers (tuple): Tuple specifying the layer/datatype of the ridge region. Defaults to (3,0) teeth_list (list): Can optionally pass a list of (gap, width) tuples to be used as the gap and teeth widths for irregularly spaced gratings. For example, [(0.6, 0.2), (0.7, 0.3), ...] would be a gap of 0.6, then a tooth of width 0.2, then gap of 0.7 and tooth of 0.3, and so on. Overrides *period*, *dutycycle*, and *length*. Defaults to None. .. plot:: :include-source: import pp c = pp.c.grating_coupler_elliptical2() pp.plotgds(c) """ c = pc.GratingCoupler( pp.call_if_func(wg_strip, **kwargs), theta=theta, length=length, taper_length=taper_length, period=period, dutycycle=dutycycle, ridge=ridge, ridge_layers=ridge_layers, teeth_list=teeth_list, port=port, direction=direction, ) c = picwriter2component(c) c.polarization = polarization c.wavelength = wavelength return c
def staircase( bend90=pp.c.bend_euler90, length_v=5.0, length_h=5.0, n_steps=4, waveguide_factory=waveguide, ): bend90 = pp.call_if_func(bend90) wgh = waveguide_factory(length=length_h, width=bend90.ports["W0"].width) wgv = waveguide_factory(length=length_v, width=bend90.ports["W0"].width) # Define a map between symbols and (component, input port, output port) string_to_device_in_out_ports = { "A": (bend90, "W0", "N0"), "B": (bend90, "N0", "W0"), "-": (wgh, "W0", "E0"), "|": (wgv, "W0", "E0"), } # Generate the sequence of staircases s = "-A|B" * n_steps + "-" # Create the component from the sequence c = component_sequence(s, string_to_device_in_out_ports, start_orientation=0) c.update_settings(n_bends=2 * n_steps) return c
def load(component=None, filepath=None, numports=None, **kwargs): """ load Sparameters for a component Args: component: component factory or instance filepath: numport number of ports **kwargs """ if filepath is None: component = pp.call_if_func(component, **kwargs) pins, f, s = pp.sp.load(component, filepath=filepath, dirpath=CONFIG["sp"], numports=numports) def interpolate_sp(freq): return interpolate(freq, f, s) m = Model() m.pins = pins m.s_params = (f, s) m.s_parameters = interpolate_sp m.freq_range = ( m.s_params[0][0], m.s_params[0][-1], ) #: The valid frequency range for this model. m.wavelengths = speed_of_light / np.array(f) m.s = s return m
def sweep_simulation(circuit, iport="input", oport="output", start=1500e-9, stop=1600e-9, num=2000, logscale=True, **kwargs): """ Plot Sparameter circuit transmission over wavelength Args: num: number of sampled points """ circuit = pp.call_if_func(circuit) simulation = SweepSimulation(circuit, start, stop, num) result = simulation.simulate() f, s = result.data(iport, oport) w = freq2wl(f) * 1e9 if logscale: s = 20 * np.log10(abs(s)) ylabel = "|S| (dB)" else: ylabel = "|S|" f, ax = plt.subplots() ax.plot(w, s) plt.xlabel("wavelength (nm)") plt.ylabel(ylabel) plt.title(circuit.name) return ax
def coupler_asymmetric( bend: Callable = bend_s, waveguide: Callable = waveguide, gap: float = 0.234, wg_width: float = 0.5, ) -> Component: """ bend coupled to straight waveguide Args: bend: waveguide: waveguide factory gap: um wg_width .. plot:: :include-source: import pp c = pp.c.coupler_asymmetric() pp.plotgds(c) """ bend = pp.call_if_func(bend, width=wg_width) wg = pp.call_if_func(waveguide, width=wg_width) w = bend.ports["W0"].width y = (w + gap) / 2 c = pp.Component() wg = wg.ref(position=(0, y), port_id="W0") bottom_bend = bend.ref(position=(0, -y), port_id="W0", v_mirror=True) c.add(wg) c.add(bottom_bend) # Using absorb here to have a flat cell and avoid # to have deeper hierarchy than needed c.absorb(wg) c.absorb(bottom_bend) port_width = 2 * w + gap c.add_port(name="W0", midpoint=[0, 0], width=port_width, orientation=180) c.add_port(port=bottom_bend.ports["E0"], name="E0") c.add_port(port=wg.ports["E0"], name="E1") return c
def ring( coupler90=pp.c.coupler90, coupler_straight=pp.c.coupler_straight, waveguide=pp.c.waveguide, bend=pp.c.bend_circular, length_y=2.0, length_x=4.0, gap=0.2, wg_width=0.5, ): """ single bus ring """ c = pp.Component() # define subcells coupler90 = pp.call_if_func(coupler90, gap=gap, width=wg_width) waveguide_x = pp.call_if_func(waveguide, length=length_x, width=wg_width) waveguide_y = pp.call_if_func(waveguide, length=length_y, width=wg_width) bend = pp.call_if_func(bend, width=wg_width) coupler_straight = pp.call_if_func(coupler_straight, gap=gap, length=length_x, width=wg_width) # add references to subcells cbl = c << coupler90 cbr = c << coupler90 cs = c << coupler_straight wyl = c << waveguide_y wyr = c << waveguide_y wx = c << waveguide_x btl = c << bend btr = c << bend # connect references wyr.connect(port="E0", destination=cbr.ports["N0"]) cs.connect(port="E0", destination=cbr.ports["W0"]) cbl.reflect(p1=(0, coupler90.y), p2=(1, coupler90.y)) cbl.connect(port="W0", destination=cs.ports["W0"]) wyl.connect(port="E0", destination=cbl.ports["N0"]) btl.connect(port="N0", destination=wyl.ports["W0"]) btr.connect(port="W0", destination=wyr.ports["W0"]) wx.connect(port="W0", destination=btl.ports["W0"]) return c
def loop_mirror( component: Callable = mmi1x2, bend90: Callable = bend_euler90 ) -> Component: c = pp.Component() component = pp.call_if_func(component) bend90 = pp.call_if_func(bend90) cref = c.add_ref(component) routes = route_manhattan( cref.ports["E0"], cref.ports["E1"], bend90=bend90, straight_factory=pp.c.waveguide, ) c.add(routes["references"]) c.add_port(name="W0", port=cref.ports["W0"]) c.absorb(cref) return c
def loop_mirror_rotated(component=mmi1x2, bend90=bend_euler90): c = pp.Component() component = pp.call_if_func(component) mirror = loop_mirror(component=component, bend90=bend90) mirror_rotated = mirror.ref(rotation=90) c.add(mirror_rotated) c.absorb(mirror_rotated) c.add_port(name="S0", port=mirror_rotated.ports["W0"]) return c
def disk( radius: float = 10.0, gap: float = 0.2, wrap_angle: int = 0, parity: int = 1, port: Tuple[int, int] = (0, 0), direction: str = "EAST", waveguide_template: Callable = wg_strip, **kwargs ) -> Component: """Disk Resonator Args: radius (float): Radius of the disk resonator gap (float): Distance between the bus waveguide and resonator wrap_angle (float): Angle in radians between 0 and pi (defaults to 0) determines how much the bus waveguide wraps along the resonator. 0 corresponds to a straight bus waveguide, pi corresponds to a bus waveguide wrapped around half of the resonator. parity (1 or -1): If 1, resonator to left of bus waveguide, if -1 resonator to the right port (tuple): Cartesian coordinate of the input port (x1, y1) direction (string): Direction that the component will point *towards*, can be of type 'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians) waveguide_template (WaveguideTemplate): Picwriter WaveguideTemplate object Other Parameters: wg_width: 0.5 wg_layer: pp.LAYER.WG[0] wg_datatype: pp.LAYER.WG[1] clad_layer: pp.LAYER.WGCLAD[0] clad_datatype: pp.LAYER.WGCLAD[1] bend_radius: 10 cladding_offset: 3 .. plot:: :include-source: import pp c = pp.c.disk(radius=10, wrap_angle=3.14/4) pp.plotgds(c) """ c = pc.Disk( pp.call_if_func(wg_strip, **kwargs), radius=radius, coupling_gap=gap, wrap_angle=wrap_angle, parity=parity, port=port, direction=direction, ) return picwriter2component(c)
def tlm( width=11.0, height=11.0, layers=[pp.LAYER.M1, pp.LAYER.M2, pp.LAYER.M3], vias=[via2, via3], ): """ Rectangular transition thru metal layers Args: name: component name width, height: rectangle parameters layers: layers on which to draw rectangles vias: vias to use to fill the rectangles Returns <pp.Component> """ # assert len(layers) - 1 == len(vias), "tlm: There should be N layers for N-1 vias" a = width / 2 b = height / 2 rect_pts = [(-a, -b), (a, -b), (a, b), (-a, b)] cmp = pp.Component() # Add metal rectangles for layer in layers: cmp.add_polygon(rect_pts, layer=layer) # Add vias for via in vias: via = pp.call_if_func(via) w = via.info["width"] h = via.info["height"] c = via.info["clearance"] period = via.info["period"] nb_vias_x = (width - w - 2 * c) / period + 1 nb_vias_y = (height - h - 2 * c) / period + 1 nb_vias_x = int(floor(nb_vias_x)) nb_vias_y = int(floor(nb_vias_y)) cw = (width - (nb_vias_x - 1) * period - w) / 2 ch = (height - (nb_vias_y - 1) * period - h) / 2 x0 = -a + cw + w / 2 y0 = -b + ch + h / 2 for i in range(nb_vias_x): for j in range(nb_vias_y): cmp.add(via.ref(position=(x0 + i * period, y0 + j * period))) return cmp
def add_tapers(component, taper): """ returns tapered component """ c = pp.Component(name=component.name + "_t") c.add_ref(component) for i, port in enumerate(component.ports.values()): taper_ref = c << pp.call_if_func(taper) taper_ref.connect(taper_ref.ports["2"].name, port) c.add_port(name="{}".format(i), port=taper_ref.ports["1"]) return c
def add_grating_couplers( component, grating_coupler=grating_coupler_te, layer_label=CONFIG["layer_label"], input_port_indexes=[0], ): """ returns component with grating ports and labels on each port """ component = pp.call_if_func(component) c = pp.Component(name=component.name + "_c") c.add_ref(component) grating_coupler = pp.call_if_func(grating_coupler) for i, port in enumerate(component.ports.values()): t_ref = c.add_ref(grating_coupler) t_ref.connect(list(t_ref.ports.values())[0], port) label = get_optical_text(port, grating_coupler, i) c.label(label, position=port.midpoint, layer=layer_label) return c
def coupler_adiabatic(length1: float = 20.0, length2: float = 50.0, length3: float = 30.0, wg_sep: float = 1.0, input_wg_sep: float = 3.0, output_wg_sep: float = 3.0, dw: float = 0.1, port: Tuple[int, int] = (0, 0), direction: str = "EAST", waveguide_template: Callable = wg_strip, **kwargs) -> Component: """ 50/50 adiabatic coupler Adiabatic Coupler Cell class. Design based on asymmetric adiabatic 3dB coupler designs, such as those from https://doi.org/10.1364/CLEO.2010.CThAA2, https://doi.org/10.1364/CLEO_SI.2017.SF1I.5, and https://doi.org/10.1364/CLEO_SI.2018.STh4B.4. Uses Bezier curves for the input, with poles set to half of the x-length of the S-bend. In this design, Region I is the first half of the input S-bend waveguide where the input waveguides widths taper by +dw and -dw, Region II is the second half of the S-bend waveguide with constant, unbalanced widths, Region III is the region where the two asymmetric waveguides gradually come together, Region IV is the coupling region where the waveguides taper back to the original width at a fixed distance from one another, and Region IV is the output S-bend waveguide. Args: length1 (float): Length of the region that gradually brings the two assymetric waveguides together. In this region the waveguide widths gradually change to be different by `dw`. length2 (float): Length of the coupling region, where the asymmetric waveguides gradually become the same width. length3 (float): Length of the output region where the two waveguides separate. wg_sep (float): Distance between the two waveguides, center-to-center, in the coupling region (Region 2). input_wg_sep (float): Separation of the two waveguides at the input, center-to-center. output_wg_sep (float): Separation of the two waveguides at the output, center-to-center. dw (float): Change in waveguide width. In Region 1, the top arm tapers to the waveguide width+dw/2.0, bottom taper to width-dw/2.0. port (tuple): Cartesian coordinate of the input port (top left). Defaults to (0,0). direction (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). Defaults to 'EAST'. waveguide_template: object or function Other Parameters: wg_width: 0.5 wg_layer: pp.LAYER.WG[0] wg_datatype: pp.LAYER.WG[1] clad_layer: pp.LAYER.WGCLAD[0] clad_datatype: pp.LAYER.WGCLAD[1] bend_radius: 10 cladding_offset: 3 """ c = pc.AdiabaticCoupler( pp.call_if_func(waveguide_template, **kwargs), length1=length1, length2=length2, length3=length3, wg_sep=wg_sep, input_wg_sep=input_wg_sep, output_wg_sep=output_wg_sep, dw=dw, port=port, direction=direction, ) c = picwriter2component(c) c = auto_rename_ports(c) return c
def coupler_full(length: float = 40.0, gap: float = 0.5, dw: float = 0.1, angle: float = np.pi / 6, parity: int = 1, port: Tuple[int, int] = (0, 0), direction: str = "EAST", waveguide_template: Callable = wg_strip, **kwargs) -> Component: """ Adiabatic Full Coupler. Design based on asymmetric adiabatic full coupler designs, such as the one reported in 'Integrated Optic Adiabatic Devices on Silicon' by Y. Shani, et al (IEEE Journal of Quantum Electronics, Vol. 27, No. 3 March 1991). In this design, Region I is the first half of the input S-bend waveguide where the input waveguides widths taper by +dw and -dw, Region II is the second half of the S-bend waveguide with constant, unbalanced widths, Region III is the coupling region where the waveguides from unbalanced widths to balanced widths to reverse polarity unbalanced widths, Region IV is the fixed width waveguide that curves away from the coupling region, and Region V is the final curve where the waveguides taper back to the regular width specified in the waveguide template. Args: length (float): Length of the coupling region. gap (float): Distance between the two waveguides. dw (float): Change in waveguide width. Top arm tapers to the waveguide width - dw, bottom taper to width - dw. angle (float): Angle in radians (between 0 and pi/2) at which the waveguide bends towards the coupling region. Default=pi/6. parity (integer -1 or 1): If -1, mirror-flips the structure so that the input port is actually the *bottom* port. Default = 1. port (tuple): Cartesian coordinate of the input port (AT TOP if parity=1, AT BOTTOM if parity=-1). Defaults to (0,0). direction (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians). Defaults to 'EAST'. waveguide_template (WaveguideTemplate): Picwriter WaveguideTemplate object Other Parameters: wg_width: 0.5 wg_layer: pp.LAYER.WG[0] wg_datatype: pp.LAYER.WG[1] clad_layer: pp.LAYER.WGCLAD[0] clad_datatype: pp.LAYER.WGCLAD[1] bend_radius: 10 cladding_offset: 3 .. plot:: :include-source: import pp c = pp.c.coupler_full(length=40, gap=0.2, dw=0.1) pp.plotgds(c) """ c = pc.FullCoupler( pp.call_if_func(waveguide_template, **kwargs), length=length, gap=gap, dw=dw, angle=angle, parity=parity, port=port, direction=direction, ) return picwriter2component(c)