你遇到的问题非常典型:**模型运行了 120 秒超时,未找到任何可行解(Solution count 0),并且在打印结果时抛出 `AttributeError: Unable to retrieve attribute 'X'`。**
下面我们一步步分析原因、给出解决方案,并提供修复后的代码逻辑。
---
## 🔍 一、错误根本原因
### ❌ 抛出异常的原因:
```python
print("启用无人机数 :", int(sum(y_u[u].X > 0.5 for u in U)))
```
> AttributeError: Unable to retrieve attribute 'X'. Did you mean: 'Xn'?
这说明:**求解器没有返回有效的解向量 `X`**,即 `m.status` 不是 `GRB.OPTIMAL` 或 `GRB.TIME_LIMIT` 下的可行解。
虽然你写了:
```python
if m.status in (GRB.OPTIMAL, GRB.TIME_LIMIT):
```
但实际上 **`TIME_LIMIT` 并不保证有可行解!**
✅ 正确判断是否有解应使用:
```python
if m.SolCount > 0:
```
否则 `.X` 属性不可访问!
---
### ❌ 模型为何找不到可行解?
从日志看:
- Root relaxation 目标值从 402 上升到 10118+ → 表明整数间隙巨大。
- 探索了 8500 个节点,但 **Solution count 0** → 没有任何整数可行解被找到。
- 最终 `Best bound ~10129`, `Incumbent = -` → 无当前解。
👉 **结论:模型过于复杂或存在建模冲突,导致无法构造一条合法路径。**
---
## ✅ 二、主要问题定位与修复建议
### ✅ 1. 【关键】访问一次约束写错了(双重求和结构错误)
你写的:
```python
for i in V:
m.addConstr(quicksum(x[j, i, u, r] for j in N if j != i for u in U for r in R) == 1)
m.addConstr(quicksum(x[i, j, u, r] for j in N if j != i for u in U for r in R) == 1)
```
⚠️ 错误点:嵌套顺序混乱,Python 中 `for j in N if j != i for u in U ...` 是合法的生成器表达式,但容易出错。
更严重的是:**这是将所有 `(u,r)` 上的所有入弧/出弧加在一起等于 1?**
❌ 错了!应该是:每个任务点 `i` 被**某一个无人机在某一个行程中恰好访问一次**。
所以正确形式是:
```python
# 每个任务点 i ∈ V 恰好被访问一次(总共一次)
m.addConstr(
quicksum(x[j,i,u,r] for j in N for u in U for r in R if j != i) == 1
)
m.addConstr(
quicksum(x[i,j,u,r] for j in N for u in U for r in R if j != i) == 1
)
```
✔️ 这是对的(你的写法其实语法上等价)。可以保留。
但注意不要重复添加两个完全一样的约束。
---
### ✅ 2. 【关键】流守恒约束范围错误
```python
for u in U:
for r in R:
for j in V:
m.addConstr(
quicksum(x[i, j, u, r] for i in N if i != j) ==
quicksum(x[j, k, u, r] for k in N if k != j)
)
```
⚠️ 问题:这个约束对每个 `j ∈ V` 成立没问题,但对基站 `0` 呢?
- 如果某个 `u,r` 没有激活(`y_ur=0`),那么所有 `x[*,*,u,r]=0`,此时流守恒成立;
- 但如果部分激活,比如只走了 `0->1->0`,那么对于 `j=1`:
- 入度:`x[0,1,u,r]=1`
- 出度:`x[1,0,u,r]=1` → 守恒 ✔️
✅ 所以这条约束本身没错,但它要求 **每个中间点流入=流出**,适用于 TSP 子环。
不过你没禁止子环(subtour elimination)→ 可能出现 `1->2->1` 的非法循环!
🔧 **这才是最可能造成无解的原因之一:隐含的子环让电量/时间无法满足!**
---
### ✅ 3. 【致命】缺少子环消除约束(Subtour Elimination)
你现在只有流守恒 + 访问一次,但没有防止子环!
例如:无人机走 `1 -> 2 -> 1`,看似满足流守恒,但它不是从基站出发再返回的有效路径。
✅ 必须添加子环消除机制。
#### 方法一:DFJ 类型(精确但指数级数量)—— 不推荐用于 10 点以上
#### 方法二:MTZ 类型(用时间和电量自然隐含)—— 你已经在做!
👉 你用了时间变量 `t_iur` 和大M约束来实现顺序传递,理论上可以防子环。
但前提是:**时间约束必须正确生效!**
而你的时间约束之前写成了:
```python
t_iur[j,u,r] >= t_iur[i,u,r] + s_i[i] + t_ij[(i,j)] + - BIGM * (1 - x[i,j,u,r])
```
❗ 这里 `+ - BIGM` 是语法错误,实际变成减去大数,导致右边极小 → 约束总是满足 → 时间无序 → 子环出现!
这就是为什么即使有时间变量也无法阻止非法回路!
---
### ✅ 4. 【修复】修正时间约束(核心!)
```python
# 改为:
m.addConstr(
t_iur[j, u, r] >= t_iur[i, u, r] + s_i[i] + t_ij[(i,j)] - BIGM_TIME * (1 - x[i,j,u,r]),
name=f"time_order_{i}_{j}_u{u}_r{r}"
)
```
✅ 注意:
- 不要写 `+ - M*()`,直接 `- M*(1-x)`
- 使用合理 `BIGM_TIME ≈ 2000`
---
### ✅ 5. 【修复】确保电量消耗也正确建模
你也写了:
```python
e_iur[j,u,r] <= e_iur[i,u,r] - E_ij[(i,j)] - SE_i[i] + BIGM_ENERGY * (1 - x[i,j,u,r])
```
✅ 方向正确:仅当 `x=1` 时才强制耗电。
但要保证初始满电:
```python
m.addConstr(e_iur[0,u,r] == E_max * y_ur[u,r], name=f"full_charge_u{u}_r{r}")
```
✔️ 正确。
---
### ✅ 6. 【修复】修改输出部分,避免访问 `.X` 失败
```python
if m.SolCount > 0:
print("\n===== 最终结果 =====")
print("启用无人机数 :", sum(1 for u in U if y_u[u].X > 0.5))
print("总行程数 :", sum(y_ur[u, r].X > 0.5 for u in U for r in R))
total_dist = sum(d_ij[(i, j)] * x[i, j, u, r].X
for i in N for j in N if i != j
for u in U for r in R)
print("总路径距离(米):", round(total_dist, 3))
for u in U:
if y_u[u].X < 0.5:
continue
print(f"\n=== UAV {u} ===")
for r in R:
if y_ur[u, r].X < 0.5:
continue
print(f" --- 行程 r = {r} ---")
print(" 使用的弧:")
for i in N:
for j in N:
if i != j and x[i, j, u, r].X > 0.5:
print(f" {i} -> {j} "
f"(t={t_iur[i,u,r].X:.1f}->{t_iur[j,u,r].X:.1f}, "
f"e={e_iur[i,u,r].X:.1f}->{e_iur[j,u,r].X:.1f})")
else:
print("❌ 未找到可行解。")
if m.status == GRB.INFEASIBLE:
print("模型不可行,尝试计算 IIS...")
m.computeIIS()
m.write("infeasible.ilp")
print("已保存 IIS 到 infeasible.ilp")
else:
print(f"状态: {m.status}, 目标界: {m.ObjBound if hasattr(m, 'ObjBound') else 'N/A'}")
```
---
### ✅ 7. 【调试建议】先测试简化版模型
#### 小规模测试配置:
```python
V = list(range(1, 4)) # 仅 3 个任务点
N = [0] + V
U = [1] # 仅 1 架无人机
R = [1] # 仅 1 次行程
```
关闭其他 `U>1`, `R>1` 的复杂性,验证是否能找到 `0->1->2->3->0` 这样的简单路径。
---
## ✅ 三、完整修复建议汇总
| 问题 | 是否修复 |
|------|----------|
| `BIGM` 太大 (`30000`) | ✅ 改为 `2000` |
| 时间约束写成 `+ - M*(...)` | ✅ 改为 `- M*(1-x)` |
| 输出时未检查 `SolCount` 导致 `.X` 报错 | ✅ 添加 `if m.SolCount > 0` |
| 缺少子环控制 | ⚠️ 靠 MTZ 时间约束(现已修复) |
| 目标函数权重失衡 | ✅ 建议改为 `alpha=1000, beta=10` |
---
### ✅ 四、推荐修改后关键代码段
```python
# === 参数调整 ===
BIGM_TIME = 2000
alpha = 1000
beta = 10
# === 时间顺序约束(修正)===
for i in N:
for j in N:
if i == j:
continue
for u in U:
for r in R:
m.addConstr(
t_iur[j, u, r] >= t_iur[i, u, r] + s_i[i] + t_ij[(i,j)] - BIGM_TIME * (1 - x[i,j,u,r]),
name=f"time_order_{i}_{j}_u{u}_r{r}"
)
# === 输出安全判断 ===
if m.SolCount > 0:
# 安全访问 .X
print("启用无人机数 :", sum(1 for u in U if y_u[u].X > 0.5))
# ...其余输出
else:
print("❌ 无可行解")
if m.status == GRB.INFEASIBLE:
m.computeIIS()
m.write("conflict.ilp")
```
---
## ✅ 五、下一步行动建议
1. ✅ 应用上述修改(特别是时间约束和输出保护)
2. ✅ 在小实例上测试能否得到可行解
3. ✅ 使用 `m.write("model.lp")` 查看生成的 LP 文件,确认约束命名清晰
4. ✅ 若仍无解,运行 IIS 分析冲突集合
---