diff --git a/README.md b/README.md index 1ce5b78..827109e 100644 --- a/README.md +++ b/README.md @@ -412,10 +412,13 @@ START │ │ → GA with BitFlipMutation + │ │ a bit-string crossover │ │ - │ ├─ Decision is Vec (permutation, e.g., TSP) - │ │ → Ant Colony (with a distance matrix) - │ │ → Tabu Search (with your own neighbor function) - │ │ → Simulated Annealing with SwapMutation + │ ├─ Decision is Vec (permutation: TSP, JSS, …) + │ │ → Ant Colony (TSP, with a distance matrix) + │ │ → Simulated Annealing / Tabu Search (strong on + │ │ sequencing — they win the harness TSP and JSS + │ │ tables — you supply the neighbour move) + │ │ → GA + permutation toolkit (ERX for TSP-shaped + │ │ instances) │ │ │ └─ Custom decision type (a struct, a tree, …) │ → Simulated Annealing or Hill Climber @@ -448,10 +451,14 @@ START │ ├─ Want decomposition / weight-vector style │ │ → MOEA/D (very fast per generation, scales well) │ │ - │ ├─ Disconnected or non-convex front + │ ├─ Disconnected front (separate arcs, e.g. ZDT3) + │ │ → IBEA (wins ZDT3 hypervolume on the harness; + │ │ MOEA/D and NSGA-II follow. Geometry-aware + │ │ methods trail when the front is in pieces) + │ │ + │ ├─ Non-convex but *contiguous* front │ │ → AGE-MOEA (estimates front geometry adaptively) │ │ → KnEA (favors knee points) - │ │ → IBEA │ │ │ ├─ Want region-based diversity │ │ → PESA-II (grid hyperboxes drive selection) diff --git a/docs/book/src/choosing-an-algorithm.md b/docs/book/src/choosing-an-algorithm.md index 5a5f426..54541dc 100644 --- a/docs/book/src/choosing-an-algorithm.md +++ b/docs/book/src/choosing-an-algorithm.md @@ -104,9 +104,9 @@ optimizer or with the problem). | `Vec` | [GA][GeneticAlgorithm] + [`BitFlipMutation`] | When bit interactions matter. | | `Vec` (permutation) | [Ant Colony][AntColonyTsp] | TSP-style with a distance matrix. | | `Vec` (permutation) | [GA][GeneticAlgorithm] + [`ShuffledPermutation`] + [`OrderCrossover`] + [`InversionMutation`] | Generic permutation GA; use [`EdgeRecombinationCrossover`] for TSP-shaped instances. | -| `Vec` (JSS multiset) | [GA][GeneticAlgorithm] + [`ShuffledMultisetPermutation`] + local POX + [`InversionMutation`] | Operation-string encoding; see [Optimize a permutation](./cookbook/permutation.md). | -| `Vec` (permutation) | [Simulated Annealing][SimulatedAnnealing] + [`SwapMutation`] | One-decision baseline. | -| `Vec` or custom | [Tabu Search][TabuSearch] | You supply the neighbor function. | +| `Vec` (JSS multiset) | [Simulated Annealing][SimulatedAnnealing] / [Tabu Search][TabuSearch] with [`InsertionMutation`], or [GA][GeneticAlgorithm] + [`ShuffledMultisetPermutation`] + local POX | Operation-string encoding. On the FT06 harness the local-search pair edges out the GA — see [Optimize a permutation](./cookbook/permutation.md). | +| `Vec` (permutation) | [Simulated Annealing][SimulatedAnnealing] + [`InversionMutation`] | Strong on sequencing, not just a baseline — wins the harness's FT06 job-shop table and ties for the TSP optimum. | +| `Vec` or custom | [Tabu Search][TabuSearch] | You supply the neighbor function; consistently near the top on the TSP and JSS tables. | | Custom struct | [Simulated Annealing][SimulatedAnnealing] / [Hill Climber][HillClimber] | With your own `Variation` impl. | heuropt's permutation operator toolkit covers four crossovers @@ -161,14 +161,24 @@ objectives. ### Disconnected or non-convex front +A *disconnected* front (separate arcs, like ZDT3) and a *non-convex but +contiguous* front are different problems — don't conflate them. + +For a **disconnected** front, [IBEA][Ibea] is the clear pick: on the +harness it wins ZDT3 — the disconnected-front benchmark — outright on +hypervolume, with [MOEA/D][Moead] and [NSGA-II][Nsga2] close behind. +Counter-intuitively the geometry-aware methods below *trail* here: +estimating a single front geometry or chasing knee points doesn't help +when the front is in pieces (on ZDT3, AGE-MOEA and KnEA finish last). + +For a **non-convex but contiguous** front: + [AGE-MOEA][AgeMoea] estimates the front geometry adaptively (the L_p parameter `p` is fit from data each generation). [KnEA][Knea] favors knee points — the regions of the front where small gains in one objective cost large losses in another. -[IBEA][Ibea] also handles disconnected fronts well. - ### Region-based diversity [PESA-II][PesaII] uses grid hyperboxes to drive selection — divide the diff --git a/examples/compare-results.md b/examples/compare-results.md index 9d2a5e9..cbbcd9a 100644 --- a/examples/compare-results.md +++ b/examples/compare-results.md @@ -60,10 +60,17 @@ dominated gaps between them. | algorithm | hypervolume ↑ | spacing ↓ | front | ms | |---|---|---|---|---| -| **IBEA** | **126.2072 ± 1.2280** | 0.0164 ± 0.0036 | 48 | 131 | -| MOEA/D | 125.2413 ± 2.1647 | 0.0198 ± 0.0043 | 92 | 27 | -| NSGA-II | 123.1826 ± 1.5829 | **0.0092 ± 0.0020** | 98 | 40 | -| AGE-MOEA | 119.5132 ± 1.2732 | 0.0136 ± 0.0023 | 90 | 171 | +| **IBEA** | **126.2072 ± 1.2280** | 0.0164 ± 0.0036 | 48 | 126 | +| MOEA/D | 125.2413 ± 2.1647 | 0.0198 ± 0.0043 | 92 | 26 | +| NSGA-II | 123.1826 ± 1.5829 | **0.0092 ± 0.0020** | 98 | 39 | +| AGE-MOEA | 119.5132 ± 1.2732 | 0.0136 ± 0.0023 | 90 | 170 | +| KnEA | 117.2180 ± 0.7027 | 0.0147 ± 0.0049 | 79 | 32 | + +The **geometry-aware methods finish last** on the disconnected front: +AGE-MOEA and KnEA both trail the dominance- and decomposition-based +methods. Estimating a single front geometry — or chasing knee points — +doesn't help when the front is in pieces; IBEA's indicator-based +selection wins here. ## DTLZ2 (3-obj, dim=12, 30000 evals/run × 10 seeds) diff --git a/examples/compare.rs b/examples/compare.rs index a26fa29..0141ee4 100644 --- a/examples/compare.rs +++ b/examples/compare.rs @@ -1574,6 +1574,29 @@ fn zdt3_age_moea(seed: u64) -> MoRun { } } +fn zdt3_knea(seed: u64) -> MoRun { + let problem = zdt3_problem(); + let bounds = vec![(0.0, 1.0); ZDT3_DIM]; + let initializer = RealBounds::new(bounds.clone()); + let variation = CompositeVariation { + crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), + mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64), + }; + let pop = 100; + let config = KneaConfig { + population_size: pop, + generations: ZDT3_BUDGET / pop, + seed, + }; + let mut opt = Knea::new(config, initializer, variation); + let t0 = Instant::now(); + let result = opt.run(&problem); + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } +} + // ----------------------------------------------------------------------------- // DTLZ1 runners (curated many-obj subset) // ----------------------------------------------------------------------------- @@ -1998,6 +2021,7 @@ fn run_zdt3_comparison() { ("MOEA/D", zdt3_moead), ("IBEA", zdt3_ibea), ("AGE-MOEA", zdt3_age_moea), + ("KnEA", zdt3_knea), ]; let mut rows: Vec<(f64, Vec)> = Vec::new(); for (name, runner) in runners {