Skip to content

Commit 7bfc7d3

Browse files
committed
Fixed Knapsack example in AGENTS.md
1 parent aa152d5 commit 7bfc7d3

1 file changed

Lines changed: 72 additions & 8 deletions

File tree

AGENTS.md

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
This file helps AI coding agents use this library correctly. It covers decision
44
guidance, API reference, gotchas, and copy-paste templates.
55

6+
**Read the Gotchas section before writing code.** Gotchas 1, 2, 3, and 7 cause
7+
compilation or runtime failures that are hard to debug after the fact.
8+
69
## Quick Start
710

811
```rust
@@ -110,6 +113,22 @@ for diversity) or `CrossoverRejuvenate` (like Clone but optimized for less memor
110113
| `MutateSingleGeneDynamic` | Auto-adjusts probability based on population cardinality. |
111114
| `MutateMultiGeneDynamic` | Auto-adjusts probability based on cardinality. Multiple genes. |
112115

116+
### If unsure, start here
117+
118+
For binary/list genotypes:
119+
```rust
120+
.with_select(SelectTournament::new(0.5, 0.02, 4))
121+
.with_crossover(CrossoverUniform::new(0.7, 0.8))
122+
.with_mutate(MutateSingleGene::new(0.2))
123+
```
124+
125+
For unique genotypes (permutation problems):
126+
```rust
127+
.with_select(SelectTournament::new(0.5, 0.02, 4))
128+
.with_crossover(CrossoverClone::new(0.7))
129+
.with_mutate(MutateMultiGene::new(2, 0.2))
130+
```
131+
113132
## Constructor Parameter Reference
114133

115134
### Select
@@ -195,6 +214,11 @@ MutateMultiGeneDynamic::new(
195214

196215
### Extension (Evolve only, all optional)
197216

217+
Extensions should not be needed when hyperparameters are properly tuned. They are
218+
a fallback when the population keeps collapsing to clones despite reasonable
219+
mutation/selection settings. Escalation order: `MassDegeneration` (least
220+
disruptive) → `MassDeduplication`/`MassExtinction``MassGenesis` (last resort).
221+
198222
```rust
199223
ExtensionMassExtinction::new(
200224
cardinality_threshold: usize, // Trigger when unique chromosomes drop below this.
@@ -226,6 +250,7 @@ ExtensionMassDeduplication::new(
226250
Required:
227251
- `.with_genotype(genotype)` — the search space
228252
- `.with_fitness(fitness)` — the evaluation function
253+
- `.with_target_population_size(n)` — number of chromosomes (**defaults to 0, always set this**)
229254
- `.with_select(select)` — parent selection strategy
230255
- `.with_crossover(crossover)` — how parents combine
231256
- `.with_mutate(mutate)` — how offspring are varied
@@ -237,7 +262,6 @@ Ending conditions (at least one required):
237262
- `.with_max_generations(n)` — stop after n total generations
238263

239264
Optional:
240-
- `.with_target_population_size(n)` — number of chromosomes (default: 0, must set)
241265
- `.with_fitness_ordering(FitnessOrdering::Minimize)` — default is Maximize
242266
- `.with_par_fitness(true)` — parallelize fitness calculation
243267
- `.with_fitness_cache(size)` — LRU cache for expensive fitness
@@ -256,7 +280,9 @@ Required:
256280
- At least ONE ending condition
257281

258282
Optional:
259-
- `.with_variant(HillClimbVariant::SteepestAscent)` — default is Stochastic
283+
- `.with_variant(HillClimbVariant::SteepestAscent)` — default is Stochastic.
284+
Stochastic: fast, one random neighbor per generation, good for large genomes.
285+
SteepestAscent: evaluates all neighbors, finds best improvement, slow for large genomes.
260286
- `.with_fitness_ordering(FitnessOrdering::Minimize)` — default is Maximize
261287
- `.with_par_fitness(true)` — parallelize fitness calculation
262288
- `.with_fitness_cache(size)` — LRU cache for expensive fitness
@@ -319,6 +345,30 @@ let (best, _all_runs) = Evolve::builder()
319345
.unwrap();
320346
```
321347

348+
Both `.call()` and `.build()` return `Result<_, TryFromEvolveBuilderError>`.
349+
Builder validation catches: missing required fields, incompatible genotype +
350+
crossover combinations, and missing ending conditions. The error message includes
351+
an actionable fix suggestion.
352+
353+
### Common mistakes
354+
355+
```rust
356+
// WRONG: UniqueGenotype + CrossoverUniform = RUNTIME PANIC
357+
// FIX: Use CrossoverClone or CrossoverRejuvenate
358+
359+
// WRONG: No ending condition = COMPILE/BUILD ERROR
360+
// FIX: Add .with_max_stale_generations(1000)
361+
362+
// WRONG: target_population_size not set (defaults to 0) = SILENT FAILURE
363+
// FIX: Add .with_target_population_size(100)
364+
365+
// WRONG: Fitness returns f64 = TYPE ERROR
366+
// FIX: Return Some((score / precision) as FitnessValue)
367+
368+
// WRONG: MutateSingleGene(0.2) with 1000+ float genes = DIVERSITY COLLAPSE
369+
// FIX: Use MutateMultiGene with higher mutation count, see Troubleshooting
370+
```
371+
322372
HillClimb also supports `.call_repeatedly(n)` and `.call_par_repeatedly(n)`.
323373

324374
## Retrieving Results
@@ -364,35 +414,49 @@ For simple fitness functions, just ignore the mutability. When using
364414

365415
## Copy-Paste Templates
366416

367-
### Binary Optimization (Knapsack-style)
417+
### Binary Optimization (Knapsack)
368418

369419
```rust
370420
use genetic_algorithm::strategy::evolve::prelude::*;
371421

422+
const ITEMS: [(isize, isize); 10] = [
423+
// (value, weight)
424+
(60, 10), (100, 20), (120, 30), (80, 15), (50, 10),
425+
(90, 25), (70, 18), (40, 8), (110, 22), (65, 12),
426+
];
427+
const MAX_WEIGHT: isize = 80;
428+
372429
#[derive(Clone, Debug)]
373-
struct MyFitness;
374-
impl Fitness for MyFitness {
430+
struct KnapsackFitness;
431+
impl Fitness for KnapsackFitness {
375432
type Genotype = BinaryGenotype;
376433
fn calculate_for_chromosome(
377434
&mut self,
378435
chromosome: &FitnessChromosome<Self>,
379436
_genotype: &FitnessGenotype<Self>,
380437
) -> Option<FitnessValue> {
381-
Some(chromosome.genes.iter().filter(|&&v| v).count() as FitnessValue)
438+
let (total_value, total_weight) = chromosome.genes.iter().enumerate()
439+
.filter(|(_, &included)| included)
440+
.fold((0, 0), |(v, w), (i, _)| (v + ITEMS[i].0, w + ITEMS[i].1));
441+
if total_weight > MAX_WEIGHT {
442+
Some(total_value - (total_weight - MAX_WEIGHT) * 10) // penalty
443+
} else {
444+
Some(total_value)
445+
}
382446
}
383447
}
384448

385449
fn main() {
386450
let genotype = BinaryGenotype::builder()
387-
.with_genes_size(100)
451+
.with_genes_size(ITEMS.len())
388452
.build()
389453
.unwrap();
390454

391455
let evolve = Evolve::builder()
392456
.with_genotype(genotype)
393457
.with_target_population_size(100)
394458
.with_max_stale_generations(100)
395-
.with_fitness(MyFitness)
459+
.with_fitness(KnapsackFitness)
396460
.with_select(SelectTournament::new(0.5, 0.02, 4))
397461
.with_crossover(CrossoverUniform::new(0.7, 0.8))
398462
.with_mutate(MutateSingleGene::new(0.2))

0 commit comments

Comments
 (0)