钙钛矿太阳能电池反向工程 — 原理与步骤分析
整理时间: 2026-06-04 | 实验时间: 2026-03-09 ~ 04-09
核心工具: SIMsalabim v2.1(漂移-扩散模拟器)、Python
目录
一、J-V 曲线与太阳能电池基础物理
1.1 原理
单二极管等效电路
太阳能电池在光照下的电流-电压关系可用单二极管模型描述:
Rs
┌───/\/\/\───┬─────────┐
│ │ │
│ ┌───D───┐ │ │
│ │ │ │ │
I →├──┤ Jph ├─┤ Rsh ├─→ V
│ └───────┘ │ │
│ │ │
└────────────┴─────────┘
核心方程:
J(V) = Jph - J0[exp(q(V + J·Rs)/(n·kT)) - 1] - (V + J·Rs)/Rsh
| 符号 | 含义 | 物理来源 |
|---|---|---|
| Jph | 光生电流密度 | 光子吸收产生电子-空穴对 |
| J0 | 反向饱和电流密度 | 热激发载流子的复合 |
| n | 理想因子 | 复合机制判断(n=1:带-带, n≈2:SRH) |
| Rs | 串联电阻 | 体电阻 + 接触电阻 + 电极电阻 |
| Rsh | 并联电阻 | 漏电流路径、针孔、边缘分流 |
| q | 元电荷 | 1.602×10⁻¹⁹ C |
| kT/q | 热电压 | ≈0.0257 V @ 298K |
四个关键性能参数
| 参数 | 定义 | 物理意义 |
|---|---|---|
| Jsc | V=0 时的电流密度 | 光子吸收→载流子收集的总效率 |
| Voc | J=0 时的电压 | 准费米能级劈裂上限,受复合强度限制 |
| FF | Pmax/(Jsc×Voc) | Rs/Rsh/n 对输出功率矩形的约束程度 |
| PCE | Jsc×Voc×FF/Pin | 最终转换效率(Pin=100mW/cm²) |
1.2 代码实现
Level 1 数据提取(04_3_levels_analysis.py):
def analyze_level1(curve):
jsc = curve['jsc_measured'] # mA/cm²
voc = curve['voc_measured'] # V
ff = curve['ff_measured'] # 无量纲
pce = jsc * voc * ff / P_in * 100 # P_in = 100 mW/cm²
return {'Jsc': jsc, 'Voc': voc, 'FF': ff, 'PCE': pce}FAPI+ALD 工具中按基底和像素分组提取(fapi_ald_reverse_engineering_tool_v2.py):
devices = []
grouped = df.groupby(['substr.', 'pixel'])
for (substr, pixel), group in grouped:
best = group.loc[group['Pmax'].idxmax()] # 取 Pmax 最优扫描方向
devices.append({
'device_id': f"S{int(substr)}_P{int(pixel)}",
'Jsc': float(best['Jsc (mA/cm2)']),
'Voc': float(best['Voc (V)']),
'FF': float(best['FF']),
'PCE': float(best['Pmax']),
'V': list(v_values), 'J': list(j_values)
})二、三级反向工程分析体系
2.1 原理
三级反向工程从测量数据逐层深入推导物理机制:
Level 1 (测量参数) PCE / Jsc / Voc / FF
↓
Level 2 (电路参数) n / Rs / Rsh / Jph / J0 — 单二极管等效模型拟合
↓
Level 3 (物理机制) 复合类型 / 损失分解 / 器件质量评估
Level 2 → Level 3 的物理映射规则
| 电路参数 | 物理含义 | 诊断规则 |
|---|---|---|
| n < 1.3 | 扩散/双分子复合主导 | 低复合,优质器件 |
| 1.3 ≤ n < 1.6 | SRH 体相复合 | 体相陷阱参与复合 |
| 1.6 ≤ n < 2.0 | SRH + 界面复合 | 界面缺陷显著 |
| n ≥ 2.0 | 高陷阱辅助复合 | 严重的非理想行为 |
| Rs < 2 Ω·cm² | 接触良好 | FF 损失可忽略 |
| Rs > 10 Ω·cm² | 串联电阻过大 | 严重 FF 损失 |
| Rsh > 5000 Ω·cm² | 无分流 | 并联路径可忽略 |
| Rsh < 200 Ω·cm² | 严重分流 | 漏电流主导 |
2.2 代码实现
Level 2:电路参数估算(简化版)
def analyze_level2(level1):
# 理想因子 n: 从 FF 偏离理想值(0.85)估计
ff_ideal, jsc, voc = 0.85, level1['Jsc'], level1['Voc']
n = 1.0 + (ff_ideal - level1['FF']) * 4
n = max(1.0, min(3.0, n))
# 串联电阻: 从 FF 损失估计
Rs = (ff_ideal - level1['FF']) * 15
# 并联电阻: 从 FF 与理想值偏差反算
Rsh = 2000 / max(0.1, (1 - level1['FF']/ff_ideal))
# 反向饱和电流: 从 Voc 反算(二极管方程反向推导)
Vt = k * T / q
J0 = jsc * np.exp(-voc / (n * Vt))
return {'n': n, 'Rs': Rs, 'Rsh': Rsh, 'Jph': jsc, 'J0': J0}注:以上为”原型”简化版。FAPI+ALD 工具 v2 使用
ImprovedFullDiodeModel进行完整牛顿迭代二极管拟合,R² ≥ 0.95 才算合格(详见fapi_ald_reverse_engineering_tool_v2.py第 101-220 行)。
Level 3:物理机制诊断
def analyze_level3(level1, level2):
n = level2['n']
# ========= 复合类型判定 =========
if n < 1.3:
recomb_type = "Diffusion/Bimolecular" # 低复合,优质
elif n < 1.6:
recomb_type = "SRH Bulk" # 体相陷阱复合
elif n < 2.0:
recomb_type = "SRH + Interface" # 界面复合参与
else:
recomb_type = "High Trap-assisted" # 严重陷阱辅助
# ========= 串联电阻质量 =========
Rs = level2['Rs']
if Rs < 2: rs_quality = "Excellent (Minimal FF loss)"
elif Rs < 5: rs_quality = "Good (Slight FF loss)"
elif Rs < 10: rs_quality = "Moderate (Noticeable FF loss)"
else: rs_quality = "Poor (Severe FF loss)"
# ========= 并联电阻质量 =========
Rsh = level2['Rsh']
if Rsh > 5000: rsh_quality = "Excellent (No shunting)"
elif Rsh > 1000: rsh_quality = "Good (Minor leakage)"
elif Rsh > 200: rsh_quality = "Moderate (Some shunt paths)"
else: rsh_quality = "Poor (Severe shunting)"
# ========= 损失分解(vs 理论极限) =========
voc_theory, jsc_theory = 1.3, 25 # 钙钛矿 Eg≈1.55eV 的理论极限
voc_loss = (1 - level1['Voc']/voc_theory) * 100
jsc_loss = (1 - level1['Jsc']/jsc_theory) * 100
return {
'recombination': {'type': recomb_type, 'n': n},
'series_resistance': {'value': Rs, 'quality': rs_quality},
'shunt_resistance': {'value': Rsh, 'quality': rsh_quality},
'losses': {'Voc_loss': voc_loss, 'Jsc_loss': jsc_loss}
}FAPI+ALD v2 的物理一致性自动验证
class QualityFlags:
GOOD = "GOOD"
WARNING = "WARNING"
FAILED = "FAILED"
def validate_physics(self, params):
issues = []
if params['Jph'] < params['Jsc_ext']:
issues.append("Jph < Jsc — 物理矛盾")
if params['n'] < 1.0:
issues.append("n < 1.0 — 非物理")
elif params['n'] > 2.5:
issues.append("n > 2.5 — 极高度非理想")
if params['Rs'] > 10:
issues.append("Rs > 10 — 串联问题严重")
if params['Rsh'] < 100:
issues.append("Rsh < 100 — 严重分流")
return len(issues) == 0, issues三、SIMsalabim 漂移-扩散模拟
3.1 原理
核心方程(三个耦合 PDE)
SIMsalabim 数值求解以下三个方程:
(1)泊松方程(电势分布):
d/dx(ε·dφ/dx) = -q(p - n + ND⁺ - NA⁻)
(2)电子连续性方程(传输 + 产生/复合):
∂n/∂t = (1/q)·dJn/dx + G - R
Jn = q·μn·n·E + q·Dn·dn/dx (Dn = μn·kT/q, Einstein 关系)
(3)空穴连续性方程(传输 + 产生/复合):
∂p/∂t = -(1/q)·dJp/dx + G - R
Jp = q·μp·p·E - q·Dp·dp/dx (Dp = μp·kT/q, Einstein 关系)
复合机制
| 机制 | 物理过程 | 关联参数 |
|---|---|---|
| SRH 体相复合 | 电子-空穴通过深能级陷阱非辐射复合 | Nt_bulk |
| 界面复合 | 电荷传输层界面处缺陷辅助复合 | Nt_int |
| 带-带辐射复合 | 导带电子直接与价带空穴复合发光 | B(辐射复合系数) |
| 俄歇复合 | 三粒子非辐射过程(高注入下重要) | — |
光学建模
SIMsalabim 使用传输矩阵法(TMM)计算各层的光吸收:
- 从
Data/目录加载各材料的 n(λ)/k(λ) 折射率数据(.txt文件) - 使用 AM1.5G 光谱(
Data/AM15G.txt)作为入射光源 - 计算各位置的光子吸收速率 G(x),以此驱动电子-空穴对产生
fitError 的计算
fitError = (1/N) × Σ|Jsim(Vi) - Jexp(Vi)|
SIMsalabim 运行结束后输出到 stdout:
fitError: 0.1721
fitError 越小 → 模拟越准确复现实验 J-V 曲线 → 物理参数越可信。
3.2 器件结构(本项目)
本项目中的钙钛矿太阳能电池为 4 层结构:
阴极 (W_L = 4.05 eV)
↓
L1: C60(电子传输层, 25 nm) — 不产生光生载流子
↓
L2: MAPI(钙钛矿活性层, 500 nm) — 主吸收层,含离子迁移
↓
L3: PTAA(空穴传输层, 10 nm) — 不产生光生载流子
↓
L4: 右接触层(重用 L2 配置)
↓
阳极 (W_R = 5.2 eV)
5 个待优化物理参数
| 参数 | 配置文件中的位置 | 物理含义 | 典型范围 |
|---|---|---|---|
| μn | L2_parameters.txt | 钙钛矿层电子迁移率 | 1e-6 ~ 5e-3 m²/V·s |
| μp | L2_parameters.txt | 钙钛矿层空穴迁移率 | 1e-7 ~ 5e-3 m²/V·s |
| Nt_bulk | L2_parameters.txt | 体相 SRH 陷阱密度 | 1e18 ~ 1e24 m⁻³ |
| Nt_int | L2_parameters.txt | 界面陷阱密度 | 1e10 ~ 1e16 m⁻² |
| R_shunt | simulation_setup.txt | 并联分流电阻 | 1e-4 ~ 1e4 Ω·cm² |
3.3 代码实现
SIMsalabim 的单次 eval 流程(exp5 中)
# ===== Step 1: 修改参数文件 =====
def set_params(p):
# L2_parameters.txt — 4 个层参数
l2 = os.path.join(WORK_DIR, 'L2_parameters.txt')
with open(l2, 'r+', encoding='utf-8') as f:
c = f.read()
# 正则替换:匹配 "param = 数字" 并替换为新区
c = re.sub(r'mu_n\s*=\s*[\d.Ee+-]+', f'mu_n = {p["mu_n"]:.6e}', c)
c = re.sub(r'mu_p\s*=\s*[\d.Ee+-]+', f'mu_p = {p["mu_p"]:.6e}', c)
c = re.sub(r'N_t_bulk\s*=\s*[\d.Ee+-]+', f'N_t_bulk = {p["Nt_bulk"]:.6e}', c)
c = re.sub(r'N_t_int\s*=\s*[\d.Ee+-]+', f'N_t_int = {p["Nt_int"]:.6e}', c)
f.seek(0); f.write(c); f.truncate()
# simulation_setup.txt — R_shunt
setup = os.path.join(WORK_DIR, 'simulation_setup.txt')
with open(setup, 'r+', encoding='utf-8') as f:
c = f.read()
c = re.sub(r'R_shunt\s*=\s*-?[\d.Ee+-]+', f'R_shunt = {p["R_shunt"]:.6e}', c)
f.seek(0); f.write(c); f.truncate()
# ===== Step 2: 准备实验 J-V 数据 =====
def prepare_exp_data():
df = pd.read_csv(os.path.join(KEY_CURVES_DIR, f'{STAGE}_curve.csv'))
lines = [" Vext Jext"] # SIMsalabim 要求的文件头
for _, row in df.iterrows():
# 电流取负号:实验数据 Jsc<0 (第四象限),SIMsalabim 用 Jsc>0 (第一象限)
lines.append(f" {row['Voltage(V)']:12.6E} {-row['Current(mA/cm2)']:12.6E}")
with open(os.path.join(WORK_DIR, 'JV_Exp.dat'), 'w') as f:
f.write('\n'.join(lines))
# ===== Step 3: 调用 simss.exe =====
def run_simss():
# 清除上次输出文件,避免误读
for f in ['JV.dat', 'scPars.dat', 'log.txt', 'Var.dat']:
fp = os.path.join(WORK_DIR, f)
if os.path.exists(fp): os.remove(fp)
# 调用 SIMsalabim,90 秒超时
r = subprocess.run(
[SIMSS_EXE, 'simulation_setup.txt'],
cwd=WORK_DIR,
capture_output=True, text=True,
timeout=90, encoding='utf-8', errors='replace'
)
# 检查是否崩溃
if r.returncode != 0:
for line in (r.stdout + r.stderr).split('\n'):
if 'Cannot' in line:
return None # SIMsalabim 无法求解此参数组合
# 从 stdout 解析 fitError
for line in r.stdout.split('\n'):
if 'fitError:' in line:
return float(line.split(':')[1].strip().split()[0])
return None关键细节:
- 电流取负号(
-row['Current(mA/cm2)']):因为 SIMsalabim 以第一象限正电流为正,而实验 Jsc 为负(第四象限测量) - 超时保护(
timeout=90):某些参数组合可能让 SIMsalabim 无限迭代 - 输出文件清理:每次运行前删除 JV.dat 等旧输出,防止读到上次结果
- 正则替换的精度:
.6e格式保证数值不回退到文本解析的精度损失
四、反向工程优化策略
4.1 问题形式化
- 输入: 某老化阶段的实验 J-V 曲线(~30 个电压点)
- 输出: 一组物理参数 θ = (μn, μp, Nt_bulk, Nt_int, R_shunt)
- 目标: min fitError(SIMsalabim(θ), JV_exp)
- 约束: θ 必须在物理合理范围内
4.2 策略一:网格搜索(Grid Search,exp1)
原理
在参数空间建立离散网格,枚举所有组合:
mu_n ∈ {1e-6, 1e-5, ..., 5e-3} (对数均匀取 8 点)
mu_p ∈ {1e-7, 1e-6, ..., 5e-3} (对数均匀取 8 点)
Nt_bulk ∈ {1e18, 1e19, ..., 1e24} (对数均匀取 7 点)
Nt_int ∈ {1e10, 1e11, ..., 1e16} (对数均匀取 7 点)
R_shunt ∈ {1e-4, 1e-3, ..., 1e4} (对数均匀取 7 点)
- 优点: 简单、全局搜索、不依赖梯度
- 缺点: 维度灾难 — 5 个参数 × 每维 8 点 = 32,768 次 eval,每次 ~10s,不可行
- exp1 实际执行: 减少采样密度,限制在 ~1000-2000 次 eval
代码实现(概念)
mu_n_vals = np.logspace(np.log10(1e-6), np.log10(5e-3), 6)
mu_p_vals = np.logspace(np.log10(1e-7), np.log10(5e-3), 6)
Nt_vals = np.logspace(np.log10(1e18), np.log10(1e24), 5)
Nt_int_vals = np.logspace(np.log10(1e10), np.log10(1e16), 5)
Rsh_vals = np.logspace(np.log10(1e-4), np.log10(1e4), 5)
best = {'fitError': float('inf')}
for mu_n in mu_n_vals:
for mu_p in mu_p_vals:
for nt in Nt_vals:
for n_int in Nt_int_vals:
for rsh in Rsh_vals:
set_params({...}); e = run_simss()
if e and e < best['fitError']:
best = {...}; best['fitError'] = e4.3 策略二:坐标下降(Coordinate Descent,exp4/exp5)
原理
核心思想: 每次只优化一个参数,固定其余,一轮一轮迭代。
轮次 1: μn → μp → Nt_bulk → Nt_int → R_shunt
轮次 2: μn → μp → Nt_bulk → Nt_int → R_shunt
轮次 3: ...(直到所有参数无法进一步改善)
每个参数的局部搜索:在对数空间中均匀采样 15 个点,扫描当前值 ±1 decade(第1轮)→ ±2 decades(第2轮)。
为什么比网格搜索高效?
- 网格搜索:N⁵ 复杂度(指数级)
- 坐标下降:每轮 5×15 = 75 次 eval,2-3 轮收敛 → 150-225 次 eval vs 32,768 次
代码实现(exp5 核心算法)
def coord_descent_clamped(start, max_cyc=5, dr=1.0, n=15):
"""带物理约束的坐标下降"""
best = clamp_params(dict(start)) # Step 0: 物理约束
set_params(best)
best['fitError'] = run_simss()
for cyc in range(max_cyc):
improved = False
for pn in PARAM_NAMES: # 逐参数优化
log_val = np.log10(max(best[pn], 1e-30))
# 搜索范围 = 当前值 ±dr 对数空间,并 clamp 到物理边界
lo_log = max(np.log10(BOUNDS[pn][0]), log_val - dr)
hi_log = min(np.log10(BOUNDS[pn][1]), log_val + dr)
if hi_log - lo_log < 0.01: continue # 搜索空间太窄,跳过
vals = np.linspace(lo_log, hi_log, n) # 对数均匀取 n 点
for v in 10**vals:
trial = dict(best); trial[pn] = v
trial = clamp_params(trial) # 每次都要约束!
set_params(trial)
e = run_simss()
if e is not None and e < best['fitError']:
best[pn] = v
best['fitError'] = e
improved = True
if not improved: # 本轮无任何参数改善 → 已收敛
break
return best4.4 物理约束(Bounds Enforcement)
原理
exp4 中无约束的坐标下降出现了严重的参数补偿过拟合:
| 阶段 | μn (m²/V·s) | Nt_bulk (m⁻³) | fitError | 问题 |
|---|---|---|---|---|
| Initial | 4.75e-2 | 3.04e23 | 0.1439 | μn 超 47× |
| Mid | 3.40e-2 | 5.67e23 | 0.1505 | μn 超 34× |
| Final | 1.30e-1 | 3.24e27 | 0.0982 | μn 超 130×,Nt 超 32,400× |
过拟合机制: 高迁移率→载流子快速收集(提升 Jsc),但同时超高陷阱密度→大量复合(压制回来)。数学上两者抵消产生了低 fitError,但物理上 μ=0.13 m²/V·s 远超钙钛矿单晶测量值(~0.01),Nt=3.24e27 意味 “每 nm³ 有 3 个陷阱”——不可能。
代码实现
# ===== 物理硬约束边界 =====
BOUNDS = {
'mu_n': (1e-7, 5e-3), # max 50 cm²/V·s
'mu_p': (1e-7, 5e-3),
'Nt_bulk': (1e18, 1e24), # 接近 Nc/2 上限
'Nt_int': (1e10, 1e16),
'R_shunt': (1e-4, 1e4),
}
# ===== 迁移率比值约束 =====
RATIO_MIN = 0.001 # μp ≥ μn/1000
RATIO_MAX = 2.0 # μp ≤ 2×μn
def clamp_params(p):
# 1. 边界约束
for k in PARAM_NAMES:
lo, hi = BOUNDS[k]
p[k] = max(lo, min(hi, p[k]))
# 2. 比值约束(防 μp/μn 极端不合理)
if p['mu_p'] / p['mu_n'] < RATIO_MIN:
p['mu_p'] = p['mu_n'] * RATIO_MIN
if p['mu_p'] / p['mu_n'] > RATIO_MAX:
p['mu_p'] = p['mu_n'] * RATIO_MAX
return pexp5 效果: 全部参数物理合理,avg fitError 0.1721(vs exp1 基线 0.2416,改善 29.1%)。
4.5 多起点搜索策略
exp5 使用 5 个不同起点运行坐标下降,取最优结果:
| 起点 | 类型 | 理由 |
|---|---|---|
| exp1-clamped | 网格搜索结果(约束后) | 已知合理区域,FM 起点 |
| exp3v4-clamped | 加密网格结果(约束后) | 更精细的 FM 起点 |
| random-1,2,3 | 对数均匀随机采样 | 探索其他区域,防局部最优 |
def random_start():
"""在对数空间中均匀随机采样"""
p = {}
for name in PARAM_NAMES:
lo, hi = BOUNDS[name]
log_val = np.random.uniform(np.log10(lo), np.log10(hi))
p[name] = 10**log_val
return clamp_params(p)
# 两阶段优化
def run_optimization(name, start):
# Phase 1: ±1 decade, 5 cycles — 粗搜索
p1 = coord_descent_clamped(start, max_cyc=5, dr=1.0, n=15)
# Phase 2: ±2 decades, 3 cycles — 扩大搜索
p2 = coord_descent_clamped(p1, max_cyc=3, dr=2.0, n=15)
return p2 if p2['fitError'] < p1['fitError'] else p14.6 并行化策略
5 个老化阶段完全独立,通过独立的 SIMsalabim 工作目录实现并行:
# 并行运行 5 个阶段
python exp5_constrained.py initial &
python exp5_constrained.py early &
python exp5_constrained.py mid &
python exp5_constrained.py late &
python exp5_constrained.py final &每个阶段的工作目录独立:
WORK_DIR = f"G:\\OpenClaw-Workspace\\simsalabim_parallel\\{STAGE}_exp5"
def setup_workspace():
shutil.rmtree(WORK_DIR) # 清除旧副本
shutil.copytree(ORIGINAL_DIR, WORK_DIR) # 创建独立副本
shutil.copytree(data_src, WORK_DIR + "\\Data") # 复制材料数据五、老化退化分析
5.1 原理
老化五阶段
对器件施加持续光照/偏压应力,在不同时间点测量 J-V 曲线:
Initial → Early → Mid → Late → Final
(0 h) (24h) (72h) (168h) (336h+)
对每个阶段的 J-V 曲线独立进行反向工程,得到物理参数随时间演化曲线。
退化机制的物理诊断
| 观测 | 物理机制 | 根因推测 |
|---|---|---|
| Voc 下降 | J0 增大 → 复合增强 | 陷阱密度增加,准费米能级收缩 |
| Jsc 下降 | 扩散长度 LD 缩短 | μ↓ 和 τ↓ 同时退化 |
| FF 下降 | Rs↑ + Rsh↓ + n↑ 三者叠加 | 传输+复合双向退化 |
| n 持续增大 | 复合中心能级展宽 | 新陷阱态(深能级)涌现 |
| Nt_bulk 增大 | 体相缺陷生成 | 离子迁移、卤素空位形成 |
| Nt_int 增大 | 界面钝化退化 | 界面氧化物/氢氧化钾生成 |
| R_shunt 下降 | 分流路径形成 | 金属电极迁移、针孔扩展 |
| μ 下降 | 载流子散射增强 | 声子散射 + 电离杂质散射 + 中性缺陷散射 |
两种老化模式的物理对比
| 特征 | 激进老化(数据集1) | 温和老化(数据集2) |
|---|---|---|
| PCE 损失 | 88.1% | 33.2% |
| 退化动力学 | 指数衰减→饱和(快→慢) | 近线性缓降(匀速) |
| 主导机制 | 复合损耗 60-70% | 传输+复合均衡 |
| μn 下降 | 96%(5e-4→2e-5) | 57%(2.56e-4→1.10e-4) |
| Nt_int 增加 | 50×(1e13→5e14) | 2.2×(1e13→3.23e13) |
| n 终值 | 2.5 | 3.0 |
关键物理发现: 存在”界面崩溃”阈值——Nt_int 在温和老化中仅增 2.2×,但在激进老化中”跃迁”到 50×。当界面钝化层整体崩坏后,老化进入不可逆加速阶段。
5.2 代码实现
老化反向工程流水线
STAGES = ['initial', 'early', 'mid', 'late', 'final']
all_results = {}
for stage in STAGES:
# 1. 加载实验 J-V(来自 key_curves 目录的 CSV)
df = pd.read_csv(f'{KEY_CURVES_DIR}/{stage}_curve.csv')
# 2. 写入 SIMsalabim 输入文件(格式:Vext Jext)
lines = [" Vext Jext"]
for _, row in df.iterrows():
lines.append(f" {row['Voltage(V)']:12.6E} {-row['Current(mA/cm2)']:12.6E}")
with open(f'{WORK_DIR}/JV_Exp.dat', 'w') as f:
f.write('\n'.join(lines))
# 3. 运行坐标下降(exp5 最优方法)
result = coord_descent_clamped(start_params[stage], max_cyc=3, dr=1.0, n=15)
# 4. 保存各阶段结果
all_results[stage] = result
with open(f'results/{stage}_results.json', 'w') as f:
json.dump(result, f)退化幅度计算
# 从两个阶段的结果计算参数变化
def compute_degradation_metrics(initial, final):
metrics = {
# 各参数相对变化
'mu_n_degradation': (initial['mu_n'] - final['mu_n']) / initial['mu_n'],
'Nt_bulk_increase': final['Nt_bulk'] / initial['Nt_bulk'],
'Nt_int_increase': final['Nt_int'] / initial['Nt_int'],
'PCE_loss': (initial['PCE'] - final['PCE']) / initial['PCE'],
# fitError 对比
'fitError_improvement': (exp1_fitError - optimized_fitError),
}
return metrics数据集交叉对比(v2)
v2 通过 generate_comparison.py 将两个数据集的参数演化放到同一张图中:
# 读取两个数据集的结果
ds1 = load_results('aging_analysis_r1-r4/results/')
ds2 = load_results('aging_analysis_v2/results/')
# 关键对比
comparisons = {
'PCE_loss': {'ds1': 0.881, 'ds2': 0.332},
'mu_n_degradation': {'ds1': 0.96, 'ds2': 0.57},
'Nt_int_increase': {'ds1': 50, 'ds2': 2.2},
'n_final': {'ds1': 2.5, 'ds2': 3.0},
}六、代码实现总结
6.1 整体架构
┌─────────────────────────────────────────────────┐
│ 输入层(数据读取) │
│ Excel → pandas CSV → 老化曲线 WPD JSON │
├─────────────────────────────────────────────────┤
│ 分析层(参数提取) │
│ L1: PCE/Jsc/Voc/FF 直接提取 │
│ L2: 二极管拟合(ImprovedFullDiodeModel) │
│ L3: 物理机制分类 + 损失分解 │
├─────────────────────────────────────────────────┤
│ 仿真层(SIMsalabim 接口) │
│ 参数模板修改 (re.sub) │
│ simss.exe 调用 (subprocess, timeout=90s) │
│ fitError 解析 (stdout 正则) │
│ 并行工作空间 (shutil.copytree) │
├─────────────────────────────────────────────────┤
│ 优化层(参数搜索) │
│ 网格搜索 → 坐标下降 → 物理约束 + 多起点 │
│ 对数空间采样 (logspace) + 并行独立工作区 │
├─────────────────────────────────────────────────┤
│ 输出层(报告生成) │
│ Markdown 报告 → JSON 结果 → PNG 图表 → Word 文档 │
└─────────────────────────────────────────────────┘
6.2 关键技术细节总结
| 技术点 | 实现方式 | 为何如此设计 |
|---|---|---|
| 正则替换参数 | re.sub(r'mu_n\s*=\s*[\d.Ee+-]+', ...) | 直接修改 SIMsalabim 文本配置文件,避免重新生成 |
| 对数空间搜索 | np.logspace(log10(lo), log10(hi), n) | 参数跨度 5 个数量级,线性搜索无效 |
| 超时保护 | subprocess.run(..., timeout=90) | 防 SIMsalabim 在非物理参数组合处死循环 |
| 物理硬约束 | max(lo, min(hi, val)) | 最小侵入式约束,不改变优化算法结构 |
| μp/μn 比值约束 | ratio ∈ [0.001, 2.0] | 防止迁移率比值脱离钙钛矿物理规律 |
| 独立工作空间 | shutil.copytree 创建全局副本 | 5 个阶段并行运行时避免 JV.dat 输出冲突 |
| 多策略起点 | exp1 + exp3v4 + 3×random | 已知合理区域(exploit)+ 探索未知区域(explore) |
| 两阶段搜索 | Phase 1: dr=1.0 → Phase 2: dr=2.0 | 先精细搜索局部最小,再扩大窗口防遗漏 |
| 正则解析 fitError | r.stdout → 'fitError:' → float | 从 stdout 解析而非文件,避免 I/O 竞态条件 |
| 电流符号转换 | -row['Current(mA/cm2)'] | 实验 Jsc<0(第四象限),SIMsalabim 用 Jsc>0(第一象限) |
6.3 五轮实验的代码演化
| 实验 | 核心文件 | 代码量 | 方法特征 |
|---|---|---|---|
| exp1 | heester_systematic.py, heester_v2.py | ~30 KB | 固定网格,全枚举 |
| exp2 | hybrid_v3_experiment.py | ~17 KB | 多方法混合,并行尝试 |
| exp3 | exp3_v4_proven.py(5 个版本) | ~58 KB | 网格加密,4 轮迭代 |
| exp4 | exp4_v3_coord_descent.py 等 7 个版本 | ~57 KB | 坐标下降(无约束),收敛但过拟合 |
| exp5 | exp5_constrained.py | ~12 KB | 坐标下降+硬约束,最优方案 |
代码量先增后减:exp1 用简单方法→exp3/4 复杂化以追求更低 fitError→exp5 用物理约束化繁为简,用更少代码实现更好的物理合理性。
6.4 后续可扩展方向
- 贝叶斯优化替代坐标下降:利用高斯过程代理模型减少 eval 次数
- 梯度辅助优化:改造 SIMsalabim 输出 Jacobian 矩阵
- 深度学习代理模型:用神经网络学习 SIMsalabim 的输入-输出映射
- 多目标优化:同时最小化所有 5 个老化阶段的 fitError(而非每个阶段独立)
- 自动化报告流水线:老炼数据→自动运行 exp5→自动生成对比图表→自动审核
撰写完成:2026-06-04 10:40 GMT+8 | 基于 321 个代码/数据文件
正在载入讨论…