ごあいさつ
昨今、国内のメタレンズ設計プレイヤーも、市場調査フェーズから実際にツール上で設計するフェーズに移行してきている印象を持っています。
これからメタサーフェース設計される方、今現在設計に着手されている方の後押しになれば幸いです。
※本記事は筆者個人の見解であり、所属組織の公式見解を示すものではありません。
PlanOpSimとは
PlanOpSimは平面光学およびメタサーフェス設計用ソフトウエア
PlanOpSim APIとは
- PlanOpSim-APIは外部プラットフォームPythonからPlanOpSimを動作させるAPI
- PlanOpSim専用SDKが用意されており、Python側からポスト処理することが可能
- より高度な解析機能を拡張したり、解析操作の自動化で高速化したり..etc
Pythonインストール
Pythonダウンロードリンクはこちら
https://www.python.org/downloads/windows/
Python 3.10.0以上が必要となります。(2025/12/3現在)
SDK要求モジュール一覧
コマンドプロンプトを管理者権限で開き以下モジュールインストールコマンドを実行。
例 : py -3.10 -m pip install matplotlib
インフォメーション
Pycharmを使用する場合、このフェーズでのインストールは不要です。
■ 要求モジュール ■
- pytest>=5.4.2
- pytest>=Pillow>=9.52
- numpy~=1.26.4
- matplotlib~=3.8.2
- pandas~=2.2.0
- scipy>=1.4.1
- seaborn>=0.10.1
- joblib>=1.1.0
- pytz>=2022.1
- numba>=0.53.1
- Cython>=0.29.30
- requests~=2.31.0
- msgpack~=1.0.7
- beautifulsoup4~=4.12.3
- lxml>=5.1.0
- scikit-learn~=1.4.2
- scikit-optimize~=0.10.1
Pycharmインストール
- PycharmはPythonに特化したIDE(=Integrated Development Environment)です。
- VisualStudioでも良いですが、Pycharmはモジュール管理などが容易です。
- Ver 2023.1.5は動作実績あり。
- 光線追跡ツールAnsys Zemax OpticStudio(以降Zemaxと呼称)のZOS-APIでも
安定性して連携できます。 - 以下無償版Pycharmダウンロードリンク
https://www.jetbrains.com/ja-jp/pycharm/download/other.html
Pycharmの紹介
1. 新しいプロジェクトの作成
2. インタプリタについて
Pythonにおけるモジュール(Module)とは、Pythonコードを整理するためのファイル単位の単位であり、関連する関数、クラス、変数をまとめたものです。モジュールは他のPythonファイルからインポートして利用することができ、コードの再利用性が高まり、プログラムの構造を整理が容易です。
3. Pycharmでのモジュールインストール
先程、要求モジュールをスキップしたのは、ここで検索&インストール出来るためです。
PlanOpSim API 立上げ
1.APIサンプルの取得
[Wiki]からサンプルコード、API関数のドキュメントがダウンロード可能です。

2.フォルダ構成
右のファイルおよびフォルダは同じ階層に保存してください。
- main.py
- API_myconfig.py(認証ファイル)
- APIUtensilClasses(解析用ライブラリフォルダ)
3.API _myconfig.py
-ライセンス認証情報管理に「API _myconfig.py」が必要です。
-APIとPlanOpSimはインタラクティブに接続する際、必ず呼び出す必要があります。
4.サンプルコードの実行
「新しいプロジェクトの作成」で取得したサンプルをmain.pyにコピーして実行します。

結像メタレンズ解析自動化の例(コア処理抜粋)
以下の解析の流れを自動化します。
- メタ原子の構造定義と解析
- メタ原子解析結果のライブラリ化
- メタサーフェスの設計と近傍界/遠方界解析
1.メタ原子構造・RCWAスイープ変数の定義
メタ原子の解析条件は以下の通りです。
- 設計波長:530nm(TE偏光 平面波 垂直入射条件)
- 400nm x 400nm 周期の円柱ピラー形状のみ
- ピラー/基板材質:SiO2/Si
- ピラー高さ:1000nm固定
- 曲率半径:10nm-190nm(30nmステップ) スイープ変数
############################LOGIN###############################
#login settings have to be set in the API_myconfig.py file
server = API_myconfig.server
POS = PlanOpSim_API(server=server)
POS.login(username=API_myconfig.username, password=API_myconfig.password)
################################################################
runmetacell = True
runmetacomp = True
version = 3
subversion = 2
wavelength = 530
if runmetacell:
# ###################LOAD existing file##########################
# #load and print existing files (not mandatory if you can locate your file based on folder and name directly)
# configurations = POS.metacell.get_existing_configurations()
# print(configurations)
# select your simulation (any combination that gives unique value, if not specified set value to None)
project = 'FocalLens_v{}'.format(version)
folderID = None
simulation = 'FocalLens_v{}_{}'.format(version, subversion)
simulationID = None
POS.metacell.load(project=project, projectID=folderID, simulation=simulation,
simulationID=simulationID) # any combination that gives a unique file, id doesn't have to be specified
# ###############################config###########################
# edit global parameters: edit the folder name, simulation name, cell width or cell height
projectname = None
simulationname = None
cellWidth = 400
cellHeight = 400
POS.metacell.global_parameters.edit(projectname=projectname, simulationname=simulationname, cellWidth=cellWidth,
cellHeight=cellHeight)
# edit simulation accuracy (diffraction orders of RCWA)
POS.metacell.simulation_settings.edit_accuracy(accuracy=[4, 4])
# edit optimizer:
POS.metacell.simulation_target.edit(name='new_target', optimizer='sweep_validation')
# create lightsettings
amplitude = 1
polarization = 'TE'
azimuth = 0
zenith = 0
POS.metacell.incident_lights[0].edit(amplitude=amplitude, wavelength=wavelength, polarization=polarization,
azimuth=azimuth, zenith=zenith)
# ###############################create sweepvars###########################
# #get existing sweep vars and print (just for convenience to check the existing sweep vars, not mandatory, if you know the sweep alias this can be skipped)
# sweepvars = POS.metacell.get_existing_sweepvars()
# print(sweepvars) if not sweepvars.empty else print('no existing sweepvars yet')
# add specific sweepvar based on alias
POS.metacell.simulation_settings.add_sweep(
SweepVariable('radius', 'Linear', '10;191;30')) # note: no special characters allowed
# create local materials to use (this will use the existing ones from your library)
Si = Material(name='Si') # Use Si from library # default None = Use from library;
SiO2 = Material(name='SiO2', refractive_index=1.5,
absorptioncoeff=None) # use SiO2 from library but overwrite refr index
Air = Material(name='Air', refractive_index=1, absorptioncoeff=0) # overwrite both refr index and abs coeff
# ##############################################define layers##################################
# edit incident and exit layer, note, these should satisfy rules for incident and output layer
layer_inc = POS.metacell.structure.layerstack[0]
layer_inc.edit('Incident layer', thickness='INF', materials=Si) # none means library value
layer_out = POS.metacell.structure.layerstack[-1]
layer_out.edit('Exit layer', thickness='INF', materials=Air)
# if other layers already exist you can replace
# them as below with 'POS.metacell.structure.layerstack[n].edit(newlayer)'
# create additional layers: examples single pillar
pillar_layer_to_add = CircleLayer(name='pillar', thickness=1000, materials=[Air, SiO2], radius='=radius',
num_steps=[500, 500], center_coord=[0, 0])
POS.metacell.structure.add_layer(position=1, layer=pillar_layer_to_add)
2. RCWA解析
曲率半径依存の位相および透過率データをプロットしてみます。
#############################################simulate##################################################
simulationjobID = POS.metacell.simulate(run_if_exist = False) # uploads/updates simulation and runs it on the server
#############################################plot##################################################
try: #check if plot is already existing, disable if you want to update your plots
PlotInfo_powercoeff = POS.metacell.MCell_plot[0].plotdata
PlotInfo_fieldcoeff = POS.metacell.MCell_plot[1].plotdata
assert (PlotInfo_fieldcoeff['yData'] is not None)
except:
while len(POS.metacell.MCell_plot) < 2: #initialize 2 empty plots
POS.metacell.add_plot(
simulationJob_id=POS.metacell.lastsimulationjob) # use either response of MCell.simulate or None (last runned simulation)
inc_light_name = POS.metacell.incident_lights[0].name
POS.metacell.MCell_plot[0].edit_configurations(plottype='line', xaxes='radius',
yaxes='{}/T/0,0/TE/power_coeff/Abs'.format(inc_light_name))
PlotInfo_powercoeff = POS.metacell.MCell_plot[0].run_plot()
POS.metacell.MCell_plot[1].edit_configurations(plottype='line', xaxes='radius',
yaxes='{}/T/0,0/TE/field_coeff/Phase'.format(inc_light_name))
PlotInfo_fieldcoeff = POS.metacell.MCell_plot[1].run_plot()
################locally postprocess the plotting data#################################
# local plot of line plot
# auto generate plot:
POS.metacell.MCell_plot[0].show_plot_locally()
POS.metacell.MCell_plot[1].show_plot_locally()
3. モード解析
Z位置(プローブ)=50nmの位置でのEx(実部) XY断面描画します。
# ###############perform mode analysis#################################
try:
mode_analysis = POS.metacell.mode_analysis[0].plot[0]
assert (mode_analysis['yData'] is not None)
except:
while len(POS.metacell.mode_analysis) < 1:
sweepvarnames = [sweepvar.alias for sweepvar in POS.metacell.simulation_settings.sweepvars]
sweepvarvalues = [sweepvar.values[0] for sweepvar in POS.metacell.simulation_settings.sweepvars]
POS.metacell.add_mode_analysis(sweepvarnames, sweepvarvalues, probe = 50)
POS.metacell.mode_analysis[0].add_plot()
POS.metacell.mode_analysis[0].plot[0].edit_configurations(quantity='Ex', representation='Re')
PlotInfo_powercoeff = POS.metacell.mode_analysis[0].plot[0].run_plot()
POS.metacell.mode_analysis[0].plot[0].show_plot_locally()
スイープ変数の範囲が大きい場合、ChromeやMicrosoft Edge等のブラウザでは、
メモリ不足で描画処理動作が不安定となりがちです。(Firefoxは比較的安定してます。)
→APIの場合は、一度の処理でデータを授受出来るため、処理動作を気にすることないこともメリットの一つですね。
4. φ100μm メタサーフェス 設計ターゲット定義
※先程のメタ原子RCWA Sim結果を全てライブラリ化しています。
- TE偏光平面波のみ 垂直入射 530nm
- メタ原子配列のターゲット位相を (2π/λ) * (√(x² + y² + f²) - f)の球面式で近傍界設計
- 焦点距離 500μm
################################################################################################
# ######################################make new library##########################################
# #get all existing families#just for reference if you need to find the id of an existing group
# mcell_groups = POS.library.get_mc_groups()
# print(mcell_groups)
# start from new group
# ##### load existing group from server
name = simulation + '_family_example'
id = None
metacell_group = POS.library.load_mcell_group(name=name,
id=id) # if name is unique, the id doesn't have to be given, if grout doesn't exist a new one will be created
# select all members from metacell simulation
sweepvar_list = POS.metacell.get_sweepvar_list()
sweepvarnamelist = [sweepvar.alias for sweepvar in sweepvar_list]
valuelists = [np.array(combination) for combination in
itertools.product(*[sweepvar.values for sweepvar in sweepvar_list])]
for valuelist in valuelists:
metacell_group.add_member(POS.metacell.lastsimulationjob, sweepvarnamelist, valuelist)
POS.library.update_mcell_group(metacell_group) # create new group
if runmetacomp:
################################################################################################
# ######################################make new component########################################
# #load and print existing metacomponent files (not mandatory if you can locate your file based on folder and name directly)
# configurations = POS.metacomponent.get_existing_configurations()
# print(configurations)
# select your simulation (any combination that gives unique value, if not specified set value to None)
comp_group = 'FocalLens_v{}'.format(version)
comp_group_id = None
component = 'FocalLens_v{}_{}'.format(version, subversion)
component_id = None
POS.metacomponent.load(comp_group=comp_group, group_id=comp_group_id, meta_component=component,
meta_component_id=component_id) # any combination that gives a unique file, id doesn't have to be specified
# ###############################config###########################
# edit global parameters: edit the folder name, simulation name, cell width or cell height
component_group = None
metacomponent_name = None
component_width = 100
component_length = 100
unit = 'mum'
POS.metacomponent.component_dimensions.edit(component_group=component_group, metacomponent_name=metacomponent_name,
component_width=component_width, component_length=component_length,
unit=unit)
# add metacell group, and metacells (member list or None for all)
name = 'FocalLens_v{}_{}'.format(version, subversion) + '_family_example'
id = None
metacell_group = POS.library.load_mcell_group(name=name, id=id)
# if name is unique, the id doesn't have to be given, if group doesn't exist a new one will be created
POS.metacomponent.add_metacell_group(group=metacell_group, member_list=None)
if len(POS.metacomponent.targets) == 0:
POS.metacomponent.add_target()
POS.metacomponent.targets[0].setpoint.edit(name='sp_target1', amplitude=1, wavelength=530, polarization='TE', azimuth=0,
zenith=0, unit='nm',
type='GB', focalspot=[0, 0, 0], beam_divergence=0.05) # note: unit only for setpoint wavelengt as in the GUI!
POS.metacomponent.targets[0].edit(name='target 1', description = 'SDK created', optimize_for='Complex', weight=1)
POS.metacomponent.targets[0].metacell_accuracy.edit(acc_x=4, acc_y=4)
POS.metacomponent.targets[0].output_light.edit(order_x=0, order_y=0, direction='Transmission', polarization='TE')
# example code to add 2nd target
# if len(POS.metacomponent.targets) == 1:
# POS.metacomponent.add_target()
#
# POS.metacomponent.targets[1].setpoint.edit(name='sp_target2', amplitude=1, wavelength=540, polarization='TE', azimuth=0,
# zenith=0, focalspot=[10, 20, 30], type='GB', beam_divergence=5,
# unit='nm') # note: unit only for setpoint wavelengt as in the GUI!
#
# POS.metacomponent.targets[1].edit(name='target 2', description='SDK created', optimize_for='Complex', weight=1)
# POS.metacomponent.targets[1].metacell_accuracy.edit(acc_x=4, acc_y=4)
# POS.metacomponent.targets[1].output_light.edit(order_x=0, order_y=0, direction='Transmission', polarization='TE')
# example to add static script from txt file
# POS.metacomponent.targets[-1].nfwf.filename='NFWF_script.txt'
# example to make a dynamic script string
def nfwf_lens_script(wl, f):
nfwfscript = "wavelength = {}; \
f = {};\
phase = 2 * np.pi / wavelength * (np.sqrt(x ** 2 + y ** 2 + f ** 2) - f);\
amplitude = np.ones_like(x);\
phase = np.mod(phase, 2 * np.pi)".format(wl, f)
return nfwfscript
focaldistance = 500
POS.metacomponent.targets[0].nfwf.script_string = nfwf_lens_script(wl=wavelength / 1000,
f=focaldistance) # note comp dimension is in unit=mum!
#example code for second target
# focaldistance = 2000
# POS.metacomponent.targets[1].nfwf.script_string = nfwf_lens_script(wl=wavelength / 1000,
# f=focaldistance) # note comp dimension is in unit=mum!
ターゲットとする振幅と位相分布断面です。
今回球面式で与えていますが、Zemaxから位相データをインポートもできます。

5. メタ原子配列
PlanOpSim側で自動配列でき、GDS上で円柱ピラーが並んでいることを確認できました。
############################################simulate##################################################
designJob_id = POS.metacomponent.design()
GDS_file = POS.metacomponent.create_gds(foldername='gds_download')
5. メタレンズの近傍界解析と遠方界解析、フーリエ解析
- 遠方界解析では、設計波長530nmの集光スポット Z=500μm±250μmのXY分布を解析
- 近傍界では、メタレンズ透過直後の位相振幅分布を解析
########################################analysis##################################################
try:
Analysis_info_nfwf = POS.metacomponent.analysis[0].plotdata
except:
while len(POS.metacomponent.analysis) <= 0:
POS.metacomponent.add_analysis(
designJob_id=POS.metacomponent.lastdesignjob) # use either response of MCell.simulate or None (last runned simulation)
POS.metacomponent.analysis[0].add_analysis_sweep_var('z', focaldistance / 2, focaldistance * 2 + 1E-5,
focaldistance / 2)
POS.metacomponent.analysis[0].edit_ff_properties(width=100, rows_count=100, height=100, columns_count=100,
unit='μm', center_coord=[0, 0, '=z'])
POS.metacomponent.analysis[0].setpoint.edit(wavelength=500,unit='nm', diffractive_order = [3,3])
POS.metacomponent.analysis[0].type = 'ALL'
POS.metacomponent.analysis[0].run()
POS.metacomponent.analysis[0].show_analysis_locally()
遠方界解析結果
近傍界解析結果
勿論ですが、手動解析と同じ解析結果が自動で出力できました。
メタ原子解析からメタレンズの伝搬解析まで約5分程度で実行できました。
(実オペレーションだと体感30分程度でしょうか..)
最終的には、Zemax上の光学系データと連成解析し、メタレンズ設計にフィードバックまで自動化できるとより簡便ですよね。
お問い合わせ
- より詳細なAPI関数を学習するには、サンプル内のドキュメントをご覧頂くか、
弊社サポートサイトをご活用下さい。 - 光学シミュレーションソフトの導入や技術相談、
設計解析委託をお考えの方はサイバネットにお問合せください。














