Files
windfarm/esau_williams.py
dmy d563905f28 添加完整的项目文档README.md
- 提供详细的功能特性说明和算法介绍
- 包含完整的安装和使用指南
- 添加电缆规格配置表格
- 更新输出示例以反映最新功能
- 完善项目结构说明和参数配置
2026-01-02 01:24:02 +08:00

242 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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