feat: 新增Web GUI界面,支持交互式设计对比
This commit is contained in:
94
main.py
94
main.py
@@ -287,20 +287,17 @@ def design_with_capacitated_sweep(turbines, substation, cable_specs=None):
|
||||
"""
|
||||
# 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) # 重置索引方便切片
|
||||
work_df = work_df.sort_values('angle').reset_index(drop=True)
|
||||
|
||||
angles = work_df['angle'].values
|
||||
n = len(angles)
|
||||
@@ -392,7 +389,6 @@ def design_with_rotational_sweep(turbines, substation, cable_specs=None):
|
||||
"""
|
||||
# 1. 获取电缆最大容量
|
||||
max_mw = get_max_cable_capacity_mw(cable_specs)
|
||||
# print(f"DEBUG: 扇区扫描算法启动 - 单回路容量限制: {max_mw:.2f} MW")
|
||||
|
||||
substation_coord = substation[0]
|
||||
|
||||
@@ -410,6 +406,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 +460,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:
|
||||
@@ -582,8 +578,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
|
||||
@@ -865,7 +861,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]
|
||||
@@ -1004,11 +1000,13 @@ 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
|
||||
if excel_path:
|
||||
@@ -1018,7 +1016,7 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
|
||||
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)
|
||||
@@ -1029,7 +1027,12 @@ def compare_design_methods(excel_path=None, n_clusters_override=None):
|
||||
# 准备三种电缆方案
|
||||
# 原始 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 +1052,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")
|
||||
|
||||
# 准备画布 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===== 开始比较电缆方案 =====")
|
||||
|
||||
@@ -1184,15 +1196,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 +1222,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 +1258,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 +1290,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)
|
||||
|
||||
Reference in New Issue
Block a user