Initial commit

This commit is contained in:
ChuXun
2026-02-16 21:52:26 +08:00
commit 18ce59bec7
334 changed files with 35333 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
"""
Fig 14: Lifecycle Degradation (Aging)
Shows SOH and TTE evolution over multiple charge cycles
"""
import os
import numpy as np
import matplotlib.pyplot as plt
from plot_style import set_oprice_style, save_figure
def make_figure(config):
"""Generate Fig 14: Lifecycle Degradation"""
set_oprice_style()
# Aging parameters
lambda_fade = config.get('battery_params', {}).get('lambda_fade', 0.0001)
n_cycles = 1000
# Cycle numbers
cycles = np.arange(0, n_cycles + 1)
# SOH degradation (exponential decay with square root of cycles term)
# More realistic aging: fast initial drop, then slower
SOH = 1.0 - lambda_fade * cycles - 0.00005 * np.sqrt(cycles)
SOH = np.clip(SOH, 0.7, 1.0) # SOH doesn't go below 70%
# TTE degradation (proportional to SOH but with additional resistance increase)
# As battery ages, internal resistance increases, reducing effective capacity
TTE_fresh = 2.5 # hours at SOH=1.0
resistance_factor = 1.0 + 0.5 * (1 - SOH) # Resistance increases as SOH decreases
TTE = TTE_fresh * SOH / resistance_factor
# Create figure with dual y-axis
fig, ax1 = plt.subplots(figsize=(12, 8))
# Plot SOH on left axis
color1 = '#1f77b4'
ax1.set_xlabel('Charge-Discharge Cycles', fontsize=11)
ax1.set_ylabel('State of Health (SOH)', fontsize=11, color=color1)
line1 = ax1.plot(cycles, SOH * 100, color=color1, linewidth=2.5,
label='SOH', marker='o', markevery=100, markersize=6)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.set_ylim(65, 105)
ax1.grid(True, alpha=0.3, axis='x')
# Mark critical SOH thresholds
ax1.axhline(80, color='orange', linestyle='--', linewidth=1.5, alpha=0.6,
label='80% SOH (End of Life)')
# Second y-axis for TTE
ax2 = ax1.twinx()
color2 = '#d62728'
ax2.set_ylabel('Time to Empty (hours)', fontsize=11, color=color2)
line2 = ax2.plot(cycles, TTE, color=color2, linewidth=2.5,
label='TTE at Full Charge', marker='s', markevery=100, markersize=6)
ax2.tick_params(axis='y', labelcolor=color2)
ax2.set_ylim(1.0, 2.8)
# Title
ax1.set_title('Battery Lifecycle Degradation: SOH and TTE Evolution',
fontsize=12, fontweight='bold')
# Combined legend
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper right', framealpha=0.9, fontsize=10)
# Find cycle at 80% SOH
idx_80 = np.where(SOH <= 0.80)[0]
if len(idx_80) > 0:
cycle_80 = cycles[idx_80[0]]
soh_80 = SOH[idx_80[0]]
tte_80 = TTE[idx_80[0]]
ax1.axvline(cycle_80, color='gray', linestyle=':', linewidth=1.5, alpha=0.5)
ax1.annotate(f'End of Life\\nCycle {cycle_80}\\nSOH={soh_80*100:.1f}%\\nTTE={tte_80:.2f}h',
xy=(cycle_80, 80), xytext=(cycle_80 + 150, 85),
arrowprops=dict(arrowstyle='->', color='darkred', lw=1.5),
fontsize=9, color='darkred',
bbox=dict(boxstyle='round', facecolor='mistyrose', alpha=0.8))
# Add aging mechanism annotation
mechanism_text = 'Aging Mechanisms:\\n' + \
'• Capacity fade\\n' + \
'• Resistance increase\\n' + \
'• Accelerated at extremes\\n' + \
f' (λ_fade = {lambda_fade:.6f})'
ax1.text(0.02, 0.30, mechanism_text, transform=ax1.transAxes,
fontsize=9, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8))
# Add degradation statistics
initial_tte = TTE[0]
final_tte = TTE[-1]
tte_loss = initial_tte - final_tte
tte_loss_pct = tte_loss / initial_tte * 100
stats_text = f'Performance Metrics:\\n'
stats_text += f'Initial TTE: {initial_tte:.2f}h\\n'
stats_text += f'After {n_cycles} cycles: {final_tte:.2f}h\\n'
stats_text += f'Total loss: {tte_loss:.2f}h ({tte_loss_pct:.1f}%)\\n'
stats_text += f'Avg loss per 100 cycles: {tte_loss/(n_cycles/100):.3f}h'
ax1.text(0.98, 0.97, stats_text, transform=ax1.transAxes,
fontsize=9, verticalalignment='top', horizontalalignment='right',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.8),
family='monospace')
plt.tight_layout()
# Save
figure_dir = config.get('global', {}).get('figure_dir', 'figures')
os.makedirs(figure_dir, exist_ok=True)
output_base = os.path.join(figure_dir, 'Fig14_Lifecycle_Degradation')
save_figure(fig, output_base, dpi=config.get('global', {}).get('dpi', 300))
plt.close()
return {
"output_files": [f"{output_base}.pdf", f"{output_base}.png"],
"computed_metrics": {
"initial_tte_h": float(initial_tte),
"final_tte_h": float(final_tte),
"tte_loss_pct": float(tte_loss_pct),
"eol_cycle": int(cycle_80) if len(idx_80) > 0 else n_cycles
},
"validation_flags": {},
"pass": True
}