from __future__ import annotations
from collections.abc import Callable
from functools import partial
import numpy as np
import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.components.grating_coupler_elliptical_trenches import grating_coupler_te
from gdsfactory.components.straight import straight as straight_function
from gdsfactory.port import select_ports_optical
from gdsfactory.routing.get_input_labels import get_input_labels_dash
from gdsfactory.routing.route_fiber_array import route_fiber_array
from gdsfactory.routing.sort_ports import sort_ports_x
from gdsfactory.typings import (
AnchorSubset,
ComponentSpec,
ComponentSpecOrList,
CrossSectionSpec,
Floats,
LayerSpec,
Literal,
)
[docs]
@gf.cell_with_child
def add_fiber_array(
component: ComponentSpec = straight_function,
grating_coupler: ComponentSpecOrList = grating_coupler_te,
gc_port_name: str = "o1",
gc_port_name_fiber: str = "o2",
gc_port_labels: tuple[str, ...] | None = None,
io_rotation: int | None = None,
component_name: str | None = None,
select_ports: Callable = select_ports_optical,
cross_section: CrossSectionSpec = "xs_sc",
get_input_labels_function: Callable | None = get_input_labels_dash,
layer_label: LayerSpec | None = None,
dev_id: str | None = None,
text: ComponentSpec | None = None,
id_placement: Literal[AnchorSubset] = "center",
id_placement_offset: Floats = (0, 0),
**kwargs,
) -> Component:
"""Returns component with south routes and grating_couplers.
You can also use pads or other terminations instead of grating couplers.
Args:
component: component spec to connect to grating couplers.
grating_coupler: spec for route terminations.
gc_port_name: grating coupler input port name.
gc_port_name_fiber: grating coupler output port name.
gc_port_labels: grating coupler list of labels.
io_rotation: fiber coupler rotation in degrees. Defaults to None.
component_name: optional for the label.
select_ports: function to select ports.
cross_section: cross_section function.
get_input_labels_function: function to get input labels. None skips labels.
layer_label: optional layer for grating coupler label.
dev_id: device if if we want to add a visible label for the device.
text: optional componentSpec to generate the text for the dev_id
id_placement: placement of the id.
"c" = center (in between the center grating couplers)
"r" = right of the right-most gc
"l" = left of the left-most gc
"s" = center and below the gc in y
id_placement_offset: offset for the id placement.
Keyword Args:
bend: bend spec.
straight: straight spec.
taper: taper spec.
get_input_label_text_loopback_function: function to get input label test.
get_input_label_text_function: for labels.
fanout_length: if None, automatic calculation of fanout length.
max_y0_optical: in um.
with_loopback: True, adds loopback structures.
straight_separation: from edge to edge.
list_port_labels: None, adds TM labels to port indices in this list.
connected_port_list_ids: names of ports only for type 0 optical routing.
nb_optical_ports_lines: number of grating coupler lines.
force_manhattan: False
excluded_ports: list of port names to exclude when adding gratings.
grating_indices: list of grating coupler indices.
routing_straight: function to route.
routing_method: get_route.
optical_routing_type: None: auto, 0: no extension, 1: standard, 2: check.
input_port_indexes: to connect.
fiber_spacing: in um.
gc_rotation: fiber coupler rotation in degrees. Defaults to -90 for south IO.
.. plot::
:include-source:
import gdsfactory as gf
c = gf.components.crossing()
cc = gf.routing.add_fiber_array(
component=c,
optical_routing_type=2,
grating_coupler=gf.components.grating_coupler_elliptical_te,
with_loopback=False
)
cc.plot()
"""
get_input_labels_function = None if gc_port_labels else get_input_labels_function
component = gf.get_component(component)
if isinstance(grating_coupler, list):
gc = grating_coupler[0]
else:
gc = grating_coupler
gc = gf.get_component(gc)
if io_rotation is not None:
gc = gf.functions.rotate(gc, angle=io_rotation)
if gc_port_name not in gc.ports:
gc_ports = list(gc.ports.keys())
raise ValueError(f"gc_port_name = {gc_port_name!r} not in {gc_ports}")
orientation = gc.ports[gc_port_name].orientation
grating_coupler = (
[gf.get_component(i) for i in grating_coupler]
if isinstance(grating_coupler, list)
else gf.get_component(grating_coupler)
)
if io_rotation is not None:
if isinstance(grating_coupler, list):
grating_coupler = [
gf.functions.rotate(component=i, angle=io_rotation)
for i in grating_coupler
]
else:
grating_coupler = gf.functions.rotate(
component=grating_coupler, angle=io_rotation
)
grating_coupler = gf.functions.move_port_to_zero(
grating_coupler, port_name=gc_port_name
)
if int(orientation) != 180:
raise ValueError(
"add_fiber_array requires a grating coupler port facing west "
f"(orientation = 180). "
f"Got orientation = {orientation} degrees for port {gc_port_name!r}"
)
if gc_port_name not in gc.ports:
raise ValueError(f"gc_port_name={gc_port_name!r} not in {gc.ports.keys()}")
component_name = component_name or component.name
component_new = Component()
component_new.component = component
optical_ports = select_ports(component.ports)
optical_ports_names = list(optical_ports.keys())
if not optical_ports:
raise ValueError(f"No optical ports found in {component.name!r}")
(
elements,
io_gratings_lines,
ports_grating_input_waveguide,
ports_loopback,
ports_component,
) = route_fiber_array(
component=component,
grating_coupler=grating_coupler,
gc_port_name=gc_port_name,
gc_port_name_fiber=gc_port_name_fiber,
component_name=component_name,
cross_section=cross_section,
select_ports=select_ports,
get_input_labels_function=get_input_labels_function,
layer_label=layer_label,
**kwargs,
)
if len(elements) == 0:
return component
for e in elements:
component_new.add(e)
for io_gratings in io_gratings_lines:
component_new.add(io_gratings)
component_new.add_ref(component)
for port in component.ports.values():
if port.name not in optical_ports_names:
component_new.add_port(port.name, port=port)
ports = sort_ports_x(ports_grating_input_waveguide + ports_loopback)
if gc_port_labels:
for gc_port_label, port in zip(gc_port_labels, ports):
if layer_label:
component_new.add_label(
text=gc_port_label, layer=layer_label, position=port.center
)
for port_component, port_grating in zip(
ports_component, ports_grating_input_waveguide
):
component_new.add_port(
port_component.name,
port=port_grating,
)
if ports_loopback:
component_new.add_port("loopback1", port=ports_loopback[0])
component_new.add_port("loopback2", port=ports_loopback[1])
component_new.copy_child_info(component)
# Add a visible label to the structure if indicated
if text:
if dev_id is None:
# Check if there is info on the component
if "dev_id" in component.info:
dev_id = component.info["dev_id"]
if dev_id:
if id_placement == "center":
lab = component_new << text(text=dev_id, justify="center")
xs = [r.x for r in io_gratings_lines[0]]
ys = [r.y for r in io_gratings_lines[0]]
lab.center = (
np.average(xs) + id_placement_offset[0],
ys[0] + +id_placement_offset[1],
)
elif id_placement == "r":
lab = component_new << text(text=dev_id, justify="left")
xmax = [r.xmax for r in io_gratings_lines[0]]
# Include the loopback ports for xmax, xmin calculation
if ports_loopback:
xmax += [
r.xmax for r in io_gratings_lines[-1] + io_gratings_lines[-2]
]
ys = [r.y for r in io_gratings_lines[0]]
lab.xmin = np.max(xmax) + 20 + id_placement_offset[0]
lab.y = ys[0] + id_placement_offset[1]
elif id_placement == "l":
lab = component_new << text(text=dev_id, justify="right")
xmin = [r.xmin for r in io_gratings_lines[0]]
# Include the loopback ports for xmax, xmin calculation
if ports_loopback:
xmin += [
r.xmin for r in io_gratings_lines[-1] + io_gratings_lines[-2]
]
ys = [r.y for r in io_gratings_lines[0]]
lab.xmax = np.min(xmin) - 20 + id_placement_offset[0]
lab.y = ys[0] + id_placement_offset[1]
elif id_placement == "s":
lab = component_new << text(text=dev_id, justify="center")
xs = [r.x for r in io_gratings_lines[0]]
ymins = [r.ymin for r in io_gratings_lines[0]]
lab.center = (
np.average(xs) + id_placement_offset[0],
np.min(ymins) - 20 + id_placement_offset[1],
)
return component_new
def demo_te_and_tm():
c = gf.Component()
w = gf.components.straight()
wte = add_fiber_array(
component=w, grating_coupler=gf.components.grating_coupler_elliptical_te
)
wtm = add_fiber_array(
component=w, grating_coupler=gf.components.grating_coupler_elliptical_tm
)
c.add_ref(wte)
wtm_ref = c.add_ref(wtm)
wtm_ref.movey(wte.size_info.height)
return c
if __name__ == "__main__":
from gdsfactory.routing.get_input_labels import get_input_labels_dash
# test_type0()
gcte = gf.components.grating_coupler_te
gctm = gf.components.grating_coupler_tm
strip = partial(
gf.cross_section.cross_section,
width=1,
layer=(2, 0),
# bbox_layers=((61, 0), (62, 0)),
# bbox_offsets=(3, 3)
# cladding_layers=((61, 0), (62, 0)),
# cladding_offsets=(3, 3)
)
# from pprint import pprint
# layer_label = (66, 0)
# layer_label = (66, 5)
# cc = demo_tapers()
# cc = test_type1()
# pprint(cc.get_json())
# c = gf.components.coupler(gap=0.2, length=5.6)
# c = gf.components.straight()
# c = gf.components.mmi2x2()
# c = gf.components.ring_single()
c = gf.components.straight(length=0.0015)
# c = add_fiber_array(c)
c.show(show_ports=True)
# c = gf.components.spiral(direction="NORTH")
# c = gf.components.bend_euler(info=dict(doe="bends"))
# cc = add_fiber_array(
# c, layer_label=None, layer_label_loopback=None, with_loopback=True
# )
# cc = add_fiber_array(
# component=c,
# # optical_routing_type=0,
# # optical_routing_type=1,
# # optical_routing_type=2,
# # layer_label=layer_label,
# # get_route_factory=route_fiber_single,
# # get_route_factory=route_fiber_array,
# grating_coupler=gctm,
# # grating_coupler=[gcte, gctm, gcte, gctm],
# # grating_coupler=gf.functions.rotate(gcte, angle=180),
# auto_widen=True,
# # layer=(2, 0),
# # gc_port_labels=["loop_in", "in", "out", "loop_out"],
# cross_section=strip,
# info=dict(a=1),
# )
# cc.pprint_ports()
# cc.show(show_ports=True)