analyze_TFT_refactored.py ダウンロード/コピー
analyze_TFT_refactored.py をダウンロード
analyze_TFT_refactored.py
analyze_TFT_refactored.py
1#!/usr/bin/env python3
2"""Id-Vg ExcelファイルからTFTの伝達特性を分析します。
3
4概要:
5 このスクリプトは、線形領域と飽和領域の2つのId-Vg Excelファイルを読み込み、
6 Vth、移動度、SS、Ionなどの代表的なTFTパラメータを計算します。
7詳細説明:
8 デフォルトの入出力ファイル名は元のスクリプトとの互換性を保っています。
9 入力ファイルは IdVg-Vd0.1.xlsx (線形領域) と IdVg-Vd10.xlsx (飽和領域) です。
10 デフォルトの出力ファイルは以下の通りです。
11
12 IdVg-Vd10_analyze.csv
13 IdVg-Vd0.1_analyze.csv
14 output.csv
15 output_log.csv
16 rootIdVg.png
17 IdVg_LIN.png
18"""
19
20from __future__ import annotations
21
22import argparse
23import logging
24import os
25import sys
26from dataclasses import dataclass
27from typing import Optional
28
29import numpy as np
30import pandas as pd
31from matplotlib import pyplot as plt
32from sklearn.linear_model import LinearRegression
33
34
35# -----------------------------------------------------------------------------
36# Default analysis parameters kept from the original script
37# -----------------------------------------------------------------------------
38DEFAULT_COX = 2.24e-8
39DEFAULT_L = 50.0e-6
40DEFAULT_W = 300.0e-6
41DEFAULT_TARGET_CURRENT = 1.0e-10
42DEFAULT_IOFF_THRESHOLD = 1.0e-14
43DEFAULT_ROLLING_WINDOW = 7
44
45
46@dataclass(frozen=True)
47class AnalysisConfig:
48 """TFTパラメータ抽出に使用される設定値を格納します。
49
50 属性:
51 :param cox: 単位面積あたりのゲート容量。L、W、Id、Vdと単位を合わせる必要があります。元のスクリプトでは 2.24e-8でした。
52 :type cox: float
53 :param channel_length: チャネル長 L。
54 :type channel_length: float
55 :param channel_width: チャネル幅 W。
56 :type channel_width: float
57 :param target_current: SSおよびVonの評価に使用される電流しきい値。
58 :type target_current: float
59 :param ioff_threshold: Ioffの平均化に使用される上限電流しきい値。
60 :type ioff_threshold: float
61 :param rolling_window: 局所勾配の移動平均のウィンドウサイズ。
62 :type rolling_window: int
63 """
64
65 cox: float = DEFAULT_COX
66 channel_length: float = DEFAULT_L
67 channel_width: float = DEFAULT_W
68 target_current: float = DEFAULT_TARGET_CURRENT
69 ioff_threshold: float = DEFAULT_IOFF_THRESHOLD
70 rolling_window: int = DEFAULT_ROLLING_WINDOW
71
72
73@dataclass
74class IdVgResult:
75 """Id-Vg曲線から計算された要約値を格納します。
76
77 属性:
78 :param dataframe: 処理されたデータを含むpandas DataFrame。
79 :type dataframe: pandas.DataFrame
80 :param vth: しきい値電圧。
81 :type vth: float
82 :param max_slope: 最大勾配。
83 :type max_slope: float
84 :param vg_at_max_slope: 最大勾配を示すVg値。
85 :type vg_at_max_slope: float
86 :param mobility: 移動度。
87 :type mobility: float
88 :param ss: サブスレッショルドスイング。
89 :type ss: float
90 :param ion: オン電流。
91 :type ion: float
92 :param ioff: オフ電流。推定できない場合はNone。
93 :type ioff: Optional[float]
94 :param ion_ioff_ratio: オン電流とオフ電流の比。IoffがNoneの場合はNone。
95 :type ion_ioff_ratio: Optional[float]
96 :param von: オン電圧。
97 :type von: float
98 """
99
100 dataframe: pd.DataFrame
101 vth: float
102 max_slope: float
103 vg_at_max_slope: float
104 mobility: float
105 ss: float
106 ion: float
107 ioff: Optional[float]
108 ion_ioff_ratio: Optional[float]
109 von: float
110
111
112@dataclass
113class IdVdResult:
114 """Id-Vd曲線から計算された要約値を格納します。
115
116 属性:
117 :param dataframe: 処理されたデータを含むpandas DataFrame。
118 :type dataframe: pandas.DataFrame
119 :param mueff: 実効移動度。
120 :type mueff: float
121 """
122
123 dataframe: pd.DataFrame
124 mueff: float
125
126
127# -----------------------------------------------------------------------------
128# Input and validation
129# -----------------------------------------------------------------------------
130def read_excel_file(path: str, label: str) -> pd.DataFrame:
131 """概要:
132 Excelファイルを読み込み、失敗時に明確なエラーメッセージを追加します。
133 引数:
134 :param path: Excelファイルへのパス。
135 :type path: str
136 :param label: エラーメッセージで使用される人間が読める名前。
137 :type label: str
138 戻り値:
139 :returns: Excelファイルから読み込まれたDataFrame。
140 :rtype: pandas.DataFrame
141 例外:
142 :raises FileNotFoundError: ファイルが存在しない場合。
143 :raises ValueError: pandasがファイルを読み込めない場合。
144 """
145
146 if not os.path.exists(path):
147 raise FileNotFoundError(f"{label} file not found: {path}")
148
149 try:
150 return pd.read_excel(path)
151 except Exception as exc: # pandas may raise different exceptions by engine/version
152 raise ValueError(f"Failed to read {label} Excel file: {path}") from exc
153
154
155def validate_required_columns(df: pd.DataFrame, required_columns: list[str], label: str) -> None:
156 """概要:
157 DataFrameが必要な列を含んでいることを検証します。
158 引数:
159 :param df: 検証するDataFrame。
160 :type df: pandas.DataFrame
161 :param required_columns: 分析に必要な列名。
162 :type required_columns: list[str]
163 :param label: エラーメッセージで使用される人間が読める名前。
164 :type label: str
165 例外:
166 :raises ValueError: 1つ以上の必須列が欠落している場合。
167 """
168
169 missing = [col for col in required_columns if col not in df.columns]
170 if missing:
171 raise ValueError(
172 f"{label} data is missing required columns: {missing}. "
173 f"Available columns: {list(df.columns)}"
174 )
175
176
177# -----------------------------------------------------------------------------
178# Numerical helper functions
179# -----------------------------------------------------------------------------
180def calculate_local_slope_by_linear_fit(
181 df: pd.DataFrame,
182 x_series: pd.Series,
183 y_series: pd.Series,
184 half_window: int = 1,
185) -> list[float]:
186 """概要:
187 各データポイント周辺で y = a*x + b をフィッティングして局所勾配を計算します。
188 詳細説明:
189 この関数は、元のスクリプトの slope3data および slope7data スタイルの関数を置き換えます。
190 half_window=1 の場合、約3点を使用して、元の slope3data の動作に可能な限り一致させます。
191 引数:
192 :param df: 元となるDataFrame。その長さのみが使用されます。
193 :type df: pandas.DataFrame
194 :param x_series: X軸の値。
195 :type x_series: pandas.Series
196 :param y_series: Y軸の値。
197 :type y_series: pandas.Series
198 :param half_window: 各側の隣接ポイント数。
199 :type half_window: int
200 戻り値:
201 :returns: 局所勾配のリスト。フィットできないポイントはNaNとして返されます。
202 :rtype: list[float]
203 """
204
205 slopes: list[float] = []
206
207 for index in range(len(df)):
208 x_window = x_series[index - half_window : index + half_window + 1]
209 y_window = y_series[index - half_window : index + half_window + 1]
210
211 valid = pd.concat([x_window, y_window], axis=1).dropna()
212 if len(valid) < 2:
213 slopes.append(np.nan)
214 continue
215
216 try:
217 x = valid.iloc[:, 0].to_numpy().reshape(-1, 1)
218 y = valid.iloc[:, 1].to_numpy()
219 model = LinearRegression().fit(x, y)
220 slopes.append(float(model.coef_[0]))
221 except ValueError as exc:
222 logging.debug("Local slope fitting failed at index %s: %s", index, exc)
223 slopes.append(np.nan)
224
225 return slopes
226
227
228def intercept_x(x: float, y: float, slope: float) -> float:
229 """概要:
230 直線 y_line = slope * (x_line - x0) のX切片を計算します。
231 引数:
232 :param x: 直線上の点のX座標。
233 :type x: float
234 :param y: 直線上の点のY座標。
235 :type y: float
236 :param slope: 直線の勾配。
237 :type slope: float
238 戻り値:
239 :returns: X切片の値。
240 :rtype: float
241 """
242
243 if pd.isna(slope) or slope == 0:
244 return np.nan
245 return float(x - y / slope)
246
247
248def calculate_vth_by_max_slope(
249 vg_series: pd.Series,
250 y_series: pd.Series,
251 slope_series: pd.Series,
252) -> tuple[float, float, float]:
253 """概要:
254 最大勾配接線法を使用してしきい値電圧を計算します。
255 引数:
256 :param vg_series: ゲート電圧の値。
257 :type vg_series: pandas.Series
258 :param y_series: 分析モードに応じてIdまたはsqrt(Id)。
259 :type y_series: pandas.Series
260 :param slope_series: 局所勾配の値。
261 :type slope_series: pandas.Series
262 戻り値:
263 :returns: (Vth, max_slope, Vg_at_max_slope) のタプル。
264 :rtype: tuple
265 例外:
266 :raises ValueError: 有効な勾配が見つからない場合。
267 """
268
269 valid_slope = slope_series.replace([np.inf, -np.inf], np.nan).dropna()
270 if valid_slope.empty:
271 raise ValueError("No valid slope values are available for Vth calculation.")
272
273 idx = valid_slope.idxmax()
274 max_slope = float(valid_slope.loc[idx])
275 x = float(vg_series.loc[idx])
276 y = float(y_series.loc[idx])
277 vth = intercept_x(x, y, max_slope)
278 return vth, max_slope, x
279
280
281def calculate_ion_ioff(id_series: pd.Series, ioff_threshold: float) -> tuple[float, Optional[float], Optional[float]]:
282 """概要:
283 Ion、Ioff、およびIon/Ioff比を計算します。
284 引数:
285 :param id_series: ドレイン電流の値。正の値が期待されます。
286 :type id_series: pandas.Series
287 :param ioff_threshold: 0 < Id < ioff_threshold を満たすデータポイントを平均してIoffを推定します。
288 :type ioff_threshold: float
289 戻り値:
290 :returns: (Ion, Ioff, Ion/Ioff) のタプル。Ioffが推定できない場合、IoffとIon/IoffはNoneとして返されます。
291 :rtype: tuple
292 """
293
294 valid_id = pd.to_numeric(id_series, errors="coerce").replace([np.inf, -np.inf], np.nan)
295 ion = float(valid_id.max()) if not valid_id.dropna().empty else np.nan
296
297 ioff_data = valid_id[(valid_id > 0) & (valid_id < ioff_threshold)].dropna()
298 if ioff_data.empty:
299 return ion, None, None
300
301 ioff = float(ioff_data.mean())
302 ratio = float(ion / ioff) if ioff != 0 else None
303 return ion, ioff, ratio
304
305
306def first_index_at_or_above(series: pd.Series, threshold: float) -> Optional[int]:
307 """概要:
308 シリーズ値がしきい値以上である最初のインデックスを返します。
309 引数:
310 :param series: 数値シリーズ。
311 :type series: pandas.Series
312 :param threshold: しきい値。
313 :type threshold: float
314 戻り値:
315 :returns: 最初の一致するインデックス。条件を満たすポイントがない場合はNone。
316 :rtype: Optional[int]
317 """
318
319 matched = series.index[series >= threshold]
320 if len(matched) == 0:
321 return None
322 return matched.min()
323
324
325def safe_inverse(value: float) -> float:
326 """概要:
327 1/value を返します。逆数が明確に定義されていない場合はNaNを返します。
328 引数:
329 :param value: 逆数を計算する数値。
330 :type value: float
331 戻り値:
332 :returns: valueの逆数、またはNaN。
333 :rtype: float
334 """
335
336 if pd.isna(value) or value == 0:
337 return np.nan
338 return float(1.0 / value)
339
340
341# -----------------------------------------------------------------------------
342# TFT analysis functions
343# -----------------------------------------------------------------------------
344def analyze_idvg_sat(df: pd.DataFrame, config: AnalysisConfig) -> IdVgResult:
345 """概要:
346 飽和領域Id-Vgデータを分析します。
347 詳細説明:
348 計算は元のスクリプトに従います。
349 - 負/非正のIdをログおよび平方根演算のためにNaNに変換します。
350 - log10(Id) および Id ** 0.5 (sqrt(Id)) を計算します。
351 - 3点線形フィッティングにより局所勾配を計算します。
352 - Id ** 0.5 (sqrt(Id)) 勾配を中心移動平均で平滑化します。
353 - 平滑化された Id ** 0.5 (sqrt(Id)) 勾配から飽和移動度を計算します。
354 - 最大勾配接線法によりVthを計算します。
355 - Id >= target_current となる最初のポイントでSSを計算します。
356 引数:
357 :param df: 入力DataFrame。必須列: Vg および Id。
358 :type df: pandas.DataFrame
359 :param config: 分析設定。
360 :type config: AnalysisConfig
361 戻り値:
362 :returns: 処理されたDataFrameと要約値を含むIdVgResult。
363 :rtype: IdVgResult
364 """
365
366 validate_required_columns(df, ["Vg", "Id"], "SAT Id-Vg")
367 df = df.copy()
368
369 df["Id"] = pd.to_numeric(df["Id"], errors="coerce")
370 df["Id_rm"] = np.where(df["Id"] > 0, df["Id"], np.nan)
371 df["logId"] = np.log10(df["Id_rm"])
372 df["rootId"] = df["Id_rm"] ** 0.5
373
374 df["slope logId"] = calculate_local_slope_by_linear_fit(df, df["Vg"], df["logId"], half_window=1)
375 df["slope rootId"] = calculate_local_slope_by_linear_fit(df, df["Vg"], df["rootId"], half_window=1)
376 df["slope logId"] = df["slope logId"].replace([np.inf, -np.inf], np.nan)
377 df["slope rootId"] = df["slope rootId"].replace([np.inf, -np.inf], np.nan)
378 df["slope rootId smooth"] = df["slope rootId"].rolling(config.rolling_window, center=True).mean()
379
380 df["muSAT"] = (
381 2.0
382 * config.channel_length
383 / config.channel_width
384 / config.cox
385 * (df["slope rootId smooth"] ** 2)
386 )
387 df["muSAT"] = df["muSAT"].replace([np.inf, -np.inf], np.nan)
388
389 vth, max_slope, vg_at_max_slope = calculate_vth_by_max_slope(
390 df["Vg"], df["rootId"], df["slope rootId smooth"]
391 )
392 mobility = float(df["muSAT"].max()) if not df["muSAT"].dropna().empty else np.nan
393
394 idx = first_index_at_or_above(df["Id_rm"], config.target_current)
395 if idx is None:
396 logging.warning("SAT: Id never reaches target current %.3e A. SS and Von are set to NaN.", config.target_current)
397 ss = np.nan
398 von = np.nan
399 else:
400 ss = safe_inverse(df.loc[idx, "slope logId"])
401 von = float(df.loc[idx, "Vg"])
402
403 ion, ioff, ratio = calculate_ion_ioff(df["Id_rm"], config.ioff_threshold)
404
405 return IdVgResult(
406 dataframe=df,
407 vth=vth,
408 max_slope=max_slope,
409 vg_at_max_slope=vg_at_max_slope,
410 mobility=mobility,
411 ss=ss,
412 ion=ion,
413 ioff=ioff,
414 ion_ioff_ratio=ratio,
415 von=von,
416 )
417
418
419def analyze_idvg_lin(df: pd.DataFrame, config: AnalysisConfig) -> IdVgResult:
420 """概要:
421 線形領域Id-Vgデータを分析します。
422 詳細説明:
423 計算は元のスクリプトに従います。
424 - 負/非正のIdをNaNに変換します。
425 - Id および log10(Id) の局所勾配を計算します。
426 - Id 勾配を中心移動平均で平滑化します。
427 - 平滑化された Id 勾配から電界効果移動度を計算します。
428 - 最大勾配接線法によりVthを計算します。
429 - Id >= target_current となる最初のポイントでSSを計算します。
430 引数:
431 :param df: 入力DataFrame。必須列: Vg、Vd、および Id。
432 :type df: pandas.DataFrame
433 :param config: 分析設定。
434 :type config: AnalysisConfig
435 戻り値:
436 :returns: 処理されたDataFrameと要約値を含むIdVgResult。
437 :rtype: IdVgResult
438 """
439
440 validate_required_columns(df, ["Vg", "Vd", "Id"], "LIN Id-Vg")
441 df = df.copy()
442
443 df["Id"] = pd.to_numeric(df["Id"], errors="coerce")
444 df["Id_rm"] = np.where(df["Id"] > 0, df["Id"], np.nan)
445 df["logId"] = np.log10(df["Id_rm"])
446
447 df["slope Id"] = calculate_local_slope_by_linear_fit(df, df["Vg"], df["Id_rm"], half_window=1)
448 df["slope logId"] = calculate_local_slope_by_linear_fit(df, df["Vg"], df["logId"], half_window=1)
449 df["slope Id"] = df["slope Id"].replace([np.inf, -np.inf], np.nan)
450 df["slope Id smooth"] = df["slope Id"].rolling(config.rolling_window, center=True).mean()
451 df["slope logId"] = df["slope logId"].replace([np.inf, -np.inf], np.nan)
452
453 df["muFE"] = (
454 config.channel_length
455 / config.channel_width
456 / config.cox
457 / df["Vd"]
458 * df["slope Id smooth"]
459 )
460 df["muFE"] = df["muFE"].replace([np.inf, -np.inf], np.nan)
461
462 vth, max_slope, vg_at_max_slope = calculate_vth_by_max_slope(
463 df["Vg"], df["Id_rm"], df["slope Id smooth"]
464 )
465 mobility = float(df["muFE"].max()) if not df["muFE"].dropna().empty else np.nan
466
467 idx = first_index_at_or_above(df["Id_rm"], config.target_current)
468 if idx is None:
469 logging.warning("LIN: Id never reaches target current %.3e A. SS and Von are set to NaN.", config.target_current)
470 ss = np.nan
471 von = np.nan
472 else:
473 ss = safe_inverse(df.loc[idx, "slope logId"])
474 von = float(df.loc[idx, "Vg"])
475
476 ion, ioff, ratio = calculate_ion_ioff(df["Id_rm"], config.ioff_threshold)
477
478 return IdVgResult(
479 dataframe=df,
480 vth=vth,
481 max_slope=max_slope,
482 vg_at_max_slope=vg_at_max_slope,
483 mobility=mobility,
484 ss=ss,
485 ion=ion,
486 ioff=ioff,
487 ion_ioff_ratio=ratio,
488 von=von,
489 )
490
491
492def analyze_idvd_lin(df: pd.DataFrame, vth: float, config: AnalysisConfig) -> IdVdResult:
493 """概要:
494 線形領域Id-Vdデータを分析します。
495 詳細説明:
496 この関数は、元のスクリプトに analyze_IdVd_LIN が含まれていたため保持されていますが、
497 メインプロセスからは呼び出されません。
498 引数:
499 :param df: 入力DataFrame。必須列: Vd、Vg、および Id。
500 :type df: pandas.DataFrame
501 :param vth: mueff計算に使用されるしきい値電圧。
502 :type vth: float
503 :param config: 分析設定。
504 :type config: AnalysisConfig
505 戻り値:
506 :returns: 処理されたDataFrameと最大mueffを含むIdVdResult。
507 :rtype: IdVdResult
508 """
509
510 validate_required_columns(df, ["Vd", "Vg", "Id"], "LIN Id-Vd")
511 df = df.copy()
512
513 df["Id"] = pd.to_numeric(df["Id"], errors="coerce")
514 df["Id_rm"] = np.where(df["Id"] > 0, df["Id"], np.nan)
515 df = df.sort_values(by="Vd")
516 df["slope Id"] = calculate_local_slope_by_linear_fit(df, df["Vd"], df["Id_rm"], half_window=1)
517 df["mueff"] = (
518 config.channel_length
519 / config.channel_width
520 / config.cox
521 / (df["Vg"] - vth)
522 * df["slope Id"]
523 )
524 df["mueff"] = df["mueff"].replace([np.inf, -np.inf], np.nan)
525 mueff = float(df["mueff"].max()) if not df["mueff"].dropna().empty else np.nan
526
527 return IdVdResult(dataframe=df, mueff=mueff)
528
529
530# -----------------------------------------------------------------------------
531# Plot functions
532# -----------------------------------------------------------------------------
533def plot_idvg_lin(df: pd.DataFrame, vth: float, max_slope: float, output_path: str) -> None:
534 """概要:
535 線形領域Id-Vgプロットを最大勾配接線とともに保存します。
536 引数:
537 :param df: プロットするデータを含むDataFrame。
538 :type df: pandas.DataFrame
539 :param vth: しきい値電圧。
540 :type vth: float
541 :param max_slope: 最大勾配。
542 :type max_slope: float
543 :param output_path: 出力画像の保存パス。
544 :type output_path: str
545 戻り値:
546 :returns: なし
547 :rtype: None
548 """
549
550 plt.figure()
551 plt.scatter(df["Vg"], df["Id"], label="Vd=0.1V", s=4)
552 df_line = max_slope * (df["Vg"] - vth)
553 plt.plot(df["Vg"], df_line, color="red")
554 plt.xlabel("Vg(V)")
555 plt.ylabel("Id(A)")
556 plt.ylim(0, 1.1 * df["Id"].max())
557 plt.legend()
558 plt.savefig(output_path)
559 plt.close()
560
561
562def plot_root_idvg(df: pd.DataFrame, vth: float, max_slope: float, output_path: str) -> None:
563 """概要:
564 飽和領域 Id ** 0.5 (sqrt(Id)) -Vg プロットを接線とともに保存します。
565 引数:
566 :param df: プロットするデータを含むDataFrame。
567 :type df: pandas.DataFrame
568 :param vth: しきい値電圧。
569 :type vth: float
570 :param max_slope: 最大勾配。
571 :type max_slope: float
572 :param output_path: 出力画像の保存パス。
573 :type output_path: str
574 戻り値:
575 :returns: なし
576 :rtype: None
577 """
578
579 plt.figure()
580 plt.scatter(df["Vg"], df["rootId"], label="Vd=10V", s=4)
581 df_line = max_slope * (df["Vg"] - vth)
582 plt.plot(df["Vg"], df_line, color="red")
583 plt.xlabel("Vg(V)")
584 plt.ylabel("Id^0.5(A^0.5)")
585 plt.ylim(0, 1.1 * df["rootId"].max())
586 plt.legend()
587 plt.savefig(output_path)
588 plt.close()
589
590
591# -----------------------------------------------------------------------------
592# Output functions
593# -----------------------------------------------------------------------------
594def output_path(output_dir: str, filename: str) -> str:
595 """概要:
596 出力ディレクトリ内のパスを返します。
597 引数:
598 :param output_dir: 出力ディレクトリのパス。
599 :type output_dir: str
600 :param filename: ファイル名。
601 :type filename: str
602 戻り値:
603 :returns: 出力ディレクトリ内のファイルの完全パス。
604 :rtype: str
605 """
606
607 return os.path.join(output_dir, filename)
608
609
610def write_summary_outputs(sat: IdVgResult, lin: IdVgResult, output_dir: str) -> None:
611 """概要:
612 元のスクリプトと互換性のある要約CSVファイルを書き込みます。
613 詳細説明:
614 この関数は意図的に、元の出力列名を互換性のために保持しています。
615 これには、SSおよびIon値が列名を変更せずにlog10に変換される output_log.csv も含まれます。
616 引数:
617 :param sat: 飽和領域の分析結果。
618 :type sat: IdVgResult
619 :param lin: 線形領域の分析結果。
620 :type lin: IdVgResult
621 :param output_dir: 出力ディレクトリのパス。
622 :type output_dir: str
623 戻り値:
624 :returns: なし
625 :rtype: None
626 """
627
628 df_summary = pd.DataFrame()
629
630 df_summary["Vth_SAT"] = [sat.vth]
631 # df_summary["Von_SAT"] = [sat.von]
632 df_summary["muSAT"] = [sat.mobility]
633 # df_summary["Vgmaxslope_SAT"] = [sat.vg_at_max_slope]
634 df_summary["SS_SAT"] = [sat.ss]
635 df_summary["Ion_SAT"] = [sat.ion]
636
637 df_summary["Vth_LIN"] = [lin.vth]
638 # df_summary["Von_LIN"] = [lin.von]
639 df_summary["muFE"] = [lin.mobility]
640 # df_summary["Vgmaxslope_LIN"] = [lin.vg_at_max_slope]
641 df_summary["SS_LIN"] = [lin.ss]
642 df_summary["Ion_LIN"] = [lin.ion]
643
644 df_summary.to_csv(output_path(output_dir, "output.csv"), index=False)
645
646 df_log = df_summary.copy()
647 for column in ["SS_SAT", "Ion_SAT", "SS_LIN", "Ion_LIN"]:
648 df_log[column] = np.log10(df_log[column])
649 df_log.to_csv(output_path(output_dir, "output_log.csv"), index=False)
650
651
652# -----------------------------------------------------------------------------
653# Command line interface
654# -----------------------------------------------------------------------------
655def parse_args(argv: Optional[list[str]] = None) -> argparse.Namespace:
656 """概要:
657 コマンドライン引数を解析します。
658 引数:
659 :param argv: コマンドライン引数のリスト。デフォルトはNoneで、sys.argvを使用します。
660 :type argv: Optional[list[str]]
661 戻り値:
662 :returns: 解析された引数を格納したargparse.Namespaceオブジェクト。
663 :rtype: argparse.Namespace
664 """
665
666 parser = argparse.ArgumentParser(
667 description="Analyze TFT Id-Vg data and calculate Vth, mobility, SS, and Ion."
668 )
669 parser.add_argument(
670 "--lin",
671 default="IdVg-Vd0.1.xlsx",
672 help="Excel file for linear-region Id-Vg data. Default: %(default)s",
673 )
674 parser.add_argument(
675 "--sat",
676 default="IdVg-Vd10.xlsx",
677 help="Excel file for saturation-region Id-Vg data. Default: %(default)s",
678 )
679 parser.add_argument(
680 "--outdir",
681 default=".",
682 help="Output directory. Default: current directory",
683 )
684 parser.add_argument(
685 "--cox",
686 type=float,
687 default=DEFAULT_COX,
688 help="Gate capacitance per unit area. Default: %(default).3e",
689 )
690 parser.add_argument(
691 "--length",
692 type=float,
693 default=DEFAULT_L,
694 help="Channel length L. Default: %(default).3e",
695 )
696 parser.add_argument(
697 "--width",
698 type=float,
699 default=DEFAULT_W,
700 help="Channel width W. Default: %(default).3e",
701 )
702 parser.add_argument(
703 "--target-current",
704 type=float,
705 default=DEFAULT_TARGET_CURRENT,
706 help="Current threshold used for SS and Von. Default: %(default).3e",
707 )
708 parser.add_argument(
709 "--ioff-threshold",
710 type=float,
711 default=DEFAULT_IOFF_THRESHOLD,
712 help="Upper current threshold used for Ioff averaging. Default: %(default).3e",
713 )
714 parser.add_argument(
715 "--rolling-window",
716 type=int,
717 default=DEFAULT_ROLLING_WINDOW,
718 help="Centered rolling-average window for slope smoothing. Default: %(default)s",
719 )
720 parser.add_argument(
721 "--verbose",
722 action="store_true",
723 help="Show detailed traceback on error.",
724 )
725 return parser.parse_args(argv)
726
727
728def validate_config(config: AnalysisConfig) -> None:
729 """概要:
730 コマンドライン設定値を検証します。
731 引数:
732 :param config: 検証する分析設定オブジェクト。
733 :type config: AnalysisConfig
734 戻り値:
735 :returns: なし
736 :rtype: None
737 例外:
738 :raises ValueError: 設定値が無効な場合。
739 """
740
741 if config.cox == 0:
742 raise ValueError("cox must not be zero.")
743 if config.channel_width == 0:
744 raise ValueError("channel width must not be zero.")
745 if config.rolling_window < 1:
746 raise ValueError("rolling-window must be at least 1.")
747 if config.target_current <= 0:
748 raise ValueError("target-current must be positive.")
749 if config.ioff_threshold <= 0:
750 raise ValueError("ioff-threshold must be positive.")
751
752
753def run(args: argparse.Namespace) -> None:
754 """概要:
755 完全な分析ワークフローを実行します。
756 引数:
757 :param args: コマンドライン引数を格納したNamespace。
758 :type args: argparse.Namespace
759 戻り値:
760 :returns: なし
761 :rtype: None
762 """
763
764 config = AnalysisConfig(
765 cox=args.cox,
766 channel_length=args.length,
767 channel_width=args.width,
768 target_current=args.target_current,
769 ioff_threshold=args.ioff_threshold,
770 rolling_window=args.rolling_window,
771 )
772 validate_config(config)
773
774 os.makedirs(args.outdir, exist_ok=True)
775
776 df_sat_input = read_excel_file(args.sat, "SAT Id-Vg")
777 df_lin_input = read_excel_file(args.lin, "LIN Id-Vg")
778
779 sat = analyze_idvg_sat(df_sat_input, config)
780 sat.dataframe.to_csv(output_path(args.outdir, "IdVg-Vd10_analyze.csv"), index=False)
781 plot_root_idvg(
782 sat.dataframe,
783 sat.vth,
784 sat.max_slope,
785 output_path(args.outdir, "rootIdVg.png"),
786 )
787
788 lin = analyze_idvg_lin(df_lin_input, config)
789 lin.dataframe.to_csv(output_path(args.outdir, "IdVg-Vd0.1_analyze.csv"), index=False)
790 plot_idvg_lin(
791 lin.dataframe,
792 lin.vth,
793 lin.max_slope,
794 output_path(args.outdir, "IdVg_LIN.png"),
795 )
796
797 write_summary_outputs(sat, lin, args.outdir)
798
799 print("Analysis finished.")
800 print(f"Output directory: {args.outdir}")
801 print(f"Vth_SAT = {sat.vth}")
802 print(f"muSAT = {sat.mobility}")
803 print(f"SS_SAT = {sat.ss}")
804 print(f"Ion_SAT = {sat.ion}")
805 print(f"Vth_LIN = {lin.vth}")
806 print(f"muFE = {lin.mobility}")
807 print(f"SS_LIN = {lin.ss}")
808 print(f"Ion_LIN = {lin.ion}")
809
810
811def main(argv: Optional[list[str]] = None) -> int:
812 """概要:
813 コマンドライン実行のエントリポイントです。
814 引数:
815 :param argv: コマンドライン引数のリスト。デフォルトはNoneで、sys.argvを使用します。
816 :type argv: Optional[list[str]]
817 戻り値:
818 :returns: 終了コード。成功時は0、エラー時は1。
819 :rtype: int
820 """
821
822 args = parse_args(argv)
823 logging.basicConfig(
824 level=logging.DEBUG if args.verbose else logging.INFO,
825 format="%(levelname)s: %(message)s",
826 )
827
828 try:
829 run(args)
830 except Exception as exc:
831 print(f"Error: {exc}", file=sys.stderr)
832 if args.verbose:
833 raise
834 return 1
835
836 return 0
837
838
839if __name__ == "__main__":
840 raise SystemExit(main())