Fix MIP solver variable duplication and function structure

This commit is contained in:
dmy
2026-01-08 15:30:36 +08:00
parent 04a5e19451
commit b3a4513f94
3 changed files with 270 additions and 54 deletions

128
mip.py
View File

@@ -129,57 +129,31 @@ def design_with_mip(
prob = pulp.LpProblem("WindFarmCollectorMIP", pulp.LpMinimize)
# Create all decision variables upfront to avoid duplicates
assign_vars = {}
for i in range(n_turbines):
for k in range(max_clusters):
assign_vars[(i, k)] = pulp.LpVariable(f"assign_{i}_{k}", cat="Binary")
cluster_vars = {}
for k in range(max_clusters):
cluster_vars[k] = pulp.LpVariable(f"cluster_{k}", cat="Binary")
# Helper functions to access variables
def assign_var(i, k):
return pulp.LpVariable(f"assign_{i}_{k}", cat="Binary")
return assign_vars[(i, k)]
def cluster_var(k):
return pulp.LpVariable(f"cluster_{k}", cat="Binary")
return cluster_vars[k]
def cluster_connection_var(k):
return pulp.LpVariable(f"cluster_connection_{k}", cat="Binary")
turbine_coords = turbines[["x", "y"]].values
turbine_powers = turbines["power"].values
# Calculate cost per meter for cluster-to-substation connections
# Higher power clusters need thicker cables = higher cost
cost_per_meter_per_mw = 1000 # Base cost per MW per meter (can be adjusted)
# Objective function: minimize total investment including:
# 1. Intra-cluster connections (estimated using pairwise distances)
# 2. Cluster-to-substation connections (based on distance and power)
objective_terms = []
# Intra-cluster connection costs (estimated)
for k in range(max_clusters):
for i in range(n_turbines):
for j in range(i + 1, n_turbines):
# Only count if both turbines are in the same cluster
# This is a simplified approximation of MST cost
both_in_cluster = assign_var(i, k) + assign_var(j, k) - 1
distance_ij = np.linalg.norm(turbine_coords[i] - turbine_coords[j])
objective_terms.append(distance_ij * both_in_cluster * 0.5)
# Cluster-to-substation connection costs
for k in range(max_clusters):
cluster_power = pulp.lpSum(
[turbine_powers[i] * assign_var(i, k) for i in range(n_turbines)]
)
cluster_to_substation_distance = dist_matrix_full[
0, :
] # Distance from each turbine to substation
# Use minimum distance from any turbine in cluster to substation
for i in range(n_turbines):
objective_terms.append(
cluster_to_substation_distance[i + 1]
* assign_var(i, k)
* cost_per_meter_per_mw
* turbine_powers[i]
* 0.001
)
prob += pulp.lpSum(objective_terms)
# Simplified objective function: minimize total distance
prob += pulp.lpSum(
[
dist_matrix_full[0, i + 1] * assign_var(i, k)
for i in range(n_turbines)
for k in range(max_clusters)
]
)
for i in range(n_turbines):
prob += pulp.lpSum([assign_var(i, k) for k in range(max_clusters)]) == 1
@@ -188,7 +162,7 @@ def design_with_mip(
cluster_power = pulp.lpSum(
[turbines.iloc[i]["power"] * assign_var(i, k) for i in range(n_turbines)]
)
prob += cluster_power <= max_mw * 1.0 * cluster_var(k)
prob += cluster_power <= max_mw * 1.2 * cluster_var(k)
for k in range(max_clusters):
for i in range(n_turbines):
@@ -198,19 +172,65 @@ def design_with_mip(
f"MIP Model: {len(prob.variables())} variables, {len(prob.constraints)} constraints"
)
print("MIP: Starting to solve...")
solver = pulp.PULP_CBC_CMD(timeLimit=time_limit, msg=0, warmStart=False, path=None)
# Debug: Print model structure
print("MIP model structure check:")
print(f" Variables: {len(prob.variables())}")
print(f" Constraints: {len(prob.constraints)}")
print(f" Time limit: {time_limit}s")
print(f" Turbines: {n_turbines}, Clusters: {max_clusters}")
# Test solver availability
try:
import subprocess
test_solver = subprocess.run(
[
r"D:\code\windfarm\.venv\Lib\site-packages\pulp\apis\..\solverdir\cbc\win\i64\cbc.exe",
"-version",
],
capture_output=True,
text=True,
timeout=5,
)
print(
f"CBC solver test: {test_solver.stdout[:100] if test_solver.stdout else 'No output'}"
)
except Exception as solver_test_error:
print(f"CBC solver test failed: {solver_test_error}")
print("MIP: Starting to solve...")
try:
# Try to use CBC solver with different configurations
solver = pulp.PULP_CBC_CMD(
timeLimit=time_limit,
msg=False,
warmStart=False,
)
print(f"Using CBC solver with time limit: {time_limit}s")
status = prob.solve(solver)
print(
f"MIP: Solver status={pulp.LpStatus[prob.status]}, Objective value={pulp.value(prob.objective):.4f}"
)
except Exception as e:
print(f"MIP: Solver execution failed: {e}, falling back to MST")
from main import design_with_mst
print(f"MIP: CBC solver execution failed: {e}")
# Try alternative solver configurations
try:
print("MIP: Trying alternative solver configuration...")
solver = pulp.PULP_CBC_CMD(
msg=True, # Enable messages for debugging
threads=1, # Single thread
timeLimit=time_limit,
)
status = prob.solve(solver)
print(
f"MIP: Alternative solver status={pulp.LpStatus[prob.status]}, Objective value={pulp.value(prob.objective):.4f}"
)
except Exception as e2:
print(f"MIP: All solver attempts failed: {e2}, falling back to MST")
from main import design_with_mst
connections = design_with_mst(turbines, substation)
return connections, turbines
connections = design_with_mst(turbines, substation)
return connections, turbines
if pulp.LpStatus[prob.status] != "Optimal":
print(