File size: 2,678 Bytes
01795ee |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# Block Optimization Fix Summary
## The Problem
NSGA-II optimizer was only producing **33-42 blocks** instead of the expected **106 blocks**.
## Root Causes
### 1. Reference vs Copy Issue
When storing best solutions from the Pareto front, we stored references instead of copies:
```python
# WRONG - stores references that get overwritten
best_solutions = [(population[i], objectives[i]) for i in fronts[0]]
best_block_solutions = [block_population[i] for i in fronts[0]]
```
Since `population` and `block_population` are replaced each generation with `offspring`, the stored references pointed to stale/corrupted data.
### 2. Block-Trainset Mismatch
Even with copies, the stored block assignments were created for a *different* trainset selection. When the best solution evolved to have different service trainsets, the old block assignment still mapped to old trainset indices.
Example:
- Generation 50: Best solution has trainsets [0, 2, 5] β blocks assigned to indices 0, 2, 5
- Generation 150: Best solution evolves to trainsets [1, 3, 7] β but block assignment still references 0, 2, 5
- Result: Many blocks map to non-service trainsets β lost blocks
## The Fix
**Always create fresh block assignments for the final best solution:**
```python
# Select best solution from Pareto front
if best_solutions:
best_idx = min(range(len(best_solutions)),
key=lambda i: self.evaluator.fitness_function(best_solutions[i][0]))
best_solution, best_objectives = best_solutions[best_idx]
if self.optimize_blocks:
# Always create fresh block assignment for the best solution
# to ensure all 106 blocks are properly assigned
best_block_sol = self._create_block_assignment(best_solution)
```
The `_create_block_assignment` distributes all blocks evenly across current service trainsets:
```python
def _create_block_assignment(self, trainset_sol: np.ndarray) -> np.ndarray:
service_indices = np.where(trainset_sol == 0)[0]
if len(service_indices) == 0:
return np.full(self.n_blocks, -1, dtype=int)
# Distribute blocks evenly across service trains
block_sol = np.zeros(self.n_blocks, dtype=int)
for i in range(self.n_blocks):
block_sol[i] = service_indices[i % len(service_indices)]
return block_sol
```
## Result
| Optimizer | Before Fix | After Fix |
|-----------|-----------|-----------|
| GA | 106 β | 106 β |
| CMA-ES | 106 β | 106 β |
| PSO | 106 β | 106 β |
| SA | 106 β | 106 β |
| NSGA-II | 33-42 β | 106 β |
All optimizers now correctly assign all 106 service blocks.
|