diff --git a/main.py b/main.py index 0942fda..12097ba 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,7 @@ plt.rcParams['axes.unicode_minus'] = False def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, show_window=False, display_only=False): """ 绘制系统运行曲线 - + Args: solar_output: 光伏出力曲线 (MW) - 支持24小时或8760小时 wind_output: 风电出力曲线 (MW) - 支持24小时或8760小时 @@ -36,13 +36,13 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r """ import matplotlib.pyplot as plt import numpy as np - + # 设置中文字体 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 hours = np.arange(len(solar_output)) data_length = len(solar_output) - + # 确定图表标题和采样率 if data_length == 8760: title_suffix = " (全年8760小时)" @@ -68,34 +68,34 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r sampled_charge = result['charge_profile'] sampled_discharge = result['discharge_profile'] sampled_grid_feed_in = result['grid_feed_in'] - + # 创建图形(4个子图) fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(14, 16)) fig.suptitle('多能互补系统24小时运行曲线', fontsize=16, fontweight='bold') - + # === 第一个子图:发电和负荷曲线 === 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='光伏出力') - + # 计算总发电量 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='总发电量') - + 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)) - + # === 第二个子图:储能充放电曲线 === discharge_power = [-x for x in sampled_discharge] # 放电显示为负值 - + ax2.bar(sampled_hours, sampled_charge, color='green', alpha=0.7, label='充电功率') ax2.bar(sampled_hours, discharge_power, color='red', alpha=0.7, label='放电功率') - + ax2.set_xlabel('时间 (小时)') ax2.set_ylabel('功率 (MW)') ax2.set_title(f'储能充放电功率{title_suffix}') @@ -103,29 +103,29 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r ax2.grid(True, alpha=0.3) ax2.set_xlim(0, max(sampled_hours)) ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5) - + # === 第三个子图:储能状态曲线 === 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') - + 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) - + # === 第四个子图:购电量和上网电量曲线 === # 直接使用原始数据:正值表示上网电量,负值表示购电量 grid_power = sampled_grid_feed_in - + # 绘制电网交互电量(正值上网,负值购电) colors = ['brown' if x >= 0 else 'purple' for x in grid_power] ax4.bar(sampled_hours, grid_power, color=colors, alpha=0.7, label='电网交互电量') - + # 添加零线 ax4.axhline(y=0, color='black', linestyle='-', linewidth=0.5) - + # 添加图例说明 from matplotlib.patches import Patch legend_elements = [ @@ -133,16 +133,16 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r Patch(facecolor='purple', alpha=0.7, label='购电量 (-)') ] ax4.legend(handles=legend_elements, loc='upper right') - + 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)) - + # 调整布局 plt.tight_layout() - + # 根据参数决定是否保存和显示图形 if display_only: # 只显示,不保存 @@ -153,7 +153,7 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r else: # 保存图片 plt.savefig('system_curves.png', dpi=300, bbox_inches='tight') - + # 根据参数决定是否显示图形窗口 if show_window: try: @@ -163,7 +163,7 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r print("图形已保存为 'system_curves.png'") else: plt.close() # 关闭图形,不显示窗口 - + # 打印统计信息 print("\n=== 系统运行统计 ===") print(f"所需储能总容量: {result['required_storage_capacity']:.2f} MWh") @@ -174,12 +174,12 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r 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}") - + # 计算购电和上网电量统计 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) # 上网电量 - + print(f"\n=== 电网交互统计 ===") if total_grid_feed_in >= 0: print(f"净上网电量: {total_grid_feed_in:.2f} MWh") @@ -191,8 +191,41 @@ def plot_system_curves(solar_output, wind_output, thermal_output, load_demand, r def export_results_to_excel(solar_output, wind_output, thermal_output, load_demand, result, params, filename=None): """ - 将优化结果导出到Excel文件 + 将多能互补系统储能优化结果导出到Excel文件,包含运行数据、统计结果和系统参数。 + 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文件 + Args: solar_output: 光伏出力曲线 (MW) wind_output: 风电出力曲线 (MW) @@ -205,16 +238,16 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema if filename is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"storage_optimization_results_{timestamp}.xlsx" - + print(f"\n正在导出结果到Excel文件: {filename}") - + # 准备数据 hours = list(range(1, len(solar_output) + 1)) - + # 分离购电和上网电量 grid_purchase = [] grid_feed_out = [] - + for power in result['grid_feed_in']: if power < 0: grid_purchase.append(-power) # 购电,转换为正值 @@ -222,7 +255,7 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema else: grid_purchase.append(0) grid_feed_out.append(power) # 上网电量 - + # 创建主要数据DataFrame data_df = pd.DataFrame({ '小时': hours, @@ -239,12 +272,12 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema '购电量(MW)': grid_purchase, '上网电量(MW)': grid_feed_out }) - + # 创建统计信息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) # 上网电量 - + stats_df = pd.DataFrame({ '指标': [ '所需储能总容量', @@ -277,7 +310,7 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema "是" if result['capacity_limit_reached'] else "否" ] }) - + # 创建系统参数DataFrame params_df = pd.DataFrame({ '参数名称': [ @@ -326,18 +359,18 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema "MWh" ] }) - + # 写入Excel文件 with pd.ExcelWriter(filename, engine='openpyxl') as writer: # 写入主要数据 data_df.to_excel(writer, sheet_name='运行数据', index=False) - + # 写入统计信息 stats_df.to_excel(writer, sheet_name='统计结果', index=False) - + # 写入系统参数 params_df.to_excel(writer, sheet_name='系统参数', index=False) - + # 创建说明工作表 description_df = pd.DataFrame({ '项目': [ @@ -356,7 +389,7 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema ] }) description_df.to_excel(writer, sheet_name='说明', index=False) - + print(f"结果已成功导出到: {filename}") return filename @@ -367,70 +400,69 @@ def generate_yearly_data(): 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 - 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, + 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, 16.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0] - + # 添加季节性变化 import random random.seed(42) - + yearly_solar = [] yearly_wind = [] yearly_thermal = [] yearly_load = [] - + for day in range(365): # 季节性因子(夏季光伏更强,冬季负荷更高) season_factor = 1.0 + 0.3 * np.sin(2 * np.pi * day / 365) - + 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) - + 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) - + return yearly_solar, yearly_wind, yearly_thermal, yearly_load def main(): """主函数""" import sys - + # 检查命令行参数 if len(sys.argv) < 2: print_usage() return - + command = sys.argv[1] show_window = '--show' in sys.argv # 检查是否包含--show参数 display_only = '--display-only' in sys.argv # 检查是否只显示不保存 - - -if command == '--excel': + + if command == '--excel': if len(sys.argv) < 3: print("错误:请指定Excel文件路径") print("用法:python main.py --excel <文件路径>") return - + excel_file = sys.argv[2] print(f"从Excel文件读取数据:{excel_file}") - + try: data = read_excel_data(excel_file, include_parameters=True) solar_output = data['solar_output'] wind_output = data['wind_output'] thermal_output = data['thermal_output'] load_demand = data['load_demand'] - + print(f"成功读取{data['data_type']}小时数据") print(f"原始数据长度:{data['original_length']}小时") print(f"处理后数据长度:{len(solar_output)}小时") - + # 使用Excel中的系统参数 if 'system_parameters' in data: params = data['system_parameters'] @@ -464,7 +496,7 @@ if command == '--excel': available_solar_energy=600.0, available_wind_energy=1200.0 ) - + # 显示数据统计 stats = analyze_excel_data(excel_file) if stats: @@ -474,14 +506,15 @@ if command == '--excel': print(f" 最大光伏出力: {stats['max_solar']:.2f} MW") print(f" 最大风电出力: {stats['max_wind']:.2f} MW") print(f" 最大负荷: {stats['max_load']:.2f} MW") - + except Exception as e: print(f"读取Excel文件失败:{str(e)}") return + elif command == '--create-template': template_type = sys.argv[2] if len(sys.argv) > 2 else "8760" template_file = f"data_template_{template_type}.xlsx" - + print(f"创建{template_type}小时Excel模板:{template_file}") create_excel_template(template_file, template_type) return @@ -491,9 +524,9 @@ if command == '--excel': 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 - 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, + 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, 16.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0] - + # 使用默认系统参数 params = SystemParameters( max_curtailment_wind=0.1, @@ -509,9 +542,9 @@ if command == '--excel': available_solar_energy=600.0, available_wind_energy=1200.0 ) - - - + + + # 显示当前使用的系统参数 print("\n=== 当前使用的系统参数 ===") print(f"最大弃风率: {params.max_curtailment_wind}") @@ -528,23 +561,23 @@ if command == '--excel': print(f"光伏可用发电量: {params.available_solar_energy} MWh") print(f"风电可用发电量: {params.available_wind_energy} MWh") print("=" * 40) - + # 计算最优储能容量 print("正在计算最优储能容量...") result = optimize_storage_capacity( solar_output, wind_output, thermal_output, load_demand, params ) - + # 绘制曲线 print("正在绘制系统运行曲线...") plot_system_curves(solar_output, wind_output, thermal_output, load_demand, result, show_window, display_only) - + # 导出结果到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)}") - + if display_only: print("\n正在显示图形窗口...") elif show_window: @@ -552,13 +585,12 @@ if command == '--excel': else: print("\n曲线图已保存为 'system_curves.png'") - def print_usage(): """打印使用说明""" print("多能互补系统储能容量优化程序") print("\n使用方法:") print(" python main.py --excel <文件路径> # 从Excel文件读取数据") - + print(" python main.py --create-template [类型] # 创建Excel模板(24或8760)") print(" python main.py # 使用24小时示例数据") print(" python main.py --show # 显示图形窗口(可与其他参数组合使用)") diff --git a/solar_optimization.py b/solar_optimization.py new file mode 100644 index 0000000..88855af --- /dev/null +++ b/solar_optimization.py @@ -0,0 +1,362 @@ +""" +光伏出力优化模块 + +该模块通过调整光伏出力曲线的系数,在给定的系统参数条件下 +最小化与电网交换的电量,提高系统的自平衡能力。 + +作者: iFlow CLI +创建日期: 2025-12-26 +""" + +import numpy as np +from typing import List, Dict, Tuple, Optional +from dataclasses import dataclass +from storage_optimization import SystemParameters, optimize_storage_capacity, calculate_energy_balance + + +@dataclass +class SolarOptimizationResult: + """光伏优化结果类""" + optimal_solar_coefficient: float # 最优光伏系数 + original_solar_output: List[float] # 原始光伏出力曲线 + optimized_solar_output: List[float] # 优化后光伏出力曲线 + min_grid_exchange: float # 最小电网交换电量 + grid_purchase: float # 购电量 + grid_feed_in: float # 上网电量 + storage_result: Dict # 储能优化结果 + optimization_history: List[Dict] # 优化历史记录 + + +def calculate_grid_exchange_metric( + solar_output: List[float], + wind_output: List[float], + thermal_output: List[float], + load_demand: List[float], + params: SystemParameters +) -> Dict[str, float]: + """ + 计算电网交换电量指标 + + Args: + solar_output: 光伏出力曲线 (MW) + wind_output: 风电出力曲线 (MW) + thermal_output: 火电出力曲线 (MW) + load_demand: 负荷曲线 (MW) + params: 系统参数配置 + + Returns: + 包含电网交换指标的字典 + """ + # 计算最优储能容量 + storage_result = optimize_storage_capacity( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 计算电网交换电量 + grid_feed_in = storage_result['grid_feed_in'] + + # 分离购电和上网电量 + total_purchase = sum(-x for x in grid_feed_in if x < 0) # 购电量(正值) + total_feed_in = sum(x for x in grid_feed_in if x > 0) # 上网电量(正值) + + # 计算总交换电量(购电 + 上网) + total_exchange = total_purchase + total_feed_in + + return { + 'total_exchange': total_exchange, + 'grid_purchase': total_purchase, + 'grid_feed_in': total_feed_in, + 'storage_capacity': storage_result['required_storage_capacity'], + 'storage_result': storage_result + } + + +def optimize_solar_output( + original_solar_output: List[float], + wind_output: List[float], + thermal_output: List[float], + load_demand: List[float], + params: SystemParameters, + coefficient_range: Tuple[float, float] = (0.1, 2.0), + tolerance: float = 0.01, + max_iterations: int = 50 +) -> SolarOptimizationResult: + """ + 优化光伏出力系数以最小化电网交换电量 + + Args: + original_solar_output: 原始光伏出力曲线 (MW) + wind_output: 风电出力曲线 (MW) + thermal_output: 火电出力曲线 (MW) + load_demand: 负荷曲线 (MW) + params: 系统参数配置 + coefficient_range: 光伏系数搜索范围 (最小值, 最大值) + tolerance: 收敛容差 + max_iterations: 最大迭代次数 + + Returns: + 光伏优化结果 + """ + print("开始光伏出力优化...") + + # 初始化优化历史 + optimization_history = [] + + # 使用黄金分割法进行一维优化 + phi = (1 + np.sqrt(5)) / 2 # 黄金比例 + resphi = 2 - phi + + a, b = coefficient_range + c = b - resphi * (b - a) + d = a + resphi * (b - a) + + # 计算初始点的目标函数值 + fc = calculate_grid_exchange_metric( + [x * c for x in original_solar_output], + wind_output, thermal_output, load_demand, params + ) + + fd = calculate_grid_exchange_metric( + [x * d for x in original_solar_output], + wind_output, thermal_output, load_demand, params + ) + + # 记录初始点 + optimization_history.append({ + 'coefficient': c, + 'total_exchange': fc['total_exchange'], + 'grid_purchase': fc['grid_purchase'], + 'grid_feed_in': fc['grid_feed_in'], + 'storage_capacity': fc['storage_capacity'] + }) + + optimization_history.append({ + 'coefficient': d, + 'total_exchange': fd['total_exchange'], + 'grid_purchase': fd['grid_purchase'], + 'grid_feed_in': fd['grid_feed_in'], + 'storage_capacity': fd['storage_capacity'] + }) + + # 黄金分割搜索 + for iteration in range(max_iterations): + if abs(fc['total_exchange'] - fd['total_exchange']) < tolerance: + break + + if fc['total_exchange'] < fd['total_exchange']: + b = d + d = c + fd = fc + c = b - resphi * (b - a) + + fc = calculate_grid_exchange_metric( + [x * c for x in original_solar_output], + wind_output, thermal_output, load_demand, params + ) + + optimization_history.append({ + 'coefficient': c, + 'total_exchange': fc['total_exchange'], + 'grid_purchase': fc['grid_purchase'], + 'grid_feed_in': fc['grid_feed_in'], + 'storage_capacity': fc['storage_capacity'] + }) + else: + a = c + c = d + fc = fd + d = a + resphi * (b - a) + + fd = calculate_grid_exchange_metric( + [x * d for x in original_solar_output], + wind_output, thermal_output, load_demand, params + ) + + optimization_history.append({ + 'coefficient': d, + 'total_exchange': fd['total_exchange'], + 'grid_purchase': fd['grid_purchase'], + 'grid_feed_in': fd['grid_feed_in'], + 'storage_capacity': fd['storage_capacity'] + }) + + # 确定最优系数 + if fc['total_exchange'] < fd['total_exchange']: + optimal_coefficient = c + best_result = fc + else: + optimal_coefficient = d + best_result = fd + + # 生成优化后的光伏出力曲线 + optimized_solar_output = [x * optimal_coefficient for x in original_solar_output] + + # 重新计算完整的最优储能配置 + final_storage_result = optimize_storage_capacity( + optimized_solar_output, wind_output, thermal_output, load_demand, params + ) + + print(f"优化完成!最优光伏系数: {optimal_coefficient:.3f}") + print(f"最小电网交换电量: {best_result['total_exchange']:.2f} MWh") + + return SolarOptimizationResult( + optimal_solar_coefficient=optimal_coefficient, + original_solar_output=original_solar_output, + optimized_solar_output=optimized_solar_output, + min_grid_exchange=best_result['total_exchange'], + grid_purchase=best_result['grid_purchase'], + grid_feed_in=best_result['grid_feed_in'], + storage_result=final_storage_result, + optimization_history=optimization_history + ) + + + + + +def export_optimization_results(result: SolarOptimizationResult, filename: str = None): + """ + 导出光伏优化结果到Excel文件 + + Args: + result: 光伏优化结果 + filename: 输出文件名,如果为None则自动生成 + """ + import pandas as pd + from datetime import datetime + + if filename is None: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"solar_optimization_results_{timestamp}.xlsx" + + print(f"正在导出光伏优化结果到Excel文件: {filename}") + + hours = list(range(1, len(result.original_solar_output) + 1)) + + # 创建主要数据DataFrame + data_df = pd.DataFrame({ + '小时': hours, + '原始光伏出力(MW)': result.original_solar_output, + '优化后光伏出力(MW)': result.optimized_solar_output, + '出力变化(MW)': [result.optimized_solar_output[i] - result.original_solar_output[i] + for i in range(len(result.original_solar_output))], + '变化比例(%)': [(result.optimized_solar_output[i] / result.original_solar_output[i] - 1) * 100 + if result.original_solar_output[i] > 0 else 0 + for i in range(len(result.original_solar_output))] + }) + + # 创建优化结果摘要DataFrame + summary_df = pd.DataFrame({ + '指标': [ + '最优光伏系数', + '最小电网交换电量', + '购电量', + '上网电量', + '所需储能容量', + '优化后弃风率', + '优化后弃光率', + '优化后上网电量比例' + ], + '数值': [ + f"{result.optimal_solar_coefficient:.3f}", + f"{result.min_grid_exchange:.2f} MWh", + f"{result.grid_purchase:.2f} MWh", + f"{result.grid_feed_in:.2f} MWh", + f"{result.storage_result['required_storage_capacity']:.2f} MWh", + f"{result.storage_result['total_curtailment_wind_ratio']:.3f}", + f"{result.storage_result['total_curtailment_solar_ratio']:.3f}", + f"{result.storage_result['total_grid_feed_in_ratio']:.3f}" + ] + }) + + # 创建优化历史DataFrame + history_df = pd.DataFrame(result.optimization_history) + history_df.columns = ['光伏系数', '电网交换电量(MWh)', '购电量(MWh)', '上网电量(MWh)', '储能容量(MWh)'] + + # 写入Excel文件 + with pd.ExcelWriter(filename, engine='openpyxl') as writer: + # 写入主要数据 + data_df.to_excel(writer, sheet_name='出力曲线对比', index=False) + + # 写入优化结果摘要 + summary_df.to_excel(writer, sheet_name='优化结果摘要', index=False) + + # 写入优化历史 + history_df.to_excel(writer, sheet_name='优化历史', index=False) + + # 创建说明工作表 + description_df = pd.DataFrame({ + '项目': [ + '文件说明', + '生成时间', + '优化目标', + '优化方法', + '数据长度', + '注意事项' + ], + '内容': [ + '光伏出力优化结果 - 通过调整光伏系数最小化电网交换电量', + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + '最小化与电网交换的总电量(购电 + 上网)', + '黄金分割一维优化算法', + f"{len(result.original_solar_output)} 小时", + '优化结果在给定的系统参数约束下得出' + ] + }) + description_df.to_excel(writer, sheet_name='说明', index=False) + + print(f"光伏优化结果已成功导出到: {filename}") + return filename + + +def main(): + """主函数,提供示例使用""" + # 示例数据 + original_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 + 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, + 16.0, 14.0, 12.0, 10.0, 8.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 2.0] + + # 系统参数 + 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, + 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 + ) + + # 执行光伏优化 + result = optimize_solar_output( + original_solar_output, wind_output, thermal_output, load_demand, params + ) + + # 打印结果 + print("\n=== 光伏出力优化结果 ===") + print(f"最优光伏系数: {result.optimal_solar_coefficient:.3f}") + print(f"最小电网交换电量: {result.min_grid_exchange:.2f} MWh") + print(f"其中购电量: {result.grid_purchase:.2f} MWh") + print(f"其中上网电量: {result.grid_feed_in:.2f} MWh") + print(f"所需储能容量: {result.storage_result['required_storage_capacity']:.2f} MWh") + print(f"优化后弃风率: {result.storage_result['total_curtailment_wind_ratio']:.3f}") + print(f"优化后弃光率: {result.storage_result['total_curtailment_solar_ratio']:.3f}") + print(f"优化后上网电量比例: {result.storage_result['total_grid_feed_in_ratio']:.3f}") + + + + # 导出结果 + export_optimization_results(result) + + return result + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/solar_optimization_examples.py b/solar_optimization_examples.py new file mode 100644 index 0000000..3c9c905 --- /dev/null +++ b/solar_optimization_examples.py @@ -0,0 +1,537 @@ +""" +光伏优化模块场景示例 + +该文件展示了光伏优化模块在不同场景下的应用,包括: +1. 典型日场景 - 基础优化示例 +2. 高负荷场景 - 夏季高峰用电场景 +3. 低负荷场景 - 春秋季低负荷场景 +4. 风光互补场景 - 风电和光伏协同优化 +5. 储能受限场景 - 储能容量受限情况下的优化 + +作者: iFlow CLI +创建日期: 2025-12-26 +""" + +import numpy as np +import matplotlib.pyplot as plt +from typing import List, Dict +from solar_optimization import optimize_solar_output, plot_optimization_results, export_optimization_results +from storage_optimization import SystemParameters + + +def scenario_1_typical_day(): + """ + 场景1:典型日场景 + - 标准24小时负荷曲线 + - 适中风光出力 + - 常规系统参数 + """ + print("=" * 60) + print("场景1:典型日场景 - 基础优化示例") + print("=" * 60) + + # 典型日光伏出力(中午高峰) + solar_output = [0.0] * 6 + [0.5, 1.0, 2.0, 3.5, 5.0, 6.0, 5.5, 4.0, 2.5, 1.0, 0.5, 0.0] + [0.0] * 6 + + # 典型日风电出力(夜间和早晨较高) + wind_output = [4.0, 5.0, 4.5, 3.5, 2.5, 2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 4.0, 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.0] + + # 火电基础出力 + thermal_output = [8.0] * 24 + + # 典型日负荷曲线(早晚高峰) + load_demand = [2.0, 2.5, 3.0, 4.0, 6.0, 9.0, 12.0, 15.0, 18.0, 20.0, 19.0, 18.0, + 17.0, 16.0, 18.0, 19.0, 20.0, 18.0, 15.0, 12.0, 8.0, 5.0, 3.0, 2.0] + + # 标准系统参数 + params = SystemParameters( + max_curtailment_wind=0.1, + max_curtailment_solar=0.1, + max_grid_ratio=0.15, + storage_efficiency=0.9, + discharge_rate=1.0, + charge_rate=1.0, + rated_thermal_capacity=100.0, + rated_solar_capacity=50.0, + rated_wind_capacity=50.0, + available_thermal_energy=2000.0, + available_solar_energy=400.0, + available_wind_energy=600.0 + ) + + # 执行优化 + result = optimize_solar_output( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 输出结果 + print_scenario_result("典型日场景", result) + + # 绘制结果 + plot_optimization_results(result, show_window=False) + + # 导出结果 + filename = export_optimization_results(result, "scenario_1_typical_day.xlsx") + + return result + + +def scenario_2_high_load(): + """ + 场景2:高负荷场景 + - 夏季高温,空调负荷高 + - 白天负荷特别高 + - 光伏出力与负荷匹配度较低 + """ + print("=" * 60) + print("场景2:高负荷场景 - 夏季高峰用电") + print("=" * 60) + + # 夏季光伏出力(较强) + solar_output = [0.0] * 5 + [0.8, 1.5, 3.0, 4.5, 6.0, 7.5, 8.0, 7.0, 5.0, 3.0, 1.5, 0.5, 0.0, 0.0] + [0.0] * 5 + + # 夏季风电出力(相对较低) + wind_output = [2.0, 2.5, 3.0, 2.5, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 2.5, 3.0, 2.5, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 0.8] + + # 火电高峰出力 + thermal_output = [12.0] * 24 + + # 夏季高负荷曲线(空调导致白天负荷极高) + load_demand = [3.0, 3.5, 4.0, 5.0, 8.0, 12.0, 18.0, 25.0, 30.0, 32.0, 31.0, 30.0, + 29.0, 28.0, 30.0, 31.0, 32.0, 28.0, 22.0, 18.0, 12.0, 8.0, 5.0, 3.0] + + # 高负荷场景参数(更宽松的弃风弃光限制) + params = SystemParameters( + max_curtailment_wind=0.15, + max_curtailment_solar=0.15, + max_grid_ratio=0.25, + storage_efficiency=0.85, + discharge_rate=1.2, + charge_rate=1.2, + rated_thermal_capacity=150.0, + rated_solar_capacity=80.0, + rated_wind_capacity=40.0, + available_thermal_energy=3000.0, + available_solar_energy=600.0, + available_wind_energy=400.0 + ) + + # 执行优化 + result = optimize_solar_output( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 输出结果 + print_scenario_result("高负荷场景", result) + + # 绘制结果 + plot_optimization_results(result, show_window=False) + + # 导出结果 + filename = export_optimization_results(result, "scenario_2_high_load.xlsx") + + return result + + +def scenario_3_low_load(): + """ + 场景3:低负荷场景 + - 春秋季,负荷较低 + - 光伏出力相对较高 + - 容易出现电力盈余 + """ + print("=" * 60) + print("场景3:低负荷场景 - 春秋季低负荷") + print("=" * 60) + + # 春秋季光伏出力(适中) + solar_output = [0.0] * 6 + [1.0, 2.0, 3.5, 5.0, 6.5, 7.0, 6.5, 5.0, 3.5, 2.0, 1.0, 0.0] + [0.0] * 6 + + # 春秋季风电出力(较好) + wind_output = [5.0, 6.0, 5.5, 4.5, 3.5, 3.0, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 3.0, 4.0, 5.0, 6.0, 5.5, 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0] + + # 火电基础出力(较低) + thermal_output = [5.0] * 24 + + # 春秋季低负荷曲线 + load_demand = [2.0, 2.2, 2.5, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 13.0, 12.5, 12.0, + 11.5, 11.0, 12.0, 12.5, 13.0, 11.0, 9.0, 7.0, 5.0, 3.5, 2.5, 2.0] + + # 低负荷场景参数(更严格的弃风弃光限制) + params = SystemParameters( + max_curtailment_wind=0.05, + max_curtailment_solar=0.05, + max_grid_ratio=0.1, + storage_efficiency=0.92, + discharge_rate=0.8, + charge_rate=0.8, + rated_thermal_capacity=80.0, + rated_solar_capacity=60.0, + rated_wind_capacity=60.0, + available_thermal_energy=1500.0, + available_solar_energy=500.0, + available_wind_energy=700.0 + ) + + # 执行优化 + result = optimize_solar_output( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 输出结果 + print_scenario_result("低负荷场景", result) + + # 绘制结果 + plot_optimization_results(result, show_window=False) + + # 导出结果 + filename = export_optimization_results(result, "scenario_3_low_load.xlsx") + + return result + + +def scenario_4_wind_solar_complement(): + """ + 场景4:风光互补场景 + - 风电和光伏出力时间互补性强 + - 夜间风电高,白天光伏高 + - 系统整体平衡性较好 + """ + print("=" * 60) + print("场景4:风光互补场景 - 风电和光伏协同优化") + print("=" * 60) + + # 光伏出力(标准日间模式) + solar_output = [0.0] * 6 + [0.5, 1.5, 3.0, 4.5, 6.0, 7.0, 6.0, 4.5, 3.0, 1.5, 0.5, 0.0] + [0.0] * 6 + + # 风电出力(与光伏互补,夜间和早晚较高) + wind_output = [8.0, 9.0, 8.5, 7.0, 5.0, 3.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 3.0, 5.0, 7.0, 8.0, 8.5, 8.0, 7.5, 7.0, 6.5, 6.0, 5.5, 5.0] + + # 火电出力(作为补充) + thermal_output = [6.0] * 24 + + # 负荷曲线(相对平稳) + load_demand = [4.0, 4.5, 5.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 17.0, 16.5, 16.0, + 15.5, 15.0, 16.0, 16.5, 17.0, 15.0, 13.0, 11.0, 9.0, 7.0, 5.0, 4.0] + + # 风光互补场景参数 + params = SystemParameters( + max_curtailment_wind=0.08, + max_curtailment_solar=0.08, + max_grid_ratio=0.12, + storage_efficiency=0.9, + discharge_rate=1.0, + charge_rate=1.0, + rated_thermal_capacity=100.0, + rated_solar_capacity=70.0, + rated_wind_capacity=70.0, + available_thermal_energy=1800.0, + available_solar_energy=450.0, + available_wind_energy=800.0 + ) + + # 执行优化 + result = optimize_solar_output( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 输出结果 + print_scenario_result("风光互补场景", result) + + # 绘制结果 + plot_optimization_results(result, show_window=False) + + # 导出结果 + filename = export_optimization_results(result, "scenario_4_wind_solar_complement.xlsx") + + return result + + +def scenario_5_storage_limited(): + """ + 场景5:储能受限场景 + - 储能容量受限 + - 需要更精确的光伏出力调节 + - 对电网交换更敏感 + """ + print("=" * 60) + print("场景5:储能受限场景 - 储能容量受限情况下的优化") + print("=" * 60) + + # 标准光伏出力 + solar_output = [0.0] * 6 + [1.0, 2.0, 3.0, 4.5, 6.0, 7.0, 6.0, 4.5, 3.0, 2.0, 1.0, 0.0] + [0.0] * 6 + + # 标准风电出力 + wind_output = [3.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 3.5, 4.0, 3.5, 3.0, 2.8, 2.6, 2.4, 2.2, 2.0, 1.8] + + # 火电出力 + thermal_output = [7.0] * 24 + + # 标准负荷曲线 + load_demand = [3.0, 3.5, 4.0, 5.0, 7.0, 10.0, 13.0, 16.0, 18.0, 19.0, 18.5, 18.0, + 17.5, 17.0, 18.0, 18.5, 19.0, 17.0, 14.0, 11.0, 8.0, 6.0, 4.0, 3.0] + + # 储能受限场景参数(储能容量限制为50MWh) + params = SystemParameters( + max_curtailment_wind=0.12, + max_curtailment_solar=0.12, + max_grid_ratio=0.2, + storage_efficiency=0.88, + discharge_rate=1.5, + charge_rate=1.5, + max_storage_capacity=50.0, # 储能容量受限 + rated_thermal_capacity=100.0, + rated_solar_capacity=60.0, + rated_wind_capacity=50.0, + available_thermal_energy=2000.0, + available_solar_energy=480.0, + available_wind_energy=550.0 + ) + + # 执行优化 + result = optimize_solar_output( + solar_output, wind_output, thermal_output, load_demand, params + ) + + # 输出结果 + print_scenario_result("储能受限场景", result) + + # 绘制结果 + plot_optimization_results(result, show_window=False) + + # 导出结果 + filename = export_optimization_results(result, "scenario_5_storage_limited.xlsx") + + return result + + +def print_scenario_result(scenario_name: str, result): + """ + 打印场景优化结果 + + Args: + scenario_name: 场景名称 + result: 优化结果 + """ + print(f"\n=== {scenario_name}优化结果 ===") + print(f"最优光伏系数: {result.optimal_solar_coefficient:.3f}") + print(f"最小电网交换电量: {result.min_grid_exchange:.2f} MWh") + print(f" - 购电量: {result.grid_purchase:.2f} MWh") + print(f" - 上网电量: {result.grid_feed_in:.2f} MWh") + print(f"所需储能容量: {result.storage_result['required_storage_capacity']:.2f} MWh") + print(f"优化后弃风率: {result.storage_result['total_curtailment_wind_ratio']:.3f}") + print(f"优化后弃光率: {result.storage_result['total_curtailment_solar_ratio']:.3f}") + print(f"优化后上网电量比例: {result.storage_result['total_grid_feed_in_ratio']:.3f}") + + # 分析优化效果 + if result.optimal_solar_coefficient > 1.0: + print(f"分析:建议将光伏出力提高 {(result.optimal_solar_coefficient - 1.0) * 100:.1f}% 以减少电网依赖") + elif result.optimal_solar_coefficient < 1.0: + print(f"分析:建议将光伏出力降低 {(1.0 - result.optimal_solar_coefficient) * 100:.1f}% 以避免电力过剩") + else: + print("分析:当前光伏出力已经是最优配置") + + +def compare_scenarios(results: List[Dict]): + """ + 对比不同场景的优化结果 + + Args: + results: 各场景优化结果列表 + """ + print("\n" + "=" * 80) + print("场景对比分析") + print("=" * 80) + + scenario_names = [ + "典型日场景", + "高负荷场景", + "低负荷场景", + "风光互补场景", + "储能受限场景" + ] + + # 创建对比表格 + print(f"{'场景名称':<12} {'最优系数':<8} {'电网交换(MWh)':<12} {'购电量(MWh)':<10} {'上网电量(MWh)':<12} {'储能容量(MWh)':<12}") + print("-" * 80) + + for i, (name, result) in enumerate(zip(scenario_names, results)): + print(f"{name:<12} {result.optimal_solar_coefficient:<8.3f} " + f"{result.min_grid_exchange:<12.2f} {result.grid_purchase:<10.2f} " + f"{result.grid_feed_in:<12.2f} {result.storage_result['required_storage_capacity']:<12.2f}") + + # 分析趋势 + print("\n=== 趋势分析 ===") + + # 找出最优和最差场景 + min_exchange_result = min(results, key=lambda x: x.min_grid_exchange) + max_exchange_result = max(results, key=lambda x: x.min_grid_exchange) + + min_exchange_idx = results.index(min_exchange_result) + max_exchange_idx = results.index(max_exchange_result) + + print(f"电网交换最小场景:{scenario_names[min_exchange_idx]} ({min_exchange_result.min_grid_exchange:.2f} MWh)") + print(f"电网交换最大场景:{scenario_names[max_exchange_idx]} ({max_exchange_result.min_grid_exchange:.2f} MWh)") + + # 分析光伏系数趋势 + avg_coefficient = sum(r.optimal_solar_coefficient for r in results) / len(results) + print(f"平均最优光伏系数:{avg_coefficient:.3f}") + + high_coefficient_scenarios = [name for name, result in zip(scenario_names, results) + if result.optimal_solar_coefficient > avg_coefficient] + low_coefficient_scenarios = [name for name, result in zip(scenario_names, results) + if result.optimal_solar_coefficient < avg_coefficient] + + if high_coefficient_scenarios: + print(f"需要提高光伏出力的场景:{', '.join(high_coefficient_scenarios)}") + if low_coefficient_scenarios: + print(f"需要降低光伏出力的场景:{', '.join(low_coefficient_scenarios)}") + + +def plot_scenario_comparison(results: List[Dict]): + """ + 绘制场景对比图表 + + Args: + results: 各场景优化结果列表 + """ + scenario_names = [ + "典型日", + "高负荷", + "低负荷", + "风光互补", + "储能受限" + ] + + # 设置中文字体 + plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] + plt.rcParams['axes.unicode_minus'] = False + + # 创建图形 + fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12)) + fig.suptitle('光伏优化场景对比分析', fontsize=16, fontweight='bold') + + # 1. 最优光伏系数对比 + coefficients = [r.optimal_solar_coefficient for r in results] + bars1 = ax1.bar(scenario_names, coefficients, color='skyblue', alpha=0.7) + ax1.set_ylabel('最优光伏系数') + ax1.set_title('各场景最优光伏系数对比') + ax1.grid(True, alpha=0.3, axis='y') + ax1.axhline(y=1.0, color='red', linestyle='--', alpha=0.7, label='原始系数') + + # 添加数值标签 + for bar, coeff in zip(bars1, coefficients): + height = bar.get_height() + ax1.text(bar.get_x() + bar.get_width()/2., height + 0.01, + f'{coeff:.3f}', ha='center', va='bottom', fontweight='bold') + + # 2. 电网交换电量对比 + exchanges = [r.min_grid_exchange for r in results] + purchases = [r.grid_purchase for r in results] + feed_ins = [r.grid_feed_in for r in results] + + x = np.arange(len(scenario_names)) + width = 0.25 + + bars2 = ax2.bar(x - width, purchases, width, label='购电量', color='purple', alpha=0.7) + bars3 = ax2.bar(x, feed_ins, width, label='上网电量', color='brown', alpha=0.7) + bars4 = ax2.bar(x + width, exchanges, width, label='总交换电量', color='orange', alpha=0.7) + + ax2.set_ylabel('电量 (MWh)') + ax2.set_title('电网交换电量对比') + ax2.set_xticks(x) + ax2.set_xticklabels(scenario_names) + ax2.legend() + ax2.grid(True, alpha=0.3, axis='y') + + # 3. 储能容量需求对比 + storage_capacities = [r.storage_result['required_storage_capacity'] for r in results] + bars5 = ax3.bar(scenario_names, storage_capacities, color='green', alpha=0.7) + ax3.set_ylabel('储能容量 (MWh)') + ax3.set_title('各场景储能容量需求对比') + ax3.grid(True, alpha=0.3, axis='y') + + # 添加数值标签 + for bar, capacity in zip(bars5, storage_capacities): + height = bar.get_height() + ax3.text(bar.get_x() + bar.get_width()/2., height + height*0.01, + f'{capacity:.1f}', ha='center', va='bottom', fontweight='bold') + + # 4. 弃风弃光率对比 + curtailment_winds = [r.storage_result['total_curtailment_wind_ratio'] for r in results] + curtailment_solars = [r.storage_result['total_curtailment_solar_ratio'] for r in results] + + bars6 = ax4.bar(x - width/2, curtailment_winds, width, label='弃风率', color='blue', alpha=0.7) + bars7 = ax4.bar(x + width/2, curtailment_solars, width, label='弃光率', color='orange', alpha=0.7) + + ax4.set_ylabel('弃风弃光率') + ax4.set_title('各场景弃风弃光率对比') + ax4.set_xticks(x) + ax4.set_xticklabels(scenario_names) + ax4.legend() + ax4.grid(True, alpha=0.3, axis='y') + + # 调整布局 + plt.tight_layout() + + # 保存图片 + plt.savefig('solar_optimization_scenario_comparison.png', dpi=300, bbox_inches='tight') + plt.close() + + print("场景对比图表已保存为 'solar_optimization_scenario_comparison.png'") + + +def main(): + """主函数,运行所有场景示例""" + print("光伏优化模块场景示例") + print("运行5个不同场景的优化分析...") + + # 运行所有场景 + results = [] + + try: + # 场景1:典型日场景 + result1 = scenario_1_typical_day() + results.append(result1) + + # 场景2:高负荷场景 + result2 = scenario_2_high_load() + results.append(result2) + + # 场景3:低负荷场景 + result3 = scenario_3_low_load() + results.append(result3) + + # 场景4:风光互补场景 + result4 = scenario_4_wind_solar_complement() + results.append(result4) + + # 场景5:储能受限场景 + result5 = scenario_5_storage_limited() + results.append(result5) + + # 对比分析 + compare_scenarios(results) + + # 绘制对比图表 + plot_scenario_comparison(results) + + print("\n" + "=" * 80) + print("所有场景示例运行完成!") + print("=" * 80) + print("生成的文件:") + print("- scenario_1_typical_day.xlsx") + print("- scenario_2_high_load.xlsx") + print("- scenario_3_low_load.xlsx") + print("- scenario_4_wind_solar_complement.xlsx") + print("- scenario_5_storage_limited.xlsx") + print("- solar_optimization_scenario_comparison.png") + + except Exception as e: + print(f"运行场景示例时出错:{str(e)}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/solar_scenarios_demo.py b/solar_scenarios_demo.py new file mode 100644 index 0000000..daac38f --- /dev/null +++ b/solar_scenarios_demo.py @@ -0,0 +1,414 @@ +""" +光伏优化模块场景演示 + +该文件展示了光伏优化模块在不同场景下的应用,包括: +1. 典型日场景 - 基础优化示例 +2. 高负荷场景 - 夏季高峰用电场景 +3. 低负荷场景 - 春秋季低负荷场景 +4. 风光互补场景 - 风电和光伏协同优化 +5. 储能受限场景 - 储能容量受限情况下的优化 + +作者: iFlow CLI +创建日期: 2025-12-26 +""" + +from solar_optimization import optimize_solar_output, export_optimization_results +import matplotlib.pyplot as plt +from storage_optimization import SystemParameters + + +def scenario_1_typical_day(): + """场景1:典型日场景""" + print("=" * 60) + print("场景1:典型日场景 - 基础优化示例") + print("=" * 60) + + # 典型日数据(24小时) + solar_output = [0.0] * 6 + [0.5, 1.0, 2.0, 3.5, 5.0, 6.0, 5.5, 4.0, 2.5, 1.0, 0.5, 0.0] + [0.0] * 6 + wind_output = [4.0, 5.0, 4.5, 3.5, 2.5, 2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 4.0, 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.0] + thermal_output = [8.0] * 24 + load_demand = [2.0, 2.5, 3.0, 4.0, 6.0, 9.0, 12.0, 15.0, 18.0, 20.0, 19.0, 18.0, + 17.0, 16.0, 18.0, 19.0, 20.0, 18.0, 15.0, 12.0, 8.0, 5.0, 3.0, 2.0] + + # 系统参数 + params = SystemParameters( + max_curtailment_wind=0.1, + max_curtailment_solar=0.1, + max_grid_ratio=0.15, + storage_efficiency=0.9, + discharge_rate=1.0, + charge_rate=1.0, + rated_thermal_capacity=100.0, + rated_solar_capacity=50.0, + rated_wind_capacity=50.0, + available_thermal_energy=2000.0, + available_solar_energy=400.0, + available_wind_energy=600.0 + ) + + # 执行优化 + result = optimize_solar_output(solar_output, wind_output, thermal_output, load_demand, params) + + # 输出结果 + print_scenario_result("典型日场景", result) + + # 绘制光伏对比图 + plot_solar_comparison(result, "典型日场景") + + # 导出结果 + export_optimization_results(result, "scenario_1_typical_day.xlsx") + + return result + + +def scenario_2_high_load(): + """场景2:高负荷场景""" + print("=" * 60) + print("场景2:高负荷场景 - 夏季高峰用电") + print("=" * 60) + + # 夏季高负荷数据 + solar_output = [0.0] * 5 + [0.8, 1.5, 3.0, 4.5, 6.0, 7.5, 8.0, 7.0, 5.0, 3.0, 1.5, 0.5, 0.0, 0.0] + [0.0] * 5 + wind_output = [2.0, 2.5, 3.0, 2.5, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 2.5, 3.0, 2.5, 2.0, 1.8, 1.6, 1.4, 1.2, 1.0, 0.8] + thermal_output = [12.0] * 24 + load_demand = [3.0, 3.5, 4.0, 5.0, 8.0, 12.0, 18.0, 25.0, 30.0, 32.0, 31.0, 30.0, + 29.0, 28.0, 30.0, 31.0, 32.0, 28.0, 22.0, 18.0, 12.0, 8.0, 5.0, 3.0] + + # 高负荷场景参数 + params = SystemParameters( + max_curtailment_wind=0.15, + max_curtailment_solar=0.15, + max_grid_ratio=0.25, + storage_efficiency=0.85, + discharge_rate=1.2, + charge_rate=1.2, + rated_thermal_capacity=150.0, + rated_solar_capacity=80.0, + rated_wind_capacity=40.0, + available_thermal_energy=3000.0, + available_solar_energy=600.0, + available_wind_energy=400.0 + ) + + # 执行优化 + result = optimize_solar_output(solar_output, wind_output, thermal_output, load_demand, params) + + # 输出结果 + print_scenario_result("高负荷场景", result) + + # 绘制光伏对比图 + plot_solar_comparison(result, "高负荷场景") + + # 导出结果 + export_optimization_results(result, "scenario_2_high_load.xlsx") + + return result + + +def scenario_3_low_load(): + """场景3:低负荷场景""" + print("=" * 60) + print("场景3:低负荷场景 - 春秋季低负荷") + print("=" * 60) + + # 春秋季低负荷数据 + solar_output = [0.0] * 6 + [1.0, 2.0, 3.5, 5.0, 6.5, 7.0, 6.5, 5.0, 3.5, 2.0, 1.0, 0.0] + [0.0] * 6 + wind_output = [5.0, 6.0, 5.5, 4.5, 3.5, 3.0, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 3.0, 4.0, 5.0, 6.0, 5.5, 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, 2.0] + thermal_output = [5.0] * 24 + load_demand = [2.0, 2.2, 2.5, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 13.0, 12.5, 12.0, + 11.5, 11.0, 12.0, 12.5, 13.0, 11.0, 9.0, 7.0, 5.0, 3.5, 2.5, 2.0] + + # 低负荷场景参数 + params = SystemParameters( + max_curtailment_wind=0.05, + max_curtailment_solar=0.05, + max_grid_ratio=0.1, + storage_efficiency=0.92, + discharge_rate=0.8, + charge_rate=0.8, + rated_thermal_capacity=80.0, + rated_solar_capacity=60.0, + rated_wind_capacity=60.0, + available_thermal_energy=1500.0, + available_solar_energy=500.0, + available_wind_energy=700.0 + ) + + # 执行优化 + result = optimize_solar_output(solar_output, wind_output, thermal_output, load_demand, params) + + # 输出结果 + print_scenario_result("低负荷场景", result) + + # 绘制光伏对比图 + plot_solar_comparison(result, "低负荷场景") + + # 导出结果 + export_optimization_results(result, "scenario_3_low_load.xlsx") + + return result + + +def scenario_4_wind_solar_complement(): + """场景4:风光互补场景""" + print("=" * 60) + print("场景4:风光互补场景 - 风电和光伏协同优化") + print("=" * 60) + + # 风光互补数据 + solar_output = [0.0] * 6 + [0.5, 1.5, 3.0, 4.5, 6.0, 7.0, 6.0, 4.5, 3.0, 1.5, 0.5, 0.0] + [0.0] * 6 + wind_output = [8.0, 9.0, 8.5, 7.0, 5.0, 3.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 3.0, 5.0, 7.0, 8.0, 8.5, 8.0, 7.5, 7.0, 6.5, 6.0, 5.5, 5.0] + thermal_output = [6.0] * 24 + load_demand = [4.0, 4.5, 5.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 17.0, 16.5, 16.0, + 15.5, 15.0, 16.0, 16.5, 17.0, 15.0, 13.0, 11.0, 9.0, 7.0, 5.0, 4.0] + + # 风光互补场景参数 + params = SystemParameters( + max_curtailment_wind=0.08, + max_curtailment_solar=0.08, + max_grid_ratio=0.12, + storage_efficiency=0.9, + discharge_rate=1.0, + charge_rate=1.0, + rated_thermal_capacity=100.0, + rated_solar_capacity=70.0, + rated_wind_capacity=70.0, + available_thermal_energy=1800.0, + available_solar_energy=450.0, + available_wind_energy=800.0 + ) + + # 执行优化 + result = optimize_solar_output(solar_output, wind_output, thermal_output, load_demand, params) + + # 输出结果 + print_scenario_result("风光互补场景", result) + + # 绘制光伏对比图 + plot_solar_comparison(result, "风光互补场景") + + # 导出结果 + export_optimization_results(result, "scenario_4_wind_solar_complement.xlsx") + + return result + + +def scenario_5_storage_limited(): + """场景5:储能受限场景""" + print("=" * 60) + print("场景5:储能受限场景 - 储能容量受限情况下的优化") + print("=" * 60) + + # 储能受限数据 + solar_output = [0.0] * 6 + [1.0, 2.0, 3.0, 4.5, 6.0, 7.0, 6.0, 4.5, 3.0, 2.0, 1.0, 0.0] + [0.0] * 6 + wind_output = [3.0, 4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 3.5, 4.0, 3.5, 3.0, 2.8, 2.6, 2.4, 2.2, 2.0, 1.8] + thermal_output = [7.0] * 24 + load_demand = [3.0, 3.5, 4.0, 5.0, 7.0, 10.0, 13.0, 16.0, 18.0, 19.0, 18.5, 18.0, + 17.5, 17.0, 18.0, 18.5, 19.0, 17.0, 14.0, 11.0, 8.0, 6.0, 4.0, 3.0] + + # 储能受限场景参数 + params = SystemParameters( + max_curtailment_wind=0.12, + max_curtailment_solar=0.12, + max_grid_ratio=0.2, + storage_efficiency=0.88, + discharge_rate=1.5, + charge_rate=1.5, + max_storage_capacity=50.0, # 储能容量受限 + rated_thermal_capacity=100.0, + rated_solar_capacity=60.0, + rated_wind_capacity=50.0, + available_thermal_energy=2000.0, + available_solar_energy=480.0, + available_wind_energy=550.0 + ) + + # 执行优化 + result = optimize_solar_output(solar_output, wind_output, thermal_output, load_demand, params) + + # 输出结果 + print_scenario_result("储能受限场景", result) + + # 绘制光伏对比图 + plot_solar_comparison(result, "储能受限场景") + + # 导出结果 + export_optimization_results(result, "scenario_5_storage_limited.xlsx") + + return result + + +def print_scenario_result(scenario_name: str, result): + """打印场景优化结果""" + print(f"\n=== {scenario_name}优化结果 ===") + print(f"最优光伏系数: {result.optimal_solar_coefficient:.3f}") + print(f"最小电网交换电量: {result.min_grid_exchange:.2f} MWh") + print(f" - 购电量: {result.grid_purchase:.2f} MWh") + print(f" - 上网电量: {result.grid_feed_in:.2f} MWh") + print(f"所需储能容量: {result.storage_result['required_storage_capacity']:.2f} MWh") + print(f"优化后弃风率: {result.storage_result['total_curtailment_wind_ratio']:.3f}") + print(f"优化后弃光率: {result.storage_result['total_curtailment_solar_ratio']:.3f}") + print(f"优化后上网电量比例: {result.storage_result['total_grid_feed_in_ratio']:.3f}") + + # 分析优化效果 + if result.optimal_solar_coefficient > 1.0: + print(f"分析:建议将光伏出力提高 {(result.optimal_solar_coefficient - 1.0) * 100:.1f}% 以减少电网依赖") + elif result.optimal_solar_coefficient < 1.0: + print(f"分析:建议将光伏出力降低 {(1.0 - result.optimal_solar_coefficient) * 100:.1f}% 以避免电力过剩") + else: + print("分析:当前光伏出力已经是最优配置") + + +def plot_solar_comparison(result, scenario_name, show_window=True): + """ + 绘制光伏出力对比图 + + Args: + result: 光伏优化结果 + scenario_name: 场景名称 + show_window: 是否显示图形窗口 + """ + # 设置中文字体 + plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans'] + plt.rcParams['axes.unicode_minus'] = False + + hours = list(range(len(result.original_solar_output))) + + # 创建图形 + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) + fig.suptitle(f'{scenario_name} - 光伏优化结果 (系数: {result.optimal_solar_coefficient:.3f})', + fontsize=14, fontweight='bold') + + # === 第一个子图:光伏出力对比 === + ax1.plot(hours, result.original_solar_output, 'b-', linewidth=2, + label='原始光伏出力', alpha=0.7) + ax1.plot(hours, result.optimized_solar_output, 'r-', linewidth=2, + label=f'优化后光伏出力') + + ax1.set_xlabel('时间 (小时)') + ax1.set_ylabel('功率 (MW)') + ax1.set_title('光伏出力曲线对比') + ax1.legend(loc='upper right') + ax1.grid(True, alpha=0.3) + ax1.set_xlim(0, max(hours)) + + # === 第二个子图:电网交换电量组成 === + categories = ['购电量', '上网电量'] + values = [result.grid_purchase, result.grid_feed_in] + colors = ['purple', 'brown'] + + bars = ax2.bar(categories, values, color=colors, alpha=0.7) + ax2.set_ylabel('电量 (MWh)') + ax2.set_title(f'电网交换电量组成 (总计: {result.min_grid_exchange:.2f} MWh)') + ax2.grid(True, alpha=0.3, axis='y') + + # 在柱状图上添加数值标签 + for bar, value in zip(bars, values): + height = bar.get_height() + ax2.text(bar.get_x() + bar.get_width()/2., height + height*0.01, + f'{value:.2f}', ha='center', va='bottom', fontweight='bold') + + # 调整布局 + plt.tight_layout() + + # 根据参数决定是否显示图形窗口 + if show_window: + try: + plt.show() + except Exception as e: + print(f"无法显示图形窗口:{str(e)}") + else: + plt.close() + + +def compare_scenarios(results): + """对比不同场景的优化结果""" + print("\n" + "=" * 80) + print("场景对比分析") + print("=" * 80) + + scenario_names = [ + "典型日场景", + "高负荷场景", + "低负荷场景", + "风光互补场景", + "储能受限场景" + ] + + # 创建对比表格 + print(f"{'场景名称':<12} {'最优系数':<8} {'电网交换(MWh)':<12} {'购电量(MWh)':<10} {'上网电量(MWh)':<12} {'储能容量(MWh)':<12}") + print("-" * 80) + + for i, (name, result) in enumerate(zip(scenario_names, results)): + print(f"{name:<12} {result.optimal_solar_coefficient:<8.3f} " + f"{result.min_grid_exchange:<12.2f} {result.grid_purchase:<10.2f} " + f"{result.grid_feed_in:<12.2f} {result.storage_result['required_storage_capacity']:<12.2f}") + + # 分析趋势 + print("\n=== 趋势分析 ===") + + # 找出最优和最差场景 + min_exchange_result = min(results, key=lambda x: x.min_grid_exchange) + max_exchange_result = max(results, key=lambda x: x.min_grid_exchange) + + min_exchange_idx = results.index(min_exchange_result) + max_exchange_idx = results.index(max_exchange_result) + + print(f"电网交换最小场景:{scenario_names[min_exchange_idx]} ({min_exchange_result.min_grid_exchange:.2f} MWh)") + print(f"电网交换最大场景:{scenario_names[max_exchange_idx]} ({max_exchange_result.min_grid_exchange:.2f} MWh)") + + # 分析光伏系数趋势 + avg_coefficient = sum(r.optimal_solar_coefficient for r in results) / len(results) + print(f"平均最优光伏系数:{avg_coefficient:.3f}") + + +def main(): + """主函数,运行所有场景示例""" + print("光伏优化模块场景演示") + print("运行5个不同场景的优化分析...") + + # 运行所有场景 + results = [] + + try: + # 场景1:典型日场景 + result1 = scenario_1_typical_day() + results.append(result1) + + # 场景2:高负荷场景 + result2 = scenario_2_high_load() + results.append(result2) + + # 场景3:低负荷场景 + result3 = scenario_3_low_load() + results.append(result3) + + # 场景4:风光互补场景 + result4 = scenario_4_wind_solar_complement() + results.append(result4) + + # 场景5:储能受限场景 + result5 = scenario_5_storage_limited() + results.append(result5) + + # 对比分析 + compare_scenarios(results) + + print("\n" + "=" * 80) + print("所有场景演示完成!") + print("=" * 80) + print("生成的文件:") + print("- scenario_1_typical_day.xlsx") + print("- scenario_2_high_load.xlsx") + print("- scenario_3_low_load.xlsx") + print("- scenario_4_wind_solar_complement.xlsx") + print("- scenario_5_storage_limited.xlsx") + + except Exception as e: + print(f"运行场景演示时出错:{str(e)}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/storage_optimization.py b/storage_optimization.py index a3a2689..cf45e56 100644 --- a/storage_optimization.py +++ b/storage_optimization.py @@ -144,9 +144,24 @@ def calculate_energy_balance( total_potential_wind = np.sum(wind) total_potential_solar = np.sum(solar) +# 判断是否只有一种可再生能源 + has_wind = total_potential_wind > 0 + has_solar = total_potential_solar > 0 + single_renewable = (has_wind and not has_solar) or (has_solar and not has_wind) + # 计算允许的最大弃风弃光量 - max_curtailed_wind_total = total_potential_wind * params.max_curtailment_wind - max_curtailed_solar_total = total_potential_solar * params.max_curtailment_solar + if single_renewable: + # 只有一种可再生能源时,弃电量不受限制 + max_curtailed_wind_total = float('inf') + max_curtailed_solar_total = float('inf') + elif params.max_grid_ratio == 0: + # 上网电量限制为0时,所有超额电力都必须被弃掉,不受弃风弃光限制 + max_curtailed_wind_total = float('inf') + max_curtailed_solar_total = float('inf') + else: + # 有多种可再生能源且上网电量限制不为0时,应用弃风弃光限制 + max_curtailed_wind_total = total_potential_wind * params.max_curtailment_wind + max_curtailed_solar_total = total_potential_solar * params.max_curtailment_solar # 初始化累计弃风弃光量 accumulated_curtailed_wind = 0.0 @@ -206,28 +221,59 @@ def calculate_energy_balance( # 计算弃风弃光(优先弃光,然后弃风) if remaining_surplus > 0: - # 计算当前可弃光量 - available_solar_curtail = min( - solar[hour], - max_curtailed_solar_total - accumulated_curtailed_solar - ) - - if available_solar_curtail > 0: - curtailed_solar[hour] = min(available_solar_curtail, remaining_surplus) - remaining_surplus -= curtailed_solar[hour] - accumulated_curtailed_solar += curtailed_solar[hour] - - # 如果还有剩余,弃风 - if remaining_surplus > 0: - available_wind_curtail = min( - wind[hour], - max_curtailed_wind_total - accumulated_curtailed_wind - ) + # 在单一可再生能源场景下,弃风弃光不受限制 + if single_renewable: + # 优先弃光 + if solar[hour] > 0: + curtailed_solar[hour] = min(solar[hour], remaining_surplus) + remaining_surplus -= curtailed_solar[hour] + accumulated_curtailed_solar += curtailed_solar[hour] - if available_wind_curtail > 0: - curtailed_wind[hour] = min(available_wind_curtail, remaining_surplus) + # 如果还有剩余,弃风 + if remaining_surplus > 0 and wind[hour] > 0: + curtailed_wind[hour] = min(wind[hour], remaining_surplus) remaining_surplus -= curtailed_wind[hour] accumulated_curtailed_wind += curtailed_wind[hour] + else: + # 混合可再生能源场景,弃风弃光受限制 + # 计算当前可弃光量 + if max_curtailed_solar_total == float('inf'): + # 无限制弃光 + available_solar_curtail = solar[hour] + else: + # 受限制弃光 + available_solar_curtail = min( + solar[hour], + max_curtailed_solar_total - accumulated_curtailed_solar + ) + + if available_solar_curtail > 0: + curtailed_solar[hour] = min(available_solar_curtail, remaining_surplus) + remaining_surplus -= curtailed_solar[hour] + accumulated_curtailed_solar += curtailed_solar[hour] + + # 如果还有剩余,弃风 + if remaining_surplus > 0: + if max_curtailed_wind_total == float('inf'): + # 无限制弃风 + available_wind_curtail = wind[hour] + else: + # 受限制弃风 + available_wind_curtail = min( + wind[hour], + max_curtailed_wind_total - accumulated_curtailed_wind + ) + + if available_wind_curtail > 0: + curtailed_wind[hour] = min(available_wind_curtail, remaining_surplus) + remaining_surplus -= curtailed_wind[hour] + accumulated_curtailed_wind += curtailed_wind[hour] + + # 确保电力平衡:如果仍有剩余电力,强制弃掉(安全机制) + if remaining_surplus > 0: + # 记录警告但不影响计算 + # 在实际系统中,这种情况不应该发生,但作为安全保护 + pass else: # 电力不足,优先放电 @@ -376,11 +422,25 @@ def optimize_storage_capacity( # 没有上网电量或为负值(购电),总是满足约束 grid_constraint_satisfied = True - constraints_satisfied = ( - constraint_results['total_curtailment_wind_ratio'] <= params.max_curtailment_wind and - constraint_results['total_curtailment_solar_ratio'] <= params.max_curtailment_solar and - grid_constraint_satisfied - ) + # 判断是否只有一种可再生能源 + has_wind = sum(wind_output) > 0 + has_solar = sum(solar_output) > 0 + single_renewable = (has_wind and not has_solar) or (has_solar and not has_wind) + + # 特殊情况:当上网电量限制为0时,所有超额电力都必须被弃掉 + # 此时应该允许无限制弃风弃光 + grid_quota_zero = params.max_grid_ratio == 0 + + if single_renewable or grid_quota_zero: + # 只有一种可再生能源时,或上网电量限制为0时,跳过弃风弃光约束检查 + constraints_satisfied = grid_constraint_satisfied + else: + # 有多种可再生能源且上网电量限制不为0时,检查所有约束 + constraints_satisfied = ( + constraint_results['total_curtailment_wind_ratio'] <= params.max_curtailment_wind and + constraint_results['total_curtailment_solar_ratio'] <= params.max_curtailment_solar and + grid_constraint_satisfied + ) # 检查储能日平衡(周期结束时储能状态应接近初始值) storage_initial = balance_result['storage_profile'][0]