Source code for gdsfactory.grid

"""pack a list of components into a grid.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan
"""

from __future__ import annotations

from functools import partial

import numpy as np

from gdsfactory.cell import cell
from gdsfactory.component import Component, valid_anchors
from gdsfactory.component_layout import Group
from gdsfactory.components.rectangle import rectangle
from gdsfactory.components.text_rectangular import text_rectangular
from gdsfactory.components.triangles import triangle
from gdsfactory.typings import Anchor, ComponentSpec, ComponentSpecs, Float2


[docs] @cell def grid( components: ComponentSpecs = (rectangle, triangle), spacing: tuple[float, float] = (5.0, 5.0), separation: bool = True, shape: tuple[int, int] | None = None, align_x: str = "x", align_y: str = "y", edge_x: str = "x", edge_y: str = "ymax", rotation: int = 0, h_mirror: bool = False, v_mirror: bool = False, add_ports_prefix: bool = True, name_ports_with_component_name: bool = False, ) -> Component: """Returns Component with a 1D or 2D grid of components. Args: components: Iterable to be placed onto a grid. (can be 1D or 2D). spacing: between adjacent elements on the grid, can be a tuple for \ different distances in height and width. separation: If True, guarantees elements are separated with fixed spacing \ if False, elements are spaced evenly along a grid. shape: x, y shape of the grid (see np.reshape). \ If no shape and the list is 1D, if np.reshape were run with (1, -1). align_x: {'x', 'xmin', 'xmax'} for x (column) alignment along. align_y: {'y', 'ymin', 'ymax'} for y (row) alignment along. edge_x: {'x', 'xmin', 'xmax'} for x (column) (ignored if separation = True). edge_y: {'y', 'ymin', 'ymax'} for y (row) along (ignored if separation = True). rotation: for each component in degrees. h_mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror. v_mirror: vertical mirror using x axis (1, y) (0, y). add_ports_prefix: adds prefix to port names. False adds suffix. name_ports_with_component_name: if True uses component.name as unique id. False uses index. Returns: Component containing components grid. .. plot:: :include-source: import gdsfactory as gf components = [gf.components.triangle(x=i) for i in range(1, 10)] c = gf.grid( components, shape=(1, len(components)), rotation=0, h_mirror=False, v_mirror=True, spacing=(100, 100), ) c.plot() """ from gdsfactory.pdk import get_component device_array = np.asarray(components) # Check arguments if device_array.ndim not in (1, 2): raise ValueError("grid() The components needs to be 1D or 2D.") if shape is not None and len(shape) != 2: raise ValueError( "grid() shape argument must be None or" f" have a length of 2, for example shape=(4,6), got {shape}" ) # Check that shape is valid and reshape array if needed if (shape is None) and (device_array.ndim == 2): # Already in desired shape shape = device_array.shape elif (shape is None) and (device_array.ndim == 1): shape = (device_array.size, -1) elif 0 < shape[0] * shape[1] < device_array.size: raise ValueError( f"Shape {shape} is too small for all {device_array.size} components" ) else: if np.min(shape) == -1: remainder = np.max(shape) - device_array.size % np.max(shape) else: remainder = shape[0] * shape[1] - device_array.size if remainder != 0: device_array = np.append( device_array, [ None, ] * remainder, ) device_array = np.reshape(device_array, shape) prefix_to_ref = {} D = Component() ref_array = np.empty(device_array.shape, dtype=object) dummy = Component() for idx, d in np.ndenumerate(device_array): if d is not None: d = get_component(d) ref = D.add_ref(d, rotation=rotation, x_reflection=h_mirror) if v_mirror: ref.mirror_y() ref_array[idx] = ref prefix = d.name if name_ports_with_component_name else str(idx) prefix = prefix.replace(" ", "") prefix = prefix.replace(",", "_") prefix = prefix.replace("(", "") prefix = prefix.replace(")", "") prefix_to_ref[prefix] = ref else: ref_array[idx] = D << dummy # Create dummy devices rows = [Group(ref_array[n, :]) for n in range(ref_array.shape[0])] cols = [Group(ref_array[:, n]) for n in range(ref_array.shape[1])] # Align rows and columns independently for r in rows: r.align(alignment=align_y) for c in cols: c.align(alignment=align_x) # Distribute rows and columns Group(cols).distribute( direction="x", spacing=spacing[0], separation=separation, edge=edge_x ) Group(rows[::-1]).distribute( direction="y", spacing=spacing[1], separation=separation, edge=edge_y ) for prefix, ref in prefix_to_ref.items(): component = ref.parent info = dict(component.info) info.update(parent=component.name) if add_ports_prefix: D.add_ports(ref.ports, prefix=f"{prefix}-", info=info) else: D.add_ports(ref.ports, suffix=f"-{prefix}", info=info) return D
grid_with_component_name = partial(grid, name_ports_with_component_name=True)
[docs] @cell def grid_with_text( components: tuple[ComponentSpec, ...] = (rectangle, triangle), text_prefix: str = "", text_offsets: tuple[Float2, ...] = ((0, 0),), text_anchors: tuple[Anchor, ...] = ("cc",), text_mirror: bool = False, text_rotation: int = 0, text: ComponentSpec | None = text_rectangular, labels: tuple[str, ...] | None = None, **kwargs, ) -> Component: """Returns Component with 1D or 2D grid of components with text labels. Args: components: Iterable to be placed onto a grid. (can be 1D or 2D). text_prefix: for labels. For example. 'A' will produce 'A1', 'A2', ... text_offsets: relative to component anchor. Defaults to center. text_anchors: relative to component (ce cw nc ne nw sc se sw center cc). text_mirror: if True mirrors text. text_rotation: Optional text rotation. text: function to add text labels. labels: optional, specify a tuple of labels rather than using a text_prefix. keyword Args: spacing: between adjacent elements on the grid, can be a tuple for \ different distances in height and width. separation: If True, guarantees elements are separated with fixed spacing \ if False, elements are spaced evenly along a grid. shape: x, y shape of the grid (see np.reshape). \ If no shape and the list is 1D, if np.reshape were run with (1, -1). align_x: {'x', 'xmin', 'xmax'} to perform the x (column) alignment along. align_y: {'y', 'ymin', 'ymax'} to perform the y (row) alignment along. edge_x: {'x', 'xmin', 'xmax'} to perform the x (column) distribution \ ignored if separation = True. edge_y: {'y', 'ymin', 'ymax'} to perform the y (row) distribution along \ ignored if separation = True. rotation: for each reference in degrees. h_mirror: horizontal mirror y axis (x, 1) (1, 0). most common mirror. v_mirror: vertical mirror using x axis (1, y) (0, y). add_ports_prefix: adds prefix to port names. False adds suffix. name_ports_with_component_name: if True uses component.name as unique id. False uses index. .. plot:: :include-source: import gdsfactory as gf components = [gf.components.triangle(x=i) for i in range(1, 10)] c = gf.grid_with_text( components, shape=(1, len(components)), rotation=0, h_mirror=False, v_mirror=True, spacing=(100, 100), text_offsets=((0, 100), (0, -100)), text_anchors=("nc", "sc"), ) c.plot() """ c = Component() g = grid_with_component_name(components=components, **kwargs) _ = c << g if text: for i, ref in enumerate(g.named_references.values()): for text_offset, text_anchor in zip(text_offsets, text_anchors): if text_anchor not in valid_anchors: raise ValueError( f"Invalid anchor {text_anchor}. Valid anchors are {valid_anchors}" ) if labels: if len(labels) > i: label = labels[i] # skip labels for dummy components else: continue else: label = f"{text_prefix}{i}" t = c << text(label) if text_mirror: t.mirror() if text_rotation: t.rotate(text_rotation) t.move(np.array(text_offset) + getattr(ref.size_info, text_anchor)) c.add_ports(g.ports) return c
def test_grid() -> None: import gdsfactory as gf c = [gf.components.straight(length=i) for i in [1, 1, 2]] c = grid( c, shape=(2, 2), rotation=0, h_mirror=False, v_mirror=False, spacing=(10, 10), ) # assert np.isclose(c.ports["1_1_o1"].center[0], 13.0), c.ports["1_1_o1"].center[0] assert c if __name__ == "__main__": import gdsfactory as gf # test_grid() # components = [gf.components.rectangle(size=(i, i)) for i in range(40, 66, 5)] c = [gf.components.rectangle(size=(i, i)) for i in range(40, 66, 10)] # c = [gf.components.triangle(x=i) for i in range(1, 10)] # print(len(c)) # c = grid_with_component_name() # c = grid_with_component_name([gf.components.straight(length=i) for i in range(1, 5)]) c = grid_with_text( c, shape=(2, 2), rotation=0, h_mirror=False, v_mirror=False, spacing=(10, 10), # text_offsets=((0, 100), (0, -100)), # text_anchors=("south",), ) c.show(show_ports=True)