Expand the footprint for downstream devices ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In photonic circuit layout design, there are often scenarios where space needs to be expanded for downstream device placement and connection. For example, ``DirectionalCoupler`` is often used for optical signal distribution, where the optical signal is passed through the waveguide to the downstream devices. **PhotoCAD** includes tools to expand the layout space for downstream devices, enabling the design of optical signals to be assigned to downstream device layouts via ``DirectionalCoupler``. HFanout ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In this example, we write the ``HFanout`` function to expand the downstream layout space horizontally. When using the HFanout function, we can customize the length and spacing of the ``Directionalcoupler(DC)``, the spacing from the expansion port to the ``DC``, the distance between the expansion ports, and the waveguide connection transition at the end of the expansion port according to the defined type. Full script ----------------------------- :: from typing import Callable, List, Optional, Sequence, Tuple, cast from fnpcell import all as fp from gpdk.components.sbend.sbend import SBendPair from gpdk.components.straight.straight import Straight from gpdk.technology import get_technology from gpdk.util import all as util class HFanout(fp.PCell): """ Attributes: device: device whose ports need fanout left_spacing: spacing between left ports right_spacing: spacing between right ports bend_degrees: defaults to 30 degrees bend_factories: a callable which receives an `IWaveguideType` and returns an `IBendWaveguideFactory` device_left_ports: Optional, device left ports from top to bottom device_right_ports: Optional, device right ports from bottom to top left_distance: Optional right_distance: Optional left_ports: Optional, port options for left ports right_ports: Optional, port options for right ports left_waveguide_type: Optional, type of left waveguide right_waveguide_type: Optional, type of right waveguide connect_length: defaults to 10, distance between generated port and sbend Examples: ```python from gpdk.technology.bend_factory import EulerBendFactory def bend_factories(waveguide_type: fp.IWaveguideType): if waveguide_type == TECH.WG.FWG.C.WIRE: return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type) return waveguide_type.bend_factory device = HFanout(device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE) fp.plot(device) ``` ![HFanout](images/h_fanout.png) """ device: fp.IDevice = fp.DeviceParam() left_spacing: float = fp.PositiveFloatParam() right_spacing: float = fp.PositiveFloatParam() bend_degrees: float = fp.DegreeParam(default=30, min=0, max=90, invalid=[0]) bend_factories: Optional[Callable[[fp.IWaveguideType], fp.IBendWaveguideFactory]] = fp.Param(required=False) device_left_ports: Sequence[str] = fp.NameListParam(required=False, doc="device left ports from top to bottom") device_right_ports: Sequence[str] = fp.NameListParam(required=False, doc="device right ports from bottom to top") left_distance: Optional[float] = fp.FloatParam(required=False, min=0) right_distance: Optional[float] = fp.FloatParam(required=False, min=0) left_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False) right_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False) left_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False) right_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False) connect_length: float = fp.PositiveFloatParam(default=10) def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]: insts, elems, ports = super().build() TECH = get_technology() device = self.device left_spacing = self.left_spacing right_spacing = self.right_spacing bend_degrees = self.bend_degrees bend_factories = self.bend_factories device_left_ports = self.device_left_ports device_right_ports = self.device_right_ports left_distance = self.left_distance right_distance = self.right_distance left_ports = self.left_ports right_ports = self.right_ports left_waveguide_type = self.left_waveguide_type right_waveguide_type = self.right_waveguide_type connect_length = self.connect_length if device_left_ports is None: device_left_ports = [port.name for port in util.ports.get_left_ports(device)] device_left_ports = list(device_left_ports) if device_right_ports is None: device_right_ports = [port.name for port in util.ports.get_right_ports(device, reverse=True)] device_right_ports = list(device_right_ports) left_ports = left_ports or device_left_ports right_ports = right_ports or device_right_ports result_left_ports: List[fp.IOwnedTerminal] = [] left_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = [] left_count = len(device_left_ports) if len(left_ports) != left_count: raise AssertionError("len(left_ports) must be equal to len(device_left_ports)") for i in range(left_count // 2): device_ltop_port = cast(fp.IOwnedPort, device[device_left_ports[i]]) ltop_port = device_ltop_port device_lbottom_port = cast(fp.IOwnedPort, device[device_left_ports[left_count - i - 1]]) lbottom_port = device_lbottom_port ltop_distance = left_distance lbottom_distance = left_distance if ltop_distance: ltop_distance -= connect_length * 2 if lbottom_distance: lbottom_distance -= connect_length * 2 ltop_transition = None lbottom_transition = None if left_waveguide_type: if ltop_port.waveguide_type != left_waveguide_type: if not ltop_distance: raise AssertionError("left_distance is required for auto transition") ltop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[ltop_port.waveguide_type >> left_waveguide_type] ltop_distance -= fp.distance_between(ltop_transition[port_in].position, ltop_transition[port_out].position) if lbottom_port.waveguide_type != left_waveguide_type: if not lbottom_distance: raise AssertionError("left_distance is required for auto transition") lbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lbottom_port.waveguide_type >> left_waveguide_type] lbottom_distance -= fp.distance_between(lbottom_transition[port_in].position, lbottom_transition[port_out].position) lconnect_top1 = Straight( name="lctop1", length=connect_length, waveguide_type=ltop_port.waveguide_type, ) lconnect_top2 = Straight( name="lctop2", length=connect_length, waveguide_type=ltop_port.waveguide_type, ) lconnect_bottom1 = Straight( name="lcbottom1", length=connect_length, waveguide_type=lbottom_port.waveguide_type, ) lconnect_bottom2 = Straight( name="lcbottom2", length=connect_length, waveguide_type=lbottom_port.waveguide_type, ) ltop_sbend, lbottom_sbend = SBendPair( top_distance=ltop_distance, bottom_distance=lbottom_distance, left_spacing=left_spacing * (left_count - i * 2 - 1), right_spacing=fp.distance_between(ltop_port.position, lbottom_port.position), bend_degrees=bend_degrees, top_type=ltop_port.waveguide_type, bottom_type=lbottom_port.waveguide_type, top_bend_factory=bend_factories and bend_factories(ltop_port.waveguide_type), bottom_bend_factory=bend_factories and bend_factories(lbottom_port.waveguide_type), ) left_joints.append(ltop_port <= lconnect_top1["op_1"]) left_joints.append(lconnect_top1["op_0"] <= ltop_sbend["op_1"]) left_joints.append(ltop_sbend["op_0"] <= lconnect_top2["op_1"]) ltop_port = lconnect_top2["op_0"] left_joints.append(lbottom_port <= lconnect_bottom1["op_1"]) left_joints.append(lconnect_bottom1["op_0"] <= lbottom_sbend["op_1"]) left_joints.append(lbottom_sbend["op_0"] <= lconnect_bottom2["op_1"]) lbottom_port = lconnect_bottom2["op_0"] if ltop_transition: left_joints.append(ltop_port <= ltop_transition["op_0"]) ltop_port = ltop_transition["op_1"] if lbottom_transition: left_joints.append(lbottom_port <= lbottom_transition["op_0"]) lbottom_port = lbottom_transition["op_1"] result_left_ports.insert(0, ltop_port) result_left_ports.append(lbottom_port) if not left_distance: left_distance = abs(ltop_port.position[0] - device_ltop_port.position[0]) if left_distance and left_count % 2: lindex = left_count // 2 lmiddle_port = cast(fp.IOwnedPort, device[device_left_ports[lindex]]) lmiddle_distance = left_distance lmiddle_transition = None if left_waveguide_type and lmiddle_port.waveguide_type != left_waveguide_type: if not lmiddle_distance: raise AssertionError("middle_distance is required for auto transition") lmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lmiddle_port.waveguide_type >> left_waveguide_type] lmiddle_distance -= fp.distance_between(lmiddle_transition[port_in].position, lmiddle_transition[port_out].position) lstraight = Straight( name="lmiddle", length=lmiddle_distance, waveguide_type=lmiddle_port.waveguide_type, ) left_joints.append(lmiddle_port <= lstraight["op_1"]) lmiddle_port = lstraight["op_0"] if lmiddle_transition: left_joints.append(lmiddle_port <= lmiddle_transition["op_0"]) lmiddle_port = lmiddle_transition["op_1"] result_left_ports.insert(lindex, lmiddle_port) ############################ result_right_ports: List[fp.IOwnedTerminal] = [] right_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = [] right_count = len(device_right_ports) if len(right_ports) != right_count: raise AssertionError("len(right_ports) must be equal to len(device_right_ports)") for i in range(right_count // 2): device_rbottom_port = cast(fp.IOwnedPort, device[device_right_ports[i]]) rbottom_port = device_rbottom_port device_rtop_port = cast(fp.IOwnedPort, device[device_right_ports[right_count - i - 1]]) rtop_port = device_rtop_port rbottom_distance = right_distance rtop_distance = right_distance if rbottom_distance: rbottom_distance -= connect_length * 2 if rtop_distance: rtop_distance -= connect_length * 2 rbottom_transition = None rtop_transition = None if right_waveguide_type: if rbottom_port.waveguide_type != right_waveguide_type: if not rbottom_distance: raise AssertionError("right_distance is required for auto transition") rbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rbottom_port.waveguide_type >> right_waveguide_type] rbottom_distance -= fp.distance_between(rbottom_transition[port_in].position, rbottom_transition[port_out].position) if rtop_port.waveguide_type != right_waveguide_type: if not rtop_distance: raise AssertionError("right_distance is required for auto transition") rtop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rtop_port.waveguide_type >> right_waveguide_type] rtop_distance -= fp.distance_between(rtop_transition[port_in].position, rtop_transition[port_out].position) rconnect_top1 = Straight( name="rctop1", length=connect_length, waveguide_type=rtop_port.waveguide_type, ) rconnect_top2 = Straight( name="rctop2", length=connect_length, waveguide_type=rtop_port.waveguide_type, ) rconnect_bottom1 = Straight( name="rcbottom1", length=connect_length, waveguide_type=rbottom_port.waveguide_type, ) rconnect_bottom2 = Straight( name="rcbottom2", length=connect_length, waveguide_type=rbottom_port.waveguide_type, ) rtop_sbend, rbottom_sbend = SBendPair( top_distance=rtop_distance, bottom_distance=rbottom_distance, left_spacing=fp.distance_between(rtop_port.position, rbottom_port.position), right_spacing=right_spacing * (right_count - i * 2 - 1), bend_degrees=bend_degrees, top_type=rtop_port.waveguide_type, bottom_type=rbottom_port.waveguide_type, top_bend_factory=bend_factories and bend_factories(rtop_port.waveguide_type), bottom_bend_factory=bend_factories and bend_factories(rbottom_port.waveguide_type), ) right_joints.append(rbottom_port <= rconnect_bottom1["op_0"]) right_joints.append(rconnect_bottom1["op_1"] <= rbottom_sbend["op_0"]) right_joints.append(rbottom_sbend["op_1"] <= rconnect_bottom2["op_0"]) rbottom_port = rconnect_bottom2["op_1"] right_joints.append(rtop_port <= rconnect_top1["op_0"]) right_joints.append(rconnect_top1["op_1"] <= rtop_sbend["op_0"]) right_joints.append(rtop_sbend["op_1"] <= rconnect_top2["op_0"]) rtop_port = rconnect_top2["op_1"] if rbottom_transition: right_joints.append(rbottom_port <= rbottom_transition["op_0"]) rbottom_port = rbottom_transition["op_1"] if rtop_transition: right_joints.append(rtop_port <= rtop_transition["op_0"]) rtop_port = rtop_transition["op_1"] result_right_ports.insert(0, rbottom_port) result_right_ports.append(rtop_port) if not right_distance: right_distance = abs(rbottom_port.position[0] - device_rbottom_port.position[0]) if right_distance and right_count % 2: rindex = right_count // 2 rmiddle_port = cast(fp.IOwnedPort, device[device_right_ports[rindex]]) rmiddle_distance = left_distance rmiddle_transition = None if right_waveguide_type and rmiddle_port.waveguide_type != right_waveguide_type: if not rmiddle_distance: raise AssertionError("middle_distance is required for auto transition") rmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rmiddle_port.waveguide_type >> right_waveguide_type] rmiddle_distance -= fp.distance_between(rmiddle_transition[port_in].position, rmiddle_transition[port_out].position) rstraight = Straight( name="rmiddle", length=right_distance, waveguide_type=rmiddle_port.waveguide_type, ) right_joints.append(rmiddle_port <= rstraight["op_0"]) rmiddle_port = rstraight["op_1"] if rmiddle_transition: left_joints.append(rmiddle_port <= rmiddle_transition["op_0"]) rmiddle_port = rmiddle_transition["op_1"] result_right_ports.insert(rindex, rmiddle_port) used_port_names = frozenset((device_left_ports or []) + (device_right_ports or [])) unused_ports = [port for port in device.ports if port.name not in used_port_names] connected = fp.Connected( joints=left_joints + right_joints, ports=( ([port.with_name(left_ports[i]) for i, port in enumerate(result_left_ports)]) + ([port.with_name(right_ports[i]) for i, port in enumerate(result_right_ports)]) + unused_ports ), ) insts += connected ports += connected.ports return insts, elems, ports if __name__ == "__main__": from gpdk.util.path import local_output_file gds_file = local_output_file(__file__).with_suffix(".gds") library = fp.Library() TECH = get_technology() # ============================================================= # fmt: off from gpdk.components.directional_coupler.directional_coupler_sbend import DirectionalCouplerSBend from gpdk.components.mmi.mmi import Mmi from gpdk.technology.waveguide_factory import EulerBendFactory def bend_factories(waveguide_type: fp.IWaveguideType): if waveguide_type == TECH.WG.FWG.C.WIRE: return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type) return waveguide_type.bend_factory library += [ HFanout( name="dc_f0", device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), # for DEMO left_spacing=20, right_spacing=40, left_distance=50, right_distance=100, bend_degrees=30, device_left_ports=["op_0"], device_right_ports=["op_1"], # left_ports=["op_0", "op_1", "op_2", "op_3"], ), HFanout( name="dc_f1", device=DirectionalCouplerSBend( name="0", coupler_length=24, coupler_spacing=2.8, waveguide_type=TECH.WG.FWG.C.WIRE, ), # for DEMO left_spacing=20, right_spacing=40, left_distance=50, right_distance=100, # bend_degrees=60, # radius_eff=7, device_left_ports=[ "op_0", ], device_right_ports=["op_2", "op_3"], left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, # ports=["op_0", "op_1", "op_2", "op_3"], ).translated(0, 50), HFanout( name="dc_f1", device=DirectionalCouplerSBend( name="0", coupler_length=24, coupler_spacing=2.8, waveguide_type=TECH.WG.FWG.C.WIRE, ), # for DEMO left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, device_left_ports=[ "op_0", ], device_right_ports=["op_2", "op_3"], left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, ).translated(0, 150), HFanout( name="dc_f1", device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, ).translated(0, 250), ] # fmt: on # ============================================================= fp.export_gds(library, file=gds_file) # fp.plot(library) Section Script Definition ----------------------------------------------- Importing libraries and modules ======================================= :: from typing import Callable, List, Optional, Sequence, Tuple, cast from fnpcell import all as fp from gpdk.components.sbend.sbend import SBendPair from gpdk.components.straight.straight import Straight from gpdk.technology import get_technology from gpdk.util import all as util Define ``HFanout`` ======================== :: class HFanout(fp.PCell): """ Attributes: device: device whose ports need fanout left_spacing: spacing between left ports right_spacing: spacing between right ports bend_degrees: defaults to 30 degrees bend_factories: a callable which receives an `IWaveguideType` and returns an `IBendWaveguideFactory` device_left_ports: Optional, device left ports from top to bottom device_right_ports: Optional, device right ports from bottom to top left_distance: Optional right_distance: Optional left_ports: Optional, port options for left ports right_ports: Optional, port options for right ports left_waveguide_type: Optional, type of left waveguide right_waveguide_type: Optional, type of right waveguide connect_length: defaults to 10, distance between generated port and sbend Examples: ```python from gpdk.technology.bend_factory import EulerBendFactory def bend_factories(waveguide_type: fp.IWaveguideType): if waveguide_type == TECH.WG.FWG.C.WIRE: return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type) return waveguide_type.bend_factory device = HFanout(device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE) fp.plot(device) ``` ![HFanout](images/h_fanout.png) """ device: fp.IDevice = fp.DeviceParam() left_spacing: float = fp.PositiveFloatParam() right_spacing: float = fp.PositiveFloatParam() bend_degrees: float = fp.DegreeParam(default=30, min=0, max=90, invalid=[0]) bend_factories: Optional[Callable[[fp.IWaveguideType], fp.IBendWaveguideFactory]] = fp.Param(required=False) device_left_ports: Sequence[str] = fp.NameListParam(required=False, doc="device left ports from top to bottom") device_right_ports: Sequence[str] = fp.NameListParam(required=False, doc="device right ports from bottom to top") left_distance: Optional[float] = fp.FloatParam(required=False, min=0) right_distance: Optional[float] = fp.FloatParam(required=False, min=0) left_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False) right_ports: Optional[fp.IPortOptions] = fp.PortOptionsParam(required=False) left_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False) right_waveguide_type: Optional[fp.IWaveguideType] = fp.WaveguideTypeParam(required=False) connect_length: float = fp.PositiveFloatParam(default=10) def build(self) -> Tuple[fp.InstanceSet, fp.ElementSet, fp.PortSet]: insts, elems, ports = super().build() TECH = get_technology() device = self.device left_spacing = self.left_spacing right_spacing = self.right_spacing bend_degrees = self.bend_degrees bend_factories = self.bend_factories device_left_ports = self.device_left_ports device_right_ports = self.device_right_ports left_distance = self.left_distance right_distance = self.right_distance left_ports = self.left_ports right_ports = self.right_ports left_waveguide_type = self.left_waveguide_type right_waveguide_type = self.right_waveguide_type connect_length = self.connect_length if device_left_ports is None: device_left_ports = cast(List[str], [port.name for port in util.ports.get_left_ports(device)]) device_left_ports = list(device_left_ports) if device_right_ports is None: device_right_ports = cast(List[str], [port.name for port in util.ports.get_right_ports(device, reverse=True)]) device_right_ports = list(device_right_ports) left_ports = left_ports or device_left_ports right_ports = right_ports or device_right_ports result_left_ports: List[fp.IOwnedTerminal] = [] left_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = [] left_count = len(device_left_ports) assert len(left_ports) == left_count, "len(left_ports) must be equal to len(device_left_ports)" for i in range(left_count // 2): device_ltop_port = cast(fp.IOwnedPort, device[device_left_ports[i]]) ltop_port = device_ltop_port device_lbottom_port = cast(fp.IOwnedPort, device[device_left_ports[left_count - i - 1]]) lbottom_port = device_lbottom_port ltop_distance = left_distance lbottom_distance = left_distance if ltop_distance: ltop_distance -= connect_length * 2 if lbottom_distance: lbottom_distance -= connect_length * 2 ltop_transition = None lbottom_transition = None if left_waveguide_type: if ltop_port.waveguide_type != left_waveguide_type: assert ltop_distance, "left_distance is required for auto transition" ltop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[ltop_port.waveguide_type >> left_waveguide_type] ltop_distance -= fp.distance_between(ltop_transition[port_in].position, ltop_transition[port_out].position) if lbottom_port.waveguide_type != left_waveguide_type: assert lbottom_distance, "left_distance is required for auto transition" lbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lbottom_port.waveguide_type >> left_waveguide_type] lbottom_distance -= fp.distance_between(lbottom_transition[port_in].position, lbottom_transition[port_out].position) lconnect_top1 = Straight( name="lctop1", length=connect_length, waveguide_type=ltop_port.waveguide_type, ) lconnect_top2 = Straight( name="lctop2", length=connect_length, waveguide_type=ltop_port.waveguide_type, ) lconnect_bottom1 = Straight( name="lcbottom1", length=connect_length, waveguide_type=lbottom_port.waveguide_type, ) lconnect_bottom2 = Straight( name="lcbottom2", length=connect_length, waveguide_type=lbottom_port.waveguide_type, ) ltop_sbend, lbottom_sbend = SBendPair( top_distance=ltop_distance, bottom_distance=lbottom_distance, left_spacing=left_spacing * (left_count - i * 2 - 1), right_spacing=fp.distance_between(ltop_port.position, lbottom_port.position), bend_degrees=bend_degrees, top_type=ltop_port.waveguide_type, bottom_type=lbottom_port.waveguide_type, top_bend_factory=bend_factories and bend_factories(ltop_port.waveguide_type), bottom_bend_factory=bend_factories and bend_factories(lbottom_port.waveguide_type), ) left_joints.append(ltop_port <= lconnect_top1["op_1"]) left_joints.append(lconnect_top1["op_0"] <= ltop_sbend["op_1"]) left_joints.append(ltop_sbend["op_0"] <= lconnect_top2["op_1"]) ltop_port = lconnect_top2["op_0"] left_joints.append(lbottom_port <= lconnect_bottom1["op_1"]) left_joints.append(lconnect_bottom1["op_0"] <= lbottom_sbend["op_1"]) left_joints.append(lbottom_sbend["op_0"] <= lconnect_bottom2["op_1"]) lbottom_port = lconnect_bottom2["op_0"] if ltop_transition: left_joints.append(ltop_port <= ltop_transition["op_0"]) ltop_port = ltop_transition["op_1"] if lbottom_transition: left_joints.append(lbottom_port <= lbottom_transition["op_0"]) lbottom_port = lbottom_transition["op_1"] result_left_ports.insert(0, ltop_port) result_left_ports.append(lbottom_port) if not left_distance: left_distance = abs(ltop_port.position[0] - device_ltop_port.position[0]) if left_distance and left_count % 2: lindex = left_count // 2 lmiddle_port = cast(fp.IOwnedPort, device[device_left_ports[lindex]]) lmiddle_distance = left_distance lmiddle_transition = None if left_waveguide_type and lmiddle_port.waveguide_type != left_waveguide_type: assert lmiddle_distance, "middle_distance is required for auto transition" lmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[lmiddle_port.waveguide_type >> left_waveguide_type] lmiddle_distance -= fp.distance_between(lmiddle_transition[port_in].position, lmiddle_transition[port_out].position) lstraight = Straight( name="lmiddle", length=lmiddle_distance, waveguide_type=lmiddle_port.waveguide_type, ) left_joints.append(lmiddle_port <= lstraight["op_1"]) lmiddle_port = lstraight["op_0"] if lmiddle_transition: left_joints.append(lmiddle_port <= lmiddle_transition["op_0"]) lmiddle_port = lmiddle_transition["op_1"] result_left_ports.insert(lindex, lmiddle_port) ############################ result_right_ports: List[fp.IOwnedTerminal] = [] right_joints: List[Tuple[fp.IOwnedTerminal, fp.IOwnedTerminal]] = [] right_count = len(device_right_ports) assert len(right_ports) == right_count, "len(right_ports) must be equal to len(device_right_ports)" for i in range(right_count // 2): device_rbottom_port = cast(fp.IOwnedPort, device[device_right_ports[i]]) rbottom_port = device_rbottom_port device_rtop_port = cast(fp.IOwnedPort, device[device_right_ports[right_count - i - 1]]) rtop_port = device_rtop_port rbottom_distance = right_distance rtop_distance = right_distance if rbottom_distance: rbottom_distance -= connect_length * 2 if rtop_distance: rtop_distance -= connect_length * 2 rbottom_transition = None rtop_transition = None if right_waveguide_type: if rbottom_port.waveguide_type != right_waveguide_type: assert rbottom_distance, "right_distance is required for auto transition" rbottom_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rbottom_port.waveguide_type >> right_waveguide_type] rbottom_distance -= fp.distance_between(rbottom_transition[port_in].position, rbottom_transition[port_out].position) if rtop_port.waveguide_type != right_waveguide_type: assert rtop_distance, "right_distance is required for auto transition" rtop_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rtop_port.waveguide_type >> right_waveguide_type] rtop_distance -= fp.distance_between(rtop_transition[port_in].position, rtop_transition[port_out].position) rconnect_top1 = Straight( name="rctop1", length=connect_length, waveguide_type=rtop_port.waveguide_type, ) rconnect_top2 = Straight( name="rctop2", length=connect_length, waveguide_type=rtop_port.waveguide_type, ) rconnect_bottom1 = Straight( name="rcbottom1", length=connect_length, waveguide_type=rbottom_port.waveguide_type, ) rconnect_bottom2 = Straight( name="rcbottom2", length=connect_length, waveguide_type=rbottom_port.waveguide_type, ) rtop_sbend, rbottom_sbend = SBendPair( top_distance=rtop_distance, bottom_distance=rbottom_distance, left_spacing=fp.distance_between(rtop_port.position, rbottom_port.position), right_spacing=right_spacing * (right_count - i * 2 - 1), bend_degrees=bend_degrees, top_type=rtop_port.waveguide_type, bottom_type=rbottom_port.waveguide_type, top_bend_factory=bend_factories and bend_factories(rtop_port.waveguide_type), bottom_bend_factory=bend_factories and bend_factories(rbottom_port.waveguide_type), ) right_joints.append(rbottom_port <= rconnect_bottom1["op_0"]) right_joints.append(rconnect_bottom1["op_1"] <= rbottom_sbend["op_0"]) right_joints.append(rbottom_sbend["op_1"] <= rconnect_bottom2["op_0"]) rbottom_port = rconnect_bottom2["op_1"] right_joints.append(rtop_port <= rconnect_top1["op_0"]) right_joints.append(rconnect_top1["op_1"] <= rtop_sbend["op_0"]) right_joints.append(rtop_sbend["op_1"] <= rconnect_top2["op_0"]) rtop_port = rconnect_top2["op_1"] if rbottom_transition: right_joints.append(rbottom_port <= rbottom_transition["op_0"]) rbottom_port = rbottom_transition["op_1"] if rtop_transition: right_joints.append(rtop_port <= rtop_transition["op_0"]) rtop_port = rtop_transition["op_1"] result_right_ports.insert(0, rbottom_port) result_right_ports.append(rtop_port) if not right_distance: right_distance = abs(rbottom_port.position[0] - device_rbottom_port.position[0]) if right_distance and right_count % 2: rindex = right_count // 2 rmiddle_port = cast(fp.IOwnedPort, device[device_right_ports[rindex]]) rmiddle_distance = left_distance rmiddle_transition = None if right_waveguide_type and rmiddle_port.waveguide_type != right_waveguide_type: assert rmiddle_distance, "middle_distance is required for auto transition" rmiddle_transition, (port_in, port_out) = TECH.AUTO_TRANSITION.DEFAULT[rmiddle_port.waveguide_type >> right_waveguide_type] rmiddle_distance -= fp.distance_between(rmiddle_transition[port_in].position, rmiddle_transition[port_out].position) rstraight = Straight( name="rmiddle", length=right_distance, waveguide_type=rmiddle_port.waveguide_type, ) right_joints.append(rmiddle_port <= rstraight["op_0"]) rmiddle_port = rstraight["op_1"] if rmiddle_transition: left_joints.append(rmiddle_port <= rmiddle_transition["op_0"]) rmiddle_port = rmiddle_transition["op_1"] result_right_ports.insert(rindex, rmiddle_port) used_port_names = frozenset((device_left_ports or []) + (device_right_ports or [])) unused_ports = [port for port in device.ports if port.name not in used_port_names] connected = fp.Connected( joints=left_joints + right_joints, ports=( ([port.with_name(left_ports[i]) for i, port in enumerate(result_left_ports)]) + ([port.with_name(right_ports[i]) for i, port in enumerate(result_right_ports)]) + unused_ports ), ) insts += connected ports += connected.ports return insts, elems, ports Create components and export layouts ============================================== :: if __name__ == "__main__": from pathlib import Path gds_file = Path(__file__).parent / "local" / Path(__file__).with_suffix(".gds").name library = fp.Library() TECH = get_technology() # ============================================================= # fmt: off from gpdk.components.directional_coupler.directional_coupler_sbend import DirectionalCouplerSBend from gpdk.components.mmi.mmi import Mmi from gpdk.technology.waveguide_factory import EulerBendFactory def bend_factories(waveguide_type: fp.IWaveguideType): if waveguide_type == TECH.WG.FWG.C.WIRE: return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type) return waveguide_type.bend_factory library += [ HFanout( name="dc_f0", device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), # for DEMO left_spacing=20, right_spacing=40, left_distance=50, right_distance=100, bend_degrees=30, device_left_ports=["op_0"], device_right_ports=["op_1"], # left_ports=["op_0", "op_1", "op_2", "op_3"], ), HFanout( name="dc_f1", device=DirectionalCouplerSBend( name="0", coupler_length=24, coupler_spacing=2.8, waveguide_type=TECH.WG.FWG.C.WIRE, ), # for DEMO left_spacing=20, right_spacing=40, left_distance=50, right_distance=100, # bend_degrees=60, # radius_eff=7, device_left_ports=[ "op_0", ], device_right_ports=["op_2", "op_3"], left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, # ports=["op_0", "op_1", "op_2", "op_3"], ).translated(0, 50), HFanout( name="dc_f1", device=DirectionalCouplerSBend( name="0", coupler_length=24, coupler_spacing=2.8, waveguide_type=TECH.WG.FWG.C.WIRE, ), # for DEMO left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, device_left_ports=[ "op_0", ], device_right_ports=["op_2", "op_3"], left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, ).translated(0, 150), HFanout( name="dc_f1", device=Mmi(waveguide_type=TECH.WG.FWG.C.WIRE), left_spacing=120, right_spacing=120, left_distance=100, right_distance=100, bend_factories=bend_factories, left_waveguide_type=TECH.WG.SWG.C.WIRE, right_waveguide_type=TECH.WG.SWG.C.WIRE, ).translated(0, 250), ] # fmt: on # ============================================================= fp.export_gds(library, file=gds_file) # fp.plot(library) When defining the layout output, the length and spacing of the ``DC``, the extension distance of the left and right side ports, the spacing of the same side port, and the waveguide type of the extension port can be flexibly controlled by the script parameters, the detailed description of which can be found in the explanation section of the source code. GDS Layout ----------------- After running the script, you can see in the layout tool that since the script calls ``HFanout`` to generate ``dc_f0`` and ``dc_f1`` and so on, ``HFanout_dc_f0``, ``HFamout_dc_f1``, ``HFamout_dc_f1_x1`` and ``HFamout_dc_f1_x2`` can be seen in the layout cell list. .. image:: ../images/hfanout1.png The four devices are visible in the same layout. .. image:: ../images/hfanout2.png #. ``dc_f0`` has a complete definition of the left and right ports in the script, with a single waveguide type, and its generated version is relatively simple. #. ``dc_f1`` defines in the script that the waveguide type of the extended port is ``SWG``, while the original waveguide type of ``DC`` is ``FWG``, and the transition from ``FWG`` to ``SWG`` is achieved by using the waveguide transition unit during the generation of the layout. At the same time, it defines only three ports, i.e., the left side ``op_0``, the right side ``op_2``, and ``op_3``, so only one port on the left side of ``DC`` is extended in the layout. #. The difference between ``dc_f1_x1`` and ``dc_f1_x2`` compared to ``dc_f1`` and ``dc_f0`` is the use of ``bend_factories`` to specify the type of waveguide curve when connection.