diff --git a/PROGRESS.md b/PROGRESS.md index 3b7b7f1..52d2e19 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -41,3 +41,21 @@ python src/experiments/run_main_comparison.py --config paper --seeds 30 --iterat ### Interpretation rule - Check whether Static > TA-Greedy > ALNS-Base > T-ALNS > T-ALNS-RRD in total cost, CES decreases, and OTDR improves. - If the paper trend is not reproduced, keep the generated tables and record the failed metrics honestly instead of tuning results by hand. + +## 2026-06-02 follow-up algorithm fidelity fix + +### Findings addressed +- ALNS, T-ALNS, and T-ALNS-RRD did not distinguish `sigma_2` current-solution improvements from `sigma_3` simulated-annealing accepted non-improving moves. +- T-ALNS diversification intensity used adaptive Tabu tenure instead of the paper's actual move-memory occupancy `|T_move| / |T_move|max`. + +### Changes made +- Reward assignment now follows the paper's four levels: + - `reward_global_best` for new global best. + - `reward_improvement` for accepted moves that improve the previous current solution. + - `reward_accepted` for accepted non-improving moves. + - `reward_rejected` for rejected moves. +- Added `MoveTabu.utilization` and changed Eq.27 diversification intensity to use actual move Tabu list occupancy. + +### Local validation +- Per user instruction, no local Python tests, smoke tests, or full experiments were run. +- Static checks only: source inspection and `git diff --check`. diff --git a/t_alns_rrd_reproduction/src/alns/alns_base.py b/t_alns_rrd_reproduction/src/alns/alns_base.py index f7f5179..82790a1 100644 --- a/t_alns_rrd_reproduction/src/alns/alns_base.py +++ b/t_alns_rrd_reproduction/src/alns/alns_base.py @@ -207,6 +207,7 @@ class ALNSBase: new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx) # Acceptance decision (Eq.20) + previous_cost = current_cost if sa.accept(current_cost, new_cost): S_current = S_new current_cost = new_cost @@ -216,9 +217,12 @@ class ALNSBase: best_cost = new_cost stall_counter = 0 reward = self.cfg["reward_global_best"] - else: + elif new_cost < previous_cost: stall_counter += 1 reward = self.cfg["reward_improvement"] + else: + stall_counter += 1 + reward = self.cfg["reward_accepted"] else: reward = self.cfg["reward_rejected"] diff --git a/t_alns_rrd_reproduction/src/rrd/t_alns_rrd.py b/t_alns_rrd_reproduction/src/rrd/t_alns_rrd.py index 7721a30..526a724 100644 --- a/t_alns_rrd_reproduction/src/rrd/t_alns_rrd.py +++ b/t_alns_rrd_reproduction/src/rrd/t_alns_rrd.py @@ -285,6 +285,7 @@ class TALNSRRD: new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx) # Acceptance + previous_cost = current_cost if sa.accept(current_cost, new_cost): S_current = S_new current_cost = new_cost @@ -300,10 +301,14 @@ class TALNSRRD: last_best_iter = iter_count self.talns.move_tabu.update_tenure(found_improvement=True) reward = self.cfg["reward_global_best"] + elif new_cost < previous_cost: + stall_counter += 1 + self.talns.move_tabu.update_tenure(found_improvement=True) + reward = self.cfg["reward_improvement"] else: stall_counter += 1 self.talns.move_tabu.update_tenure(found_improvement=False) - reward = self.cfg["reward_improvement"] + reward = self.cfg["reward_accepted"] accepted = True else: reward = self.cfg["reward_rejected"] diff --git a/t_alns_rrd_reproduction/src/tabu/move_tabu.py b/t_alns_rrd_reproduction/src/tabu/move_tabu.py index ecc3b57..974e40f 100644 --- a/t_alns_rrd_reproduction/src/tabu/move_tabu.py +++ b/t_alns_rrd_reproduction/src/tabu/move_tabu.py @@ -75,6 +75,11 @@ class MoveTabu: while len(self._entries) > self.tenure_max: self._entries.popleft() + @property + def utilization(self) -> float: + """Normalized |T_move| / |T_move|max for Eq.27 diversification.""" + return len(self._entries) / max(self.tenure_max, 1) + def update_tenure(self, found_improvement: bool): """Adapt tenure based on improvement (Eq.32). diff --git a/t_alns_rrd_reproduction/src/tabu/t_alns.py b/t_alns_rrd_reproduction/src/tabu/t_alns.py index c5d2dad..5b12d91 100644 --- a/t_alns_rrd_reproduction/src/tabu/t_alns.py +++ b/t_alns_rrd_reproduction/src/tabu/t_alns.py @@ -160,8 +160,9 @@ class TALNS: # Time since last improvement (normalized) time_factor = (current_iter - last_best_iter) / max(t_max, 1) - # Move Tabu utilization - move_factor = self.move_tabu.tenure / self.cfg["move_tabu_tenure_max"] + # Move Tabu utilization: Eq.27 uses actual memory occupancy |T_move|, + # not the adaptive tenure parameter from Eq.32. + move_factor = self.move_tabu.utilization # Frequency std freq_std = self.freq_mem.get_std_assignment_freq() @@ -379,6 +380,7 @@ class TALNS: new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx) # Acceptance decision + previous_cost = current_cost if sa.accept(current_cost, new_cost): S_current = S_new current_cost = new_cost @@ -398,10 +400,14 @@ class TALNS: last_best_iter = iter_count self.move_tabu.update_tenure(found_improvement=True) reward = self.cfg["reward_global_best"] + elif new_cost < previous_cost: + stall_counter += 1 + self.move_tabu.update_tenure(found_improvement=True) + reward = self.cfg["reward_improvement"] else: stall_counter += 1 self.move_tabu.update_tenure(found_improvement=False) - reward = self.cfg["reward_improvement"] + reward = self.cfg["reward_accepted"] accepted = True else: reward = self.cfg["reward_rejected"]