#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
一ダイオードモデル(SDM)を用いた太陽電池IV特性のフィッティングツール。
このスクリプトは、測定された電流-電圧(IV)特性データに対し、
一ダイオードモデルに基づいてフィッティングを行い、モデルパラメータを抽出します。
初期パラメータの推定、最適化、結果の保存、およびIV曲線のプロット機能を提供します。
関連リンク:
:doc:`pvfit_usage`
"""
import argparse
import csv
import math
import os
import sys
import traceback
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize, brentq
# --- 定数 ---
KB = 1.380649e-23
E_CHARGE = 1.602176634e-19
PARAM_NAMES = ["I0", "ndiode", "IPV", "Rs", "Rsh"]
EPS_I = 1.0e-15
EPS_R = 1.0e-6
# --- データ入出力 ---
[ドキュメント]
def read_data(infile, xmin=None, xmax=None):
"""
入力CSVファイルからIV特性データを読み込み、電圧(V)の昇順でソートして返します。
データはCSVファイルから読み込まれ、"DataValue"セクションに続く数値がIVデータとして解釈されます。
オプションで電圧範囲を指定してデータをフィルタリングできます。
データが存在しない場合や無効なデータが多い場合はエラーを発生させます。
:param infile: 読み込むCSVファイルのパス。
:type infile: str
:param xmin: プロットおよびフィッティングに使用する電圧範囲の最小値 (V)。Noneの場合、制限なし。
:type xmin: float or None
:param xmax: プロットおよびフィッティングに使用する電圧範囲の最大値 (V)。Noneの場合、制限なし。
:type xmax: float or None
:returns:
- V (np.ndarray): 読み込まれた電圧データ(ソート済み)。
- I (np.ndarray): 読み込まれた電流データ(ソート済み)。
- inf (dict): ファイル名と記録時間を含む情報辞書。
:rtype: tuple[np.ndarray, np.ndarray, dict]
:raises ValueError: 有効なデータがファイル内に見つからない場合。
"""
data_started = False
raw_x, raw_y = [], []
inf = {"RecordTime": "Unknown", "FileName": infile}
with open(infile, "r", encoding="utf-8-sig", newline="") as f:
reader = csv.reader(f)
for row in reader:
if not row: continue
cols = [c.strip() for c in row]
if len(cols) < 2: continue
if cols[0] == "MetaData" and cols[1] == "TestRecord.RecordTime":
if len(cols) >= 3: inf["RecordTime"] = cols[2]
if cols[0] == "DataName":
data_started = True
continue
if data_started and cols[0] == "DataValue":
try:
raw_x.append(float(cols[1]))
raw_y.append(float(cols[2]))
except (ValueError, IndexError): continue
if not raw_x: raise ValueError(f"No valid data found in {infile}")
# 配列化
x_arr, y_arr = np.array(raw_x), np.array(raw_y)
# --- 重要: 電圧 V でソートを行う ---
# Double Sweep(往復測定)データでも fill_between が正しく動くようにするため
sort_idx = np.argsort(x_arr)
x_arr = x_arr[sort_idx]
y_arr = y_arr[sort_idx]
# ---------------------------------
print(f"File Name: {inf['FileName']}")
print(f"Record Time: {inf['RecordTime']}")
print(f"Voltage range: {x_arr.min():.4f} to {x_arr.max():.4f} V")
mask = np.ones(len(x_arr), dtype=bool)
if xmin is not None: mask &= (x_arr >= xmin)
if xmax is not None: mask &= (x_arr <= xmax)
return x_arr[mask], y_arr[mask], inf
[ドキュメント]
def load_param_csv(csv_path):
"""
指定されたCSVファイルからモデルパラメータと固定するパラメータのセットを読み込みます。
CSVファイルには、"varname", "value", "optid" (0:固定, 1:自由) の列が必要です。
`PARAM_NAMES` に含まれるパラメータのみがフィッティングの対象として扱われ、
`optid` が0のパラメータは固定としてマークされます。
:param csv_path: パラメータを読み込むCSVファイルのパス。
:type csv_path: str
:returns:
- params (dict): 読み込まれたパラメータ名とその値の辞書。
- fix_set (set): 固定としてマークされたパラメータ名のセット。
:rtype: tuple[dict, set]
"""
params, fix_set = {}, set()
if os.path.exists(csv_path):
with open(csv_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["varname"]
try:
val = float(row["value"])
if name in PARAM_NAMES:
params[name] = val
if int(row.get("optid", 1)) == 0: fix_set.add(name)
else: params[name] = val # フィッティングパラメータ以外の値も読み込む
except (ValueError, TypeError): continue
return params, fix_set
[ドキュメント]
def save_param_csv(csv_path, params, fix_set, errors=None, rss=None):
"""
モデルパラメータ、固定設定、推定誤差、およびRSSをCSVファイルに保存します。
結果として得られたフィッティングパラメータ、その固定/自由設定、
そして推定された誤差(利用可能な場合)を整理してCSV形式で出力します。
推定された開回路電圧 (VOC_est) と短絡電流 (ISC_est) も含められます。
:param csv_path: パラメータを保存するCSVファイルのパス。
:type csv_path: str
:param params: 保存するパラメータ名とその値の辞書。
:type params: dict
:param fix_set: 固定されたパラメータ名のセット。
:type fix_set: set
:param errors: 各パラメータの推定誤差の辞書。デフォルトはNone。
:type errors: dict or None
:param rss: 残差平方和 (Residual Sum of Squares) の値。デフォルトはNone。
:type rss: float or None
:returns: なし
:rtype: None
"""
if errors is None: errors = {}
with open(csv_path, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["varname", "value", "optid", "error"])
for name in PARAM_NAMES:
err = errors.get(name, 0) if errors else 0
writer.writerow([name, params[name], 0 if name in fix_set else 1, err])
writer.writerow(["VOC_est", params.get("VOC", 0), 0, 0])
writer.writerow(["ISC_est", params.get("ISC", 0), 0, 0])
if rss is not None: writer.writerow(["RSS_logy", rss, 0, 0])
# --- 物理モデル・計算 ---
[ドキュメント]
def solve_root_poly(x, y, target_y=0, order=3):
"""
多項式近似を用いて、与えられたデータセット(x, y)からyが特定の`target_y`になるxを推定します。
与えられたIVデータに対して、n次多項式で近似し、`y - target_y = 0`となる根を探します。
複数の実数根が見つかった場合、データの平均x値に最も近い根を選択します。
実数根が見つからない場合は、線形補間を使用します。
:param x: x軸のデータポイント(電圧)。
:type x: np.ndarray
:param y: y軸のデータポイント(電流)。
:type y: np.ndarray
:param target_y: 求めるyの値。デフォルトは0(開回路電圧VOCの推定に相当)。
:type target_y: float
:param order: 多項式近似の次数。デフォルトは3。
:type order: int
:returns: `target_y` に対応する推定されたx値。
:rtype: float
"""
if len(x) < order + 1: order = max(1, len(x) - 1)
z = np.polyfit(x, y - target_y, order)
roots = np.roots(z)
real_roots = roots[np.isreal(roots)].real
if len(real_roots) == 0: return np.interp(target_y, y, x)
return real_roots[np.argmin(np.abs(real_roots - np.mean(x)))]
[ドキュメント]
def model(V, I0, ndiode, IPV, Rs, Rsh, temperature=300.0):
"""
一ダイオードモデル(Single-Diode Model, SDM)に基づいてIV特性の電流を計算します。
このモデルは以下の数式で表されます:
I = IPV - I0 * (exp((V + I*Rs) / (ndiode * k * T / q)) - 1) - (V + I*Rs) / Rsh
ここで、Iは全電流、IPVは光電流、I0は飽和電流、ndiodeはダイオード理想因子、
Rsは直列抵抗、Rshはシャント抵抗、kはボルツマン定数、Tは温度、qは素電荷です。
この式はIについて解析的に解けないため、`scipy.optimize.brentq` を用いて数値的に解を求めます。
:param V: 電圧のNumpy配列または単一値。
:type V: np.ndarray or float
:param I0: 飽和電流 (A)。
:type I0: float
:param ndiode: ダイオード理想因子。
:type ndiode: float
:param IPV: 光電流 (A)。
:type IPV: float
:param Rs: 直列抵抗 (Ω)。
:type Rs: float
:param Rsh: シャント抵抗 (Ω)。
:type Rsh: float
:param temperature: セル温度 (K)。デフォルトは300.0。
:type temperature: float
:returns: 入力電圧Vに対応する計算された電流 (A) のNumpy配列。
:rtype: np.ndarray
"""
V = np.atleast_1d(V)
vt = ndiode * KB * temperature / E_CHARGE
def resid(Idark, v_val):
vd = v_val - Rs * Idark
arg = np.clip(vd / vt, -700, 700) # オーバーフロー防止
return I0 * (np.exp(arg) - 1.0) + vd / Rsh - Idark
results = []
for v_val in V:
try:
# 探索範囲を適切に設定
limit = abs(v_val / max(Rsh, EPS_R)) + abs(I0) * 1e5 + abs(IPV) + 1.0
id_val = brentq(resid, -limit, limit, args=(v_val,))
results.append(id_val - IPV)
except (ValueError, RuntimeError): # brentqが根を見つけられない場合
# フォールバックとして、Rsが非常に大きい場合の近似を返す
# または、単純な抵抗モデルに近い値を返す
results.append(v_val / max(Rsh, EPS_R) - IPV)
return np.array(results)
[ドキュメント]
def objective(p_free, V, I_meas, temp, base_params, fix_set):
"""
最適化アルゴリズムのための目的関数を計算します。
この関数は、自由パラメータ`p_free`を更新し、それらの値を用いてモデルを呼び出し、
測定された電流と計算された電流の対数絶対値の差の二乗和(残差平方和)を返します。
`ndiode`を除くパラメータは対数スケールで最適化されます。
:param p_free: 最適化中の自由パラメータの配列。
:type p_free: np.ndarray
:param V: 測定された電圧データ。
:type V: np.ndarray
:param I_meas: 測定された電流データ。
:type I_meas: np.ndarray
:param temp: セル温度 (K)。
:type temp: float
:param base_params: 固定パラメータを含む、全てのパラメータの初期値または現在の値の辞書。
:type base_params: dict
:param fix_set: 固定されたパラメータ名のセット。
:type fix_set: set
:returns: 測定値とモデル予測値の対数絶対値の差の二乗和。
:rtype: float
"""
curr = dict(base_params); idx = 0
for name in PARAM_NAMES:
if name not in fix_set:
val = p_free[idx]
curr[name] = 10**val if name != "ndiode" else val # ndiode以外はlog10スケールで最適化
idx += 1
I_calc = model(V, **{k: curr[k] for k in PARAM_NAMES}, temperature=temp)
# 微小値EPS_Iを加えてログ計算でのゼロ除算を防ぐ
return np.sum((np.log10(np.abs(I_meas) + EPS_I) - np.log10(np.abs(I_calc) + EPS_I))**2)
# --- 誤差推定関連 ---
[ドキュメント]
def get_jacobian(p_free, V, temp, base_params, fix_set):
"""
モデルの対数電流に対するヤコビ行列を数値的に計算します。
ヤコビ行列は、各自由パラメータに関するモデル出力(対数電流)の偏微分を近似したものです。
有限差分法を用いて計算され、誤差推定に使用されます。
:param p_free: 現在の自由パラメータの配列。
:type p_free: np.ndarray
:param V: 測定された電圧データ。
:type V: np.ndarray
:param temp: セル温度 (K)。
:type temp: float
:param base_params: 固定パラメータを含む、全てのパラメータの初期値または現在の値の辞書。
:type base_params: dict
:param fix_set: 固定されたパラメータ名のセット。
:type fix_set: set
:returns: モデルの対数電流に対するヤコビ行列。
:rtype: np.ndarray
"""
eps = 1e-5 # 微小な摂動値
n_params, n_points = len(p_free), len(V)
J = np.zeros((n_points, n_params))
def get_log_model(p):
"""与えられたパラメータでモデル電流を計算し、その対数絶対値を返すヘルパー関数"""
curr = dict(base_params); idx = 0
for name in PARAM_NAMES:
if name not in fix_set:
val = p[idx]
curr[name] = 10**val if name != "ndiode" else val
idx += 1
I_c = model(V, **{k: curr[k] for k in PARAM_NAMES}, temperature=temp)
return np.log10(np.abs(I_c) + EPS_I)
f0 = get_log_model(p_free)
for i in range(n_params):
p_eps = np.copy(p_free); p_eps[i] += eps # 各パラメータを微小に摂動
J[:, i] = (get_log_model(p_eps) - f0) / eps # 有限差分で偏微分を計算
return J
[ドキュメント]
def estimate_errors(res, V, I_meas, temp, base_params, fix_set):
"""
非線形最小二乗法で得られた結果に基づき、パラメータの誤差とモデル曲線の標準偏差を推定します。
フィッティング結果(`res`)とヤコビ行列を用いて、線形近似に基づいた共分散行列を計算します。
この共分散行列から、各パラメータの標準誤差と、モデル曲線自体の信頼性区間を推定します。
`np.linalg.pinv` を使用することで、数値的に不安定な場合でも計算を継続します。
:param res: `scipy.optimize.minimize` の結果オブジェクト。
:type res: scipy.optimize.OptimizeResult
:param V: 測定された電圧データ。
:type V: np.ndarray
:param I_meas: 測定された電流データ。
:type I_meas: np.ndarray
:param temp: セル温度 (K)。
:type temp: float
:param base_params: 固定パラメータを含む、全てのパラメータの初期値または現在の値の辞書。
:type base_params: dict
:param fix_set: 固定されたパラメータ名のセット。
:type fix_set: set
:returns:
- errors_dict (dict): 各自由パラメータの推定誤差の辞書。
- sigma_log_I (np.ndarray): モデル曲線の対数スケールでの標準偏差。
:rtype: tuple[dict, np.ndarray]
"""
p_opt = res.x
n, p = len(I_meas), len(p_opt)
if n <= p: # データポイント数がパラメータ数以下の場合、誤差推定は不可能
return {}, np.zeros(len(V))
J = get_jacobian(p_opt, V, temp, base_params, fix_set)
sigma2_hat = res.fun / (n - p) # 残差の分散を推定
try:
# inv ではなく pinv (擬似逆行列) を使うことで、数値的に不安定な場合でも計算を継続
# J.T @ J は Hessian の近似(Gauss-Newton法における)
cov = sigma2_hat * np.linalg.pinv(J.T @ J)
p_errors_log = np.sqrt(np.maximum(np.diag(cov), 0)) # パラメータの対数スケールでの標準誤差
sigma_log_I = np.sqrt(np.maximum(np.diag(J @ cov @ J.T), 0)) # モデル曲線の対数スケールでの標準偏差
errors_dict, idx = {}, 0
for name in PARAM_NAMES:
if name not in fix_set:
if name == "ndiode":
errors_dict[name] = p_errors_log[idx] # ndiodeは線形スケール
else:
# log10スケールの誤差から元のスケールでの誤差に変換
# d(10^x)/dx = 10^x * ln(10)
errors_dict[name] = (10**p_opt[idx]) * np.log(10) * p_errors_log[idx]
idx += 1
return errors_dict, sigma_log_I
except Exception as e:
print(f"Warning: Error estimation failed ({e})")
return {}, np.zeros(len(V))
# --- 描画ロジック ---
[ドキュメント]
def plot_iv(V, I_meas, I_final, axes, sigma_log=None, label="Model", title=""):
"""
測定IV特性とモデルIV特性を線形スケールおよび対数スケールでプロットします。
線形スケールのプロットと対数スケールのプロットの2つのサブプロット(axes)に、
測定データ、フィッティングされたモデル曲線、および(指定された場合)信頼性区間を描画します。
:param V: 電圧データ。
:type V: np.ndarray
:param I_meas: 測定された電流データ。
:type I_meas: np.ndarray
:param I_final: モデルによって計算された最終電流データ。
:type I_final: np.ndarray
:param axes: プロットに使用するmatplotlibの2つのサブプロット軸 (ax_lin, ax_log)。
:type axes: tuple[matplotlib.axes.Axes, matplotlib.axes.Axes]
:param sigma_log: モデル曲線の対数スケールでの標準偏差。提供された場合、信頼性区間が描画されます。
:type sigma_log: np.ndarray or None
:param label: モデル曲線の凡例ラベル。デフォルトは"Model"。
:type label: str
:param title: プロットのタイトル。デフォルトは空文字列。
:type title: str
:returns: なし
:rtype: None
"""
ax_lin, ax_log = axes
ax_lin.plot(V, I_meas, "o", color="blue", markersize=4, alpha=0.3, label="Data")
ax_lin.plot(V, I_final, "-", color="orange", lw=2, label=label)
# 信頼性区間の塗りつぶし (水色領域)
if sigma_log is not None:
upper = 10**(np.log10(np.abs(I_final) + EPS_I) + sigma_log)
lower = 10**(np.log10(np.abs(I_final) + EPS_I) - sigma_log)
# np.sign(I_final) を乗算することで、電流の符号を維持しつつ信頼性区間をプロット
ax_lin.fill_between(V, np.sign(I_final)*lower, np.sign(I_final)*upper, color='lightblue', alpha=0.5)
# 対数プロットは絶対値でプロットされるため、符号は不要
ax_log.fill_between(V, lower, upper, color='lightblue', alpha=0.5)
ax_lin.axhline(0, color='k', lw=0.5); ax_lin.axvline(0, color='k', lw=0.5)
ax_lin.set_title(title); ax_lin.grid(True); ax_lin.legend()
# 対数プロットは絶対値で描画されるため、EPS_Iを加えてゼロを防ぐ
ax_log.semilogy(V, np.abs(I_meas) + EPS_I, "o", color="blue", markersize=4, alpha=0.3)
ax_log.semilogy(V, np.abs(I_final) + EPS_I, "-", color="orange", lw=2)
ax_log.grid(True, which="both")
# --- モード別実行 ---
[ドキュメント]
def run_fit_mode(V, I_meas, init_params, args, csv_path, fix_set):
"""
フィッティングモードを実行し、IV特性データに一ダイオードモデルを適合させます。
初期パラメータから最適化を開始し、`scipy.optimize.minimize` を用いて目的関数を最小化します。
フィッティングの進行状況は、指定された間隔でプロットとコンソール出力で表示されます。
最終的なフィッティングパラメータ、その誤差、および推定されたVOCとISCをCSVファイルに保存します。
:param V: 測定された電圧データ。
:type V: np.ndarray
:param I_meas: 測定された電流データ。
:type I_meas: np.ndarray
:param init_params: フィッティングの初期パラメータの辞書。
:type init_params: dict
:param args: コマンドライン引数を格納したargparse.Namespaceオブジェクト。
:type args: argparse.Namespace
:param csv_path: 結果のパラメータを保存するCSVファイルのパス。
:type csv_path: str
:param fix_set: 固定するパラメータ名のセット。
:type fix_set: set
:returns:
- fit_params (dict): フィッティング後の最適化されたパラメータの辞書。
- sigma_log_curve (np.ndarray): 最適化されたモデル曲線の対数スケールでの標準偏差。
:rtype: tuple[dict, np.ndarray]
"""
# ndiode以外はlog10スケールで最適化するため変換
p0_free = [init_params[p] if p == "ndiode" else np.log10(max(init_params[p], EPS_R if "R" in p else EPS_I))
for p in PARAM_NAMES if p not in fix_set]
plt.ion(); fig, axes = plt.subplots(1, 2, figsize=(11, 5)); iters = [0]
def callback(xk):
"""最適化中の進捗をプロット・表示するためのコールバック関数"""
iters[0] += 1
if iters[0] % args.ninterval_plot == 0:
curr = dict(init_params); idx = 0
for name in PARAM_NAMES:
if name not in fix_set:
curr[name] = 10**xk[idx] if name != "ndiode" else xk[idx]; idx += 1
I_c = model(V, **{k: curr[k] for k in PARAM_NAMES}, temperature=args.temperature)
rss = objective(xk, V, I_meas, args.temperature, init_params, fix_set)
for ax in axes: ax.cla() # 既存のプロットをクリア
plot_iv(V, I_meas, I_c, axes, label=f"Iter {iters[0]}", title=f"RSS: {rss:.2e}")
plt.pause(0.01) # 短時間一時停止してGUIを更新
if iters[0] % args.ninterval_print == 0:
p_str = " | ".join([f"{n}: {curr[n]:.2e}" + ("(F)" if n in fix_set else "") for n in PARAM_NAMES])
print(f"Iter {iters[0]:4d} | RSS: {rss:.4e} | {p_str}")
res = minimize(objective, p0_free, args=(V, I_meas, args.temperature, init_params, fix_set),
method=args.method, callback=callback)
plt.ioff(); plt.close(fig) # インタラクティブモードを終了し、フィッティング中のプロットウィンドウを閉じる
fit_params = dict(init_params); idx = 0
for name in PARAM_NAMES:
if name not in fix_set:
val = res.x[idx]; fit_params[name] = 10**val if name != "ndiode" else val; idx += 1
errors, sigma_log_curve = estimate_errors(res, V, I_meas, args.temperature, init_params, fix_set)
# 推定VOCとISCを計算
# ISCはV=0での電流値
fit_params["ISC"] = model(0, **{k: fit_params[k] for k in PARAM_NAMES}, temperature=args.temperature)[0]
# VOCはI=0となる電圧値(多項式根探索で推定)
v_f = np.linspace(min(V.min(), 0), max(V.max(), 1.2), 2000) # VOC推定のための広範囲電圧データ
fit_params["VOC"] = solve_root_poly(v_f, model(v_f, **{k: fit_params[k] for k in PARAM_NAMES}, temperature=args.temperature))
save_param_csv(csv_path, fit_params, fix_set, errors=errors, rss=res.fun)
print(f"\nFinal Results (RSS: {res.fun:.4e})")
for p in PARAM_NAMES:
err_str = f" +/- {errors[p]:.2e}" if p in errors else " (Fixed)"
print(f" {p:8s}: {fit_params[p]:.6e}{err_str}")
return fit_params, sigma_log_curve
[ドキュメント]
def get_initial_params(V, I_meas, args, csv_path):
"""
フィッティングのための初期パラメータを生成または既存のCSVファイルから読み込みます。
`--mode init` が指定されている場合、IV曲線の特徴から初期パラメータを推定します。
それ以外の場合、既存のパラメータCSVファイルから読み込みます。
コマンドライン引数で明示的に指定されたパラメータは、常に読み込み値や推定値を上書きします。
:param V: 測定された電圧データ。
:type V: np.ndarray
:param I_meas: 測定された電流データ。
:type I_meas: np.ndarray
:param args: コマンドライン引数を格納したargparse.Namespaceオブジェクト。
:type args: argparse.Namespace
:param csv_path: 初期パラメータを読み書きするCSVファイルのパス。
:type csv_path: str
:returns:
- params (dict): 初期パラメータ名とその値の辞書。
- csv_fix (set): CSVファイルで固定として指定されたパラメータ名のセット。
:rtype: tuple[dict, set]
"""
if args.mode == "init":
# 初期パラメータの推定ロジック
# V=0近傍の電流からIPVを推定
uV, idxs = np.unique(V, return_index=True); uI = I_meas[idxs] # 重複電圧を除去
idx_v0 = np.argmin(np.abs(uV)) # V=0に最も近いインデックス
# V=0近傍のIV曲線から、直線近似/多項式近似でISCを推定(IPVに相当)
ipv_est = max(-np.polyval(np.polyfit(uV[max(0,idx_v0-3):idx_v0+4], uI[max(0,idx_v0-3):idx_v0+4], 3), 0), EPS_I)
# Vが負の領域の傾きからRshを推定
rsh_est = 1.0 / max(np.gradient(uI, uV)[uV < 0][0], 1e-12) if np.any(uV < 0) else 1e6
params = {"I0": 1e-10, "ndiode": 1.5, "IPV": ipv_est, "Rs": 10.0, "Rsh": rsh_est, "VOC": 0.5, "ISC": -ipv_est}
csv_fix = set()
else:
params, csv_fix = load_param_csv(csv_path)
if not params: # CSVファイルが存在しない、または空の場合、initモードで初期化
print(f"Warning: Parameter CSV '{csv_path}' not found or empty. Generating initial parameters.")
temp_args = argparse.Namespace(**{**vars(args), 'mode': 'init'}) # 一時的にinitモードに設定
params, _ = get_initial_params(V, I_meas, temp_args, csv_path)
# コマンドライン引数で指定されたパラメータで上書き
for p in PARAM_NAMES:
val = getattr(args, p);
if val is not None: params[p] = val
return params, csv_fix
# --- メイン ---
[ドキュメント]
def main():
"""
スクリプトの主要な実行フローを制御します。
コマンドライン引数を解析し、指定されたモード(init, fit, sim)に基づいて、
データの読み込み、初期パラメータの取得、フィッティング実行、シミュレーション、
そして結果の保存とプロットを行います。
"""
parser = argparse.ArgumentParser(description="一ダイオードモデルによる太陽電池IV特性フィッティング")
parser.add_argument("--mode", choices=["init", "fit", "sim"], default="fit",
help="実行モード: init (初期パラメータ生成), fit (フィッティング), sim (シミュレーション)")
parser.add_argument("--method", default="Nelder-Mead",
help="scipy.optimize.minimizeで使う最適化アルゴリズム (例: Nelder-Mead, L-BFGS-B)")
parser.add_argument("--infile", default="input.csv",
help="入力IV特性CSVファイルのパス")
parser.add_argument("--temperature", type=float, default=300.0,
help="計算に使用するセル温度 (K)")
parser.add_argument("--xmin", type=float,
help="プロットおよびフィッティングに使用する電圧範囲の最小値 (V)")
parser.add_argument("--xmax", type=float,
help="プロットおよびフィッティングに使用する電圧範囲の最大値 (V)")
for p in PARAM_NAMES: # 各モデルパラメータに対する引数を動的に追加
parser.add_argument(f"--{p}", type=float, help=f"パラメータ {p} の値を指定 (既存設定を上書き)")
parser.add_argument("--fix", nargs="*", default=[],
help="フィッティング中に固定するパラメータの名前 (例: I0 ndiode)")
parser.add_argument("--nlsq_points", type=int, default=7, # 現在未使用だが、将来の拡張用
help="非線形最小二乗近似のためのデータポイント数")
parser.add_argument("--ninterval_print", type=int, default=10,
help="フィッティング中のコンソール出力間隔 (イテレーション数)")
parser.add_argument("--ninterval_plot", type=int, default=10,
help="フィッティング中のプロット更新間隔 (イテレーション数)")
args = parser.parse_args()
V, I_meas, inf = read_data(args.infile, xmin=args.xmin, xmax=args.xmax)
csv_path = Path(args.infile).stem + "-parameters.csv"
params, csv_fix = get_initial_params(V, I_meas, args, csv_path)
fix_set = set(args.fix) | csv_fix # コマンドライン引数とCSV設定の両方で固定されたパラメータを結合
sigma_log = None
if args.mode == "init":
save_param_csv(csv_path, params, fix_set); print("\n--- Initial Parameters Saved ---")
elif args.mode == "fit":
params, sigma_log = run_fit_mode(V, I_meas, params, args, csv_path, fix_set)
elif args.mode == "sim":
print("\n--- Simulation Mode ---")
for p in PARAM_NAMES: print(f" {p:8s}: {params[p]:.6e}")
# 最終結果のプロット
I_final = model(V, **{k: params[k] for k in PARAM_NAMES}, temperature=args.temperature)
fig, axes = plt.subplots(1, 2, figsize=(11, 5))
plot_iv(V, I_meas, I_final, axes, sigma_log=sigma_log, label=f"Final ({args.mode})", title=inf["FileName"])
plt.tight_layout(); plt.show()
if __name__ == "__main__":
try: main()
except Exception: traceback.print_exc(); sys.exit(1)