LoginSignup
0
0

GDSII制作ソフト gdstk の高度な使い方を google colab で紹介

Last updated at Posted at 2023-12-03

はじめに

この記事では、GDSII制作ソフト gdstk の高度な使い方を google colab で紹介します。

gdstk の基礎的な使い方は、

を参考にしてください。

になるべく準じて google colab 上で解説します。

が、google colab 上で動作した例になります。

gtdtk の install

  • 2023.11.28 pip didnot work for Downloading gdstk-0.9.46.zip (1.4 MB)
  • 2023.11.28 using condacolab and the following conda commands worked.

インストール方法は、基礎編を参考にしてください。google colab で動作させるには、condacolab を使います。

# install condacolab
!pip install -q condacolab
import condacolab
condacolab.install() # Mambaforge-23.1.0-1-Linux-x86_64.sh...  @2023.11.30
# check conda version
!conda --version # conda 23.1.0
# Create a new conda environment named gdstk
!conda create -n gdstk -c conda-forge --strict-channel-priority
# Activate the new environment
!conda activate gdstk
# Install gdstk
!conda install gdstk

必要なモジュールの import

import matplotlib.pyplot as plt
import numpy as np
from IPython.display import *
import gdstk
print("gdstk.__version__ = ", gdstk.__version__)
import pathlib

gdstk.version = 0.9.47 (2023.11.29)

GDS ファイルを matplotlib で表示する関数

で説明されている plot 関数を使います。

def plot_gds(gdsfile, transformation=None, maxlayer = 10, margin=3, debug=False):
  # GDSファイルを読み込む
  lib = gdstk.read_gds(gdsfile)
  # トップレベルのセルを取得
  top_cells = lib.top_level()
  # サブプロットを作成
  fig, axes = plt.subplots(1, 3, figsize=(10,6), tight_layout=True)
  # カラーマップを設定
  cmap = plt.get_cmap('viridis', maxlayer)

  # すべてのトップレベルセルのポリゴンをプロット
  for j, cell in enumerate(top_cells):
    # セルの境界ボックスを取得
    if j == 0:
      (xmin,ymin),(xmax,ymax) = cell.bounding_box()
    else:
      (tmp_xmin,tmp_ymin),(tmp_xmax,tmp_ymax) = cell.bounding_box()
      xmin = tmp_xmin if tmp_xmin  < xmin else xmin
      ymin = tmp_ymin if tmp_ymin  < ymin else ymin
      xmax = tmp_xmax if tmp_xmax > xmax else xmax
      ymax = tmp_ymax if tmp_ymax > ymax else ymax

    if hasattr(cell, 'polygons'):
      print("polygons in cell is found. length = ", len(cell.get_polygons()))
      for polygon in cell.get_polygons(): # cell.polygons does not work when the reference is used.
        points = np.array(polygon.points)
        if debug: print("     layer = ", polygon.layer, " area = ", polygon.area())
        # ポリゴンをプロット
        axes[0].plot(*np.vstack((points, points[0])).T, linestyle='solid', linewidth = 1, alpha=0.8, color=cmap(polygon.layer))
    if hasattr(cell, 'paths'):
      print("paths in cell is found. Length = ", len(cell.paths))
      for path in cell.paths:
        polys = path.to_polygons()
        for poly in polys:
            if debug: print("     layer = ", poly.layer, " area = ", poly.area())
            points = np.array(poly.points)
            # パスをプロット
            axes[1].plot(*np.vstack((points, points[0])).T, linestyle='solid', linewidth = 1, alpha=0.8, color=cmap(poly.layer))

    if hasattr(cell, 'references'):
      print("references in cell is found. length = ", len(cell.references))
      for reference in cell.references:
        # 参照の位置、回転、スケーリングを取得
        origin = reference.origin
        rotation = -1 * reference.rotation
#        rotation = reference.rotation
        magnification = reference.magnification if reference.magnification is not None else 1
        # 変換を適用してプロット
        for polygon in reference.cell.get_polygons(): # cell.polygons does not work when the reference is used.
          if debug: print("     layer = ", polygon.layer, " area = ", polygon.area())
          points = np.array(polygon.points)
          # スケーリングと回転を適用
          transformed_points = magnification * points.dot(
          np.array([[np.cos(rotation), -np.sin(rotation)],
                         [np.sin(rotation),  np.cos(rotation)]])
          )  + origin
          # 参照をプロット
          axes[2].plot(*np.vstack((transformed_points, transformed_points[0])).T, linestyle='solid', linewidth=1, alpha=0.8, color=cmap(polygon.layer))

  # 各サブプロットのタイトルを設定
  axes[0].set_title("polygons")
  axes[1].set_title("paths")
  axes[2].set_title("references")
  # サブプロットの範囲を設定
  axes[0].set_xlim(xmin - margin, xmax + margin)
  axes[1].set_xlim(xmin - margin, xmax + margin)
  axes[2].set_xlim(xmin - margin, xmax + margin)
  axes[0].set_ylim(ymin - margin, ymax + margin)
  axes[1].set_ylim(ymin - margin, ymax + margin)
  axes[2].set_ylim(ymin - margin, ymax + margin)
  # サブプロットのアスペクト比を設定
  axes[0].set_aspect('equal', adjustable='box')
  axes[1].set_aspect('equal', adjustable='box')
  axes[2].set_aspect('equal', adjustable='box')
  # グリッドを表示
  axes[0].grid(alpha=0.3)
  axes[1].grid(alpha=0.3)
  axes[2].grid(alpha=0.3)

  # プロットを保存
  plt.savefig(gdsfile.replace(".gds",".png"))
  # プロットを表示
  plt.show()

gdstk の How-Tos

ここでは、

になるべく準じて、google colab で確実に再現できて、再利用できることを意識して
一部書き換えたものを解説付きで紹介したいと思います。

パラメトリックセル

パラメトリックセルは、ユーザー定義のパラメータに基づいたジオメトリの作成を容易にするために、いくつかのレイアウトエディターで存在する概念です。Gdstkにはパラメータ化されたセルクラスはありませんが、pythonを使ってレイアウトを構築しているので、構造化が簡単にできます。

この例では、ユーザー定義のパラメータに基づいたグレーティングカプラーを返す関数を定義しています。

def grating(
    period, fill_frac=0.5, length=20, width=25, layer=0, datatype=0, cell_name="Grating"
):
    """
    Straight grating:

    Args:
        period: Grating period.
        fill_frac: Filling fraction of the teeth (wrt period).
        length: Length of the grating.
        width: Width of the grating.
        layer: GDSII layer number
        datatype: GDSII data type number

    Return:
        gdstk.Cell
    """
    result = gdstk.Cell(cell_name)
    x = width / 2
    w = period * fill_frac
    result.add(
        gdstk.rectangle(
            (-x, y * period), (x, y * period + w), layer=layer, datatype=datatype
        )
        for y in range(int(length / period))
    )
    return result

として、関数を定義する。下記で実行する。

lib = gdstk.Library()

length = 20
grat1 = grating(3.5, length=length, layer=1, cell_name="Grating 1")
grat2 = grating(3.0, length=length, layer=1, cell_name="Grating 2")
lib.add(grat1, grat2)

main = lib.new_cell("Main")
main.add(gdstk.rectangle((0, -10), (150, 10)))
# Check the definitin of Reference
# https://heitzmann.github.io/gdstk/library/gdstk.Reference.html#gdstk.Reference
# classgdstk Reference(cell, origin=(0, 0), rotation=0, magnification=1, x_reflection=False, columns=1, rows=1, spacing=None)
main.add(gdstk.Reference(grat1, (2*length, 0), rotation=np.pi / 2))
main.add(gdstk.Reference(grat2, (150 - 2*length, 0), rotation=-np.pi / 2))

outname = "pcell"
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

# plot SVG using all cells
main.write_svg(outname + ".svg", scaling=5, background="none")
display_svg(SVG(outname + ".svg"))

output_13_1.png

パーツライブラリ

ライブラリの作成

  • GDSIIパーツライブラリは、異なるレイアウトで頻繁に使用される複数のデバイスがある場合に使用できます。これは個人のデバイスライブラリであったり、製造を担当する会社が提供するプロセスデザインキット(PDK)の一部であったりします。

ここでは、アライメントマーク、方向性カプラー、マッハツェンダー干渉計の3つのコンポーネントを含むシンプルな個人ライブラリを作成します。すべてのパーツはGDSIIファイルに追加され、後で使用するために保存されます。なお、干渉計はすでに方向性カプラーをサブコンポーネントとして使用していることに注意してください。

def alignment_mark(lib):
    cross = gdstk.cross((0, 0), 50, 3, layer=1)
    lib.new_cell("Alignment Mark").add(cross)

def directional_coupler(lib):
    path = gdstk.RobustPath((0, 0), [0.5, 0.5], 2, simple_path=True, layer=1)
    path.segment((0.1, 0), relative=True)
    path.segment((2.2, 0), offset=(0.6, "smooth"), relative=True)
    path.segment((0.4, 0), relative=True)
    path.segment((2.2, 0), offset=(2, "smooth"), relative=True)
    path.segment((0.1, 0), relative=True)
    lib.new_cell("Directinal Coupler").add(path)

def mach_zehnder_interferometer(lib):
    cell = lib.new_cell("MZI")
    cell.add(gdstk.Reference("Directinal Coupler", (0, 0)))
    cell.add(gdstk.Reference("Directinal Coupler", (75, 0)))

    points = np.array([(5, 1), (25, 1), (25, 40), (55, 40), (55, 1), (75, 1)])
    arm1 = gdstk.FlexPath(points, 0.5, bend_radius=15, simple_path=True, layer=1)
    points[:, 1] *= -1
    arm2 = gdstk.FlexPath(points, 0.5, bend_radius=15, simple_path=True, layer=1)
    points = np.array([(25, 20), (25, 40), (55, 40), (55, 20)])
    heater1 = gdstk.FlexPath(points, 2, bend_radius=15, simple_path=True, layer=10)
    points[:, 1] *= -1
    heater2 = gdstk.FlexPath(points, 2, bend_radius=15, simple_path=True, layer=10)
    cell.add(arm1, arm2, heater1, heater2)
lib = gdstk.Library("Photonics")

alignment_mark(lib)
directional_coupler(lib)
mach_zehnder_interferometer(lib)

outname = "photonics"
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

# plot SVG using all cells
cell = gdstk.Cell("SVG")
for onecell in lib.top_level():
  cell.add(*onecell.get_polygons())
cell.write_svg(outname + ".svg", scaling=5, background="none")
display_svg(SVG(outname + ".svg"))

output_16_1.png

ライブラリを書き出す

ライブラリを import するデモンとレーション用に、pcell2.py というファイルを生成します。

# `grating` 関数と必要なインポートを含むテキストを用意します。
code = """import numpy as np
import gdstk
def grating(period, fill_frac=0.5, length=20, width=25, layer=0, datatype=0, cell_name="Grating"):
    result = gdstk.Cell(cell_name)
    x = width / 2
    w = period * fill_frac
    result.add(
        gdstk.rectangle(
            (-x, y * period), (x, y * period + w), layer=layer, datatype=datatype
        )
        for y in range(int(length / period))
    )
    return result
"""

# このテキストを `pcell.py` というファイルに書き出します。
with open('pcell2.py', 'w') as file:
    file.write(code)

# check if pcell2 is working. If not, sometimes we need to restart the kernel..
import pcell2
print(help(pcell2.grating))
pcell2.grating(1)

これで、正しく import できるか確認しましょう。

# Check library units. In this case it is using the default units.
units = gdstk.gds_units("photonics.gds")
print(f"Using unit = {units[0]}, precision = {units[1]}")

# Load the library as a dictionary of RawCell
pdk = gdstk.read_rawcells("photonics.gds")

# Cell holding a single device (MZI)
dev_cell = gdstk.Cell("Device")
dev_cell.add(gdstk.Reference(pdk["MZI"], (-40, 0)))

# Create a grating coupler using the function imported from the
# pcell module (pcell.py) created earlier.
grating = pcell2.grating(0.62, layer=2)
# Add 4 grating couplers to the device cell: one for each port.
dev_cell.add(
    gdstk.Reference(
        grating, (-200, -150), rotation=np.pi / 2, columns=2, spacing=(300, 0)
    ),
    gdstk.Reference(
        grating, (200, 150), rotation=-np.pi / 2, columns=2, spacing=(300, 0)
    ),
)

# Create a waveguide connecting a grating to a MZI port.
waveguide = gdstk.FlexPath((-220, -150), 20, bend_radius=15, layer=1)
# Grating background
# Check the definition of segment
# https://github.com/heitzmann/gdstk/tree/5bf1aa2c048a0cbcac96ab5e23d906fb037592da
# https://github.com/heitzmann/gdstk/blob/5bf1aa2c048a0cbcac96ab5e23d906fb037592da/src/flexpath.cpp#L1017
waveguide.segment((20, 0), relative=True)
# Linear taper
waveguide.segment((-100, -150), 0.5)
# Connection to MZI
waveguide.segment([(-70, -150), (-70, -1), (-40, -1)])
# Since the device is symmetrical, we can create a cell with the
# waveguide geometry and reuse it for all 4 ports.
wg_cell = gdstk.Cell("Waveguide")
wg_cell.add(waveguide)

dev_cell.add(gdstk.Reference(wg_cell))
dev_cell.add(gdstk.Reference(wg_cell, x_reflection=True))
dev_cell.add(gdstk.Reference(wg_cell, rotation=np.pi))
dev_cell.add(gdstk.Reference(wg_cell, rotation=np.pi, x_reflection=True))

# Main cell with 2 devices and lithography alignment marks
main = gdstk.Cell("Main")
main.add(
    gdstk.Reference(dev_cell, (250, 250)),
    gdstk.Reference(dev_cell, (250, 750)),
    gdstk.Reference(pdk["Alignment Mark"], columns=2, rows=3, spacing=(500, 500)),
)

lib = gdstk.Library()
lib.add(main, *main.dependencies(True))

outname = "layout"
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

# plot SVG using all cells
cell = gdstk.Cell("SVG")
for onecell in lib.top_level():
  cell.add(*onecell.get_polygons())
for onecell in lib.top_level():
  for reference in onecell.references:
    cell.add(*reference.get_polygons())
cell.write_svg(outname + ".svg", scaling=0.5, background="none")
display_svg(SVG(outname + ".svg"))

output_20_1.png

ライブラリのマージ

2つ以上のライブラリをマージするのは、一方のライブラリからもう一方にすべてのセルを追加するだけのことです。もちろん、後で追加のセルを追加して、両方のオリジナルからの参照を持つ新しいトップレベルのセルを作成することもできます。この例では、2つのGDSIIファイルを新しいものにマージするだけですが、結果として2つのトップレベルセルができ、すべてのセルの名前を変更して衝突しないようにします。

def make_first_lib(filename):
    lib = gdstk.Library("First")
    main = lib.new_cell("Main")
    main.add(*gdstk.text("First", 10, (0, 0)))
    ref1 = lib.new_cell("Square")
    ref1.add(gdstk.rectangle((-15, 0), (-5, 10)))
    main.add(gdstk.Reference(ref1))
    ref2 = lib.new_cell("Circle")
    ref2.add(gdstk.ellipse((0, 0), 4))
    ref1.add(gdstk.Reference(ref2, (-10, 5)))
    lib.write_gds(filename)

def make_second_lib(filename):
    lib = gdstk.Library("Second")
    main = lib.new_cell("Main")
    main.add(*gdstk.text("Second", 10, (5, -10)))
    ref = lib.new_cell("Circle")
    ref.add(gdstk.ellipse((-10, 5), 5))
    main.add(gdstk.Reference(ref))
    lib.write_gds(filename)
# First we create the two libraries we'll be merging
make_first_lib("lib1.gds")
make_second_lib("lib2.gds")

# Now we load the existing libraries
lib1 = gdstk.read_gds("lib1.gds")
outname = "lib1"
lib1.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

lib2 = gdstk.read_gds("lib2.gds")
outname = "lib2"
lib2.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

# We add all cells from the second library to the first
lib1_cell_names = {c.name for c in lib1.cells}
for cell in lib2.cells:
    # We must check that all names are unique within the merged library
    if cell.name in lib1_cell_names:
        cell.name += "-lib2"
        assert cell.name not in lib1_cell_names
    # Now we add the cell and update the set of names
    lib1.add(cell)
    lib1_cell_names.add(cell.name)

lib1.write_gds("merging.gds")

outname = "merging"
lib1.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

output_23_1.png

output_23_3.png

上の2つをマージしたので、

output_23_5.png

になります。

変換 transformations

ジオメトリの変換はいくつかの方法で行うことができます。個々のポリゴンやパスは、それぞれの方法で変換することができます(gdstk.Polygon.scale()gdstk.FlexPath.rotate()gdstk.RobustPath.translate()など)。

gdstk.Cell全体を変換するためには、望ましい変換を持つ gdstk.Reference を使用するか、gdstk.Cell.copy()で変換されたコピーを作成することができます。前者は実際のジオメトリやラベルの実際のコピーを作成しないため、メモリを少なく使用するという利点があり、一般的に好まれます。後者は、変換されたセルの内容に変更が必要であり、オリジナルを変更しない場合に特に便利です。

python の shallow copy (ポインタだけコピーする) と deep copy (実体ごとまるっとコピーする) の違いと同じで、どちらを採用するかは、用途に依存します。

lib = gdstk.Library()

n = 3  # Number of unit cells around defect
d = 0.2  # Unit cell size
r = 0.05  # Circle radius
s = 1.5  # Scaling factor

# Create a simple unit cell
unit_cell = gdstk.Cell("Unit Cell")
unit_cell.add(gdstk.ellipse((0, 0), r, tolerance=1e-3))

# Build a resonator from a unit cell grid with a defect inside
ressonator = gdstk.Cell("Resonator")
patches = [
    gdstk.Reference(
        unit_cell, (-n * d, -n * d), columns=2 * n + 1, rows=n, spacing=(d, d)
    ),
    gdstk.Reference(
        unit_cell, (-n * d, d), columns=2 * n + 1, rows=n, spacing=(d, d)
    ),
    gdstk.Reference(unit_cell, (-n * d, 0), columns=n, rows=1, spacing=(d, d)),
    gdstk.Reference(unit_cell, (d, 0), columns=n, rows=1, spacing=(d, d)),
]
# Defect
rect = gdstk.rectangle((-r / 2, -r / 2), (r / 2, r / 2))
# Path for illustration
path = gdstk.FlexPath(
    [(-n * d, 0), (n * d, 0)],
    r,
    ends=(r, r),
    simple_path=True,
    scale_width=False,
    layer=1,
)
ressonator.add(rect, path, *patches)

# Main output cell with the original resonator,…
#main = lib.new_cell("Main")
main = gdstk.Cell("Main")
main.add(gdstk.Reference(ressonator))
main.add(*gdstk.text("Original", d, ((n + 1) * d, -d / 2)))

# … a copy created by scaling a reference to the original resonator,…
main.add(gdstk.Reference(ressonator, (0, (1 + s) * (n + 1) * d), magnification=s))
main.add(
    *gdstk.text("Reference\nscaling", d, (s * (n + 1) * d, (1 + s) * (n + 1) * d))
)

# … and another copy created by copying and scaling the Cell itself.
ressonator_copy = ressonator.copy("Resonator Copy", magnification=s)
main.add(gdstk.Reference(ressonator_copy, (0, (1 + 3 * s) * (n + 1) * d)))
main.add(
    *gdstk.text(
        "Cell copy\nscaling", d, (s * (n + 1) * d, (1 + 3 * s) * (n + 1) * d)
    )
)

outname = "trans"
cell = lib.new_cell("trans")
for poly in main.get_polygons():
  cell.add(poly)
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")
main.write_svg(outname + ".svg", scaling=100, background="none")
display_svg(SVG(outname + ".svg"))

スクリーンショット 2023-12-03 15.18.03.png

反復

参照は、レイアウト全体にわたって繰り返しジオメトリを効果的にインスタンス化するために使用できます。反復はそのアイデアの拡張であり、gdstk.Cell を作成する必要なく、どんな要素も再利用することを可能にします。実際に、gdstk.Reference を配列として作成することは、長方形(または規則的な)反復を持つ単一の参照の作成へのショートカットに過ぎません。次の例は、最終的なGDSIIファイルにすべてのコピーが含まれるように、すべてのオブジェクトをメモリに作成するのを避けるために、異なる形式の反復の使用を示しています。

lib = gdstk.Library() # initialize

# Rectangular repetition
square = gdstk.regular_polygon((0, 0), 0.2, 4)
square.repetition = gdstk.Repetition(3, 2, spacing=(1, 1))

# Regular repetition
triangle = gdstk.regular_polygon((0, 2.5), 0.2, 3)
triangle.repetition = gdstk.Repetition(3, 5, v1=(0.4, -0.3), v2=(0.4, 0.2))

# Explicit repetition
circle = gdstk.ellipse((3.5, 0), 0.1)
circle.repetition = gdstk.Repetition(offsets=[(0.5, 1), (2, 0), (1.5, 0.5)])

# X-explicit repetition
vline = gdstk.FlexPath([(3, 2), (3, 3.5)], 0.1, simple_path=True)
vline.repetition = gdstk.Repetition(x_offsets=[0.2, 0.6, 1.4, 3.0])

# Y-explicit repetition
hline = gdstk.RobustPath((3, 2), 0.05, simple_path=True)
hline.segment((6, 2))
hline.repetition = gdstk.Repetition(y_offsets=[0.1, 0.3, 0.7, 1.5])

main_repeti = gdstk.Cell("Main_repeti")
main_repeti.add(square, triangle, circle, vline, hline)

outname = "repeti"
cell2 = lib.new_cell("repeti")
for poly in main_repeti.get_polygons():
  cell2.add(poly)
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")
main.write_svg(outname + ".svg", scaling=100, background="none")
display_svg(SVG(outname + ".svg"))

スクリーンショット 2023-12-03 15.18.56.png

繰り返しを持つ要素にジオメトリ操作が適用される場合、それらは自動的には適用されません。必要に応じて、望ましい操作を実行する前に、手動で繰り返しを適用することができます。次の例は、この使用方法を示しています。

lib = gdstk.Library() # initialize

# X-explicit repetition
vline = gdstk.FlexPath([(3, 2), (3, 3.5)], 0.1, simple_path=True)
vline.repetition = gdstk.Repetition(x_offsets=[0.2, 0.6, 1.4, 3.0])

# Y-explicit repetition
hline = gdstk.RobustPath((3, 2), 0.05, simple_path=True)
hline.segment((6, 2))
hline.repetition = gdstk.Repetition(y_offsets=[0.1, 0.3, 0.7, 1.5])

# Create all copies
vlines = vline.apply_repetition()
hlines = hline.apply_repetition()

# Include original elements for boolean operation
vlines.append(vline)
hlines.append(hline)

result = gdstk.boolean(vlines, hlines, "or")

main = gdstk.Cell("Main")
main.add(*result)

outname = "repeti2"
cell = lib.new_cell("repeti2")
for poly in main.get_polygons():
  cell.add(poly)
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")
main.write_svg(outname + ".svg", scaling=100, background="none")
display_svg(SVG(outname + ".svg"))

スクリーンショット 2023-12-03 15.19.39.png

ジオメトリのフィルタリング

読み込んだライブラリのジオメトリをフィルタリングするには、必要なセルとオブジェクトを反復処理し、不要なものをテストして除去するだけです。この例では、「ライブラリの使用」で作成したレイアウトを読み込み、レイヤー2(グレーティングの歯)のポリゴンとレイヤー10(MZI内)のパスを削除します。

# Load existing library
lib = gdstk.read_gds("layout.gds")

for cell in lib.cells:
    # Remove any polygons in layer 2
    cell.filter([(2, 0)], paths=False, labels=False)
    # Remove any paths in layer 10
    cell.filter([(10, 0)], polygons=False, labels=False)

outname = "filtered-layout"
lib.write_gds(outname + ".gds")
plot_gds(outname + ".gds")

output_31_1.png

フィルタリングのもう一つの一般的な用途は、特定の領域内のジオメトリを除去することです。この例では、周期的な背景を作成し、gdstk.inside() を使用してテストすることで、特定の形状と重なるすべての要素を除去します。

lib = gdstk.Library() # initialize

unit = gdstk.Cell("Unit")
unit.add(gdstk.cross((0, 0), 1, 0.2))

main = gdstk.Cell("Main")

# Create repeating pattern using references
d = 2
ref1 = gdstk.Reference(unit, columns=11, rows=6, spacing=(d, d * 3**0.5))
ref2 = gdstk.Reference(
    unit, (d / 2, d * 3**0.5 / 2), columns=10, rows=5, spacing=(d, d * 3**0.5)
)
main.add(ref1, ref2)
main.flatten()

hole = gdstk.text("PY", 8 * d, (0.5 * d, 0), layer=1)
for pol in main.polygons:
    if gdstk.any_inside(pol.points, hole):
        main.remove(pol)

main.add(*hole)

gdstk.Library().add(main).write_gds("pos_filtering.gds")
plot_gds(outname + ".gds")

main.write_svg(outname + ".svg", scaling=7, background="none")
display_svg(SVG(outname + ".svg"))

output_33_1.png

最後に、gdstk.read_gds() は、GDSIIファイルから特定のレイヤーとデータタイプのみを読み込む方法を提供し、gdstk.Cell.get_polygons()gdstk.Cell.get_paths()gdstk.Cell.get_labels() のメソッドも、特定のレイヤーやタイプからの要素のみを収集するために使用できます。

パスに沿ったポイント

次の例は、gdstk.RobustPathに沿ってマーカーを追加する方法です。この方法は、パスの元のパラメータ化を使用して、曲線に沿ってマーカーを配置します。固定距離に配置されたマーカーは、曲線の部位ごとに独立して計算しています。

lib = gdstk.Library() # initialize

main = gdstk.Cell("Main")

# Create a path
path = gdstk.RobustPath((0, 0), 0.5)
path.segment((8, 0))
path.interpolation(
    [(2, -4), (-2, -6), (-5, -8), (-4, -12)],
    angles=[0, None, None, None, -np.pi / 4],
    relative=True,
)
path.segment((5, 1), relative=True)
main.add(path)

# Major and minor markers
major = gdstk.regular_polygon((0, 0), 0.5, 6, layer=1)
minor = gdstk.rectangle((-0.1, -0.5), (0.1, 0.5), layer=1)
print("path.size = ", path)
for s in range(path.size):
    # A major marker is added at the start of each path section
    m = major.copy()
    print("path.position(",s,") = ", path.position(s))
    m.translate(path.position(s))
    main.add(m)
    for u in np.linspace(0, 1, 5)[1:-1]:
        # Each section receives 3 equally-spaced minor markers
        # rotated to be aligned to the path direction
        m = minor.copy()
        grad = path.gradient(s + u)
        m.rotate(np.arctan2(grad[1], grad[0]))
        m.translate(path.position(s + u))
        main.add(m)
# Add a major marker at the end of the path
major.translate(path.position(path.size))
main.add(major)

outname = "path_along"

gdstk.Library().add(main).write_gds(outname + ".gds")
plot_gds(outname + ".gds")

main.write_svg(outname + ".svg", scaling=15, background="none")
display_svg(SVG(outname + ".svg"))

path.size =  RobustPath with 1 paths and 6 sections
path.position( 0 ) =  [0. 0.]
path.position( 1 ) =  [8. 0.]
path.position( 2 ) =  [10. -4.]
path.position( 3 ) =  [ 6. -6.]
path.position( 4 ) =  [ 3. -8.]
path.position( 5 ) =  [  4. -12.]
polygons in cell is found. length =  26
paths in cell is found. Length =  0
references in cell is found. length =  0

output_36_1.png

接続パッド

この例では、電気トレース用の接続パッドを提供するためにカスタムエンド関数が使用されています。簡単のため、パスの幅が最初と最後のセグメントで変わらないこと、またパッドの形状をサポートするのに十分な長さがあること、さらにパッドの直径がパスの幅よりも大きいことを前提としています。パッドがトレースに接続する点は、オプションでフィレット(角の丸め)が可能です。

def filleted_pad(pad_radius, fillet_radius=0, tolerance=0.01):
    def _f(p0, v0, p1, v1):
        p0 = np.array(p0)
        v0 = np.array(v0)
        p1 = np.array(p1)
        v1 = np.array(v1)

        half_trace_width = 0.5 * np.sqrt(np.sum((p0 - p1) ** 2))
        a = half_trace_width + fillet_radius
        c = pad_radius + fillet_radius
        b = (c**2 - a**2) ** 0.5
        alpha = np.arccos(a / c)
        gamma = np.arctan2(v0[1], v0[0]) + 0.5 * np.pi

        curve = gdstk.Curve(p0 - v0 * b, tolerance=tolerance)
        if fillet_radius > 0:
            curve.arc(fillet_radius, gamma, gamma - alpha)
        curve.arc(pad_radius, gamma - np.pi - alpha, gamma + alpha)
        if fillet_radius > 0:
            curve.arc(fillet_radius, gamma - np.pi + alpha, gamma - np.pi)

        return curve.points()

    return _f

main = gdstk.Cell("Main")

# Create a bus with 4 traces
bus = gdstk.FlexPath(
    [(0, 0), (10, 5)], [3] * 4, offset=15, joins="round", ends=filleted_pad(5, 3)
)
bus.segment((20, 10), offset=6)
bus.segment([(40, 20), (40, 50), (80, 50)])
bus.segment((100, 50), offset=12)
main.add(bus)

outname = "filleted_pad"

gdstk.Library().add(main).write_gds(outname + ".gds")
plot_gds(outname + ".gds")

main.write_svg(outname + ".svg", scaling=5, background="none")
display_svg(SVG(outname + ".svg"))

output_37_1.png

システムフォント

この例では、matplotlibを使用してシステムに存在する任意のタイプフェイスでテキストをレンダリングします。その後、グリフのパスはポリゴン配列に変換され、gdstk.Polygonオブジェクトを作成するために使用できます。

import gdstk
from matplotlib.font_manager import FontProperties
from matplotlib.textpath import TextPath


def render_text(text, size=None, position=(0, 0), font_prop=None, tolerance=0.1):
    precision = 0.1 * tolerance
    path = TextPath(position, text, size=size, prop=font_prop)
    polys = []
    xmax = position[0]
    for points, code in path.iter_segments():
        if code == path.MOVETO:
            c = gdstk.Curve(points, tolerance=tolerance)
        elif code == path.LINETO:
            c.segment(points.reshape(points.size // 2, 2))
        elif code == path.CURVE3:
            c.quadratic(points.reshape(points.size // 2, 2))
        elif code == path.CURVE4:
            c.cubic(points.reshape(points.size // 2, 2))
        elif code == path.CLOSEPOLY:
            pts = c.points()
            if pts.size > 0:
                poly = gdstk.Polygon(pts)
                if pts[:, 0].min() < xmax:
                    i = len(polys) - 1
                    while i >= 0:
                        if polys[i].contain_any(*poly.points):
                            p = polys.pop(i)
                            poly = gdstk.boolean(p, poly, "xor", precision)[0]
                            break
                        elif poly.contain_any(*polys[i].points):
                            p = polys.pop(i)
                            poly = gdstk.boolean(p, poly, "xor", precision)[0]
                        i -= 1
                xmax = max(xmax, poly.points[:, 0].max())
                polys.append(poly)
    return polys


cell = gdstk.Cell("fonts")
fp = FontProperties(family="serif", style="italic")
polygons = render_text("Text rendering", 10, font_prop=fp)
cell.add(*polygons)

outname = "sysfont"
cell.write_svg(outname + ".svg", scaling=5, background="none")
display_svg(SVG(outname + ".svg"))

スクリーンショット 2023-12-03 15.21.52.png

大規模なプログラムに向けて

クラスベースのアプローチの簡単な例

パーツライブラリのところで、アライメントマーク、方向性カプラー、マッハツェンダー干渉計の3つのコンポーネントを含むシンプルなライブラリを紹介しましたが、このコードをリファクタリングして、大規模なコーディングにも適応できるような例を紹介します。

各機能をクラスメソッドとして組み込む方法があります。以下に、基本的なクラスベースのアプローチを示します。

  • PhotonicsLibraryクラスの作成: 各機能(アライメントマーク、方向性カプラー、マッハ・ツェンダー干渉計)をこのクラスのメソッドとして実装します。
  • クラス初期化時にGdstkライブラリのインスタンスを生成: このクラスのインスタンスが作成されるとき、Gdstkライブラリも自動的に生成されます。
  • 各機能をメソッドとして実装: アライメントマーク、方向性カプラー、マッハ・ツェンダー干渉計の各機能をクラスのメソッドとして定義します。

以下はこのアプローチに基づくコードの例です。

import numpy as np
import gdstk

class PhotonicsLibrary:
    def __init__(self, name):
        self.lib = gdstk.Library(name)

    def alignment_mark(self):
        cross = gdstk.cross((0, 0), 50, 3, layer=1)
        self.lib.new_cell("Alignment Mark").add(cross)

    def directional_coupler(self):
        path = gdstk.RobustPath((0, 0), [0.5, 0.5], 2, simple_path=True, layer=1)
        path.segment((0.1, 0), relative=True)
        path.segment((2.2, 0), offset=(0.6, "smooth"), relative=True)
        path.segment((0.4, 0), relative=True)
        path.segment((2.2, 0), offset=(2, "smooth"), relative=True)
        path.segment((0.1, 0), relative=True)
        self.lib.new_cell("Directional Coupler").add(path)

    def mach_zehnder_interferometer(self):
        cell = self.lib.new_cell("MZI")
        cell.add(gdstk.Reference("Directional Coupler", (0, 0)))
        cell.add(gdstk.Reference("Directional Coupler", (75, 0)))

        points = np.array([(5, 1), (25, 1), (25, 40), (55, 40), (55, 1), (75, 1)])
        arm1 = gdstk.FlexPath(points, 0.5, bend_radius=15, simple_path=True, layer=1)
        points[:, 1] *= -1
        arm2 = gdstk.FlexPath(points, 0.5, bend_radius=15, simple_path=True, layer=1)
        points = np.array([(25, 20), (25, 40), (55, 40), (55, 20)])
        heater1 = gdstk.FlexPath(points, 2, bend_radius=15, simple_path=True, layer=10)
        points[:, 1] *= -1
        heater2 = gdstk.FlexPath(points, 2, bend_radius=15, simple_path=True, layer=10)
        cell.add(arm1, arm2, heater1, heater2)

# 使用例
photonics_lib = PhotonicsLibrary("Photonics")
photonics_lib.alignment_mark()
photonics_lib.directional_coupler()
photonics_lib.mach_zehnder_interferometer()

outname = "photonics"
photonics_lib.lib.write_gds(outname + ".gds")
# plot 
plot_gds(outname + ".gds")

最後に

python の細かい使い方の説明は省いていますが、大体はこれで、

が習得できるかと思います。

で Shapely を用いた例を紹介しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0