Skip to content

G1–G10: document forensic-audit gaps without rewriting the 15-seed table - #8

Merged
jkbennitt merged 10 commits into
mainfrom
cursor/forensic-gaps-g1-g10-ee88
Sep 22, 2026
Merged

jkbennitt merged 10 commits into
mainfrom
cursor/forensic-gaps-g1-g10-ee88

Conversation

@jkbennitt

Copy link
Copy Markdown
Member

Summary

Ten commits, one per verified finding from the 2026-09-22 forensic audit. Docs, protocol, fixtures, and guards come first. The default tournament stays RankNicheDistance. The 15-seed Wilcoxon number table is not regenerated. No version bump.

Commits

  1. G1 — DTLZ2 oracle passes n_var=12 (k=10), matching Dtlz2Problem. pymoo’s default n_var=10 (k=8) is documented as the published mismatch. Seed 1 remeasured; docs/WILCOXON-RESULTS.md numbers left as published.
  2. G2 — ZDT IGD set definition: C# scores the full non-dominated front; pymoo res.F is the niche optimum. Seed 1 remeasured on a shared reference front. ReferenceDirectionThinning.OnePerDirection is a cardinality aid, not a parity claim.
  3. G3 — Collapsed nadir stays ideal+1 when the span remains ≤ 1e-6. Unit test locks {2, 2+1e-8} → nadir 3. pymoo 0.6.2 stops at worst-of-population.
  4. G4 — RankNicheDistance is not Seada & Deb Algorithm 2. PymooCompatible matches the same-niche split and pymoo’s distance-tie coin flip. pc stays 1.0 (paper experiments use 0.9). Default tournament unchanged.
  5. G5 — Fixture: feasible (1, 1) beside infeasible (0, 0) sets ideal to (0, 0). Ideal and worst still come from the whole pool.
  6. G6 — PrepareForSelection re-normalizes survivors and overwrites niche ids. pymoo keeps the survival ids. Five-point fixture locks the current ids.
  7. G7 — WithDasDennis(1, 1) throws because N must be ≥ 2. Single-objective runs pass populationSize. N=1 is not accepted.
  8. G8 — Euclidean distance rejects a shorter vector. IGD+ hand case. Zdt1(1) and the other one-point samplers throw. ZDT6 floor 0.280775 and ZDT3 endpoint 0.1822287280 are documented and locked (pymoo writes 0.182228780).
  9. G9 — Duplicate-elimination attempt cap when mutation cannot diversify; remaining slots may be duplicates. The key is G12 significant digits, not 12 decimal places.
  10. G10 — Published Mann–Whitney figures replace the 1–2% sentence. DTLZ2 seed-1 bar is 2× the mismatched scalar 0.00350, so a regression to about 2.9× fails. ReferenceDirections.Count uses a checked long and throws past Int32. Two-layer reference directions stay absent.

Measured seed 1 (pasted from this run, pymoo 0.6.2)

DTLZ2, p=12, pop=92, 150 gen, PymooCompatible, n_var=12:

Solver Console IGD Meta IGD Front
C# 0.00403168 0.004031675764658275 92
pymoo 0.00308392 0.003083921253245871 91

Ratio of the meta values: 1.30732. One seed, fronts still differ by one point. The 15-seed pymoo column remains the old n_var=10 run (seed 1 there is C# 0.00403168 / pymoo 0.00349879).

ZDT1, p=12, pop=52, 100 gen, default tournament. Shared 100-point pymoo PF:

Set n IGD
C# full non-dominated front 52 0.05119280568479224
pymoo population, non-dominated 52 0.05378307132263516
pymoo res.F 13 0.0628633417931784

C# on the library 500-point sampler reprinted 0.0514307. Not an algorithm ranking.

Docs vs code

  • Docs / protocol / fixtures: G1 notes, G2 set definition, G3 nadir note, G4 mating note, G5–G7 fixtures, G6 re-association note, G8 endpoint notes, G10 equivalence wording and two-layer absence.
  • Code: oracle n_var=12; optional thinning helper; Euclidean length check; single-point sampler guards; duplicate-elimination attempt cap; Das–Dennis Count checked long; DTLZ2 CI factor 3× → 2×. No survival/mating/nadir formula change. Default tournament unchanged.

Checklist

  • dotnet test Unsga3.slnx -c Release passes locally (66 tests)
  • CHANGELOG.md updated under Unreleased
  • Public API changes documented (XML docs / README)
  • Survival / normalization / metrics: behavior of the hyperplane and mating is unchanged; oracle impact is documented. Multi-seed table was not re-run.

Test plan

  • dotnet test Unsga3.slnx -c Release — 66 passed.
  • DTLZ2 and ZDT1 seed-1 oracles remeasured as tabulated above.
  • docs/WILCOXON-RESULTS.md numeric rows were not regenerated.
Open in Web Open in Cursor 

cursoragent and others added 10 commits September 22, 2026 05:47
C# Dtlz2Problem(k:10) uses n_var=12. pymoo's default is n_var=10 (k=8).
The pymoo oracle now passes n_var=12. Seed 1 was remeasured; the 15-seed
table stays the published mismatched run.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
C# scores the full non-dominated front. The pymoo oracle scores res.F.
Seed 1 was remeasured on a shared reference front. One-per-direction
thinning is a cardinality aid, not a parity claim.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
When the objective span stays at most 1e-6, nadir becomes ideal+1.
pymoo 0.6.2 stops at the worst point in the population. A unit test
locks {2, 2+1e-8} to nadir 3.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
RankNicheDistance stays the default and prefers lower niche count across
niches. PymooCompatible matches the paper's same-niche split and pymoo's
distance-tie coin flip. pc stays 1.0; the paper's experiments use 0.9.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
Ideal and worst still come from the whole pool. A feasible (1, 1) next
to an infeasible (0, 0) keeps ideal at (0, 0). The rule is unchanged.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
PrepareForSelection normalizes survivors again and overwrites niche ids.
pymoo keeps the ids from survival. A five-point fixture locks the current
ids and shows the mating call rewrites them.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
A single objective has one Das-Dennis direction, so the default population
size is 1 and the constructor throws. Callers pass populationSize for that
degeneration. N = 1 stays rejected.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
Euclidean distance rejects unequal vector lengths. IGD+ has a hand-case
test. Single-point ZDT samplers throw. ZDT6's truncated f1 floor and
ZDT3's second endpoint stay as they are and are locked by tests.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
The fallback accepted a duplicate only for the last empty slot, so a
mutation that cannot change x spun forever. Attempts are now capped and
the rest of the slots may be duplicates. The decision key is G12
significant digits, not 12 decimal places.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
The Wilcoxon notes now cite the published Mann-Whitney results. The DTLZ2
seed-1 bar is 2x the mismatched 0.00350 scalar, so a regression to about
2.9x fails. Das-Dennis Count uses a checked 64-bit combination. Two-layer
reference directions stay absent.

Co-authored-by: Jason <jkbennitt@users.noreply.github.com>
@jkbennitt
jkbennitt merged commit 7d81701 into main Sep 22, 2026
2 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