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:
376
t_alns_rrd_reproduction/OPTIMIZATION_PLAN.md
Normal file
376
t_alns_rrd_reproduction/OPTIMIZATION_PLAN.md
Normal 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-3500,TA-Greedy 改善从 70% → 15-25%
|
||||
|
||||
---
|
||||
|
||||
### 修改 2:CES 缩放校准 【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 更接近论文报告的质量标准
|
||||
Reference in New Issue
Block a user