gdstkでPython最速のGDSII layout制作環境を作る

この記事は、[@heitzmann](https://github.com/heitzmann/)によるPythonライブラリ "[gdstk](https://github.com/heitzmann/gdstk)" (GDSII Tool Kit)を用いて、LSIやMEMS設計に用いられるGDS II layoutファイルを高速に出力する方法を解説する。

#目次
* [はじめに](#はじめに)
* [GDSTKの環境構築](#gdstkの環境構築)
    * [GDSTKの歩き方](#gdstkの歩き方)
    * [GDSTKではじめてのGDSIIファイルを出力する](#gdstkではじめてのgdsiiファイルを出力する)
* [GDSTKのクラス、関数(ライブラリ構成関係)](#gdstkのクラス関数ライブラリ構成関係)
    * [gdstk.Library](#gdstklibrary)
    * [gdstk.Cell](#gdstkcell)
    * [gdstk.Reference](#gdstkreference)
* [GDSTKのクラス、関数(ジオメトリ関係)](#gdstkのクラス関数ジオメトリ関係)
    * [gdstk.Polygon](#gdstkpolygon)
    * [gdstk.rectangle](#gdstkrectangle)
    * [gdstk.cross](#gdstkcross)
    * [gdstk.text](#gdstktext)
* [OASIS形式でファイルを出力する](#oasis形式でファイルを出力する)

#はじめに
この記事は、過去記事 ”[PythonでGDSII formatを扱う](https://qiita.com/isourou/items/5ae84a020da65338d19b)” および ”[gdspyを使って、PythonではじめてのGDSIIファイルを作る](https://qiita.com/isourou/items/9d8d9220063e839f3e56)” の続きである。これらの記事では、Python上でGDSII layoutを扱うためのライブラリ "[gdspy](https://github.com/heitzmann/gdspy)" について説明してきたが、これはPythonネイティブで動作するため遅すぎた。そのため作者である[@heitzmann](https://github.com/heitzmann/)はver.1.6系を最後にgdspyの継続をあきらめ、新規プロジェクト "[gdstk](https://github.com/heitzmann/gdstk)" (GDSII Tool Kit) に現在注力している。gdstkは、C++で書かれたGDSIIファイルI/Oと、そのPythonラッパーで構成される。したがって我々ユーザの立場からは、Pythonの**高い可読性と機能性**とを維持したまま、C++に由来する**高速性**を発揮してGDSII layoutファイルを取り扱うことができる[^1]。高速化の寄与は驚異的で、gdspyその他PythonによるGDSII関係ライブラリに比べて**10倍~20倍は当たり前**である[^2]。本稿執筆現在、ver0.5.0までが公開されている。本記事では、まずgdstkを利用するための環境構築について述べ、次にgdstkを利用した簡単なGDSIIファイル作成について述べる。

#GDSTKの環境構築
gdstkはconda-forgeおよびpipパッケージに収録されているため、そのインストールは**とっても簡単[^3]**である。
`> conda install -c conda-forge gdstk`
または
`> pip install gdstk`
でインストールできる。そのほか、Anaconda3などを用いれば、GUIで簡単にインストールできる。Anaconda等でgdstk用仮想環境を作成の上インストールすることをお勧めする。

##GDSTKの歩き方
更新など最新情報は[GitHub](https://github.com/heitzmann/gdstk)で、APIリファレンス含む完全なドキュメントは[こちら](https://heitzmann.github.io/gdstk/)で公開されている。英語力に自信のある向きはこんな記事読んでないでこれら公式を読むべきである。GitHubでウォッチしてメーリングリストを購読すると、細かいノウハウに関する情報共有ができるのでお勧めである。またプロジェクト継続のためぜひ作者である@heitzmannに[寄付](https://www.paypal.com/donate/?cmd=_s-xclick&hosted_button_id=JD2EUE2WPPBQQ)をお願いしたい。

##GDSTKではじめてのGDSIIファイルを出力する
次のコードを実行してみよう。

```python:first.py
import gdstk

lib = gdstk.Library() #特に指定しなければ単位はum
topcell = lib.new_cell("Top") #Topセルを作成

subcell = lib.new_cell("subcell") #サブセルを作成
box = gdstk.rectangle((0,0), (1,2)) #左下(0,0),右上(1,2)の四角形を作成
subcell.add(box) #四角形をサブセルに追加
ref = gdstk.Reference(subcell, origin=(0,0), columns =3, rows =2, spacing = (5,5)) #サブセルへの配列リファレンスを原点(0,0)に挿入。3行2列で、間隔は(5,5)
topcell.add(ref) #サブセルへのリファレンスをTopセルに挿入。


text = gdstk.text('First GDS',2, (0,10)) #高さ2のテキストを位置(0,10)に挿入。
topcell.add(*text) #gdstk.textは1文字ずつのPolygonのリストを返すので、*textでリストをunpackしてadd()に渡す。

lib.write_gds('output.gds') #gdsファイル出力
topcell.write_svg('output.svg') #svgファイル出力
```

`output.gds`と`output.svg`の2つのファイルが出力される。`output.svg`の中身はこんな感じ。
![output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377851/582e54e5-82fb-ee9a-9a93-dea141b345a9.png)

`output.gds`は、[KLayout](https://www.klayout.de/)等で閲覧できる。

以下ではGDSTKでよく用いられるクラス、関数を列挙して説明する。ここで説明しきれなかったクラスのメソッドや関数のリファレンスについては、公式へのリンクを貼っておくので参考にしてほしい。

# [GDSTKのクラス、関数(ライブラリ構成関係)](https://heitzmann.github.io/gdstk/reference_python.html#library-organization)
ここではライブラリ構成関係のクラス、関数を説明する。

##[gdstk.Library](https://heitzmann.github.io/gdstk/library/gdstk.Library.html#gdstk.Library)
`class gdstk.Library(name='library', unit=1e-6, precision=1e-9)`
GDSII または OASIS形式のライブラリを生成する。ライブラリには、設計を表現するための複数のセルや、寸法単位の情報が含まれる。

###引数:
* name: ライブラリ名の文字列。
* unit: ライブラリ名で用いられる長さ単位[m]。特に指定しなければ1 umになる。
* precision: ライブラリ内で用いられる寸法精度[m]。リソグラフィ装置の仕様に応じて決定すること。特に指定しなければ1 nmになる。ライブラリ内部では、寸法はprecisionを1とした整数値として保存される。

###戻り値:
gdstk.Libraryオブジェクトを出力する。

###メソッド(抜粋):
* add(*cells): ライブラリにセルを追加する。
* new_cell(name): nameという名前のセルを生成してlibraryに追加する。戻り値はcellオブジェクト。
* top_level(): Top level cellの名前を返す。
* write_gds(outfile[, max_points, timestamp]): GDSII形式のレイアウトファイルを出力する。
* write_oas(outfile[, compression_level, …]): OASIS形式のレイアウトファイルを出力する。

###例:
`lib = gdstk.Library()` 無名のライブラリを単位1um, 精度1nmで生成して変数libに格納する。
`topcell = lib.new_cell("Top")` libに"Top"という名前のセルを追加し、変数topcellに格納する。
`lib.write_gds('output.gds')` libの内容を'output.gds'という名前のGDSIIファイルに出力する。

##[gdstk.Cell](https://heitzmann.github.io/gdstk/library/gdstk.Cell.html#gdstk.Cell)
`class gdstk.Cell(name)`
Cellストラクチャを出力する。Cellはレイアウトの基本構造である。あるセル中には、ジオメトリ(各種図形)、ラベル、他セルへのリファレンスが含まれ、これらが階層的に定義されることで最終的なレイアウトが形成される。

###引数
* name: Cellの名前を与える文字列。Asciiコードでなければいけない。また、ライブラリ中でUniqueでなければならない。Uniqueでない名前を付けた場合、少なくともGDS IIレイアウトの仕様に従った環境ではエラーになる可能性がある。(Uniqueかどうかをチェックするハンドラを設けた方が安全。)

###戻り値:
gdstk.Cellオブジェクトを出力する。

###メソッド(抜粋):
* add(*elements): セルにジオメトリ要素（ポリゴン、パス、テキスト）やラベル、リファレンス要素を追加する。
* write_svg(outfile[,scaling, precision, ...]): セルの図形をsvg形式で出力する。

##[gdstk.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)`
他セルへの参照（リファレンス）を出力する。このクラスは繰り返し参照や変形をサポートする。

###引数
* cell (gdstk.Cell, gdstk.RawCell, str): 参照されるセル。gdstk.Cellオブジェクト、gdstk.RawCellオブジェクトをサポートする。
* origin: 参照セルが導入される座標のタプル（x, y）。指定しなければ原点になる。
* rotation: 参照されるCellの回転角(ラジアン)。指定しなければ0。
* magnification: 参照セルの倍率。指定しなければ1(等倍)。
* x_reflection: 横方向で鏡面にするかどうかのBoolean。直描か,マスクかなどで切り替える。rotationの前に処理される。
* columns: Referenceをx軸方向に何回挿入するかを表す整数。
* rows: Referenceをy軸方向に何回挿入するかを表す整数。
* spacing: Referenceを繰り返し挿入する場合のx軸方向, y軸方向の間隔を表すタプル(column, raw)。gdstk.Referenceは基底ベクトルをx軸・y軸にとる長方格子のみをサポートするので、それ以外の格子を表現したい場合はgdstk.Repetitionを用いること。ただし面心長方格子や六方格子は長方格子にちょっと工夫するとgdstk.Referenceで表現でき、これはGDS IIレイアウトのサイズ圧縮に貢献するので、できるだけgdstk.Referenceを利用すべき。

###例
```
lib = gdstk.Library() #特に指定しなければ単位はum
topcell = lib.new_cell("Top") #Topセルを作成

subcell = lib.new_cell("subcell") #サブセルを作成
box = gdstk.rectangle((0,0), (1,2)) #左下(0,0),右上(1,2)の四角形を作成
subcell.add(box) #四角形をサブセルに追加
ref = gdstk.Reference(subcell, origin=(0,0), columns =3, rows =2, spacing = (5,5)) #サブセルへの配列リファレンスを原点(0,0)に挿入。3行2列で、間隔は(5,5)
topcell.add(ref) #サブセルへのリファレンスをTopセルに挿入。
```

#[GDSTKのクラス・関数(ジオメトリ関係)](https://heitzmann.github.io/gdstk/reference_python.html#geometry-construction)
ここではジオメトリ関係（図形生成関係）のクラス、関数を説明する。

##[gdstk.Polygon](https://heitzmann.github.io/gdstk/geometry/gdstk.Polygon.html)
`class gdstk.Polygon(points, layer=0, datatype=0)`
多角形（ポリゴン）オブジェクトを出力する。

###引数：
* points: 頂点のシーケンス（リスト、タプル、またはrangeオブジェクト）。各頂点は座標ペア(x,y)または複素表現（x+yj）を受け付ける。(虚数単位はjを用いて1j, 1.5jのように書く。)
* layer: レイヤ番号。
* datatype: データタイプ。

###例:
```
polygon = gdstk.Polygon([(0,0), (1,0), 2+2j, 3j, 2*np.exp(1j*np.pi*7/8)], layer=1)
polygon2 = gdstk.Polygon([(2*np.exp(1j*np.pi*i/4)+6j) for i in range(8)], layer = 1)
polycell = lib.new_cell('Poly')
polycell.add(*[polygon, polygon2])
```
![poly.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377851/c45ed60e-3e1d-c176-7d64-9916e2017314.png)


polygonではpointsをリストで表現している。各要素は直接(x,y)式に書いてもいいし、複素数表記やオイラー(np.exp(1j*θ))表記（np.cos(θ)+1j*np.sin(θ)に展開される）も受け入れられる。
polygon2ではオイラーの公式をジェネレータ式で回した後、[ ]で囲んでリストにしてPolygonに渡している。
polycell.addでは、2つのPolygonオブジェクトを[ ]でリストにまとめた後、*でアンパックしてaddに渡している。addに複数のオブジェクトを渡すときはリストにまとめたものをアンパックして渡す必要がある。

##[gdstk.rectangle](https://heitzmann.github.io/gdstk/geometry/gdstk.rectangle.html)
`function gdstk.rectangle(corner1, corner2, layer=0, datatype=0)`
長方形オブジェクトを出力する。

###引数：
* corner1, corner2: それぞれ、長方形の1頂点とその対角点の座標。各頂点は座標ペア(x,y)または複素表現（x+yj）を受け付ける。(虚数単位はjを用いて1j, 1.5jのように書く。)
* layer: レイヤ番号。
* datatype: データタイプ。

###戻り値:
長方形のgdstk.Polygonオブジェクト。

###例：
```
box1 = gdstk.rectangle((1,1), (2,3), layer=2)
box2 = gdstk.rectangle(np.exp(1j*np.pi*5/4),np.exp(1j*np.pi/4),layer=2)
boxcell = lib.new_cell('Box')
boxcell.add(*[box1,box2])
```

![box.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377851/a442022e-411d-da5b-11b3-d538f1a2437a.png)

##[gdstk.cross](https://heitzmann.github.io/gdstk/geometry/gdstk.cross.html)
`gdstk.cross(center, full_size, arm_width, layer=0, datatype=0)`
十文字マークを出力する。アライメントマークなどに利用できる。自動検出を利用する場合はリソグラフィ装置のマニュアルを読んで推奨のマークサイズを用いること。

###引数：
* center: 中心座標。座標ペア(x,y)または複素表現（x+yj）、オイラーの公式を受け付ける。(虚数単位はjを用いて1j, 1.5jのように書く。)
* full_size: 全体の幅/高さ。
* arm_width: 腕の幅。
* layer: レイヤ番号。
* datatype: データタイプ。

###戻り値:
十文字形のgdstk.Polygonオブジェクト。

###例：
cross = gdstk.cross((0,0), 4, 0.5, layer = 3)
crosscell = lib.new_cell('cross')
crosscell.add(cross)

![cross.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377851/ffe52ada-935a-c57a-2dde-0e11e2d1f3c9.png)

##[gdstk.text](https://heitzmann.github.io/gdstk/geometry/gdstk.text.html)
`gdstk.text(text, size, position, vertical=False, layer=0, datatype=0)`
テキストパターンを出力する。

###引数：
* text (str): 出力したい文字列。
* size: 文字のサイズ。
* 文字列の始点座標。座標ペア(x,y)または複素表現（x+yj）、オイラーの公式を受け付ける。(虚数単位はjを用いて1j, 1.5jのように書く。)
* vertical (boolean):
* layer: レイヤ番号。
* datatype: データタイプ。

###戻り値:
gdstk.Polygonの**リスト**。

###例：
```
text = gdstk.text('First GDS\n by K. Iwami \t Nov 29\n日本語\nπ,θ,Schrödinger', 2, (0,10),layer = 4) #高さ2のテキストを位置(0,10)に挿入。
textcell = lib.new_cell('textcell')
textcell.add(*text) #gdstk.textは1文字ずつのPolygonのリストを返すので、*textでリストをunpackしてadd()に渡す。
```
![text.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377851/84885d02-d0c5-35db-a254-57c051fbeb89.png)
textには\n, \tなどの制御文字が使える。フォントが見つからない文字は無視される？
gdstk.textは1文字ずつのPolygonのリストを返すので、*textでリストをunpackしてadd()に渡す。

#OASIS形式でファイルを出力する
OASIS形式はGDSIIの後継となるレイアウトフォーマットである。非矩形配列、Parametric cell、ファイル内部でのzlib圧縮などをサポートしているためGDSIIに比べファイルサイズを1/10程度にできる。KLayoutをインストールしていれば、それと同じフォルダにstrm2oas.exeが入っているはずである[^4]。これはコマンドラインでKLayoutを呼び出してGDSIIをOASIS形式に変換してくれる。下記をfirst.pyに追記して実行すればOASISファイルを出力できる。これはWindowsの場合であるが、strm2oasのパスは適宜読み替えてほしい。

```python:first.py
import subprocess
del lib #ファイルを出力したのでメモリ確保のためgdsライブラリを破棄する。
subprocess.call('C:\\Users\\<username>\\AppData\\Roaming\\KLayout\\strm2oas.exe output.gds output.oas')

```

[^1]: c++で直にGDS入出力もできるらしい。

[^2]: gdspyで30分かかっていたファイル出力(2GB,リファレンス数が2600万くらい)が、100秒程度になった。

[^3]: ちょっと前までは依存モジュール(numpy, cython, cmake, LAPACK, BLAS, etc...)を手動インストールの上、自分でコンパイルしないといけなかった。楽になったなあ。

[^4]: 「KLayoutのPython script機能でOASIS吐けばいーじゃん」と思うかもしれない。確かにそうなのだが、やってみるとこの機能はgdspyよりも遅く、大規模なGDSで使うのはちょっと気が引けた。