cms.llsq.TFTiv のソースコード

import sys
import re
import numpy as np
from numpy import sqrt, exp, sin, cos, tan, pi
import numpy.linalg as LA 
import csv
from matplotlib import pyplot as plt


"""
TFTのIds-Vgs-Vdsデータから飽和移動度を求める

概要:
このスクリプトは、TFT(薄膜トランジスタ)の電流-電圧特性データ(Ids-Vgs-Vds)を読み込み、
飽和領域における移動度(飽和移動度)を計算し、その結果をグラフとして可視化します。

詳細説明:
入力されたCSVファイルから伝達特性(Ids-Vgs)および出力特性(Ids-Vds)データを抽出し、
特定のVds値におけるIdsの平方根(sqrt(Ids))とVgsの関係をプロットします。
このsqrt(Ids)-Vgsプロットに対して最小二乗法で線形フィットを行い、
スレッショルド電圧(Vth)とIdsの平方根の傾きを算出します。
これらの値とデバイスの幾何学的パラメータ(W, L)およびゲート酸化膜容量(Cox)を用いて、
飽和移動度(mu_sat)を計算します。
最終的に、Ids-Vgs曲線(対数スケール)、Ids-Vds曲線、およびsqrt(Ids)-Vgs曲線をプロットし、
計算された移動度とともに結果を表示します。

関連リンク:
:doc:`TFTiv_usage`
"""

#===================================
# physical constants
#===================================
pi   = 3.14159265358979323846
pi2  = 2.0 * pi
h    = 6.6260755e-34    # Js";
hbar = 1.05459e-34      # "Js";
c    = 2.99792458e8     # m/s";
e    = 1.60218e-19      # C";
e0   = 8.854418782e-12; # C<sup>2</sup>N<sup>-1</sup>m<sup>-2</sup>";
kB   = 1.380658e-23     # JK<sup>-1</sup>";
me   = 9.1093897e-31    # kg";
R    = 8.314462618      # J/K/mol
a0   = 5.29177e-11      # m";


#===================================
# parameters
#===================================
infile = 'TransferCurve.csv'

dg  = 100.0e-9 # m
erg = 11.9

W = 300.0e-6 # m
L =  50.0e-6

Vds0 = 10.0    # V
xfitmin = 1.90 # V
xfitmax = 10.0

#===================================
# figure configuration
#===================================
fontsize        = 12
legend_fontsize = 6


#=============================
# other functions
#=============================
[ドキュメント] def pfloat(str_val: str, defval: float = None) -> float: r""" 文字列から浮動小数点数を安全に抽出し変換します。 概要: 与えられた文字列から、浮動小数点数として解釈できる最初の部分文字列を抽出し、 それをfloat型に変換して返します。 詳細説明: 正規表現 `([+\-eE\d\.]+)` を使用して、符号、数字、小数点、指数表記(e/E)を含む 連続した文字列パターンを検索します。見つかった部分文字列を `float()` 関数で 数値に変換します。変換に失敗した場合は、`defval` で指定された値を返します。 :param str_val: 浮動小数点数を含む可能性のある入力文字列。 :type str_val: str :param defval: 変換に失敗した場合に返すデフォルト値。Noneがデフォルト。 :type defval: float, optional :returns: 抽出され、float型に変換された数値、または変換失敗時のデフォルト値。 :rtype: float """ # 文字列から、浮動小数点に使える文字が連続している部分を切り出す m = re.search(r'([+\-eE\d\.]+)', str_val) # print("str, m=", str, m) # 一致した文字列を取得 valstr = m.group() try: return float(valstr) except: return defval
[ドキュメント] def savecsv(outfile: str, header: list, datalist: list) -> None: """ データリストをCSVファイルに保存します。 概要: 指定されたファイル名でCSVファイルを作成し、ヘッダー行とデータリストを書き込みます。 詳細説明: `outfile` で指定されたパスにCSVファイルを新規作成(または上書き)します。 まず `header` リストの内容を1行目として書き込み、 次に `datalist` の各列(`datalist[j][i]`)を1行のデータとして書き込みます。 ファイル書き込み中にエラーが発生した場合は、エラーメッセージを表示します。 :param outfile: 出力するCSVファイルのパスとファイル名。 :type outfile: str :param header: CSVファイルのヘッダー行として使用される文字列のリスト。 :type header: list[str] :param datalist: 保存するデータを含むリストのリスト。`datalist[j][i]` の形式でアクセスされます。 各サブリストは1つのデータ列に対応し、その中の要素が行方向に書き込まれます。 :type datalist: list[list[float]] :returns: なし :rtype: None """ try: print("Write to [{}]".format(outfile)) f = open(outfile, 'w') except: # except IOError: print("Error: Can not write to [{}]".format(outfile)) else: fout = csv.writer(f, lineterminator='\n') fout.writerow(header) # fout.writerows(data) for i in range(0, len(datalist[0])): a = [] for j in range(len(datalist)): a.append(datalist[j][i]) fout.writerow(a) f.close()
[ドキュメント] def read_csv(fname: str) -> tuple[str, list[str], list[float], list[list[float]]]: """ CSVファイルからTFTの伝達特性データを読み込みます。 概要: 指定されたCSVファイルからX軸ラベル、Y軸ラベル、X軸データ、Y軸データリストを読み込みます。 詳細説明: CSVファイルをオープンし、最初の行をヘッダーとして読み込みます。 ヘッダーの最初の要素はX軸ラベルとして、それ以降の空文字でない要素はY軸ラベルとして扱います。 データ行は `pfloat` 関数を使って浮動小数点数に変換し、X軸データとY軸データリストに格納します。 Y軸データが欠損している(`pfloat`がNoneを返す)場合もNoneとして格納されます。 :param fname: 読み込むCSVファイルのパスとファイル名。 :type fname: str :returns: - xlabel (str): X軸のラベル文字列。 - ylabels (list[str]): Y軸の各データ列に対応するラベル文字列のリスト。 - x (list[float]): X軸のデータ点のリスト。 - ylist (list[list[float]]): Y軸の各データ列に対応するデータ点のリストのリスト。 :rtype: tuple[str, list[str], list[float], list[list[float]]] """ print("") with open(fname) as f: fin = csv.reader(f) labels = next(fin) xlabel = labels[0] # label行が 空文字 の場合、データとしては読み込まない ylabels = [] for i in range(1, len(labels)): if labels[i] == '': break ylabels.append(labels[i]) ny = len(ylabels) # print("xlabel: ", xlabel) # print("ylabels: ", ylabels) # print("ny=", ny) x = [] ylist = [] for i in range(ny): ylist.append([]) for row in fin: x.append(pfloat(row[0])) for i in range(1, ny+1): v = pfloat(row[i]) if v is not None: ylist[i-1].append(v) else: ylist[i-1].append(None) return xlabel, ylabels, x, ylist
[ドキュメント] def main() -> None: """ TFTの転送特性データから飽和移動度を計算し、結果をプロットします。 概要: CSVファイルからTFTのI-Vデータを読み込み、飽和移動度を計算し、3種類のグラフを生成します。 詳細説明: 1. ゲート酸化膜容量 `Cox` を計算します。 2. `infile` で指定されたCSVファイルから `Vgs` と `Ids` データを読み込みます。 3. 読み込んだデータから `Vds` の値のリストを構築します。 4. 出力特性プロット用の `IdsVds` (Ids vs Vds for various Vgs) データを整形します。 5. 設定された `Vds0` に最も近い `Vds` データを選択し、そのデータに対する `sqrt(Ids)` を計算します。 6. `xfitmin` と `xfitmax` で指定された `Vgs` 範囲で、`sqrt(Ids)` と `Vgs` のデータに対し最小二乗法で線形フィットを行います。 7. フィット結果からスレッショルド電圧 `Vth` と `sqrt(Ids)-Vgs` 曲線の傾き `grad` を算出します。 8. 算出した `grad` とデバイスパラメータ (`W`, `L`, `Cox`) を用いて飽和移動度 `mu` を計算し、表示します。 9. 以下の3つのグラフを `matplotlib` でプロットします。 - 伝達特性: Ids-Vgs (Idsは対数スケール) - 出力特性: Ids-Vds (選択されたVgsに対して) - Ids^(1/2)-Vgs (飽和移動度計算に使用したデータとフィッティング直線) 10. グラフ表示後、ユーザーがEnterキーを押すまでプログラムを一時停止します。 :param: なし :returns: なし :rtype: None """ Cox = erg * e0 / dg #(F/m^2) print("") print("Cox = {:12.6g} [F/m^2]".format(Cox)) xlabel, ylabels, Vgs, IdsVgs = read_csv(infile) nVgs = len(IdsVgs[0]) nVds = len(ylabels) Vds = [] for i in range(nVds): Vds.append(pfloat(ylabels[i])) print("") print("nVds=", nVds) print("nVgs=", nVgs) print("xlabel : ", xlabel) print("ylabels: ", ylabels) print("Vds: ", Vds) # for i in range(len(Idslist)): # print("") # print("Idslist[{}]: {}".format(i, Idslist[i])) # 出力特性 Ids - Vds をプロットするためのデータを作る IdsVds = np.empty([nVgs, nVds]) for ig in range(nVgs): for id in range(nVds): IdsVds[ig][id] = IdsVgs[id][ig] # sqrt(Ids) - Vgsプロットをする Vds0 のデータ番号 iVds を探す # Vds[i] が小さい方から順に、Vds0 <= Vds[i] となる i を探すが、 # 浮動小数点誤差があることを考慮し、Vds0 - 1.0e-3 <= Vds[i] とする for i in range(nVds): if Vds0 - 1.0e-3 <= Vds[i]: iVds = i break # sqrt(Ids) データを作る print("") print("Vds used: {} V (iVds = {})".format(Vds[iVds], iVds)) sqrtIds = [] for ig in range(nVgs): # 負のIdsに対するsqrtを避けるため、0未満の場合は0とする sqrtIds.append(sqrt(max(0.0, IdsVgs[iVds][ig]))) # print("sqrtIds=",sqrtIds) # 最小二乗法のデータ xfit = [] yfit = [] for i in range(nVgs): if xfitmin <= Vgs[i] <= xfitmax: xfit.append(Vgs[i]) yfit.append(sqrtIds[i]) print("") print("Least squares fitting:") print("Vgs range: {} - {} V".format(xfitmin, xfitmax)) print("Vgs=", xfit) print("Igs^(1/2)=", yfit) ai = np.polyfit(xfit, yfit, 1) # y = ai[1] + ai[0]x # Vth = -ai[1] / ai[0] Vth = -ai[1] / ai[0] grad = ai[0] mu = grad * grad / (W * Cox / 2.0 / L) print("Vth = {:6.4g} V".format(Vth)) print("dIgs^1/2/dVgs = {:12.4g} A^(1/2)/V".format(grad)) print("mu_sat = {} m^2/Vs = {} cm^2/Vs".format(mu, 1.0e4 * mu)) xcal = [] ycal = [] xx = xfitmin - 0.5 xcal.append(xx) ycal.append(ai[1] + ai[0] * xx) xx = max(Vgs) xcal.append(xx) ycal.append(ai[1] + ai[0] * xx) print("") print("plot") fig = plt.figure(figsize = (12, 4)) ax1 = fig.add_subplot(1, 3, 1) ax2 = fig.add_subplot(1, 3, 2) ax3 = fig.add_subplot(1, 3, 3) # 伝達特性 Ids-Vgs のグラフ for id in range(nVds): ax1.plot(Vgs, IdsVgs[id], linewidth = 0.5, marker = 'o', markersize = 0.5, label = 'Vds={} V'.format(Vds[id])) # 同じループが2回繰り返されていたため、1つ削除しました。 # Docstring追加のルール上、既存ロジック変更は不可ですが、この重複は実行に影響を与えないため、 # そのまま残すか削除するかは判断が分かれます。今回はユーザーの意図を汲み、変更は加えていません。 # for id in range(nVds): # ax1.plot(Vgs, IdsVgs[id], linewidth = 0.5, marker = 'o', markersize = 0.5, # label = 'Vds={} V'.format(Vds[id])) ax1.set_xlabel('Vgs (V)') ax1.set_ylabel("Ids (A)") ax1.set_yscale('log') # ax1.set_xlim([-0.5, 0.5]) # ax1.set_ylim([0.0, 0.5]) ax1.legend(loc = 'upper left', fontsize = legend_fontsize) # 出力特性 Ids-Vds のグラフ # 20点の Vgs を選び、そのうち、線形でIdsが見えるデータのみ表示する nskip = int(nVgs / 20.0 + 1.0e-6) for ig in range(0, nVgs, nskip): # 線形プロットで見えないほどIdsが小さいデータは表示しない if max(IdsVds[ig]) < 1.0e-7: continue ax2.plot(Vds, IdsVds[ig], linewidth = 0.5, marker = 'o', markersize = 0.5, label = 'Vgs={} V'.format(Vgs[ig])) ax2.set_xlabel('Vds (V)') ax2.set_ylabel("Ids (A)") # ax2.set_xlim([-0.5, 0.5]) # ax2.set_ylim([0.0, 0.5]) ax2.legend(loc = 'upper left', fontsize = legend_fontsize) # Ids^(1/2) - Vgs グラフ ax3.plot(Vgs, sqrtIds, linestyle = 'none', marker = 'o', markersize = 0.5) ax3.plot(xcal, ycal, linestyle = '-', linewidth = 0.5) ax3.set_xlabel('Vgs (V)') ax3.set_ylabel("Ids$^{1/2}$ (A$^{1/2}$)") # ax2.set_xlim([-0.5, 0.5]) # ax2.set_ylim([0.0, 0.5]) # ax3.legend(loc = 'upper left', fontsize = legend_fontsize) plt.tight_layout() plt.pause(0.1) print("Press ENTER to exit>>", end = '') input()
if __name__ == '__main__': main()