diff --git a/example_usage.py b/example_usage.py index 96ba5cb..fa190bb 100644 --- a/example_usage.py +++ b/example_usage.py @@ -195,11 +195,26 @@ def plot_results(result, title, solar_output, wind_output, thermal_output, load_ ax4.legend() ax4.grid(True) - # 上网电量 + # 上网电量/购电量 ax5.plot(hours, result['grid_feed_in'], 'orange', linewidth=2) - ax5.set_title('上网电量 (MW)') + ax5.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.5) + ax5.fill_between(hours, 0, result['grid_feed_in'], + where=[x >= 0 for x in result['grid_feed_in']], + alpha=0.3, color='green', label='上网') + ax5.fill_between(hours, 0, result['grid_feed_in'], + where=[x < 0 for x in result['grid_feed_in']], + alpha=0.3, color='red', label='购电') + + # 动态设置标题 + total_grid = sum(result['grid_feed_in']) + if total_grid < 0: + ax5.set_title(f'购电量 (总计: {abs(total_grid):.1f} MWh)') + else: + ax5.set_title(f'上网电量 (总计: {total_grid:.1f} MWh)') + ax5.set_xlabel('时间 (小时)') ax5.set_ylabel('功率 (MW)') + ax5.legend() ax5.grid(True) # 能量平衡分析 @@ -230,6 +245,93 @@ def plot_results(result, title, solar_output, wind_output, thermal_output, load_ plt.show() +def example_5_high_load_grid_purchase_scenario(): + """示例5: 高负荷购电场景""" + print("\n=== 示例5: 高负荷购电场景 ===") + + # 高负荷场景数据 - 有充电和放电时段 + solar_output = [0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 9.0, 8.0, + 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0] + + wind_output = [5.0, 5.5, 6.0, 6.5, 6.0, 5.5, 5.0, 4.5, 4.0, 3.5, 3.0, 2.5, + 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 5.5, 5.0, 5.0] + + thermal_output = [8.0] * 24 # 火电基荷 + + # 负荷曲线:夜间低负荷(充电时段),白天高负荷(放电和购电时段) + load_demand = [10.0, 9.0, 8.0, 7.0, 8.0, 12.0, 18.0, 25.0, 35.0, 42.0, 45.0, 43.0, + 40.0, 38.0, 35.0, 30.0, 25.0, 20.0, 15.0, 12.0, 11.0, 10.0, 10.0, 10.0] + + # 系统参数 - 允许从电网购电(负的上网电量) + params = SystemParameters( + max_curtailment_wind=0.05, # 严格的弃风控制 + max_curtailment_solar=0.02, # 严格的弃光控制 + max_grid_ratio=-0.3, # 负值表示允许从电网购电,最大购电比例30% + storage_efficiency=0.9, # 储能效率90% + discharge_rate=2.0, # 2C放电,满足高峰需求 + charge_rate=1.0, # 1C充电 + max_storage_capacity=8.0 # 限制储能容量为8MWh,确保储能被充分利用 + ) + + result = optimize_storage_capacity(solar_output, wind_output, thermal_output, load_demand, params, tolerance=0.1) + + print(f"所需储能容量: {result['required_storage_capacity']:.2f} MWh") + print(f"储能容量上限: {result['max_storage_limit']:.2f} MWh") + print(f"是否达到容量上限: {'是' if result['capacity_limit_reached'] else '否'}") + print(f"实际弃风率: {result['total_curtailment_wind_ratio']:.3f} (约束: {params.max_curtailment_wind})") + print(f"实际弃光率: {result['total_curtailment_solar_ratio']:.3f} (约束: {params.max_curtailment_solar})") + print(f"实际上网电量比例: {result['total_grid_feed_in_ratio']:.3f} (负值表示购电)") + print(f"能量平衡校验: {'通过' if result['energy_balance_check'] else '未通过'}") + + # 调试信息 + total_gen = sum(solar_output) + sum(wind_output) + sum(thermal_output) + total_load = sum(load_demand) + total_charge = sum(result['charge_profile']) + total_discharge = sum(result['discharge_profile']) + + print(f"\n=== 调试信息 ===") + print(f"总发电量: {total_gen:.2f} MWh") + print(f"总负荷: {total_load:.2f} MWh") + print(f"负荷-发电差: {total_load - total_gen:.2f} MWh") + print(f"总充电量: {total_charge:.2f} MWh") + print(f"总放电量: {total_discharge:.2f} MWh") + print(f"储能净变化: {total_discharge - total_charge:.2f} MWh") + + # 计算购电量统计 + total_grid_feed = sum(result['grid_feed_in']) + if total_grid_feed < 0: + print(f"总购电量: {abs(total_grid_feed):.2f} MWh") + + # 显示前几个小时的详细情况 + print(f"\n前6小时详细情况:") + print(f"小时 | 发电 | 负荷 | 储能充电 | 储能放电 | 购电") + print("-" * 55) + for i in range(6): + gen = solar_output[i] + wind_output[i] + thermal_output[i] + charge = result['charge_profile'][i] + discharge = result['discharge_profile'][i] + grid = result['grid_feed_in'][i] + print(f"{i:2d} | {gen:4.1f} | {load_demand[i]:4.1f} | {charge:7.2f} | {discharge:7.2f} | {grid:5.2f}") + + # 计算最大缺电功率 + max_deficit = 0 + for hour in range(24): + total_gen = solar_output[hour] + wind_output[hour] + thermal_output[hour] + deficit = max(0, load_demand[hour] - total_gen - result['discharge_profile'][hour]) + max_deficit = max(max_deficit, deficit) + + if max_deficit > 0: + print(f"\n最大缺电功率: {max_deficit:.2f} MW") + + return { + 'result': result, + 'solar_output': solar_output, + 'wind_output': wind_output, + 'thermal_output': thermal_output, + 'load_demand': load_demand + } + + def example_4_capacity_limited_scenario(): """示例4: 储能容量限制场景""" print("\n=== 示例4: 储能容量限制场景 ===") @@ -280,54 +382,61 @@ def compare_scenarios(): """比较不同场景的结果""" print("\n=== 场景比较 ===") - # 运行四个场景 + # 运行五个场景 data1 = example_1_basic_scenario() data2 = example_2_high_renewable_scenario() data3 = example_3_winter_scenario() data4 = example_4_capacity_limited_scenario() + data5 = example_5_high_load_grid_purchase_scenario() # 比较结果 - scenarios = ['基础场景', '高可再生能源场景', '冬季场景', '容量限制场景'] + scenarios = ['基础场景', '高可再生能源场景', '冬季场景', '容量限制场景', '高负荷购电场景'] storage_capacities = [ data1['result']['required_storage_capacity'], data2['result']['required_storage_capacity'], data3['result']['required_storage_capacity'], - data4['result']['required_storage_capacity'] + data4['result']['required_storage_capacity'], + data5['result']['required_storage_capacity'] ] curtailment_wind = [ data1['result']['total_curtailment_wind_ratio'], data2['result']['total_curtailment_wind_ratio'], data3['result']['total_curtailment_wind_ratio'], - data4['result']['total_curtailment_wind_ratio'] + data4['result']['total_curtailment_wind_ratio'], + data5['result']['total_curtailment_wind_ratio'] ] curtailment_solar = [ data1['result']['total_curtailment_solar_ratio'], data2['result']['total_curtailment_solar_ratio'], data3['result']['total_curtailment_solar_ratio'], - data4['result']['total_curtailment_solar_ratio'] + data4['result']['total_curtailment_solar_ratio'], + data5['result']['total_curtailment_solar_ratio'] ] grid_feed_in = [ data1['result']['total_grid_feed_in_ratio'], data2['result']['total_grid_feed_in_ratio'], data3['result']['total_grid_feed_in_ratio'], - data4['result']['total_grid_feed_in_ratio'] + data4['result']['total_grid_feed_in_ratio'], + data5['result']['total_grid_feed_in_ratio'] ] capacity_limit = [ '无', '无', '无', - f"{data4['result']['max_storage_limit']:.1f}MWh" + f"{data4['result']['max_storage_limit']:.1f}MWh", + f"{data5['result']['max_storage_limit']:.1f}MWh" ] print("\n场景比较结果:") - print(f"{'场景':<15} {'储能容量(MWh)':<12} {'容量限制':<10} {'弃风率':<8} {'弃光率':<8} {'上网比例':<8}") - print("-" * 75) + print(f"{'场景':<15} {'储能容量(MWh)':<12} {'容量限制':<10} {'弃风率':<8} {'弃光率':<8} {'上网/购电':<8}") + print("-" * 80) for i, scenario in enumerate(scenarios): - limit_reached = "✓" if data4['result']['capacity_limit_reached'] and i == 3 else "" + grid_text = f"{grid_feed_in[i]:.3f}" if grid_feed_in[i] >= 0 else f"{grid_feed_in[i]:.3f}↓" + limit_reached = "*" if (data4['result']['capacity_limit_reached'] and i == 3) or (data5['result']['capacity_limit_reached'] and i == 4) else "" print(f"{scenario:<15} {storage_capacities[i]:<12.2f} {capacity_limit[i]:<10} {curtailment_wind[i]:<8.3f} " - f"{curtailment_solar[i]:<8.3f} {grid_feed_in[i]:<8.3f} {limit_reached}") + f"{curtailment_solar[i]:<8.3f} {grid_text:<8} {limit_reached}") - return data1, data2, data3, data4 + return data1, data2, data3, data4, data5 if __name__ == "__main__": @@ -335,7 +444,7 @@ if __name__ == "__main__": print("=" * 50) # 运行示例 - data1, data2, data3, data4 = compare_scenarios() + data1, data2, data3, data4, data5 = compare_scenarios() # 绘制图表(如果matplotlib可用) try: @@ -351,6 +460,9 @@ if __name__ == "__main__": plot_results(data4['result'], "容量限制场景储能运行情况", data4['solar_output'], data4['wind_output'], data4['thermal_output'], data4['load_demand']) + plot_results(data5['result'], "高负荷购电场景储能运行情况", + data5['solar_output'], data5['wind_output'], + data5['thermal_output'], data5['load_demand']) except ImportError: print("\n注意: matplotlib未安装,无法绘制图表") print("要安装matplotlib,请运行: pip install matplotlib") diff --git a/storage_optimization.py b/storage_optimization.py index d4d9f9e..b2bc3bc 100644 --- a/storage_optimization.py +++ b/storage_optimization.py @@ -70,8 +70,8 @@ def validate_inputs( raise ValueError("弃风率必须在0.0-1.0之间") if not (0.0 <= params.max_curtailment_solar <= 1.0): raise ValueError("弃光率必须在0.0-1.0之间") - if not (0.0 <= params.max_grid_ratio <= 1.0): - raise ValueError("上网电量比例必须在0.0-1.0之间") + if not (-1.0 <= params.max_grid_ratio <= 1.0): + raise ValueError("上网电量比例必须在-1.0到1.0之间(负值表示购电)") if not (0.0 < params.storage_efficiency <= 1.0): raise ValueError("储能效率必须在0.0-1.0之间") if params.discharge_rate <= 0 or params.charge_rate <= 0: @@ -193,6 +193,7 @@ def calculate_energy_balance( else: # 电力不足,优先放电 power_deficit = -power_surplus + grid_feed_in[hour] = 0 # 初始化购电为0 max_discharge = min( storage_soc[hour], # 储能状态限制 @@ -208,8 +209,19 @@ def calculate_energy_balance( if hour < hours - 1: storage_soc[hour + 1] = storage_soc[hour] - actual_discharge / params.storage_efficiency - # 剩余缺电(理论上应该为0,否则系统不平衡) - # 在实际系统中,这部分可能需要从电网购电或削减负荷 + # 计算剩余缺电,需要从电网购电 + remaining_deficit = power_deficit - actual_discharge + + # 如果还有缺电且允许购电,则从电网购电 + if remaining_deficit > 0: + # 检查是否允许购电(max_grid_ratio为负值) + if params.max_grid_ratio < 0: + # 购电功率为负值,表示从电网输入 + grid_feed_in[hour] = -remaining_deficit + else: + # 不允许购电,缺电部分无法满足 + # 在实际系统中可能需要削减负荷 + grid_feed_in[hour] = 0 # 不购电 return { 'storage_profile': storage_soc.tolist(), @@ -321,10 +333,17 @@ def optimize_storage_capacity( constraint_results = check_constraints(solar_output, wind_output, thermal_output, balance_result, params) # 检查是否满足所有约束 + # 对于负的max_grid_ratio(购电约束),实际grid_feed_in_ratio应该大于等于约束值 + grid_constraint_satisfied = ( + constraint_results['total_grid_feed_in_ratio'] <= params.max_grid_ratio + if params.max_grid_ratio >= 0 + else constraint_results['total_grid_feed_in_ratio'] >= params.max_grid_ratio + ) + constraints_satisfied = ( constraint_results['total_curtailment_wind_ratio'] <= params.max_curtailment_wind and constraint_results['total_curtailment_solar_ratio'] <= params.max_curtailment_solar and - constraint_results['total_grid_feed_in_ratio'] <= params.max_grid_ratio + grid_constraint_satisfied ) # 检查储能日平衡(周期结束时储能状态应接近初始值) @@ -382,9 +401,16 @@ def optimize_storage_capacity( energy_to_storage = total_charge * params.storage_efficiency # 储能消耗的电网能量 # 能量平衡校验:应该接近0,但允许一定误差 - energy_balance_error = abs( - total_generation + energy_from_storage - total_consumption - energy_to_storage - total_curtailed - total_grid - ) + # 当total_grid为负时(购电),应该加到左侧(供给侧) + # 当total_grid为正时(上网),应该加到右侧(需求侧) + if total_grid < 0: # 购电情况 + energy_balance_error = abs( + total_generation + energy_from_storage + abs(total_grid) - total_consumption - energy_to_storage - total_curtailed + ) + else: # 上网情况 + energy_balance_error = abs( + total_generation + energy_from_storage - total_consumption - energy_to_storage - total_curtailed - total_grid + ) # 使用更大的容差,考虑储能效率损失和数值误差 # 允许误差为总发电量的15%或10MW,取较大者 # 储能效率损失可能达到总能量的10%以上