electrical.analyze_2layer_Hall_excel のソースコード

#!/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 normalize_header(s: Any) -> str: """Excelの列名を正規化し、ゆらぎを吸収する。 概要: Excelの列名に含まれる可能性のある表記のゆらぎ(大文字小文字、特殊文字、スペースなど)を吸収し、 標準的な形式に正規化します。 詳細説明: 前後の空白を削除し、文字列を小文字に変換します。 全角/半角の「μ」「µ」を「u」に、全角/半角のハイフンを半角ハイフンに統一します。 スペース、アンダースコア、括弧、スラッシュなどの区切り文字を削除します。 引数 (Parameters): :param s: 正規化する列名の文字列。 戻り値 (Returns): :returns: 正規化された列名の文字列。 """ if s is None: return "" x = str(s).strip().lower() x = x.replace("μ", "u").replace("µ", "u") x = x.replace("−", "-").replace("-", "-") # 単位の ^ や - は情報として残すより、候補名側でゆるく合わせる x = re.sub(r"[\s_、,,\[\]\(\)(){}<>/\\:=]+", "", x) return x
[ドキュメント] def make_header_map(header_row: Iterable[Any]) -> Dict[str, int]: """ヘッダー行から正規化された列名と1ベースの列インデックスのマッピングを生成する。 概要: Excelのヘッダー行を受け取り、各列の生の小文字名と正規化された名前をキーとして、 対応する1ベースの列インデックスを値とする辞書を作成します。 詳細説明: `normalize_header` 関数を使用して列名を正規化し、ヘッダーマップに格納します。 同じ正規化名が存在する場合、最初に見つかった列のインデックスが採用されます。 引数 (Parameters): :param header_row: Excelのヘッダー行のセル値のIterable。 戻り値 (Returns): :returns: 正規化列名または小文字列名から1ベースの列インデックスへのマッピング辞書。 """ header_map: Dict[str, int] = {} for idx, h in enumerate(header_row, start=1): if h is None: continue raw = str(h).strip() if raw: header_map.setdefault(raw.lower(), idx) header_map.setdefault(normalize_header(raw), idx) return header_map
[ドキュメント] 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()