feat: 优化风电场集电系统设计,支持电缆规格配置

主要更改:
• 新增电缆规格配置支持
  - Excel文件新增Cables工作表,支持自定义电缆参数(截面、载流量、电阻、成本)
  - 实现容量约束扫描算法(Capacitated Sweep),替代原有K-means方法
  - 动态计算所需回路数量,确保每条回路的电缆载流量符合约束

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

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

优化后的设计更符合实际工程需求,支持电缆容量约束,输出更准确的成本和损耗评估。
This commit is contained in:
dmy
2026-01-01 11:39:14 +08:00
parent 2f70b2fc72
commit 4db9d138b8
4 changed files with 361 additions and 79 deletions

9
GEMINI.md Normal file
View File

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

View File

@@ -34,10 +34,26 @@ def create_template():
df = pd.DataFrame(data) df = pd.DataFrame(data)
# Create Cable data
cable_data = [
{'CrossSection': 35, 'Capacity': 150, 'Resistance': 0.524, 'Cost': 80},
{'CrossSection': 70, 'Capacity': 215, 'Resistance': 0.268, 'Cost': 120},
{'CrossSection': 95, 'Capacity': 260, 'Resistance': 0.193, 'Cost': 150},
{'CrossSection': 120, 'Capacity': 295, 'Resistance': 0.153, 'Cost': 180},
{'CrossSection': 150, 'Capacity': 330, 'Resistance': 0.124, 'Cost': 220},
{'CrossSection': 185, 'Capacity': 370, 'Resistance': 0.0991, 'Cost': 270},
{'CrossSection': 240, 'Capacity': 425, 'Resistance': 0.0754, 'Cost': 350},
{'CrossSection': 300, 'Capacity': 500, 'Resistance': 0.0601, 'Cost': 450},
{'CrossSection': 400, 'Capacity': 580, 'Resistance': 0.0470, 'Cost': 600}
]
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()

350
main.py
View File

@@ -7,6 +7,7 @@ from sklearn.cluster import KMeans
from collections import defaultdict from collections import defaultdict
import networkx as nx import networkx as nx
import math import math
import argparse
# 设置matplotlib支持中文显示 # 设置matplotlib支持中文显示
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial'] plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'Arial']
@@ -69,11 +70,19 @@ def generate_wind_farm_data(n_turbines=30, seed=42, layout='random', spacing=800
# 1.5 从Excel加载数据 # 1.5 从Excel加载数据
def load_data_from_excel(file_path): def load_data_from_excel(file_path):
""" """
从Excel文件读取风机和升压站坐标 从Excel文件读取风机和升压站坐标,以及可选的电缆规格
Excel格式要求包含列: Type (Turbine/Substation), ID, X, Y, Power Excel格式要求:
- Sheet 'Coordinates' (或第一个Sheet): Type (Turbine/Substation), ID, X, Y, Power
- Sheet 'Cables' (可选): CrossSection, Capacity, Resistance, Cost
""" """
try: try:
df = pd.read_excel(file_path) xl = pd.ExcelFile(file_path)
# 读取坐标数据
if 'Coordinates' in xl.sheet_names:
df = pd.read_excel(xl, 'Coordinates')
else:
df = pd.read_excel(xl, 0)
# 标准化列名(忽略大小写) # 标准化列名(忽略大小写)
df.columns = [c.capitalize() for c in df.columns] df.columns = [c.capitalize() for c in df.columns]
@@ -104,8 +113,35 @@ def load_data_from_excel(file_path):
'cumulative_power': np.zeros(len(turbines_df)) 'cumulative_power': np.zeros(len(turbines_df))
}) })
# 读取电缆数据 (如果存在)
cable_specs = None
if 'Cables' in xl.sheet_names:
cables_df = pd.read_excel(xl, 'Cables')
# 标准化列名
cables_df.columns = [c.replace(' ', '').capitalize() for c in cables_df.columns] # Handle 'Cross Section' vs 'CrossSection'
# 尝试匹配列
# 目标格式: (截面mm², 载流量A, 电阻Ω/km, 基准价格元/m)
specs = []
for _, row in cables_df.iterrows():
# 容错处理列名
section = row.get('Crosssection', row.get('Section', 0))
capacity = row.get('Capacity', row.get('Current', 0))
resistance = row.get('Resistance', 0)
cost = row.get('Cost', row.get('Price', 0))
if section > 0 and capacity > 0:
specs.append((section, capacity, resistance, cost))
if specs:
specs.sort(key=lambda x: x[1]) # 按载流量排序
cable_specs = specs
print(f"成功加载: {len(turbines)} 台风机, {len(substation)} 座升压站") print(f"成功加载: {len(turbines)} 台风机, {len(substation)} 座升压站")
return turbines, substation if cable_specs:
print(f"成功加载: {len(cable_specs)} 种电缆规格")
return turbines, substation, cable_specs
except Exception as e: except Exception as e:
print(f"读取Excel文件失败: {str(e)}") print(f"读取Excel文件失败: {str(e)}")
@@ -220,66 +256,167 @@ def design_with_kmeans(turbines, substation, n_clusters=3):
return cluster_connections + substation_connections, turbines return cluster_connections + substation_connections, turbines
# 3.5 带容量约束的扇区扫描算法 (Capacitated Angular Sweep)
def design_with_capacitated_sweep(turbines, substation, cable_specs=None):
"""
使用带容量约束的扇区扫描算法设计集电线路
原理:
1. 计算所有风机相对于升压站的角度。
2. 找到角度间隔最大的位置作为起始“切割线”,以避免切断密集的风机群。
3. 沿圆周方向扫描,贪婪地将风机加入当前回路,直到达到电缆容量上限。
4. 满载后开启新回路。
"""
# 1. 获取电缆最大容量
max_mw = get_max_cable_capacity_mw(cable_specs)
print(f"DEBUG: 扇区扫描算法启动 - 单回路容量限制: {max_mw:.2f} MW")
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) # 重置索引方便切片
angles = work_df['angle'].values
n = len(angles)
if n > 1:
# 计算相邻角度差
diffs = np.diff(angles)
# 计算首尾角度差 (跨越 ±pi 处)
wrap_diff = (2 * np.pi) - (angles[-1] - angles[0])
diffs = np.append(diffs, wrap_diff)
# 找到最大间隙的索引 (该索引对应元素的 *后面* 是间隙)
max_gap_idx = np.argmax(diffs)
# 如果最大间隙不是在队尾,则需要旋转数组,使最大间隙成为新的起点(即队尾)
# 实际上我们希望扫描的起点是最大间隙的“右侧”
# 例如: [0, 10, 100, 110]. Gaps: 10, 90, 10, 250. Max gap is wrap (250). Start at 0 is fine.
# 例如: [0, 10, 200, 210]. Gaps: 10, 190, 10, 150. Max gap is 190 (idx 1).
# 我们应该从 idx 2 (200) 开始扫描。
start_idx = (max_gap_idx + 1) % n
# 重新排序风机列表
if start_idx != 0:
work_df = pd.concat([work_df.iloc[start_idx:], work_df.iloc[:start_idx]]).reset_index(drop=True)
# 4. 贪婪分组 (Capacity Constrained Clustering)
work_df['cluster'] = -1
cluster_id = 0
current_power = 0
current_indices = []
for i, row in work_df.iterrows():
p = row['power']
# 检查是否超载
# 注意:如果是空簇,必须加入至少一个(即使单个风机超载也得加,但在风电中单机不会超载)
if len(current_indices) > 0 and (current_power + p > max_mw):
# 当前簇已满,结束当前簇,开启新簇
work_df.loc[current_indices, 'cluster'] = cluster_id
cluster_id += 1
current_power = 0
current_indices = []
current_indices.append(i)
current_power += p
# 处理最后一个簇
if current_indices:
work_df.loc[current_indices, 'cluster'] = cluster_id
cluster_id += 1 # 计数用
print(f"DEBUG: 生成了 {cluster_id} 个回路 (簇)")
# 将 cluster 标记映射回原始 turbines DataFrame (通过 original_id 或 索引匹配)
# 这里我们简单地重建 turbines因为 work_df 包含了所有信息且顺序变了
# 为了保持外部一致性,我们把 cluster 列 map 回去
# 建立 id -> cluster 的映射
id_to_cluster = dict(zip(work_df['id'], work_df['cluster']))
turbines['cluster'] = turbines['id'].map(id_to_cluster)
# 5. 对每个簇内部进行MST连接 (复用现有逻辑)
cluster_connections = []
substation_connections = []
n_clusters = cluster_id
for cid in range(n_clusters):
cluster_turbines = turbines[turbines['cluster'] == cid]
if len(cluster_turbines) == 0:
continue
# --- 簇内 MST ---
cluster_indices = cluster_turbines.index.tolist()
coords = cluster_turbines[['x', 'y']].values
if len(cluster_indices) > 1:
dist_matrix = distance_matrix(coords, coords)
mst = minimum_spanning_tree(dist_matrix).toarray()
for i in range(len(cluster_indices)):
for j in range(len(cluster_indices)):
if mst[i, j] > 0:
source = f'turbine_{cluster_indices[i]}'
target = f'turbine_{cluster_indices[j]}'
cluster_connections.append((source, target, mst[i, j]))
# --- 连接到升压站 ---
# 找到簇内离升压站最近的风机
dists = np.sqrt((cluster_turbines['x'] - substation_coord[0])**2 +
(cluster_turbines['y'] - substation_coord[1])**2)
closest_id = dists.idxmin() # 返回的是原始DataFrame的index
# 添加升压站连接
min_dist = dists.min()
substation_connections.append((f'turbine_{closest_id}', 'substation', min_dist))
return cluster_connections + substation_connections, turbines
# 常量定义 # 常量定义
VOLTAGE_LEVEL = 66000 # 66kV VOLTAGE_LEVEL = 66000 # 66kV
POWER_FACTOR = 0.95 POWER_FACTOR = 0.95
# 4. 电缆选型函数(简化版) def get_max_cable_capacity_mw(cable_specs=None):
def select_cable(power, length, is_offshore=False):
""" """
基于功率和长度选择合适的电缆截面 计算给定电缆规格中能够承载的最大功率 (单位: MW)。
:param is_offshore: 是否为海上环境(成本更高)
基于提供的电缆规格列表,选取最大载流量,结合系统电压和功率因数计算理论最大传输功率。
参数:
cable_specs (list, optional): 电缆规格列表。每个元素应包含 (截面积, 额定电流, 单价, 损耗系数)。
返回:
float: 最大功率承载能力 (MW)。
异常:
Exception: 当未提供 cable_specs 时抛出,提示截面不满足。
""" """
# 成本乘数:海缆材料+敷设成本通常是陆缆的4-6倍 if cable_specs:
cost_multiplier = 5.0 if is_offshore else 1.0 # 从所有电缆规格中找到最大的额定电流容量
max_current_capacity = max(spec[1] for spec in cable_specs)
else:
# 如果没有传入电缆规格,通常意味着已尝试过所有规格但仍不满足需求
raise Exception("没有提供电缆参数")
# 电缆规格库: (截面mm², 载流量A, 电阻Ω/km, 基准价格元/m) # 计算最大功率P = √3 * U * I * cosφ
cable_specs = [ # 这里假设降额系数为 1 (不降额)
(35, 150, 0.524, 80), max_current = max_current_capacity * 1
(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)
]
# 估算电流
# power是MW, 换算成W需要 * 1e6
current = (power * 1e6) / (np.sqrt(3) * VOLTAGE_LEVEL * POWER_FACTOR)
# 选择满足载流量的最小电缆
selected_spec = None
for spec in cable_specs:
if current <= spec[1] * 0.8: # 80%负载率
selected_spec = spec
break
if selected_spec is None:
selected_spec = cable_specs[-1]
resistance = selected_spec[2] * length / 1000 # 电阻(Ω)
cost = selected_spec[3] * length * cost_multiplier # 电缆成本(含敷设)
return {
'cross_section': selected_spec[0],
'current_capacity': selected_spec[1],
'resistance': resistance,
'cost': cost,
'current': current
}
def get_max_cable_capacity_mw():
"""计算最大电缆(400mm2)能承载的最大功率(MW)"""
# 400mm2载流量580A
max_current = 580 * 0.8 # 80%降额
max_power_w = np.sqrt(3) * VOLTAGE_LEVEL * max_current * POWER_FACTOR max_power_w = np.sqrt(3) * VOLTAGE_LEVEL * max_current * POWER_FACTOR
return max_power_w / 1e6 # MW
# 将单位从 W 转换为 MW
return max_power_w / 1e6
# 5. 计算集电线路方案成本 # 5. 计算集电线路方案成本
def evaluate_design(turbines, connections, substation, is_offshore=False): def evaluate_design(turbines, connections, substation, cable_specs=None, is_offshore=False, method_name="Unknown Method"):
"""评估设计方案的总成本和损耗""" """评估设计方案的总成本和损耗"""
total_cost = 0 total_cost = 0
total_loss = 0 total_loss = 0
@@ -320,9 +457,10 @@ def evaluate_design(turbines, connections, substation, is_offshore=False):
except nx.NetworkXNoPath: except nx.NetworkXNoPath:
pass pass
# DEBUG: 打印最大功率流 # DEBUG: 打印最大功率流 (不含升压站本身)
max_power = max(power_flow.values()) if power_flow else 0 node_powers = [v for k, v in power_flow.items() if k != 'substation']
print(f"DEBUG: 最大线路功率 = {max_power:.2f} MW") max_power = max(node_powers) if node_powers else 0
print(f"DEBUG [{method_name}]: 最大线路功率 = {max_power:.2f} MW")
# 计算成本和损耗 # 计算成本和损耗
detailed_connections = [] detailed_connections = []
@@ -345,7 +483,49 @@ def evaluate_design(turbines, connections, substation, is_offshore=False):
power = 0 power = 0
# 电缆选型 # 电缆选型
cable = select_cable(power, length, is_offshore=is_offshore) # 成本乘数:海缆材料+敷设成本通常是陆缆的4-6倍
cost_multiplier = 5.0 if is_offshore else 1.0
# 默认电缆规格库 (如果未提供)
if cable_specs is None:
cable_specs_to_use = [
(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)
]
else:
cable_specs_to_use = cable_specs
# 估算电流
current = (power * 1e6) / (np.sqrt(3) * VOLTAGE_LEVEL * POWER_FACTOR)
# 选择满足载流量的最小电缆
selected_spec = None
for spec in cable_specs_to_use:
if current <= spec[1] * 1: # 100%负载率
selected_spec = spec
break
if selected_spec is None:
selected_spec = cable_specs_to_use[-1]
print(f"WARNING [{method_name}]: Current {current:.2f} A (Power: {power:.2f} MW) exceeds max cable capacity {selected_spec[1]} A!")
resistance = selected_spec[2] * length / 1000 # 电阻(Ω)
cost = selected_spec[3] * length * cost_multiplier # 电缆成本(含敷设)
cable = {
'cross_section': selected_spec[0],
'current_capacity': selected_spec[1],
'resistance': resistance,
'cost': cost,
'current': current
}
# 记录详细信息 # 记录详细信息
detailed_connections.append({ detailed_connections.append({
@@ -562,19 +742,21 @@ def visualize_design(turbines, substation, connections, title, ax=None, show_cos
return ax return ax
# 7. 主函数:比较两种设计方法 # 7. 主函数:比较两种设计方法
def compare_design_methods(excel_path=None): def compare_design_methods(excel_path=None, n_clusters_override=None):
""" """
比较MST和K-means两种设计方法 (海上风电场场景) 比较MST和K-means两种设计方法 (海上风电场场景)
:param excel_path: Excel文件路径如果提供则从文件读取数据 :param excel_path: Excel文件路径如果提供则从文件读取数据
:param n_clusters_override: 可选,手动指定簇的数量
""" """
cable_specs = None
if excel_path: if excel_path:
print(f"正在从 {excel_path} 读取坐标数据...") print(f"正在从 {excel_path} 读取坐标数据...")
try: try:
turbines, substation = load_data_from_excel(excel_path) turbines, substation, cable_specs = load_data_from_excel(excel_path)
scenario_title = "Offshore Wind Farm (Imported Data)" scenario_title = "Offshore Wind Farm (Imported Data)"
except Exception: except Exception:
print("回退到自动生成数据模式...") print("回退到自动生成数据模式...")
return compare_design_methods(excel_path=None) return compare_design_methods(excel_path=None, n_clusters_override=n_clusters_override)
else: else:
print("正在生成海上风电场数据 (规则阵列布局)...") print("正在生成海上风电场数据 (规则阵列布局)...")
# 使用规则布局间距800m # 使用规则布局间距800m
@@ -586,29 +768,35 @@ def compare_design_methods(excel_path=None):
# 方法1最小生成树 # 方法1最小生成树
# 注意MST方法在不考虑容量约束时可能会导致根部线路严重过载 # 注意MST方法在不考虑容量约束时可能会导致根部线路严重过载
mst_connections = design_with_mst(turbines, substation) mst_connections = design_with_mst(turbines, substation)
mst_evaluation = evaluate_design(turbines, mst_connections, substation, is_offshore=is_offshore) mst_evaluation = evaluate_design(turbines, mst_connections, substation, cable_specs=cable_specs, is_offshore=is_offshore, method_name="MST Method")
# 方法2K-means聚类 (容量受限聚类) # 方法2K-means聚类 (容量受限聚类)
# 计算总功率和所需的最小回路数 # 计算总功率和所需的最小回路数
total_power = turbines['power'].sum() total_power = turbines['power'].sum()
max_cable_mw = get_max_cable_capacity_mw() max_cable_mw = get_max_cable_capacity_mw(cable_specs=cable_specs)
min_clusters_needed = int(np.ceil(total_power / max_cable_mw))
# 允许指定簇的数量,如果设置为 None 则自动计算
if n_clusters_override is not None:
n_clusters = n_clusters_override
min_clusters_needed = int(np.ceil(total_power / max_cable_mw))
print(f"使用手动指定的回路数(簇数): {n_clusters} (理论最小需求 {min_clusters_needed})")
else:
min_clusters_needed = int(np.ceil(total_power / max_cable_mw))
# 增加一定的安全裕度 (1.2倍) 并确保至少有一定数量的簇 # 增加一定的安全裕度 (1.2倍) 并确保至少有一定数量的簇
n_clusters = max(int(min_clusters_needed * 1.2), 4) n_clusters = max(int(min_clusters_needed * 1.2), 4)
if len(turbines) < n_clusters: # 避免簇数多于风机数 if len(turbines) < n_clusters: # 避免簇数多于风机数
n_clusters = len(turbines) n_clusters = len(turbines)
print(f"系统设计参数: 总功率 {total_power:.1f} MW, 单回路最大容量 {max_cable_mw:.1f} MW") print(f"系统设计参数: 总功率 {total_power:.1f} MW, 单回路最大容量 {max_cable_mw:.1f} MW")
print(f"计算建议回路数(簇数): {n_clusters} (最小需求 {min_clusters_needed})")
kmeans_connections, clustered_turbines = design_with_kmeans(turbines.copy(), substation, n_clusters=n_clusters) # 替换为带容量约束的扫描算法
kmeans_evaluation = evaluate_design(turbines, kmeans_connections, substation, is_offshore=is_offshore) kmeans_connections, clustered_turbines = design_with_capacitated_sweep(turbines.copy(), substation, cable_specs=cable_specs)
kmeans_evaluation = evaluate_design(turbines, kmeans_connections, substation, cable_specs=cable_specs, is_offshore=is_offshore, method_name="Capacitated Sweep")
# 创建结果比较 # 创建结果比较
results = { results = {
'MST Method': mst_evaluation, 'MST Method': mst_evaluation,
'K-means Method': kmeans_evaluation 'Capacitated Sweep': kmeans_evaluation
} }
# 可视化 # 可视化
@@ -619,9 +807,11 @@ def compare_design_methods(excel_path=None):
f"MST Design - {scenario_title}\nTotal Cost: ¥{mst_evaluation['total_cost']/10000:.2f}\nTotal Loss: {mst_evaluation['total_loss']:.2f} kW", f"MST Design - {scenario_title}\nTotal Cost: ¥{mst_evaluation['total_cost']/10000:.2f}\nTotal Loss: {mst_evaluation['total_loss']:.2f} kW",
ax=axes[0]) ax=axes[0])
# 可视化K-means方法 # 可视化K-means方法 (现在是 Capacitated Sweep)
# 获取实际生成的簇数
n_actual_clusters = clustered_turbines['cluster'].nunique()
visualize_design(clustered_turbines, substation, kmeans_evaluation['details'], visualize_design(clustered_turbines, substation, kmeans_evaluation['details'],
f"Sector Clustering (Angular) ({n_clusters} clusters) - {scenario_title}\nTotal Cost: ¥{kmeans_evaluation['total_cost']/10000:.2f}\nTotal Loss: {kmeans_evaluation['total_loss']:.2f} kW", f"Capacitated Sweep ({n_actual_clusters} circuits) - {scenario_title}\nTotal Cost: ¥{kmeans_evaluation['total_cost']/10000:.2f}\nTotal Loss: {kmeans_evaluation['total_loss']:.2f} kW",
ax=axes[1]) ax=axes[1])
plt.tight_layout() plt.tight_layout()
@@ -648,10 +838,16 @@ def compare_design_methods(excel_path=None):
# 8. 执行比较 # 8. 执行比较
if __name__ == "__main__": if __name__ == "__main__":
import os # 解析命令行参数
# 检查是否存在 coordinates.xlsx存在则优先使用 parser = argparse.ArgumentParser(description='Wind Farm Collector System Design')
default_excel = 'coordinates.xlsx' parser.add_argument('--clusters', type=int, help='Specify the number of clusters (circuits) manually', default=None)
if os.path.exists(default_excel): args = parser.parse_args()
results = compare_design_methods(excel_path=default_excel)
else: # 2. 读取 Excel 坐标数据 (如果存在)
results = compare_design_methods() excel_path = 'coordinates2.xlsx'
# 尝试从命令行参数获取文件路径 (可选扩展)
# if len(sys.argv) > 1: excel_path = sys.argv[1]
# 3. 运行比较
# 如果本地没有excel文件将自动回退到生成数据模式
compare_design_methods(excel_path, n_clusters_override=args.clusters)

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.