2025-12-25 18:06:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
多能互补系统储能容量优化可视化程序
|
|
|
|
|
|
|
|
|
|
|
|
该程序绘制负荷曲线、发电曲线和储能出力曲线,直观展示系统运行状态。
|
|
|
|
|
|
|
|
|
|
|
|
作者: iFlow CLI
|
|
|
|
|
|
创建日期: 2025-12-25
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
import matplotlib
|
|
|
|
|
|
matplotlib.use('TkAgg') # 设置为TkAgg后端以支持图形窗口显示
|
2025-12-25 18:06:12 +08:00
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
import numpy as np
|
2025-12-26 01:44:08 +08:00
|
|
|
|
import pandas as pd
|
|
|
|
|
|
from datetime import datetime
|
2025-12-25 18:06:12 +08:00
|
|
|
|
from storage_optimization import optimize_storage_capacity, SystemParameters
|
2025-12-25 19:56:21 +08:00
|
|
|
|
from excel_reader import read_excel_data, create_excel_template, analyze_excel_data
|
2025-12-25 18:06:12 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置中文字体
|
|
|
|
|
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
|
|
|
|
|
|
plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, show_window=False, display_only=False):
|
2025-12-25 18:06:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
绘制系统运行曲线
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
solar_output: 光伏出力曲线 (MW) - 支持24小时或8760小时
|
|
|
|
|
|
wind_output: 风电出力曲线 (MW) - 支持24小时或8760小时
|
|
|
|
|
|
thermal_output: 火电出力曲线 (MW) - 支持24小时或8760小时
|
|
|
|
|
|
load_demand: 负荷曲线 (MW) - 支持24小时或8760小时
|
|
|
|
|
|
result: 优化结果字典
|
2025-12-26 01:06:53 +08:00
|
|
|
|
show_window: 是否显示图形窗口
|
|
|
|
|
|
display_only: 是否只显示不保存文件
|
2025-12-25 18:06:12 +08:00
|
|
|
|
"""
|
2025-12-25 19:56:21 +08:00
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
import numpy as np
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
# 设置中文字体
|
|
|
|
|
|
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
|
|
|
|
|
|
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
|
2025-12-25 18:06:12 +08:00
|
|
|
|
hours = np.arange(len(solar_output))
|
|
|
|
|
|
data_length = len(solar_output)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 确定图表标题和采样率
|
|
|
|
|
|
if data_length == 8760:
|
|
|
|
|
|
title_suffix = " (全年8760小时)"
|
|
|
|
|
|
# 对于全年数据,我们采样显示(每6小时显示一个点)
|
|
|
|
|
|
sample_rate = 6
|
|
|
|
|
|
sampled_hours = hours[::sample_rate]
|
|
|
|
|
|
sampled_solar = solar_output[::sample_rate]
|
|
|
|
|
|
sampled_wind = wind_output[::sample_rate]
|
|
|
|
|
|
sampled_thermal = thermal_output[::sample_rate]
|
|
|
|
|
|
sampled_load = load_demand[::sample_rate]
|
|
|
|
|
|
sampled_storage = result['storage_profile'][::sample_rate]
|
|
|
|
|
|
sampled_charge = result['charge_profile'][::sample_rate]
|
|
|
|
|
|
sampled_discharge = result['discharge_profile'][::sample_rate]
|
2025-12-26 01:44:08 +08:00
|
|
|
|
sampled_grid_feed_in = result['grid_feed_in'][::sample_rate]
|
2025-12-25 18:06:12 +08:00
|
|
|
|
else:
|
|
|
|
|
|
title_suffix = " (24小时)"
|
|
|
|
|
|
sampled_hours = hours
|
|
|
|
|
|
sampled_solar = solar_output
|
|
|
|
|
|
sampled_wind = wind_output
|
|
|
|
|
|
sampled_thermal = thermal_output
|
|
|
|
|
|
sampled_load = load_demand
|
|
|
|
|
|
sampled_storage = result['storage_profile']
|
|
|
|
|
|
sampled_charge = result['charge_profile']
|
|
|
|
|
|
sampled_discharge = result['discharge_profile']
|
2025-12-26 01:44:08 +08:00
|
|
|
|
sampled_grid_feed_in = result['grid_feed_in']
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 创建图形(4个子图)
|
|
|
|
|
|
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(14, 16))
|
2025-12-25 18:06:12 +08:00
|
|
|
|
fig.suptitle('多能互补系统24小时运行曲线', fontsize=16, fontweight='bold')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# === 第一个子图:发电和负荷曲线 ===
|
|
|
|
|
|
ax1.plot(sampled_hours, sampled_load, 'r-', linewidth=2, label='负荷需求')
|
|
|
|
|
|
ax1.plot(sampled_hours, sampled_thermal, 'b-', linewidth=2, label='火电出力')
|
|
|
|
|
|
ax1.plot(sampled_hours, sampled_wind, 'g-', linewidth=2, label='风电出力')
|
|
|
|
|
|
ax1.plot(sampled_hours, sampled_solar, 'orange', linewidth=2, label='光伏出力')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 计算总发电量
|
|
|
|
|
|
total_generation = [sampled_thermal[i] + sampled_wind[i] + sampled_solar[i] for i in range(len(sampled_thermal))]
|
|
|
|
|
|
ax1.plot(sampled_hours, total_generation, 'k--', linewidth=1.5, alpha=0.7, label='总发电量')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
ax1.set_xlabel('时间 (小时)')
|
|
|
|
|
|
ax1.set_ylabel('功率 (MW)')
|
|
|
|
|
|
ax1.set_title(f'发电与负荷曲线{title_suffix}')
|
|
|
|
|
|
ax1.legend(loc='upper right')
|
|
|
|
|
|
ax1.grid(True, alpha=0.3)
|
|
|
|
|
|
ax1.set_xlim(0, max(sampled_hours))
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# === 第二个子图:储能充放电曲线 ===
|
|
|
|
|
|
discharge_power = [-x for x in sampled_discharge] # 放电显示为负值
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
ax2.bar(sampled_hours, sampled_charge, color='green', alpha=0.7, label='充电功率')
|
|
|
|
|
|
ax2.bar(sampled_hours, discharge_power, color='red', alpha=0.7, label='放电功率')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
ax2.set_xlabel('时间 (小时)')
|
|
|
|
|
|
ax2.set_ylabel('功率 (MW)')
|
|
|
|
|
|
ax2.set_title(f'储能充放电功率{title_suffix}')
|
|
|
|
|
|
ax2.legend(loc='upper right')
|
|
|
|
|
|
ax2.grid(True, alpha=0.3)
|
|
|
|
|
|
ax2.set_xlim(0, max(sampled_hours))
|
|
|
|
|
|
ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# === 第三个子图:储能状态曲线 ===
|
|
|
|
|
|
ax3.plot(sampled_hours, sampled_storage, 'b-', linewidth=1, marker='o', markersize=2)
|
|
|
|
|
|
ax3.fill_between(sampled_hours, 0, sampled_storage, alpha=0.3, color='blue')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
ax3.set_xlabel('时间 (小时)')
|
|
|
|
|
|
ax3.set_ylabel('储能容量 (MWh)')
|
|
|
|
|
|
ax3.set_title(f'储能状态 (总容量: {result["required_storage_capacity"]:.2f} MWh){title_suffix}')
|
|
|
|
|
|
ax3.grid(True, alpha=0.3)
|
|
|
|
|
|
ax3.set_xlim(0, max(sampled_hours))
|
|
|
|
|
|
ax3.set_ylim(bottom=0)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# === 第四个子图:购电量和上网电量曲线 ===
|
|
|
|
|
|
# 直接使用原始数据:正值表示上网电量,负值表示购电量
|
|
|
|
|
|
grid_power = sampled_grid_feed_in
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 绘制电网交互电量(正值上网,负值购电)
|
|
|
|
|
|
colors = ['brown' if x >= 0 else 'purple' for x in grid_power]
|
|
|
|
|
|
ax4.bar(sampled_hours, grid_power, color=colors, alpha=0.7, label='电网交互电量')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 添加零线
|
|
|
|
|
|
ax4.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 添加图例说明
|
|
|
|
|
|
from matplotlib.patches import Patch
|
|
|
|
|
|
legend_elements = [
|
|
|
|
|
|
Patch(facecolor='brown', alpha=0.7, label='上网电量 (+)'),
|
|
|
|
|
|
Patch(facecolor='purple', alpha=0.7, label='购电量 (-)')
|
|
|
|
|
|
]
|
|
|
|
|
|
ax4.legend(handles=legend_elements, loc='upper right')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
ax4.set_xlabel('时间 (小时)')
|
|
|
|
|
|
ax4.set_ylabel('功率 (MW)')
|
|
|
|
|
|
ax4.set_title(f'电网交互电量{title_suffix} (正值:上网, 负值:购电)')
|
|
|
|
|
|
ax4.grid(True, alpha=0.3)
|
|
|
|
|
|
ax4.set_xlim(0, max(sampled_hours))
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 调整布局
|
|
|
|
|
|
plt.tight_layout()
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
# 根据参数决定是否保存和显示图形
|
|
|
|
|
|
if display_only:
|
|
|
|
|
|
# 只显示,不保存
|
|
|
|
|
|
try:
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"无法显示图形窗口:{str(e)}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 保存图片
|
|
|
|
|
|
plt.savefig('system_curves.png', dpi=300, bbox_inches='tight')
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
# 根据参数决定是否显示图形窗口
|
|
|
|
|
|
if show_window:
|
|
|
|
|
|
try:
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"无法显示图形窗口:{str(e)}")
|
|
|
|
|
|
print("图形已保存为 'system_curves.png'")
|
|
|
|
|
|
else:
|
|
|
|
|
|
plt.close() # 关闭图形,不显示窗口
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 打印统计信息
|
|
|
|
|
|
print("\n=== 系统运行统计 ===")
|
|
|
|
|
|
print(f"所需储能总容量: {result['required_storage_capacity']:.2f} MWh")
|
|
|
|
|
|
print(f"最大储能状态: {max(result['storage_profile']):.2f} MWh")
|
|
|
|
|
|
print(f"最小储能状态: {min(result['storage_profile']):.2f} MWh")
|
|
|
|
|
|
print(f"总充电量: {sum(result['charge_profile']):.2f} MWh")
|
|
|
|
|
|
print(f"总放电量: {sum(result['discharge_profile']):.2f} MWh")
|
|
|
|
|
|
print(f"弃风率: {result['total_curtailment_wind_ratio']:.3f}")
|
|
|
|
|
|
print(f"弃光率: {result['total_curtailment_solar_ratio']:.3f}")
|
|
|
|
|
|
print(f"上网电量比例: {result['total_grid_feed_in_ratio']:.3f}")
|
2025-12-26 16:56:51 +08:00
|
|
|
|
|
|
|
|
|
|
# 计算总弃风弃光量
|
|
|
|
|
|
total_curtail_wind = sum(result['curtailed_wind'])
|
|
|
|
|
|
total_curtail_solar = sum(result['curtailed_solar'])
|
|
|
|
|
|
print(f"\n=== 弃风弃光统计 ===")
|
|
|
|
|
|
print(f"总弃风电量: {total_curtail_wind:.2f} MWh")
|
|
|
|
|
|
print(f"总弃光电量: {total_curtail_solar:.2f} MWh")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 计算购电和上网电量统计
|
|
|
|
|
|
total_grid_feed_in = sum(result['grid_feed_in'])
|
|
|
|
|
|
total_grid_purchase = sum(-x for x in result['grid_feed_in'] if x < 0) # 购电量
|
|
|
|
|
|
total_grid_feed_out = sum(x for x in result['grid_feed_in'] if x > 0) # 上网电量
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
print(f"\n=== 电网交互统计 ===")
|
|
|
|
|
|
if total_grid_feed_in >= 0:
|
|
|
|
|
|
print(f"净上网电量: {total_grid_feed_in:.2f} MWh")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"净购电量: {-total_grid_feed_in:.2f} MWh")
|
|
|
|
|
|
print(f"总购电量: {total_grid_purchase:.2f} MWh")
|
|
|
|
|
|
print(f"总上网电量: {total_grid_feed_out:.2f} MWh")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params, filename=None):
|
|
|
|
|
|
"""
|
2025-12-26 16:48:11 +08:00
|
|
|
|
将多能互补系统储能优化结果导出到Excel文件,包含运行数据、统计结果和系统参数。
|
2025-12-26 01:44:08 +08:00
|
|
|
|
|
2025-12-26 16:48:11 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
solar_output (list): 光伏出力曲线 (MW)
|
|
|
|
|
|
wind_output (list): 风电出力曲线 (MW)
|
|
|
|
|
|
thermal_output (list): 火电出力曲线 (MW)
|
|
|
|
|
|
load_demand (list): 负荷需求曲线 (MW)
|
|
|
|
|
|
result (dict): 包含以下键的优化结果字典:
|
|
|
|
|
|
- charge_profile: 储能充电功率曲线 (MW)
|
|
|
|
|
|
- discharge_profile: 储能放电功率曲线 (MW)
|
|
|
|
|
|
- storage_profile: 储能状态曲线 (MWh)
|
|
|
|
|
|
- curtailed_wind: 弃风功率曲线 (MW)
|
|
|
|
|
|
- curtailed_solar: 弃光功率曲线 (MW)
|
|
|
|
|
|
- grid_feed_in: 电网交互功率曲线 (MW, 负值表示购电)
|
|
|
|
|
|
- required_storage_capacity: 所需储能总容量 (MWh)
|
|
|
|
|
|
- total_curtailment_wind_ratio: 总弃风率
|
|
|
|
|
|
- total_curtailment_solar_ratio: 总弃光率
|
|
|
|
|
|
- total_grid_feed_in_ratio: 总上网电量比例
|
|
|
|
|
|
- energy_balance_check: 能量平衡校验结果
|
|
|
|
|
|
- capacity_limit_reached: 容量限制是否达到
|
|
|
|
|
|
params (object): 系统参数对象,包含各种技术参数
|
|
|
|
|
|
filename (str, optional): 输出文件名,如未提供则自动生成
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
str: 生成的Excel文件路径
|
|
|
|
|
|
|
|
|
|
|
|
生成的Excel文件包含以下工作表:
|
|
|
|
|
|
- 运行数据: 小时级运行数据
|
|
|
|
|
|
- 统计结果: 关键性能指标统计
|
|
|
|
|
|
- 系统参数: 输入参数汇总
|
|
|
|
|
|
- 说明: 文件使用说明
|
|
|
|
|
|
"""
|
|
|
|
|
|
"""
|
|
|
|
|
|
将优化结果导出到Excel文件
|
|
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
solar_output: 光伏出力曲线 (MW)
|
|
|
|
|
|
wind_output: 风电出力曲线 (MW)
|
|
|
|
|
|
thermal_output: 火电出力曲线 (MW)
|
|
|
|
|
|
load_demand: 负荷曲线 (MW)
|
|
|
|
|
|
result: 优化结果字典
|
|
|
|
|
|
params: 系统参数
|
|
|
|
|
|
filename: 输出文件名,如果为None则自动生成
|
|
|
|
|
|
"""
|
|
|
|
|
|
if filename is None:
|
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
|
filename = f"storage_optimization_results_{timestamp}.xlsx"
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
print(f"\n正在导出结果到Excel文件: {filename}")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 准备数据
|
|
|
|
|
|
hours = list(range(1, len(solar_output) + 1))
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 分离购电和上网电量
|
|
|
|
|
|
grid_purchase = []
|
|
|
|
|
|
grid_feed_out = []
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
for power in result['grid_feed_in']:
|
|
|
|
|
|
if power < 0:
|
|
|
|
|
|
grid_purchase.append(-power) # 购电,转换为正值
|
|
|
|
|
|
grid_feed_out.append(0)
|
|
|
|
|
|
else:
|
|
|
|
|
|
grid_purchase.append(0)
|
|
|
|
|
|
grid_feed_out.append(power) # 上网电量
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 创建主要数据DataFrame
|
|
|
|
|
|
data_df = pd.DataFrame({
|
|
|
|
|
|
'小时': hours,
|
|
|
|
|
|
'光伏出力(MW)': solar_output,
|
|
|
|
|
|
'风电出力(MW)': wind_output,
|
|
|
|
|
|
'火电出力(MW)': thermal_output,
|
|
|
|
|
|
'总发电量(MW)': [solar_output[i] + wind_output[i] + thermal_output[i] for i in range(len(solar_output))],
|
|
|
|
|
|
'负荷需求(MW)': load_demand,
|
|
|
|
|
|
'储能充电(MW)': result['charge_profile'],
|
|
|
|
|
|
'储能放电(MW)': result['discharge_profile'],
|
|
|
|
|
|
'储能状态(MWh)': result['storage_profile'],
|
|
|
|
|
|
'弃风量(MW)': result['curtailed_wind'],
|
|
|
|
|
|
'弃光量(MW)': result['curtailed_solar'],
|
|
|
|
|
|
'购电量(MW)': grid_purchase,
|
|
|
|
|
|
'上网电量(MW)': grid_feed_out
|
|
|
|
|
|
})
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 创建统计信息DataFrame
|
|
|
|
|
|
total_grid_feed_in = sum(result['grid_feed_in'])
|
|
|
|
|
|
total_grid_purchase = sum(-x for x in result['grid_feed_in'] if x < 0) # 购电量
|
|
|
|
|
|
total_grid_feed_out = sum(x for x in result['grid_feed_in'] if x > 0) # 上网电量
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
stats_df = pd.DataFrame({
|
|
|
|
|
|
'指标': [
|
|
|
|
|
|
'所需储能总容量',
|
|
|
|
|
|
'最大储能状态',
|
|
|
|
|
|
'最小储能状态',
|
|
|
|
|
|
'总充电量',
|
|
|
|
|
|
'总放电量',
|
|
|
|
|
|
'弃风率',
|
|
|
|
|
|
'弃光率',
|
|
|
|
|
|
'上网电量比例',
|
|
|
|
|
|
'能量平衡校验',
|
|
|
|
|
|
'净购电量/净上网电量',
|
|
|
|
|
|
'总购电量',
|
|
|
|
|
|
'总上网电量',
|
|
|
|
|
|
'容量限制是否达到'
|
|
|
|
|
|
],
|
|
|
|
|
|
'数值': [
|
|
|
|
|
|
f"{result['required_storage_capacity']:.2f} MWh",
|
|
|
|
|
|
f"{max(result['storage_profile']):.2f} MWh",
|
|
|
|
|
|
f"{min(result['storage_profile']):.2f} MWh",
|
|
|
|
|
|
f"{sum(result['charge_profile']):.2f} MWh",
|
|
|
|
|
|
f"{sum(result['discharge_profile']):.2f} MWh",
|
|
|
|
|
|
f"{result['total_curtailment_wind_ratio']:.3f}",
|
|
|
|
|
|
f"{result['total_curtailment_solar_ratio']:.3f}",
|
|
|
|
|
|
f"{result['total_grid_feed_in_ratio']:.3f}",
|
|
|
|
|
|
"通过" if result['energy_balance_check'] else "未通过",
|
|
|
|
|
|
f"{-total_grid_feed_in:.2f} MWh" if total_grid_feed_in < 0 else f"{total_grid_feed_in:.2f} MWh",
|
|
|
|
|
|
f"{total_grid_purchase:.2f} MWh",
|
|
|
|
|
|
f"{total_grid_feed_out:.2f} MWh",
|
|
|
|
|
|
"是" if result['capacity_limit_reached'] else "否"
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 创建系统参数DataFrame
|
|
|
|
|
|
params_df = pd.DataFrame({
|
|
|
|
|
|
'参数名称': [
|
|
|
|
|
|
'最大弃风率',
|
|
|
|
|
|
'最大弃光率',
|
|
|
|
|
|
'最大上网电量比例',
|
|
|
|
|
|
'储能效率',
|
|
|
|
|
|
'放电倍率',
|
|
|
|
|
|
'充电倍率',
|
2025-12-26 02:28:54 +08:00
|
|
|
|
'最大储能容量',
|
|
|
|
|
|
'额定火电装机容量',
|
|
|
|
|
|
'额定光伏装机容量',
|
|
|
|
|
|
'额定风电装机容量',
|
|
|
|
|
|
'火电可用发电量',
|
|
|
|
|
|
'光伏可用发电量',
|
|
|
|
|
|
'风电可用发电量'
|
2025-12-26 01:44:08 +08:00
|
|
|
|
],
|
|
|
|
|
|
'参数值': [
|
|
|
|
|
|
params.max_curtailment_wind,
|
|
|
|
|
|
params.max_curtailment_solar,
|
|
|
|
|
|
params.max_grid_ratio,
|
|
|
|
|
|
params.storage_efficiency,
|
|
|
|
|
|
params.discharge_rate,
|
|
|
|
|
|
params.charge_rate,
|
2025-12-26 02:28:54 +08:00
|
|
|
|
params.max_storage_capacity if params.max_storage_capacity is not None else "无限制",
|
|
|
|
|
|
params.rated_thermal_capacity,
|
|
|
|
|
|
params.rated_solar_capacity,
|
|
|
|
|
|
params.rated_wind_capacity,
|
|
|
|
|
|
params.available_thermal_energy,
|
|
|
|
|
|
params.available_solar_energy,
|
|
|
|
|
|
params.available_wind_energy
|
2025-12-26 01:44:08 +08:00
|
|
|
|
],
|
|
|
|
|
|
'单位': [
|
|
|
|
|
|
"比例",
|
|
|
|
|
|
"比例",
|
|
|
|
|
|
"比例",
|
|
|
|
|
|
"效率",
|
|
|
|
|
|
"C-rate",
|
|
|
|
|
|
"C-rate",
|
2025-12-26 02:28:54 +08:00
|
|
|
|
"MWh",
|
|
|
|
|
|
"MW",
|
|
|
|
|
|
"MW",
|
|
|
|
|
|
"MW",
|
|
|
|
|
|
"MWh",
|
|
|
|
|
|
"MWh",
|
2025-12-26 01:44:08 +08:00
|
|
|
|
"MWh"
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 写入Excel文件
|
|
|
|
|
|
with pd.ExcelWriter(filename, engine='openpyxl') as writer:
|
|
|
|
|
|
# 写入主要数据
|
|
|
|
|
|
data_df.to_excel(writer, sheet_name='运行数据', index=False)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 写入统计信息
|
|
|
|
|
|
stats_df.to_excel(writer, sheet_name='统计结果', index=False)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 写入系统参数
|
|
|
|
|
|
params_df.to_excel(writer, sheet_name='系统参数', index=False)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 创建说明工作表
|
|
|
|
|
|
description_df = pd.DataFrame({
|
|
|
|
|
|
'项目': [
|
|
|
|
|
|
'文件说明',
|
|
|
|
|
|
'生成时间',
|
|
|
|
|
|
'数据长度',
|
|
|
|
|
|
'数据内容',
|
|
|
|
|
|
'注意事项'
|
|
|
|
|
|
],
|
|
|
|
|
|
'内容': [
|
|
|
|
|
|
'多能互补系统储能容量优化结果',
|
|
|
|
|
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
|
f"{len(solar_output)} 小时",
|
|
|
|
|
|
'包含发电、负荷、储能、弃风弃光、购电上网等完整数据',
|
|
|
|
|
|
'负值表示购电,正值表示上网电量'
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
description_df.to_excel(writer, sheet_name='说明', index=False)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
print(f"结果已成功导出到: {filename}")
|
|
|
|
|
|
return filename
|
2025-12-25 18:06:12 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_yearly_data():
|
|
|
|
|
|
"""生成8760小时的示例数据"""
|
|
|
|
|
|
# 基础日模式
|
|
|
|
|
|
daily_solar = [0.0] * 6 + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + [0.0] * 6
|
|
|
|
|
|
daily_wind = [2.0, 3.0, 4.0, 3.0, 2.0, 1.0] * 4
|
|
|
|
|
|
daily_thermal = [5.0] * 24
|
2025-12-26 16:48:11 +08:00
|
|
|
|
daily_load = [3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 18.0,
|
2025-12-25 18:06:12 +08:00
|
|
|
|
16.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0]
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 添加季节性变化
|
|
|
|
|
|
import random
|
|
|
|
|
|
random.seed(42)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
yearly_solar = []
|
|
|
|
|
|
yearly_wind = []
|
|
|
|
|
|
yearly_thermal = []
|
|
|
|
|
|
yearly_load = []
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
for day in range(365):
|
|
|
|
|
|
# 季节性因子(夏季光伏更强,冬季负荷更高)
|
|
|
|
|
|
season_factor = 1.0 + 0.3 * np.sin(2 * np.pi * day / 365)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
for hour in range(24):
|
|
|
|
|
|
# 添加随机变化
|
|
|
|
|
|
solar_variation = 1.0 + 0.2 * (random.random() - 0.5)
|
|
|
|
|
|
wind_variation = 1.0 + 0.3 * (random.random() - 0.5)
|
|
|
|
|
|
load_variation = 1.0 + 0.1 * (random.random() - 0.5)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
yearly_solar.append(daily_solar[hour] * season_factor * solar_variation)
|
|
|
|
|
|
yearly_wind.append(daily_wind[hour] * wind_variation)
|
|
|
|
|
|
yearly_thermal.append(daily_thermal[hour])
|
|
|
|
|
|
yearly_load.append(daily_load[hour] * (2.0 - season_factor) * load_variation)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
return yearly_solar, yearly_wind, yearly_thermal, yearly_load
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""主函数"""
|
|
|
|
|
|
import sys
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 检查命令行参数
|
2025-12-25 19:56:21 +08:00
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
|
|
print_usage()
|
|
|
|
|
|
return
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
command = sys.argv[1]
|
2025-12-26 01:06:53 +08:00
|
|
|
|
show_window = '--show' in sys.argv # 检查是否包含--show参数
|
|
|
|
|
|
display_only = '--display-only' in sys.argv # 检查是否只显示不保存
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
|
|
|
|
|
if command == '--excel':
|
2025-12-25 19:56:21 +08:00
|
|
|
|
if len(sys.argv) < 3:
|
|
|
|
|
|
print("错误:请指定Excel文件路径")
|
|
|
|
|
|
print("用法:python main.py --excel <文件路径>")
|
|
|
|
|
|
return
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
excel_file = sys.argv[2]
|
|
|
|
|
|
print(f"从Excel文件读取数据:{excel_file}")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
try:
|
2025-12-26 01:06:53 +08:00
|
|
|
|
data = read_excel_data(excel_file, include_parameters=True)
|
2025-12-25 19:56:21 +08:00
|
|
|
|
solar_output = data['solar_output']
|
|
|
|
|
|
wind_output = data['wind_output']
|
|
|
|
|
|
thermal_output = data['thermal_output']
|
|
|
|
|
|
load_demand = data['load_demand']
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
print(f"成功读取{data['data_type']}小时数据")
|
|
|
|
|
|
print(f"原始数据长度:{data['original_length']}小时")
|
|
|
|
|
|
print(f"处理后数据长度:{len(solar_output)}小时")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
# 使用Excel中的系统参数
|
|
|
|
|
|
if 'system_parameters' in data:
|
|
|
|
|
|
params = data['system_parameters']
|
|
|
|
|
|
print("\n使用Excel中的系统参数:")
|
|
|
|
|
|
print(f" 最大弃风率: {params.max_curtailment_wind}")
|
|
|
|
|
|
print(f" 最大弃光率: {params.max_curtailment_solar}")
|
|
|
|
|
|
print(f" 最大上网电量比例: {params.max_grid_ratio}")
|
|
|
|
|
|
print(f" 储能效率: {params.storage_efficiency}")
|
|
|
|
|
|
print(f" 放电倍率: {params.discharge_rate}")
|
|
|
|
|
|
print(f" 充电倍率: {params.charge_rate}")
|
|
|
|
|
|
print(f" 最大储能容量: {params.max_storage_capacity}")
|
2025-12-26 02:28:54 +08:00
|
|
|
|
print(f" 额定火电装机容量: {params.rated_thermal_capacity} MW")
|
|
|
|
|
|
print(f" 额定光伏装机容量: {params.rated_solar_capacity} MW")
|
|
|
|
|
|
print(f" 额定风电装机容量: {params.rated_wind_capacity} MW")
|
|
|
|
|
|
print(f" 火电可用发电量: {params.available_thermal_energy} MWh")
|
|
|
|
|
|
print(f" 光伏可用发电量: {params.available_solar_energy} MWh")
|
|
|
|
|
|
print(f" 风电可用发电量: {params.available_wind_energy} MWh")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
else:
|
|
|
|
|
|
print("\n警告:未找到系统参数,使用默认参数")
|
|
|
|
|
|
params = SystemParameters(
|
|
|
|
|
|
max_curtailment_wind=0.1,
|
|
|
|
|
|
max_curtailment_solar=0.1,
|
|
|
|
|
|
max_grid_ratio=0.2,
|
|
|
|
|
|
storage_efficiency=0.9,
|
|
|
|
|
|
discharge_rate=1.0,
|
2025-12-26 02:28:54 +08:00
|
|
|
|
charge_rate=1.0,
|
|
|
|
|
|
rated_thermal_capacity=100.0,
|
|
|
|
|
|
rated_solar_capacity=100.0,
|
|
|
|
|
|
rated_wind_capacity=100.0,
|
|
|
|
|
|
available_thermal_energy=2400.0,
|
|
|
|
|
|
available_solar_energy=600.0,
|
|
|
|
|
|
available_wind_energy=1200.0
|
2025-12-26 01:06:53 +08:00
|
|
|
|
)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
# 显示数据统计
|
|
|
|
|
|
stats = analyze_excel_data(excel_file)
|
|
|
|
|
|
if stats:
|
|
|
|
|
|
print("\n数据统计:")
|
|
|
|
|
|
print(f" 总发电量: {stats['total_generation']:.2f} MW")
|
|
|
|
|
|
print(f" 总负荷: {stats['total_load']:.2f} MW")
|
|
|
|
|
|
print(f" 最大光伏出力: {stats['max_solar']:.2f} MW")
|
|
|
|
|
|
print(f" 最大风电出力: {stats['max_wind']:.2f} MW")
|
|
|
|
|
|
print(f" 最大负荷: {stats['max_load']:.2f} MW")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"读取Excel文件失败:{str(e)}")
|
|
|
|
|
|
return
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
elif command == '--create-template':
|
|
|
|
|
|
template_type = sys.argv[2] if len(sys.argv) > 2 else "8760"
|
|
|
|
|
|
template_file = f"data_template_{template_type}.xlsx"
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
print(f"创建{template_type}小时Excel模板:{template_file}")
|
|
|
|
|
|
create_excel_template(template_file, template_type)
|
|
|
|
|
|
return
|
2025-12-25 18:06:12 +08:00
|
|
|
|
else:
|
|
|
|
|
|
print("使用24小时示例数据...")
|
|
|
|
|
|
# 示例数据
|
|
|
|
|
|
solar_output = [0.0] * 6 + [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0] + [0.0] * 6
|
|
|
|
|
|
wind_output = [2.0, 3.0, 4.0, 3.0, 2.0, 1.0] * 4
|
|
|
|
|
|
thermal_output = [5.0] * 24
|
2025-12-26 16:48:11 +08:00
|
|
|
|
load_demand = [3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 18.0,
|
2025-12-25 18:06:12 +08:00
|
|
|
|
16.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0]
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
# 使用默认系统参数
|
|
|
|
|
|
params = SystemParameters(
|
|
|
|
|
|
max_curtailment_wind=0.1,
|
|
|
|
|
|
max_curtailment_solar=0.1,
|
|
|
|
|
|
max_grid_ratio=0.2,
|
|
|
|
|
|
storage_efficiency=0.9,
|
|
|
|
|
|
discharge_rate=1.0,
|
2025-12-26 02:28:54 +08:00
|
|
|
|
charge_rate=1.0,
|
|
|
|
|
|
rated_thermal_capacity=100.0,
|
|
|
|
|
|
rated_solar_capacity=100.0,
|
|
|
|
|
|
rated_wind_capacity=100.0,
|
|
|
|
|
|
available_thermal_energy=2400.0,
|
|
|
|
|
|
available_solar_energy=600.0,
|
|
|
|
|
|
available_wind_energy=1200.0
|
2025-12-26 01:06:53 +08:00
|
|
|
|
)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
# 显示当前使用的系统参数
|
|
|
|
|
|
print("\n=== 当前使用的系统参数 ===")
|
|
|
|
|
|
print(f"最大弃风率: {params.max_curtailment_wind}")
|
|
|
|
|
|
print(f"最大弃光率: {params.max_curtailment_solar}")
|
|
|
|
|
|
print(f"最大上网电量比例: {params.max_grid_ratio}")
|
|
|
|
|
|
print(f"储能效率: {params.storage_efficiency}")
|
|
|
|
|
|
print(f"放电倍率: {params.discharge_rate}")
|
|
|
|
|
|
print(f"充电倍率: {params.charge_rate}")
|
|
|
|
|
|
print(f"最大储能容量: {params.max_storage_capacity if params.max_storage_capacity is not None else '无限制'}")
|
2025-12-26 02:28:54 +08:00
|
|
|
|
print(f"额定火电装机容量: {params.rated_thermal_capacity} MW")
|
|
|
|
|
|
print(f"额定光伏装机容量: {params.rated_solar_capacity} MW")
|
|
|
|
|
|
print(f"额定风电装机容量: {params.rated_wind_capacity} MW")
|
|
|
|
|
|
print(f"火电可用发电量: {params.available_thermal_energy} MWh")
|
|
|
|
|
|
print(f"光伏可用发电量: {params.available_solar_energy} MWh")
|
|
|
|
|
|
print(f"风电可用发电量: {params.available_wind_energy} MWh")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
print("=" * 40)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 计算最优储能容量
|
|
|
|
|
|
print("正在计算最优储能容量...")
|
|
|
|
|
|
result = optimize_storage_capacity(
|
|
|
|
|
|
solar_output, wind_output, thermal_output, load_demand, params
|
|
|
|
|
|
)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
# 绘制曲线
|
|
|
|
|
|
print("正在绘制系统运行曲线...")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, show_window, display_only)
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:44:08 +08:00
|
|
|
|
# 导出结果到Excel
|
|
|
|
|
|
try:
|
|
|
|
|
|
export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"导出Excel文件失败:{str(e)}")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-26 01:06:53 +08:00
|
|
|
|
if display_only:
|
|
|
|
|
|
print("\n正在显示图形窗口...")
|
|
|
|
|
|
elif show_window:
|
|
|
|
|
|
print("\n曲线图已保存为 'system_curves.png' 并显示图形窗口")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("\n曲线图已保存为 'system_curves.png'")
|
2025-12-25 18:06:12 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
def print_usage():
|
|
|
|
|
|
"""打印使用说明"""
|
|
|
|
|
|
print("多能互补系统储能容量优化程序")
|
|
|
|
|
|
print("\n使用方法:")
|
|
|
|
|
|
print(" python main.py --excel <文件路径> # 从Excel文件读取数据")
|
2025-12-26 16:48:11 +08:00
|
|
|
|
|
2025-12-25 19:56:21 +08:00
|
|
|
|
print(" python main.py --create-template [类型] # 创建Excel模板(24或8760)")
|
|
|
|
|
|
print(" python main.py # 使用24小时示例数据")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
print(" python main.py --show # 显示图形窗口(可与其他参数组合使用)")
|
|
|
|
|
|
print(" python main.py --display-only # 只显示图形窗口,不保存文件")
|
2025-12-25 19:56:21 +08:00
|
|
|
|
print("\n示例:")
|
|
|
|
|
|
print(" python main.py --excel data.xlsx")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
print(" python main.py --excel data.xlsx --show")
|
|
|
|
|
|
print(" python main.py --excel data.xlsx --display-only")
|
2025-12-25 19:56:21 +08:00
|
|
|
|
print(" python main.py --create-template 8760")
|
|
|
|
|
|
print(" python main.py --create-template 24")
|
2025-12-26 01:06:53 +08:00
|
|
|
|
print(" python main.py --display-only # 使用示例数据并只显示图形窗口")
|
2025-12-25 19:56:21 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-25 18:06:12 +08:00
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|