fix ALNS rewards and tabu diversification
This commit is contained in:
18
PROGRESS.md
18
PROGRESS.md
@@ -41,3 +41,21 @@ python src/experiments/run_main_comparison.py --config paper --seeds 30 --iterat
|
|||||||
### Interpretation rule
|
### Interpretation rule
|
||||||
- Check whether Static > TA-Greedy > ALNS-Base > T-ALNS > T-ALNS-RRD in total cost, CES decreases, and OTDR improves.
|
- 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.
|
- 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`.
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ class ALNSBase:
|
|||||||
new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx)
|
new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx)
|
||||||
|
|
||||||
# Acceptance decision (Eq.20)
|
# Acceptance decision (Eq.20)
|
||||||
|
previous_cost = current_cost
|
||||||
if sa.accept(current_cost, new_cost):
|
if sa.accept(current_cost, new_cost):
|
||||||
S_current = S_new
|
S_current = S_new
|
||||||
current_cost = new_cost
|
current_cost = new_cost
|
||||||
@@ -216,9 +217,12 @@ class ALNSBase:
|
|||||||
best_cost = new_cost
|
best_cost = new_cost
|
||||||
stall_counter = 0
|
stall_counter = 0
|
||||||
reward = self.cfg["reward_global_best"]
|
reward = self.cfg["reward_global_best"]
|
||||||
else:
|
elif new_cost < previous_cost:
|
||||||
stall_counter += 1
|
stall_counter += 1
|
||||||
reward = self.cfg["reward_improvement"]
|
reward = self.cfg["reward_improvement"]
|
||||||
|
else:
|
||||||
|
stall_counter += 1
|
||||||
|
reward = self.cfg["reward_accepted"]
|
||||||
else:
|
else:
|
||||||
reward = self.cfg["reward_rejected"]
|
reward = self.cfg["reward_rejected"]
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ class TALNSRRD:
|
|||||||
new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx)
|
new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx)
|
||||||
|
|
||||||
# Acceptance
|
# Acceptance
|
||||||
|
previous_cost = current_cost
|
||||||
if sa.accept(current_cost, new_cost):
|
if sa.accept(current_cost, new_cost):
|
||||||
S_current = S_new
|
S_current = S_new
|
||||||
current_cost = new_cost
|
current_cost = new_cost
|
||||||
@@ -300,10 +301,14 @@ class TALNSRRD:
|
|||||||
last_best_iter = iter_count
|
last_best_iter = iter_count
|
||||||
self.talns.move_tabu.update_tenure(found_improvement=True)
|
self.talns.move_tabu.update_tenure(found_improvement=True)
|
||||||
reward = self.cfg["reward_global_best"]
|
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:
|
else:
|
||||||
stall_counter += 1
|
stall_counter += 1
|
||||||
self.talns.move_tabu.update_tenure(found_improvement=False)
|
self.talns.move_tabu.update_tenure(found_improvement=False)
|
||||||
reward = self.cfg["reward_improvement"]
|
reward = self.cfg["reward_accepted"]
|
||||||
accepted = True
|
accepted = True
|
||||||
else:
|
else:
|
||||||
reward = self.cfg["reward_rejected"]
|
reward = self.cfg["reward_rejected"]
|
||||||
|
|||||||
@@ -75,6 +75,11 @@ class MoveTabu:
|
|||||||
while len(self._entries) > self.tenure_max:
|
while len(self._entries) > self.tenure_max:
|
||||||
self._entries.popleft()
|
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):
|
def update_tenure(self, found_improvement: bool):
|
||||||
"""Adapt tenure based on improvement (Eq.32).
|
"""Adapt tenure based on improvement (Eq.32).
|
||||||
|
|
||||||
|
|||||||
@@ -160,8 +160,9 @@ class TALNS:
|
|||||||
# Time since last improvement (normalized)
|
# Time since last improvement (normalized)
|
||||||
time_factor = (current_iter - last_best_iter) / max(t_max, 1)
|
time_factor = (current_iter - last_best_iter) / max(t_max, 1)
|
||||||
|
|
||||||
# Move Tabu utilization
|
# Move Tabu utilization: Eq.27 uses actual memory occupancy |T_move|,
|
||||||
move_factor = self.move_tabu.tenure / self.cfg["move_tabu_tenure_max"]
|
# not the adaptive tenure parameter from Eq.32.
|
||||||
|
move_factor = self.move_tabu.utilization
|
||||||
|
|
||||||
# Frequency std
|
# Frequency std
|
||||||
freq_std = self.freq_mem.get_std_assignment_freq()
|
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)
|
new_cost = self.cost_calc.compute_total_cost(S_new, self.ctx)
|
||||||
|
|
||||||
# Acceptance decision
|
# Acceptance decision
|
||||||
|
previous_cost = current_cost
|
||||||
if sa.accept(current_cost, new_cost):
|
if sa.accept(current_cost, new_cost):
|
||||||
S_current = S_new
|
S_current = S_new
|
||||||
current_cost = new_cost
|
current_cost = new_cost
|
||||||
@@ -398,10 +400,14 @@ class TALNS:
|
|||||||
last_best_iter = iter_count
|
last_best_iter = iter_count
|
||||||
self.move_tabu.update_tenure(found_improvement=True)
|
self.move_tabu.update_tenure(found_improvement=True)
|
||||||
reward = self.cfg["reward_global_best"]
|
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:
|
else:
|
||||||
stall_counter += 1
|
stall_counter += 1
|
||||||
self.move_tabu.update_tenure(found_improvement=False)
|
self.move_tabu.update_tenure(found_improvement=False)
|
||||||
reward = self.cfg["reward_improvement"]
|
reward = self.cfg["reward_accepted"]
|
||||||
accepted = True
|
accepted = True
|
||||||
else:
|
else:
|
||||||
reward = self.cfg["reward_rejected"]
|
reward = self.cfg["reward_rejected"]
|
||||||
|
|||||||
Reference in New Issue
Block a user