Greenfield Bend 2 port of U-NSGA-III (Seada & Deb, 2016).
This repository is standalone public OSS. It is not a NuGet package, not PackageId Unsga3, not a drop-in replacement for C# consumers, and not a dependency of any private product. Consumers that already use the C# library keep using that library; this repo does not know about those applications.
https://github.com/AppSprout-dev/unsga3-bend
The reference implementation is the existing C# library:
- Source: AppSprout-dev/Unsga3
- PackageId
Unsga3stays C# only — NuGet / GitHub Packages
unsga3-bend is a from-scratch Bend 2 port of the algorithm core, not a binding and not a republish of that package. The two stacks sit beside each other: C# remains the .NET package; this tree is the Bend hub package. The shared validation plan is the same public protocol the C# docs use: ZDT / DTLZ problems and IGD against a pymoo UNSGA3 oracle (C# docs/EQUIVALENCE.md).
0.2.2 is the current Bend content-hash hub package. bend src/lib.bend --publish ran again for this tree (Bend 2.0.34) and the content hash changed. Artifacts do not go to nuget.org or GitHub Packages.
0.1.0 was the first hub publish (0xcd07e24a626a62e74603d48f436cd679). GitHub 0.1.1 was a docs/ab confidence bump (multi-seed IGD tables + Layer-1 fixture check) and did not re-publish. GitHub 0.1.2 (0x527a2a4fa91b05a0250d7be0e11d232a) includes the RankNicheDistance fix. 0.2.0 (0xa2f9d6ef8c474468bf1de15ebe70c512) adds the unconstrained problem catalog, Deb constraint-domination, the offspring duplicate contract, and the odd-N SBX fix. 0.2.1 (0x2bc7fb472c80bd6a0e04725c117edb2a) adds the DTLZ3 axis clamp, OSY / TNK / C1-DTLZ1, and the CV-fill overflow fix. 0.2.2 (0xd1de66b5d9157913a654c39186289f3d) keeps the speed-dig niche bags and associate residual. A/B stays PymooCompatible. Git tags v0.2.0 and v0.2.1 and their GitHub Releases exist. Tag v0.2.2 follows once this commit is main's tip.
Bend fetches a published package by content hash (bend guide § Modules). Consumer-facing import (package alias Unsga3):
import 0xd1de66b5d9157913a654c39186289f3d/lib.bend as Unsga3
bend src/lib.bend --publish printed this exact line (alias Lib):
import 0xd1de66b5d9157913a654c39186289f3d/lib.bend as Lib
The hub entry path is /lib.bend (what bend printed), not /src/lib.bend. Content hash: 0xd1de66b5d9157913a654c39186289f3d.
The named package unified-nsga-iii@0.1.2.0 still points at the 0.1.2 hash. unified-nsga-iii@0.2.0.0 still resolves to 0xa2f9d6ef8c474468bf1de15ebe70c512. Once this commit is main's tip, bend link unified-nsga-iii@0.2.2.0 0xd1de66b5d9157913a654c39186289f3d.
Develop against src/ in this checkout with a local path:
import ./src/lib.bend as Unsga3
src/lib.bend is the module-graph facade (core + variation + problems + Run). See CHANGELOG.md.
Shipped in the 0.2.2 hub package (algorithm + A/B helpers):
- Core — non-dominated sort, NSGA-III normalization, Das–Dennis directions, niching / association, survival
- Variation + Run — decision variables, SBX (η=30, p=1.0), polynomial mutation (η=20, p=1/n), ZDT1 / ZDT2 / DTLZ2 (3-obj),
PymooCompatibletournament,Unsga3Algorithm.Run - Catalog — ZDT3 / ZDT4 / ZDT6, DTLZ1 / DTLZ3 / DTLZ4 / DTLZ7, Sphere / Ackley / Rosenbrock. In this hub hash, including the DTLZ3 axis clamp. Dumpable. Measured IGD: docs/ORACLE-CATALOG.md
- Constrained demos — OSY, TNK, C1-DTLZ1 in this hub hash. Feasible-only survival and hyperplane. Smoke binary:
bend src/constrained_smoke.bend -o. Cells: docs/CONSTRAINED-SURFACES.md - Constraints + offspring — Deb constraint-domination (
cvonIndividual), CV-first tournaments, survivor-set duplicate keys, odd-N SBX (parentN-1with parent0). A/B staysPymooCompatible - Parallel maps — independent per-individual work uses Bend
a b = f(lo) f(hi)mid-splits. Tournament, SBX, mutation, and last-front niching stay sequential so a fixed seed consumes RNG in the same order - Native dumps —
bend src/….bend -o …then run the binary.ab/dump_bend_run.py --nativeprefers that path and falls back tobend file.bendif the build fails - Proofs —
bend PROOF.bendis 0?TODO. Public “proves” / “resolved” verbs follow docs/PROOF-SURFACE.md.
Measured native phase tables (not IGD): docs/PERF_NOTES.md. Protocol: docs/EQUIVALENCE.md.
| Module | C# surface it mirrors |
|---|---|
Individual |
objectives + variables + evaluated + rank/niche bookkeeping |
NonDominatedSort |
fast non-dominated sort + Pareto compare |
Normalization |
NSGA-III adaptive hyperplane (persistent across Run) |
ReferenceDirections |
Das–Dennis directions / count |
Survival |
niching association + environmental selection |
problems |
Zdt1 / Zdt2 / Zdt3 / Zdt4 / Zdt6, Dtlz1 / Dtlz2 / Dtlz3 / Dtlz4 / Dtlz7, Sphere / Ackley / Rosenbrock, Osy / Tnk / C1Dtlz1 |
tournament |
TournamentSelection / TournamentMode |
algorithm |
Unsga3Algorithm.Run |
Two layers; do not invent IGD numbers in this repo.
- v0 core A/B — feed the same objective population to C# and Bend sort / normalize / associate / select. Compare ranks, associations, and the selected index set.
- Algorithm A/B — dump ND fronts from Bend
Run(and optionally C#OracleComparewhenUNSGA3_CS_ROOTis set) on shared ZDT/DTLZ settings; IGD vs pymoo when installed.
Protocol (same as C# docs/EQUIVALENCE.md / tools/OracleCompare; dump defaults in ab/protocol.py):
| Label | Problem | Partitions | Pop | Gens | Seed | Tournament |
|---|---|---|---|---|---|---|
| oracle ZDT1 | ZDT1 n=30 | 12 | 52 | 100 | 1 | PymooCompatible |
| oracle ZDT2 | ZDT2 n=30 | 12 | 52 | 250 | 1 | PymooCompatible |
| oracle DTLZ2 | DTLZ2 M=3 k=10 | 12 | 92 | 150 | 1 | PymooCompatible |
| smoke ZDT1 (checked-in) | ZDT1 n=30 | 4 | 8 | 3 | 1 | PymooCompatible |
Operators: SBX η=30, PM η=20, p_c=1.0, p_m=1/n. Smoke is labeled smoke and is not an oracle claim. ZDT2 quality protocol is gens=250; gens=100 is an early-stress snapshot (PR #16: Bend 10/15 and C# 8/15 collapse), not the A/B default — see docs/ZDT2_COLLAPSE.md and ab/protocol.py. RankNicheDistance is an optional dump flag (--tournament rank_niche), not the new default.
Intentional deltas vs C#: Sort uses Deb constraint-domination (equal infeasible CV is mutual non-domination; published C# CompareConstraintDominated still Pareto-compares that tie). Oracle ZDT1 / ZDT2 / DTLZ2 stay unconstrained (CV 0); OSY / TNK / C1-DTLZ1 exercise the Deb / CV path. Bend RNG is a portable LCG (not System.Random), so fronts will not match bit-for-bit. Run survival niching threads rng for min-count niche ties (C# Select(..., rng)); last-front extras are random among near-best on the ray, not uniform inNiche[rng.Next] (that LCG path collapsed oracle ZDT2). v0 select stays deterministic. Duplicate keys round decision variables to 12 decimal places (round(x*1e12)/1e12), which is not C# ToString("G12") significant digits (small variables diverge; see src/g12_key.bend). Unequal-length objectives are mutual non-domination (C# ComparePareto throws; see src/nds_unequal.bend). C# ctor default tournament is RankNicheDistance; Bend A/B / smoke uses PymooCompatible. GitHub 0.1.2 orders that optional key as rank, then niche count, then perpendicular distance, then a coin. DTLZ2 IGD uses a Das–Dennis-density PF (see ab/README.md); pymoo’s default ~136-pt PF is a different yardstick.
Bend is not assumed to be on PATH. Install from the official script:
curl -fsSL https://bend-lang.com/install.sh | sh
export PATH="$HOME/.bend/bin:$PATH"
export BEND_NO_TELEMETRY=1
bend guideSmoke (not the quality protocol — ZDT1 pop=8, gens=3):
bend src/lib.bend # module graph: All terms check (Bend 2.0.10+)
bend src/ab_select.bend # v0 selection smoke (prints CSV front)
bend src/op_smoke.bend # SBX + poly mutation on a 2-var box, seed 42
bend src/run_smoke.bend # short fixed-seed ZDT1 Run; prints ND front CSV
bend PROOF.bend # gate: all LAWS.bend claims closed (0 ?TODO)
mkdir -p ab/out
bend src/run_smoke.bend -o ab/out/run_smoke # clang 14+
./ab/out/run_smoke --threads 8
python3 ab/dump_bend_front.py
python3 ab/dump_bend_run.py --native # same smoke; fallback to `bend file.bend`Quality A/B (optional; pass the table knobs — see docs/EQUIVALENCE.md):
python3 ab/dump_bend_run.py --native --problem zdt1 --partitions 12 --pop 52 --gens 100 --seed 1
python3 ab/dump_bend_run.py --native --problem zdt2 --partitions 12 --pop 52 --seed 1
# omitted --gens on zdt2 is 250 (ab/protocol.py). --gens 100 is early-stress only.
python3 ab/dump_bend_run.py --native --problem dtlz2 --partitions 12 --pop 92 --gens 150 --seed 1
python3 ab/dump_csharp_front.py # skip if UNSGA3_CS_ROOT unset
python3 ab/dump_csharp_run.py # skip if UNSGA3_CS_ROOT unset
python3 ab/igd_vs_pymoo.py --front ab/out/bend_run_F.csv --problem zdt1 --pf-points 500dump_bend_run.py writes the # header plus objective rows (Bend's All terms check banner is stripped so interpreter and native dumps match). --bin PATH runs an already-built binary. UNSGA3_BEND_NATIVE=1 is the same as --native. --interpreter forces bend file.bend. Do not invent timings or IGD.
Language: bend-lang.com · github.com/bendlang/bend. How to contribute: CONTRIBUTING.md.
Modules are .bend files: import Base, import ./x.bend as M. Laws live in LAWS.bend (human-owned). Proofs live in PROOF.bend. bend PROOF.bend is the gate.
unsga3-bend/
├── AGENTS.md # Bend agent rules + product locks
├── CHANGELOG.md # 0.2.2 hub re-publish (speed-dig niche + associate)
├── CONTRIBUTING.md # install / smoke / proofs / do-nots
├── LAWS.bend # core + operator + Run/ZDT claims (human-owned)
├── PROOF.bend # imports LAWS; closed proofs
├── challenges/ # statement fixtures; they do not replace the wall
├── src/ # core + variation + problems + Run + smokes
├── ab/ # core + algorithm dump / optional IGD / optional C#
├── ab/protocol.py # A/B defaults (ZDT2 gens=250)
├── docs/EQUIVALENCE.md # public protocol (this tree)
├── docs/PROOF-SURFACE.md # proof_surface enum + public verb gate
├── docs/ORACLE-MULTISEED.md # 15-seed IGD + Layer-1 fixture check
├── docs/ROADMAP.md
├── docs/PERF_NOTES.md # measured warm-native phases (not IGD)
├── docs/ZDT2_COLLAPSE.md # ZDT2 gens=100 early-stress vs gens=250 quality
└── LICENSE # MIT
| Doc | Role |
|---|---|
| CONTRIBUTING.md | how to check the tree |
| docs/EQUIVALENCE.md | quality protocol + C# pointer |
| docs/PROOF-SURFACE.md | proof_surface enum and the verb gate |
| challenges/README.md | Laws.niche_count_sum statement fixture |
| docs/ORACLE-MULTISEED.md | 15-seed IGD (Bend / C# / pymoo NSGA-III) + fixture bit-check |
| docs/ZDT2_COLLAPSE.md | why ZDT2 gens=100 is early-stress |
| docs/PERF_NOTES.md | warm-native phase tables |
| docs/ROADMAP.md | shipped vs open |
| ab/README.md | dump / IGD scripts |
MIT — see LICENSE.
Not affiliated with pymoo. A/B scripts compare against pymoo as an external tool when it is installed; this repo does not vendor pymoo.