Class: Evolvable::Population

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/evolvable/population.rb

Overview

Populations orchestrate the evolutionary process through four key components:

  1. Evaluation: Sorts evolvable instances by fitness
  2. Selection: Chooses parents for combination
  3. Combination: Creates new evolvables from selected parents
  4. Mutation: Introduces variation to maintain genetic diversity

Features:

Initialize a population with default or custom parameters:

population = YourEvolvable.new_population(
  size: 50,
  evaluation: { equalize: 0 },
  selection: { size: 10 },
  mutation: { probability: 0.2, rate: 0.02 }
)

Or inject fully customized strategy objects:

population = YourEvolvable.new_population(
  evaluation: Your::Evaluation.new,
  evolution: Your::Evolution.new,
  selection: Your::Selection.new,
  combination: Your::Combination.new,
  mutation: Your::Mutation.new
)

Evolve your population:

population.evolve(count: 20)            # Run for 20 generations
population.evolve_to_goal               # Run until the current goal is met
population.evolve_to_goal(0.0)          # Run until a specific goal is met
population.evolve_forever               # Run indefinitely, ignoring any goal
population.evolve_selected([...])       # Use a custom subset of evolvables

Create new evolvables:

new = population.new_evolvable
many = population.new_evolvables(count: 10)
with_genome = population.new_evolvable(genome: another.genome)

Customize the evolution lifecycle by implementing hooks:

def self.before_evaluation(pop); end
def self.before_evolution(pop); end
def self.after_evolution(pop); end

Evaluate progress:

best = population.best_evolvable if population.met_goal?

Constant Summary collapse

DUMP_METHODS =

List of attributes that can be dumped during serialization.

Returns:

  • (Array<Symbol>)

    The dumpable attributes

%i[evolvable_type
id
name
size
evolutions_count
gene_space
evolution
evaluation].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(evolvable_type:, id: nil, name: nil, size: 0, evolutions_count: 0, gene_space: nil, parent_evolvables: [], selected_evolvables: [], evaluation: Evaluation.new, evolution: Evolution.new, selection: nil, combination: nil, mutation: nil, evolvables: []) ⇒ Population

Initializes an Evolvable::Population.

Parameters:

  • evolvable_type (Class)

    Required. The class of evolvables to create

  • id (String, nil) (defaults to: nil)

    Optional identifier, not used by Evolvable internally

  • name (String, nil) (defaults to: nil)

    Optional name, not used by Evolvable internally

  • size (Integer) (defaults to: 0)

    The number of instances in the population (default: 0)

  • evolutions_count (Integer) (defaults to: 0)

    The number of evolutions completed (default: 0)

  • gene_space (Evolvable::GeneSpace, nil) (defaults to: nil)

    The gene space for initializing evolvables

  • parent_evolvables (Array<Evolvable>) (defaults to: [])

    Parent evolvables for breeding the next generation

  • selected_evolvables (Array<Evolvable>) (defaults to: [])

    Evolvables selected for combinations

  • evaluation (Evolvable::Evaluation, Hash) (defaults to: Evaluation.new)

    The evaluation strategy

  • evolution (Evolvable::Evolution, Hash) (defaults to: Evolution.new)

    The evolution strategy

  • selection (Evolvable::Selection, Hash, nil) (defaults to: nil)

    Optional selection strategy

  • combination (Evolvable::Combination, Hash, nil) (defaults to: nil)

    Optional combination strategy

  • mutation (Evolvable::Mutation, Hash, nil) (defaults to: nil)

    Optional mutation strategy

  • evolvables (Array<Evolvable>) (defaults to: [])

    Initial evolvables (will be supplemented if fewer than size)



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/evolvable/population.rb', line 102

def initialize(evolvable_type:,
               id: nil,
               name: nil,
               size: 0,
               evolutions_count: 0,
               gene_space: nil,
               parent_evolvables: [],
               selected_evolvables: [],
               evaluation: Evaluation.new,
               evolution: Evolution.new,
               selection: nil,
               combination: nil,
               mutation: nil,
               evolvables: [])
  @id = id
  @evolvable_type = evolvable_type.is_a?(Class) ? evolvable_type : Object.const_get(evolvable_type)
  @name = name
  @size = size
  @evolutions_count = evolutions_count
  @gene_space = initialize_gene_space(gene_space)
  @parent_evolvables = parent_evolvables
  @selected_evolvables = selected_evolvables
  self.evaluation = evaluation
  @evolution = evolution
  self.selection = selection if selection
  self.combination = combination if combination
  self.mutation = mutation if mutation
  self.evolvables = evolvables || []
  new_evolvables(count: @size - evolvables.count)
end

Instance Attribute Details

#evaluationEvolvable::Evaluation

The evaluation strategy used to assess evolvables

Returns:



184
185
186
# File 'lib/evolvable/population.rb', line 184

def evaluation
  @evaluation
end

#evolutionObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def evolution
  @evolution
end

#evolutions_countObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def evolutions_count
  @evolutions_count
end

#evolvable_typeObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def evolvable_type
  @evolvable_type
end

#evolvablesObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def evolvables
  @evolvables
end

#gene_spaceObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def gene_space
  @gene_space
end

#idObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def id
  @id
end

#nameObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def name
  @name
end

#parent_evolvablesObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def parent_evolvables
  @parent_evolvables
end

#selected_evolvablesObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def selected_evolvables
  @selected_evolvables
end

#sizeObject

Population properties



136
137
138
# File 'lib/evolvable/population.rb', line 136

def size
  @size
end

Class Method Details

.load(data) ⇒ Evolvable::Population

Loads a population from serialized data.

Parameters:

  • data (String)

    The serialized population data

Returns:



79
80
81
82
# File 'lib/evolvable/population.rb', line 79

def self.load(data)
  dump_attrs = Serializer.load(data)
  new(**dump_attrs)
end

Instance Method Details

#best_evolvableEvolvable

Returns the best evolvable in the population according to the evaluation goal.

Returns:

  • (Evolvable)

    The best evolvable based on the current goal



269
270
271
# File 'lib/evolvable/population.rb', line 269

def best_evolvable
  evaluation.best_evolvable(self)
end

#dump(only: nil, except: nil) ⇒ String

Serializes the population to a string.

Parameters:

  • only (Array<Symbol>, nil) (defaults to: nil)

    Optional list of attributes to include

  • except (Array<Symbol>, nil) (defaults to: nil)

    Optional list of attributes to exclude

Returns:

  • (String)

    The serialized population



353
354
355
# File 'lib/evolvable/population.rb', line 353

def dump(only: nil, except: nil)
  Serializer.dump(dump_attrs(only: only, except: except))
end

#dump_attrs(only: nil, except: nil) ⇒ Hash

Returns a hash of attributes for serialization.

Parameters:

  • only (Array<Symbol>, nil) (defaults to: nil)

    Optional list of attributes to include

  • except (Array<Symbol>, nil) (defaults to: nil)

    Optional list of attributes to exclude

Returns:

  • (Hash)

    The attributes hash for serialization



378
379
380
381
382
383
384
# File 'lib/evolvable/population.rb', line 378

def dump_attrs(only: nil, except: nil)
  attrs = {}
  dump_methods = only || DUMP_METHODS
  dump_methods -= except if except
  dump_methods.each { |m| attrs[m] = send(m) }
  attrs
end

#evolve(count: 1, goal_value: nil) ⇒ Evolvable::Population

Evolves the population for a specified number of generations or until the goal is achieved.

Parameters:

  • count (Integer, Float) (defaults to: 1)

    Number of generations to evolve. Use Float::INFINITY for indefinite evolution. Defaults to 1.

  • goal_value (Numeric, nil) (defaults to: nil)

    Optional target value for the goal. If provided, evolution halts when this value is met.

Returns:



210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/evolvable/population.rb', line 210

def evolve(count: 1, goal_value: nil)
  goal.value = goal_value if goal_value
  1.upto(count) do
    before_evaluation(self)
    evaluation.call(self)
    before_evolution(self)
    break if met_goal?

    evolution.call(self)
    self.evolutions_count += 1
    after_evolution(self)
  end
end

#evolve_foreverEvolvable::Population

Evolves the population indefinitely, ignoring any goal.

Clears any previously set goal.value to ensure evolution continues indefinitely.

Returns:



244
245
246
247
# File 'lib/evolvable/population.rb', line 244

def evolve_forever
  goal.value = nil
  evolve(count: Float::INFINITY)
end

#evolve_selected(selected_evolvables) ⇒ Evolvable::Population

Evolves the population using a pre-selected set of evolvables. This allows for custom selection beyond the built-in selection strategies.

Parameters:

  • selected_evolvables (Array<Evolvable>)

    The evolvables selected for combinations

Returns:



256
257
258
259
260
261
262
# File 'lib/evolvable/population.rb', line 256

def evolve_selected(selected_evolvables)
  self.selected_evolvables = selected_evolvables
  before_evolution(self)
  evolution.call(self)
  self.evolutions_count += 1
  after_evolution(self)
end

#evolve_to_goal(goal_value = nil) ⇒ Evolvable::Population

Evolves the population until the goal is met.

If no goal value is provided, it uses the currently defined goal.value.

Parameters:

  • goal_value (Numeric, nil) (defaults to: nil)

    Optional target value. Overrides the current goal if provided.

Returns:



232
233
234
235
# File 'lib/evolvable/population.rb', line 232

def evolve_to_goal(goal_value = nil)
  goal_value ||= goal.value
  evolve(count: Float::INFINITY, goal_value: goal_value)
end

#met_goal?Boolean

Checks if the goal has been met by any evolvable in the population.

Returns:

  • (Boolean)

    True if the goal has been met, false otherwise



278
279
280
# File 'lib/evolvable/population.rb', line 278

def met_goal?
  evaluation.met_goal?(self)
end

#new_evolvable(genome: nil) ⇒ Evolvable

Creates a new evolvable instance with an optional genome. If no genome is provided and there are parent evolvables, a genome will be generated through combination.

Parameters:

  • genome (Evolvable::Genome, nil) (defaults to: nil)

    Optional genome for the new evolvable

Returns:



290
291
292
293
294
295
296
297
298
# File 'lib/evolvable/population.rb', line 290

def new_evolvable(genome: nil)
  return generate_evolvables(1).first unless genome || parent_evolvables.empty?

  evolvable = evolvable_type.new_evolvable(population: self,
                                           genome: genome || new_genome,
                                           generation_index: @evolvables.count)
  @evolvables << evolvable
  evolvable
end

#new_evolvables(count:) ⇒ Array<Evolvable>

Creates multiple new evolvable instances.

Parameters:

  • count (Integer)

    The number of evolvables to create

Returns:

  • (Array<Evolvable>)

    The newly created evolvables



306
307
308
309
310
311
312
313
314
315
# File 'lib/evolvable/population.rb', line 306

def new_evolvables(count:)
  if parent_evolvables.empty?
    Array.new(count) { new_evolvable(genome: new_genome) }
  else
    evolvables = generate_evolvables(count)
    @evolvables ||= []
    @evolvables.concat evolvables
    evolvables
  end
end

#new_genomeEvolvable::Genome

Creates a new genome from the gene space.

Returns:



322
323
324
# File 'lib/evolvable/population.rb', line 322

def new_genome
  gene_space.new_genome
end

#new_parent_genome_cycleEnumerator

Creates a cycle of parent genome pairs for combination. Shuffles parent genomes and creates combinations of two.

Returns:

  • (Enumerator)

    A cycle of parent genome pairs



342
343
344
# File 'lib/evolvable/population.rb', line 342

def new_parent_genome_cycle
  parent_evolvables.map(&:genome).shuffle!.combination(2).cycle
end

#reset_evolvablesArray<Evolvable>

Resets the population by clearing all evolvables and creating new ones.

Returns:

  • (Array<Evolvable>)

    The new collection of evolvables



331
332
333
334
# File 'lib/evolvable/population.rb', line 331

def reset_evolvables
  self.evolvables = []
  new_evolvables(count: size)
end