Skip to content

Align TS-NSGA-II's environmental selection with the reference implementation - #46

Merged
Zhenyu2Liang merged 2 commits into
EMI-Group:mainfrom
flyingdoubleG:fix-tsnsga2-selection
Oct 29, 2026
Merged

Zhenyu2Liang merged 2 commits into
EMI-Group:mainfrom
flyingdoubleG:fix-tsnsga2-selection

Conversation

@flyingdoubleG

Copy link
Copy Markdown
Contributor

TSNSGAII._environmental_selection differs from PlatEMO's TS-NSGA-II (TSNSGAII.m, EnvironmentalSelection.m, EnvironmentalSelection1.m, SPDSort.m, DensityEstimate.m) in ways that change the results a lot, most visibly on DTLZ1, where main does not converge (IGD ≈ 171–237).

Stage 2

  • _level_sort_selection took, for each weight, the best-cosine solution among all 2N solutions, so dominated solutions were kept and stage 2 had no convergence pressure. The reference first keeps the first non-dominated fronts that hold at least N solutions, then for each reference vector in turn takes the remaining solution with the largest cosine, normalizing the remaining ones each time. This PR does that.
  • The level computation (torch.max(...).floor()) was computed and discarded. I dropped it rather than wiring it in: in the reference, LevelSort's early exit tests size(levelPopulation,1) >= N, but levelPopulation is a row of solutions, so that size is always 1 and every level is kept. The level sort therefore does not narrow the set there either.
  • The second mating key in stage 2 is now the region density (DensityEstimate.m) instead of d2.
  • When uniform_sampling returns fewer reference vectors than pop_size (91 for pop_size=100, n_objs=3), the vectors are reused in order until pop_size solutions are chosen. The reference instead sets N to the number of vectors.

Stage 1

  • _spd_sort returned the number of SPD-dominators of each solution, not SPD front numbers. It now peels fronts until at least N solutions are sorted.
  • As in the reference, the extreme non-dominated solution of each objective gets d1 = d2 = 0, and the last front is filled by smallest d2 (it was lexsorted by d1 + 5·d2).

Stage switch: at (0.8 + 0.2 * (1 - 3 / M)) * max_fe (0.7 for M = 2, 0.8 for M = 3), as in TSNSGAII.m, instead of max_fe // 2. The docstring is updated.

IGD after 200 generations, pop_size=100, seeds 0–4, with max_fe=20000 (the budget actually used):

problem main (mean) this PR (mean) this PR, per seed
DTLZ1 (m=3, d=7) 171.20 0.210 0.583 0.021 0.022 0.302 0.122
DTLZ2 (m=3, d=12) 0.0728 0.0539 0.0539 0.0539 0.0539 0.0540 0.0539
DTLZ4 (m=3, d=12) 0.1801 0.0540 0.0540 0.0540 0.0539 0.0540 0.0540
ZDT1 0.1066 0.0691 0.068 0.062 0.023 0.124 0.068
ZDT2 0.1898 0.1190 0.080 0.150 0.109 0.065 0.191
ZDT3 0.1187 0.1001 0.077 0.069 0.121 0.130 0.103

With the default max_fe=10000 the picture is the same (DTLZ1 237.4 → 0.233, DTLZ2 0.0859 → 0.0539, ZDT1 0.159 → 0.095).

ZDT is still well above the other algorithms. One contributor I found while checking this, and which I will send separately because it affects ten algorithms: polynomial_mutation(..., pro_m=1.0 / D) mutates with per-gene probability 1/D², because evox's polynomial_mutation already divides pro_m by D.

Tests: new unit_test/algorithms/test_tsnsga2.py covers SPD front peeling, the same-reference-vector tie break, and stage 2 keeping only non-dominated solutions when there are enough. test_moea_generated.py still passes. ruff check is clean.

This was prepared with AI assistance (Claude); I reviewed and tested the change.

- Stage 2 chose among all 2N solutions, dominated ones included, so it had no
  convergence pressure. Restrict it to the first non-dominated fronts holding
  at least N solutions and, as the reference does, take for each reference
  vector in turn the remaining solution with the largest cosine, normalizing
  the remaining ones each time.
- Stage 1 ranked by the number of SPD-dominators. Peel SPD fronts instead,
  zero d1 and d2 of the extreme non-dominated solutions, and fill from the
  last front by d2.
- Switch stages at (0.8 + 0.2 * (1 - 3 / M)) * max_fe rather than max_fe / 2,
  and use the region density as the second mating key in stage 2.
- Drop the level computation whose result was discarded; the reference's
  level sort does not narrow the set either.
@read-the-docs-community

read-the-docs-community Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 evomo | 🛠️ Build #35505879 | 📁 Comparing e1363f6 against latest (f8e95b8)

  🔍 Preview build  

2 files changed
± autoapi/evomo/algorithms/index.html
± autoapi/evomo/algorithms/ts_nsga2/index.html

@Zhenyu2Liang

Copy link
Copy Markdown
Collaborator

Updated this PR in e1363f6 to preserve PlatEMO's sequential SPD elimination order. SPD dominance is not transitive: candidates eliminated earlier in a front must not participate in later comparisons, and comparisons must stop when the current candidate is eliminated. The graph-peeling fallback has been removed.

Also removed unit_test/algorithms/test_tsnsga2.py from the PR as requested.

Validation with current main (f7e57b1, including the mutation-rate fix in #47) merged locally:

  • CPU/CUDA selection regressions passed, including duplicate points, constant objectives, and cyclic SPD relations. The existing generated suite passed all 48 algorithm subtests. Regression checks were kept outside the repository.
  • All 60 reference comparisons matched the scalar sequential implementation (population sizes 20/100; 2/3/5 objectives; 10 cases per configuration).
  • Ruff check and format check passed.

Mean IGD over seeds 0-4, population 100, and 200 steps after initialization (20,100 evaluations per run; CPU float32). DTLZ1 uses m=3/d=7, DTLZ2/4 m=3/d=12, and ZDT1/2/3 d=30. All columns use the same main mutation behavior; lower is better:

Problem Current main main + original PR main + corrected PR
DTLZ1 167.29230 0.02325 0.02227
DTLZ2 0.18299 0.05398 0.05401
DTLZ4 0.31785 0.05411 0.05412
ZDT1 0.02845 0.07136 0.04168
ZDT2 0.03720 0.04451 0.06721
ZDT3 0.03162 0.04544 0.06617

This fixes the SPD reference mismatch and preserves the DTLZ improvement, but it does not resolve the ZDT regressions in these five-seed runs. The existing data-dependent front loop still prevents torch.compile(fullgraph=True) in both the original and corrected versions; compilation with graph breaks using the eager backend matched eager results.

@Zhenyu2Liang
Zhenyu2Liang merged commit 000617b into EMI-Group:main Oct 29, 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