Compare commits

...

10 Commits

Author SHA1 Message Date
dmy
d563905f28 添加完整的项目文档README.md
- 提供详细的功能特性说明和算法介绍
- 包含完整的安装和使用指南
- 添加电缆规格配置表格
- 更新输出示例以反映最新功能
- 完善项目结构说明和参数配置
2026-01-02 01:24:02 +08:00
dmy
b5718a0cc2 优化风电场设计方案对比算法:添加旋转算法和双模式对比
- 新增design_with_rotational_sweep函数:实现旋转优化算法
- 修改compare_design_methods函数:
  * 将MST结果纳入对比列表
  * 每个电缆场景运行Base和Rotational两种算法
  * 添加成本和损耗对比显示
  * 优化可视化展示和文件输出
- 改进算法选择逻辑:增强簇数计算的智能化
- 更新输出格式:区分不同算法结果并优化显示
2026-01-02 00:25:33 +08:00
dmy
6cac8806f0 更新风电场设计工具:扩展电缆规格并优化多方案比较功能
- 更新generate_template.py:增加电缆型号至9种,添加Optional字段完善数据结构
- 重构main.py比较流程:
  * 实现多方案结果存储机制
  * 添加交互式DXF导出选择功能(支持单方案/全部导出)
  * 优化多方案可视化对比展示
  * 改进Excel导出功能,整合所有方案数据
  * 增强用户交互体验和结果展示
2026-01-01 23:58:03 +08:00
dmy
34b0d70309 feat: 新增Excel报表导出功能 2026-01-01 16:25:55 +08:00
dmy
6454a2c01e feat: 增强DXF导出和命令行参数支持 2026-01-01 14:31:46 +08:00
dmy
2d50ab0df0 feat: 优化K-means簇数计算逻辑,基于风机数量与电缆型号数量 2026-01-01 13:56:02 +08:00
dmy
41e3cf355c fix: 调整海上风电场电缆成本计算方式 2026-01-01 13:24:44 +08:00
dmy
e6d98297b1 feat: 增加平台高度参数,优化电缆长度计算 2026-01-01 12:23:00 +08:00
dmy
e7e12745d1 feat: 优化风电场集电系统设计,支持电缆规格配置 2026-01-01 11:55:05 +08:00
dmy
4db9d138b8 feat: 优化风电场集电系统设计,支持电缆规格配置
主要更改:
• 新增电缆规格配置支持
  - Excel文件新增Cables工作表,支持自定义电缆参数(截面、载流量、电阻、成本)
  - 实现容量约束扫描算法(Capacitated Sweep),替代原有K-means方法
  - 动态计算所需回路数量,确保每条回路的电缆载流量符合约束

• 代码增强
  - main.py: 集成电缆规格参数,新增命令行参数支持(--clusters手动指定簇数)
  - generate_template.py: 模板文件新增Cables工作表,提供9种标准电缆规格(35mm²-400mm²)

• 文档更新
  - 新增project_context.md: 详细记录项目背景、算法逻辑、电气建模和当前状态
  - 新增GEMINI.md: 开发者偏好配置

优化后的设计更符合实际工程需求,支持电缆容量约束,输出更准确的成本和损耗评估。
2026-01-01 11:39:14 +08:00
6 changed files with 1148 additions and 207 deletions

9
GEMINI.md Normal file
View File

@@ -0,0 +1,9 @@
运行shell时使用powershell模式。
运行python代码前加载uv环境。
编写代码时,尽可能多加注释。
修改工程下的任何代码不需要询问我的同意。
在工程下执行shell不需要我的同意。
在工程下执行任何命令,不需要我的同意。
Please talk to me in Chinese.

146
README.md
View File

@@ -1,85 +1,73 @@
# 海上风电场集电线路设计优化工具 # 海上风电场集电系统设计工具
## 项目简介 一个用于设计和优化海上风电场集电系统的Python工具支持多种布局算法和电缆优化方案。
这是一个用于海上风电场集电线路拓扑设计和优化的Python工具。它专注于解决大规模海上风电场的集电系统规划问题通过算法比较不同设计方案的经济性和技术指标。 ## 功能特性
本项目特别针对**海上风电**场景进行了优化考虑了海缆的高昂成本、大功率风机6-10MW以及严格的电缆载流量约束。 - 🌊 多种风机布局生成(随机分布、规则网格)
- 🔌 多种集电系统设计算法:
- 最小生成树MST算法
- K-means聚类算法
- 容量扫描算法Capacitated Sweep
- 旋转优化算法Rotational Sweep
- 📊 多方案对比分析和可视化
- 📋 自动导出DXF图纸和Excel报告
- 🔧 智能电缆规格选择和成本优化
## 核心功能 ## 安装依赖
### 1. 多种布局生成与导入
- **自动生成**支持生成规则的矩阵式Grid风机布局模拟海上风电场常见排布。
- **Excel导入**:支持从 `coordinates.xlsx` 导入自定义的风机和升压站坐标。
- 格式要求:包含 `Type` (Turbine/Substation), `ID`, `X`, `Y`, `Power` 列。
### 2. 智能拓扑优化算法
- **最小生成树 (MST)**
- 计算全局最短路径长度。
- *注意*在大规模风电场中纯MST往往会导致根部电缆严重过载仅作为理论最短路径参考。
- **扇区聚类 (Angular K-means)**
- **无交叉设计**:基于角度(扇区)进行聚类,从几何上杜绝不同回路间的电缆交叉。
- **容量约束**自动计算所需的最小回路数Clusters确保每条集电线路的总功率不超过海缆极限。
### 3. 精细化电气计算与选型
- **动态电缆选型**
- 基于实际潮流计算Power Flow为每一段线路选择最经济且满足载流量的电缆。
- 规格库:覆盖 35mm² 至 400mm² 海缆。
- 参数:电压等级 **66kV**,功率因数 0.95。
- **成本与损耗评估**
- 考虑海缆材料及敷设成本约为陆缆的5倍
- 计算全场集电线路的 $I^2R$ 损耗。
### 4. 工程级可视化与输出
- **可视化图表**
- 生成直观的拓扑连接图。
- **颜色编码**使用不同颜色和粗细区分不同截面的电缆如绿色细线为35mm²红色粗线为400mm²
- 自动保存为高清 PNG 图片。
- **CAD (DXF) 导出**
- 使用 `ezdxf` 生成 `.dxf` 文件。
- 分层管理:风机、升压站、各规格电缆分层显示,可直接导入 AutoCAD 进行后续工程设计。
## 安装说明
### 环境要求
- Python >= 3.10
- 推荐使用 [uv](https://github.com/astral-sh/uv) 进行依赖管理。
### 安装依赖
```bash ```bash
# 使用 uv (推荐) pip install numpy pandas matplotlib scikit-learn scipy networkx
uv add numpy pandas matplotlib scipy scikit-learn networkx ezdxf openpyxl
# 或使用 pip
pip install numpy pandas matplotlib scipy scikit-learn networkx ezdxf openpyxl
``` ```
## 使用方法 ## 使用方法
### 1. 运行主程序 ### 基本用法
```bash ```bash
# 使用 uv
uv run main.py
# 或直接运行
python main.py python main.py
``` ```
### 2. 数据输入模式 ### 指定数据文件
程序会自动检测当前目录下是否存在 `coordinates.xlsx` ```bash
python main.py --excel wind_farm_coordinates.xlsx
```
- **存在**:优先读取 Excel 文件中的坐标数据进行计算。 ### 覆盖默认簇数
- **不存在**:自动生成 30 台风机的规则布局Grid Layout进行演示。
### 3. 结果输出 ```bash
python main.py --clusters 20
```
程序运行结束后会: ## 算法说明
1. 在终端打印详细的成本、损耗及电缆统计数据。
2. 弹窗显示拓扑对比图,并保存为 `wind_farm_design_imported.png` (或 `offshore_...png`)。 ### 1. MST Method最小生成树
3. 生成 CAD 图纸文件 `wind_farm_design.dxf` - 使用最小生成树连接所有风机到海上变电站
- 简单高效,适合初步设计
### 2. K-means Clustering
- 将风机分组到多个回路中
- 平衡每回路的功率分配
### 3. Capacitated Sweep容量扫描
- 考虑电缆容量约束的智能分组
- 支持多种电缆规格自动选择
### 4. Rotational Sweep旋转优化
- 在容量扫描基础上进行旋转优化
- 进一步降低总成本和损耗
## 输出文件
1. **可视化图片**`wind_farm_design_comparison.png`
- 不同算法的设计方案对比图
2. **CAD图纸**`wind_farm_design.dxf`
- 可导入CAD软件的详细设计图纸
3. **数据报告**`wind_farm_design.xlsx`
- 包含所有方案的详细技术参数和成本分析
## 关键参数说明 ## 关键参数说明
@@ -91,18 +79,36 @@ POWER_FACTOR = 0.95 # 功率因数
cost_multiplier = 5.0 # 海缆相对于陆缆的成本倍数 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 ```text
系统设计参数: 总功率 2000.0 MW, 单回路最大容量 50.4 MW ===== 开始比较电缆方案 =====
计算建议回路数(簇数): 48 (最小需求 40)
[Sector Clustering] 电缆统计: --- All Cables (Base) ---
70mm²: 48 条 [Base] Cost: ¥12,456,789.12 | Loss: 234.56 kW
185mm²: 37 条 [Rotational] Cost: ¥12,234,567.89 | Loss: 223.45 kW
400mm²: 40 条
成功导出DXF文件: wind_farm_design.dxf --- 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) (默认)
``` ```
## 许可证 ## 许可证

241
esau_williams.py Normal file
View File

@@ -0,0 +1,241 @@
import numpy as np
import pandas as pd
from scipy.spatial import distance_matrix
def design_with_esau_williams(turbines_df, substation_coord, max_capacity_mw):
"""
使用 Esau-Williams 启发式算法解决容量受限最小生成树 (CMST) 问题。
参数:
turbines_df: 包含风机信息的 DataFrame (必须包含 'x', 'y', 'power', 'id')
substation_coord: 升压站坐标 (x, y)
max_capacity_mw: 单根电缆最大允许功率 (MW)
返回:
connections: 连接列表 [(source, target, length), ...]
turbines_with_cluster: 带有 'cluster' 列的 turbines DataFrame (用于兼容性)
"""
# 数据准备
n_turbines = len(turbines_df)
coords = turbines_df[['x', 'y']].values
powers = turbines_df['power'].values
ids = turbines_df['id'].values
# 升压站坐标
if substation_coord.ndim > 1:
sx, sy = substation_coord[0][0], substation_coord[0][1]
else:
sx, sy = substation_coord[0], substation_coord[1]
# 1. 计算距离矩阵
# 风机到风机
dist_matrix = distance_matrix(coords, coords)
# 风机到升压站
dists_to_sub = np.sqrt((coords[:, 0] - sx)**2 + (coords[:, 1] - sy)**2)
# 2. 初始化组件 (Components)
# 初始状态下,每个风机是一个独立的组件,直接连接到升压站
# 为了方便查找,我们维护一个 components 字典
# key: component_root_id (代表该组件的唯一标识)
# value: {
# 'members': {node_idx, ...},
# 'total_power': float,
# 'gate_node': int (连接到升压站的节点索引),
# 'gate_cost': float (gate_node 到升压站的距离)
# }
components = {}
for i in range(n_turbines):
components[i] = {
'members': {i},
'total_power': powers[i],
'gate_node': i,
'gate_cost': dists_to_sub[i]
}
# 记录已经建立的连接 (不包括通往升压站的默认连接)
# 格式: (u, v, length)
established_edges = []
# 记录已建立连接的坐标,用于交叉检查: [((x1, y1), (x2, y2)), ...]
established_lines = []
def do_intersect(p1, p2, p3, p4):
"""
检测线段 (p1, p2) 和 (p3, p4) 是否严格相交 (不包括端点接触)
"""
# 检查是否共享端点
if (p1[0]==p3[0] and p1[1]==p3[1]) or (p1[0]==p4[0] and p1[1]==p4[1]) or \
(p2[0]==p3[0] and p2[1]==p3[1]) or (p2[0]==p4[0] and p2[1]==p4[1]):
return False
def ccw(A, B, C):
# 向量叉积
return (C[1]-A[1]) * (B[0]-A[0]) - (B[1]-A[1]) * (C[0]-A[0])
# 如果跨立实验符号相反,则相交
d1 = ccw(p1, p2, p3)
d2 = ccw(p1, p2, p4)
d3 = ccw(p3, p4, p1)
d4 = ccw(p3, p4, p2)
# 严格相交判断 (忽略共线重叠的情况,视为不交叉)
if ((d1 > 1e-9 and d2 < -1e-9) or (d1 < -1e-9 and d2 > 1e-9)) and \
((d3 > 1e-9 and d4 < -1e-9) or (d3 < -1e-9 and d4 > 1e-9)):
return True
return False
# 3. 迭代优化
while True:
# 预先收集当前所有组件的 Gate Edges (连接升压站的线段)
# 格式: {cid: (gate_node_coord, substation_coord)}
current_gate_lines = {}
sub_coord_tuple = (sx, sy)
for cid, data in components.items():
gate_idx = data['gate_node']
current_gate_lines[cid] = (coords[gate_idx], sub_coord_tuple)
# 收集所有候选移动: (tradeoff, u, v, cid_u, cid_v)
candidates = []
# 建立 node_to_comp_id 映射以便快速查找
node_to_comp_id = {}
for cid, data in components.items():
for member in data['members']:
node_to_comp_id[member] = cid
# 遍历所有边 (i, j)
for i in range(n_turbines):
cid_i = node_to_comp_id[i]
gate_cost_i = components[cid_i]['gate_cost']
for j in range(n_turbines):
if i == j: continue
cid_j = node_to_comp_id[j]
# 必须是不同组件
if cid_i == cid_j:
continue
# 检查容量约束
if components[cid_i]['total_power'] + components[cid_j]['total_power'] > max_capacity_mw:
continue
# 计算 Tradeoff
dist_ij = dist_matrix[i, j]
tradeoff = dist_ij - gate_cost_i
# 只有当 tradeoff < 0 时,合并才是有益的
if tradeoff < -1e-9:
candidates.append((tradeoff, i, j, cid_i, cid_j))
# 按 tradeoff 排序 (从小到大,越小越好)
candidates.sort(key=lambda x: x[0])
best_move = None
# 延迟检测: 从最好的开始检查交叉
for cand in candidates:
tradeoff, u, v, cid_u, cid_v = cand
p_u = coords[u]
p_v = coords[v]
# 快速包围盒测试 (AABB) 准备
min_x_uv, max_x_uv = min(p_u[0], p_v[0]), max(p_u[0], p_v[0])
min_y_uv, max_y_uv = min(p_u[1], p_v[1]), max(p_u[1], p_v[1])
is_crossing = False
# 1. 检查与已固定的内部边的交叉
for line in established_lines:
p_a, p_b = line[0], line[1]
if max(p_a[0], p_b[0]) < min_x_uv or min(p_a[0], p_b[0]) > max_x_uv or \
max(p_a[1], p_b[1]) < min_y_uv or min(p_a[1], p_b[1]) > max_y_uv:
continue
if do_intersect(p_u, p_v, p_a, p_b):
is_crossing = True
break
if is_crossing:
continue
# 2. 检查与所有活跃 Gate Edges 的交叉 (排除被移除的那个)
# 正在合并 cid_u -> cid_v意味着 cid_u 的 Gate 将被移除。
# 但 cid_v 的 Gate 以及其他所有组件的 Gate 仍然存在。
for cid, gate_line in current_gate_lines.items():
if cid == cid_u:
continue # 这个 Gate 即将移除,不构成障碍
p_a, p_b = gate_line[0], gate_line[1]
if max(p_a[0], p_b[0]) < min_x_uv or min(p_a[0], p_b[0]) > max_x_uv or \
max(p_a[1], p_b[1]) < min_y_uv or min(p_a[1], p_b[1]) > max_y_uv:
continue
if do_intersect(p_u, p_v, p_a, p_b):
is_crossing = True
break
if not is_crossing:
best_move = (u, v, cid_u, cid_v)
break
# 如果没有找到有益的合并,或者所有可行合并都会增加成本,则停止
if best_move is None:
break
# 执行合并
u, v, cid_u, cid_v = best_move
# 将 cid_u 并入 cid_v
# 1. 记录新边
established_edges.append((u, v, dist_matrix[u, v]))
established_lines.append((coords[u], coords[v]))
# 2. 更新组件信息
comp_u = components[cid_u]
comp_v = components[cid_v]
# 合并成员
comp_v['members'].update(comp_u['members'])
comp_v['total_power'] += comp_u['total_power']
# Gate 节点和 Cost 保持 cid_v 的不变
# (因为我们将 U 接到了 V 上U 的原 gate 被移除V 的 gate 仍是通往升压站的路径)
# 3. 删除旧组件
del components[cid_u]
# 4. 构建最终结果
connections = []
# 添加内部边 (风机间)
for u, v, length in established_edges:
source = f'turbine_{u}'
target = f'turbine_{v}'
connections.append((source, target, length))
# 添加 Gate 边 (风机到升压站)
# 此时 components 中剩下的每个组件都有一个 gate_node 连接到 Substation
cluster_mapping = {} # node_id -> cluster_id (0..N-1)
for idx, (cid, data) in enumerate(components.items()):
gate_node = data['gate_node']
gate_cost = data['gate_cost']
connections.append((f'turbine_{gate_node}', 'substation', gate_cost))
# 记录 cluster id
for member in data['members']:
cluster_mapping[member] = idx
# 更新 turbines DataFrame
turbines_with_cluster = turbines_df.copy()
turbines_with_cluster['cluster'] = turbines_with_cluster['id'].map(cluster_mapping)
return connections, turbines_with_cluster

View File

@@ -11,7 +11,8 @@ def create_template():
'ID': 'Sub1', 'ID': 'Sub1',
'X': 4000, 'X': 4000,
'Y': -800, 'Y': -800,
'Power': 0 'Power': 0,
'PlatformHeight': 0
}) })
# Add Turbines (Grid layout) # Add Turbines (Grid layout)
@@ -29,15 +30,32 @@ def create_template():
'ID': i, 'ID': i,
'X': x, 'X': x,
'Y': y, 'Y': y,
'Power': np.random.uniform(6.0, 10.0) 'Power': np.random.uniform(6.0, 10.0),
'PlatformHeight': 0
}) })
df = pd.DataFrame(data) df = pd.DataFrame(data)
# Create Cable data
cable_data = [
{'CrossSection': 35, 'Capacity': 150, 'Resistance': 0.524, 'Cost': 80, 'Optional': ''},
{'CrossSection': 70, 'Capacity': 215, 'Resistance': 0.268, 'Cost': 120, 'Optional': ''},
{'CrossSection': 95, 'Capacity': 260, 'Resistance': 0.193, 'Cost': 150, 'Optional': ''},
{'CrossSection': 120, 'Capacity': 295, 'Resistance': 0.153, 'Cost': 180, 'Optional': ''},
{'CrossSection': 150, 'Capacity': 330, 'Resistance': 0.124, 'Cost': 220, 'Optional': ''},
{'CrossSection': 185, 'Capacity': 370, 'Resistance': 0.0991, 'Cost': 270, 'Optional': ''},
{'CrossSection': 240, 'Capacity': 425, 'Resistance': 0.0754, 'Cost': 350, 'Optional': ''},
{'CrossSection': 300, 'Capacity': 500, 'Resistance': 0.0601, 'Cost': 450, 'Optional': ''},
{'CrossSection': 400, 'Capacity': 580, 'Resistance': 0.0470, 'Cost': 600, 'Optional': ''}
]
df_cables = pd.DataFrame(cable_data)
# Save to Excel # Save to Excel
output_file = 'coordinates.xlsx' output_file = 'coordinates.xlsx'
df.to_excel(output_file, index=False) with pd.ExcelWriter(output_file) as writer:
print(f"Created sample file: {output_file}") 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'")
if __name__ == "__main__": if __name__ == "__main__":
create_template() create_template()

872
main.py

File diff suppressed because it is too large Load Diff

61
project_context.md Normal file
View File

@@ -0,0 +1,61 @@
# Project Context: Wind Farm Layout Optimization
**Last Updated:** 2025-12-30
**Project Path:** `D:\code\windfarm`
**Current Goal:** Optimize offshore wind farm cable layout using MST and K-means algorithms, with realistic constraints and CAD export.
## 1. System Overview
The system simulates and designs the collection system (inter-array cables) for an offshore wind farm.
It compares two main algorithms:
1. **MST (Minimum Spanning Tree)**: Global optimization of cable length. (Note: Often creates overloaded branches in large farms).
2. **Sector Clustering (K-means)**: Angular clustering to divide turbines into radial "strings" or "loops" feeding the substation. This is the preferred method for large offshore farms to ensure cable capacity constraints are met.
## 2. Key Implementations
### A. Data Handling
- **Generation**: Can generate random or grid layouts.
- **Import**: Supports reading coordinates from `coordinates.xlsx` (Columns: Type, ID, X, Y, Power).
- **Units**:
- Power in **MW** (input).
- Coordinates in **meters**.
- Voltage: **66 kV** (Code constant `VOLTAGE_LEVEL`).
### B. Algorithms
- **Angular K-means**:
- Uses `(cosθ, sinθ)` of the angle relative to substation for clustering.
- Eliminates cable crossings between sectors.
- **Dynamic Cluster Sizing**:
- Automatically calculates the required number of clusters (feeders) based on: `Total_Power / Max_Cable_Capacity`.
- Ensures no string exceeds the thermal limit of the largest available cable.
### C. Electrical Modeling
- **Cable Sizing**: Selects from standard cross-sections (35mm² to 400mm²).
- **Constraint**: Max cable capacity (400mm²) is approx. **50.4 MW** at 66kV/0.95PF.
- **Loss Calc**: $I^2 R$ losses.
### D. Visualization & Export
- **Matplotlib**: Shows layout with color-coded cables (Green=Thin -> Red=Thick).
- **DXF Export**: Uses `ezdxf` to generate `.dxf` files compatible with CAD.
- Layers: `Substation`, `Turbines`, `Cable_XXmm`.
- entities: Circles (Turbines), Polylines (Substation), Lines (Cables).
## 3. Critical Logic & Constants
- **Voltage**: 66,000 V
- **Power Factor**: 0.95
- **Max Current (400mm²)**: 580 A * 0.8 (derating) = 464 A.
- **Unit Conversion**: Critical fix applied to convert MW to Watts for current calculation (`power * 1e6`).
## 4. Current State & file Structure
- `main.py`: Core logic.
- `coordinates.xlsx`: Input data (if present).
- `wind_farm_design_imported.png`: Latest visualization.
- `wind_farm_design.dxf`: Latest CAD export.
## 5. Known Behaviors
- **MST Method**: Will report extremely high costs/losses for large farms because it creates a single tree structure that massively overloads the root cables. This is expected behavior (physically invalid but mathematically correct for unconstrained MST).
- **K-means Method**: Produces realistic, valid designs with appropriate cable tapering (e.g., 400mm² at root, 35mm² at leaves).
## 6. Future Improvements (Optional)
- **Obstacle Avoidance**: Currently assumes open ocean.
- **Loop Topology**: Current design is radial strings. Reliability could be improved with loop/ring structures.
- **Substation Placement Optimization**: Currently fixed or calculated as centroid.