Skip to content

Fix SIBEA selection ignoring the first front - #41

Merged
Zhenyu2Liang merged 1 commit into
EMI-Group:mainfrom
flyingdoubleG:fix-sibea-front-index
Oct 26, 2026
Merged

Zhenyu2Liang merged 1 commit into
EMI-Group:mainfrom
flyingdoubleG:fix-sibea-front-index

Conversation

@flyingdoubleG

@flyingdoubleG flyingdoubleG commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Description

SIBEA's environmental selection keeps more than pop_size survivors, so the population grows over the run:

import torch
from evomo.algorithms import SIBEA
from evomo.problems.numerical import DTLZ2
from evomo.workflows import UnifiedWorkflow

torch.manual_seed(0)
algo = SIBEA(pop_size=100, n_objs=3, lb=torch.zeros(12), ub=torch.ones(12))
wf = UnifiedWorkflow(algo, DTLZ2(d=12, m=3))
wf.init_step()
for _ in range(30):
    wf.step()
print(algo.pop.shape[0])  # 343; it is 100 after the fix

Per generation: 139, 100, 145, 159, 164, ..., 318, 334, 343. After 200 generations, the population holds 4385 solutions.

Cause. non_dominate_rank returns zero-based ranks (see AGENTS.md), but the front counting follows PlatEMO's one-based FrontNo:

ranks = torch.arange(1, max_rank + 1, device=device)

So rank 0 is never counted, and the cumulative counts start at the second front. As a result:

  • max_f_no is at least 1, and mask_keep = front_no < max_f_no always keeps the whole first front.
  • num_needed = N - num_kept goes negative once the first front is larger than N, and sort_idx[:num_needed] then drops only the last few solutions of the next front instead of selecting nothing.

Fix. Count fronts from rank 0. With that change, num_kept < N <= num_kept + |last front|, and exactly N solutions survive.

Testing.

  • test_sibea_keeps_population_size runs 10 generations and checks the population and fitness sizes. Before the fix it fails with 38 != 20; with the fix it passes.
  • unit_test/algorithms/test_moea_generated.py passes.
  • After 200 generations on DTLZ2 (m=3) the population stays at 100, with IGD 0.073. On ZDT1 the IGD is 0.0056, compared with 0.0052 for NSGA-II under the same budget.

Checklist

  • I have formatted my Python code with ruff.
  • I have good commit messages.
  • If adding new algorithms, problems, operators:
    • Added related test cases.
    • Added docstring to explain important parameters.

non_dominate_rank is zero-based, but SIBEA counted fronts from rank 1, so the
first front was never counted toward the N survivors. Every rank-0 solution was
always kept and num_needed could go negative, so the population grew from
generation to generation (100 -> 343 after 30 generations on DTLZ2).

Count fronts from rank 0.
@Zhenyu2Liang

Copy link
Copy Markdown
Collaborator

Thank you for fixing the bugs.

@Zhenyu2Liang
Zhenyu2Liang merged commit 5638433 into EMI-Group:main Oct 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants