diff --git a/main.py b/main.py index b36cfee..3ffa476 100644 --- a/main.py +++ b/main.py @@ -641,6 +641,14 @@ def main(): result = optimize_storage_capacity( solar_output, wind_output, thermal_output, load_demand, params ) + + # 显示优化结果摘要 + print("\n=== 优化结果摘要 ===") + print(f"优化目标: 最小化弃电量") + print(f"所选储能容量: {result['required_storage_capacity']:.2f} MWh") + if result.get('total_curtailed_energy') is not None: + print(f"总弃电量: {result['total_curtailed_energy']:.2f} MWh") + print(f"储能容量上限: {result.get('max_storage_limit', '无限制')}") # 绘制曲线 print("正在绘制系统运行曲线...") diff --git a/src/storage_optimization.py b/src/storage_optimization.py index 6adcedd..525f7df 100644 --- a/src/storage_optimization.py +++ b/src/storage_optimization.py @@ -447,10 +447,11 @@ def optimize_storage_capacity( load_demand: List[float], params: SystemParameters, max_iterations: int = 100, - tolerance: float = 0.01 + tolerance: float = 0.01, + search_step: float = 0.01 ) -> Dict: """ - 优化储能容量,使用迭代方法寻找满足所有约束的最小储能容量 + 优化储能容量,在不超过设定储能容量上限的前提下,选择使弃电量最小的储能容量 Args: solar_output: 光伏出力曲线 (MW) - 支持24小时或8760小时 @@ -460,6 +461,7 @@ def optimize_storage_capacity( params: 系统参数配置 max_iterations: 最大迭代次数 tolerance: 收敛容差 + search_step: 搜索步长(相对于最大容量的比例) Returns: 包含优化结果的字典 @@ -476,6 +478,7 @@ def optimize_storage_capacity( upper_bound = min(theoretical_max, params.max_storage_capacity) else: upper_bound = theoretical_max + print("警告:未设置储能容量上限,将使用理论最大值") # 判断数据类型(24小时或8760小时) data_length = len(solar_output) @@ -484,17 +487,26 @@ def optimize_storage_capacity( if is_yearly_data: print(f"处理8760小时全年数据,启用周期性平衡优化...") - # 二分搜索寻找最小储能容量 + # 在容量范围内搜索,找到弃电量最小的储能容量 + # 使用二分搜索 + 局部搜索相结合的方法 + + # 首先使用二分搜索找到一个满足约束的基准容量 + print("第一阶段:二分搜索寻找满足约束的基准容量...") + best_capacity = upper_bound best_result = None - solution_found = False # 标记是否找到可行解 - + best_curtailed = float('inf') + solution_found = False + + # 二分搜索寻找最小可行容量 + lower_bound_search = lower_bound + upper_bound_search = upper_bound + feasible_capacity = None + for iteration in range(max_iterations): - mid_capacity = (lower_bound + upper_bound) / 2 + mid_capacity = (lower_bound_search + upper_bound_search) / 2 # 计算当前容量下的平衡 - # 对于8760小时数据,使用周期性平衡函数 - # 对于24小时数据,使用普通平衡函数(初始SOC=0) if is_yearly_data: balance_result = find_periodic_steady_state( solar_output, wind_output, thermal_output, load_demand, @@ -509,30 +521,20 @@ def optimize_storage_capacity( constraint_results = check_constraints(solar_output, wind_output, thermal_output, balance_result, params) # 检查是否满足所有约束 - # max_grid_ratio只限制上网电量比例,不约束购电 - # 只有当grid_feed_in为正时(上网)才需要检查约束 total_grid_feed_in = sum(balance_result['grid_feed_in']) if total_grid_feed_in > 0: - # 有上网电量,检查是否超过限制 grid_constraint_satisfied = constraint_results['total_grid_feed_in_ratio'] <= params.max_grid_ratio else: - # 没有上网电量或为负值(购电),总是满足约束 grid_constraint_satisfied = True - # 判断是否只有一种可再生能源 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 @@ -546,50 +548,125 @@ def optimize_storage_capacity( if constraints_satisfied and daily_balance: # 满足条件,尝试减小容量 - best_capacity = mid_capacity - best_result = {**balance_result, **constraint_results} - solution_found = True - upper_bound = mid_capacity + feasible_capacity = mid_capacity + upper_bound_search = mid_capacity else: # 不满足条件,增大容量 - lower_bound = mid_capacity + lower_bound_search = mid_capacity # 检查收敛 - if upper_bound - lower_bound < tolerance: + if upper_bound_search - lower_bound_search < tolerance: break - - # 处理储能容量上限限制的情况 - if not solution_found and params.max_storage_capacity is not None: - print(f"警告:在储能容量上限 {params.max_storage_capacity:.2f} MWh 内无法找到满足所有约束的解") - print("使用最大允许容量进行计算,但某些约束条件可能无法满足") - - # 使用最大允许容量计算结果 + + # 如果找到了可行解,使用它作为基准;否则使用上限 + if feasible_capacity is not None: + print(f"找到基准可行容量: {feasible_capacity:.2f} MWh") + else: + print(f"未找到可行解,使用上限容量: {upper_bound:.2f} MWh") + feasible_capacity = upper_bound + + # 第二阶段:在 [feasible_capacity, upper_bound] 范围内搜索弃电量最小的容量 + print("\n第二阶段:在可行容量范围内搜索弃电量最小的储能容量...") + + # 使用梯度下降方法搜索最优容量 + # 定义搜索区间 + search_lower = feasible_capacity + search_upper = upper_bound + + # 计算当前最小弃电量 + best_capacity = feasible_capacity + + # 计算基准容量的弃电量 + if is_yearly_data: + base_result = find_periodic_steady_state( + solar_output, wind_output, thermal_output, load_demand, + params, best_capacity + ) + else: + base_result = calculate_energy_balance( + solar_output, wind_output, thermal_output, load_demand, params, best_capacity + ) + + base_curtailed = sum(base_result['curtailed_wind']) + sum(base_result['curtailed_solar']) + best_curtailed = base_curtailed + best_result = {**base_result, **check_constraints(solar_output, wind_output, thermal_output, base_result, params)} + + print(f"基准容量 {best_capacity:.2f} MWh 的弃电量: {best_curtailed:.2f} MWh") + + # 使用黄金分割搜索法寻找最优容量 + # 黄金分割点 + golden_ratio = (3 - 5**0.5) / 2 # 约0.382 + + # 初始化两个测试点 + a = search_lower + b = search_upper + + c = b - golden_ratio * (b - a) + d = a + golden_ratio * (b - a) + + max_refinement_iterations = 50 + + for iter_num in range(max_refinement_iterations): + # 计算点 c 的弃电量 if is_yearly_data: - balance_result = find_periodic_steady_state( + result_c = find_periodic_steady_state( solar_output, wind_output, thermal_output, load_demand, - params, params.max_storage_capacity + params, c ) else: - balance_result = calculate_energy_balance( - solar_output, wind_output, thermal_output, load_demand, params, params.max_storage_capacity + result_c = calculate_energy_balance( + solar_output, wind_output, thermal_output, load_demand, params, c ) - constraint_results = check_constraints(solar_output, wind_output, thermal_output, balance_result, params) - best_result = {**balance_result, **constraint_results} - best_capacity = params.max_storage_capacity - elif best_result is None: - # 如果没有找到可行解(且没有容量上限限制),使用最大容量 + + curtailed_c = sum(result_c['curtailed_wind']) + sum(result_c['curtailed_solar']) + + # 计算点 d 的弃电量 if is_yearly_data: - balance_result = find_periodic_steady_state( + result_d = find_periodic_steady_state( solar_output, wind_output, thermal_output, load_demand, - params, upper_bound + params, d ) else: - balance_result = calculate_energy_balance( - solar_output, wind_output, thermal_output, load_demand, params, upper_bound + result_d = calculate_energy_balance( + solar_output, wind_output, thermal_output, load_demand, params, d ) - constraint_results = check_constraints(solar_output, wind_output, thermal_output, balance_result, params) - best_result = {**balance_result, **constraint_results} - best_capacity = upper_bound + + curtailed_d = sum(result_d['curtailed_wind']) + sum(result_d['curtailed_solar']) + + # 比较并更新最优解 + if curtailed_c < best_curtailed: + best_curtailed = curtailed_c + best_capacity = c + best_result = {**result_c, **check_constraints(solar_output, wind_output, thermal_output, result_c, params)} + print(f" 发现更优容量: {best_capacity:.2f} MWh, 弃电量: {best_curtailed:.2f} MWh") + + if curtailed_d < best_curtailed: + best_curtailed = curtailed_d + best_capacity = d + best_result = {**result_d, **check_constraints(solar_output, wind_output, thermal_output, result_d, params)} + print(f" 发现更优容量: {best_capacity:.2f} MWh, 弃电量: {best_curtailed:.2f} MWh") + + # 缩小搜索区间 + if curtailed_c < curtailed_d: + b = d + d = c + c = b - golden_ratio * (b - a) + else: + a = c + c = d + d = a + golden_ratio * (b - a) + + # 检查收敛 + if b - a < tolerance: + print(f"搜索收敛,区间宽度: {b - a:.4f} MWh") + break + + # 限制最大迭代次数 + if iter_num % 10 == 0 and iter_num > 0: + print(f" 已完成 {iter_num} 次迭代,当前搜索区间: [{a:.2f}, {b:.2f}] MWh") + + print(f"\n最终选择储能容量: {best_capacity:.2f} MWh (容量上限: {upper_bound:.2f} MWh)") + print(f"最终弃电量: {best_curtailed:.2f} MWh") # 添加能量平衡校验 total_generation = sum(thermal_output) + sum(wind_output) + sum(solar_output) @@ -630,8 +707,8 @@ def optimize_storage_capacity( print(f" 最终SOC: {best_result['storage_profile'][-1]:.4f} MWh") print(f" SOC差值: {soc_initial_final_diff:.4f} MWh") - # 返回最终结果 - return { + # 最终结果 + result = { 'required_storage_capacity': best_capacity, 'storage_profile': best_result['storage_profile'], 'charge_profile': best_result['charge_profile'], @@ -644,9 +721,13 @@ def optimize_storage_capacity( 'total_grid_feed_in_ratio': best_result['total_grid_feed_in_ratio'], 'energy_balance_check': energy_balance_check, 'capacity_limit_reached': params.max_storage_capacity is not None and best_capacity >= params.max_storage_capacity, - 'theoretical_optimal_capacity': best_capacity if solution_found else None, - 'max_storage_limit': params.max_storage_capacity + 'total_curtailed_energy': best_curtailed, # 总弃电量 + 'min_curtailed_capacity': best_capacity, # 弃电量最小时的储能容量 + 'max_storage_limit': params.max_storage_capacity, + 'optimization_goal': 'minimize_curtailment' # 优化目标:最小化弃电量 } + + return result def main():