You can define components, circuits and masks in YAML syntax.


gdsfactory.component_from_yaml(yaml_str, component_factory={'C': <function C>, 'L': <function L>, 'add_frame': <function add_frame>, 'align_wafer': <function align_wafer>, 'array': <function array>, 'array_with_fanout': <function array_with_fanout>, 'array_with_fanout_2d': <function array_with_fanout_2d>, 'array_with_via': <function array_with_via>, 'array_with_via_2d': <function array_with_via_2d>, 'awg': <function awg>, 'bbox': <function bbox>, 'bend_circular': <function bend_circular>, 'bend_circular180': <function bend_circular180>, 'bend_circular_heater': <function bend_circular_heater>, 'bend_euler': <function bend_euler>, 'bend_euler180': functools.partial(<function bend_euler>, angle=180), 'bend_euler_s': <function bend_euler_s>, 'bend_port': <function bend_port>, 'bend_s': <function bend_s>, 'cavity': <function cavity>, 'cdc': <function cdc>, 'circle': <function circle>, 'compass': <function compass>, 'compensation_path': <function compensation_path>, 'component_lattice': <function component_lattice>, 'component_sequence': <function component_sequence>, 'coupler': <function coupler>, 'coupler90': <function coupler90>, 'coupler90bend': <function coupler90bend>, 'coupler90circular': <function coupler90circular>, 'coupler_adiabatic': <function coupler_adiabatic>, 'coupler_asymmetric': <function coupler_asymmetric>, 'coupler_full': <function coupler_full>, 'coupler_ring': <function coupler_ring>, 'coupler_straight': <function coupler_straight>, 'coupler_symmetric': <function coupler_symmetric>, 'cross': <function cross>, 'crossing': <function crossing>, 'crossing45': <function crossing45>, 'crossing_arm': <function crossing_arm>, 'crossing_etched': <function crossing_etched>, 'crossing_from_taper': <function crossing_from_taper>, 'cutback_bend': <function cutback_bend>, 'cutback_bend180': <function cutback_bend180>, 'cutback_bend180circular': functools.partial(<function cutback_bend180>, bend180=<function bend_circular180>), 'cutback_bend90': <function cutback_bend90>, 'cutback_bend90circular': functools.partial(<function cutback_bend90>, bend90=<function bend_circular>), 'cutback_component': <function cutback_component>, 'cutback_component_flipped': <function cutback_component_flipped>, 'dbr': <function dbr>, 'dbr2': <function dbr2>, 'delay_snake': <function delay_snake>, 'delay_snake2': <function delay_snake2>, 'delay_snake3': <function delay_snake3>, 'die': <function die>, 'die_bbox': <function die_bbox>, 'disk': <function disk>, 'ellipse': <function ellipse>, 'extend_port': <function extend_port>, 'extend_ports': <function extend_ports>, 'extend_ports_list': <function extend_ports_list>, 'fiber': <function fiber>, 'fiber_array': <function fiber_array>, 'grating_coupler_array': <function grating_coupler_array>, 'grating_coupler_elliptical': <function grating_coupler_elliptical>, 'grating_coupler_elliptical2': <function grating_coupler_elliptical2>, 'grating_coupler_elliptical_te': <function grating_coupler_elliptical_te>, 'grating_coupler_elliptical_tm': <function grating_coupler_elliptical_tm>, 'grating_coupler_elliptical_trenches': <function grating_coupler_elliptical_trenches>, 'grating_coupler_loss': <function grating_coupler_loss>, 'grating_coupler_te': functools.partial(<function grating_coupler_elliptical_trenches>, polarization='te', taper_angle=35), 'grating_coupler_tm': functools.partial(<function grating_coupler_elliptical_trenches>, polarization='tm', neff=1.8, grating_line_width=0.6), 'grating_coupler_tree': <function grating_coupler_tree>, 'grating_coupler_uniform': <function grating_coupler_uniform>, 'hline': <function hline>, 'litho_calipers': <function litho_calipers>, 'litho_steps': <function litho_steps>, 'logo': <function logo>, 'loop_mirror': <function loop_mirror>, 'loss_deembedding_ch12_34': <function loss_deembedding_ch12_34>, 'loss_deembedding_ch13_24': <function loss_deembedding_ch13_24>, 'loss_deembedding_ch14_23': <function loss_deembedding_ch14_23>, 'manhattan_text': <function manhattan_text>, 'mmi1x2': <function mmi1x2>, 'mmi2x2': <function mmi2x2>, 'mzi': <function mzi>, 'mzi_arm': <function mzi_arm>, 'mzi_lattice': <function mzi_lattice>, 'mzi_phase_shifter': functools.partial(<function mzi>, straight_x_top=functools.partial(<function straight_heater_metal_undercut>, with_undercut=False), length_x=320.0), 'mzi_phase_shifter_90_90': functools.partial(<function mzi>, straight_x_top=functools.partial(<function straight_heater_metal_undercut>, with_undercut=False, port_orientation1=90, port_orientation2=90), length_x=320.0), 'mzit': <function mzit>, 'mzit_lattice': <function mzit_lattice>, 'nxn': <function nxn>, 'pad': <function pad>, 'pad_array': <function pad_array>, 'pads_shorted': <function pads_shorted>, 'pcm_optical': <function pcm_optical>, 'pixel': <function pixel>, 'qrcode': <function qrcode>, 'ramp': <function ramp>, 'rectangle': <function rectangle>, 'resistance_meander': <function resistance_meander>, 'ring': <function ring>, 'ring_double': <function ring_double>, 'ring_single': <function ring_single>, 'ring_single_array': <function ring_single_array>, 'ring_single_dut': <function ring_single_dut>, 'spiral': <function spiral>, 'spiral_circular': <function spiral_circular>, 'spiral_external_io': <function spiral_external_io>, 'spiral_inner_io': <function spiral_inner_io>, 'spiral_inner_io_fiber_single': <function spiral_inner_io_fiber_single>, 'splitter_chain': <function splitter_chain>, 'splitter_tree': <function splitter_tree>, 'staircase': <function staircase>, 'straight': <function straight>, 'straight_array': <function straight_array>, 'straight_heater_doped_rib': <function straight_heater_doped_rib>, 'straight_heater_doped_strip': functools.partial(<function straight_heater_doped_rib>, cross_section_heater=<cyfunction strip_heater_doped>, via_stack_contact=functools.partial(<function via_stack>, layers=((1, 0), (24, 0), (41, 0)), vias=(None, None, functools.partial(<function via>, layer=(40, 0))))), 'straight_heater_metal': functools.partial(<function straight_heater_metal_undercut>, with_undercut=False), 'straight_heater_metal_90_90': functools.partial(<function straight_heater_metal_undercut>, with_undercut=False, port_orientation1=90, port_orientation2=90), 'straight_heater_metal_undercut': <function straight_heater_metal_undercut>, 'straight_heater_metal_undercut_90_90': functools.partial(<function straight_heater_metal_undercut>, with_undercut=False, port_orientation1=90, port_orientation2=90), 'straight_pin': <function straight_pin>, 'straight_pin_slot': <function straight_pin_slot>, 'straight_pn': functools.partial(<function straight_pin>, cross_section=<function pn>), 'straight_rib': functools.partial(<function straight>, cross_section=functools.partial(<cyfunction cross_section>, sections=(slab_6000_3_0_optical_optical, ))), 'straight_rib_tapered': functools.partial(<function extend_ports>, component=functools.partial(<function straight>, cross_section=functools.partial(<cyfunction cross_section>, sections=(slab_6000_3_0_optical_optical, ))), extension_factory=<function taper_strip_to_ridge>, port1='o2', port2='o1'), 'switch_tree': functools.partial(<function splitter_tree>, coupler=functools.partial(<function mzi>, straight_x_top=functools.partial(<function straight_heater_metal_undercut>, with_undercut=False), length_x=320.0, combiner=<function mmi2x2>, delta_length=0), spacing=(500, 100)), 'taper': <function taper>, 'taper2': functools.partial(<function taper>, width2=3), 'taper_0p5_to_3_l36': <function taper_0p5_to_3_l36>, 'taper_cross_section_linear': functools.partial(<function taper_cross_section>, linear=True, npoints=2), 'taper_cross_section_sine': functools.partial(<function taper_cross_section>, linear=False, npoints=101), 'taper_from_csv': <function taper_from_csv>, 'taper_strip_to_ridge': <function taper_strip_to_ridge>, 'taper_strip_to_ridge_trenches': <function taper_strip_to_ridge_trenches>, 'taper_w10_l100': <function taper_w10_l100>, 'taper_w10_l150': <function taper_w10_l150>, 'taper_w10_l200': <function taper_w10_l200>, 'taper_w11_l200': <function taper_w11_l200>, 'taper_w12_l200': <function taper_w12_l200>, 'text': <function text>, 'triangle': <function triangle>, 'verniers': <function verniers>, 'version_stamp': <function version_stamp>, 'via': <function via>, 'via1': functools.partial(<function via>, layer=(44, 0), enclosure=2), 'via2': functools.partial(<function via>, layer=(43, 0)), 'via_cutback': <function via_cutback>, 'via_stack': <function via_stack>, 'via_stack_heater': functools.partial(<function via_stack>, layers=((47, 0), (45, 0), (49, 0)), vias=(functools.partial(<function via>, layer=(44, 0), enclosure=2), functools.partial(<function via>, layer=(43, 0)))), 'via_stack_slab': functools.partial(<function via_stack>, layers=((3, 0), (41, 0), (45, 0), (49, 0)), vias=(functools.partial(<function via>, layer=(40, 0)), functools.partial(<function via>, layer=(44, 0), enclosure=2), functools.partial(<function via>, layer=(43, 0)))), 'via_stack_slot': <function via_stack_slot>, 'via_stack_with_offset': <function via_stack_with_offset>, 'viac': functools.partial(<function via>, layer=(40, 0)), 'wire_corner': <function wire_corner>, 'wire_sbend': <function wire_sbend>, 'wire_straight': functools.partial(<function straight>, with_cladding_box=False, cross_section=functools.partial(<cyfunction cross_section>, layer=(49, 0), width=10.0, port_names=('e1', 'e2'), port_types=('electrical', 'electrical')))}, routing_strategy={'get_bundle': <function get_bundle>, 'get_bundle_from_waypoints': <function get_bundle_from_waypoints>, 'get_bundle_path_length_match': <function get_bundle_path_length_match>, 'get_bundle_same_axis_no_grouping': <function get_bundle_same_axis_no_grouping>}, cross_section_factory={'cross_section': <cyfunction cross_section>, 'metal1': functools.partial(<cyfunction cross_section>, layer=(41, 0), width=10.0, port_names=('e1', 'e2'), port_types=('electrical', 'electrical')), 'metal2': functools.partial(<cyfunction cross_section>, layer=(45, 0), width=10.0, port_names=('e1', 'e2'), port_types=('electrical', 'electrical')), 'metal3': functools.partial(<cyfunction cross_section>, layer=(49, 0), width=10.0, port_names=('e1', 'e2'), port_types=('electrical', 'electrical')), 'nitride': functools.partial(<cyfunction cross_section>, layer=(34, 0), width=1.0), 'pin': <function pin>, 'rib': functools.partial(<cyfunction cross_section>, sections=(slab_6000_3_0_optical_optical, )), 'rib_heater_doped': <cyfunction rib_heater_doped>, 'strip': functools.partial(<cyfunction cross_section>), 'strip_auto_widen': functools.partial(<cyfunction cross_section>, width_wide=0.9, auto_widen=True), 'strip_heater_doped': <cyfunction strip_heater_doped>, 'strip_heater_metal': <cyfunction strip_heater_metal>, 'strip_heater_metal_undercut': <cyfunction strip_heater_metal_undercut>}, label_instance_function=<function add_instance_label>, **kwargs)[source]

Returns a Component defined in YAML file or string.

  • yaml – YAML IO describing Component file or string (with newlines) (instances, placements, routes, ports, connections, names)

  • component_factory (Dict[str, Callable[…, Component]]) – dict of functions {factory_name: factory_function}

  • routing_strategy (Dict[str, Callable]) – for links

  • label_instance_function (Callable) – to label each instance

  • kwargs – cache, pins … to pass to all factories

Return type




valid properties:
name: name of Component
        settings (Optional)
    x: Optional[float, str]  str can be instanceName,portName
    y: Optional[float, str]
    rotation: Optional[float]
    mirror: Optional[bool, float] float is x mirror axis
    port: Optional[str] port anchor
connections (Optional): between instances
ports (Optional): defines ports to expose
routes (Optional): defines bundles of routes
    library: optical
        instance1,port1: instance2,port2
      component: mmi1x2
        width_mmi: 4.5
        length_mmi: 10
      component: mmi1x2
        width_mmi: 4.5
        length_mmi: 5

        port: o1
        x: 0
        y: 0
        port: o1
        x: mmi_top,o2
        y: mmi_top,o2
        dx: 30
        dy: -30
        library: optical
            mmi_top,o3: mmi_bot,o1


  • It assumes that each DOE is provided in a folder together with a text file containing the list of each GDS file. (gdsfactory.placer)

  • The placing instructions are taken from a YAML file. Typically the same as the one used for specifying the DOEs

The YAML Placer addresses the following requirements:

  • Fast mask assembly

  • Absolute or relative placement

  • Adjustable margins between DOEs and within a DOE

  • multirow / multi-column packing

The YAML Placer does not check for collisions and does not guarantee a valid placing. It just puts together a mask allows you to change components placements.

You can specify DOEs (Design Of Experiments) and placement in a single YAML file.

In a typical workflow, this file is parsed twice: The first time, generate_does builds the GDS files for each Design of Experiment variation. The second time, place_from_yaml reads the placing instructions for every DOE and places them in a new GDS file.


import pathlib
import gdsfactory as gf
from gdsfactory.autoplacer.yaml_placer import place_from_yaml
from gdsfactory.generate_does import generate_does
from gdsfactory.mask.merge_metadata import merge_metadata

def test_mask():
    """Returns gdspath for a Mask

    - Write GDS files defined in does.yml (with JSON metadata)
    - place them into a mask following placer information in does.yml
    - merge mask JSON metadata into a combined JSON file

    cwd = pathlib.Path.cwd()
    does_path = cwd / "does.yml"
    doe_root_path = cwd / "build" / "cache_doe_directory"
    mask_path = cwd / "build" / "mask"
    gdspath = mask_path / "mask.gds"
    mask_path.mkdir(parents=True, exist_ok=True)

        str(does_path), doe_root_path=doe_root_path,
    top_level = place_from_yaml(does_path, root_does=doe_root_path)
    return gdspath

if __name__ == "__main__":
    gdspath = test_mask()

Corresponding YAML

    width: 10000
    height: 10000
    name: mask2

    # Setting cache to `true`: By default, all generated GDS are cached and won't be regenerated
    # This default behaviour can be overwritten within each DOE.
    # To rebuild the full mask from scratch, just set this to `false`, and ensure there is no
    # cache: true specified in any other component
    cache: true

## =======================================================================
## Templates - global settings for DOEs (Optional)
## =======================================================================
    type: template
        type: pack_row
        x0: E
        y0: S
        align_x: W
        align_y: S
        margin: 0

    type: template
        x0: W
        y0: S
        align_x: W
        align_y: N
        margin: 0

    type: template
    add_doe_label: true
    with_doe_name: false

## =============================
## Does (Design Of exeriments)
## =============================

    component: mmi2x2
    settings: # Uses the combination of settings to produce 9 devices
        width_mmi: [4.5, 5.6]
        length_mmi: 10
        type: pack_row
        x0: 0 # Absolute coordinate placing
        y0: 0
        align_x: W # x origin is west
        margin: 25. # x and y margin between the components within this DOE
        align_y: S # y origin is south

    component: mmi1x2
    do_permutation: False
        length_mmi: [10, 20]
        width_mmi: [5, 10]

        type: pack_row
        next_to: mmi2x2_width
# Relative placing: this DOE is using the West South of the previous DOE as the origin
        x0: W # x0 is the west of the DOE specified in next_to
        y0: S # y0 is the south of the DOE specified in next_to
# The West side of the component is aligned to x0 + margin
# The North side of the component is aligned to y0 + margin
        align_x: W
        align_y: N
        inter_margin_y: 100 # y margin between this DOE and the one used for relative placement
        margin_x: 50. # x margin between the components within this DOE
        margin_y: 20. # y margin between the components within this DOE

    component: bend_circular
    template: template_align_south_west
        radius: [5, 10]
        type: pack_col # These devices are packed in a column

    component: ring_single
    template: template_add_labels
    cache: False
        bend_radius: [5, 10]
        next_to: mmi1x2_width_length
# Assuming I am working on this DOE, it is convenient to set cache to false here.
# The full mask rebuilds quickly thanks to
# the default cahe=true for all other DOEs, but all the changes to
# this DOE are captured at every iteration

Placer arguments

	component: mmi1x2
		length: [5, 10, 15]
		type: pack_row / pack_col # placer type
		row_ids / col_ids: list of integers, if specified, should have the same length as the total number of components within the DOE
			by defaults all devices are in the first column/row (index 0)
			If we want multiple rows, columns, we need to specify in which column they each go
			e.g [0, 0, 1]

		x0: <float> /  `E` / `W`
		y0: <float> / `S` / `N`
		align_x: `E` / `W`
		align_y: `S` / `N`
		margin: <float>
		margin_x: <float>
		margin_y: <float>
		inter_margin_x: <float>
		inter_margin_y: <float>
		next_to: <DOE_NAME>