修复了储能电量不平衡的问题。

This commit is contained in:
dmy
2025-12-27 12:25:01 +08:00
parent a522132ede
commit 164b9da026
2 changed files with 251 additions and 101 deletions

75
main.py
View File

@@ -25,7 +25,7 @@ plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False plt.rcParams['axes.unicode_minus'] = False
def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, storage_efficiency=0.9, show_window=False, display_only=False): def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, storage_efficiency=0.9, show_window=False, display_only=False, output_dir=None):
""" """
绘制系统运行曲线 绘制系统运行曲线
@@ -37,6 +37,7 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r
result: 优化结果字典 result: 优化结果字典
show_window: 是否显示图形窗口 show_window: 是否显示图形窗口
display_only: 是否只显示不保存文件 display_only: 是否只显示不保存文件
output_dir: 输出目录路径,默认为 None当前目录
""" """
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
@@ -148,15 +149,17 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r
plt.tight_layout() plt.tight_layout()
# 根据参数决定是否保存和显示图形 # 根据参数决定是否保存和显示图形
if display_only: if not display_only:
# 只显示,不保存 # 确定输出目录
try: if output_dir is None:
plt.show() output_dir = 'results'
except Exception as e:
print(f"无法显示图形窗口:{str(e)}") # 创建输出目录(如果不存在)
else: os.makedirs(output_dir, exist_ok=True)
# 保存图片
plt.savefig('system_curves.png', dpi=300, bbox_inches='tight') # 保存图片到指定目录
output_path = os.path.join(output_dir, 'system_curves.png')
plt.savefig(output_path, dpi=300, bbox_inches='tight')
# 根据参数决定是否显示图形窗口 # 根据参数决定是否显示图形窗口
if show_window: if show_window:
@@ -164,7 +167,7 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r
plt.show() plt.show()
except Exception as e: except Exception as e:
print(f"无法显示图形窗口:{str(e)}") print(f"无法显示图形窗口:{str(e)}")
print("图形已保存为 'system_curves.png'") print(f"图形已保存为 '{output_path}'")
else: else:
plt.close() # 关闭图形,不显示窗口 plt.close() # 关闭图形,不显示窗口
@@ -238,7 +241,7 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r
print(f"储能损耗率: {(storage_loss/total_charge*100) if total_charge > 0 else 0:.2f}%") print(f"储能损耗率: {(storage_loss/total_charge*100) if total_charge > 0 else 0:.2f}%")
def export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params, filename=None): def export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params, filename=None, output_dir=None):
""" """
将多能互补系统储能优化结果导出到Excel文件包含运行数据、统计结果和系统参数。 将多能互补系统储能优化结果导出到Excel文件包含运行数据、统计结果和系统参数。
@@ -262,6 +265,7 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema
- capacity_limit_reached: 容量限制是否达到 - capacity_limit_reached: 容量限制是否达到
params (object): 系统参数对象,包含各种技术参数 params (object): 系统参数对象,包含各种技术参数
filename (str, optional): 输出文件名,如未提供则自动生成 filename (str, optional): 输出文件名,如未提供则自动生成
output_dir (str, optional): 输出目录路径,默认为 None使用 results 目录)
Returns: Returns:
str: 生成的Excel文件路径 str: 生成的Excel文件路径
@@ -288,7 +292,17 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"storage_optimization_results_{timestamp}.xlsx" filename = f"storage_optimization_results_{timestamp}.xlsx"
print(f"\n正在导出结果到Excel文件: {filename}") # 确定输出目录
if output_dir is None:
output_dir = 'results'
# 创建输出目录(如果不存在)
os.makedirs(output_dir, exist_ok=True)
# 构建完整的输出路径
output_path = os.path.join(output_dir, filename)
print(f"\n正在导出结果到Excel文件: {output_path}")
# 准备数据 # 准备数据
hours = list(range(1, len(solar_output) + 1)) hours = list(range(1, len(solar_output) + 1))
@@ -410,7 +424,7 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema
}) })
# 写入Excel文件 # 写入Excel文件
with pd.ExcelWriter(filename, engine='openpyxl') as writer: with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# 写入主要数据 # 写入主要数据
data_df.to_excel(writer, sheet_name='运行数据', index=False) data_df.to_excel(writer, sheet_name='运行数据', index=False)
@@ -439,8 +453,8 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema
}) })
description_df.to_excel(writer, sheet_name='说明', index=False) description_df.to_excel(writer, sheet_name='说明', index=False)
print(f"结果已成功导出到: {filename}") print(f"结果已成功导出到: {output_path}")
return filename return output_path
def generate_yearly_data(): def generate_yearly_data():
@@ -492,6 +506,17 @@ def main():
show_window = '--show' in sys.argv # 检查是否包含--show参数 show_window = '--show' in sys.argv # 检查是否包含--show参数
display_only = '--display-only' in sys.argv # 检查是否只显示不保存 display_only = '--display-only' in sys.argv # 检查是否只显示不保存
# 解析输出目录参数
output_dir = None
if '--output' in sys.argv:
output_index = sys.argv.index('--output')
if output_index + 1 < len(sys.argv):
output_dir = sys.argv[output_index + 1]
else:
print("错误:--output 参数需要指定目录路径")
print("用法python main.py --output <目录路径>")
return
if command == '--excel': if command == '--excel':
if len(sys.argv) < 3: if len(sys.argv) < 3:
print("错误请指定Excel文件路径") print("错误请指定Excel文件路径")
@@ -619,27 +644,29 @@ def main():
# 绘制曲线 # 绘制曲线
print("正在绘制系统运行曲线...") print("正在绘制系统运行曲线...")
plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, params.storage_efficiency, show_window, display_only) plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, params.storage_efficiency, show_window, display_only, output_dir)
# 导出结果到Excel # 导出结果到Excel
try: try:
export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params) export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params, output_dir=output_dir)
except Exception as e: except Exception as e:
print(f"导出Excel文件失败{str(e)}") print(f"导出Excel文件失败{str(e)}")
if display_only: if display_only:
print("\n正在显示图形窗口...") print("\n正在显示图形窗口...")
elif show_window: elif show_window:
print("\n曲线图已保存为 'system_curves.png' 并显示图形窗口") output_path = os.path.join(output_dir if output_dir else 'results', 'system_curves.png')
print(f"\n曲线图已保存为 '{output_path}' 并显示图形窗口")
else: else:
print("\n曲线图已保存为 'system_curves.png'") output_path = os.path.join(output_dir if output_dir else 'results', 'system_curves.png')
print(f"\n曲线图已保存为 '{output_path}'")
def print_usage(): def print_usage():
"""打印使用说明""" """打印使用说明"""
print("多能互补系统储能容量优化程序") print("多能互补系统储能容量优化程序")
print("\n使用方法:") print("\n使用方法:")
print(" python main.py --excel <文件路径> # 从Excel文件读取数据") print(" python main.py --excel <文件路径> # 从Excel文件读取数据")
print(" python main.py --output <目录路径> # 指定输出目录默认results")
print(" python main.py --create-template [类型] # 创建Excel模板(24或8760)") print(" python main.py --create-template [类型] # 创建Excel模板(24或8760)")
print(" python main.py # 使用24小时示例数据") print(" python main.py # 使用24小时示例数据")
print(" python main.py --show # 显示图形窗口(可与其他参数组合使用)") print(" python main.py --show # 显示图形窗口(可与其他参数组合使用)")
@@ -647,10 +674,16 @@ def print_usage():
print("\n示例:") print("\n示例:")
print(" python main.py --excel data.xlsx") print(" python main.py --excel data.xlsx")
print(" python main.py --excel data.xlsx --show") print(" python main.py --excel data.xlsx --show")
print(" python main.py --excel data.xlsx --output my_results")
print(" python main.py --excel data.xlsx --display-only") print(" python main.py --excel data.xlsx --display-only")
print(" python main.py --create-template 8760") print(" python main.py --create-template 8760")
print(" python main.py --create-template 24") print(" python main.py --create-template 24")
print(" python main.py --display-only # 使用示例数据并只显示图形窗口") print(" python main.py --display-only # 使用示例数据并只显示图形窗口")
print(" python main.py --output custom_results # 使用示例数据,输出到 custom_results 目录")
print("\n说明:")
print(" - 结果文件默认保存到 results 目录")
print(" - 使用 --output 参数可指定自定义输出目录")
print(" - 如果输出目录不存在,程序会自动创建")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -109,7 +109,8 @@ def calculate_energy_balance(
thermal_output: List[float], thermal_output: List[float],
load_demand: List[float], load_demand: List[float],
params: SystemParameters, params: SystemParameters,
storage_capacity: float storage_capacity: float,
initial_soc: float = 0.0
) -> Dict[str, List[float]]: ) -> Dict[str, List[float]]:
""" """
计算给定储能容量下的系统电能平衡 计算给定储能容量下的系统电能平衡
@@ -121,6 +122,7 @@ def calculate_energy_balance(
load_demand: 负荷曲线 (MW) - 支持24小时或8760小时 load_demand: 负荷曲线 (MW) - 支持24小时或8760小时
params: 系统参数配置 params: 系统参数配置
storage_capacity: 储能容量 (MWh) storage_capacity: 储能容量 (MWh)
initial_soc: 初始储能状态 (MWh)默认为0.0
Returns: Returns:
包含各种功率曲线的字典 包含各种功率曲线的字典
@@ -140,6 +142,9 @@ def calculate_energy_balance(
curtailed_solar = np.zeros(hours) # 弃光量 (MW) curtailed_solar = np.zeros(hours) # 弃光量 (MW)
grid_feed_in = np.zeros(hours) # 上网电量 (MW) grid_feed_in = np.zeros(hours) # 上网电量 (MW)
# 设置初始储能状态
storage_soc[0] = initial_soc
# 计算总发电潜力 # 计算总发电潜力
total_potential_wind = np.sum(wind) total_potential_wind = np.sum(wind)
total_potential_solar = np.sum(solar) total_potential_solar = np.sum(solar)
@@ -176,8 +181,8 @@ def calculate_energy_balance(
# 逐小时计算 # 逐小时计算
for hour in range(hours): for hour in range(hours):
# 确保储能状态不为负 # 确保储能状态不为负且不超过容量
storage_soc[hour] = max(0, storage_soc[hour]) storage_soc[hour] = max(0, min(storage_capacity, storage_soc[hour]))
# 可用发电量(未考虑弃风弃光) # 可用发电量(未考虑弃风弃光)
available_generation = thermal[hour] + wind[hour] + solar[hour] available_generation = thermal[hour] + wind[hour] + solar[hour]
@@ -312,6 +317,83 @@ def calculate_energy_balance(
} }
def find_periodic_steady_state(
solar_output: List[float],
wind_output: List[float],
thermal_output: List[float],
load_demand: List[float],
params: SystemParameters,
storage_capacity: float,
soc_convergence_threshold: float = 0.001,
max_iterations: int = 100
) -> Dict[str, List[float]]:
"""
通过迭代找到满足周期性平衡的储能初始状态
步骤:
1. 从初始SOC=0开始运行一次全年仿真记录最后一小时的SOC值
2. 将这个SOC值作为新的"初始SOC",再次运行仿真
3. 重复上述过程直到首尾SOC的差值小于设定的阈值
Args:
solar_output: 光伏出力曲线 (MW) - 支持24小时或8760小时
wind_output: 风电出力曲线 (MW) - 支持24小时或8760小时
thermal_output: 火电出力曲线 (MW) - 支持24小时或8760小时
load_demand: 负荷曲线 (MW) - 支持24小时或8760小时
params: 系统参数配置
storage_capacity: 储能容量 (MWh)
soc_convergence_threshold: SOC收敛阈值相对于容量的比例默认0.1%
max_iterations: 最大迭代次数
Returns:
包含各种功率曲线的字典,且满足周期性平衡条件
"""
# 计算收敛阈值的绝对值
absolute_threshold = storage_capacity * soc_convergence_threshold
# 初始SOC从0开始
initial_soc = 0.0
iteration = 0
soc_diff = float('inf')
print(f"正在寻找周期性平衡状态SOC收敛阈值: {absolute_threshold:.4f} MWh...")
while iteration < max_iterations and soc_diff > absolute_threshold:
# 运行仿真
balance_result = calculate_energy_balance(
solar_output, wind_output, thermal_output, load_demand,
params, storage_capacity, initial_soc
)
# 获取初始和最终的SOC
storage_initial = balance_result['storage_profile'][0]
storage_final = balance_result['storage_profile'][-1]
# 计算SOC差值
soc_diff = abs(storage_final - storage_initial)
# 更新初始SOC使用最终SOC作为下一次的初始SOC
initial_soc = storage_final
# 确保SOC在合理范围内
initial_soc = max(0, min(storage_capacity, initial_soc))
iteration += 1
# 输出迭代信息每10次迭代或最后一次
if iteration % 10 == 0 or iteration == 1 or soc_diff <= absolute_threshold:
print(f" 迭代 {iteration}: 初始SOC={storage_initial:.4f} MWh, "
f"最终SOC={storage_final:.4f} MWh, 差值={soc_diff:.4f} MWh")
# 输出收敛结果
if soc_diff <= absolute_threshold:
print(f"✓ 周期性平衡收敛成功(迭代{iteration}SOC差值={soc_diff:.4f} MWh")
else:
print(f"⚠ 未达到收敛条件(迭代{iteration}SOC差值={soc_diff:.4f} MWh")
return balance_result
def check_constraints( def check_constraints(
solar_output: List[float], solar_output: List[float],
wind_output: List[float], wind_output: List[float],
@@ -395,6 +477,13 @@ def optimize_storage_capacity(
else: else:
upper_bound = theoretical_max upper_bound = theoretical_max
# 判断数据类型24小时或8760小时
data_length = len(solar_output)
is_yearly_data = data_length == 8760
if is_yearly_data:
print(f"处理8760小时全年数据启用周期性平衡优化...")
# 二分搜索寻找最小储能容量 # 二分搜索寻找最小储能容量
best_capacity = upper_bound best_capacity = upper_bound
best_result = None best_result = None
@@ -404,6 +493,14 @@ def optimize_storage_capacity(
mid_capacity = (lower_bound + upper_bound) / 2 mid_capacity = (lower_bound + upper_bound) / 2
# 计算当前容量下的平衡 # 计算当前容量下的平衡
# 对于8760小时数据使用周期性平衡函数
# 对于24小时数据使用普通平衡函数初始SOC=0
if is_yearly_data:
balance_result = find_periodic_steady_state(
solar_output, wind_output, thermal_output, load_demand,
params, mid_capacity
)
else:
balance_result = calculate_energy_balance( balance_result = calculate_energy_balance(
solar_output, wind_output, thermal_output, load_demand, params, mid_capacity solar_output, wind_output, thermal_output, load_demand, params, mid_capacity
) )
@@ -467,6 +564,12 @@ def optimize_storage_capacity(
print("使用最大允许容量进行计算,但某些约束条件可能无法满足") print("使用最大允许容量进行计算,但某些约束条件可能无法满足")
# 使用最大允许容量计算结果 # 使用最大允许容量计算结果
if is_yearly_data:
balance_result = find_periodic_steady_state(
solar_output, wind_output, thermal_output, load_demand,
params, params.max_storage_capacity
)
else:
balance_result = calculate_energy_balance( balance_result = calculate_energy_balance(
solar_output, wind_output, thermal_output, load_demand, params, params.max_storage_capacity solar_output, wind_output, thermal_output, load_demand, params, params.max_storage_capacity
) )
@@ -475,6 +578,12 @@ def optimize_storage_capacity(
best_capacity = params.max_storage_capacity best_capacity = params.max_storage_capacity
elif best_result is None: elif best_result is None:
# 如果没有找到可行解(且没有容量上限限制),使用最大容量 # 如果没有找到可行解(且没有容量上限限制),使用最大容量
if is_yearly_data:
balance_result = find_periodic_steady_state(
solar_output, wind_output, thermal_output, load_demand,
params, upper_bound
)
else:
balance_result = calculate_energy_balance( balance_result = calculate_energy_balance(
solar_output, wind_output, thermal_output, load_demand, params, upper_bound solar_output, wind_output, thermal_output, load_demand, params, upper_bound
) )
@@ -513,6 +622,14 @@ def optimize_storage_capacity(
tolerance = max(10.0, total_generation * 0.15) tolerance = max(10.0, total_generation * 0.15)
energy_balance_check = energy_balance_error < tolerance energy_balance_check = energy_balance_error < tolerance
# 输出周期性平衡信息
if is_yearly_data:
soc_initial_final_diff = abs(best_result['storage_profile'][-1] - best_result['storage_profile'][0])
print(f"\n周期性平衡信息:")
print(f" 初始SOC: {best_result['storage_profile'][0]:.4f} MWh")
print(f" 最终SOC: {best_result['storage_profile'][-1]:.4f} MWh")
print(f" SOC差值: {soc_initial_final_diff:.4f} MWh")
# 返回最终结果 # 返回最终结果
return { return {
'required_storage_capacity': best_capacity, 'required_storage_capacity': best_capacity,