Grid snapping#

GDS represents points as integers where each point corresponds to a database unit (DBU). Typically, in most foundries, the default DBU is set to 1 nanometer (1nm).

gdsfactory snaps ports to the grid by default to avoid grid snapping errors which are common in layout designs.

Example of grid snapping errors.

import gdsfactory as gf

nm = 1e-3

wg1 = gf.Component("polygon_snapping")
p1 = wg1.add_polygon(
    [(0, 0), (1.4 * nm, 0), (1.4 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # rounds to 1 nm

p2 = wg1.add_polygon(
    [(0, 0), (1.6 * nm, 0), (1.6 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # rounds to 2 nm
p2.movey(-2 * nm)

p3 = wg1.add_polygon([(0, 0), (1 * nm, 0), (1 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0))
p3.movey(-4 * nm)
wg1.plot()
../_images/fbe34bbbf1fd69c8aaba698ce2a9370fdfaac1baabc53e68365373182879c85f.png
import gdsfactory as gf

nm = 1e-3

c = gf.Component("polygon_snapping_and_moving")
p1 = c.add_polygon(
    [(0, 0), (1 * nm, 0), (1 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # 1nm long
p2 = c.add_polygon(
    [(0, 0), (1.5 * nm, 0), (1.5 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # rounds to 2 nm long
p2.movex(1 * nm)
c.plot()
../_images/5e74c30ec76b2c60569d8557b25d8990ef109b47a38a5c6474c27060f340227d.png
import gdsfactory as gf

nm = 1e-3

c = gf.Component("polygon_snapping_and_moving2")
p1 = c.add_polygon(
    [(0, 0), (1.5 * nm, 0), (1.5 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # rounds to 2 nm long
p2 = c.add_polygon(
    [(0, 0), (1 * nm, 0), (1 * nm, 1 * nm), (0, 1 * nm)], layer=(1, 0)
)  # 1nm long
p2.movex(1.5 * nm)  # rounds to 2nm movement
c.plot()
../_images/9a6621a2b74dbd2bee8ac0b5e212971218287bc9c9d23e2c983ac5a1c0879616.png

Why you need to round gaps and lengths by 2nm?#

If you center your polygon at zero, you may need to round your lengths by 2nm

import gdsfactory as gf

nm = 1e-3

c = gf.Component("gap")
p1 = c.add_polygon(
    [(-1.5 * nm, 0), (1.5 * nm, 0), (1.5 * nm, 1 * nm), (-1.5 * nm, 1 * nm)],
    layer=(1, 0),
)  # You wanted 3nm long but it rounds to 4 nm long
p2 = c.add_polygon(
    [(-1 * nm, 0), (1 * nm, 0), (1 * nm, 1 * nm), (-1 * nm, 1 * nm)], layer=(1, 0)
)  # 2nm long as expected
p2.movey(-2 * nm)
c.plot()
../_images/ceaa4870751129cab16bacbe52e77acaf0d857b5bc2cae02912b120ea523e633.png

⚠️ Warning: Manhattan Orientations#

It’s crucial to always maintain ports with Manhattan orientations (0, 90, 180, 270 degrees). Non-Manhattan ports can lead to ports snapping off the grid, resulting in 1nm gaps in your layouts, which can be detrimental to the design’s integrity.

Although gdsfactory provides functions to connect and route component ports that are off-grid or have non-Manhattan orientations, this feature is disabled by default for safety reasons.

Note: If you intend to create off-grid ports and non-Manhattan connections, you must enable this feature manually. This should be done with caution and a thorough understanding of the potential implications on your design.

c = gf.Component("non_manhattan_error_overlap")
w1 = c << gf.c.straight(length=4 * nm, width=4 * nm)
w1.rotate(45)

w2 = c << gf.c.straight(length=4 * nm, width=4 * nm)
w2.connect("o1", w1["o2"])
c  # in this case ports overlap by an extra nm because of the rotation, instead of connecting them with no overlap
2024-04-27 01:04:04.297 | WARNING  | gdsfactory.component:_write_library:1933 - UserWarning: Component non_manhattan_error_overlap has invalid transformations. Try component.flatten_offgrid_references() first.
2024-04-27 01:04:04.300 | WARNING  | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
non_manhattan_error_overlap: uid 72ffd426, ports [], references ['straight_1', 'straight_2'], 0 polygons
../_images/e102f959b3644023bc681520f46442d1013ac25428f2a8e761146af4e0e1d147.png
c = gf.Component("non_manhattan_error_gap")
w1 = c << gf.components.straight(length=4 * nm, width=4 * nm)
w1.rotate(30)

w2 = c << gf.components.straight(length=4 * nm, width=4 * nm)
w2.connect("o1", w1["o2"])
c  # in this case ports have a 1nm gap error because of the rotation
2024-04-27 01:04:04.542 | WARNING  | gdsfactory.component:_write_library:1933 - UserWarning: Component non_manhattan_error_gap has invalid transformations. Try component.flatten_offgrid_references() first.
non_manhattan_error_gap: uid b1a4c2d6, ports [], references ['straight_1', 'straight_2'], 0 polygons
../_images/af4b0ee6c6282e8bf98910ef54b77ce98159987d67dbe3c5b8a633f09c095899.png

Fix Non manhattan connections#

The GDS format often has issues with non-manhattan shapes, due to the rounding of vertices to a unit grid and to downstream tools (i.e. DRC) which often tend to assume cell references only have rotations at 90 degree intervals. For example:

import gdsfactory as gf
from gdsfactory.decorators import has_valid_transformations

gf.config.enable_offgrid_ports()  # enable off grid ports

c = gf.Component("non_manhattan")
w1 = c << gf.components.straight(length=4 * nm, width=4 * nm)
w1.rotate(30)

w2 = c << gf.components.straight(length=4 * nm, width=4 * nm)
w2.connect("o1", w1["o2"])
c = c.flatten_offgrid_references()
c
non_manhattan_offgrid: uid 3a122766, ports [], references ['straight_1', 'straight_2'], 0 polygons
../_images/620fa4ae9148aa100a5e56200695dd75370741c5bd526d63472701b9d7c1ff6c.png
import gdsfactory as gf
from gdsfactory.decorators import has_valid_transformations


@gf.cell
def demo_non_manhattan():
    c = gf.Component("bend")
    b = c << gf.components.bend_circular(angle=30)
    s = c << gf.components.straight(length=5)
    s.connect("o1", b.ports["o2"])
    return c


c1 = demo_non_manhattan()
print(has_valid_transformations(c1))
c1
False
2024-04-27 01:04:04.931 | WARNING  | gdsfactory.component:_write_library:1933 - UserWarning: Component demo_non_manhattan has invalid transformations. Try component.flatten_offgrid_references() first.
demo_non_manhattan: uid 9f226d97, ports [], references ['bend_circular_1', 'straight_1'], 0 polygons
../_images/113533c773542bf082d519ec8905ee6da13b8053c953ef56d6160327acfbcb2b.png

if you zoom in between the bends you will see a notch between waveguides due to non-manhattan connection between the bends.

gap

You an fix it with the flatten_offgrid_references flag when you call Component.write_gds().

help(c1.write_gds)
Help on method write_gds in module gdsfactory.component:

write_gds(gdspath: 'PathType | None' = None, gdsdir: 'PathType | None' = None, **kwargs) -> 'pathlib.Path' method of gdsfactory.component.Component instance
    Write component to GDS and returns gdspath.
    
    Args:
        gdspath: GDS file path to write to.
        gdsdir: directory for the GDS file. Defaults to /tmp/randomFile/gdsfactory.
    
    Keyword Args:
        unit: unit size for objects in library. 1um by default.
        precision: for dimensions in the library (m). 1nm by default.
        logging: disable GDS path logging, for example for showing it in KLayout.
        on_duplicate_cell: specify how to resolve duplicate-named cells. Choose one of the following:
            "warn" (default): overwrite all duplicate cells with one of the duplicates (arbitrarily).
            "error": throw a ValueError when attempting to write a gds with duplicate cells.
            "overwrite": overwrite all duplicate cells with one of the duplicates, without warning.
        on_uncached_component: Literal["warn", "error"] = "warn"
        flatten_offgrid_references: flattens component references which have invalid transformations.
        max_points: Maximal number of vertices per polygon.
            Polygons with more vertices that this are automatically fractured.
        with_metadata: writes metadata in YAML format.
        with_netlist: writes a netlist in JSON format.
        netlist_function: function to generate the netlist.
gdspath = c1.write_gds(flatten_offgrid_references=True)
c2 = gf.import_gds(gdspath)
has_valid_transformations(c1)  # has gap issues
2024-04-27 01:04:05.120 | INFO     | gdsfactory.component:_write_library:2003 - Wrote to '/tmp/gdsfactory/demo_non_manhattan.gds'
False
has_valid_transformations(c2)  # works perfect
True
c2.plot()
../_images/8be886bec0dad1d0803ca0459088497ed00b327a14c60ab70a8cd8063cdf5860.png

If you zoom in the connection the decorator you can see it fixed the issue in c that we fixed in c2 thanks to the flatten_offgrid_references flag.

no gap

Local flattening references.#

Sometimes this global flatten_offgrid_references can change your vias or other components by 1nm.

You can also create a copy of the cell after applying flatten_references. This can be applied to a single cell.

c3 = c1.flatten_offgrid_references()
c3.plot()
../_images/8be886bec0dad1d0803ca0459088497ed00b327a14c60ab70a8cd8063cdf5860.png

Default PDK GdsWriteSettings#

If you are frequently (or even sometimes!) creating geometries like this, with non-manhattan ports and/or references with non-90-degree rotations, I highly recommend that you set flatten_offgrid_references=True in your PDK’s GdsWriteSettings. If you are the PDK author, you can do this in the definition of the pdk. Or, you can modify the PDK at runtime like.

pdk = gf.get_active_pdk()
pdk.gds_write_settings.flatten_offgrid_references = True

With this flag set, invalid references will be flattened by default, preventing gaps and errors in downstream tools which may not support cell references with arbitrary rotation, without needing to specify this on each GDS write.

You should note, however, that this will not fix all gaps between faces of unequal length, as it is impossible to guarantee this for diagonal line segments of unequal lengths constrained to end on integer grid values.

Avoid Non manhattan connections#

Here are some strategies to avoid or fix non-manhattan connections.

Extrude at the end (ideal)#

Some connections are hard to fix due to the ports of the bends being slightly off-center.

For that the best is to concatenate the path first and then extrude last.

@gf.cell
def demo_non_manhattan_extrude_fix():
    c = gf.Component("bend")
    p1 = gf.path.arc(angle=30)
    p2 = gf.path.straight(length=5)
    p = p1 + p2
    c = p.extrude(cross_section="xs_sc")
    return c


c1 = demo_non_manhattan_extrude_fix()
c1.plot()
../_images/75524841a78cff387e01521312c90dffcd4b5904767d9b0fd246a74c06562b5c.png

Fix polygons#

You can also fix polygons by merge

import gdsfactory as gf

c = gf.Component("bend")
b = c << gf.components.bend_circular(angle=30)
s = c << gf.components.straight(length=5)
s.connect("o1", b.ports["o2"])
p = c.get_polygons(as_shapely_merged=True)
c2 = gf.Component("bend_fixed")
c2.add_polygon(p, layer=(1, 0))
c2.plot()
../_images/cb67275f59bb0a2ce5be35a54a8128b0045de1555af793618cf734a0dd8a152c.png
import gdsfactory as gf

c = gf.Component("bend")
b = c << gf.components.bend_circular(angle=30)
s = c << gf.components.straight(length=5)
s.connect("o1", b.ports["o2"])
p = c.get_polygons(as_shapely_merged=True)
c2 = gf.Component("bend_fixed")
c2.add_polygon(p, layer=(1, 0))
c2.plot()
../_images/cb67275f59bb0a2ce5be35a54a8128b0045de1555af793618cf734a0dd8a152c.png
@gf.cell
def demo_non_manhattan_merge_polygons():
    c = gf.Component("bend")
    b = c << gf.components.bend_circular(angle=30)
    s = c << gf.components.straight(length=5)
    s.connect("o1", b.ports["o2"])
    p = c.get_polygons(as_shapely_merged=True)

    c2 = gf.Component()
    c2.add_polygon(p, layer=(1, 0))
    c2.add_port("o1", port=b["o1"])
    c2.add_port("o2", port=s["o2"])
    return c2


c1 = demo_non_manhattan_merge_polygons()
c1.plot()
../_images/c8a8d7d5179ee2cfdc5852aaf2354e7e05d51021faa23c35d4f3b41ecb9c974e.png