Files
MCM/A题/分析/框架2/TTE预测与不确定性量化.py
2026-01-31 13:43:21 +08:00

256 lines
9.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import spearmanr
import os
import time
# ==========================================
# 1. 配置与初始化
# ==========================================
def configure_environment():
"""
配置绘图环境,解决中文乱码和负号显示问题,
并设置符合学术规范的绘图风格。
"""
# 设置字体为 Times New Roman (英文) 或 SimHei (中文兼容)
# 为了美赛(MCM)标准,主要使用英文标签,但配置好中文支持以防万一
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial'] # 优先使用黑体显示中文
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示为方块的问题
# 学术风格配置
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams.update({
'font.size': 12,
'axes.labelsize': 14,
'axes.titlesize': 16,
'xtick.labelsize': 12,
'ytick.labelsize': 12,
'legend.fontsize': 12,
'figure.figsize': (10, 6),
'figure.dpi': 150
})
# 确保输出目录存在
if not os.path.exists('output_q2'):
os.makedirs('output_q2')
# ==========================================
# 2. 核心物理模型类
# ==========================================
class StochasticBatteryModel:
def __init__(self):
# 电池物理参数
self.Q_design = 4000 / 1000.0 # 4000 mAh -> 4.0 Ah
self.R_int_base = 0.15 # 内阻 (Ohm)
# Shepherd 模型参数 (OCV 曲线)
self.E0 = 3.4
self.K = 0.02
self.A = 0.5
self.B = 15.0
def get_capacity_correction(self, temp_c):
"""
根据温度修正有效容量 (Arrhenius 效应)
Temp_c: 环境温度 (摄氏度)
"""
temp_k = temp_c + 273.15
ref_k = 298.15 # 25°C
# 温度越低,容量越小;温度越高,容量略微增加但有限制
factor = np.exp(0.8 * (1 - ref_k / temp_k))
return np.clip(factor, 0.4, 1.1) # 限制修正因子范围
def get_ocv(self, soc):
"""计算开路电压 (Open Circuit Voltage)"""
soc = np.clip(soc, 0.001, 1.0)
# Shepherd Model + 线性项
term1 = self.E0
term2 = -self.K / soc
term3 = -self.R_int_base * 0.1 # 简化极化项
term4 = self.A * np.exp(-self.B * (1 - soc))
term5 = 0.7 * soc # 线性部分
v_ocv = 3.0 + term5 + term4 + term2
return np.clip(v_ocv, 2.8, 4.35)
def calculate_power_drain(self, base_power, signal_dbm, noise_scale=0.1):
"""
计算瞬时功率,包含信号强度的非线性影响和随机噪声
P_total = P_base + P_signal + Noise
"""
# 1. 信号功耗模型:信号越弱(越负),功耗呈指数上升
# 假设 -60dBm 为基准,每下降 10dBm 功耗显著增加
# 归一化信号强度:将 -120dBm 到 -50dBm 映射到 0-1 之间
sig_norm = np.clip((-signal_dbm - 50) / 70, 0, 1)
p_net = 2.0 * (sig_norm ** 3) # 三次幂关系,模拟弱信号时的急剧恶化
# 2. 随机噪声 (模拟 CPU 动态调频)
noise = np.random.normal(0, noise_scale)
return max(0.1, base_power + p_net + noise)
# ==========================================
# 3. 蒙特卡洛模拟引擎
# ==========================================
def run_monte_carlo_simulation(n_simulations=2000):
"""
执行 N 次蒙特卡洛模拟
"""
print(f"Starting Monte Carlo Simulation with {n_simulations} samples...")
model = StochasticBatteryModel()
results = []
# 定义时间步长 (秒)
dt = 60.0 # 1分钟一步平衡精度与速度
max_time = 48 * 3600 # 最大模拟 48 小时
start_time = time.time()
for i in range(n_simulations):
# --- A. 随机生成初始条件 (输入分布) ---
# 1. 初始电量 SOC0: 均匀分布 U(0.2, 1.0)
# 模拟用户在不同电量下开始使用手机
soc_0 = np.random.uniform(0.2, 1.0)
# 2. 环境温度 Temp: 正态分布 N(25, 8),截断在 [-10, 45]
temp_c = np.random.normal(25, 8)
temp_c = np.clip(temp_c, -10, 45)
# 3. 平均信号强度 Signal: 偏态分布
# 大部分时候信号较好 (-80dBm), 偶尔很差 (-110dBm)
signal_dbm = np.random.triangular(-120, -85, -50)
# 4. 用户行为模式 (基准功率)
# 混合高斯分布:待机为主(0.5W),偶尔重度使用(4W)
user_type = np.random.choice(['Light', 'Medium', 'Heavy'], p=[0.4, 0.4, 0.2])
if user_type == 'Light':
base_power_avg = 0.5 # 待机、阅读
elif user_type == 'Medium':
base_power_avg = 1.5 # 视频、社交
else:
base_power_avg = 4.0 # 游戏
# --- B. 执行单次时间步进模拟 (Euler Integration) ---
soc = soc_0
t = 0
q_eff = model.Q_design * model.get_capacity_correction(temp_c)
while soc > 0.02 and t < max_time: # 截止电压设为 2%
# 计算当前功率 (加入随机性)
p_inst = model.calculate_power_drain(base_power_avg, signal_dbm, noise_scale=0.2)
# 计算端电压
v_term = model.get_ocv(soc) - (p_inst / 3.7) * model.R_int_base
v_term = max(v_term, 3.0) # 防止电压过低
# 计算电流 I = P / V
current = p_inst / v_term
# 更新 SOC: dS = -I * dt / Q
d_soc = -current * dt / (q_eff * 3600)
soc += d_soc
t += dt
# --- C. 记录结果 ---
tte_hours = t / 3600.0
results.append({
'Initial_SOC': soc_0,
'Temperature_C': temp_c,
'Signal_dBm': signal_dbm,
'User_Profile': user_type,
'Base_Power': base_power_avg,
'TTE_Hours': tte_hours
})
if (i+1) % 500 == 0:
print(f" Processed {i+1}/{n_simulations} simulations...")
print(f"Simulation completed in {time.time() - start_time:.2f} seconds.")
return pd.DataFrame(results)
# ==========================================
# 4. 结果分析与可视化
# ==========================================
def analyze_and_plot(df):
"""
对模拟结果进行统计分析和绘图
"""
print("\n=== Analysis Report ===")
print(df.describe())
# --- 图1: TTE 分布直方图 (Probability Distribution) ---
plt.figure(figsize=(10, 6))
sns.histplot(data=df, x='TTE_Hours', hue='User_Profile', element="step", stat="density", common_norm=False, palette='viridis')
plt.title('Distribution of Time-to-Empty (TTE) by User Profile', fontweight='bold')
plt.xlabel('Time to Empty (Hours)')
plt.ylabel('Probability Density')
plt.grid(True, alpha=0.3)
plt.savefig('output_q2/fig1_tte_distribution.png')
plt.show()
# --- 图2: 初始电量 vs TTE (非线性关系展示) ---
plt.figure(figsize=(10, 6))
# 使用散点图,颜色映射温度
sc = plt.scatter(df['Initial_SOC']*100, df['TTE_Hours'], c=df['Temperature_C'], cmap='coolwarm', alpha=0.6, s=20)
plt.colorbar(sc, label='Temperature (°C)')
plt.title('Impact of Initial SOC and Temperature on TTE', fontweight='bold')
plt.xlabel('Initial State of Charge (%)')
plt.ylabel('Time to Empty (Hours)')
plt.grid(True, linestyle='--', alpha=0.5)
# 添加拟合曲线 (展示非线性)
# 简单的多项式拟合用于视觉引导
z = np.polyfit(df['Initial_SOC']*100, df['TTE_Hours'], 2)
p = np.poly1d(z)
x_range = np.linspace(20, 100, 100)
plt.plot(x_range, p(x_range), 'k--', linewidth=2, label='Trend Line')
plt.legend()
plt.savefig('output_q2/fig2_soc_vs_tte.png')
plt.show()
# --- 图3: 全局灵敏度分析 (Tornado Plot 替代品) ---
# 计算 Spearman 相关系数
corr_cols = ['Initial_SOC', 'Temperature_C', 'Signal_dBm', 'Base_Power']
corr_matrix = df[corr_cols + ['TTE_Hours']].corr(method='spearman')
sensitivity = corr_matrix['TTE_Hours'].drop('TTE_Hours').sort_values()
plt.figure(figsize=(10, 5))
colors = ['red' if x < 0 else 'green' for x in sensitivity.values]
sensitivity.plot(kind='barh', color=colors, alpha=0.8)
plt.title('Sensitivity Analysis: Correlation with TTE', fontweight='bold')
plt.xlabel('Spearman Correlation Coefficient')
plt.axvline(0, color='black', linewidth=0.8)
plt.grid(axis='x', linestyle='--', alpha=0.5)
# 添加数值标签
for index, value in enumerate(sensitivity):
plt.text(value, index, f' {value:.2f}', va='center', fontsize=10, fontweight='bold')
plt.tight_layout()
plt.savefig('output_q2/fig3_sensitivity.png')
plt.show()
print("\nKey Insights:")
print(f"1. Base Power Correlation: {sensitivity['Base_Power']:.2f} (Dominant negative factor)")
print(f"2. Initial SOC Correlation: {sensitivity['Initial_SOC']:.2f} (Dominant positive factor)")
print(f"3. Signal Strength Correlation: {sensitivity['Signal_dBm']:.2f} (Significant environmental factor)")
# ==========================================
# 5. 主程序入口
# ==========================================
if __name__ == "__main__":
# 1. 配置环境
configure_environment()
# 2. 运行模拟
# 模拟 3000 次以获得平滑的分布
simulation_data = run_monte_carlo_simulation(n_simulations=3000)
# 3. 分析与绘图
analyze_and_plot(simulation_data)
print("\nAll tasks completed. Results saved in 'output_q2' directory.")