GDSIIファイルを gdstk と Shapely を使って設計する方法

# はじめに

この記事では、GDSIIファイルを gdstk だけで生成するのではなく、 Shapely の機能を使って簡単に設計する方法を紹介します。

gdstk の使い方については、

https://qiita.com/yamadasuzaku/items/cdeae106a56b2070b0b6

https://qiita.com/yamadasuzaku/items/b8725ed5b8bda4f877e3

などで紹介したのでそちらを参考にしてください。

ここでは、gdstk を使うよりも、[Shapely](https://shapely.readthedocs.io/en/stable/manual.html) という [GEOS](https://trac.osgeo.org/geos/)をベースとしたpythonライブラリで、ジオメトリの操作および分析のために使われます。Shapely を使うと、簡単にジオメトリの重なりや、面積なども計算できるので、Shapelyを使ってジオメトリを生成し、それを gdstk を用いて GDSファイルに変換する方法を紹介します。

この記事は、google colab でも実行できるように、

https://colab.research.google.com/drive/1PlCxVs1ZT131kuQb2OJP2eplRqYCj-As?usp=sharing

を用意してあります。

可変長の配線幅を生成したいときは、

https://qiita.com/yamadasuzaku/items/17e0b978ff44bc01f879

を参考にしてください。

## 前準備

### gtdtk の install

詳細は、

https://qiita.com/yamadasuzaku/items/cdeae106a56b2070b0b6

を参考にしてください。

ここでは、google colab の例だけ紹介しておきます。

```python: condacolab の install 
# 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
```    

```python: gdstk の install 
# 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

```python
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import *
import gdstk
import shapely.geometry as geometry

print("gdstk.__version__ = ", gdstk.__version__)
```
gdstk.\__version\__ =  0.9.47 (2023.11.29) を用いた例になります。

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

これも詳細は[別の記事](https://qiita.com/yamadasuzaku/items/cdeae106a56b2070b0b6
) を参考にしてください。ここでは、 gdsファイルを matplotlib で表示する関数の定義だけ紹介しておきます。

```python
def plot_gds_onlypoly(gdsfile, transformation=None, maxlayer = 10, margin=3, debug=False):
  lib = gdstk.read_gds(gdsfile)
  top_cells = lib.top_level()
  fig, ax = plt.subplots(1, 1, figsize=(8,8), tight_layout=True)
  cmap = plt.get_cmap('viridis', maxlayer)
  # すべてのトップレベルセルのポリゴンをプロット
  for cell in top_cells:
    (xmin,ymin),(xmax,ymax) = cell.bounding_box()
    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())
        ax.plot(*np.vstack((points, points[0])).T, linestyle='solid', linewidth = 1, alpha=0.8, color=cmap(polygon.layer))

  ax.set_title("polygons")
  ax.set_xlim(xmin - margin, xmax + margin)
  ax.set_xlim(xmin - margin, xmax + margin)
  ax.set_aspect('equal', adjustable='box')
  ax.grid(alpha=0.3)
  plt.savefig(gdsfile.replace(".gds",".png"))
  plt.show()
```

# Shapely を用いて多画素のジオメトリを生成する関数

まず、関数 `create_multi_pixel_array` を作成します。多数のピクセルを含む配列の幾何学的レイアウトを生成し、それを可視化するために設計されています。関数は shapely ライブラリを使用して幾何学的オブジェクトを生成し、matplotlib を使用して結果を描画します。以下は各部分の詳細です。

### 関数の定義
- num_rows, num_cols: 配列の行数と列数。
- pad_size, pixel_size: パッドとピクセルのサイズ。
- wire_width, wire_length: ワイヤの幅と長さ。
- spacing: ピクセル間のスペース。

### リストの初期化:

pads, pixels, wires というリストを初期化して、パッド、ピクセル、ワイヤの幾何学的形状を格納します。

``` python:
    # Initialize lists for pads, pixels, and wires
    pads, pixels, wires = [], [], []
```

### 全体的なレイアウトサイズの計算:

配列の全幅と全高を計算します。

``` python:
    # Calculate total layout size
    total_width = num_cols * (pixel_size + spacing)
    total_height = num_rows * (pixel_size + spacing)
```

### 各ピクセルの幾何学的形状の生成:

二重ループを使用して各ピクセルの位置を計算し、shapely.geometry.box と shapely.geometry.LineString を使用してピクセル、パッド、ワイヤの形状を生成します。

``` python:
    # Create geometry for each pixel in the array
    for i in range(num_rows):
        for j in range(num_cols):
            # Calculate positions
            x_position = (j - num_cols / 2) * (spacing) + spacing / 2
            y_position = (i - num_rows / 2) * (spacing) + spacing / 2
...
```

美しい書き方ではないかもしれませんが、、この for loop の中でジオメトリを生成します。

``` python:
            wire_left = wire_left.buffer(wire_width, cap_style="flat")
            wire_right = wire_right.buffer(wire_width, cap_style="flat")
```

この操作により、LineString という幅のないオブジェクトから、幅のある Polygon に変換します。配線は一次元の紐で生成してから、幅をもたせる、という作戦です。cap_style はデフォルトで round で丸みを持った形状になりますので、ここでは flat にして、特に余計なことはしない、という設定にしてあります。

### matplotlibを使用した描画:

plt.subplots で描画エリアを作成し、ax.fill と ax.plot を使用してパッド、ピクセル、ワイヤを描画します。

``` python:
    # Plotting with matplotlib
    fig, ax = plt.subplots()
    for pad in pads:
        x, y = pad.exterior.xy
        ax.fill(x, y, alpha=0.5, ec='none')
    for pixel in pixels:
        x, y = pixel.exterior.xy
        ax.fill(x, y, alpha=0.5, ec='none', fc='blue')
    for wire in wires:
        x, y = wire.exterior.xy
        ax.plot(x, y, linewidth=wire_width, color='black')
```

`pads` と `pixels` は fill で中身を塗りつぶして、`wires` という配線を想定したオブジェクトは plot で線で描画しています。

### 戻り値

関数は、生成されたパッド、ピクセル、ワイヤのリストを返します。

この関数は、電子工学やマイクロエレクトロニクスの分野で、微細な電子回路やセンサーアレイの設計の簡易版をイメージして作成しています。

下記、`def create_multi_pixel_array` の全文になります。

```python
def create_multi_pixel_array(num_rows, num_cols, pad_size, pixel_size, wire_width, wire_length, spacing):
    """
    Create a geometry layout for a multi-pixel array setup with shapely and plot it with matplotlib.
    """

    # Initialize lists for pads, pixels, and wires
    pads, pixels, wires = [], [], []

    # Calculate total layout size
    total_width = num_cols * (pixel_size + spacing)
    total_height = num_rows * (pixel_size + spacing)

    # Create geometry for each pixel in the array
    for i in range(num_rows):
        for j in range(num_cols):
            # Calculate positions
            x_position = (j - num_cols / 2) * (spacing) + spacing / 2
            y_position = (i - num_rows / 2) * (spacing) + spacing / 2

            # Create pixel
            pixel = geometry.box(x_position - pixel_size / 2, y_position - pixel_size / 2,
                                 x_position + pixel_size / 2, y_position + pixel_size / 2)
            pixels.append(pixel)

            # Create pads for each pixel
            pad_left = geometry.box(x_position - pad_size / 2, y_position - pad_size/2 - pixel_size/2 - wire_length/2,
                                    x_position + pad_size / 2, y_position + pad_size/2 - pixel_size/2 - wire_length/2)
            pad_right = geometry.box(x_position - pad_size / 2, y_position - pad_size/2 + pixel_size/2 + wire_length/2,
                                     x_position + pad_size / 2, y_position + pad_size/2 + pixel_size/2 + wire_length/2)
            pads.extend([pad_left, pad_right])

            # Create wires for each pixel
            wire_left = geometry.LineString([(x_position, y_position - pixel_size / 2), (x_position, y_position - pixel_size / 2 - wire_length/2)])
            wire_right = geometry.LineString([(x_position, y_position + pixel_size / 2), (x_position, y_position + pixel_size / 2 + wire_length/2)])
            wire_left = wire_left.buffer(wire_width, cap_style="flat")
            wire_right = wire_right.buffer(wire_width, cap_style="flat")

            wires.extend([wire_left, wire_right])

    # Plotting with matplotlib
    fig, ax = plt.subplots()
    for pad in pads:
        x, y = pad.exterior.xy
        ax.fill(x, y, alpha=0.5, ec='none')
    for pixel in pixels:
        x, y = pixel.exterior.xy
        ax.fill(x, y, alpha=0.5, ec='none', fc='blue')
    for wire in wires:
        x, y = wire.exterior.xy
        ax.plot(x, y, linewidth=wire_width, color='black')

    ax.set_aspect('equal', adjustable='box')
    plt.savefig("geom_"+str(num_rows) + "_" + str(num_cols) + ".png")
    plt.show()

    return pads, pixels, wires

```

# 実行例

いよいよ準備が整ったので実行してみましょう。

## 1x2 画素の例

now_rows と num_cols を 1, 3 に設定し、下記のパラメータで画素を生成してみます。


```python
# Example parameters
num_rows = 1    # Number of rows in the pixel array
num_cols = 3    # Number of columns in the pixel array
pad_size = 4   # Size of the bonding pads
pixel_size = 12.0 # Size of each pixel
wire_width = 1 # Width of the wires
wire_length = 10  # Length of the wires
spacing = 30.0    # Spacing between pixels

# Create and plot the geometry
pads, pixels, wires = create_multi_pixel_array(num_rows, num_cols, pad_size, pixel_size, wire_width, wire_length, spacing)
```

これを実行すると、次のような３つの画素が生成されます。

![output_15_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/39a2d933-8948-1101-52f2-ed3e2b8d70b5.png)




## 10 x 5 画素の例

次に、now_rows と num_cols を 5, 5 に設定し、25画素の例を作成してみます。

```python
# Example parameters
num_rows = 5    # Number of rows in the pixel array
num_cols = 5    # Number of columns in the pixel array
pad_size = 4   # Size of the bonding pads
pixel_size = 12.0 # Size of each pixel
wire_width = 1 # Width of the wires
wire_length = 10  # Length of the wires
spacing = 30.0    # Spacing between pixels

# Create and plot the geometry
pads, pixels, wires = create_multi_pixel_array(num_rows, num_cols, pad_size, pixel_size, wire_width, wire_length, spacing)
```

これを実行すると、5x5画素のジオメトリが生成されます。

![output_17_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/3e8ab1b3-3d5a-b491-dbbe-1c9bd744b724.png)


# gdstk で GDSII 形式に書き出して保存し、matplotlib で確認

このようにして、Shapelyでジオメトリを作成して、問題なければ、gdstk を用いて GDSII形式に書き出してみましょう。

ここも詳細は[別の記事](https://qiita.com/yamadasuzaku/items/cdeae106a56b2070b0b6
) を参考にしてください。

```python
# LSI回路のジオメトリを作成
lib = gdstk.Library()
cell = lib.new_cell("IMAGER_ARRAY")

tag = {
    "pixel": {"layer": 1, "datatype": 1},
    "wire": {"layer": 5, "datatype": 5},
    "pad": {"layer": 10, "datatype": 10},
}

# Convert shapely geometries to gdstk polygons
for pad in pads:
    cell.add(gdstk.Polygon(pad.exterior.coords, **tag["pad"]))
for pixel in pixels:
    cell.add(gdstk.Polygon(pixel.exterior.coords, **tag["pixel"]))
for wire in wires:
    cell.add(gdstk.Polygon(wire.exterior.coords, **tag["wire"]))

# Save the GDS file
outgdsfile="image_array.gds"
lib.write_gds(outgdsfile)

# GDSファイルに保存
lib.write_gds(outgdsfile)
plot_gds_onlypoly(outgdsfile, debug=False)
```

これを実行すると、

![output_19_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/7ff7fb17-36d2-9c36-7a34-634e2f869781.png)

このように、 gdsファイルから次のようなジオメトリが生成されます。

gdsファイルを [KLayout](https://www.klayout.de) で見ると、

![スクリーンショット 2023-12-12 23.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/bdc76ef4-0686-14b9-e6b9-9b6ef6c5067f.png)

のように見えます。

# 最後に

gdstk を生で動かしてジオメトリを描くよりも、Shapely を使った方が便利かなぁと思って、この記事を書いてみました。