Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #13
0
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
Beispiel #14
0
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
Beispiel #15
0
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
Beispiel #17
0
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
Beispiel #18
0
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
Beispiel #19
0
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
Beispiel #20
0
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
Beispiel #22
0
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
Beispiel #23
0
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
Beispiel #24
0
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
Beispiel #25
0
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)
Beispiel #26
0
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
Beispiel #27
0
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
Beispiel #28
0
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
Beispiel #30
0
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)