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:
9
GEMINI.md
Normal file
9
GEMINI.md
Normal file
@@ -0,0 +1,9 @@
|
||||
运行shell时使用powershell模式。
|
||||
运行python代码前加载uv环境。
|
||||
编写代码时,尽可能多加注释。
|
||||
|
||||
修改工程下的任何代码不需要询问我的同意。
|
||||
在工程下执行shell,不需要我的同意。
|
||||
在工程下执行任何命令,不需要我的同意。
|
||||
|
||||
Please talk to me in Chinese.
|
||||
@@ -34,10 +34,26 @@ def create_template():
|
||||
|
||||
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
|
||||
output_file = 'coordinates.xlsx'
|
||||
df.to_excel(output_file, index=False)
|
||||
print(f"Created sample file: {output_file}")
|
||||
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'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_template()
|
||||
350
main.py
350
main.py
@@ -7,6 +7,7 @@ from sklearn.cluster import KMeans
|
||||
from collections import defaultdict
|
||||
import networkx as nx
|
||||
import math
|
||||
import argparse
|
||||
|
||||
# 设置matplotlib支持中文显示
|
||||
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加载数据
|
||||
def load_data_from_excel(file_path):
|
||||
"""
|
||||
从Excel文件读取风机和升压站坐标
|
||||
Excel格式要求包含列: Type (Turbine/Substation), ID, X, Y, Power
|
||||
从Excel文件读取风机和升压站坐标,以及可选的电缆规格
|
||||
Excel格式要求:
|
||||
- Sheet 'Coordinates' (或第一个Sheet): Type (Turbine/Substation), ID, X, Y, Power
|
||||
- Sheet 'Cables' (可选): CrossSection, Capacity, Resistance, Cost
|
||||
"""
|
||||
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]
|
||||
@@ -104,8 +113,35 @@ def load_data_from_excel(file_path):
|
||||
'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)} 座升压站")
|
||||
return turbines, substation
|
||||
if cable_specs:
|
||||
print(f"成功加载: {len(cable_specs)} 种电缆规格")
|
||||
|
||||
return turbines, substation, cable_specs
|
||||
|
||||
except Exception as 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
|
||||
|
||||
# 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
|
||||
POWER_FACTOR = 0.95
|
||||
|
||||
# 4. 电缆选型函数(简化版)
|
||||
def select_cable(power, length, is_offshore=False):
|
||||
def get_max_cable_capacity_mw(cable_specs=None):
|
||||
"""
|
||||
基于功率和长度选择合适的电缆截面
|
||||
:param is_offshore: 是否为海上环境(成本更高)
|
||||
计算给定电缆规格中能够承载的最大功率 (单位: MW)。
|
||||
|
||||
基于提供的电缆规格列表,选取最大载流量,结合系统电压和功率因数计算理论最大传输功率。
|
||||
|
||||
参数:
|
||||
cable_specs (list, optional): 电缆规格列表。每个元素应包含 (截面积, 额定电流, 单价, 损耗系数)。
|
||||
|
||||
返回:
|
||||
float: 最大功率承载能力 (MW)。
|
||||
|
||||
异常:
|
||||
Exception: 当未提供 cable_specs 时抛出,提示截面不满足。
|
||||
"""
|
||||
# 成本乘数:海缆材料+敷设成本通常是陆缆的4-6倍
|
||||
cost_multiplier = 5.0 if is_offshore else 1.0
|
||||
if cable_specs:
|
||||
# 从所有电缆规格中找到最大的额定电流容量
|
||||
max_current_capacity = max(spec[1] for spec in cable_specs)
|
||||
else:
|
||||
# 如果没有传入电缆规格,通常意味着已尝试过所有规格但仍不满足需求
|
||||
raise Exception("没有提供电缆参数")
|
||||
|
||||
# 电缆规格库: (截面mm², 载流量A, 电阻Ω/km, 基准价格元/m)
|
||||
cable_specs = [
|
||||
(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)
|
||||
]
|
||||
|
||||
# 估算电流
|
||||
# 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%降额
|
||||
# 计算最大功率:P = √3 * U * I * cosφ
|
||||
# 这里假设降额系数为 1 (不降额)
|
||||
max_current = max_current_capacity * 1
|
||||
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. 计算集电线路方案成本
|
||||
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_loss = 0
|
||||
@@ -320,9 +457,10 @@ def evaluate_design(turbines, connections, substation, is_offshore=False):
|
||||
except nx.NetworkXNoPath:
|
||||
pass
|
||||
|
||||
# DEBUG: 打印最大功率流
|
||||
max_power = max(power_flow.values()) if power_flow else 0
|
||||
print(f"DEBUG: 最大线路功率 = {max_power:.2f} MW")
|
||||
# DEBUG: 打印最大功率流 (不含升压站本身)
|
||||
node_powers = [v for k, v in power_flow.items() if k != 'substation']
|
||||
max_power = max(node_powers) if node_powers else 0
|
||||
print(f"DEBUG [{method_name}]: 最大线路功率 = {max_power:.2f} MW")
|
||||
|
||||
# 计算成本和损耗
|
||||
detailed_connections = []
|
||||
@@ -345,7 +483,49 @@ def evaluate_design(turbines, connections, substation, is_offshore=False):
|
||||
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({
|
||||
@@ -562,19 +742,21 @@ def visualize_design(turbines, substation, connections, title, ax=None, show_cos
|
||||
return ax
|
||||
|
||||
# 7. 主函数:比较两种设计方法
|
||||
def compare_design_methods(excel_path=None):
|
||||
def compare_design_methods(excel_path=None, n_clusters_override=None):
|
||||
"""
|
||||
比较MST和K-means两种设计方法 (海上风电场场景)
|
||||
:param excel_path: Excel文件路径,如果提供则从文件读取数据
|
||||
:param n_clusters_override: 可选,手动指定簇的数量
|
||||
"""
|
||||
cable_specs = None
|
||||
if excel_path:
|
||||
print(f"正在从 {excel_path} 读取坐标数据...")
|
||||
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)"
|
||||
except Exception:
|
||||
print("回退到自动生成数据模式...")
|
||||
return compare_design_methods(excel_path=None)
|
||||
return compare_design_methods(excel_path=None, n_clusters_override=n_clusters_override)
|
||||
else:
|
||||
print("正在生成海上风电场数据 (规则阵列布局)...")
|
||||
# 使用规则布局,间距800m
|
||||
@@ -586,29 +768,35 @@ def compare_design_methods(excel_path=None):
|
||||
# 方法1:最小生成树
|
||||
# 注意:MST方法在不考虑容量约束时,可能会导致根部线路严重过载
|
||||
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")
|
||||
|
||||
# 方法2:K-means聚类 (容量受限聚类)
|
||||
# 计算总功率和所需的最小回路数
|
||||
total_power = turbines['power'].sum()
|
||||
max_cable_mw = get_max_cable_capacity_mw()
|
||||
min_clusters_needed = int(np.ceil(total_power / max_cable_mw))
|
||||
max_cable_mw = get_max_cable_capacity_mw(cable_specs=cable_specs)
|
||||
|
||||
# 允许指定簇的数量,如果设置为 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倍) 并确保至少有一定数量的簇
|
||||
n_clusters = max(int(min_clusters_needed * 1.2), 4)
|
||||
if len(turbines) < n_clusters: # 避免簇数多于风机数
|
||||
n_clusters = len(turbines)
|
||||
|
||||
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 = {
|
||||
'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",
|
||||
ax=axes[0])
|
||||
|
||||
# 可视化K-means方法
|
||||
# 可视化K-means方法 (现在是 Capacitated Sweep)
|
||||
# 获取实际生成的簇数
|
||||
n_actual_clusters = clustered_turbines['cluster'].nunique()
|
||||
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])
|
||||
|
||||
plt.tight_layout()
|
||||
@@ -648,10 +838,16 @@ def compare_design_methods(excel_path=None):
|
||||
|
||||
# 8. 执行比较
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
# 检查是否存在 coordinates.xlsx,存在则优先使用
|
||||
default_excel = 'coordinates.xlsx'
|
||||
if os.path.exists(default_excel):
|
||||
results = compare_design_methods(excel_path=default_excel)
|
||||
else:
|
||||
results = compare_design_methods()
|
||||
# 解析命令行参数
|
||||
parser = argparse.ArgumentParser(description='Wind Farm Collector System Design')
|
||||
parser.add_argument('--clusters', type=int, help='Specify the number of clusters (circuits) manually', default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
# 2. 读取 Excel 坐标数据 (如果存在)
|
||||
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
61
project_context.md
Normal 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.
|
||||
Reference in New Issue
Block a user