Add MIP module for collector layout optimization
This commit is contained in:
13
gui.py
13
gui.py
@@ -91,6 +91,7 @@ def index():
|
||||
"current_file_container": None, # 替换 label 为 container
|
||||
"info_container": None, # 新增信息展示容器
|
||||
"ga_switch": None, # 遗传算法开关
|
||||
"mip_switch": None, # MIP开关
|
||||
}
|
||||
|
||||
def update_info_panel():
|
||||
@@ -677,8 +678,9 @@ def index():
|
||||
refs["log_box"].clear()
|
||||
log_queue = queue.Queue()
|
||||
|
||||
# 获取遗传算法开关状态
|
||||
# 获取开关状态
|
||||
use_ga = refs["ga_switch"].value if refs["ga_switch"] else False
|
||||
use_mip = refs["mip_switch"].value if refs["mip_switch"] else False
|
||||
|
||||
class QueueLogger(io.StringIO):
|
||||
def write(self, message):
|
||||
@@ -728,6 +730,7 @@ def index():
|
||||
interactive=False,
|
||||
plot_results=False,
|
||||
use_ga=use_ga,
|
||||
use_mip=use_mip,
|
||||
)
|
||||
|
||||
# 在后台线程运行计算任务
|
||||
@@ -920,8 +923,6 @@ def index():
|
||||
# with refs["current_file_container"]:
|
||||
# ui.label("未选择文件").classes("text-xs text-gray-500 italic ml-1")
|
||||
|
||||
|
||||
|
||||
# 3. 运行按钮
|
||||
refs["run_btn"] = (
|
||||
ui.button(
|
||||
@@ -937,6 +938,12 @@ def index():
|
||||
"color=orange"
|
||||
)
|
||||
|
||||
# 5. MIP开关
|
||||
with ui.column().classes("flex-1 gap-0 justify-center items-center"):
|
||||
refs["mip_switch"] = ui.switch("启用MIP", value=False).props(
|
||||
"color=blue"
|
||||
)
|
||||
|
||||
with ui.column().classes("w-full gap-4"):
|
||||
# 新增:信息展示卡片
|
||||
with (
|
||||
|
||||
49
main.py
49
main.py
@@ -15,6 +15,11 @@ from sklearn.cluster import KMeans
|
||||
from esau_williams import design_with_esau_williams
|
||||
from ga import design_with_ga
|
||||
|
||||
try:
|
||||
from mip import design_with_mip
|
||||
except ImportError:
|
||||
design_with_mip = None
|
||||
|
||||
# 设置matplotlib支持中文显示
|
||||
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"]
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
@@ -1399,6 +1404,7 @@ def compare_design_methods(
|
||||
interactive=True,
|
||||
plot_results=True,
|
||||
use_ga=False,
|
||||
use_mip=False,
|
||||
):
|
||||
"""
|
||||
比较MST和三种电缆方案下的K-means设计方法
|
||||
@@ -1709,6 +1715,49 @@ def compare_design_methods(
|
||||
f" [GA] Cost: ¥{eval_ga['total_cost']:,.2f} | Loss: {eval_ga['total_loss']:.2f} kW | Circuits: {n_circuits_ga}"
|
||||
)
|
||||
|
||||
if use_mip and design_with_mip:
|
||||
# --- Run 5: Mixed Integer Programming ---
|
||||
mip_name = f"{name} (MIP)"
|
||||
conns_mip, turbines_mip = design_with_mip(
|
||||
turbines.copy(),
|
||||
substation,
|
||||
current_specs,
|
||||
voltage,
|
||||
power_factor,
|
||||
system_params,
|
||||
evaluate_func=evaluate_design,
|
||||
total_invest_func=total_investment,
|
||||
get_max_capacity_func=get_max_cable_capacity_mw,
|
||||
)
|
||||
eval_mip = evaluate_design(
|
||||
turbines,
|
||||
conns_mip,
|
||||
substation,
|
||||
cable_specs=current_specs,
|
||||
is_offshore=is_offshore,
|
||||
method_name=mip_name,
|
||||
voltage=voltage,
|
||||
power_factor=power_factor,
|
||||
)
|
||||
n_circuits_mip = sum(
|
||||
1
|
||||
for d in eval_mip["details"]
|
||||
if d["source"] == "substation" or d["target"] == "substation"
|
||||
)
|
||||
comparison_results.append(
|
||||
{
|
||||
"name": mip_name,
|
||||
"cost": eval_mip["total_cost"],
|
||||
"loss": eval_mip["total_loss"],
|
||||
"eval": eval_mip,
|
||||
"turbines": turbines_mip,
|
||||
"specs": current_specs,
|
||||
}
|
||||
)
|
||||
print(
|
||||
f" [MIP] Cost: ¥{eval_mip['total_cost']:,.2f} | Loss: {eval_mip['total_loss']:.2f} kW | Circuits: {n_circuits_mip}"
|
||||
)
|
||||
|
||||
# 记录最佳
|
||||
if eval_rot["total_cost"] < best_cost:
|
||||
best_cost = eval_rot["total_cost"]
|
||||
|
||||
142
mip.py
Normal file
142
mip.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.spatial import distance_matrix
|
||||
from scipy.sparse.csgraph import minimum_spanning_tree
|
||||
from collections import defaultdict
|
||||
import pulp
|
||||
|
||||
|
||||
def design_with_mip(
|
||||
turbines,
|
||||
substation,
|
||||
cable_specs=None,
|
||||
voltage=66000,
|
||||
power_factor=0.95,
|
||||
system_params=None,
|
||||
max_clusters=None,
|
||||
time_limit=300, # seconds
|
||||
evaluate_func=None,
|
||||
total_invest_func=None,
|
||||
get_max_capacity_func=None,
|
||||
):
|
||||
"""
|
||||
使用混合整数规划(MIP)优化集电线路布局
|
||||
:param turbines: 风机DataFrame
|
||||
:param substation: 升压站坐标
|
||||
:param cable_specs: 电缆规格
|
||||
:param system_params: 系统参数(用于NPV计算)
|
||||
:param max_clusters: 最大簇数,默认基于功率计算
|
||||
:param time_limit: 求解时间限制(秒)
|
||||
:param evaluate_func: 评估函数
|
||||
:param total_invest_func: 总投资计算函数
|
||||
:param get_max_capacity_func: 获取最大容量函数
|
||||
:return: 连接列表和带有簇信息的turbines
|
||||
"""
|
||||
if get_max_capacity_func:
|
||||
max_mw = get_max_capacity_func(cable_specs, voltage, power_factor)
|
||||
else:
|
||||
max_mw = 100.0 # 默认值
|
||||
|
||||
total_power = turbines["power"].sum()
|
||||
if max_clusters is None:
|
||||
max_clusters = int(np.ceil(total_power / max_mw))
|
||||
n_turbines = len(turbines)
|
||||
|
||||
# 预计算距离矩阵
|
||||
all_coords = np.vstack([substation, turbines[["x", "y"]].values])
|
||||
dist_matrix_full = distance_matrix(all_coords, all_coords)
|
||||
|
||||
# MIP 模型
|
||||
prob = pulp.LpProblem("WindFarmCollectorMIP", pulp.LpMinimize)
|
||||
|
||||
# 决策变量:风机分配到簇 (binary)
|
||||
x = pulp.LpVariable.dicts(
|
||||
"assign", (range(n_turbines), range(max_clusters)), cat="Binary"
|
||||
)
|
||||
|
||||
# 簇使用变量 (binary)
|
||||
y = pulp.LpVariable.dicts("use_cluster", range(max_clusters), cat="Binary")
|
||||
|
||||
# 目标函数:最小化总成本 (简化版:距离成本)
|
||||
# 这里使用简化成本:簇内距离 + 到升压站距离
|
||||
prob += pulp.lpSum(
|
||||
[
|
||||
dist_matrix_full[i + 1, j + 1] * x[i][k] * x[j][k]
|
||||
for i in range(n_turbines)
|
||||
for j in range(n_turbines)
|
||||
for k in range(max_clusters)
|
||||
if i < j
|
||||
]
|
||||
) + pulp.lpSum(
|
||||
[
|
||||
dist_matrix_full[0, i + 1] * y[k] # 假设每个簇连接到升压站
|
||||
for i in range(n_turbines)
|
||||
for k in range(max_clusters)
|
||||
]
|
||||
)
|
||||
|
||||
# 约束:每个风机分配到一个簇
|
||||
for i in range(n_turbines):
|
||||
prob += pulp.lpSum([x[i][k] for k in range(max_clusters)]) == 1
|
||||
|
||||
# 簇功率约束
|
||||
for k in range(max_clusters):
|
||||
prob += (
|
||||
pulp.lpSum([turbines.iloc[i]["power"] * x[i][k] for i in range(n_turbines)])
|
||||
<= max_mw * y[k]
|
||||
)
|
||||
|
||||
# 如果簇未使用,则无分配
|
||||
for k in range(max_clusters):
|
||||
for i in range(n_turbines):
|
||||
prob += x[i][k] <= y[k]
|
||||
|
||||
# 求解
|
||||
solver = pulp.PULP_CBC_CMD(timeLimit=time_limit)
|
||||
status = prob.solve(solver)
|
||||
|
||||
if pulp.LpStatus[prob.status] != "Optimal":
|
||||
print(f"MIP not optimal: {pulp.LpStatus[prob.status]}")
|
||||
# 返回默认方案,如 MST
|
||||
from main import design_with_mst
|
||||
|
||||
return design_with_mst(turbines, substation)
|
||||
|
||||
# 提取结果
|
||||
cluster_assign = [-1] * n_turbines
|
||||
for i in range(n_turbines):
|
||||
for k in range(max_clusters):
|
||||
if pulp.value(x[i][k]) > 0.5:
|
||||
cluster_assign[i] = k
|
||||
break
|
||||
|
||||
# 构建连接
|
||||
clusters = defaultdict(list)
|
||||
for i, c in enumerate(cluster_assign):
|
||||
clusters[c].append(i)
|
||||
|
||||
connections = []
|
||||
for c, members in clusters.items():
|
||||
if len(members) == 0:
|
||||
continue
|
||||
coords = turbines.iloc[members][["x", "y"]].values
|
||||
if len(members) > 1:
|
||||
dm = distance_matrix(coords, coords)
|
||||
mst = minimum_spanning_tree(dm).toarray()
|
||||
for i in range(len(members)):
|
||||
for j in range(len(members)):
|
||||
if mst[i, j] > 0:
|
||||
connections.append(
|
||||
(
|
||||
f"turbine_{members[i]}",
|
||||
f"turbine_{members[j]}",
|
||||
mst[i, j],
|
||||
)
|
||||
)
|
||||
# 连接到升压站
|
||||
dists = [dist_matrix_full[0, m + 1] for m in members]
|
||||
closest = members[np.argmin(dists)]
|
||||
connections.append((f"turbine_{closest}", "substation", min(dists)))
|
||||
|
||||
turbines["cluster"] = cluster_assign
|
||||
return connections, turbines
|
||||
Reference in New Issue
Block a user