1. 背景
Lumerical DEVICE は CHARGE(電気ポテンシャル・キャリア分布)、FEEM(有限要素光モード解析)を統合的に扱える強力なデバイスシミュレーション環境です。
特に高速光変調器を設計していると、
印加電圧による屈折率変化
電極配置やギャップなどの位相シフタの断面形状が性能(VπL・損失)へ与える影響
を同時に評価する必要があります。
本記事では Ansys が公開している Thin Film Lithium Niobate Electro-Optic Phase Modulator の事例(公式ドキュメント:
https://optics.ansys.com/hc/en-us/articles/19435937674387-Thin-Film-Lithium-Niobate-Electro-Optic-Phase-Modulator
)をベースに、
LiNbO₃ 導波路のコア幅(shift)をずらしながら
CHARGE → FEEM の連成計算を自動実行し
neff, Lπ, VπL, 損失を voltage sweep とともに可視化する
Python スクリプトを解説します。
2. この記事で扱う内容
本記事の目的は次の通りです。
Lumerical CHARGE FEEM を Python API(lumapi)で自動操作する
導波路コアの頂点座標をパラメトリックに変化させて geometry を更新
CHARGE(静電場)→ FEEM(モード解析)の連続計算
VπL や損失など光変調器の指標を voltage sweep で取得
shift パラメータごとに重ね書きプロット
実際の開発環境では パラメトリック設計→性能評価 を Python で一気に回すことで圧倒的に設計効率が上がります。
3. 使用する Lumerical 事例
本記事の内容は、以下の公式アプリケーション例を Python で自動化したものです:
Thin Film Lithium Niobate Electro-Optic Phase Modulator
https://optics.ansys.com/hc/en-us/articles/19435937674387-Thin-Film-Lithium-Niobate-Electro-Optic-Phase-Modulator
プロジェクトファイルや、lsfスクリプトはこちらからダウンロードできます。
今回は上の図に示すように、導波路コアの幅をデフォルト値、デフォルト値+1.0 um変えた場合について自動化するスクリプトを組んでみました。
4. スクリプトの構成と解説
以下で紹介するスクリプトは次の流れで実行されます
①DEVICE プロジェクト(.ldev)や.lsfスクリプトを読み込み
②shift 値をループし、導波路コア幅を変更
③geometry に新しい頂点座標(vertices)をセット
④CHARGE の Lumerical Script(.lsf)を実行
⑤FEEM の Script(.lsf)を実行
⑥出力値(neff, dneff, Lπ, α)を取得
⑦voltage sweep のグラフを shift ごとに描画
CHARGE/FEEM の .lsf は GUI でボタンを押したときとまったく同じ処理が走り、Python から完全自動化できます。
5. スクリプト全文
こちらが今回作成したpythonスクリプトになります。
導波路形状はポリゴンで定義してます。
あと、application galleryの事例ではfeemの電圧スイープ範囲は0~5Vで11ポイントで定義されているので、python上からは制御しないようにしています。
電圧スイープを変更する場合にはプロジェクトファイルを変更する必要があります。
# -*- coding: utf-8 -*-
"""
LN 位相変調器:CHARGE + FEEM 連成 & shift 掃引
横軸=Voltage、各 shift で重ね書きプロット
"""
import lumapi
import os
import numpy as np
import matplotlib.pyplot as plt
# ==============================
# パス設定
# ==============================
project_dir = r"C:/Users/babatake/Documents/LN modulator" #ここのフォルダパスは変更してください
project_file = os.path.join(project_dir, "LN_phase_modulator.ldev").replace("\\", "/")
charge_script = os.path.join(project_dir, "LN_phase_modulator_CHARGE.lsf").replace("\\", "/")
feem_script = os.path.join(project_dir, "LN_phase_modulator_FEEM.lsf").replace("\\", "/")
# ==============================
# パラメータ設定
# ==============================
# ずらし量 shift
shift_values = np.linspace(0.0, 0.5, 2)
# FEEM 側の電圧 sweep に対応(例:0〜5V, 11点)
v_start = 0.0
v_end = 5.0
v_points = 11
# shift ごとの結果を保存
shift_results = {}
print("Starting Lumerical DEVICE simulations...")
with lumapi.DEVICE() as device:
device.load(project_file)
# 元の座標
x1_original = -6.35
x2_original = -6.7
x3_original = -7.8
x4_original = -8.15
center_x = (x2_original + x3_original) / 2.0
for shift in shift_values:
device.switchtolayout()
print(f"\n🔹 Running simulation for shift = {shift} ...")
half_distance = abs(x2_original - x3_original) / 2.0
x2 = center_x + half_distance + shift
x3 = center_x - half_distance - shift
x1 = x2
x4 = x3
# 頂点(µm → m)
verts = np.array([
[x1, 2.7],
[x2, 2.86],
[x3, 2.86],
[x4, 2.7],
[-25.0, 2.7],
[-25.0, 2.0],
[25.0, 2.0],
[25.0, 2.7],
]) * 1e-6
device.setnamed("geometry::LiNbO3 WG", "vertices", verts)
print("✔️ geometry updated")
# CHARGE
with open(charge_script, "r", encoding="utf-8") as f:
device.eval(f.read())
print("✔️ CHARGE done")
# FEEM
with open(feem_script, "r", encoding="utf-8") as f:
device.eval(f.read())
print("✔️ FEEM done")
# ---------- FEEM 結果取得 ----------
neff = np.array(device.getv("neff_TE")).ravel()
dneff = np.array(device.getv("dneff")).ravel()
L_pi = np.array(device.getv("L_pi")).ravel()
alpha = np.array(device.getv("alpha_dB")).ravel()
Volt = np.linspace(v_start, v_end, neff.size)
# 保存
shift_results[shift] = {
"Volt": Volt,
"neff": neff,
"dneff": dneff,
"L_pi": L_pi,
"alpha": alpha
}
print("\nAll simulations completed.\n")
# ======================================================
# グラフ作成(横軸=Voltage、各 shift を重ね書き)
# ======================================================
plt.figure(figsize=(12, 8))
for shift, data in shift_results.items():
V = data["Volt"]
neff = data["neff"]
Lpi = data["L_pi"]
alpha = data["alpha"]
# neff
plt.subplot(2, 2, 1)
plt.plot(V, np.real(neff), label=f"shift={shift:.2f}", linewidth=2)
plt.xlabel("Voltage (V)")
plt.ylabel("neff")
plt.title("Effective Index vs Voltage")
plt.grid(True)
# L_pi
plt.subplot(2, 2, 2)
plt.plot(V, Lpi * 100, label=f"shift={shift:.2f}", linewidth=2)
plt.xlabel("Voltage (V)")
plt.ylabel("L_pi (cm)")
plt.title("L_pi vs Voltage")
plt.grid(True)
# V_piL
plt.subplot(2, 2, 3)
VpiL = V * Lpi * 100 # ここが 1D × 1D の安全な積
plt.plot(V, VpiL, label=f"shift={shift:.2f}", linewidth=2)
plt.xlabel("Voltage (V)")
plt.ylabel("V_piL (V·cm)")
plt.title("V_piL vs Voltage")
plt.grid(True)
# alpha
plt.subplot(2, 2, 4)
plt.plot(V, alpha, label=f"shift={shift:.2f}", linewidth=2)
plt.xlabel("Voltage (V)")
plt.ylabel("Loss (dB/cm)")
plt.title("Loss vs Voltage")
plt.grid(True)
# 凡例をまとめて追加
for i in range(1, 5):
plt.subplot(2, 2, i)
plt.legend()
plt.tight_layout()
plt.show()
print("All plots completed.")
6. 実行結果(期待されるプロット)
光導波路の有効屈折率:neff(TE)
πの位相変化に必要な位相シフタの長さ:Lπ(cm)
変調効率:VπL(V·cm)
損失: α(dB/cm)
を各シフト量に対してプロットを重ね書きした図になります。
このスクリプトを実行すると、LiNbO₃ 位相変調器の重要パラメータが voltage sweep に対してプロットされます。導波路コア幅をちょっとずらしても結果がほとんど変わらないので、グラフは一つに見えていますが。
shift を変更した複数ケースを重ねて比較できるため、電極の位置ずれや設計最適化の感度評価が容易になります。
特に VπL の最適化では、電極ギャップと導波路中心位置の相対位置が支配的であり、本ツールはその評価に極めて有効です。
7. まとめ
本記事では、Lumerical DEVICE CHARGE + FEEM を Python(lumapi)から完全自動化し、電極位置 shift を掃引しながら性能評価する手法を紹介しました。
geometry 更新を自動化
CHARGE/FEEM の連成呼び出し
neff, Lπ, VπL, α の取得
shift ごとの重ね書きプロット
これにより、設計パラメータ探索が GUI を使うより圧倒的に高速になります。
Lumerical とpython apiとを組み合わせた光デバイス設計の自動化は、企業・研究用途で非常に有効ですので、ぜひ自身のプロジェクトにも応用してみてください。
※本記事は筆者個人の見解であり、所属組織の公式見解を示すものではありません。
問い合わせ
光学シミュレーションソフトの導入や技術相談、
設計解析委託をお考えの方はサイバネットシステムにお問合せください。
光学ソリューションサイトについては以下の公式サイトを参照:
👉 光学ソリューションサイト(サイバネット)
光学分野のエンジニアリングサービスについては以下の公式サイトを参照:
👉 光学エンジニアリングサービス(サイバネット)

