#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
analyze_film_hall_excel.py
Excel にまとめた基板単体 (sub) と薄膜+基板 (tot) の Hall 測定結果から、
2層モデルで薄膜 (film) の有効 Hall 物性を計算する。
使い方:
python analyze_film_hall_excel.py input.xlsx
python analyze_film_hall_excel.py input.xlsx -o output.xlsx
python analyze_film_hall_excel.py input.xlsx --sheet Sheet1
標準出力:
{inputのstem}_analyzed.xlsx
前提:
- 基板、薄膜は同じ carrier type として扱う。
- RH は符号なしの大きさとして扱う。
ユーザ側で n 型を正に統一した表を入力する想定。
- density 計算では abs(RH_sheet) を使う。
- tot の RH_total は、装置に入力した厚さ thickness_tot_cm を使って
体積 Hall 係数 [cm^3/C] に換算済みの値として扱う。
このスクリプトでは、入力表の d_film[um] を thickness_tot_cm として使う。
必要な列名の例:
T[K]
d_sub[mm]
d_film[um]
G_sub[S/sq]
RH_sub[cm3/C]
G_tot[S/sq]
RH_total[cm3/C]
出力シート:
analyzed_wide : 1行 = 1温度。sub/tot/film を横持ちで出力。
analyzed_long : 1行 = 1温度 x 1 layer。プロットやピボット向き。
README_Hall : 使った式と列の説明。
関連リンク:
:doc:`analyze_2layer_Hall_excel_usage`
"""
from __future__ import annotations
import argparse
import math
import re
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
try:
import openpyxl
from openpyxl import load_workbook
from openpyxl.styles import Alignment, Font, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
except ImportError as exc:
raise SystemExit(
"ERROR: openpyxl が必要です。\n"
"インストール例: pip install openpyxl"
) from exc
E_CHARGE = 1.602176634e-19 # C
# ----------------------------------------------------------------------
# Numeric utilities
# ----------------------------------------------------------------------
[ドキュメント]
def to_float(value: Any) -> Optional[float]:
"""Excel cell value を float に変換する。#N/A, 空欄などは None。
概要:
Excelのセル値を安全に浮動小数点数に変換します。
詳細説明:
None、空文字列、"#N/A"などのエラー値、NaN、InfをNoneとして処理します。
数値形式のゆらぎ(カンマ、全角マイナスなど)も吸収します。
引数 (Parameters):
:param value: 変換するExcelのセル値。
戻り値 (Returns):
:returns: 変換された浮動小数点数、またはNone(変換不能な場合)。
"""
if value is None:
return None
if isinstance(value, (int, float)):
if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
return None
return float(value)
s = str(value).strip()
if not s:
return None
if s.upper() in {"#N/A", "N/A", "NA", "NAN", "INF", "-INF", "ERROR"}:
return None
# Excel/日本語出力で混ざることがある表記を軽く吸収
s = s.replace(",", "")
s = s.replace("−", "-").replace("-", "-")
try:
v = float(s)
except ValueError:
return None
if math.isnan(v) or math.isinf(v):
return None
return v
[ドキュメント]
def safe_div(num: Optional[float], den: Optional[float]) -> Optional[float]:
"""ゼロ除算やNone値入力を安全に処理する浮動小数点数の除算。
概要:
分子または分母がNoneの場合、あるいは分母がゼロの場合にNoneを返します。
詳細説明:
浮動小数点数の除算 `num / den` を行いますが、numまたはdenがNoneの場合、
あるいはdenが0の場合には `ValueError` や `ZeroDivisionError` を発生させず、
代わりにNoneを返します。
引数 (Parameters):
:param num: 分子となる数値。
:param den: 分母となる数値。
戻り値 (Returns):
:returns: 除算結果の浮動小数点数、またはNone(計算不能な場合)。
"""
if num is None or den is None or den == 0:
return None
return num / den
[ドキュメント]
def density_from_rh_sheet(rh_sheet_cm2_c: Optional[float]) -> Optional[float]:
"""シートホール係数からシートキャリア密度を計算する。
概要:
シートホール係数 `RH_sheet [cm^2/C]` からシートキャリア密度 `n_sheet [cm^-2]` を計算します。
詳細説明:
`n_sheet = 1 / (e * |RH_sheet|)` の式を使用します。ここで `e` は電気素量 `E_CHARGE` です。
`rh_sheet_cm2_c` がNoneまたはゼロの場合はNoneを返します。
引数 (Parameters):
:param rh_sheet_cm2_c: シートホール係数 `[cm^2/C]`。
戻り値 (Returns):
:returns: 計算されたシートキャリア密度 `[cm^-2]`、またはNone。
"""
"""n_sheet [cm^-2] = 1/(e |RH_sheet|)."""
if rh_sheet_cm2_c is None or rh_sheet_cm2_c == 0:
return None
return 1.0 / (E_CHARGE * abs(rh_sheet_cm2_c))
# ----------------------------------------------------------------------
# Header utilities
# ----------------------------------------------------------------------
[ドキュメント]
def find_col(header_map: Dict[str, int], candidates: Iterable[str], required: bool = True) -> Optional[int]:
"""指定された候補リストに基づいてヘッダーマップから列インデックスを検索する。
概要:
与えられたヘッダーマップと列名の候補リストを使用して、
該当する列の1ベースのインデックスを検索します。
詳細説明:
`candidates` リスト内の各候補名に対し、生の小文字名と正規化された名前の両方で
`header_map` を検索します。
`required` がTrueで列が見つからない場合、`KeyError` を発生させます。
`required` がFalseで列が見つからない場合はNoneを返します。
引数 (Parameters):
:param header_map: `make_header_map` で作成されたヘッダーマッピング辞書。
:param candidates: 検索する列名の候補のリスト。
:param required: 列が必須であるかを示すブール値。Trueの場合、見つからないとKeyErrorが発生します。
戻り値 (Returns):
:returns: 見つかった列の1ベースのインデックス、または見つからずrequired=Falseの場合はNone。
:raises KeyError: required=Trueで列が見つからない場合。
"""
for c in candidates:
if c.lower() in header_map:
return header_map[c.lower()]
nc = normalize_header(c)
if nc in header_map:
return header_map[nc]
if required:
raise KeyError("required column not found. candidates=" + ", ".join(candidates))
return None
COLUMN_CANDIDATES = {
"T_K": ["T[K]", "T_K", "temperature_K", "temp_K", "T"],
"d_sub_mm": ["d_sub[mm]", "d_sub_mm", "sub_thickness_mm", "thickness_sub_mm", "dsub[mm]"],
"d_film_um": ["d_film[um]", "d_film[µm]", "d_film[μm]", "d_film_um", "film_thickness_um", "thickness_film_um"],
"G_sub": ["G_sub[S/sq]", "G_sub", "Gsub", "sheet_conductance_sub", "G_sub_S_per_sq"],
"RH_sub": ["RH_sub[cm3/C]", "RH_sub", "RHsub", "RH_sub_cm3_C", "Hall_coeff_sub"],
"RH_sub_sheet_input": ["RH,sub,sheet[cm2/C]", "RH_sub_sheet[cm2/C]", "RHsubsheet[cm2/C]", "RH_sub_sheet"],
"G_tot": ["G_tot[S/sq]", "G_tot", "Gtot", "sheet_conductance_tot", "G_total[S/sq]"],
"G_film_input": ["G_film[S/sq]", "G_film", "Gfilm", "sheet_conductance_film"],
"I_total_A": ["I_total[A]", "I_tot[A]", "Itotal[A]"],
"B_total_T": ["B_total[Tesla]", "B_total[T]", "B_tot[T]", "Btotal[Tesla]"],
"VH_total_V": ["VH_total[V]", "V_H_total[V]", "VH_tot[V]", "VHall_total[V]"],
"RH_tot": ["RH_total[cm3/C]", "RH_tot[cm3/C]", "RH_total", "RH_tot", "RHtotal[cm3/C]"],
"RH_tot_sheet_input": ["Rhtot,sheet[cm2/C]", "RH_tot_sheet[cm2/C]", "RH,total,sheet[cm2/C]", "RHtot_sheet"],
}
# ----------------------------------------------------------------------
# Hall model
# ----------------------------------------------------------------------
[ドキュメント]
def analyze_two_layer_row(
*,
T_K: Optional[float],
thickness_sub_cm: float,
thickness_film_cm: float,
G_sub: float,
RH_sub_volume: float,
G_tot: float,
RH_tot_volume: float,
source_sheet: str,
source_row: int,
optional_input: Optional[Dict[str, Optional[float]]] = None,
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
"""2層ホールモデルを用いて、Excelの1行分の測定データから薄膜のホール物性を解析する。
概要:
基板 (sub) と薄膜+基板 (tot) のHall測定値から、
薄膜 (film) のシートコンダクタンス、シートホール係数、移動度、キャリア密度などを計算します。
詳細説明:
入力された体積Hall係数をシートHall係数に変換し、2層ホールモデルの式に基づいて
薄膜の物性を抽出します。計算結果は、ワイド形式 (1行にsub/tot/filmを横並び) と
ロング形式 (1行に1層の物性) の2種類の辞書リストとして返されます。
G_filmが0以下の場合など、計算が困難な場合には警告が生成されます。
引数 (Parameters):
:param T_K: 温度 [K]。
:param thickness_sub_cm: 基板の厚さ [cm]。
:param thickness_film_cm: 薄膜の厚さ [cm]。
:param G_sub: 基板のシートコンダクタンス [S/sq]。
:param RH_sub_volume: 基板の体積Hall係数 [cm^3/C]。
:param G_tot: 全体(薄膜+基板)のシートコンダクタンス [S/sq]。
:param RH_tot_volume: 全体(薄膜+基板)の体積Hall係数 [cm^3/C]。
:param source_sheet: 元データのシート名。
:param source_row: 元データの行番号。
:param optional_input: オプションの入力データを含む辞書。G_film_inputなどの比較用データ。
戻り値 (Returns):
:returns: (wide_row, long_rows) のタプル。
wide_row: 全ての計算結果と入力値を1行にまとめた辞書。
long_rows: 各層(sub, tot, film)の計算結果をそれぞれ1行とした辞書のリスト。
:raises ValueError: thickness_sub_cm または thickness_film_cm が正でない場合。
"""
optional_input = optional_input or {}
warning: List[str] = []
if thickness_sub_cm <= 0:
raise ValueError("d_sub must be positive")
if thickness_film_cm <= 0:
raise ValueError("d_film must be positive")
# sub/tot の体積 Hall 係数をシート Hall 係数へ変換。
# input.xlsx では tot の RH_total は d_film を装置に入力した apparent RH として扱う。
RH_sub_sheet = RH_sub_volume / thickness_sub_cm
RH_tot_sheet = RH_tot_volume / thickness_film_cm
G_film = G_tot - G_sub
if G_film <= 0:
warning.append("G_film = G_tot - G_sub <= 0; film cannot be extracted")
RH_film_sheet = None
else:
RH_film_sheet = (G_tot**2 * RH_tot_sheet - G_sub**2 * RH_sub_sheet) / (G_film**2)
# Optional input consistency checks
G_film_input = optional_input.get("G_film_input")
if G_film_input is not None and abs(G_film_input - G_film) > max(1e-30, abs(G_film) * 1e-8):
warning.append("G_film input differs from calculated G_tot-G_sub")
def layer_props(
layer: str,
thickness_cm: float,
G_sheet: Optional[float],
RH_sheet: Optional[float],
RH_volume: Optional[float],
note: str = "",
) -> Dict[str, Any]:
sigma = safe_div(G_sheet, thickness_cm)
mu = None if G_sheet is None or RH_sheet is None else G_sheet * RH_sheet
n_sheet = density_from_rh_sheet(RH_sheet)
n_volume = safe_div(n_sheet, thickness_cm)
return {
"source_sheet": source_sheet,
"source_row": source_row,
"T_K": T_K,
"layer": layer,
"carrier_type_assumed": "n (RH stored as positive magnitude)",
"thickness_cm": thickness_cm,
"G_sheet_S_per_sq": G_sheet,
"sigma_S_per_cm": sigma,
"RH_volume_cm3_per_C": RH_volume,
"RH_sheet_cm2_per_C": RH_sheet,
"mu_H_avg_cm2_per_Vs": mu,
"n_sheet_cm_minus2": n_sheet,
"n_volume_cm_minus3": n_volume,
"note": note,
}
sub = layer_props(
"sub",
thickness_sub_cm,
G_sub,
RH_sub_sheet,
RH_sub_volume,
"substrate-only measured value",
)
tot = layer_props(
"tot",
thickness_film_cm,
G_tot,
RH_tot_sheet,
RH_tot_volume,
"film+substrate apparent value; thickness is d_film used for RH_total conversion",
)
film_RH_volume = None if RH_film_sheet is None else RH_film_sheet * thickness_film_cm
film = layer_props(
"film",
thickness_film_cm,
G_film,
RH_film_sheet,
film_RH_volume,
"extracted by two-layer Hall model",
)
long_rows = [sub, tot, film]
wide: Dict[str, Any] = {
"source_sheet": source_sheet,
"source_row": source_row,
"T_K": T_K,
"d_sub_cm": thickness_sub_cm,
"d_film_cm": thickness_film_cm,
"d_sub_mm": thickness_sub_cm / 0.1,
"d_film_um": thickness_film_cm / 1e-4,
"I_total_A": optional_input.get("I_total_A"),
"B_total_T": optional_input.get("B_total_T"),
"VH_total_V": optional_input.get("VH_total_V"),
"warning": "; ".join(warning),
}
for prefix, props in [("sub", sub), ("tot", tot), ("film", film)]:
for key in [
"G_sheet_S_per_sq",
"sigma_S_per_cm",
"RH_volume_cm3_per_C",
"RH_sheet_cm2_per_C",
"mu_H_avg_cm2_per_Vs",
"n_sheet_cm_minus2",
"n_volume_cm_minus3",
]:
wide[f"{prefix}_{key}"] = props[key]
# input に存在した比較用列も最後に残す
wide["input_RH_sub_sheet_cm2_per_C"] = optional_input.get("RH_sub_sheet_input")
wide["input_RH_tot_sheet_cm2_per_C"] = optional_input.get("RH_tot_sheet_input")
wide["input_G_film_S_per_sq"] = optional_input.get("G_film_input")
return wide, long_rows
# ----------------------------------------------------------------------
# Excel I/O
# ----------------------------------------------------------------------
[ドキュメント]
def iter_data_rows(ws_values) -> Iterable[Tuple[int, Dict[str, Any]]]:
"""指定 worksheet からデータ行を辞書として返す。
概要:
OpenPyXLのワークシートオブジェクトから、ヘッダーを解釈してデータ行を辞書の形でイテレートします。
詳細説明:
ワークシートの最初の行をヘッダーとして読み込み、`make_header_map` と `find_col` を使用して
必要な列とオプションの列を特定します。
各データ行は辞書として抽出され、`to_float` 関数によって値が浮動小数点数に変換されます。
完全に空の行はスキップされます。
引数 (Parameters):
:param ws_values: 読み込むOpenPyXLのワークシートオブジェクト。
戻り値 (Returns):
:returns: (行番号, データ辞書) のタプルのIterable。
"""
header_values = [cell.value for cell in ws_values[1]]
header_map = make_header_map(header_values)
required_cols = {
key: find_col(header_map, candidates, required=True)
for key, candidates in COLUMN_CANDIDATES.items()
if key in {"T_K", "d_sub_mm", "d_film_um", "G_sub", "RH_sub", "G_tot", "RH_tot"}
}
optional_cols = {
key: find_col(header_map, candidates, required=False)
for key, candidates in COLUMN_CANDIDATES.items()
if key not in required_cols
}
for row_idx in range(2, ws_values.max_row + 1):
vals = {key: ws_values.cell(row_idx, col).value for key, col in required_cols.items()}
# 完全な空行はスキップ
if all(v is None or str(v).strip() == "" for v in vals.values()):
continue
record: Dict[str, Any] = {key: to_float(value) for key, value in vals.items()}
for key, col in optional_cols.items():
record[key] = to_float(ws_values.cell(row_idx, col).value) if col else None
yield row_idx, record
[ドキュメント]
def remove_sheet_if_exists(wb, name: str) -> None:
"""ワークブックから指定された名前のシートが存在すれば削除する。
概要:
OpenPyXLのワークブックから、指定された名前のシートが存在する場合に削除します。
詳細説明:
主に、新しい解析結果シートを書き込む前に、以前のシートをクリーンアップするために使用されます。
シートが存在しない場合は何も行いません。
引数 (Parameters):
:param wb: 処理対象のOpenPyXLワークブックオブジェクト。
:param name: 削除するシートの名前。
戻り値 (Returns):
:returns: None
"""
if name in wb.sheetnames:
ws = wb[name]
wb.remove(ws)
[ドキュメント]
def write_table(ws, rows: List[Dict[str, Any]], title: Optional[str] = None) -> None:
"""辞書リストをExcelワークシートに書き込み、基本的なスタイルを設定する。
概要:
辞書のリストをOpenPyXLのワークシートにテーブル形式で書き込み、
読みやすいように基本的なスタイル(ヘッダー、数値フォーマット、列幅など)を設定します。
詳細説明:
辞書リストの最初の辞書のキーをヘッダーとして使用し、各辞書の値をデータ行として書き込みます。
オプションでタイトル行を追加できます。
ヘッダー行は太字、背景色付きで中央揃えに、データ行は数値に応じて適切なフォーマット(例: 科学表記)が適用されます。
フィルタ、フリーズペイン、自動列幅調整も行われます。
引数 (Parameters):
:param ws: 書き込み先のOpenPyXLワークシートオブジェクト。
:param rows: 書き込むデータの辞書リスト。
:param title: テーブルのタイトルとして使用する文字列(オプション)。
戻り値 (Returns):
:returns: None
"""
if not rows:
ws.append(["no data"])
return
headers = list(rows[0].keys())
if title:
ws.append([title])
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(headers))
cell = ws.cell(1, 1)
cell.font = Font(bold=True, size=14, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="1F4E78")
cell.alignment = Alignment(horizontal="center")
header_row = 2
else:
header_row = 1
ws.append(headers)
for row in rows:
ws.append([row.get(h) for h in headers])
# style
header_fill = PatternFill("solid", fgColor="D9EAF7")
thin = Side(style="thin", color="D9D9D9")
for cell in ws[header_row]:
cell.font = Font(bold=True)
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell.border = Border(bottom=thin)
ws.freeze_panes = ws.cell(header_row + 1, 1)
ws.auto_filter.ref = ws.dimensions
# number formats
for col_idx, h in enumerate(headers, start=1):
h_low = h.lower()
for row_idx in range(header_row + 1, ws.max_row + 1):
c = ws.cell(row_idx, col_idx)
if isinstance(c.value, (int, float)):
if h_low in {"source_row"}:
c.number_format = "0"
elif h_low.endswith("_k"):
c.number_format = "0.00"
elif "warning" in h_low or "note" in h_low:
pass
else:
c.number_format = "0.000000E+00"
# column widths
for col_idx, h in enumerate(headers, start=1):
col_letter = get_column_letter(col_idx)
max_len = len(str(h))
for row_idx in range(1, min(ws.max_row, 80) + 1):
v = ws.cell(row_idx, col_idx).value
if v is not None:
max_len = max(max_len, len(str(v)))
ws.column_dimensions[col_letter].width = min(max(max_len + 2, 11), 34)
for row in ws.iter_rows():
for cell in row:
cell.alignment = Alignment(vertical="top", wrap_text=True)
[ドキュメント]
def write_readme(ws) -> None:
"""2層ホール解析のREADMEシートをExcelワークシートに書き込む。
概要:
2層ホール解析に関する前提、使用される方程式、および出力シートの説明を含むREADME情報を、
OpenPyXLワークシートに書き込みます。
詳細説明:
シートのタイトルとセクションタイトルにスタイルが適用され、
テキストは折り返し表示されるように設定されます。
引数 (Parameters):
:param ws: 書き込み先のOpenPyXLワークシートオブジェクト。
戻り値 (Returns):
:returns: None
"""
lines = [
["2-layer Hall analysis README"],
[""],
["Assumptions"],
["- sub = substrate-only Hall measurement."],
["- tot = film + substrate Hall measurement."],
["- RH values are treated as positive magnitudes. User has unified n-type data to positive RH."],
["- Carrier densities use abs(RH_sheet)."],
["- d_sub[mm] is converted to cm by d_sub_cm = d_sub_mm * 0.1."],
["- d_film[um] is converted to cm by d_film_cm = d_film_um * 1e-4."],
["- For tot, d_film_cm is used as the apparent thickness used to convert RH_total to RH_tot_sheet."],
[""],
["Equations"],
["RH_sub_sheet = RH_sub_volume / d_sub_cm"],
["RH_tot_sheet = RH_tot_volume / d_film_cm"],
["G_film = G_tot - G_sub"],
["RH_tot_sheet = (G_film^2 RH_film_sheet + G_sub^2 RH_sub_sheet) / G_tot^2"],
["RH_film_sheet = (G_tot^2 RH_tot_sheet - G_sub^2 RH_sub_sheet) / G_film^2"],
["mu_H = G_sheet * RH_sheet"],
["n_sheet = 1 / (e * abs(RH_sheet))"],
["n_volume = n_sheet / thickness_cm"],
["sigma = G_sheet / thickness_cm"],
[""],
["Output sheets"],
["analyzed_wide: one row per temperature, sub/tot/film side-by-side."],
["analyzed_long: one row per temperature and layer, convenient for plotting/pivot tables."],
]
for row in lines:
ws.append(row)
ws.column_dimensions["A"].width = 120
ws["A1"].font = Font(bold=True, size=14, color="FFFFFF")
ws["A1"].fill = PatternFill("solid", fgColor="1F4E78")
for r in range(1, ws.max_row + 1):
ws.cell(r, 1).alignment = Alignment(wrap_text=True, vertical="top")
if ws.cell(r, 1).value in {"Assumptions", "Equations", "Output sheets"}:
ws.cell(r, 1).font = Font(bold=True, color="1F4E78")
[ドキュメント]
def analyze_workbook(input_path: Path, output_path: Path, sheet_name: Optional[str] = None) -> Tuple[int, int]:
"""入力Excelファイルから2層ホールデータを読み込み、解析し、結果を新しいExcelファイルに書き出す。
概要:
指定されたExcelファイルからホール測定データを読み込み、各行に対して2層ホールモデル解析を実行し、
解析結果をワイド形式、ロング形式、およびREADMEを含む新しいExcelファイルに出力します。
詳細説明:
`input_path` で指定されたExcelブックを読み込み、`sheet_name` で指定されたシート(または最初のシート)から
`iter_data_rows` を使ってデータを抽出します。
各データ行は `analyze_two_layer_row` によって解析され、結果は `wide_rows` と `long_rows` に収集されます。
解析が失敗した行はスキップされ、警告メッセージが生成されます。
最終的に、`output_path` に指定されたExcelファイルに3つの新しいシート (`analyzed_wide`, `analyzed_long`, `README_Hall`)
として結果が書き込まれます。既存の同名シートは上書きされます。
引数 (Parameters):
:param input_path: 入力Excelファイルのパスオブジェクト。
:param output_path: 出力Excelファイルのパスオブジェクト。
:param sheet_name: 解析対象のシート名(Noneの場合は最初のシートが使用されます)。
戻り値 (Returns):
:returns: (解析された行数, スキップされた行数) のタプル。
:raises KeyError: 指定されたシート名が見つからない場合。
"""
# values workbook: formula cached values for reading
wb_values = load_workbook(input_path, data_only=True)
# output workbook: preserve source workbook as much as possible
wb_out = load_workbook(input_path)
if sheet_name is None:
source_ws_values = wb_values[wb_values.sheetnames[0]]
source_sheet_name = source_ws_values.title
else:
if sheet_name not in wb_values.sheetnames:
raise KeyError(f"sheet not found: {sheet_name}")
source_ws_values = wb_values[sheet_name]
source_sheet_name = sheet_name
wide_rows: List[Dict[str, Any]] = []
long_rows: List[Dict[str, Any]] = []
skipped = 0
for row_idx, record in iter_data_rows(source_ws_values):
required = ["d_sub_mm", "d_film_um", "G_sub", "RH_sub", "G_tot", "RH_tot"]
if any(record.get(k) is None for k in required):
skipped += 1
continue
try:
d_sub_cm = record["d_sub_mm"] * 0.1
d_film_cm = record["d_film_um"] * 1e-4
wide, longs = analyze_two_layer_row(
T_K=record.get("T_K"),
thickness_sub_cm=d_sub_cm,
thickness_film_cm=d_film_cm,
G_sub=record["G_sub"],
RH_sub_volume=record["RH_sub"],
G_tot=record["G_tot"],
RH_tot_volume=record["RH_tot"],
source_sheet=source_sheet_name,
source_row=row_idx,
optional_input=record,
)
except Exception as exc:
skipped += 1
wide = {
"source_sheet": source_sheet_name,
"source_row": row_idx,
"T_K": record.get("T_K"),
"warning": f"failed: {exc}",
}
longs = []
wide_rows.append(wide)
long_rows.extend(longs)
for name in ["analyzed_wide", "analyzed_long", "README_Hall"]:
remove_sheet_if_exists(wb_out, name)
ws_wide = wb_out.create_sheet("analyzed_wide")
write_table(ws_wide, wide_rows, title="Two-layer Hall analysis - wide format")
ws_long = wb_out.create_sheet("analyized_long")
write_table(ws_long, long_rows, title="Two-layer Hall analysis - long format")
ws_readme = wb_out.create_sheet("README_Hall")
write_readme(ws_readme)
wb_out.save(output_path)
return len(wide_rows), skipped
# ----------------------------------------------------------------------
# CLI
# ----------------------------------------------------------------------
[ドキュメント]
def initialize() -> argparse.Namespace:
"""コマンドライン引数を解析する。
概要:
スクリプトの実行に必要なコマンドライン引数を定義し、解析します。
詳細説明:
入力Excelファイルのパスは必須です。
出力Excelファイルのパスと解析対象のシート名はオプションです。
出力パスが指定されない場合、入力ファイル名に基づいて自動生成されます。
ヘルプメッセージはスクリプトの目的を説明します。
引数 (Parameters):
なし
戻り値 (Returns):
:returns: 解析されたコマンドライン引数を格納する argparse.Namespace オブジェクト。
"""
parser = argparse.ArgumentParser(
description="Analyze film Hall properties from sub/tot data in an Excel workbook."
)
parser.add_argument("input_xlsx", help="input Excel file, e.g. input.xlsx")
parser.add_argument(
"-o", "--output",
default=None,
help="output Excel file. Default: {input_stem}_analyzed.xlsx",
)
parser.add_argument(
"--sheet",
default=None,
help="source sheet name. Default: first sheet",
)
return parser.parse_args()
[ドキュメント]
def main() -> None:
"""スクリプトのエントリーポイント。
概要:
コマンドライン引数の解析、入力ファイルの検証、およびホール解析ワークフロー全体を実行します。
詳細説明:
`initialize` 関数を呼び出して引数を取得し、入力ファイルの存在を確認します。
出力ファイルのパスが指定されていない場合は、入力ファイル名から自動生成します。
`analyze_workbook` 関数を呼び出してExcel解析を実行し、
処理された行数とスキップされた行数を標準出力に表示します。
引数 (Parameters):
なし
戻り値 (Returns):
:returns: None
:raises SystemExit: 入力ファイルが見つからない場合。
"""
args = initialize()
input_path = Path(args.input_xlsx)
if not input_path.exists():
raise SystemExit(f"ERROR: input file not found: {input_path}")
output_path = Path(args.output) if args.output else input_path.with_name(input_path.stem + "_analyzed.xlsx")
n_rows, skipped = analyze_workbook(input_path, output_path, sheet_name=args.sheet)
print("=== Two-layer Hall analysis completed ===")
print(f"Input : {input_path}")
print(f"Output: {output_path}")
print(f"Analyzed rows: {n_rows}")
print(f"Skipped rows : {skipped}")
if __name__ == "__main__":
main()