OghmaNanoにおける発光スペクトル
発光スペクトルとは、物質から放出される光が波長ごとにどのように分布しているかを示すものである。OghmaNanoではこのようなスペクトルが材料データベースに保存されており、ユーザーはそれらを確認したりインポートしたり更新したりすることが可能だ。本ページでは、発光スペクトルの意味するところ、その保存・インポート方法、そして実測データが存在しない場合にPythonを用いて近似的なスペクトルを生成する手順について説明する。
1. 発光スペクトルとは何ですか?
電子と正孔が放射再結合を起こす際、光子は単一の特定波長ではなく、さまざまなエネルギー値で放出される。発光スペクトルとは、この光の相対強度が波長の関数としてどのように変化するかを示すものである。このスペクトルは、フォトルミネッセンス(PL)やエレクトロルミネッセンス(EL)といった実験結果から得られる場合もあれば、理論モデルから算出される場合もある。なお、発光スペクトルは物質の吸収係数や屈折率とは異なる概念である。
発光スペクトルの形状は、半導体の電子構造や温度、組成に加え、実際のデバイスにおいてはキャリア密度や光学的効果にも依存する。正規化された発光スペクトルは波長に応じた相対的な分布を示すものであり、絶対的な光出力やLEDの効率を表すものではない。
2. OghmaNanoの材料データベースにおける発光スペクトル
各材料にはOghmaNano材料データベース内に専用のディレクトリが存在します。発光データが利用可能な場合、そのデータはemission.csvという名前のファイルに、alpha.csvやn.csvといった他のファイルと共に保存されます。例えば、ある材料用のフォルダー内には以下のようなファイルが含まれることになります:
materials/
GaAs/
emission.csv
alpha.csv
n.csv
シミュレーションで利用される材料データベースは、ユーザーのoghma_local/materialsディレクトリ配下に配置されています。ディレクトリ構造や材料ファイルの形式については材料データベース:パートC – 材料の保存方法をご参照ください。また、材料の閲覧および編集方法の概要については材料データベース:パートAをご覧ください。
emission.csvファイルには、単位はメートルで表された波長と、任意単位で表された発光の相対強度が記録されています。OghmaNano形式のファイルにはメタデータヘッダーも含まれており、このヘッダー内のy_len値がその後に続く波長と強度のデータ点数を示しています。
3. 測定済みの発光スペクトルのインポート
測定済みのフォトルミネッセンス(PL)またはエレクトロルミネッセンス(EL)データが存在する場合、そのデータが対象材料および目的とする半導体デバイスに適合していれば、近似的に生成された発光スペクトルよりも好ましい選択肢となる。まずMaterialsデータベースから該当する材料を開き、その発光スペクトルを選択した上でデータインポート機能を用いて測定値の曲線を取り込む。2列からなるデータセットをインポートし、波長単位を指定して変換後の値を確認する一連の手順については Materials database: Part A – Importing data に記載されている。
単位について:保存されている波長値はメートル単位ですが、グラフ上ではナノメートル単位で表示されます。代表的な発光形状データとしては正規化強度(a.u.)を使用してください。スペクトルをインポートまたは生成した後には、ファイルのメタデータおよびデータ点の数を確認する必要があります。
4. 測定データが存在しない場合の発光スペクトルの推定方法
測定済みの発光スペクトルが存在しない場合、以下のPythonスクリプトを用いて一時的な正規化済みのバルク半導体発光スペクトルを生成できる。このスクリプトは直接遷移型材料向けの簡略化された熱的バンド間遷移モデルを利用している。
\(S_E(E) \propto \sqrt{E-E_g}\,\exp[-(E-E_g)/(k_B T)]\)は\(E \geq E_g\)用に使用される。
ここで\(E_g\)はバンドギャップを表し、\(T\)は温度を表します。このスクリプトは計算されたエネルギー領域における発光スペクトルを単位波長あたりの強度に変換し、そのピーク値を1に正規化した後、OghmaNanoで用いられているメタデータおよびデータ形式に従ってemission.csvを作成します。デフォルト設定では、300 Kにおいて2,036個の波長–強度データ点が生成されます。
提供されたスクリプトは、GaAs、AlGaAs、GaN、InGaN、InP、InGaAsを含む29種類の半導体材料ごとに個別のディレクトリを作成する。各材料のバンドギャップ値は参考用の初期値であり、組成比に依存する合金の場合、生成される発光スペクトルは単一の公称組成のみを表している。
いつこれらを利用すべきか?生成されたこれらのスペクトル曲線は、特に発光処理系のテスト用、あるいはより正確な測定データやバンド構造に基づく計算結果が得られるまでの間に、バルク半導体の発光特性の例示として用いるのが適切である。これらは実測された発光スペクトルではなく、特定のLEDの定量的予測値として扱うべきものではない。
このモデルでは量子井戸効果、合金の不規則性、励起子現象、欠陥に起因する発光、ひずみ、および光の取り出し効率は考慮されていない。特に間接遷移型半導体の場合には不適切であり、GaPに関しても同様である。本モデルによって生成される発光スペクトルは、従来のGaP LEDの発光特性を示すものではなく、あくまで説明的な直接遷移型の発光プロファイルに過ぎない。
5. 例:Pythonを用いた発光スペクトルの生成
下記のコードをgenerate_emission_spectra.pyとして保存し、実行してください:
python3 generate_emission_spectra.py
デフォルトの出力ディレクトリは emission_generated です。別のディレクトリを選択したり、生成するデータ点の数を変更したりするには:
python3 generate_emission_spectra.py --output my_emission_database --points 2036
動作温度を変更したり、独自のバンドギャップ値を指定したりする場合:
python3 generate_emission_spectra.py --temperature 300 --bandgap-file bandgaps.json
例としての bandgaps.json ファイルは次の通りです:
{
"GaAs": 1.424,
"AlGaAs": 1.80,
"InGaN": 2.75
}
生成された発光スペクトルを確認した上で、適切なemission.csvファイルを対応する材料フォルダー内に配置してください。このスクリプトにおいては、バンドギャップ値は手動で設定された代表的な値であり、バンドギャップの計算のためにLua製の材料スクリプトが呼び出されることはありません。
6. 完全なPythonソースコード
以下が完全な発光スペクトル生成用コードであり、Pythonの標準ライブラリのみを使用している。このソースコードを展開して、ローカルのPythonファイルにコピーしてください。
Pythonによる発光スペクトル生成プログラムの全体を表示する
#!/usr/bin/env python3
"""Generate illustrative bulk band-edge emission.csv files for OghmaNano.
No external Python dependencies. Each directory contains a 2036-point
OghmaNano-format emission.csv. Spectra are normalised per wavelength and
are NOT measured or k.p-calculated spectra. Alloy bandgaps below represent
one nominal composition only; edit MATERIALS or use --bandgap-file.
"""
import argparse
import json
import math
from pathlib import Path
# Representative, approximate 300 K bulk DIRECT-transition gaps (eV).
# NOT universal values for alloys. Entries such as AlInP and AlInAs may be
# indirect at common compositions: their curves are illustrative ONLY.
# One directory per unique material; InSb was duplicated in the supplied list.
MATERIALS = {
"InP": (1.344, "InP/InP.lua"),
"GaP": (2.78, "GaP/GaP.lua"), # direct transition; GaP fundamental gap is indirect!
"AlGaInP": (2.05, "AlGaInP.lua"),
"GaAs": (1.424, "GaAs/GaAs.lua"),
"AlInP": (2.35, "AlInP.lua"), # illustrative; verify direct-gap composition
"InSb": (0.17, "InSb/InSb.lua"),
"GaSb": (0.726, "GaSb/GaSb.lua"),
"CdS": (2.42, "CdS/CdS.lua"),
"GaN": (3.40, "GaN/GaN.lua"),
"AlN": (6.03, "AlN.lua"),
"ZnS": (3.68, "ZnS.lua"),
"InAs": (0.354, "InAs/InAs.lua"),
"AlGaAs": (1.80, "AlGaAs/AlGaAs.lua"), # nominal Al x~0.3
"InAsP": (0.95, "InAsP.lua"),
"InGaAsP": (0.80, "InGaAsP.lua"),
"AlInAs": (1.45, "AlInAs.lua"), # composition-dependent and possibly indirect
"GaAsP": (1.90, "GaAsP.lua"),
"InGaAs": (0.75, "InGaAs/InGaAs.lua"), # nominal In0.53Ga0.47As
"ZnTe": (2.26, "ZnTe.lua"),
"InGaN": (2.75, "InGaN.lua"),
"GaInNAs": (0.95, "GaInNAs.lua"),
"InGaP": (1.90, "InGaP.lua"),
"GaInAs": (0.75, "GaInAs.lua"),
"InN": (0.69, "InN.lua"),
"AlGaN": (4.43, "AlGaN.lua"),
"AlGaInAs": (0.80, "AlGaInAs.lua"),
"CdSe": (1.74, "CdSe.lua"),
"ZnSe": (2.70, "ZnSe.lua"),
"CdTe": (1.50, "CdTe/CdTe.lua"),
}
HC_EV_NM = 1239.8419843320026
KB_EV_K = 8.617333262145e-5
def make_spectrum(eg, temp, points):
"""3D nondegenerate direct-gap model, intensity per unit wavelength.
S_E(E) ~ sqrt(E-Eg)*exp(-(E-Eg)/kT), E>=Eg.
Convert using dE/dlambda ~ 1/lambda**2, then peak-normalise.
Span covers Eg to Eg + max(0.35 eV, 12 kT); wavelength increases.
"""
kt = KB_EV_K * temp
upper = eg + max(0.35, 12.0 * kt)
lo_nm = HC_EV_NM / upper
hi_nm = HC_EV_NM / eg
values = []
for i in range(points):
wavelength_nm = lo_nm + (hi_nm - lo_nm) * i / (points - 1)
e = HC_EV_NM / wavelength_nm
de = max(0.0, e - eg)
intensity = math.sqrt(de) * math.exp(-de / kt) / (wavelength_nm * wavelength_nm)
values.append((wavelength_nm * 1e-9, intensity))
top = max(v for _, v in values)
return [(lam, value / top) for lam, value in values]
def generate(out, material, eg, lua_path, temp, points):
folder = out / material
folder.mkdir(parents=True, exist_ok=True)
metadata = {
"title": "Illustrative band-edge emission of " + material,
"type": "xy",
"y_label": "Wavelength",
"data_label": "Intensity",
"y_units": "nm",
"y_mul": 1e9,
"data_units": "au",
"icon": "spectra_file",
"time ": 0.0,
"Vexternal": 0.0,
"x_len": 1,
"y_len": points,
"z_len": 1,
"cols": "yd",
}
values = make_spectrum(eg, temp, points)
path = folder / "emission.csv"
with path.open("w", encoding="utf-8", newline="\n") as f:
f.write("#oghma_csv " + json.dumps(metadata, ensure_ascii=False, separators=(",", ":")) + "*\n")
for lam, value in values:
f.write(f"{lam:.12e}\t{value:.12e}\n")
return path
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=Path("emission_generated"))
parser.add_argument("--points", type=int, default=2036)
parser.add_argument("--temperature", type=float, default=300.0)
parser.add_argument("--bandgap-file", type=Path,
help='Optional JSON map e.g. {"GaAs":1.424,"AlGaAs":1.80} to override nominal gaps')
args = parser.parse_args()
if args.points < 3 or args.temperature <= 0:
parser.error("--points must be >=3 and --temperature must be positive")
gap_overrides = {}
if args.bandgap_file:
gap_overrides = json.loads(args.bandgap_file.read_text(encoding="utf-8"))
unknown = set(gap_overrides) - set(MATERIALS)
if unknown:
parser.error("Unknown material(s): " + ", ".join(sorted(unknown)))
args.output.mkdir(parents=True, exist_ok=True)
for material, (nominal_eg, lua_path) in MATERIALS.items():
eg = float(gap_overrides.get(material, nominal_eg))
if not math.isfinite(eg) or eg <= 0:
parser.error(f"Invalid bandgap for {material}: {eg}")
path = generate(args.output, material, eg, lua_path, args.temperature, args.points)
print(f"{path} Eg={eg:g} eV (source: {lua_path})")
print(f"Generated {len(MATERIALS)} unique material directories with {args.points} points each.")
print("CAUTION: nominal/illustrative bulk direct-transition profiles; validate alloy composition and gap type.")
if __name__ == "__main__":
main()
注意:生成された発光スペクトルは一時的な代替手段に過ぎません。正確なデバイスシミュレーションを行うためには、実測された発光データ、あるいはk·p計算のような電子バンド構造および光遷移モデルを用いて算出されたスペクトルを利用することが推奨されます。