256 lines
9.7 KiB
Python
256 lines
9.7 KiB
Python
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.") |