Example #1
0
def extend_ports_list(
    ports: List[Port],
    extension_factory: ComponentOrFactory,
    extension_port_name: Optional[str] = None,
) -> Component:
    """Returns a component with the extensions for a list of ports.

    Args:
        ports: list of ports
        extension_factory: function for extension
        extension_port_name: to connect extension

    """
    c = Component()
    extension = (extension_factory()
                 if callable(extension_factory) else extension_factory)

    extension_port_name = extension_port_name or list(
        extension.ports.keys())[0]

    for i, port in enumerate(ports):
        extension_ref = c << extension
        extension_ref.connect(extension_port_name, port)

        for port_name, port in extension_ref.ports.items():
            # if port_name not in extension_port_name:
            c.add_port(f"{i}_{port_name}", port=port)

    c.auto_rename_ports()
    return c
Example #2
0
def coupler_straight(length: float = 10.0,
                     gap: float = 0.27,
                     straight: ComponentFactory = straight_function,
                     **kwargs) -> Component:
    """Coupler_straight with two parallel straights.

    Args:
        length: of straight
        gap: between straights
        straight: straight waveguide function
        kwargs: cross_section settings
    """
    component = Component()

    straight_component = (straight(length=length, **kwargs)
                          if callable(straight) else straight)

    top = component << straight_component
    bot = component << straight_component

    # bot.ymax = 0
    # top.ymin = gap

    top.movey(straight_component.info.width + gap)

    component.add_port("o1", port=bot.ports["o1"])
    component.add_port("o2", port=top.ports["o1"])
    component.add_port("o3", port=bot.ports["o2"])
    component.add_port("o4", port=top.ports["o2"])
    component.auto_rename_ports()
    return component
Example #3
0
def straight_array(n: int = 4,
                   spacing: float = 4.0,
                   straigth: ComponentOrFactory = straight_function,
                   **kwargs) -> Component:
    """Array of straights connected with grating couplers.

    useful to align the 4 corners of the chip

    Args:
        n: number of straights
        spacing: edge to edge straight spacing
        straigth: straigth straight Component or library
        **kwargs
    """

    c = Component()
    wg = straigth(**kwargs) if callable(straigth) else straigth

    for i in range(n):
        wref = c.add_ref(wg)
        wref.y += i * (spacing + wg.info.width)
        c.add_ports(wref.ports, prefix=str(i))

    c.auto_rename_ports()
    return c
Example #4
0
def crossing_from_taper(taper=lambda: taper(width2=2.5, length=3.0)):
    """
    Crossing based on a taper. The default is a dummy taper
    """
    taper = taper() if callable(taper) else taper

    c = Component()
    for i, a in enumerate([0, 90, 180, 270]):
        _taper = taper.ref(position=(0, 0), port_id="o2", rotation=a)
        c.add(_taper)
        c.add_port(name=i, port=_taper.ports["o1"])
        c.absorb(_taper)

    c.auto_rename_ports()
    return c
Example #5
0
def crossing(arm: ComponentFactory = crossing_arm) -> Component:
    """Waveguide crossing"""
    cx = Component()
    arm = arm() if callable(arm) else 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=port_id, port=p)
            port_id += 1
    cx.auto_rename_ports()
    return cx
Example #6
0
def mzi_lattice(
    coupler_lengths: Tuple[float, ...] = (10.0, 20.0),
    coupler_gaps: Tuple[float, ...] = (0.2, 0.3),
    delta_lengths: Tuple[float, ...] = (10.0,),
    mzi_factory: ComponentFactory = mzi_function,
    splitter: ComponentFactory = coupler_function,
    straight: ComponentFactory = straight_function,
    **kwargs,
) -> Component:
    r"""Mzi lattice filter.

    .. code::

               ______             ______
              |      |           |      |
              |      |           |      |
         cp1==|      |===cp2=====|      |=== .... ===cp_last===
              |      |           |      |
              |      |           |      |
             DL1     |          DL2     |
              |      |           |      |
              |______|           |      |
                                 |______|

    """
    assert len(coupler_lengths) == len(coupler_gaps)
    assert len(coupler_lengths) == len(delta_lengths) + 1

    c = Component()

    splitter_settings = dict(gap=coupler_gaps[0], length=coupler_lengths[0])
    combiner_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1])

    splitter1 = partial(splitter, **splitter_settings)
    combiner1 = partial(splitter, **combiner_settings)

    cp1 = splitter1()

    sprevious = c << mzi_factory(
        splitter=splitter1,
        combiner=combiner1,
        with_splitter=True,
        delta_length=delta_lengths[0],
        straight=straight,
        **kwargs,
    )

    stages = []

    for length, gap, delta_length in zip(
        coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:]
    ):

        splitter_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1])
        combiner_settings = dict(length=length, gap=gap)
        splitter1 = partial(splitter, **splitter_settings)
        combiner1 = partial(splitter, **combiner_settings)

        stage = c << mzi_factory(
            splitter=splitter1,
            combiner=combiner1,
            with_splitter=False,
            delta_length=delta_length,
            straight=straight,
            **kwargs,
        )
        splitter_settings = combiner_settings

        stages.append(stage)

    for stage in stages:
        stage.connect("o1", sprevious.ports["o4"])
        # stage.connect('o2', sprevious.ports['o1'])
        sprevious = stage

    for port in cp1.get_ports_list(orientation=180):
        c.add_port(port.name, port=port)

    for port in sprevious.get_ports_list(orientation=0):
        c.add_port(f"o_{port.name}", port=port)

    c.auto_rename_ports()
    return c
Example #7
0
def mzi_lattice(
    coupler_lengths: Tuple[float, ...] = (10.0, 20.0),
    coupler_gaps: Tuple[float, ...] = (0.2, 0.3),
    delta_lengths: Tuple[float, ...] = (10.0, ),
    mzi: ComponentFactory = mzi_coupler,
    splitter: ComponentFactory = coupler_function,
    **kwargs,
) -> Component:
    r"""Mzi lattice filter.

    Args:
        coupler_lengths: list of length for each coupler
        coupler_gaps: list of coupler gaps
        delta_lengths: list of length differences
        mzi: function for the mzi
        splitter: splitter function

    keyword Args:
        length_y: vertical length for both and top arms
        length_x: horizontal length
        bend: 90 degrees bend library
        straight: straight function
        straight_y: straight for length_y and delta_length
        straight_x_top: top straight for length_x
        straight_x_bot: bottom straight for length_x
        cross_section: for routing (sxtop/sxbot to combiner)

    .. code::

               ______             ______
              |      |           |      |
              |      |           |      |
         cp1==|      |===cp2=====|      |=== .... ===cp_last===
              |      |           |      |
              |      |           |      |
             DL1     |          DL2     |
              |      |           |      |
              |______|           |      |
                                 |______|

    """
    assert len(coupler_lengths) == len(coupler_gaps)
    assert len(coupler_lengths) == len(delta_lengths) + 1

    c = Component()

    splitter_settings = dict(gap=coupler_gaps[0], length=coupler_lengths[0])
    combiner_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1])

    splitter1 = partial(splitter, **splitter_settings)
    combiner1 = partial(splitter, **combiner_settings)

    cp1 = splitter1()

    sprevious = c << mzi(
        splitter=splitter1,
        combiner=combiner1,
        with_splitter=True,
        delta_length=delta_lengths[0],
        **kwargs,
    )
    c.add_ports(sprevious.get_ports_list(port_type="electrical"))

    stages = []

    for length, gap, delta_length in zip(coupler_lengths[2:], coupler_gaps[2:],
                                         delta_lengths[1:]):

        splitter_settings = dict(gap=coupler_gaps[1],
                                 length=coupler_lengths[1])
        combiner_settings = dict(length=length, gap=gap)
        splitter1 = partial(splitter, **splitter_settings)
        combiner1 = partial(splitter, **combiner_settings)

        stage = c << mzi(
            splitter=splitter1,
            combiner=combiner1,
            with_splitter=False,
            delta_length=delta_length,
            **kwargs,
        )
        splitter_settings = combiner_settings

        stages.append(stage)
        c.add_ports(stage.get_ports_list(port_type="electrical"))

    for stage in stages:
        stage.connect("o1", sprevious.ports["o4"])
        # stage.connect('o2', sprevious.ports['o1'])
        sprevious = stage

    for port in cp1.get_ports_list(orientation=180, port_type="optical"):
        c.add_port(port.name, port=port)

    for port in sprevious.get_ports_list(orientation=0, port_type="optical"):
        c.add_port(f"o_{port.name}", port=port)

    c.auto_rename_ports()
    return c
def ring_double_heater(
        gap: float = 0.2,
        radius: float = 10.0,
        length_x: float = 0.01,
        length_y: float = 0.01,
        coupler_ring: ComponentFactory = coupler_ring_function,
        straight: ComponentFactory = straight_function,
        bend: Optional[ComponentFactory] = None,
        cross_section_heater: gf.types.CrossSectionFactory = gf.cross_section.
    strip_heater_metal,
        cross_section: CrossSectionFactory = strip,
        contact: gf.types.ComponentFactory = contact_heater_m3_mini,
        port_orientation: int = 90,
        contact_offset: Float2 = (0, 0),
        **kwargs) -> Component:
    """Double bus ring made of two couplers (ct: top, cb: bottom)
    connected with two vertical straights (sl: left, sr: right)
    includes heater on top

    Args:
        gap: gap between for coupler
        radius: for the bend and coupler
        length_x: ring coupler length
        length_y: vertical straight length
        coupler_ring: ring coupler function
        straight: straight function
        bend: bend function
        cross_section_heater:
        cross_section:
        contact:
        port_orientation: for electrical ports to promote from contact
        contact_offset: for each contact
        kwargs: cross_section settings

    .. code::

         --==ct==--
          |      |
          sl     sr length_y
          |      |
         --==cb==-- gap

          length_x

    """
    assert_on_2nm_grid(gap)

    coupler_component = (coupler_ring(gap=gap,
                                      radius=radius,
                                      length_x=length_x,
                                      bend=bend,
                                      cross_section=cross_section,
                                      bend_cross_section=cross_section_heater,
                                      **kwargs)
                         if callable(coupler_ring) else coupler_ring)
    straight_component = call_if_func(straight,
                                      length=length_y,
                                      cross_section=cross_section_heater,
                                      **kwargs)

    c = Component()
    cb = c.add_ref(coupler_component)
    ct = c.add_ref(coupler_component)
    sl = c.add_ref(straight_component)
    sr = c.add_ref(straight_component)

    sl.connect(port="o1", destination=cb.ports["o2"])
    ct.connect(port="o3", destination=sl.ports["o2"])
    sr.connect(port="o2", destination=ct.ports["o2"])
    c.add_port("o1", port=cb.ports["o1"])
    c.add_port("o2", port=cb.ports["o4"])
    c.add_port("o3", port=ct.ports["o4"])
    c.add_port("o4", port=ct.ports["o1"])

    c1 = c << contact()
    c2 = c << contact()
    c1.xmax = -length_x / 2 + cb.x - contact_offset[0]
    c2.xmin = +length_x / 2 + cb.x + contact_offset[0]
    c1.movey(contact_offset[1])
    c2.movey(contact_offset[1])
    c.add_ports(c1.get_ports_list(orientation=port_orientation), prefix="e1")
    c.add_ports(c2.get_ports_list(orientation=port_orientation), prefix="e2")
    c.auto_rename_ports()
    return c
Example #9
0
def coupler_ring(
        gap: float = 0.2,
        radius: float = 5.0,
        length_x: float = 4.0,
        coupler90: ComponentFactory = coupler90function,
        bend: Optional[ComponentFactory] = None,
        coupler_straight: ComponentFactory = coupler_straight_function,
        cross_section: CrossSectionFactory = strip,
        **kwargs) -> Component:
    r"""Coupler for ring.

    Args:
        gap: spacing between parallel coupled straight waveguides.
        radius: of the bends.
        length_x: length of the parallel coupled straight waveguides.
        coupler90: straight coupled to a 90deg bend.
        straight: library for straight waveguides.
        bend: library for bend
        coupler_straight: two parallel coupled straight waveguides.
        cross_section:
        **kwargs: cross_section settings

    .. code::

           2             3
           |             |
            \           /
             \         /
           ---=========---
         1    length_x    4


    """
    bend = bend or bend_euler

    c = Component()
    assert_on_2nm_grid(gap)

    # define subcells
    coupler90_component = (coupler90(gap=gap,
                                     radius=radius,
                                     bend=bend,
                                     cross_section=cross_section,
                                     **kwargs)
                           if callable(coupler90) else coupler90)
    coupler_straight_component = (coupler_straight(
        gap=gap, length=length_x, cross_section=cross_section, **
        kwargs) if callable(coupler_straight) else coupler_straight)

    # add references to subcells
    cbl = c << coupler90_component
    cbr = c << coupler90_component
    cs = c << coupler_straight_component

    # connect references
    y = coupler90_component.y
    cs.connect(port="o4", destination=cbr.ports["o1"])
    cbl.reflect(p1=(0, y), p2=(1, y))
    cbl.connect(port="o2", destination=cs.ports["o2"])

    c.absorb(cbl)
    c.absorb(cbr)
    c.absorb(cs)

    c.add_port("o1", port=cbl.ports["o3"])
    c.add_port("o2", port=cbl.ports["o4"])
    c.add_port("o3", port=cbr.ports["o3"])
    c.add_port("o4", port=cbr.ports["o4"])
    c.auto_rename_ports()
    return c
Example #10
0
def crossing45(
    crossing: ComponentFactory = crossing,
    port_spacing: float = 40.0,
    dx: Optional[float] = None,
    alpha: float = 0.08,
    npoints: int = 101,
) -> Component:
    r"""Returns 45deg crossing with bends.

    Args:
        crossing: crossing function
        port_spacing: target I/O port spacing
        dx: target length
        alpha: optimization parameter. diminish it for tight bends,
          increase it if raises assertion angle errors
        npoints: number of points.


    Implementation note: The 45 Degree crossing CANNOT be kept as an SRef since
    we only allow for multiples of 90Deg rotations in SRef

    .. code::

        ----   ----
            \ /
             X
            / \
        ---    ----

    """

    crossing = crossing() if callable(crossing) else crossing

    c = Component()
    _crossing = crossing.ref(rotation=45)
    c.add(_crossing)

    # Add bends
    p_e = _crossing.ports["o3"].midpoint
    p_w = _crossing.ports["o1"].midpoint
    p_n = _crossing.ports["o2"].midpoint
    p_s = _crossing.ports["o4"].midpoint

    # Flatten the crossing - not an SRef anymore
    c.absorb(_crossing)
    dx = dx or port_spacing
    dy = port_spacing / 2

    start_angle = 45
    end_angle = 0
    cpts = find_min_curv_bezier_control_points(
        start_point=p_e,
        end_point=(dx, dy),
        start_angle=start_angle,
        end_angle=end_angle,
        npoints=npoints,
        alpha=alpha,
    )

    bend = bezier(
        control_points=cpts,
        start_angle=start_angle,
        end_angle=end_angle,
        npoints=npoints,
    )

    tol = 1e-2
    assert abs(bend.info["start_angle"] -
               start_angle) < tol, bend.info["start_angle"]
    assert abs(bend.info["end_angle"] -
               end_angle) < tol, bend.info["end_angle"]

    b_tr = bend.ref(position=p_e, port_id="o1")
    b_tl = bend.ref(position=p_n, port_id="o1", h_mirror=True)
    b_bl = bend.ref(position=p_w, port_id="o1", rotation=180)
    b_br = bend.ref(position=p_s, port_id="o1", v_mirror=True)

    for cmp_ref in [b_tr, b_br, b_tl, b_bl]:
        # cmp_ref = _cmp.ref()
        c.add(cmp_ref)
        c.absorb(cmp_ref)

    c.info.bezier_length = bend.info.length
    c.info.crossing = crossing.info
    c.info.min_bend_radius = b_br.info.min_bend_radius

    c.bezier = bend
    c.crossing = crossing

    c.add_port("o1", port=b_br.ports["o2"])
    c.add_port("o2", port=b_tr.ports["o2"])
    c.add_port("o3", port=b_bl.ports["o2"])
    c.add_port("o4", port=b_tl.ports["o2"])
    c.snap_ports_to_grid()
    c.auto_rename_ports()
    return c
Example #11
0
def crossing_etched(
    width: float = 0.5,
    r1: float = 3.0,
    r2: float = 1.1,
    w: float = 1.2,
    L: float = 3.4,
    layer_wg: Layer = LAYER.WG,
    layer_slab: Layer = LAYER.SLAB150,
):
    """
    Waveguide crossing:
    - The full crossing has to be on WG layer (to start with a 220nm slab)
    - Then we etch the ellipses down to 150nm slabs and we keep linear taper at 220nm.
    What we write is what we etch on this step
    """

    # Draw the ellipses
    c = Component()
    _ellipse1 = c << ellipse(radii=(r1, r2), layer=layer_wg)
    _ellipse2 = c << ellipse(radii=(r2, r1), layer=layer_wg)
    c.absorb(_ellipse1)
    c.absorb(_ellipse2)

    a = L + w / 2
    h = width / 2

    taper_cross_pts = [
        (-a, h),
        (-w / 2, w / 2),
        (-h, a),
        (h, a),
        (w / 2, w / 2),
        (a, h),
        (a, -h),
        (w / 2, -w / 2),
        (h, -a),
        (-h, -a),
        (-w / 2, -w / 2),
        (-a, -h),
    ]

    c.add_polygon(taper_cross_pts, layer=layer_wg)

    # tapers_poly = c.add_polygon(taper_cross_pts, layer=layer_wg)
    # b = a - 0.1  # To make sure we get 4 distinct polygons when doing bool ops
    # tmp_polygon = [(-b, b), (b, b), (b, -b), (-b, -b)]
    # polys_etch = gdspy.fast_boolean([tmp_polygon], tapers_poly, "not", layer=layer_slab)
    # c.add(polys_etch)

    positions = [(a, 0), (0, a), (-a, 0), (0, -a)]
    angles = [0, 90, 180, 270]

    i = 0
    for p, angle in zip(positions, angles):
        c.add_port(
            name=str(i),
            midpoint=p,
            orientation=angle,
            width=width,
            layer=layer_wg,
        )
        i += 1

    c.auto_rename_ports()
    return c
Example #12
0
def mzi_arms(
    delta_length: float = 10.0,
    length_y: float = 0.8,
    length_x: float = 0.1,
    bend: ComponentOrFactory = bend_euler,
    straight: ComponentFactory = straight_function,
    straight_y: Optional[ComponentFactory] = None,
    straight_x_top: Optional[ComponentFactory] = None,
    straight_x_bot: Optional[ComponentFactory] = None,
    splitter: ComponentOrFactory = mmi1x2,
    combiner: Optional[ComponentFactory] = None,
    with_splitter: bool = True,
    delta_yright: float = 0,
    **kwargs,
) -> Component:
    """Mzi made with arms.

    This MZI code is slightly deprecated
    You can find a more robust mzi in gf.components.mzi

    Args:
        delta_length: bottom arm vertical extra length
        length_y: vertical length for both and top arms
        length_x: horizontal length
        bend: 90 degrees bend library
        straight: straight function
        straight_y: straight for length_y and delta_length
        straight_x_top: top straight for length_x
        straight_x_bot: bottom straight for length_x
        splitter: splitter function
        combiner: combiner function
        with_splitter: if False removes splitter
        delta_yright: extra length for right y-oriented waveguide
        kwargs: cross_section settings

    .. code::

                   __Lx__
                  |      |
                  Ly     Lyr (not a parameter)
                  |      |
        splitter==|      |==combiner
                  |      |
                  Ly     Lyr (not a parameter)
                  |      |
                  | delta_length/2
                  |      |
                  |__Lx__|

            ____________           __________
            |          |          |
            |          |       ___|
        ____|          |____
            | splitter   d1     d2  combiner
        ____|           ____
            |          |       ____
            |          |          |
            |__________|          |__________
    """
    combiner = combiner or splitter

    straight_x_top = straight_x_top or straight
    straight_x_bot = straight_x_bot or straight
    straight_y = straight_y or straight

    c = Component()
    cp1 = splitter() if callable(splitter) else splitter
    cp2 = combiner() if combiner else cp1

    if with_splitter:
        cin = c << cp1
    cout = c << cp2

    ports_cp1 = cp1.get_ports_list(clockwise=False)
    ports_cp2 = cp2.get_ports_list(clockwise=False)

    port_e1_cp1 = ports_cp1[1]
    port_e0_cp1 = ports_cp1[0]

    port_e1_cp2 = ports_cp2[1]
    port_e0_cp2 = ports_cp2[0]

    y1t = port_e1_cp1.y
    y1b = port_e0_cp1.y

    y2t = port_e1_cp2.y
    y2b = port_e0_cp2.y

    d1 = abs(y1t - y1b)  # splitter ports distance
    d2 = abs(y2t - y2b)  # combiner ports distance

    delta_symm_half = -delta_yright / 2

    if d2 > d1:
        length_y_left = length_y + (d2 - d1) / 2
        length_y_right = length_y
    else:
        length_y_right = length_y + (d1 - d2) / 2
        length_y_left = length_y

    _top_arm = mzi_arm(
        straight_x=straight_x_top,
        straight_y=straight_y,
        length_x=length_x,
        length_y_left=length_y_left + delta_symm_half,
        length_y_right=length_y_right + delta_symm_half + delta_yright,
        bend=bend,
        **kwargs,
    )

    top_arm = c << _top_arm

    bot_arm = c << mzi_arm(
        straight_x=straight_x_bot,
        straight_y=straight_y,
        length_x=length_x,
        length_y_left=length_y_left + delta_length / 2,
        length_y_right=length_y_right + delta_length / 2,
        bend=bend,
        **kwargs,
    )

    bot_arm.mirror()
    top_arm.connect("o1", port_e1_cp1)
    bot_arm.connect("o1", port_e0_cp1)
    cout.connect(port_e1_cp2.name, bot_arm.ports["o2"])
    if with_splitter:
        c.add_ports(cin.get_ports_list(orientation=180), prefix="in")
    else:
        c.add_port("o1", port=bot_arm.ports["o1"])
        c.add_port("o2", port=top_arm.ports["o1"])

    c.add_ports(cout.get_ports_list(orientation=0), prefix="out")
    c.add_ports(top_arm.get_ports_list(port_type="electrical"), prefix="top")
    c.add_ports(bot_arm.get_ports_list(port_type="electrical"), prefix="bot")
    c.auto_rename_ports()
    return c
Example #13
0
def coupler(gap: float = 0.236,
            length: float = 20.0,
            coupler_symmetric: ComponentFactory = coupler_symmetric_function,
            coupler_straight: ComponentFactory = coupler_straight_function,
            dy: float = 5.0,
            dx: float = 10.0,
            cross_section: CrossSectionFactory = strip,
            **kwargs) -> Component:
    r"""Symmetric coupler.

    Args:
        gap: between straights
        length: of coupling region
        coupler_symmetric
        coupler_straight
        dy: port to port vertical spacing
        dx: length of bend in x direction
        cross_section: factory
        kwargs: cross_section settings

    .. code::

               dx                                 dx
            |------|                           |------|
         o2 ________                           ______o3
                    \                         /           |
                     \        length         /            |
                      ======================= gap         | dy
                     /                       \            |
            ________/                         \_______    |
         o1                                          o4

                        coupler_straight  coupler_symmetric


    """
    length = snap_to_grid(length)
    assert_on_2nm_grid(gap)
    c = Component()

    sbend = coupler_symmetric(gap=gap,
                              dy=dy,
                              dx=dx,
                              cross_section=cross_section,
                              **kwargs)

    sr = c << sbend
    sl = c << sbend
    cs = c << coupler_straight(
        length=length, gap=gap, cross_section=cross_section, **kwargs)
    sl.connect("o2", destination=cs.ports["o1"])
    sr.connect("o1", destination=cs.ports["o4"])

    c.add_port("o1", port=sl.ports["o3"])
    c.add_port("o2", port=sl.ports["o4"])
    c.add_port("o3", port=sr.ports["o3"])
    c.add_port("o4", port=sr.ports["o4"])

    c.absorb(sl)
    c.absorb(sr)
    c.absorb(cs)
    c.info.length = sbend.info.length
    c.info.min_bend_radius = sbend.info.min_bend_radius
    c.auto_rename_ports()
    return c
Example #14
0
def mzi(
    delta_length: float = 10.0,
    length_y: float = 2.0,
    length_x: Optional[float] = 0.1,
    bend: ComponentOrFactory = bend_euler,
    straight: ComponentFactory = straight_function,
    straight_y: Optional[ComponentFactory] = None,
    straight_x_top: Optional[ComponentFactory] = None,
    straight_x_bot: Optional[ComponentFactory] = None,
    splitter: ComponentOrFactory = mmi1x2,
    combiner: Optional[ComponentFactory] = None,
    with_splitter: bool = True,
    port_e1_splitter: str = "o2",
    port_e0_splitter: str = "o3",
    port_e1_combiner: str = "o2",
    port_e0_combiner: str = "o3",
    nbends: int = 2,
    cross_section: CrossSectionFactory = strip,
) -> Component:
    """Mzi.

    Args:
        delta_length: bottom arm vertical extra length
        length_y: vertical length for both and top arms
        length_x: horizontal length. None uses to the straight_x_bot/top defaults
        bend: 90 degrees bend library
        straight: straight function
        straight_y: straight for length_y and delta_length
        straight_x_top: top straight for length_x
        straight_x_bot: bottom straight for length_x
        splitter: splitter function
        combiner: combiner function
        with_splitter: if False removes splitter
        port_e1_combiner: east top combiner port
        port_e0_splitter: east bot splitter port
        port_e1_splitter: east top splitter port
        port_e0_combiner: east bot combiner port
        nbends: from straight top/bot to combiner (at least 2)
        cross_section: for routing (sxtop/sxbot to combiner)

    .. code::

                       b2______b3
                      |  sxtop  |
              straight_y        |
                      |         |
                      b1        b4
            splitter==|         |==combiner
                      b5        b8
                      |         |
              straight_y        |
                      |         |
        delta_length/2          |
                      |         |
                     b6__sxbot__b7
                          Lx
    """
    combiner = combiner or splitter
    straight = partial(straight, cross_section=cross_section)

    straight_x_top = straight_x_top or straight
    straight_x_bot = straight_x_bot or straight
    straight_y = straight_y or straight
    bend_factory = bend
    bend = bend_factory(cross_section=cross_section)

    c = Component()
    cp1 = splitter() if callable(splitter) else splitter
    cp2 = combiner() if combiner else cp1

    if with_splitter:
        cp1 = c << cp1

    cp2 = c << cp2
    b5 = c << bend
    b5.mirror()
    b5.connect("o1", cp1.ports[port_e0_splitter])

    syl = c << straight_y(length=delta_length / 2 + length_y, )
    syl.connect("o1", b5.ports["o2"])
    b6 = c << bend
    b6.connect("o1", syl.ports["o2"])

    straight_x_bot = straight_x_bot(
        length=length_x) if length_x else straight_x_bot()
    sxb = c << straight_x_bot
    sxb.connect("o1", b6.ports["o2"])

    b1 = c << bend
    b1.connect("o1", cp1.ports[port_e1_splitter])

    sy = c << straight_y(length=length_y)
    sy.connect("o1", b1.ports["o2"])

    b2 = c << bend
    b2.connect("o2", sy.ports["o2"])
    straight_x_top = straight_x_top(
        length=length_x) if length_x else straight_x_top()
    sxt = c << straight_x_top
    sxt.connect("o1", b2.ports["o1"])

    cp2.mirror()
    cp2.xmin = sxt.ports["o2"].x + bend.info["radius"] * nbends + 0.1

    route = get_route(
        sxt.ports["o2"],
        cp2.ports[port_e1_combiner],
        straight=straight,
        bend=bend_factory,
        cross_section=cross_section,
    )
    c.add(route.references)
    route = get_route(
        sxb.ports["o2"],
        cp2.ports[port_e0_combiner],
        straight=straight,
        bend=bend_factory,
        cross_section=cross_section,
    )
    c.add(route.references)

    if with_splitter:
        c.add_ports(cp1.get_ports_list(orientation=180), prefix="in")
    else:
        c.add_port("o1", port=b1.ports["o1"])
        c.add_port("o2", port=b5.ports["o1"])
    c.add_ports(cp2.get_ports_list(orientation=0), prefix="out")
    c.add_ports(sxt.get_ports_list(port_type="electrical"), prefix="top")
    c.add_ports(sxb.get_ports_list(port_type="electrical"), prefix="bot")
    c.auto_rename_ports()
    return c