@@ -42,7 +42,6 @@ Creative applications of Evolvable include:
42
42
* [ Genes] ( #genes )
43
43
* [ Populations] ( #populations )
44
44
* [ Evaluation] ( #evaluation )
45
- * [ Goals] ( #goals )
46
45
* [ Evolution] ( #evolution )
47
46
* [ Selection] ( #selection )
48
47
* [ Combination] ( #combination )
@@ -202,174 +201,228 @@ configuration settings.
202
201
203
202
Populations orchestrate the evolutionary process through four key components:
204
203
205
- 1 . ** Evaluation** : Ranks instances by fitness
204
+ 1 . ** Evaluation** : Sorts instances by fitness
206
205
2 . ** Selection** : Chooses parents for combination
207
- 3 . ** Combination** : Creates offspring from parents
206
+ 3 . ** Combination** : Creates new instances from parents
208
207
4 . ** Mutation** : Adds genetic diversity
209
208
210
- ** Features**
209
+ ** Features** :
211
210
212
- - Easy configuration via parameters
213
- - Lifecycle hooks (` before_evaluation ` , ` before_evolution ` , ` after_evolution ` )
214
- - Progress tracking with ` best_evolvable `
215
- - Goal-based or generation-count evolution
216
- - Serialization for saving/restoring
211
+ Initialize your population with parameters
217
212
218
- ** Configuration**
213
+ ``` ruby
214
+ population = YourEvolvable .new_population(
215
+ size: 50 , # Population size (default is 0)
216
+ evaluation: :minimize , # Set goal type (default is :maximize)
217
+ evaluation: { equalize: 0 }, # Supports setting an explicit goal (for fitness to equal 0)
218
+ selection: { size: 10 }, # Parent selection count (default is 2)
219
+ mutation: { probability: 0.2 , # Odds of instance undergoing mutation (default: 0.03)
220
+ rate: 0.02 } # Odds of gene being mutated (1 gene is mutated by default)
221
+ )
222
+ ```
219
223
224
+ Supports setting custom objects for the following components:
220
225
``` ruby
221
- population = MyEvolvable .new_population(
222
- size: 50 , # Population size
223
- evaluation: { maximize: true }, # Fitness goal
224
- selection: { size: 10 }, # Parent selection count
225
- mutation: { probability: 0.2 } # Mutation rate
226
+ population = YourEvolvable .new_population(
227
+ gene_space: Your ::GeneSpace .new ,
228
+ evaluation: Your ::Evaluation .new ,
229
+ evolution: Your ::Evolution .new ,
230
+ selection: Your ::Selection .new ,
231
+ combination: Your ::Combination .new ,
232
+ mutation: Your ::Mutation .new
226
233
)
234
+ ```
227
235
228
- # Run evolution
229
- population.evolve( count: 20 ) # For 20 generations
230
- # or
231
- population.evolve(goal_value: 100 ) # Until fitness goal reached
236
+ Evolve your population with a certain number of generations and/or until a goal is met
237
+
238
+ ``` ruby
239
+ population.evolve(count: 20 , goal_value: 100 )
232
240
```
233
241
242
+ Initialize new evolvables with the evolution strategy defined by the population
234
243
235
- ** Example**
236
244
``` ruby
237
- # Track evolution progress over generations
238
- population = Shape .new_population(size: 30 )
245
+ new_evolvable = population.new_evolvable
246
+ ten_evolvables = population.new_evolvables(count: 10 )
247
+ ```
248
+
249
+ Initialize an evolvable with a custom genome
239
250
240
- 10 .times do |i |
241
- population.evolve(count: 1 )
242
- best = population.best_evolvable
243
- puts " Generation #{ i } : Best fitness = #{ best.fitness } "
251
+ ``` ruby
252
+ outsider_genome = other_population.best_evolvable.genome
253
+ outsider_evolvable = population.new_evolvable(genome: outsider_genome)
254
+ ```
255
+
256
+ Hook into class methods that are called during evolution
257
+
258
+ ``` ruby
259
+ class YourEvolvable
260
+ include Evolvable
261
+
262
+ def self .before_evaluation (population ); end
263
+ def self .before_evolution (population ); end
264
+ def self .after_evolution (population ); end
244
265
end
266
+ ` ` `
267
+
268
+ Return the bsest evolvable
245
269
246
- # Get and use the best solution
247
- best_shape = population.best_evolvable
248
- puts " Final solution: #{ best_shape } "
270
+ ` ` ` ruby
271
+ best_evolvable = population.best_evolvable if population.met_goal?
272
+ ` ` `
273
+
274
+ Check if the population's goal has been met
275
+
276
+ ` ` ` ruby
277
+ population.met_goal?
249
278
` ` `
250
279
251
280
252
281
[Population Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Population)
253
282
254
283
## Evaluation
255
284
256
- Evaluation determines how evolvables are ranked based on their fitness scores and provides
257
- mechanisms to specify evolutionary goals (maximize, minimize, or equalize).
285
+ Evaluation sorts evolvables based on their fitness and provides mechanisms to
286
+ change the goal type and value (fitness goal). Goals define the success criteria
287
+ for evolution. They allow you to specify what your population is evolving toward,
288
+ whether it's maximizing a value, minimizing a value, or seeking a specific value.
258
289
259
290
**How It Works**
260
291
261
- 1 . Your evolvable class defines a ` #fitness ` method that returns a numeric score
262
- 2 . The evaluation's goal determines how this score is interpreted:
263
- - ` maximize ` : Higher values are better (default)
264
- - ` minimize ` : Lower values are better
265
- - ` equalize ` : Values closer to target are better
266
- 3 . During evolution, evolvables are sorted based on the goal's interpretation
267
- 4 . Evolution can stop when an evolvable reaches a specified goal value
292
+ 1. Your evolvable class defines a ` # fitness` method that returns a
293
+ [Comparable ](https: // docs.ruby- lang.org/ en// 3.4 / Comparable .html) object.
294
+ - Preferably a numeric value like an integer or float.
268
295
296
+ 2 . During evolution, evolvables are sorted by your goal' s fitness interpretation
297
+ - The default goal type is `:maximize`, see goal types below for other options
298
+
299
+ 3. If a goal value is specified, evolution will stop when it is met
300
+
301
+ **Goal Types**
302
+
303
+ - Maximize (higher is better)
269
304
270
- ** Example**
271
305
```ruby
272
- # Define an evolvable with a fitness function
273
- class Robot
274
- include Evolvable
306
+ robots = Robot.new_population(evaluation: :maximize) # Defaults to infinity
307
+ robots.evolve(goal_value: 100) # Evolve until fitness reaches 100+
275
308
276
- gene :speed , type: SpeedGene , count: 1
277
- gene :sensors , type: SensorGene , count: 1 ..5
309
+ # Same as above
310
+ Robot.new_population(evaluation: { maximize: 100 }).evolve
311
+ ```
278
312
279
- def fitness
280
- # Calculate fitness based on speed and sensor quality
281
- score = speed.value * 10
282
- score += sensors.sum(& :accuracy ) * 5
283
- score -= sensors.size > 3 ? (sensors.size - 3 ) * 10 : 0 # Penalty for too many sensors
284
- score
285
- end
286
- end
313
+ - Minimize (lower is better)
287
314
288
- # Different goal types
315
+ ```ruby
316
+ errors = ErrorModel.new_population(evaluation: :minimize) # Defaults to -infinity
317
+ errors.evolve(goal_value: 0.01) # Evolve until error rate reaches 0.01 or less
289
318
290
- # 1. Maximize (higher is better)
291
- robots = Robot .new_population(evaluation: { maximize: true })
292
- robots.evolve( goal_value: 100 ) # Until fitness reaches 100+
319
+ # Same as above
320
+ ErrorModel .new_population(evaluation: { minimize: 0.01 }).evolve
321
+ ```
293
322
294
- # 2. Minimize (lower is better)
295
- errors = ErrorModel .new_population(evaluation: { minimize: true })
296
- errors.evolve(goal_value: 0.01 ) # Until error rate reaches 0.01 or less
323
+ - Equalize (closer to target is better)
297
324
298
- # 3. Equalize (closer to target is better)
299
- targets = TargetMatcher .new_population(evaluation: { equalize: 42 })
300
- targets.evolve(goal_value: 42 ) # Until we match the target value
325
+ ```ruby
326
+ targets = TargetMatcher.new_population(evaluation: :equalize) # Defaults to 0
327
+ targets.evolve(goal_value: 42) # Evolve until we match the target value
328
+
329
+ # Same as above
330
+ TargetMatcher.new_population(evaluation: { equalize: 42 }).evolve
301
331
```
302
332
303
333
304
- [ Evaluation Documentation ] ( https://mattruzicka.github.io/evolvable/Evolvable/Evaluation )
334
+ **Custom Goals**
305
335
306
- ## Goals
336
+ You can create custom goals by subclassing `Evolvable::Goal`` and implementing:
337
+ - `evaluate(evolvable)`: Return a value that for sorting evolvables
338
+ - `met?(evolvable)`: Returns true when the goal value is reached
307
339
308
- Goals define the success criteria for evolution. They allow you to specify what your
309
- population is evolving toward, whether it's maximizing a value, minimizing a value,
310
- or reaching a specific target value.
311
340
312
- Evolvable provides three built-in goal types:
313
- - ** MaximizeGoal ** : Higher fitness values are better (e.g., scoring more points)
314
- - ** MinimizeGoal ** : Lower fitness values are better (e.g., reducing errors)
315
- - ** EqualizeGoal ** : Values closer to a target are better (e.g., matching a pattern)
341
+ Evaluation sorts evolvables based on their fitness and provides mechanisms to
342
+ change the goal type and value (fitness goal). Goals define the success criteria
343
+ for evolution. They allow you to specify what your population is evolving toward,
344
+ whether it ' s maximizing a value, minimizing a value, or seeking a specific value.
316
345
317
- Each goal type influences:
318
- 1 . How evolvables are ranked during evaluation
319
- 2 . Which evolvables are selected as parents
320
- 3 . When to stop evolving if a goal value is reached
346
+ ** How It Works **
321
347
322
- ** Custom Goals**
348
+ 1 . Your evolvable class defines a ` #fitness` method that returns a
349
+ [Comparable ](https: // docs.ruby- lang.org/ en// 3.4 / Comparable .html) object.
350
+ - Preferably a numeric value like an integer or float.
323
351
324
- You can create custom goals by subclassing Goal and implementing:
325
- - ` evaluate(evolvable) ` : Returns a value used to rank evolvables
326
- - ` met?(evolvable) ` : Returns true when the goal is reached
352
+ 2 . During evolution, evolvables are sorted by your goal' s fitness interpretation
353
+ - The default goal type is `:maximize`, see goal types below for other options
327
354
355
+ 3. If a goal value is specified, evolution will stop when it is met
356
+
357
+ **Goal Types**
358
+
359
+ - Maximize (higher is better)
328
360
329
- ** Example**
330
361
```ruby
331
- # Using different goal types
332
- class RuleOptimizer
333
- include Evolvable
362
+ robots = Robot.new_population(evaluation: :maximize) # Defaults to infinity
363
+ robots.evolve(goal_value: 100) # Evolve until fitness reaches 100+
334
364
335
- gene :rules , type: RuleGene , count: 5 ..20
365
+ # Same as above
366
+ Robot.new_population(evaluation: { maximize: 100 }).evolve
367
+ ```
336
368
337
- def fitness
338
- # Calculate fitness based on rule effectiveness
339
- accuracy = calculate_accuracy
340
- complexity_penalty = rules.count * 0.5
341
- accuracy - complexity_penalty
342
- end
343
- end
369
+ - Minimize (lower is better)
344
370
345
- # Configure populations with different goals
346
- max_population = RuleOptimizer .new_population(
347
- evaluation: { maximize: true } # Find most effective rules
348
- )
371
+ ```ruby
372
+ errors = ErrorModel.new_population(evaluation: :minimize) # Defaults to -infinity
373
+ errors.evolve(goal_value: 0.01) # Evolve until error rate reaches 0.01 or less
349
374
350
- min_population = RuleOptimizer .new_population(
351
- evaluation: { minimize: 0.1 } # Minimize error rate to 10%
352
- )
375
+ # Same as above
376
+ ErrorModel.new_population( evaluation: { minimize: 0.01 }).evolve
377
+ ```
353
378
354
- equal_population = RuleOptimizer .new_population(
355
- evaluation: { equalize: 50 } # Reach exactly 50% performance
356
- )
379
+ - Equalize (closer to target is better)
380
+
381
+ ```ruby
382
+ targets = TargetMatcher.new_population(evaluation: :equalize) # Defaults to 0
383
+ targets.evolve(goal_value: 42) # Evolve until we match the target value
384
+
385
+ # Same as above
386
+ TargetMatcher.new_population(evaluation: { equalize: 42 }).evolve
387
+ ```
388
+
389
+
390
+ Example goal implementation that prioritizes evolvables with fitness values within a specific range:
391
+
392
+ ```ruby
393
+ class YourRangeGoal < Evolvable::Goal
394
+ def value
395
+ @value ||= 0..100
396
+ end
397
+
398
+ def evaluate(evolvable)
399
+ return 1 if value.include?(evolvable.fitness)
357
400
358
- # Stopping evolution when goal is reached
359
- max_population.evolve(goal_value: 95 ) # Evolve until 95% accuracy
401
+ min, max = value.minmax
402
+ -[(min - evolvable.fitness).abs, (max - evolvable.fitness).abs].min
403
+ end
404
+
405
+ def met?(evolvable)
406
+ value.include?(evolvable.fitness)
407
+ end
408
+ end
360
409
```
361
410
411
+
412
+ [Evaluation Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Evaluation)
413
+
362
414
[Goal Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Goal)
363
415
364
416
## Evolution
365
417
366
- After a population's instances are evaluated, they undergo evolution.
367
- The default evolution object is composed of selection,
368
- combination, and mutation objects and applies them as operations to
369
- a population's evolvables in that order.
418
+ **Evolution** moves a population from one generation to the next.
419
+ It runs in three steps: selection, combination, and mutation.
420
+ You can swap out any step with your own strategy.
370
421
371
- Each evolutionary operation can be customized individually, allowing you to
372
- fine-tune the evolutionary process to fit your specific problem domain.
422
+ Default pipeline:
423
+ 1. **Selection** – keep the most fit evolvables
424
+ 2. **Combination** – create offspring by recombining genes
425
+ 3. **Mutation** – add random variation to preserve diversity
373
426
374
427
375
428
[Evolution Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Evolution)
0 commit comments