Initial commit: T-ALNS-RRD paper reproduction project

- Paper: Optimizing urban last mile delivery efficiency (Liu & Wang, 2025)
- 5 algorithms: Static-VRPTW, TA-Greedy, ALNS-Base, T-ALNS, T-ALNS-RRD
- v1 baseline + v2 calibrated experiments with full results
- Tabu memory ablation study with convergence analysis
- Comprehensive final report (FINAL_REPORT.md)
This commit is contained in:
huangfu
2026-06-02 21:11:00 +08:00
commit 8ff9715fb7
45 changed files with 9293 additions and 0 deletions

View File

@@ -0,0 +1,376 @@
# T-ALNS-RRD 复现优化计划
## 版本管理策略
### 目录组织
```
t_alns_rrd_reproduction/
├── configs/
│ ├── default.yaml # v1 原始配置(保留不动)
│ └── calibrated.yaml # v2 校准配置(新)
├── src/ # 核心代码(通过配置参数化,不复制)
│ └── ... # 只修改 bug不破坏 v1
├── results/
│ ├── v1_baseline/ # v1 运行结果(已生成 → 移入此处)
│ │ ├── tables/
│ │ ├── figures/
│ │ └── logs/
│ └── v2_calibrated/ # v2 运行结果(新生成)
│ ├── tables/
│ ├── figures/
│ └── logs/
└── CHANGELOG.md # 记录版本变更
```
### 运行方式:通过配置切换版本
```bash
# v1: 原始(已完成的实验)
python3 src/experiments/run_main_comparison.py --config default
# v2: 校准后
python3 src/experiments/run_main_comparison.py --config calibrated
```
输出自动路由到对应目录:
```python
output_dir = f"results/{config_name}/"
```
---
## 修改清单6 项,按实施顺序)
### 修改 1修复 Static 基线公平性 【P0 | ~10 行】
**问题**`static_vrptw.py:62` 构建路径时只用 interval 0 的旅行时间,评估时却用时间依赖时间 → Static 被不公平地"惩罚"
**修改文件**: `src/baselines/static_vrptw.py`
**改动内容**:
```python
# 原代码 (line ~60)
def _static_route_cost(self, nodes: list) -> float:
depart_time = self.ctx.op_start
for idx in range(1, len(nodes)):
i, j = nodes[idx - 1], nodes[idx]
tt = self.ctx.traffic.travel_time[i, j, 0] # ❌ 永远 interval 0
total += tt
# 新代码
def _static_route_cost(self, nodes: list) -> float:
"""使用所有时段的平均旅行时间,而非仅 interval 0"""
depart_time = self.ctx.op_start
for idx in range(1, len(nodes)):
i, j = nodes[idx - 1], nodes[idx]
# 对所有 12 个时段取平均 → 公平的"无交通感知"基线
avg_tt = np.mean([self.ctx.traffic.travel_time[i, j, h]
for h in range(self.ctx.n_intervals)
if not np.isinf(self.ctx.traffic.travel_time[i, j, h])])
tt = avg_tt
total += tt
```
**同时需要**Static 成本评估时不加 λ₁ 和 λ₂ 惩罚(只有纯旅行时间),让它和 TA-Greedy 在评估维度上可比。
**在 `cost.py` 的 `evaluate_solution` 中加一个参数**
```python
def evaluate_solution(self, solution, problem_ctx, include_penalties=True):
# Static: include_penalties=False → 只算旅行时间
# 其他: include_penalties=True → 完整 Eq.1
```
**预期效果**Static 成本从 ~7580 → ~2500-3500TA-Greedy 改善从 70% → 15-25%
---
### 修改 2CES 缩放校准 【P0 | ~5 行】
**问题**`data_generator.py:326``ρ = θ × γ`,θ=1.0,γ∈[0,1] → CES 只有 ~25。论文 CES ~1300-2850。
**修改文件**: `src/data_generator.py`
**改动内容**:
```python
# 原代码 (line ~326)
rho = self.congestion_scale * gamma # ρ ∈ [0, 1],太小
# 新代码
# ρ 应反映"拥堵带来的额外时间成本",量级应与 travel_time 可比
extra_time = base_time * (self.traffic_multipliers[h] - 1.0) # 拥堵造成的额外分钟
rho = self.congestion_scale * extra_time * gamma
# γ 作为缩放因子:[0, 1] × extra_time → ρ ∈ [0, 额外时间]
# θ 控制在 config 中设置
```
**修改文件**: `configs/calibrated.yaml`
```yaml
traffic:
congestion_scale_theta: 50.0 # v1 用 1.0 → v2 用 50.0
```
**预期效果**CES 从 ~25 → ~300-800 范围(取决于 θ 值,可通过 config 微调)
---
### 修改 3数据集难度提升 【P1 | ~15 行配置 + 生成参数】
**问题**47 客户/4 车/120kg/宽时间窗 → OTDR 100%,无优化压力
**修改文件**: `configs/calibrated.yaml`
**改动内容**:
```yaml
problem:
n_customers: 60 # 47 → 60 (+28%)
vehicle_capacity_kg: 100 # 120 → 100 (更紧)
# 其余不变
customers:
demand_min_kg: 4 # 3 → 4
demand_max_kg: 15 # 12 → 15 (更多变异性)
time_window_categories:
morning:
earliest: 540
latest: 720
afternoon:
earliest: 780
latest: 960
evening:
earliest: 1020
latest: 1200
# 每个客户窗口随机 30-90 分钟(而非 60-150
window_length_min: 30 # 新增
window_length_max: 90 # 新增
```
**修改文件**: `src/data_generator.py`(支持新配置参数)
```python
# 在 _extract_params 中添加
self.tw_window_min = c.get("window_length_min", 60)
self.tw_window_max = c.get("window_length_max", 150)
# 在 generate_customers 中使用
window_len = self.rng.uniform(self.tw_window_min, self.tw_window_max)
```
**预期效果**OTDR 从 100% → 75-90%,各算法间出现明显差异
---
### 修改 4增加迭代数与事件频率 【P1 | 配置改动】
**修改文件**: `configs/calibrated.yaml`
```yaml
alns:
max_iterations: 1000 # 500 → 1000 (对齐论文)
time_limit_sec: 600
stall_limit: 400 # 200 → 400
rrd:
rollout:
horizon_min_min: 30
horizon_max_min: 120
n_sim_min: 5 # 2 → 5
n_sim_max: 30 # 50 → 30
events:
urgency_threshold: 0.3 # 0.5 → 0.3 (更容易触发)
event_probability: 0.5 # 新增在顶层
event_check_interval: 5 # 10 → 5
```
**同时修改**: `src/rrd/event_generator.py` 让 E1 事件真正修改 travel_time 张量
```python
def generate_traffic_incident(self, current_time, solution):
arc = ...
# 临时将受影响的弧的旅行时间乘以 3
i, j = arc
saved_times = self.ctx.traffic.travel_time[i, j, :].copy()
self.ctx.traffic.travel_time[i, j, :] *= 3.0
event._undo = lambda: setattr(self.ctx.traffic, 'travel_time', ...) # rollback
return event
```
---
### 修改 5实验参数 → 30 seeds + 统计 【P2 | ~20 行】
**修改文件**: `src/experiments/run_main_comparison.py`
**改动内容**:
```python
# 1. 接受 --config 命令行参数
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--config", default="default", help="Config name")
args = parser.parse_args()
# 2. 加载对应 config
config_path = Path(f"configs/{args.config}.yaml")
with open(config_path) as f: cfg = yaml.safe_load(f)
# 3. 输出到版本目录
output_dir = Path(f"results/{args.config}/")
# 4. n_seeds 从 config 读取
n_seeds = cfg.get("experiments", {}).get("random_seeds", 30)
```
**新增**: 统计检验
```python
from scipy import stats
# 对每对算法做 paired t-test
for i in range(len(algorithms)):
for j in range(i+1, len(algorithms)):
t_stat, p_val = stats.ttest_rel(costs_i, costs_j)
print(f" {names[i]} vs {names[j]}: t={t_stat:.2f}, p={p_val:.4f}")
```
**修改文件**: `requirements.txt`
```
scipy>=1.10 # 新增
```
---
### 修改 6实验结果版本隔离 【架构 | ~10 行】
**修改文件**: `src/experiments/run_main_comparison.py`
**改动内容**:
```python
def run_experiment(config_name="default", ...):
output_dir = Path(f"results/{config_name}/")
output_dir.mkdir(parents=True, exist_ok=True)
(output_dir / "tables").mkdir(exist_ok=True)
(output_dir / "figures").mkdir(exist_ok=True)
(output_dir / "logs").mkdir(exist_ok=True)
# 保存一份 config 副本到结果目录
import shutil
shutil.copy(f"configs/{config_name}.yaml", output_dir / "config_used.yaml")
# ... 后续所有输出路径都基于 output_dir
```
---
## 完整文件变更清单
| # | 文件 | 操作 | 内容 |
|---|------|------|------|
| 1 | `configs/calibrated.yaml` | **新建** | v2 完整配置(复制 default + 修改 6 个参数组) |
| 2 | `src/baselines/static_vrptw.py` | 修改 10 行 | 使用平均旅行时间 + 移除评估中的 λ 惩罚 |
| 3 | `src/cost.py` | 修改 5 行 | evaluate_solution 加 `include_penalties` 参数 |
| 4 | `src/data_generator.py` | 修改 8 行 | 客户生成支持 window_length_min/maxρ 改用 extra_time |
| 5 | `src/rrd/event_generator.py` | 修改 15 行 | E1 事件真正修改 travel time 张量 |
| 6 | `src/experiments/run_main_comparison.py` | 修改 30 行 | 支持 --config版本输出目录统计检验 |
| 7 | `src/visualization/plot_results.py` | 修改 5 行 | 接受 output_dir 参数 |
| 8 | `requirements.txt` | 修改 1 行 | 添加 scipy |
| 9 | `CHANGELOG.md` | **新建** | 记录 v1/v2 差异 |
| 10 | `results/v1_baseline/` | 移动 | 将已有 v1 结果移入子目录 |
**不动的文件**v1 和 v2 共用):
```
src/problem.py (无 bug不碰)
src/alns/* (无 bug参数通过 config 控制)
src/tabu/* (无 bug参数通过 config 控制)
src/rrd/dispatch.py (无 bug)
src/rrd/rollout.py (无 bug)
src/rrd/candidate_actions.py (无 bug)
```
---
## 实施步骤6 步,约 1-2 小时)
### Step 0: 版本快照 + 目录准备
```bash
cd t_alns_rrd_reproduction
# 0.1 创建 CHANGELOG
echo "# Changelog\n\n## v1.0-baseline (default)\n- 47 customers, 4 vehicles, 120kg\n- θ=1.0, 200 iter, 5 seeds, complete graph\n- Known issues: inflated Static baseline, low CES, OTDR=100%\n" > CHANGELOG.md
# 0.2 隔离 v1 结果
mkdir -p results/v1_baseline
mv results/tables results/figures results/logs results/v1_baseline/ 2>/dev/null
mkdir -p results/v1_baseline/{tables,figures,logs}
```
### Step 1: 创建 calibrated.yaml
复制 `configs/default.yaml` → 修改以上所有参数
### Step 2: 修改 static_vrptw.py + cost.py
修复 Static 基线公平性(两个文件,共约 15 行改动)
### Step 3: 修改 data_generator.py
支持新客户窗口参数 + 修复 ρ 计算(一个文件,约 10 行改动)
### Step 4: 修改 event_generator.py
让 E1 事件真正影响 travel time一个文件约 15 行改动)
### Step 5: 修改 run_main_comparison.py + 依赖
支持 --config、版本目录、统计检验、scipy一个文件 + requirements.txt
### Step 6: 生成 v2 数据 + 跑实验
```bash
# 6.1 重新生成校准数据集
python3 -c "from src.data_generator import DataGenerator; DataGenerator(config_path='configs/calibrated.yaml', seed=42).generate_all()"
# 6.2 跑 v2 实验30 seeds × 1000 iter ~ 20-30 分钟)
python3 src/experiments/run_main_comparison.py --config calibrated
```
---
## 预期结果对比v1 vs v2
| 指标 | v1 当前 | v2 预期 | 变化原因 |
|------|---------|---------|---------|
| Static Cost | 7580 | 2500-3500 | 用平均时间,移除评估偏差 |
| TA-Greedy Cost | 2250 | 2000-2500 | 更难的数据集 |
| OTDR (all algos) | 100% | 75-95% | 更紧时间窗 + 更多客户 |
| CES (all algos) | 22-30 | 300-800 | θ=50 + ρ 改用 extra_time |
| Static→TA drop | 70% | 15-25% | 基线公平后差距缩小 |
| ALNS→T-ALNS drop | 0.2% | 2-5% | 更多迭代让 Tabu 生效 |
| T-ALNS→RRD drop | 0.1% | 1-3% | 更多事件 + 事件真正改 travel time |
| n_seeds | 5 | 30 | 对齐论文 |
| p-values | 无 | < 0.05 | 添加 t-test |
---
## 风险控制
| 风险 | 缓解措施 |
|------|---------|
| v2 改动破坏 v1 | 所有改动通过 config 参数化,不改 v1 逻辑路径 |
| 数据集变难导致无可行解 | 先保守收紧n=55, Q=110不行再松 |
| 跑 30 seeds 太慢 | 先用 10 seeds 快速验证,确认趋势正确后再跑全量 |
| CES 调到什么值合适 | 先设 θ=30 跑一轮看效果,再调 |
---
## 对比报告模板
完成后 `results/` 目录结构:
```
results/
├── v1_baseline/
│ ├── tables/main_comparison.csv
│ ├── figures/fig1-7.png
│ ├── logs/convergence.npz
│ └── config_used.yaml
├── v2_calibrated/
│ ├── tables/main_comparison.csv
│ ├── figures/fig1-7.png
│ ├── logs/convergence.npz
│ └── config_used.yaml
└── comparison_report.md # 自动生成的 v1 vs v2 对比
```
对比报告包含:
- 两张表并排v1 数值 vs v2 数值)
- 趋势一致性检查(每个算法的递进方向)
- 统计显著性v2 的 t-test 结果)
- 结论v2 更接近论文报告的质量标准