From 2956bc80fb5fde05c53c6be7c8ca84b326b37244 Mon Sep 17 00:00:00 2001 From: dmy Date: Sat, 27 Dec 2025 17:57:38 +0800 Subject: [PATCH] =?UTF-8?q?excel=E7=BB=93=E6=9E=9C=E4=B8=AD=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E5=BC=83=E7=94=B5=E9=87=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CURTAILMENT_LOSS_FEATURES.md | 134 ++++ docs/GRID_FEED_IN_RATIO_CALCULATION.md | 121 ++++ excel_reader.py | 840 ------------------------- main.py | 28 + 4 files changed, 283 insertions(+), 840 deletions(-) create mode 100644 docs/CURTAILMENT_LOSS_FEATURES.md create mode 100644 docs/GRID_FEED_IN_RATIO_CALCULATION.md delete mode 100644 excel_reader.py diff --git a/docs/CURTAILMENT_LOSS_FEATURES.md b/docs/CURTAILMENT_LOSS_FEATURES.md new file mode 100644 index 0000000..64909bf --- /dev/null +++ b/docs/CURTAILMENT_LOSS_FEATURES.md @@ -0,0 +1,134 @@ +# 弃电损失量功能增强文档 + +## 修改概述 + +在用户要求下,对 `main.py` 文件的 Excel 输出功能进行了增强,新增了**弃电损失量**相关的统计和输出。 + +## 主要修改内容 + +### 1. 统计结果工作表增强 + +在 `export_results_to_excel` 函数的 `stats_df` 中新增了以下指标: + +| 新增指标 | 说明 | 单位 | +|---------|------|------| +| 总弃风电量 | 全部时间的弃风量总和 | MWh | +| 总弃光电量 | 全部时间的弃光量总和 | MWh | +| 总弃电量 | 弃风量 + 弃光量 | MWh | +| 弃电损失比例 | 总弃电量 / 总潜在发电量 | % | + +**代码位置**:[`main.py:374-377`](main.py:374-377) + +```python +# 计算弃电损失量 +total_curtail_wind = sum(result['curtailed_wind']) +total_curtail_solar = sum(result['curtailed_solar']) +total_curtail_energy = total_curtail_wind + total_curtail_solar + +# 计算总潜在发电量 +total_potential_generation = sum(solar_output) + sum(wind_output) + sum(thermal_output) +curtailment_loss_ratio = (total_curtail_energy / total_potential_generation * 100) if total_potential_generation > 0 else 0 +``` + +### 2. 运行数据工作表增强 + +在 `data_df` 中新增了以下列: + +| 新增列名 | 说明 | 计算方式 | +|---------|------|----------| +| 弃电损失量(MW) | 每小时的弃电损失量 | 弃风量 + 弃光量 | +| 累计弃电量(MWh) | 从开始到当前的累计弃电量 | 逐小时累加弃电损失量 | + +**代码位置**:[`main.py:323-348`](main.py:323-348) + +```python +# 计算弃电损失量(弃风+弃光) +curtailment_loss = [result['curtailed_wind'][i] + result['curtailed_solar'][i] for i in range(len(result['curtailed_wind']))] + +# 计算累计弃电损失量 +cumulative_curtailment = [] +cumulative = 0 +for loss in curtailment_loss: + cumulative += loss + cumulative_curtailment.append(cumulative) +``` + +## 功能特点 + +### 1. 完整的弃电损失分析 +- **小时级分析**:`弃电损失量(MW)` 提供每小时的弃电情况 +- **累计分析**:`累计弃电量(MWh)` 提供弃电的累积趋势 +- **总量统计**:总弃风量、总弃光量、总弃电量的完整统计 +- **比例分析**:弃电损失比例,评估系统效率 + +### 2. 数据一致性保证 +- 运行数据中的累计弃电量 = 统计结果中的总弃电量 +- 弃电损失量 = 弃风量 + 弃光量(逐小时匹配) +- 所有计算基于原始优化结果,确保数据一致性 + +### 3. 用户友好的输出 +- Excel文件中清晰的工作表分类 +- 中文列名,便于理解 +- 合理的单位标注(MW用于功率,MWh用于能量) + +## 输出效果 + +### 统计结果工作表示例 +``` +指标 | 数值 +-------------------|------------------ +总弃风电量 | 12.50 MWh +总弃光电量 | 8.30 MWh +总弃电量 | 20.80 MWh +弃电损失比例 | 2.34% +``` + +### 运行数据工作表新增列 +``` +小时 | 弃风量(MW) | 弃光量(MW) | 弃电损失量(MW) | 累计弃电量(MWh) +-----|-----------|-----------|---------------|-------------- +1 | 0.5 | 0.3 | 0.8 | 0.8 +2 | 0.2 | 0.4 | 0.6 | 1.4 +3 | 0.0 | 0.5 | 0.5 | 1.9 +... | ... | ... | ... | ... +``` + +## 使用说明 + +1. **运行程序**:`python main.py --excel data.xlsx` +2. **查看结果**:在生成的Excel文件中查看"统计结果"和"运行数据"工作表 +3. **分析弃电**: + - 统计结果:了解总体弃电情况和损失比例 + - 运行数据:分析逐小时弃电模式和累计趋势 + +## 技术细节 + +### 计算公式 +- **弃电损失量** = 弃风量 + 弃光量 +- **累计弃电量** = Σ(弃电损失量) (逐小时累加) +- **弃电损失比例** = (总弃电量 / 总潜在发电量) × 100% +- **总潜在发电量** = 光伏总出力 + 风电总出力 + 火电总出力 + +### 数据来源 +- 基于 `storage_optimization.py` 中的 `result` 字典 +- 使用 `curtailed_wind` 和 `curtailed_solar` 数据 +- 确保与优化算法结果完全一致 + +## 兼容性 + +- ✅ 向后兼容:不影响原有功能 +- ✅ Excel格式:保持原有文件结构 +- ✅ 时间尺度:支持24小时和8760小时数据 +- ✅ 数据精度:保持原有计算精度 + +## 验证 + +创建了测试文件 `test_main_modifications.py` 用于验证功能正确性: +- 验证Excel文件导出成功 +- 检查新增列是否正确添加 +- 验证数据计算的一致性 +- 确认统计结果的准确性 + +## 总结 + +此次修改成功地在Excel输出中增加了完整的弃电损失量分析功能,为用户提供了更详细和直观的弃电情况分析工具,有助于优化储能系统配置和运行策略。 \ No newline at end of file diff --git a/docs/GRID_FEED_IN_RATIO_CALCULATION.md b/docs/GRID_FEED_IN_RATIO_CALCULATION.md new file mode 100644 index 0000000..634bd43 --- /dev/null +++ b/docs/GRID_FEED_IN_RATIO_CALCULATION.md @@ -0,0 +1,121 @@ +# 上网电量比例计算方法说明 + +## 计算公式 + +**上网电量比例** = 总上网电量 / 总发电量 + +其中: +- **总上网电量** = sum(grid_feed_in) - 只计算正值(上网电量) +- **总发电量** = 火电发电量 + 实际风电发电量 + 实际光伏发电量 + +## 详细计算过程 + +### 1. 基础数据获取 + +在 [`check_constraints()`](src/storage_optimization.py:350) 函数中: + +```python +# 计算总量 +total_wind_potential = sum(wind_output) # 风电总潜力 +total_solar_potential = sum(solar_output) # 光伏总潜力 +total_thermal = sum(thermal_output) # 火电总量 + +total_curtailed_wind = sum(balance_result['curtailed_wind']) # 弃风总量 +total_curtailed_solar = sum(balance_result['curtailed_solar']) # 弃光总量 +total_grid_feed_in = sum(balance_result['grid_feed_in']) # 电网交互总量 +``` + +### 2. 计算实际发电量 + +```python +# 实际发电量(考虑弃风弃光) +actual_wind_generation = total_wind_potential - total_curtailed_wind +actual_solar_generation = total_solar_potential - total_curtailed_solar +total_generation = total_thermal + actual_wind_generation + actual_solar_generation +``` + +### 3. 计算上网电量比例 + +```python +actual_grid_feed_in_ratio = total_grid_feed_in / total_generation if total_generation > 0 else 0 +``` + +## 数据来源说明 + +### grid_feed_in 数据含义 +在 [`calculate_energy_balance()`](src/storage_optimization.py:107) 函数中: + +- **正值** = 向电网输送的电量(上网) +- **负值** = 从电网购买的电量(购电) + +```python +# 上网情况 +grid_feed_in[hour] = grid_feed_allowed # 正值 + +# 购电情况 +grid_feed_in[hour] = -remaining_deficit # 负值 +``` + +### 分母说明 +**总发电量**包括: +1. **火电发电量** - 全部计入(不考虑弃火电) +2. **实际风电发电量** = 风电总潜力 - 弃风量 +3. **实际光伏发电量** = 光伏总潜力 - 弃光量 + +## 计算示例 + +假设有如下数据: +- 火电总量:120 MWh (24小时 × 5MW) +- 风电总量:72 MWh (24小时 × 平均3MW) +- 光伏总量:48 MWh (24小时 × 平均2MW) +- 弃风量:7.2 MWh +- 弃光量:4.8 MWh +- 上网电量:10 MWh + +计算过程: +```python +# 1. 实际发电量 +actual_wind = 72 - 7.2 = 64.8 MWh +actual_solar = 48 - 4.8 = 43.2 MWh +total_generation = 120 + 64.8 + 43.2 = 228 MWh + +# 2. 上网电量比例 +grid_feed_in_ratio = 10 / 228 = 0.0438 ≈ 4.38% +``` + +## 注意事项 + +### 1. 购电情况的处理 +- 如果系统净购电(total_grid_feed_in < 0),上网电量比例仍按公式计算 +- 负的上网电量会降低比例值 + +### 2. 零分母处理 +- 如果总发电量为0,比例设为0,避免除零错误 + +### 3. 约束检查 +在优化算法中,上网电量比例用于约束检查: +```python +grid_constraint_satisfied = constraint_results['total_grid_feed_in_ratio'] <= params.max_grid_ratio +``` + +## 输出位置 + +上网电量比例在以下位置输出: +1. **控制台输出**:[`main.py:749`](main.py:749) + ```python + print(f"实际上网电量比例: {result['total_grid_feed_in_ratio']:.3f}") + ``` + +2. **Excel统计结果**:[`main.py:393`](main.py:393) + ```python + f"{result['total_grid_feed_in_ratio']:.3f}", + ``` + +3. **可视化显示**:[`src/advanced_visualization.py:158`](src/advanced_visualization.py:158) + ```python + 上网电量比例: {result['total_grid_feed_in_ratio']:.1%} + ``` + +## 总结 + +上网电量比例反映的是系统向电网输送电量占总发电量的比例,是评估系统电网交互特性的重要指标。该比例越低,说明系统越倾向于本地消纳新能源;比例越高,说明系统向电网输出的电量越多。 \ No newline at end of file diff --git a/excel_reader.py b/excel_reader.py deleted file mode 100644 index 69bb46d..0000000 --- a/excel_reader.py +++ /dev/null @@ -1,840 +0,0 @@ -""" -Excel数据读取模块 - -该模块提供从Excel文件中读取8760小时负荷和发电曲线数据的功能。 - -作者: iFlow CLI -创建日期: 2025-12-25 -""" - -import pandas as pd -import numpy as np -from typing import Dict, List, Optional, Tuple, Any -import os -from storage_optimization import SystemParameters - - -def validate_excel_data(df: pd.DataFrame, data_type: str = "8760") -> bool: - """ - 验证Excel数据格式是否正确 - - Args: - df: pandas DataFrame对象 - data_type: 数据类型,"24"或"8760" - - Returns: - bool: 验证是否通过 - """ - expected_length = 8760 if data_type == "8760" else 24 - - # 检查行数 - if len(df) != expected_length: - print(f"错误:数据行数应为{expected_length},实际为{len(df)}") - return False - - # 检查必需的列 - required_columns = ['光伏出力(MW)', '风电出力(MW)', '火电出力(MW)', '负荷需求(MW)'] - missing_columns = [col for col in required_columns if col not in df.columns] - - if missing_columns: - print(f"错误:缺少必需的列:{missing_columns}") - return False - - # 检查数据类型和非负值 - for col in required_columns: - if not pd.api.types.is_numeric_dtype(df[col]): - print(f"错误:列'{col}'必须为数值类型") - return False - - if (df[col] < 0).any(): - print(f"错误:列'{col}'包含负值") - return False - - return True - - -def read_system_parameters(file_path: str) -> SystemParameters: - """ - 从Excel文件读取系统参数 - - Args: - file_path: Excel文件路径 - - Returns: - SystemParameters对象 - - Raises: - FileNotFoundError: 文件不存在 - ValueError: 参数格式错误 - """ - # 检查文件是否存在 - if not os.path.exists(file_path): - raise FileNotFoundError(f"文件不存在:{file_path}") - - try: - # 读取参数工作表 - df_params = pd.read_excel(file_path, sheet_name='参数') - - # 验证参数工作表格式 - required_columns = ['参数名称', '参数值', '参数说明'] - missing_columns = [col for col in required_columns if col not in df_params.columns] - - if missing_columns: - raise ValueError(f"参数工作表缺少必需的列:{missing_columns}") - - # 提取参数值 - params_dict = {} - for _, row in df_params.iterrows(): - param_name = row['参数名称'] - param_value = row['参数值'] - - # 跳过空行 - if pd.isna(param_name) or pd.isna(param_value): - continue - - # 转换参数值 - try: - if isinstance(param_value, str): - # 尝试转换为浮点数 - param_value = float(param_value) - params_dict[param_name] = param_value - except (ValueError, TypeError): - raise ValueError(f"参数 '{param_name}' 的值 '{param_value}' 不是有效的数值") - - # 读取各参数值,如果找不到则使用默认值 - get_param_value = lambda param_name: df_params.loc[df_params['参数名称'] == param_name, '参数值'].iloc[0] if param_name in df_params['参数名称'].values else None - - max_storage_capacity = get_param_value('最大储能容量') - # 处理空值或字符串"空" - if pd.isna(max_storage_capacity) or max_storage_capacity == '空': - max_storage_capacity = None - - try: - # 获取各参数值,区分None、NaN、0和有效值 - def get_param_with_default(param_name, default_value): - value = get_param_value(param_name) - if value is None or pd.isna(value): - return default_value - else: - return value - - return SystemParameters( - max_curtailment_wind=get_param_with_default('最大弃风率', 0.1), - max_curtailment_solar=get_param_with_default('最大弃光率', 0.1), - max_grid_ratio=get_param_with_default('最大上网电量比例', 0.2), - storage_efficiency=get_param_with_default('储能效率', 0.9), - discharge_rate=get_param_with_default('放电倍率', 1.0), - charge_rate=get_param_with_default('充电倍率', 1.0), - max_storage_capacity=max_storage_capacity, - rated_thermal_capacity=get_param_with_default('额定火电装机容量', 100.0), - rated_solar_capacity=get_param_with_default('额定光伏装机容量', 100.0), - rated_wind_capacity=get_param_with_default('额定风电装机容量', 100.0), - available_thermal_energy=get_param_with_default('火电可用发电量', 2400.0), - available_solar_energy=get_param_with_default('光伏可用发电量', 600.0), - available_wind_energy=get_param_with_default('风电可用发电量', 1200.0) - ) - except (KeyError, IndexError, Exception) as e: - print(f"读取参数失败:{str(e)},使用默认参数") - return 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 - ) - - except Exception as e: - print(f"读取参数工作表失败,使用默认参数:{str(e)}") - # 如果参数工作表不存在或读取失败,返回默认参数 - return SystemParameters() - - -def read_excel_data(file_path: str, sheet_name: str = 0, include_parameters: bool = True) -> Dict[str, List[float]]: - """ - 从Excel文件读取8760小时数据 - - Args: - file_path: Excel文件路径 - sheet_name: 工作表名称或索引,默认为第一个工作表 - include_parameters: 是否同时读取系统参数 - - Returns: - 包含所有数据的字典 - - Raises: - FileNotFoundError: 文件不存在 - ValueError: 数据格式错误 - """ - # 检查文件是否存在 - if not os.path.exists(file_path): - raise FileNotFoundError(f"文件不存在:{file_path}") - - try: - # 读取Excel文件 - df = pd.read_excel(file_path, sheet_name=sheet_name) - - # 自动检测数据类型 - data_type = "8760" if len(df) >= 8760 else "24" - - # 验证数据格式 - if not validate_excel_data(df, data_type): - raise ValueError("Excel数据格式验证失败") - - # 提取数据并转换为列表 - solar_output = df['光伏出力(MW)'].tolist() - wind_output = df['风电出力(MW)'].tolist() - thermal_output = df['火电出力(MW)'].tolist() - load_demand = df['负荷需求(MW)'].tolist() - - # 如果是24小时数据,扩展到8760小时(重复365天) - if data_type == "24" and len(df) == 24: - print("检测到24小时数据,自动扩展到8760小时(重复365天)") - solar_output = solar_output * 365 - wind_output = wind_output * 365 - thermal_output = thermal_output * 365 - load_demand = load_demand * 365 - - # 构建返回结果 - result = { - 'solar_output': solar_output, - 'wind_output': wind_output, - 'thermal_output': thermal_output, - 'load_demand': load_demand, - 'data_type': data_type, - 'original_length': len(df) - } - - # 如果需要读取参数 - if include_parameters: - try: - result['system_parameters'] = read_system_parameters(file_path) - print("成功读取系统参数") - except Exception as e: - print(f"读取系统参数失败,使用默认参数:{str(e)}") - result['system_parameters'] = SystemParameters() - - try: - result['economic_parameters'] = read_economic_parameters(file_path) - print("成功读取经济参数") - except Exception as e: - print(f"读取经济参数失败,使用默认参数:{str(e)}") - from economic_optimization import EconomicParameters - result['economic_parameters'] = EconomicParameters() - - try: - result['optimization_settings'] = get_optimization_settings(file_path) - print("成功读取优化设置") - except Exception as e: - print(f"读取优化设置失败,使用默认设置:{str(e)}") - result['optimization_settings'] = { - 'storage_capacity_range': (0, 1000), - 'rate_range': (0.1, 2.0), - 'max_iterations': 100, - 'tolerance': 0.01 - } - - return result - - except Exception as e: - raise ValueError(f"读取Excel文件失败:{str(e)}") - - -def create_excel_template(file_path: str, data_type: str = "8760"): - """ - 创建Excel数据模板文件 - - Args: - file_path: 保存路径 - data_type: 数据类型,"24"或"8760" - """ - # 生成示例数据 - if data_type == "24": - hours = 24 - # 24小时典型日数据 - 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 - wind = [2.0, 3.0, 4.0, 3.0, 2.0, 1.0] * 4 - thermal = [5.0] * 24 - 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] - description = "24小时典型日数据模板" - else: - hours = 8760 - # 生成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 - 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] - - solar = [] - wind = [] - thermal = [] - load = [] - - np.random.seed(42) # 确保可重复性 - - 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 * (np.random.random() - 0.5) - wind_variation = 1.0 + 0.3 * (np.random.random() - 0.5) - load_variation = 1.0 + 0.1 * (np.random.random() - 0.5) - - solar.append(daily_solar[hour] * season_factor * solar_variation) - wind.append(daily_wind[hour] * wind_variation) - thermal.append(daily_thermal[hour]) - load.append(daily_load[hour] * (2.0 - season_factor) * load_variation) - - description = "8760小时全年数据模板" - - # 创建DataFrame - df = pd.DataFrame({ - '小时': range(1, hours + 1), - '光伏出力(MW)': solar, - '风电出力(MW)': wind, - '火电出力(MW)': thermal, - '负荷需求(MW)': load - }) - - # 保存到Excel - with pd.ExcelWriter(file_path, engine='openpyxl') as writer: - df.to_excel(writer, sheet_name='数据', index=False) - - # 添加参数工作表 - parameters_df = pd.DataFrame({ - '参数名称': [ - '最大弃风率', - '最大弃光率', - '最大上网电量比例', - '储能效率', - '放电倍率', - '充电倍率', - '最大储能容量', - '额定火电装机容量', - '额定光伏装机容量', - '额定风电装机容量', - '火电可用发电量', - '光伏可用发电量', - '风电可用发电量' - ], - '参数值': [ - 0.1, # 最大弃风率 - 0.1, # 最大弃光率 - 0.2, # 最大上网电量比例 - 0.9, # 储能效率 - 1.0, # 放电倍率 - 1.0, # 充电倍率 - '', # 最大储能容量(空表示无限制) - 0.0, # 额定火电装机容量(可以为0) - 100.0, # 额定光伏装机容量 - 100.0, # 额定风电装机容量 - 2400.0, # 火电可用发电量 - 600.0, # 光伏可用发电量 - 1200.0 # 风电可用发电量 - ], - '参数说明': [ - '允许的最大弃风率(0.0-1.0)', - '允许的最大弃光率(0.0-1.0)', - '允许的最大上网电量比例(0.0-∞,只限制上网电量)', - '储能充放电效率(0.0-1.0)', - '储能放电倍率(C-rate,>0)', - '储能充电倍率(C-rate,>0)', - '储能容量上限(MWh,空表示无限制)', - '额定火电装机容量(MW,可以为0)', - '额定光伏装机容量(MW)', - '额定风电装机容量(MW)', - '火电可用发电量(MWh)', - '光伏可用发电量(MWh)', - '风电可用发电量(MWh)' - ], - '取值范围': [ - '0.0-1.0', - '0.0-1.0', - '≥0.0', - '0.0-1.0', - '>0', - '>0', - '>0或空', - '≥0', - '>0', - '>0', - '≥0', - '≥0', - '≥0' - ], - '默认值': [ - '0.1', - '0.1', - '0.2', - '0.9', - '1.0', - '1.0', - '无限制', - '0.0', - '100.0', - '100.0', - '2400.0', - '600.0', - '1200.0' - ] - }) - parameters_df.to_excel(writer, sheet_name='参数', index=False) - - # 添加经济参数工作表 - economic_params_df = pd.DataFrame({ - '参数名称': [ - '光伏建设成本', - '风电建设成本', - '储能建设成本', - '购电价格', - '上网电价', - '光伏运维成本', - '风电运维成本', - '储能运维成本', - '项目寿命', - '折现率', - '储能容量搜索范围-最小值', - '储能容量搜索范围-最大值', - '充放电倍率搜索范围-最小值', - '充放电倍率搜索范围-最大值', - '最大迭代次数', - '收敛容差' - ], - '参数值': [ - 3000000, # 光伏建设成本 (元/MW) - 2500000, # 风电建设成本 (元/MW) - 800000, # 储能建设成本 (元/MWh) - 600, # 购电价格 (元/MWh) - 400, # 上网电价 (元/MWh) - 50000, # 光伏运维成本 (元/MW/年) - 45000, # 风电运维成本 (元/MW/年) - 3000, # 储能运维成本 (元/MW/年) - 25, # 项目寿命 (年) - 0.08, # 折现率 - 0, # 储能容量搜索范围-最小值 (MWh) - 1000, # 储能容量搜索范围-最大值 (MWh) - 0.1, # 充放电倍率搜索范围-最小值 - 2.0, # 充放电倍率搜索范围-最大值 - 100, # 最大迭代次数 - 0.01 # 收敛容差 - ], - '参数说明': [ - '光伏发电系统建设成本 (元/MW)', - '风力发电系统建设成本 (元/MW)', - '储能系统建设成本 (元/MWh)', - '从电网购电价格 (元/MWh)', - '向电网售电价格 (元/MWh)', - '光伏系统年度运维成本 (元/MW/年)', - '风电系统年度运维成本 (元/MW/年)', - '储能系统年度运维成本 (元/MW/年)', - '项目运营寿命 (年)', - '项目折现率 (用于NPV计算)', - '储能容量优化搜索范围下限 (MWh)', - '储能容量优化搜索范围上限 (MWh)', - '充放电倍率优化搜索范围下限', - '充放电倍率优化搜索范围上限', - '优化算法最大迭代次数', - '优化算法收敛容差' - ], - '取值范围': [ - '>0', - '>0', - '>0', - '>0', - '≥0', - '≥0', - '≥0', - '≥0', - '>0', - '0-1', - '≥0', - '>0', - '>0', - '>0', - '>0', - '>0' - ], - '默认值': [ - '3,000,000', - '2,500,000', - '800,000', - '600', - '400', - '50,000', - '45,000', - '3,000', - '25', - '0.08', - '0', - '1000', - '0.1', - '2.0', - '100', - '0.01' - ] - }) - economic_params_df.to_excel(writer, sheet_name='经济参数', index=False) - - # 添加说明工作表 - description_df = pd.DataFrame({ - '项目': ['数据说明', '数据类型', '时间范围', '单位', '注意事项', '参数说明', '经济优化说明'], - '内容': [ - description, - f'{data_type}小时电力数据', - f'1-{hours}小时', - 'MW (兆瓦)', - '所有数值必须为非负数', - '系统参数请在"参数"工作表中修改', - '经济优化参数请在"经济参数"工作表中修改' - ] - }) - description_df.to_excel(writer, sheet_name='说明', index=False) - - print(f"Excel模板已创建:{file_path}") - - -def analyze_excel_data(file_path: str) -> Dict[str, float]: - """ - 分析Excel数据的基本统计信息 - - Args: - file_path: Excel文件路径 - - Returns: - 包含统计信息的字典 - """ - try: - data = read_excel_data(file_path) - - solar = data['solar_output'] - wind = data['wind_output'] - thermal = data['thermal_output'] - load = data['load_demand'] - - return { - 'data_length': len(solar), - 'total_solar': sum(solar), - 'total_wind': sum(wind), - 'total_thermal': sum(thermal), - 'total_generation': sum(solar) + sum(wind) + sum(thermal), - 'total_load': sum(load), - 'max_solar': max(solar), - 'max_wind': max(wind), - 'max_thermal': max(thermal), - 'max_load': max(load), - 'avg_solar': np.mean(solar), - 'avg_wind': np.mean(wind), - 'avg_thermal': np.mean(thermal), - 'avg_load': np.mean(load) - } - except Exception as e: - print(f"分析数据失败:{str(e)}") - return {} - - -def read_economic_parameters(file_path: str): - """ - 从Excel文件读取经济参数 - - Args: - file_path: Excel文件路径 - - Returns: - EconomicParameters对象 - - Raises: - FileNotFoundError: 文件不存在 - ValueError: 参数格式错误 - """ - from economic_optimization import EconomicParameters - - # 检查文件是否存在 - if not os.path.exists(file_path): - raise FileNotFoundError(f"文件不存在:{file_path}") - - try: - # 读取经济参数工作表 - df_params = pd.read_excel(file_path, sheet_name='经济参数') - - # 验证经济参数工作表格式 - required_columns = ['参数名称', '参数值', '参数说明'] - missing_columns = [col for col in required_columns if col not in df_params.columns] - - if missing_columns: - raise ValueError(f"经济参数工作表缺少必需的列:{missing_columns}") - - # 提取参数值 - params_dict = {} - for _, row in df_params.iterrows(): - param_name = row['参数名称'] - param_value = row['参数值'] - - # 跳过空行 - if pd.isna(param_name) or pd.isna(param_value): - continue - - # 转换参数值 - try: - if isinstance(param_value, str): - # 尝试转换为浮点数 - param_value = float(param_value) - params_dict[param_name] = param_value - except (ValueError, TypeError): - raise ValueError(f"经济参数 '{param_name}' 的值 '{param_value}' 不是有效的数值") - - # 读取各参数值,如果找不到则使用默认值 - get_param_value = lambda param_name: df_params.loc[df_params['参数名称'] == param_name, '参数值'].iloc[0] if param_name in df_params['参数名称'].values else None - - try: - # 获取各参数值,区分None、NaN、0和有效值 - def get_param_with_default(param_name, default_value): - value = get_param_value(param_name) - if value is None or pd.isna(value): - return default_value - else: - return value - - return EconomicParameters( - solar_capex=get_param_with_default('光伏建设成本', 3000000), - wind_capex=get_param_with_default('风电建设成本', 2500000), - storage_capex=get_param_with_default('储能建设成本', 800000), - electricity_price=get_param_with_default('购电价格', 600), - feed_in_price=get_param_with_default('上网电价', 400), - solar_om=get_param_with_default('光伏运维成本', 50000), - wind_om=get_param_with_default('风电运维成本', 45000), - storage_om=get_param_with_default('储能运维成本', 3000), - project_lifetime=int(get_param_with_default('项目寿命', 25)), - discount_rate=get_param_with_default('折现率', 0.08) - ) - except (KeyError, IndexError, Exception) as e: - print(f"读取经济参数失败:{str(e)},使用默认参数") - return EconomicParameters( - solar_capex=3000000, - wind_capex=2500000, - storage_capex=800000, - electricity_price=600, - feed_in_price=400, - solar_om=50000, - wind_om=45000, - storage_om=3000, - project_lifetime=25, - discount_rate=0.08 - ) - - except Exception as e: - print(f"读取经济参数工作表失败,使用默认参数:{str(e)}") - # 如果经济参数工作表不存在或读取失败,返回默认参数 - return EconomicParameters() - - -def get_optimization_settings(file_path: str) -> Dict[str, Any]: - """ - 从Excel文件读取优化设置参数 - - Args: - file_path: Excel文件路径 - - Returns: - 优化设置字典 - """ - try: - # 读取经济参数工作表 - df_params = pd.read_excel(file_path, sheet_name='经济参数') - - # 提取优化设置参数 - get_param_value = lambda param_name: df_params.loc[df_params['参数名称'] == param_name, '参数值'].iloc[0] if param_name in df_params['参数名称'].values else None - - def get_param_with_default(param_name, default_value): - value = get_param_value(param_name) - if value is None or pd.isna(value): - return default_value - else: - return value - - return { - 'storage_capacity_range': ( - get_param_with_default('储能容量搜索范围-最小值', 0), - get_param_with_default('储能容量搜索范围-最大值', 1000) - ), - 'rate_range': ( - get_param_with_default('充放电倍率搜索范围-最小值', 0.1), - get_param_with_default('充放电倍率搜索范围-最大值', 2.0) - ), - 'max_iterations': int(get_param_with_default('最大迭代次数', 100)), - 'tolerance': get_param_with_default('收敛容差', 0.01) - } - - except Exception as e: - print(f"读取优化设置失败,使用默认设置:{str(e)}") - return { - 'storage_capacity_range': (0, 1000), - 'rate_range': (0.1, 2.0), - 'max_iterations': 100, - 'tolerance': 0.01 - } - - -def validate_system_parameters(params: SystemParameters) -> Dict[str, Any]: - """ - 验证系统参数的有效性 - - Args: - params: SystemParameters对象 - - Returns: - 验证结果字典 - """ - validation_result = { - 'valid': True, - 'errors': [], - 'warnings': [] - } - - # 检查弃风率 - if not (0.0 <= params.max_curtailment_wind <= 1.0): - validation_result['valid'] = False - validation_result['errors'].append(f"弃风率必须在0.0-1.0之间,当前值:{params.max_curtailment_wind}") - - # 检查弃光率 - if not (0.0 <= params.max_curtailment_solar <= 1.0): - validation_result['valid'] = False - validation_result['errors'].append(f"弃光率必须在0.0-1.0之间,当前值:{params.max_curtailment_solar}") - - # 检查上网电量比例 - if not (0.0 <= params.max_grid_ratio): - validation_result['valid'] = False - validation_result['errors'].append(f"上网电量比例必须为非负值,当前值:{params.max_grid_ratio}") - - # 检查储能效率 - if not (0.0 < params.storage_efficiency <= 1.0): - validation_result['valid'] = False - validation_result['errors'].append(f"储能效率必须在0.0-1.0之间,当前值:{params.storage_efficiency}") - - # 检查放电倍率 - if params.discharge_rate <= 0: - validation_result['valid'] = False - validation_result['errors'].append(f"放电倍率必须大于0,当前值:{params.discharge_rate}") - - # 检查充电倍率 - if params.charge_rate <= 0: - validation_result['valid'] = False - validation_result['errors'].append(f"充电倍率必须大于0,当前值:{params.charge_rate}") - - # 检查储能容量上限 - if params.max_storage_capacity is not None and params.max_storage_capacity <= 0: - validation_result['valid'] = False - validation_result['errors'].append(f"储能容量上限必须大于0,当前值:{params.max_storage_capacity}") - - # 添加警告信息 - if params.storage_efficiency < 0.8: - validation_result['warnings'].append("储能效率较低,可能影响系统性能") - - if params.max_curtailment_wind > 0.3 or params.max_curtailment_solar > 0.3: - validation_result['warnings'].append("弃风弃光率较高,可能造成能源浪费") - - if params.max_grid_ratio > 0.5: - validation_result['warnings'].append("上网电量比例较高,可能影响电网稳定性") - - return validation_result - - -def main(): - """主函数,演示Excel数据读取功能""" - import sys - - # 检查命令行参数 - if len(sys.argv) > 1 and sys.argv[1] == '--economic': - print("=== 创建经济优化Excel模板 ===") - - # 创建经济优化模板文件 - economic_template_8760 = "economic_data_template_8760.xlsx" - economic_template_24 = "economic_data_template_24.xlsx" - - print("\n1. 创建经济优化Excel模板文件...") - create_excel_template(economic_template_8760, "8760") - create_excel_template(economic_template_24, "24") - - print(f"\n[OK] 经济优化Excel模板创建完成!") - print(f"[FILE] 8760小时模板: {economic_template_8760}") - print(f"[FILE] 24小时模板: {economic_template_24}") - print(f"\n[INFO] 模板包含以下工作表:") - print(f" 1. 数据 - 8760小时电力数据") - print(f" 2. 参数 - 系统运行参数") - print(f" 3. 经济参数 - 经济优化参数") - print(f" 4. 说明 - 使用说明") - print(f"\n[USAGE] 使用方法:") - print(f" uv run python economic_optimization.py --excel {economic_template_8760}") - return - - print("=== Excel数据读取模块演示 ===") - - # 创建模板文件 - template_8760 = "data_template_8760.xlsx" - template_24 = "data_template_24.xlsx" - - print("\n1. 创建Excel模板文件...") - create_excel_template(template_8760, "8760") - create_excel_template(template_24, "24") - - # 分析模板数据 - print(f"\n2. 分析{template_8760}数据...") - stats = analyze_excel_data(template_8760) - if stats: - print("数据统计信息:") - for key, value in stats.items(): - print(f" {key}: {value:.2f}") - - print(f"\n3. 演示读取{template_24}数据...") - try: - data = read_excel_data(template_24) - print(f"成功读取数据,类型:{data['data_type']}") - print(f"光伏出力前10小时:{data['solar_output'][:10]}") - print(f"风电出力前10小时:{data['wind_output'][:10]}") - print(f"负荷需求前10小时:{data['load_demand'][:10]}") - - # 演示参数读取 - if 'system_parameters' in data: - params = data['system_parameters'] - print(f"\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}") - - # 验证参数 - validation = validate_system_parameters(params) - if validation['valid']: - print("[OK] 参数验证通过") - else: - print("[ERROR] 参数验证失败:") - for error in validation['errors']: - print(f" - {error}") - - if validation['warnings']: - print("[WARNING] 参数警告:") - for warning in validation['warnings']: - print(f" - {warning}") - - except Exception as e: - print(f"读取失败:{str(e)}") - - print("\n=== 演示完成 ===") - print("模板文件已创建,您可以根据实际数据修改Excel文件。") - print("系统参数可以在Excel的'参数'工作表中直接修改。") - - -if __name__ == "__main__": - main() diff --git a/main.py b/main.py index 3ffa476..16ac7cb 100644 --- a/main.py +++ b/main.py @@ -320,6 +320,15 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema grid_feed_out.append(power) # 上网电量 # 创建主要数据DataFrame + # 计算弃电损失量(弃风+弃光) + curtailment_loss = [result['curtailed_wind'][i] + result['curtailed_solar'][i] for i in range(len(result['curtailed_wind']))] + # 计算累计弃电损失量 + cumulative_curtailment = [] + cumulative = 0 + for loss in curtailment_loss: + cumulative += loss + cumulative_curtailment.append(cumulative) + data_df = pd.DataFrame({ '小时': hours, '光伏出力(MW)': solar_output, @@ -332,6 +341,8 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema '储能状态(MWh)': result['storage_profile'], '弃风量(MW)': result['curtailed_wind'], '弃光量(MW)': result['curtailed_solar'], + '弃电损失量(MW)': curtailment_loss, + '累计弃电量(MWh)': cumulative_curtailment, '购电量(MW)': grid_purchase, '上网电量(MW)': grid_feed_out }) @@ -340,6 +351,15 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema 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) # 上网电量 + + # 计算弃电损失量 + total_curtail_wind = sum(result['curtailed_wind']) + total_curtail_solar = sum(result['curtailed_solar']) + total_curtail_energy = total_curtail_wind + total_curtail_solar + + # 计算总潜在发电量 + total_potential_generation = sum(solar_output) + sum(wind_output) + sum(thermal_output) + curtailment_loss_ratio = (total_curtail_energy / total_potential_generation * 100) if total_potential_generation > 0 else 0 stats_df = pd.DataFrame({ '指标': [ @@ -351,6 +371,10 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema '弃风率', '弃光率', '上网电量比例', + '总弃风电量', + '总弃光电量', + '总弃电量', + '弃电损失比例', '能量平衡校验', '净购电量/净上网电量', '总购电量', @@ -366,6 +390,10 @@ def export_results_to_excel(solar_output, wind_output, thermal_output, load_dema f"{result['total_curtailment_wind_ratio']:.3f}", f"{result['total_curtailment_solar_ratio']:.3f}", f"{result['total_grid_feed_in_ratio']:.3f}", + f"{total_curtail_wind:.2f} MWh", + f"{total_curtail_solar:.2f} MWh", + f"{total_curtail_energy:.2f} MWh", + f"{curtailment_loss_ratio:.2f}%", "通过" 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",