regression.arrhenius_plot_argparse のソースコード

"""
Arrhenius plotと多項式フィットを行うためのスクリプト。

関連リンク: :doc:`arrhenius_plot_argparse_usage`
"""
import sys
import argparse
import numpy as np
from numpy import exp, log
import matplotlib.pyplot as plt

from tklib.tkutils import replace_path, pint, pfloat
from tklib.tkvariousdata import tkVariousData
from tklib.tkapplication import tkApplication
from tklib.tkparams import tkParams
from tklib.tksci.tkFit import tkFit
from tklib.tkgraphic.tkplotevent import tkPlotEvent
from tklib.tksci.tksci import kB, e


[ドキュメント] def initialize(): """ argparseの結果argsを受け取り、app.cfgに設定する。 コマンドライン引数を解析し、tkApplicationとtkParamsのインスタンスを初期化して各種設定を行う。 :returns: tuple. (app, cfg, parser) のタプル。 """ app = tkApplication() cfg = tkParams() parser = argparse.ArgumentParser(description="Arrhenius plot と多項式フィット") parser.add_argument('--infile', type=str, default="Hall-T.xlsx", help='Input file') parser.add_argument('--model', type=str, default='simple Arrhenius #log(P)=A-(eEa/kB)*(1/T)', help="モデル選択") parser.add_argument('--Tlabel', type=str, default='T(K)', help='T関連データ列ラベル') parser.add_argument('--Plabel', type=str, default='P', help='P関連データ列ラベル') parser.add_argument('--Ttype', type=str, choices=['T(K)', 'T(C)', '1/T', '1000/T'], default='T(K)', help='Tデータ変換方法') parser.add_argument('--Ptype', type=str, choices=['P', 'log10(P)', 'log_e(P)'], default='P', help='Pデータ変換方法') parser.add_argument('--xmin', type=float, default=-1.0e100, help='フィットする x の下限') parser.add_argument('--xmax', type=float, default=1.0e100, help='フィットする x の上限') parser.add_argument('--Tmin', type=float, default=-1.0e100, help='フィットする T の下限') parser.add_argument('--Tmax', type=float, default=1.0e100, help='フィットする T の上限') parser.add_argument('--Tcalmin', type=str, default='*', help="計算用する の下限('*' で自動)") parser.add_argument('--Tcalmax', type=str, default='*', help="計算用する の上限('*' で自動)") parser.add_argument('--ncal', type=int, default=201, help='Number of points in xcal grid') parser.add_argument('--xlsm_template', type=str, default="StandardGraph.xlsm", help='Excel template path') parser.add_argument('--plot_ci', type=int, default=1, help='Plot confidence interval') parser.add_argument('--plot_sigma_param', type=int, default=1, help='Plot parameter uncertainty band') parser.add_argument('--plot_sigma_pred', type=int, default=1, help='Plot prediction uncertainty band') parser.add_argument('--plot_sigma_combined', type=int, default=0, help='Plot combined uncertainty band') parser.add_argument('--figsize', type=float, nargs=2, default=[12, 8], help='プロット figsize, 例: --figsize 8 8') parser.add_argument('--fontsize', type=int, default=16, help='Font size for plots') parser.add_argument('--fontsize_legend', type=int, default=12, help='Legend font size') group = parser.add_mutually_exclusive_group() group.add_argument('--pause', dest='pause', action='store_true', help='Pause on terminate') group.add_argument('--no-pause', dest='pause', action='store_false', help='Do not pause on terminate') parser.set_defaults(pause=True) args = parser.parse_args() app.cfg = cfg for key in vars(args): setattr(cfg, key, getattr(args, key)) cfg.logfile = app.replace_path(cfg.infile) # cfg.outfile = app.replace_path(cfg.infile, template = "{dirname}/{filebody}-out.xlsx") cfg.output_fitting_path = app.replace_path(cfg.infile, template = "{dirname}/{filebody}-fit.xlsm") cfg.output_parameter_path = app.replace_path(cfg.infile, template = ["{dirname}", "{filebody}-parameters.xlsx"]) return app, cfg, parser
[ドキュメント] def build_design_matrix(x, order): """ 多項式回帰のための計画行列(デザインマトリックス)を構築する。 与えられたデータ点と多項式の次数に基づいて、各データ点のべき乗からなる行列を生成する。 :param x: array-like. 入力データ点。 :param order: int. 多項式の次数。 :returns: np.ndarray. (N, order+1) の計画行列。 """ x = np.asarray(x) N = len(x) X = np.ones((N, order + 1)) for j in range(1, order + 1): # Using scalar pow for clarity X[:, j] = [pow(val, j) for val in x] return X
[ドキュメント] def mlsq_error(X, y): """ 最小二乗法によるフィッティングを実行する。 計画行列と観測値から、回帰係数、係数の標準偏差、共分散行列、残差分散を計算する。 :param X: np.ndarray. 計画行列 (N, p)。 :param y: array-like. 観測値 (N,)。 :returns: tuple. (beta, beta_std, cov_beta, sigma2_resid) のタプル。betaは推定係数、beta_stdは係数の標準偏差、cov_betaはパラメータの共分散行列、sigma2_residは残差分散の推定値。 """ y = np.asarray(y) XtX = X.T @ X XtX_inv = np.linalg.inv(XtX) beta = XtX_inv @ (X.T @ y) # Residuals and variance residuals = y - X @ beta N, p = X.shape RSS = float(residuals.T @ residuals) sigma2_resid = RSS / (N - p) cov_beta = sigma2_resid * XtX_inv beta_std = np.sqrt(np.diag(cov_beta)) return beta, beta_std, cov_beta, sigma2_resid
[ドキュメント] def compute_param_uncertainty(X, cov_beta): """ パラメータに基づく予測値の分散を計算する。 各データ点(Xの行)における予測値の分散を、共分散行列を用いて計算する。 :param X: np.ndarray. 計画行列 (N, p)。 :param cov_beta: np.ndarray. 共分散行列 (p, p)。 :returns: np.ndarray. 予測の分散 (N,)。 """ # Var[y_mean] = diag(X @ cov_beta @ X.T) return np.sum((X @ cov_beta) * X, axis=1)
[ドキュメント] def compute_measurement_error(y): """ 入力データの分散から測定誤差を推定する。 観測値の不偏分散を用いて測定誤差の標準偏差を算出する。 :param y: array-like. 観測値。 :returns: float. 推定された測定誤差の標準偏差(不偏)。 """ y = np.asarray(y) var_unbiased = np.var(y, ddof=1) if len(y) > 1 else 0.0 return np.sqrt(var_unbiased)
[ドキュメント] def compute_bands(xcal, beta, cov_beta, sigma2_resid, sigma_meas): """ xcalにおける平均予測値と各種不確実性バンドを計算する。 パラメータの不確実性、残差分散、および測定誤差を考慮した各標準偏差を算出する。 :param xcal: array-like. 評価するデータ点。 :param beta: np.ndarray. 最小二乗法の回帰係数。 :param cov_beta: np.ndarray. パラメータの共分散行列。 :param sigma2_resid: float. 残差分散。 :param sigma_meas: float. 測定誤差の標準偏差。 :returns: dict. 'y_mean' (平均予測値), 'sigma_param' (パラメータ起因の標準偏差), 'sigma_pred' (予測の標準偏差), 'sigma_combined' (複合標準偏差) を含む辞書。 """ Xcal = build_design_matrix(xcal, len(beta) - 1) y_mean = Xcal @ beta var_param = compute_param_uncertainty(Xcal, cov_beta) sigma_param = np.sqrt(var_param) sigma_pred = np.sqrt(var_param + sigma2_resid) sigma_combined = np.sqrt(var_param + sigma_meas**2) return { 'y_mean': y_mean, 'sigma_param': sigma_param, 'sigma_pred': sigma_pred, 'sigma_combined': sigma_combined }
[ドキュメント] def execute(app): """ アプリケーションのメイン処理を実行する。 データの読み込み、前処理、多項式フィッティング、誤差計算、ファイル出力、およびグラフのプロットまでの一連の処理を行う。 :param app: tkApplication. アプリケーションオブジェクト。 :returns: None. """ cfg = app.cfg print(f"Open logfile [{cfg.logfile}]") app.redict(targets=["stdout", cfg.logfile], mode='w') # Tcalmin/Tcalmax が '*' なら自動設定 later cfg.xmin = pfloat(cfg.xmin) cfg.xmax = pfloat(cfg.xmax) cfg.Tmin = pfloat(cfg.Tmin) cfg.Tmax = pfloat(cfg.Tmax) # 以下元コード同様にデータ読み込みと変換 print("#======================================================") print("# Analyze activation energy etc by Arrhenius plot") print("#======================================================") print(f"infile : {cfg.infile}") print(f"model : {cfg.model}") print(f"Tlabel : {cfg.Tlabel}, Plabel: {cfg.Plabel}") print(f"Ttype : {cfg.Ttype}, Ptype: {cfg.Ptype}") print(f"Fitting x range: {cfg.xmin} - {cfg.xmax}") print(f"Fitting T range: {cfg.Tmin} - {cfg.Tmax}") if '***' in cfg.model: app.terminate("Error: Choose model", pause=cfg.pause) # データ読み込み print(f"Read [{cfg.infile}]") datafile = tkVariousData(cfg.infile) labels, datalist = datafile.Read_minimum_matrix(close_fp=True, usage=app.usage) label_x, xX = datafile.FindDataArray(cfg.Tlabel, flag='i') label_y, yY = datafile.FindDataArray(cfg.Plabel, flag='i') if xX is None or yY is None: app.terminate("Error: 指定ラベルのデータが見つかりません", pause=cfg.pause) # T の変換 if cfg.Ttype == 'T(K)': T = xX elif cfg.Ttype == 'T(C)': T = [v + 273.15 for v in xX] elif cfg.Ttype == '1/T': T = [1.0 / v for v in xX] elif cfg.Ttype == '1000/T': T = [1000.0 / v for v in xX] else: app.terminate(f"Invalid Ttype [{cfg.Ttype}]", usage=app.usage, pause=cfg.pause) # P の変換 if cfg.Ptype == 'P': P = yY elif cfg.Ptype == 'log10(P)': P = [10**v for v in yY] elif cfg.Ptype == 'log_e(P)': P = [np.exp(v) for v in yY] else: app.terminate(f"Invalid Ptype [{cfg.Ptype}]", usage=app.usage, pause=cfg.pause) print("T(K)=", T) print("P =", P) # フィッティング対象抽出 T1000 = [1000.0 / v for v in T] log10P = [log(v)/log(10.0) for v in P] x_fit = [] y_fit = [] for xi_orig, xi, yi in zip(xX, T1000, log10P): if cfg.xmin <= xi_orig <= cfg.xmax and cfg.Tmin <= (1000.0/xi if xi!=0 else float('inf')) <= cfg.Tmax: x_fit.append(xi) y_fit.append(yi) # 多項式次数決定 if cfg.model == 'percolation': norder = 2 elif cfg.model == '3rd order': norder = 3 elif cfg.model == '4th order': norder = 4 else: norder = 1 print(f"\nLeast-squares fitting with {norder}-th order polynomial of 1000/T") print("Data to be fitted:") print(f" {'1000/T':12} {'log10(P)':12}") for xi, yi in zip(x_fit, y_fit): print(f" {xi:12.4g} {yi:12.4g}") X = build_design_matrix(x_fit, norder) beta, beta_std, cov_beta, sigma2_resid = mlsq_error(X, y_fit) print("Fitted polynomial coefficients:") for i, coef in enumerate(beta): print(f" c{i} = {coef:g} +- {beta_std[i]:g}") print(f" Residual variance sigma2_resid = {sigma2_resid:12.4g}") print(f"Save parameters and stds to {cfg.output_parameter_path}") fit = tkFit() fit.to_excel(cfg.output_parameter_path, ["coeff", "std"], [beta, beta_std]) # 測定誤差目安 sigma_meas = compute_measurement_error(y_fit) print(f"Estimated measurement error sigma_meas = {sigma_meas:g}") Tcalmin = pfloat(cfg.Tcalmin, defval=min(T)) Tcalmax = pfloat(cfg.Tcalmax, defval=max(T)) Tstep = (Tcalmax - Tcalmin) / (cfg.ncal - 1) T_plot = [Tcalmin + i * Tstep for i in range(cfg.ncal)] x_plot = [1000.0 / v for v in T_plot] xcal = np.linspace(1000 / Tcalmax, 1000 / Tcalmin, cfg.ncal) ycal = build_design_matrix(xcal, norder) @ beta # log10P 予測 P_plot = [10**val for val in ycal] fit_obj = tkFit() P_fit = [10**val for val in (X @ beta)] fit_obj.print_scores(heading="\nScores between P(input) and P(fit)", y1=P, y2=P_fit) fit_obj.print_scores(heading="\nScores between log10(P(input)) and log10(P(fit))", y1=log10P, y2=list(X @ beta)) bands = compute_bands(xcal, beta, cov_beta, sigma2_resid, sigma_meas) print(f"Save results to [{cfg.output_fitting_path}]") xlabel = label_x ylabel = label_y X_for_cal = build_design_matrix(T1000, norder) ycal = X_for_cal @ beta fit.to_excel(cfg.output_fitting_path, [xlabel, ylabel, "", "1000/T (K^-1)", "log10(P)", "log10(P)(cal)", "", "1000/T (K^-1)", "log10(P)(cal)", "sigma(param)", 'sigma(param&resid)', 'sigma(param&noise)'], [xX, yY, [], T1000, log10P, ycal, [], xcal, bands['y_mean'], bands['sigma_param'], bands['sigma_pred'], bands['sigma_combined']], template = cfg.xlsm_template) """ fit.to_excel(cfg.output_fitting_path, [xlabel, ylabel, 'T(K)', 'P', '1000/T (K^-1)', 'log10(P)', "log10(P)(cal)", "", "T (K)", "P(cal)", "sigma(param)", 'sigma(param&resid)', 'sigma(param&noise)'], [xX, yY, T, P, T1000, log10P, ycal, [], xcal, bands['y_mean'], bands['sigma_param'], bands['sigma_pred'], bands['sigma_combined']], template = cfg.xlsm_template) """ """ print(f"Save to [{cfg.outfile}]") fit_obj.to_excel(cfg.outfile, [label_x, label_y, 'T(K)', 'P', '1000/T (K^-1)', 'log10(P)', 'log10(P)(cal)', '', 'T (K)', '1000/T (K^-1)', 'P(cal)', 'log10(P)(cal)'], [xX, yY, T, P, T1000, log10P, list(X @ beta), [], T_plot, x_plot, P_plot, list(ycal)]) """ # Ea plot: 数値微分で再計算 diff_plot = np.gradient(bands['y_mean'], xcal) Ea_plot = [-d * kB / e * 1000.0 * log(10.0) for d in diff_plot] fig, axes = plt.subplots(2, 3, figsize=cfg.figsize) plot_event = tkPlotEvent(plt) axes = axes.flatten() for ax in axes: ax.tick_params(labelsize=cfg.fontsize) # (0) X-Y axes[0].plot(xX, yY, linestyle='', marker='o', markerfacecolor='black', markersize=5.0) axes[0].set_xlabel(label_x, fontsize=cfg.fontsize) axes[0].set_ylabel(label_y, fontsize=cfg.fontsize) # (1) T-P axes[1].plot(T, P, linestyle='', marker='o', markerfacecolor='black', markersize=5.0) # axes[1].plot(T_plot, P_plot, linestyle='-', color='red', linewidth=0.5) axes[1].set_xlabel('$T$ (K)', fontsize=cfg.fontsize) axes[1].set_ylabel('$P$', fontsize=cfg.fontsize) # (2) Arrhenius plot axes[2].plot(x_fit, y_fit, 'o', label='data', color = 'black', markersize=1.5) axes[2].plot(xcal, bands['y_mean'], label='fit', linewidth = 0.5, color='red') if cfg.plot_sigma_param: axes[2].fill_between(xcal, bands['y_mean'] - bands['sigma_param'], bands['y_mean'] + bands['sigma_param'], color='#0000cc', alpha=0.5, label='±σ(param)') if cfg.plot_sigma_pred: axes[2].fill_between(xcal, bands['y_mean'] - bands['sigma_pred'], bands['y_mean'] + bands['sigma_pred'], color='#ddddFF', alpha=0.5, label='±σ(param&resid)') if cfg.plot_sigma_combined: axes[2].fill_between(xcal, bands['y_mean'] - bands['sigma_combined'], bands['y_mean'] + bands['sigma_combined'], color='purple', alpha=0.5, label='±σ(param&noise)') axes[2].set_xlabel('$1000/T$ (K$^{-1}$)', fontsize=cfg.fontsize) axes[2].set_ylabel(r'$\log_{10}(P)$', fontsize=cfg.fontsize) axes[2].legend(fontsize=cfg.fontsize_legend) axes[3].plot(xcal, Ea_plot, label='Ea (eV)', linestyle='-', linewidth=0.5, color='black') axes[3].set_xlabel('$1000/T$ (K$^{-1}$)', fontsize=cfg.fontsize) axes[3].set_ylabel('$E_a$ (eV)', fontsize=cfg.fontsize) axes[3].legend(fontsize=cfg.fontsize_legend) minEa = min(Ea_plot) maxEa = max(Ea_plot) if abs(maxEa - minEa) < 1.0e-6: minEa -= 1.0e-3 maxEa += 1.0e-3 axes[3].set_ylim([minEa, maxEa]) if axes[4]: idxs = np.arange(len(beta)) axes[4].errorbar(idxs, beta, yerr=beta_std, fmt='o', capsize=3, label='coeff ±1σ') axes[4].set_xlabel('$i$', fontsize=cfg.fontsize) axes[4].set_ylabel('$c_i$', fontsize=cfg.fontsize) axes[4].tick_params(labelsize=cfg.fontsize) axes[4].legend(fontsize=cfg.fontsize_legend) # plot_event 登録 ''' all_data = datalist plot_event.add_data({"label": "X-Y plot", "plot_type": "2D", "axis": axes[0], "data": None, "xlist": all_data, "xlabels": labels}) plot_event.add_data({"label": "T-P plot", "plot_type": "2D", "axis": axes[1], "data": None, "xlist": all_data, "xlabels": labels}) plot_event.add_data({"label": "Arrhenius plot", "plot_type": "2D", "axis": axes[2], "data": None, "xlist": all_data, "xlabels": labels}) plot_event.add_data({"label": "Ea", "plot_type": "2D", "axis": axes[3], "data": None}) plot_event.register_event(fig, event="button_press_event", callback=lambda event: plot_event.onclick(event)) ''' plt.tight_layout() plt.pause(0.001) app.terminate("", usage=None, pause=cfg.pause)
[ドキュメント] def main(): """ スクリプトのエントリポイント。 初期化処理を行い、メインロジックを実行する。 :returns: None. """ app, cfg, parser = initialize() execute(app)
if __name__ == "__main__": main()