# 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 更接近论文报告的质量标准