heitzmann / gdspy

Python module for creating GDSII stream files, usually CAD layouts.
Boost Software License 1.0
348 stars 126 forks source link

Cannot set FlexPath's `ends` argument correctly #200

Closed aycandv closed 2 years ago

aycandv commented 2 years ago

Hi,

I am trying to modify ends of the cell using a custom function. More specifically, I want to have a cell that has perfect vertical aligned ends. However, I could not figure out how to use a callable for ends argument.

Here is my initial code without setting ends:

import gdspy
import numpy as np
import random

Ls = 8
y_separation = 4
gap = 0.2
width = 0.45

rn = random.randint(0, 1e4)
lib = gdspy.GdsLibrary()

cell = gdspy.Cell(f"Cell_{rn}")

path = gdspy.FlexPath([(0,0)], 0.45, gdsii_path=False, tolerance=1e-3, precision=1e-9)
path.parametric(
    lambda u: (Ls * u, (np.cos(np.pi * u) - 1) / 2 * (y_separation - gap - width) / 2),
    width=width
)
path.segment(
    [(10,0)],
    relative=True
)
path.parametric(
    lambda u: (Ls * u, (-1) * (np.cos(np.pi / Ls * u * Ls) - 1) / 2 * (y_separation - gap - width) / 2),
    width=width,
)

phase_sec = gdspy.FlexPath([(26, 0)], 0.45, gdsii_path=True, tolerance=1e-3, precision=1e-9)
phase_sec.segment(
    [(10,0)],
    relative=True
)

cell.add([path, phase_sec])

lib.add(cell)
lib.write_gds("device.gds")

After executing the code above, if you open device.gds and zoom in to the coord. (26, 0.22), you will see that edge of the s-bend is not vertical: image

To make edges vertical, I added a callable to set ends:

def ends_vertical(p1, v1, p2, v2):
    dc_start = 0
    dc_end = 26

    if np.abs(p1[0] - dc_start) < np.abs(p1[0] - dc_end):
        p1[0] = dc_end
        p2[0] = dc_end
        v1 = [1, 0]
        v2 = [-1, 0]
    else:
        p1[0] = dc_start
        p2[0] = dc_start
        v1 = [1, 0]
        v2 = [-1, 0]
    return [p1, v1, p2, v2]

By using ends_vertical function above, I got a strange layout as following: image

I wonder how can I correct this function so that the device will have vertical ends. Thanks in advance.

heitzmann commented 2 years ago

@aycandv Unfortunately you won't be able to solve this issue with a callable for ends using FlexPath.

The reason for the angled end edge is that the parametric method calculates the path direction form a numerical derivative that, at the endpoints, is not centered (we cannot evaluate the parametric function outside the unit interval, because it might not be defined). That results in a direction that is not perfectly horizontal in you case. The only sensible way to fix the issue, is to force a horizontal segment at both ends of the parametric section (note that this happens on both ends).

I modified you original code to include those segments with dimension 1, but they can be anything.

import gdspy
import numpy as np
import random

Ls = 8
y_separation = 4
gap = 0.2
width = 0.45

rn = random.randint(0, 1e4)
lib = gdspy.GdsLibrary()

cell = gdspy.Cell(f"Cell_{rn}")

path = gdspy.FlexPath([(-1,0)], 0.45, gdsii_path=False, tolerance=1e-3, precision=1e-9)
path.segment(
    [(1,0)],
    relative=True
)
path.parametric(
    lambda u: (Ls * u, (np.cos(np.pi * u) - 1) / 2 * (y_separation - gap - width) / 2),
    width=width
)
path.segment(
    [(10,0)],
    relative=True
)
path.parametric(
    lambda u: (Ls * u, (-1) * (np.cos(np.pi / Ls * u * Ls) - 1) / 2 * (y_separation - gap - width) / 2),
    width=width,
)
path.segment(
    [(1,0)],
    relative=True
)

phase_sec = gdspy.FlexPath([(27, 0)], 0.45, gdsii_path=True, tolerance=1e-3, precision=1e-9)
phase_sec.segment(
    [(9,0)],
    relative=True
)

cell.add([path, phase_sec])

lib.add(cell)
lib.write_gds("device.gds")

Alternatively, if you can use RobustPath, its parametric method accepts a derivative function that you can provide:

import gdspy
import numpy as np
import random

Ls = 8
y_separation = 4
gap = 0.2
width = 0.45

rn = random.randint(0, 1e4)
lib = gdspy.GdsLibrary()

cell = gdspy.Cell(f"Cell_{rn}")

def s_bend(length, delta):
    func = lambda u: np.array((length * u, (1 - np.cos(np.pi * u)) / 2 * delta))
    grad = lambda u: np.array((length, np.sin(np.pi * u) / 2 * delta))
    return func, grad

delta = (y_separation - gap - width) / 2

path = gdspy.RobustPath((0, 0), 0.45, gdsii_path=False, tolerance=1e-3, precision=1e-9)
path.parametric(*s_bend(Ls, -delta))
path.segment((10, 0), relative=True)
path.parametric(*s_bend(Ls, delta))

phase_sec = gdspy.FlexPath(
    [(26, 0)], 0.45, gdsii_path=True, tolerance=1e-3, precision=1e-9
)
phase_sec.segment([(10, 0)], relative=True)

cell.add([path, phase_sec])

lib.add(cell)
lib.write_gds("device.gds")