はじめに
前回の記事でRCWAの計算を行うのに使いやすいPythonのライブラリを知らないと述べた。しかしながら、最新のmetasaface関連の論文[changhyun2024]を読んでいるときにsamsung資本で開発されたrcwaのPythonモジュール、torcwaが存在することを知った。後述するが計算が遅いPythonにおいて計算の最適化や近年のmetasurface計算の潮流にあった構成となっている。
TORCWAについて
torcwaとは機械学習でよく使われているPythonのtorchとRCWAを組み合わせたモジュールである詳細は[changhyun2023]に記載されている。torchで作られているため、GPUベースでの計算もサポートされている。また、torchが機械学習界隈でよく用いられることから計算最適化の最前線を走っているのも魅力の一つであるといえる。
一方、torch関連のインストールは少しめんどくさいので環境構築にはある程度苦労が必要である。
torcwaのインストール
torcwaのインストールの手順はこちらを参考にしてインストールを行った。以下のようなロジックツリーに沿ってインストールをしていく。現在、使っているPCはGPUがあるためGPUベースで、会社のPCはまともなGPUは乗っていないのでCPUベースで環境構築を行った。
Pytorchのインストールは基本的に最新の情報を見たほうがいいと思う。自分は「Pytorch install 2025」で検索して出てきたこちらのサイトを参考にした。以下の手順を絶対守ってインストールしてください。(一度失敗しました。)
GPUベースでの環境構築
- 自分のGPUを確認します。
検索窓から「コントロールパネル」→「GPU」で調べることができます。
筆者の場合はRTX4060tiであることが確認できました

- 自分のGPUとどこまでcudaが対応しているかを確認します。
GPUの型番にあったCUDAバージョンの選び方←を参考にすると筆者の場合は基本的に最新バージョンは全部行けることがわかった。 - Pytorchのサイトで現在インストールできるtorchとcudaの組み合わせを確認します(確認だけしてください、インストールはダメ絶対)。
stableのバージョンで、2025/7現在cuda11.8, 12.6, 12.8がサポートされているようです。
先ほど確認したGPUとcudaの組み合わせを満たすもので最新のを選択します。筆者にマッチしたcuda12.8になりそうです。

- バージョンのあったcudaをインストールします。
全部のバージョンのアーカイブからマッチしたcudaをインストールします。
筆者は12.8をインストールしました。 - cuDNNをインストールします。
これもcudaのバージョンにあったものを選びます。こちらにテーブルがあるので似ためっこしながらあったバージョンを入れます。(こいつを入れる意味はよくわかりませんが…)
ここでいったん再起動をしてください。 - Pytorchをインストールします。
先ほどのPytorchのサイトからインストールします。最初に決めたバージョンを選択して、コピペしてインストールするだけです。 - Python上で以下のコードでインストールできたかを確認します。
import torch
print(torch.__version__) # 2.7.1+cu128
print(torch.cuda.is_available()) # True- 最後にいつものpip installでtorcwaをinstallして終了です。
!pip install torcwaCPUベースでの環境構築
- Pytorchのサイトでバージョン確認してインストールします。
stableのバージョンで、CPUを選択、Run this commandをコピペして、コマンドプロンプトで実行するだけです。

- いつものpip installでtorcwaをinstallして終了です。
!pip install torcwatorcwaでのサンプルコール
前回同様に[ma2024]の論文の結果を参考に実際に透過率の計算を行ってみる。
基本的な使い方はdeepwikiを参考にしてやってみるとよかったので、ぜひこちらを読んでほしい。
この論文によるとメタアトムのパラメータは以下のようにまとめられる。
| 項目 | 数値・仕様 |
|---|---|
| 材料(メタアトム) | アモルファスシリコン(α-Si) |
| 基板材料 | 石英(quartz, n ~ 1.45) |
| 波長 | 1064 nm |
| メタアトム周期 | P = 500 nm |
| メタアトム高さ | H = 800 nm |
| メタアトム半径 | 80 nm ~ 147 nm(2π位相範囲で選定) |
また、図より形状は円柱状であることがわかっている。これをjulia同様計算してみると
# Import
import numpy as np
import torch
from matplotlib import pyplot as plt
import scipy.io
import tqdm
import torcwa
# Hardware
# If GPU support TF32 tensor core, the matmul operation is faster than FP32 but with less precision.
# If you need accurate operation, you have to disable the flag below.
torch.backends.cuda.matmul.allow_tf32 = False
sim_dtype = torch.complex64
geo_dtype = torch.float32
device = torch.device('cuda') #CPUの場合はcpuを選択
# Simulation environment
# light
inc_ang = 0.*(np.pi/180) # radian
azi_ang = 0.*(np.pi/180) # radian
# material
substrate_eps = 1.45**2
air = 1.0**2
a_Si = 3.55**2
lam = 1064. # nm
# geometry
L = [500., 500.] # nm / nm
torcwa.rcwa_geo.dtype = geo_dtype
torcwa.rcwa_geo.device = device
torcwa.rcwa_geo.Lx = L[0]
torcwa.rcwa_geo.Ly = L[1]
torcwa.rcwa_geo.nx = 500
torcwa.rcwa_geo.ny = 500
torcwa.rcwa_geo.grid()
torcwa.rcwa_geo.edge_sharpness = 1000.
z = torch.linspace(-500,1500,101,device=device)
x_axis = torcwa.rcwa_geo.x.cpu()
y_axis = torcwa.rcwa_geo.y.cpu()
z_axis = z.cpu()
# layers
layer0_geometry = torcwa.rcwa_geo.circle(R=90.,Cx=L[0]/2.,Cy=L[1]/2.)
layer0_thickness = 800.
# View layers
plt.imshow(torch.transpose(layer0_geometry,-2,-1).cpu(),origin='lower',extent=[x_axis[0],x_axis[-1],y_axis[0],y_axis[-1]])
plt.title('Layer 0')
plt.xlim([0,L[0]])
plt.xlabel('x (nm)')
plt.ylim([0,L[1]])
plt.ylabel('y (nm)')
plt.colorbar()
# Generate and perform simulation
order_N = 5
order = [order_N,order_N]
pillar_list = torch.linspace(50.,147,50,dtype=geo_dtype,device=device)
txx = []
for pillar_radius in tqdm.tqdm(range(len(pillar_list))):
sim = torcwa.rcwa(freq = 1/lam, order = order, L = L , dtype = sim_dtype, device = device)
sim.add_input_layer(eps = substrate_eps)
sim.set_incident_angle(inc_ang = inc_ang, azi_ang = azi_ang)
layer0_geometry = torcwa.rcwa_geo.circle(R = pillar_list[pillar_radius], Cx=L[0]/2., Cy=L[1]/2.)
layer0_eps = layer0_geometry*a_Si + (1.-layer0_geometry)
sim.add_layer(thickness = layer0_thickness, eps = layer0_eps)
sim.solve_global_smatrix()
txx.append(sim.S_parameters(orders=[0,0],direction='forward',port='transmission',polarization='xx',ref_order=[0,0]))
txx = torch.cat(txx)
# View Transmittance and phase
fig,ax = plt.subplots()
ax.plot(pillar_list.cpu(),torch.abs(txx).cpu()**2,label='Transmittance (a.u.)',color='blue')
ax2 = ax.twinx()
ax2.plot(pillar_list.cpu(),np.unwrap(torch.angle(txx).cpu())*180/np.pi,color = 'orange',linestyle='--',label='Phase (rad)')
ax.set_title('Spectrum (order: '+str(order_N)+')')
ax.set_xlabel('Pillar radius (nm)')
ax.set_ylabel('Transmittance (a.u.)',color='blue')
ax2.set_ylabel('Phase (rad)',color='orange')
plt.grid()
およそ再現できていることがわかった。また、phaseのところに差異が見られるが、unwrapの計算に違いがあることがjuliaの計算結果も合わせてわかっており、計算方法差分である。
最後に
今回紹介したrcwa計算はtorchベースで作られており、計算の速さはもちろん機械学習との組み合わせも考えられる。さすがはsamusungに資金提供を受けた研究開発ということで、先見の明にドキュメント類の充足もありすごいと感じた。一方、前回紹介したjuliaのほうがソースコードを読みやすく、いろいろカスタマイズもしやすいと感じた。これからpython, julia, matlabで並行してメタサーフェイス設計を行っていきたい。



コメント