Skip to content

Commit 357b20d

Browse files
committed
Update docs for Population, Evaluation, and Evolution
1 parent cebf14d commit 357b20d

37 files changed

+1365
-1312
lines changed

README.md

Lines changed: 162 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ Creative applications of Evolvable include:
4242
* [Genes](#genes)
4343
* [Populations](#populations)
4444
* [Evaluation](#evaluation)
45-
* [Goals](#goals)
4645
* [Evolution](#evolution)
4746
* [Selection](#selection)
4847
* [Combination](#combination)
@@ -202,174 +201,228 @@ configuration settings.
202201

203202
Populations orchestrate the evolutionary process through four key components:
204203

205-
1. **Evaluation**: Ranks instances by fitness
204+
1. **Evaluation**: Sorts instances by fitness
206205
2. **Selection**: Chooses parents for combination
207-
3. **Combination**: Creates offspring from parents
206+
3. **Combination**: Creates new instances from parents
208207
4. **Mutation**: Adds genetic diversity
209208

210-
**Features**
209+
**Features**:
211210

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
217212

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+
```
219223

224+
Supports setting custom objects for the following components:
220225
```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
226233
)
234+
```
227235

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)
232240
```
233241

242+
Initialize new evolvables with the evolution strategy defined by the population
234243

235-
**Example**
236244
```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
239250

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
244265
end
266+
```
267+
268+
Return the bsest evolvable
245269
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?
249278
```
250279
251280
252281
[Population Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Population)
253282
254283
## Evaluation
255284
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.
258289
259290
**How It Works**
260291
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.
268295

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)
269304
270-
**Example**
271305
```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+
275308
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+
```
278312
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)
287314
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
289318
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+
```
293322
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)
297324
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
301331
```
302332
303333
304-
[Evaluation Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Evaluation)
334+
**Custom Goals**
305335
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
307339
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.
311340
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.
316345

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**
321347

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.
323351

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
327354
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)
328360
329-
**Example**
330361
```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+
334364
335-
gene :rules, type: RuleGene, count: 5..20
365+
# Same as above
366+
Robot.new_population(evaluation: { maximize: 100 }).evolve
367+
```
336368
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)
344370
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
349374
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+
```
353378
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)
357400
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
360409
```
361410
411+
412+
[Evaluation Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Evaluation)
413+
362414
[Goal Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Goal)
363415
364416
## Evolution
365417
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.
370421
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
373426
374427
375428
[Evolution Documentation](https://mattruzicka.github.io/evolvable/Evolvable/Evolution)

0 commit comments

Comments
 (0)