Compare commits

...

16 Commits

Author SHA1 Message Date
dmy
b924f75add docs: 完善项目文档和使用说明
- 更新README.md,补充GUI特性说明和参数配置文档
- 新增使用说明目录,包含完整的操作手册和界面截图
- 优化generate_template.py,支持自定义模板输出路径
- 改进GUI界面布局,优化文件上传和模板导出功能
- 添加系统参数配置说明(电压、功率因数等)
2026-01-05 23:30:29 +08:00
dmy
c6168afd1d refactor: 调整GUI界面布局宽度为全屏显示 2026-01-05 22:27:15 +08:00
dmy
9352005db3 更新了gui界面 2026-01-05 22:06:03 +08:00
dmy
15d8f4881d feat: 改进文件保存对话框,支持跨平台系统原生保存
主要改进:
1. 新增 save_file_with_dialog 函数
   - 优先使用 PyWebview 原生模式保存对话框
   - 回退到 Tkinter 对话框(本地环境)
   - 最终回退到浏览器下载方式

2. 优化所有导出功能
   - Excel 对比表导出支持系统保存对话框
   - DXF 文件导出支持系统保存对话框
   - ZIP 批量导出支持系统保存对话框
   - 模板导出支持系统保存对话框

3. 代码质量改进
   - 统一异步函数命名规范(on_click_*)
   - 改进代码格式化和缩进
   - 添加详细的调试日志

4. 用户体验提升
   - 用户可以自由选择保存位置
   - 支持文件类型过滤
   - 自动处理文件名后缀
2026-01-05 21:32:46 +08:00
dmy
751bdef245 feat: 优化GUI用户体验和打包配置
主要改进:
1. GUI界面优化
   - 自定义文件上传显示组件,替换默认列表为更美观的卡片式展示
   - 支持环境变量 PROJECT_TEMP_DIR 配置临时目录路径
   - 优化文件导出路径管理,统一使用临时目录
   - 改进端口查找逻辑,从8082开始避免常用端口冲突
   - 修复打包后无控制台模式的stdout/stderr处理

2. 打包配置改进
   - 更新Makefile使用.spec文件进行打包
   - 添加nicegui-pack打包选项
   - 优化clean命令,使用Python跨平台清理

3. 代码优化
   - 注释掉main.py中的详细统计信息打印
   - 改进打包环境的日志配置方式
2026-01-05 17:09:39 +08:00
dmy
05ac7a3388 refactor: 统一导出文件命名规则,使用文件前缀变量
提取file_prefix变量统一管理导出文件的命名前缀,
确保所有导出的Excel和DXF文件使用一致的命名规则,
提升代码可维护性和文件命名的一致性。
2026-01-05 10:35:09 +08:00
dmy
f28e087cd2 feat: 添加Makefile用于PyInstaller打包
提供便捷的打包命令:make build/rebuild/clean/help
使用uv run确保在虚拟环境中执行pyinstaller命令
2026-01-05 10:14:40 +08:00
dmy
a5b46529da fix: 修复打包后exe程序在无控制台模式下运行时的uvicorn日志配置错误
通过检测sys.frozen判断运行环境,只在打包后的exe程序中禁用日志配置,
避免AttributeError: 'NoneType' object has no attribute 'isatty'错误。
普通Python运行环境保留完整日志功能,方便调试。
2026-01-05 09:52:51 +08:00
dmy
2ec763b86a feat: 增强电缆数据校验和UI优化 2026-01-04 19:11:45 +08:00
dmy
a42a8ec7f1 feat: 增强日志显示和方案表格信息展示 2026-01-04 18:56:00 +08:00
dmy
dd7265ff4f feat: 导出全部方案时自动包含Excel报表 2026-01-04 18:40:48 +08:00
dmy
3f73a9be26 feat: 增强导出功能和端口自动分配 2026-01-04 18:33:34 +08:00
dmy
369430aa67 feat: 优化GUI推荐方案选择和用户交互 2026-01-04 17:39:09 +08:00
dmy
00d480edbb refactor: 代码格式统一和Excel导出优化 2026-01-04 14:01:16 +08:00
dmy
06680a6e33 fix: 修复GUI界面图表显示和事件处理问题 2026-01-04 12:05:06 +08:00
dmy
6f2f851a6e feat: 新增Web GUI界面,支持交互式设计对比 2026-01-04 11:53:15 +08:00
12 changed files with 1469 additions and 150 deletions

31
Makefile Normal file
View File

@@ -0,0 +1,31 @@
.PHONY: help clean build rebuild
# 默认目标
help:
@echo "海上风电场集电线路设计优化系统 - 构建脚本"
@echo ""
@echo "可用命令:"
@echo " make build - 使用 .spec 文件生成单文件 exe 程序"
@echo " make rebuild - 清理并重新构建"
@echo " make clean - 清理构建生成的临时文件和缓存"
@echo " make help - 显示此帮助信息"
# 生成单文件exe程序
# 使用 --clean 清理 PyInstaller 缓存,-y 自动覆盖输出
build:
@echo "开始打包程序..."
uv run pyinstaller --clean -y "海上风电场集电线路设计优化系统.spec"
@echo "打包完成!"
@echo "可执行文件位于: dist/海上风电场集电线路设计优化系统.exe"
# 清理构建生成的临时文件
clean:
@echo "正在清理临时文件..."
@uv run python -c "import shutil, pathlib; [shutil.rmtree(p) for p in pathlib.Path('.').rglob('__pycache__')]; shutil.rmtree('build', ignore_errors=True); shutil.rmtree('dist', ignore_errors=True)"
@echo "清理完成!"
nice:
uv run nicegui-pack --onefile --name "海上风电场集电线路设计优化系统" gui.py --onefile --windowed
# 清理并重新构建
rebuild: clean build

151
README.md
View File

@@ -1,116 +1,91 @@
# 海上风电场集电系统设计工具
# 海上风电场集电系统设计优化工具 (Wind Farm Collector System Optimizer)
一个用于设计和优化海上风电场集电系统的Python工具支持多种布局算法和电缆优化方案。
一个用于设计和优化海上风电场集电系统拓扑的综合工具支持多种先进算法,能够根据风机坐标、功率以及海缆规格,自动生成投资成本最低、损耗最小的设计方案。
## 功能特性
## 🌟 主要功能
- 🌊 多种风机布局生成(随机分布、规则网格)
- 🔌 多种集电系统设计算法:
- 最小生成树MST算法
- K-means聚类算法
- 容量扫描算法(Capacitated Sweep
- 旋转优化算法(Rotational Sweep
- 📊 多方案对比分析和可视化
- 📋 自动导出DXF图纸和Excel报告
- 🔧 智能电缆规格选择和成本优化
- 🖥️ **原生桌面体验**:支持 Native 模式运行,提供类似本地应用的流畅体验(基于 NiceGUI & PyWebview
- 🌊 **多种布局生成**:内置模拟数据生成器,支持规则网格和随机分布布局。
- 🔌 **先进设计算法**
- **MST (Minimum Spanning Tree)**:无容量约束基准方案。
- **Capacitated Sweep (Base)**:基础扇区扫描分组。
- **Rotational Sweep**:全局最优起始角度旋转扫描优化。
- **Esau-Williams**:经典启发式算法,在距离与容量间寻找最优平衡。
- ⚙️ **灵活参数配置**:支持通过 Excel 自定义系统电压、功率因数及详细电缆规格。
- 📊 **智能方案对比**:自动运行三大场景(标准方案、含可选电缆方案、限制最大截面方案)并对比结果。
- 📁 **多格式导出**
- 自动生成 CAD 图纸 (`.dxf`),按电缆规格分层并着色。
- 导出详细的 Excel 对比报告及单方案电缆清册。
- 支持一键打包导出所有方案压缩包 (`.zip`)。
## 安装依赖
## 🛠️ 安装依赖
本项目使用 `uv``pip` 管理环境。推荐安装依赖:
```bash
pip install numpy pandas matplotlib scikit-learn scipy networkx
pip install numpy pandas matplotlib scikit-learn scipy networkx ezdxf nicegui openpyxl pywebview
```
*注:`pywebview` 用于支持原生窗口模式。*
## 使用方法
## 🚀 使用方法
### 基本用法
### 1. 启动图形化界面 (推荐)
运行以下命令启动应用,程序将自动弹出独立窗口:
```bash
python main.py
python gui.py
```
### 指定数据文件
**GUI 特性:**
- **模板导出**:点击界面上的 "导出 Excel 模板" 按钮可获取标准输入格式文件。
- **参数概览**:上传文件后自动解析并显示系统参数及电缆规格。
- **交互式分析**:点击结果表格中的行,可实时查看对应方案的拓扑图。
### 2. 命令行模式
```bash
python main.py --excel wind_farm_coordinates.xlsx
python main.py --excel your_data.xlsx
```
### 覆盖默认簇数
## 📝 输入数据规范 (Excel)
```bash
python main.py --clusters 20
```
为了确保计算结果的准确性,输入 Excel 文件应包含以下 Sheet推荐使用 GUI 导出模板):
## 算法说明
### 1. Coordinates (坐标)
| Type | ID | X | Y | Power | PlatformHeight |
|------|----|---|---|-------|----------------|
| Substation | Sub1 | 4000 | -800 | 0 | 0 |
| Turbine | 1 | 0 | 0 | 8.0 | 25 |
| ... | ... | ... | ... | ... | ... |
### 1. MST Method最小生成树
- 使用最小生成树连接所有风机到海上变电站
- 简单高效,适合初步设计
### 2. Cables (电缆)
**必须遵守以下规则:**
- **单调递增性**:电缆必须按截面从小到大排列,且对应的额定载流量也必须严格递增。
- **可选电缆规则**
- `Optional` 列标记为 'Y' 的电缆最多只能有一条。
- 若存在可选电缆,它必须是列表中截面最大的一条。
### 2. K-means Clustering
- 将风机分组到多个回路中
- 平衡每回路的功率分配
### 3. Parameters (参数) [可选]
可自定义系统级参数,若不提供则使用默认值。
### 3. Capacitated Sweep容量扫描
- 考虑电缆容量约束的智能分组
- 支持多种电缆规格自动选择
| Parameter | Value | 说明 |
|-----------|-------|------|
| Voltage (kV) | 66 | 系统电压,支持 `Voltage``System Voltage`。若 key 包含 `kV` 则自动 *1000。 |
| Power Factor | 0.95 | 功率因数 (0-1)。 |
### 4. Rotational Sweep旋转优化
- 在容量扫描基础上进行旋转优化
- 进一步降低总成本和损耗
## 📈 场景说明 (Scenarios)
## 输出文件
1. **Scenario 1 (Standard)**:仅使用非可选(标准)电缆进行优化。
2. **Scenario 2 (With Optional)**:包含标记为 'Y' 的大型电缆,适用于尝试增加单回路容量的场景。
3. **Scenario 3 (No Max)**:排除最大截面电缆,测试在电缆供应受限时的最优拓扑。
1. **可视化图片**`wind_farm_design_comparison.png`
- 不同算法的设计方案对比图
## 📂 输出文件说明
2. **CAD图纸**`wind_farm_design.dxf`
- 可导入CAD软件的详细设计图纸
- **Excel 报告**`[文件名]_result.xlsx` 包含所有方案的总览及详细连接清单。
- **CAD 图纸**`design_[方案名].dxf` 包含分层分色的拓扑图。
- **全部方案**`[文件名]_result.zip` 包含所有图纸及 Excel 报告。
3. **数据报告**`wind_farm_design.xlsx`
- 包含所有方案的详细技术参数和成本分析
## ⚖️ 许可证
## 关键参数说明
可以在 `main.py` 中调整以下核心常量以适配不同项目:
```python
VOLTAGE_LEVEL = 66000 # 集电系统电压 (V)
POWER_FACTOR = 0.95 # 功率因数
cost_multiplier = 5.0 # 海缆相对于陆缆的成本倍数
```
## 电缆规格配置
项目支持多种电缆规格,可在 `generate_template.py` 中配置:
| 截面积(mm²) | 容量(MW) | 电阻(Ω/km) | 成本(元/m) |
|-------------|----------|------------|------------|
| 35 | 150 | 0.524 | 80 |
| 70 | 215 | 0.268 | 120 |
| 95 | 260 | 0.193 | 150 |
| 120 | 295 | 0.153 | 180 |
| 150 | 330 | 0.124 | 220 |
| 185 | 370 | 0.0991 | 270 |
| 240 | 425 | 0.0754 | 350 |
| 300 | 500 | 0.0601 | 450 |
| 400 | 580 | 0.0470 | 600 |
## 输出示例
```text
===== 开始比较电缆方案 =====
--- All Cables (Base) ---
[Base] Cost: ¥12,456,789.12 | Loss: 234.56 kW
[Rotational] Cost: ¥12,234,567.89 | Loss: 223.45 kW
--- High Current (Base) ---
[Base] Cost: ¥11,987,654.32 | Loss: 245.67 kW
[Rotational] Cost: ¥11,876,543.21 | Loss: 234.56 kW
推荐方案: High Current (Rotational) (默认)
```
## 许可证
本项目仅供学习和研究使用。
本项目仅供工程学习、研究和初步设计评估使用。详细计算应以专业设计院规范为准。

View File

@@ -1,7 +1,7 @@
import pandas as pd
import numpy as np
def create_template():
def create_template(output_file='windfarm_template.xlsx'):
# Create sample data similar to the internal generator
data = []
@@ -50,12 +50,19 @@ def create_template():
]
df_cables = pd.DataFrame(cable_data)
# Create System Parameters data
param_data = [
{'Parameter': 'Voltage (kV) / 电压 (kV)', 'Value': 66},
{'Parameter': 'Power Factor / 功率因数', 'Value': 0.95}
]
df_params = pd.DataFrame(param_data)
# Save to Excel
output_file = 'coordinates.xlsx'
with pd.ExcelWriter(output_file) as writer:
df.to_excel(writer, sheet_name='Coordinates', index=False)
df_cables.to_excel(writer, sheet_name='Cables', index=False)
print(f"Created sample file: {output_file} with sheets 'Coordinates' and 'Cables'")
df_params.to_excel(writer, sheet_name='Parameters', index=False)
print(f"Created sample file: {output_file} with sheets 'Coordinates', 'Cables', and 'Parameters'")
if __name__ == "__main__":
create_template()

1028
gui.py Normal file

File diff suppressed because it is too large Load Diff

233
main.py
View File

@@ -153,14 +153,85 @@ def load_data_from_excel(file_path):
specs.append((section, capacity, resistance, cost, is_optional))
if specs:
specs.sort(key=lambda x: x[1]) # 按载流量排序
# --- 输入数据校验:单调性 ---
for i in range(len(specs) - 1):
curr_s = specs[i]
next_s = specs[i + 1]
# 校验截面 (section)
if curr_s[0] >= next_s[0]:
raise ValueError(
f"电缆数据校验失败Excel中电缆顺序必须按截面从小到大排列。第{i+1}行({curr_s[0]}mm²) >= 第{i+2}行({next_s[0]}mm²)。"
)
# 校验载流量 (capacity)
if curr_s[1] >= next_s[1]:
raise ValueError(
f"电缆数据校验失败Excel中电缆载流量必须严格递增。第{i+1}行({curr_s[1]}A) >= 第{i+2}行({next_s[1]}A)。"
)
specs.sort(key=lambda x: x[1]) # 按载流量排序
# --- 输入数据校验 ---
# 筛选出所有标记为 Optional='Y' 的电缆
optional_cables = [s for s in specs if s[4]]
# 规则1: 最多只能有一条可选电缆
if len(optional_cables) > 1:
raise ValueError(
f"电缆数据校验失败:检测到 {len(optional_cables)} 条可选电缆(Optional='Y')。系统限制最多只能指定 1 条可选电缆。"
)
# 规则2: 如果存在可选电缆,它必须是所有电缆中截面最大的一条
if len(optional_cables) == 1:
opt_cable = optional_cables[0]
# s[0] 是截面积
max_section = max(s[0] for s in specs)
if opt_cable[0] < max_section:
raise ValueError(
f"电缆数据校验失败:可选电缆 ({opt_cable[0]}mm²) 必须是所有电缆中截面最大的一条 (当前最大为 {max_section}mm²)。"
)
# --------------------
cable_specs = specs
print(f"成功加载: {len(turbines)} 台风机, {len(substation)} 座升压站")
if cable_specs:
print(f"成功加载: {len(cable_specs)} 种电缆规格")
return turbines, substation, cable_specs
# 读取参数数据 (如果存在)
system_params = {}
param_sheet_name = None
if 'Parameters' in xl.sheet_names:
param_sheet_name = 'Parameters'
elif '参数' in xl.sheet_names:
param_sheet_name = '参数'
if param_sheet_name:
try:
params_df = pd.read_excel(xl, param_sheet_name)
# 假设格式为两列Parameter (参数名), Value (值)
if len(params_df.columns) >= 2:
for _, row in params_df.iterrows():
key = str(row[0]).strip().lower()
try:
val = float(row[1])
if 'voltage' in key or '电压' in key:
# 检测是否为kV单位
if 'kv' in key:
system_params['voltage'] = val * 1000
else:
system_params['voltage'] = val
elif 'factor' in key or '功率因数' in key:
system_params['power_factor'] = val
except ValueError:
pass
if system_params:
print(f"成功加载系统参数: {system_params}")
except Exception as e:
print(f"读取参数Sheet失败: {e}")
return turbines, substation, cable_specs, system_params
except Exception as e:
print(f"读取Excel文件失败: {str(e)}")
@@ -276,7 +347,7 @@ def design_with_kmeans(turbines, substation, n_clusters=3):
return cluster_connections + substation_connections, turbines
# 3.5 带容量约束的扇区扫描算法 (Capacitated Sweep) - 基础版
def design_with_capacitated_sweep(turbines, substation, cable_specs=None):
def design_with_capacitated_sweep(turbines, substation, cable_specs=None, voltage=VOLTAGE_LEVEL, power_factor=POWER_FACTOR):
"""
使用带容量约束的扇区扫描算法设计集电线路 (基础版:单次扫描)
原理:
@@ -286,21 +357,17 @@ def design_with_capacitated_sweep(turbines, substation, cable_specs=None):
4. 满载后开启新回路。
"""
# 1. 获取电缆最大容量
max_mw = get_max_cable_capacity_mw(cable_specs)
# print(f"DEBUG: 扇区扫描算法启动 - 单回路容量限制: {max_mw:.2f} MW")
substation_coord = substation[0]
max_mw = get_max_cable_capacity_mw(cable_specs, voltage=voltage, power_factor=power_factor)
substation_coord = substation[0]
# 2. 计算角度 (使用 arctan2 返回 -pi 到 pi)
# 避免直接修改原始DataFrame使用副本
work_df = turbines.copy()
dx = work_df['x'] - substation_coord[0]
dy = work_df['y'] - substation_coord[1]
work_df['angle'] = np.arctan2(dy, dx)
# 3. 寻找最佳起始角度 (最大角度间隙)
# 按角度排序
work_df = work_df.sort_values('angle').reset_index(drop=True) # 重置索引方便切片
work_df = work_df.sort_values('angle').reset_index(drop=True)
angles = work_df['angle'].values
n = len(angles)
@@ -381,7 +448,7 @@ def design_with_capacitated_sweep(turbines, substation, cable_specs=None):
return cluster_connections + substation_connections, turbines
# 3.6 旋转扫描算法 (Rotational Sweep) - 优化版
def design_with_rotational_sweep(turbines, substation, cable_specs=None):
def design_with_rotational_sweep(turbines, substation, cable_specs=None, voltage=VOLTAGE_LEVEL, power_factor=POWER_FACTOR):
"""
使用带容量约束的扇区扫描算法设计集电线路 (优化版:旋转扫描)
原理:
@@ -391,8 +458,7 @@ def design_with_rotational_sweep(turbines, substation, cable_specs=None):
4. 对每种分组方案计算MST成本选出总成本最低的方案。
"""
# 1. 获取电缆最大容量
max_mw = get_max_cable_capacity_mw(cable_specs)
# print(f"DEBUG: 扇区扫描算法启动 - 单回路容量限制: {max_mw:.2f} MW")
max_mw = get_max_cable_capacity_mw(cable_specs, voltage=voltage, power_factor=power_factor)
substation_coord = substation[0]
@@ -410,6 +476,7 @@ def design_with_rotational_sweep(turbines, substation, cable_specs=None):
best_connections = []
best_turbines_state = None
best_start_idx = -1
best_id_to_cluster = {}
# 遍历所有可能的起始点
for start_idx in range(n_turbines):
@@ -463,8 +530,7 @@ def design_with_rotational_sweep(turbines, substation, cable_specs=None):
# 2. 连接升压站长度
dists = np.sqrt((cluster_rows['x'] - substation_coord[0])**2 +
(cluster_rows['y'] - substation_coord[1])**2)
min_dist = dists.min()
current_total_length += min_dist
current_total_length += dists.min()
# --- 比较并保存最佳结果 ---
if current_total_length < best_cost:
@@ -506,7 +572,7 @@ def design_with_rotational_sweep(turbines, substation, cable_specs=None):
return final_connections, turbines
def get_max_cable_capacity_mw(cable_specs=None):
def get_max_cable_capacity_mw(cable_specs=None, voltage=VOLTAGE_LEVEL, power_factor=POWER_FACTOR):
"""
计算给定电缆规格中能够承载的最大功率 (单位: MW)。
@@ -514,6 +580,8 @@ def get_max_cable_capacity_mw(cable_specs=None):
参数:
cable_specs (list, optional): 电缆规格列表。每个元素应包含 (截面积, 额定电流, 单价, 损耗系数)。
voltage (float): 系统电压 (V), 默认 66000
power_factor (float): 功率因数, 默认 0.95
返回:
float: 最大功率承载能力 (MW)。
@@ -541,13 +609,13 @@ def get_max_cable_capacity_mw(cable_specs=None):
# 计算最大功率P = √3 * U * I * cosφ
# 这里假设降额系数为 1 (不降额)
max_current = max_current_capacity * 1
max_power_w = np.sqrt(3) * VOLTAGE_LEVEL * max_current * POWER_FACTOR
max_power_w = np.sqrt(3) * voltage * max_current * power_factor
# 将单位从 W 转换为 MW
return max_power_w / 1e6
# 5. 计算集电线路方案成本
def evaluate_design(turbines, connections, substation, cable_specs=None, is_offshore=False, method_name="Unknown Method"):
def evaluate_design(turbines, connections, substation, cable_specs=None, is_offshore=False, method_name="Unknown Method", voltage=VOLTAGE_LEVEL, power_factor=POWER_FACTOR):
"""评估设计方案的总成本和损耗"""
total_cost = 0
total_loss = 0
@@ -582,8 +650,8 @@ def evaluate_design(turbines, connections, substation, cable_specs=None, is_offs
try:
# 找到通往升压站的最短路径上的下一个节点
path = nx.shortest_path(graph, source=node, target='substation')
if len(path) > 1:
parent = path[1] # path[0]是node自己path[1]是父节点
if len(path) > 1: # path[0]是node自己path[1]是父节点
parent = path[1]
power_flow[parent] += power_flow[node]
except nx.NetworkXNoPath:
pass
@@ -650,7 +718,7 @@ def evaluate_design(turbines, connections, substation, cable_specs=None, is_offs
cable_specs_to_use = cable_specs
# 估算电流
current = (power * 1e6) / (np.sqrt(3) * VOLTAGE_LEVEL * POWER_FACTOR)
current = (power * 1e6) / (np.sqrt(3) * voltage * power_factor)
# 选择满足载流量的最小电缆
selected_spec = None
@@ -842,7 +910,8 @@ def export_all_scenarios_to_excel(results, filename):
:param filename: 输出文件路径
"""
try:
with pd.ExcelWriter(filename) as writer:
# 使用 openpyxl 引擎以便后续写入单元格
with pd.ExcelWriter(filename, engine='openpyxl') as writer:
# 1. 总览 Sheet
summary_data = []
for res in results:
@@ -865,7 +934,7 @@ def export_all_scenarios_to_excel(results, filename):
# 2. 每个方案的详细 Sheet
for res in results:
# 清理 Sheet 名称
safe_name = res['name'].replace(':', '').replace('/', '-').replace('\\', '-')
safe_name = res['name'].replace(':', '').replace('/', '-').replace('\\', '-').replace(' ', '_')
# 截断过长的名称 (Excel限制31字符)
if len(safe_name) > 25:
safe_name = safe_name[:25]
@@ -886,7 +955,13 @@ def export_all_scenarios_to_excel(results, filename):
'Cost (¥)': conn['cable']['cost']
})
df = pd.DataFrame(data)
df.to_excel(writer, sheet_name=safe_name, index=False)
# 从第 2 行开始写入数据startrow=1Excel中为第2行留出第 1 行写标题
df.to_excel(writer, sheet_name=safe_name, index=False, startrow=1)
# 在第一行写入方案名称
ws = writer.sheets[safe_name]
ws.cell(row=1, column=1, value=f"Scenario: {res['name']}")
print(f"成功导出包含所有方案的Excel文件: {filename}")
except Exception as e:
@@ -976,10 +1051,10 @@ def visualize_design(turbines, substation, connections, title, ax=None, show_cos
legend_handles[section] = line
# 打印统计信息
if is_detailed:
print(f"[{title.splitlines()[0]}] 电缆统计:")
for section in sorted(cable_counts.keys()):
print(f" {section}mm²: {cable_counts[section]}")
# if is_detailed:
# print(f"[{title.splitlines()[0]}] 电缆统计:")
# for section in sorted(cable_counts.keys()):
# print(f" {section}mm²: {cable_counts[section]} 条")
# 设置图形属性
ax.set_title(title, fontsize=10)
@@ -1004,21 +1079,25 @@ def visualize_design(turbines, substation, connections, title, ax=None, show_cos
return ax
# 7. 主函数:比较两种设计方法
def compare_design_methods(excel_path=None, n_clusters_override=None):
def compare_design_methods(excel_path=None, n_clusters_override=None, interactive=True, plot_results=True):
"""
比较MST和三种电缆方案下的K-means设计方法
:param excel_path: Excel文件路径
:param n_clusters_override: 可选,手动指定簇的数量
:param interactive: 是否启用交互式导出 (CLI模式)
:param plot_results: 是否生成和保存对比图表
"""
cable_specs = None
system_params = {}
if excel_path:
print(f"正在从 {excel_path} 读取坐标数据...")
try:
turbines, substation, cable_specs = load_data_from_excel(excel_path)
turbines, substation, cable_specs, system_params = load_data_from_excel(excel_path)
scenario_title = "Offshore Wind Farm (Imported Data)"
except Exception:
print("回退到自动生成数据模式...")
return compare_design_methods(excel_path=None, n_clusters_override=n_clusters_override)
return compare_design_methods(excel_path=None, n_clusters_override=n_clusters_override, interactive=interactive, plot_results=plot_results)
else:
print("正在生成海上风电场数据 (规则阵列布局)...")
turbines, substation = generate_wind_farm_data(n_turbines=30, layout='grid', spacing=800)
@@ -1026,10 +1105,19 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
is_offshore = True
voltage = system_params.get('voltage', VOLTAGE_LEVEL)
power_factor = system_params.get('power_factor', POWER_FACTOR)
print(f"使用的系统参数: 电压={voltage} V, 功率因数={power_factor}")
# 准备三种电缆方案
# 原始 specs 是 5 元素元组: (section, capacity, resistance, cost, is_optional)
# 下游函数期望 4 元素元组: (section, capacity, resistance, cost)
has_optional_cables = False
if cable_specs:
# 检查是否存在 Optional 为 Y 的电缆
has_optional_cables = any(s[4] for s in cable_specs)
# 方案 1: 不含 Optional='Y' (Standard)
specs_1 = [s[:4] for s in cable_specs if not s[4]]
@@ -1049,25 +1137,34 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
specs_1 = default_specs
specs_2 = default_specs
specs_3 = default_specs[:-1]
# 默认库视为没有 optional
has_optional_cables = False
scenarios = [
("Scenario 1 (Standard)", specs_1),
("Scenario 2 (With Optional)", specs_2),
("Scenario 3 (No Max)", specs_3)
("Scenario 1 (Standard)", specs_1)
]
if has_optional_cables:
scenarios.append(("Scenario 2 (With Optional)", specs_2))
scenarios.append(("Scenario 3 (No Max)", specs_3))
else:
# 重新编号,保证连续性
scenarios.append(("Scenario 2 (No Max)", specs_3))
# 1. MST 方法作为基准 (使用 Scenario 1)
mst_connections = design_with_mst(turbines, substation)
mst_evaluation = evaluate_design(turbines, mst_connections, substation, cable_specs=specs_1, is_offshore=is_offshore, method_name="MST Method")
mst_evaluation = evaluate_design(turbines, mst_connections, substation, cable_specs=specs_1, is_offshore=is_offshore, method_name="MST Method", voltage=voltage, power_factor=power_factor)
# 准备画布 2x2
fig, axes = plt.subplots(2, 2, figsize=(20, 18))
axes = axes.flatten()
# 绘制 MST
visualize_design(turbines, substation, mst_evaluation['details'],
f"MST Method (Standard Cables)\nTotal Cost: ¥{mst_evaluation['total_cost']/10000:.2f}",
ax=axes[0])
fig = None
axes = []
if plot_results:
fig, axes = plt.subplots(2, 2, figsize=(20, 18))
axes = axes.flatten()
# 绘制 MST
visualize_design(turbines, substation, mst_evaluation['details'],
f"MST Method (Standard Cables)\nTotal Cost: ¥{mst_evaluation['total_cost']/10000:.2f}",
ax=axes[0])
print(f"\n===== 开始比较电缆方案 =====")
@@ -1094,7 +1191,7 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
# 计算参数
total_power = turbines['power'].sum()
max_cable_mw = get_max_cable_capacity_mw(cable_specs=current_specs)
max_cable_mw = get_max_cable_capacity_mw(cable_specs=current_specs, voltage=voltage, power_factor=power_factor)
# 确定簇数 (针对 Base 算法)
if n_clusters_override is not None:
@@ -1114,11 +1211,11 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
# --- Run 1: Base Algorithm (Capacitated Sweep) ---
base_name = f"{name} (Base)"
conns_base, turbines_base = design_with_capacitated_sweep(
turbines.copy(), substation, cable_specs=current_specs
turbines.copy(), substation, cable_specs=current_specs, voltage=voltage, power_factor=power_factor
)
eval_base = evaluate_design(
turbines, conns_base, substation, cable_specs=current_specs,
is_offshore=is_offshore, method_name=base_name
is_offshore=is_offshore, method_name=base_name, voltage=voltage, power_factor=power_factor
)
comparison_results.append({
@@ -1134,11 +1231,11 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
# --- Run 2: Rotational Algorithm (Optimization) ---
rot_name = f"{name} (Rotational)"
conns_rot, turbines_rot = design_with_rotational_sweep(
turbines.copy(), substation, cable_specs=current_specs
turbines.copy(), substation, cable_specs=current_specs, voltage=voltage, power_factor=power_factor
)
eval_rot = evaluate_design(
turbines, conns_rot, substation, cable_specs=current_specs,
is_offshore=is_offshore, method_name=rot_name
is_offshore=is_offshore, method_name=rot_name, voltage=voltage, power_factor=power_factor
)
comparison_results.append({
@@ -1158,7 +1255,7 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
)
eval_ew = evaluate_design(
turbines, conns_ew, substation, cable_specs=current_specs,
is_offshore=is_offshore, method_name=ew_name
is_offshore=is_offshore, method_name=ew_name, voltage=voltage, power_factor=power_factor
)
comparison_results.append({
@@ -1184,15 +1281,17 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
# 可视化 (只画 Base 版本)
ax_idx = i + 1
if ax_idx < 4:
if plot_results and ax_idx < 4:
n_circuits = turbines_base['cluster'].nunique()
title = f"{base_name} ({n_circuits} circuits)\nCost: ¥{eval_base['total_cost']/10000:.2f}"
visualize_design(turbines_base, substation, eval_base['details'], title, ax=axes[ax_idx])
plt.tight_layout()
output_filename = 'wind_farm_design_comparison.png'
plt.savefig(output_filename, dpi=300)
print(f"\n比较图(Base版)已保存至: {output_filename}")
if plot_results:
plt.tight_layout()
output_filename = 'wind_farm_design_comparison.png'
plt.savefig(output_filename, dpi=300)
plt.close()
print(f"\n比较图(Base版)已保存至: {output_filename}")
# 准备文件路径
if excel_path:
@@ -1208,6 +1307,10 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
if comparison_results:
export_all_scenarios_to_excel(comparison_results, excel_out_filename)
if not interactive:
print(f"非交互模式:已自动导出 Excel 对比报表: {excel_out_filename}")
return comparison_results
# 交互式选择导出 DXF
print("\n===== 方案选择 =====")
best_idx = 0
@@ -1240,13 +1343,25 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
choice = best_idx
selected_res = comparison_results[choice]
print(f"正在导出 '{selected_res['name']}' 到 DXF: {dxf_filename} ...")
export_to_dxf(selected_res['turbines'], substation, selected_res['eval']['details'], dxf_filename)
# 生成带方案名称的文件名
base_dxf_name, ext = os.path.splitext(dxf_filename)
safe_suffix = selected_res['name'].replace(' ', '_').replace(':', '').replace('(', '').replace(')', '').replace('/', '-')
final_filename = f"{base_dxf_name}_{safe_suffix}{ext}"
print(f"正在导出 '{selected_res['name']}' 到 DXF: {final_filename} ...")
export_to_dxf(selected_res['turbines'], substation, selected_res['eval']['details'], final_filename)
except Exception as e:
print(f"输入处理出错: {e},将使用默认推荐方案。")
selected_res = comparison_results[best_idx]
print(f"正在导出 '{selected_res['name']}' 到 DXF: {dxf_filename} ...")
export_to_dxf(selected_res['turbines'], substation, selected_res['eval']['details'], dxf_filename)
# 生成带方案名称的文件名
base_dxf_name, ext = os.path.splitext(dxf_filename)
safe_suffix = selected_res['name'].replace(' ', '_').replace(':', '').replace('(', '').replace(')', '').replace('/', '-')
final_filename = f"{base_dxf_name}_{safe_suffix}{ext}"
print(f"正在导出 '{selected_res['name']}' 到 DXF: {final_filename} ...")
export_to_dxf(selected_res['turbines'], substation, selected_res['eval']['details'], final_filename)
return comparison_results
@@ -1260,4 +1375,4 @@ if __name__ == "__main__":
# 3. 运行比较
# 如果没有提供excel文件将自动回退到生成数据模式
compare_design_methods(args.excel, n_clusters_override=args.clusters)
compare_design_methods(args.excel, n_clusters_override=args.clusters, interactive=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,163 @@
# 海上风电场集电线路设计优化软件 - 操作手册
**文档版本:** v1.0
**适用对象:** 海上能源业务开发部 - 电气专业组
**编制日期:** 2026年1月5日
---
## 1. 软件概述
本软件专为海上风电场内集电系统35kV/66kV/110kV设计旨在通过多种先进的拓扑优化算法如Esau-Williams、MST、旋转扫描法辅助电气工程师快速完成集电线路的路径规划与经济性比选。
软件能够根据风机坐标、海缆载流量及造价数据,自动计算并生成线损最小、投资最优的接线方案,并支持一键导出 CAD 图纸和海缆长度。
---
## 2. 准备工作:输入数据编制
软件通过读取 Excel 文件获取设计输入条件。为了确保计算准确,请严格按照以下格式准备数据。
### 2.1 获取标准模板
启动软件后,点击左侧配置面板顶部的 **“导出 Excel 模板”** 按钮,保存 `coordinates.xlsx` 文件。建议在此模板基础上进行修改。
![图示:软件主界面左侧配置面板,高亮“导出 Excel 模板”按钮](导出模板按钮截图.png)
### 2.2 数据表详解
输入文件通常包含三个 Sheet工作表
![图示:输入数据表](输入数据表.png)
#### (1) Coordinates (坐标数据) - **[必须]**
录入升压站和所有风机的位置及参数。
* **Type**: 填写 `Substation` (升压站) 或 `Turbine` (风机)。
* **ID**: 设备编号(如 Sub1, 01, 02...)。
* **X / Y**: 投影坐标(单位:米)。建议使用高斯投影坐标 (X, Y),以保证距离计算准确。
* **Power**: 设备功率单位MW。升压站填 0。
* **PlatformHeight**: 塔筒/升压站平台高度(单位:米),用于计算海缆爬升段长度。
<!-- ![图示Excel中Coordinates工作表的填写范例](占位符-坐标数据表截图) -->
#### (2) Cables (电缆规格) - **[必须]**
录入本项目拟采用的海缆截面库。
* **Section**: 导体截面 (mm²)。
* **Capacity**: 额定载流量 (A)。**注意:** 需填入考虑降容系数后的实际允许载流量。
* **Resistance**: 交流电阻 (Ω/km)。
* **Cost**: 综合单价 (元/m)。包含本体及敷设费用。
* **Optional**: 可选标记。
*`Y`: 表示该型号为“可选大截面电缆”(例如仅在特定大负荷回路使用)。
* *规则:* 列表中必须按截面**从小到大**排序。
#### (3) Parameters (系统参数) - **[必须]**
定义系统级电气参数。
* **Voltage**: 集电系统标称电压(如 66 或 35单位 kV。
* **Power Factor**: 系统功率因数(如 0.95)。
---
## 3. 算法与优化策略介绍
本软件内置了四种不同机制的拓扑优化算法,分别适用于不同的工程场景。在点击“运行”后,系统会并发执行所有算法,模型自动筛选最优解。
### 3.1 最小生成树算法 (MST Method)
* **原理**:基于图论中的 Kruskal 或 Prim 算法,寻找连接所有风机且总路径长度最短的树状结构。此算法**不考虑电缆载流量限制**。
* **作用**:仅作为理论上的“距离基准”参考。它展示了如果导线无限粗、电流无限制情况下的最短布线可能。在实际工程中通常不可行,但可用于评估其他方案的距离效率。
### 3.2 基础扇区扫描法 (Capacitated Sweep - Base)
* **原理**:以升压站为中心,将平面划分为若干个扇区。算法按顺时针方向扫描风机,一旦累积功率达到当前最大电缆的承载上限,就进行“切分”,形成一个独立的集电回路。
* **特点**:计算速度极快,拓扑结构简单清晰,类似于人工排布的辐射状接线。
* **局限**:对起始扫描角度敏感,可能因为恰好在某个位置切分而导致该回路包含距离很远的风机。
### 3.3 旋转扫描优化法 (Rotational Sweep)
* **原理**:这是对“基础扇区扫描法”的增强版。它会自动尝试 0° 到 360° 之间的所有可能的起始扫描角度。
* **优势**:通过旋转扫描角度,可以有效避免因特定方位角切分不当造成的“长尾巴”连线,通常能比基础扫描法节省 3%~8% 的线缆成本。这是最接近人工精细化排布的自动化算法。
### 3.4 Esau-Williams 启发式算法 (Esau-Williams Heuristic)
* **原理**经典的约束最小生成树CMST算法。它从“所有风机都直连升压站”的初始状态开始迭代计算“将两台风机互联并断开其中一条回升压站连线”所能带来的成本节省Trade-off。在满足载流量约束的前提下优先执行节省最大的互联操作。
* **优势**:能够跳出辐射状的思维定式,自动发现树状、多分叉等复杂但更经济的拓扑结构。在风机分布不规则、离岸距离较远或电缆造价极高的情况下,往往能得到比扫描法更优的结果。
---
## 4. 操作流程
### 步骤一:启动软件
双击运行程序,等待主界面加载完成。界面分为上侧“操作区”和下侧“结果展示区”。
### 步骤二:上传数据
在上侧“配置面板”中,点击 **“选择Excel文件”** 区域(或点击云朵图标),选择编制好的项目 Excel 文件。
上传成功后:
1. 文件名右侧会出现绿色对勾。
2. 右侧信息面板会自动解析并显示**系统参数**(电压、功率因数)和**电缆规格列表**,请务必核对这些数据是否正确。
![图示:文件上传成功后的状态,以及右侧参数预览面板](数据加载成功截图.png)
### 步骤三:运行计算
点击左侧下方的大型按钮 **“运行方案对比”**。
软件将自动执行以下计算任务:
1. **多场景分析**
* *Scenario 1 (标准)*:仅使用标准电缆库进行优化。
* *Scenario 2 (含可选)*:尝试引入更大截面的可选电缆,评估是否能减少回路数。
* *Scenario 3 (限制)*:模拟最大截面电缆缺货情况下的次优方案。
2. **多算法寻优**:对每个场景同时运行 MST、基础扫描、旋转扫描、Esau-Williams 等多种算法。
*注意:计算过程中下方黑色日志窗口会实时滚动显示计算进度,通常耗时 10-60 秒,取决于风机数量。*
![图示:正在计算时的进度条和日志窗口](计算过程截图.png)
### 步骤四:查看与比选
计算完成后,系统会自动筛选出**综合造价最低**的推荐方案,并在界面上展示。
1. **结果列表**
右侧中部的表格列出了所有计算出的可行方案。
* `Cost (万元)`:总投资估算。
* `Loss (kW)`:全场集电线路总线损。
* **操作**:点击表格中的任意一行,下方的拓扑图会自动切换到该方案。
2. **拓扑可视化**
右侧下方的绘图区展示集电线路走向。
* 不同颜色的线条代表不同截面的海缆。
* 图例会标明线型对应的截面。
* 升压站显示为红色方块,风机为圆点。
![图示:结果对比表格和拓扑图联动展示](结果比选交互截图.png)
---
## 5. 成果导出
比选确定最终方案后,可以使用底部的 **“导出与下载”** 功能区生成设计文件。
### 5.1 导出 CAD 图纸 (.dxf)
* **导出推荐方案**:直接点击 **“导出推荐方案 DXF”**。
* **导出特定方案**:在表格中选中任意一行,点击 **“导出选中方案 DXF”**。
生成的 DXF 文件特点:
* **分层管理**不同截面的电缆位于不同图层Layer方便在 AutoCAD 中通过图层过滤器批量修改线型或颜色。
* **地理坐标**:图纸保留了 Excel 中的原始坐标系,可直接通过“原点粘贴”功能合并到项目总图中。
![图示导出的DXF图纸在CAD中打开的效果](CAD图纸效果示例.png)
### 5.2 导出 Excel 报告
点击 **“下载 Excel 对比表”**,将生成一份包含详细工程数据的 Excel 文件,内容包括:
* **Summary**: 所有方案的经济技术指标汇总。
* **Details**: 推荐方案的每一条海缆连接明细(起点、终点、长度、型号、负载率)。
### 5.3 批量归档
点击 **“导出全部方案 DXF (ZIP)”**,可将所有计算产生的方案图纸和报表打包下载,便于项目归档。
---
## 6. 常见问题 (FAQ)
**Q: 为什么上传文件后提示“电缆数据校验失败”?**
A: 请检查 `Cables` 表。电缆必须严格按照**截面从小到大**排列,且载流量也必须随截面增加而增加。如果定义了 `Optional` 电缆,它必须是列表中截面最大的一条。
**Q: 计算出的方案有的回路负载率过高怎么办?**
A: 软件算法以不超过额定载流量为约束条件(默认允许 100% 满载)。在实际工程中,建议在 `Cables` 表录入载流量时,预先乘以 0.95 或其他安全系数,留出裕度。
**Q: 图纸导出后,在 CAD 里看不到东西?**
A: 请双击鼠标滚轮Zoom Extents全屏显示。由于风机坐标通常是大地坐标数值很大如果 CAD 当前视口在 (0,0) 附近,可能会找不到图形。
---
**技术支持:** 海上能源业务开发部 - 数字化小组

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB