Source code for gdsfactory.components.component_lattice_generic

"""
In a multiple-topology Clements Scheme we can implement any universal photonic function.
"""

from __future__ import annotations

import numpy as np

from gdsfactory.cell import cell
from gdsfactory.component import Component
from gdsfactory.components.mzi import mzi2x2_2x2
from gdsfactory.components.straight import straight
from gdsfactory.port import select_ports_electrical
from gdsfactory.routing import get_route


def find_largest_component(component_list: list) -> Component:
    # TODO optimise speed here
    largest_component = component_list[0]
    for component_i in component_list:
        # If element is larger than largest component
        if (component_i.xsize * component_i.ysize) > (
            largest_component.xsize * largest_component.ysize
        ):
            largest_component = component_i
    return largest_component


[docs] @cell def component_lattice_generic( network: list[list] | None = None, ) -> Component: """ The shape of the `network` matrix determines the physical interconnection. Note that there should be at least S+1=N modes based on this formalism of interconnection, and the position of the component implements a connectivity in between the modes, and assumes a 2x2 network encoding. One nice functionality by this component is that it can generate a component lattice for generic variable components with different x and y pitches. Initially this will maximise the surface area required but different placement algorithms can compact the size. Args: network: A list of lists of components that are to be placed in the lattice. Returns: Component: A component lattice that implements the physical network. The placement matrix is in this form: .. math:: M = X & 0 & X 0 & P & 0 X & 0 & X :include-source: import gdsfactory as gf from gdsfactory.components.mzi import mzi2x2_2x2 example_component_lattice = [ [mzi2x2_2x2(), 0, mzi2x2_2x2()], [0, mzi2x2_2x2(delta_length=30.0), 0], [mzi2x2_2x2(), 0, mzi2x2_2x2()], ] c = gf.components.component_lattice_generic(example_component_lattice) Another example that demonstrates the generic-nature of this component lattice algorithm can be with an mixed set of actively driven and passiver interferometers. The placement matrix is in this form: .. math:: M = Y & 0 & A 0 & B & 0 C & 0 & Y :include-source: import gdsfactory as gf from gdsfactory.components import mzi2x2_2x2_phase_shifter, mzi2x2_2x2 example_mixed_component_lattice = [ [mzi2x2_2x2_phase_shifter(), 0, mzi2x2_2x2(delta_length=20.0)], [0, mzi2x2_2x2(delta_length=30.0), 0], [mzi2x2_2x2(delta_length=15.0), 0, mzi2x2_2x2_phase_shifter()], ] c = gf.components.component_lattice_generic( network=example_mixed_component_lattice ) # TODO implement balanced waveguide paths function per stage # TODO automatic electrical fanout? # TODO multiple placement optimization algorithms. """ network = network or [ [mzi2x2_2x2(), 0, mzi2x2_2x2()], [0, mzi2x2_2x2(delta_length=30.0), 0], [mzi2x2_2x2(), 0, mzi2x2_2x2()], ] element_references = list() network = np.array(network) # Check number of dimensions is 2 if network.ndim != 2: # Get the length and then width of the array raise AttributeError( "Physical network dimensions don't work." "Check the dimensional structure of your network matrix." ) C = Component() # Estimate the size of the network fabric elements_list = np.vstack([np.nonzero(network), network[np.nonzero(network)]]) largest_component = find_largest_component(elements_list[2]) # List of elements component_column_amount = len(network[0]) mode_amount = component_column_amount + 1 inter_stage_clearance_x_offset = 40 inter_stage_clearance_y_offset = 40 x_length = ( mode_amount * largest_component.xsize + mode_amount * inter_stage_clearance_x_offset ) y_length = ( mode_amount * largest_component.ysize + mode_amount * inter_stage_clearance_y_offset ) x_component_pitch = x_length / mode_amount y_component_pitch = y_length / mode_amount x_mode_pitch = x_length / mode_amount y_mode_pitch = y_length / mode_amount # each distinct operation on the network is a separate iteration for-loop so new # functionality can be extended and easily identified. # Create all the waveguides inputs and outputs # Originally implemented in gdsfactory array but got netlist errors interconnection_ports_array = [] for column_j in range(mode_amount): interconnection_ports_array.append([]) for row_i in range(mode_amount): straight_i = C << straight(length=1, width=0.5) interconnection_ports_array[column_j].extend([straight_i]) interconnection_ports_array[column_j][row_i].move( destination=(x_mode_pitch * column_j, -y_mode_pitch * row_i) ) if column_j == 0: # Inputs C.add_port( port=straight_i.ports["o1"], name="in_o_" + str(row_i), ) elif column_j == (mode_amount - 1): # Outputs C.add_port( port=straight_i.ports["o2"], name="out_o_" + str(row_i), ) # Place the components in between the corresponding waveguide modes j = 0 k = 0 for column_j in network: i = 0 for element_i in column_j: # Check if element is nonzero if element_i != 0: element_references.append(C << element_i) element_references[k].center = (0, 0) element_references[k].move( destination=( x_component_pitch * j + largest_component.xsize / 2 + inter_stage_clearance_x_offset / 2, -y_component_pitch * i - inter_stage_clearance_y_offset, ) ) k += 1 i += 1 j += 1 # Go position by position to place and connect everything, column by column j = 0 k = 0 for column_j in network: i = 0 # Connect the modes together for element_i in column_j: # Row in column if element_i != 0: # Connect the adjacent input waveguide ports to the first element columns # if j == 0: route_0 = get_route( interconnection_ports_array[j][i].ports["o2"], element_references[k].ports["o2"], radius=5, ) route_i = get_route( interconnection_ports_array[j][i + 1].ports["o2"], element_references[k].ports["o1"], radius=5, ) # Connect output of the component to the component route_0_out = get_route( interconnection_ports_array[j + 1][i].ports["o1"], element_references[k].ports["o3"], radius=5, ) route_i_out = get_route( interconnection_ports_array[j + 1][i + 1].ports["o1"], element_references[k].ports["o4"], radius=5, ) C.add(route_0.references) C.add(route_i.references) C.add(route_0_out.references) C.add(route_i_out.references) k += 1 elif element_i == 0: # When no element at junction, connect straight ahead between if i == 0: # If at start top row then just connect top route_i = get_route( interconnection_ports_array[j][i].ports["o2"], interconnection_ports_array[j + 1][i].ports["o1"], radius=5, ) C.add(route_i.references) elif i == (len(column_j) - 1): # If at end then connect bottom route_i = get_route( interconnection_ports_array[j][i + 1].ports["o2"], interconnection_ports_array[j + 1][i + 1].ports["o1"], radius=5, ) C.add(route_i.references) if column_j[i - 1] != 0: # If previous element nonzero then pass pass elif column_j[i - 1] == 0: # If previous element is zero then connect top straight route_i = get_route( interconnection_ports_array[j][i].ports["o2"], interconnection_ports_array[j + 1][i].ports["o1"], radius=5, ) C.add(route_i.references) elif column_j[i - 1] == 0: # If previous element is zero then connect top straight route_i = get_route( interconnection_ports_array[j][i].ports["o2"], interconnection_ports_array[j + 1][i].ports["o1"], radius=5, ) C.add(route_i.references) elif column_j[i - 1] != 0: # If previous element nonzero then pass pass i += 1 j += 1 # Append electrical ports to component to total connectivity can be constructed. j = 0 k = 0 for column_j in network: i = 0 for element_i in column_j: # Check if element is nonzero if element_i != 0: electrical_ports_list_i = select_ports_electrical( element_references[k].ports ).items() if len(electrical_ports_list_i) > 0: # Electrical ports exist in component for electrical_port_i in electrical_ports_list_i: C.add_port( port=electrical_port_i[1], name=electrical_port_i[0] + "_" + str(i) + "_" + str(j), ) # Row column notation k += 1 i += 1 j += 1 return C
if __name__ == "__main__": # from gdsfactory.components.mzi import mzi2x2_2x2 # from gdsfactory.components.mzi_phase_shifter import mzi2x2_2x2_phase_shifter # example_component_lattice = [ # [mzi2x2_2x2(), 0, mzi2x2_2x2()], # [0, mzi2x2_2x2(), 0], # [mzi2x2_2x2(), 0, mzi2x2_2x2()], # ] # c = component_lattice_generic(example_component_lattice) # c.show(show_ports=True) # example_mixed_component_lattice = [ # [mzi2x2_2x2_phase_shifter(), 0, mzi2x2_2x2(delta_length=20.0)], # [0, mzi2x2_2x2(delta_length=50.0), 0], # [mzi2x2_2x2(delta_length=100.0), 0, mzi2x2_2x2_phase_shifter()], # ] # c_mixed = component_lattice_generic(example_mixed_component_lattice) # c_mixed.show(show_ports=True) c = component_lattice_generic() c.show()