Individuals

This section covers the class Individual in more detail.

Class Summary

_images/individual-class-diagram.svg

Fig. 5 The `Individual` class This class diagram shows the detail for Individual. In additional to the association with Decoder and ProbLem, each Individual has a genome and fitness. There are also several member functions for cloning, decoding, and evaluating individuals. Not shown are such member functions as __repr__() and __str__().

An Individual poses a unique instance of a solution to the associated Problem. Each Individual has a genome, which contains state representing that posed solution. The genome can be a sequence or a matrix or a tree or some other data structure, but in practice a genome is usually a binary or a real-value sequence represented as a numpy array. Every Individual is connected to an associated Problem and relies on the Problem to evaluate its fitness and to compare itself with another Individual to determine the better of the two.

The clone() method will create a duplicate of a given Individual; the new Individual gets a deep copy of the genome and refers to the same Problem and Decoder; also, the clone gets its own UUID and has its self.parents set updated to include the individual from which it was cloned (i.e., its parent). evaluate() calls evaluate_imp() that, in turn, calls decode() to translate the genome into phenomes, or values meaningful to the Problem, and then passes those values to the Problem where it returns a fitness. This fitness is then assigned to the Individual.

The reason for the indirection using evaluate_imp() is that evaluate_imp() allows sub-classes to pass ancillary information to Problem during evaluation. For example, an Individual may have a UUID that the Problem needs in order to create a file or sub-directory using that UUID. evaluate_imp() can be over-ridden in a sub-class to pass along the UUID in addition to the decoded genome.

The @total_ordering class wrapper is used to expand the member functions __lt__() and __eq__() that are, in turn, heavily used in sorting, selection, and comparison operators.

RobustIndividual

RobustIndividual is a sub-class of Individual that over-rides evaluate() to handle exceptions thrown during evaluation. If no exceptions are thrown, then self.is_viable is set to True. If an exception happens, then the following occurs:

  • self.is_viable is set to False

  • self.fitness is set to math.nan

  • self.exception is assigned the Exception object

In turn, this class has another sub-class leap_ec.distributed.individual.DistributedIndividual.

Class API

Inheritance diagram of leap_ec.individual.RobustIndividual
class leap_ec.individual.Individual(genome, decoder=IdentityDecoder(), problem=None)

Represents a single solution to a Problem.

We represent an Individual by a genome and a fitness. Individual also maintains a reference to the Problem it will be evaluated on, and an decoder, which defines how genomes are converted into phenomes for fitness evaluation.

__init__(genome, decoder=IdentityDecoder(), problem=None)

Initialize an Individual with a given genome. A UUID is generated and assigned to self.uuid. The parents set is initialized to be empty.

We also require Individual`s to maintain a reference to the `Problem:

>>> from leap_ec.binary_rep.problems import MaxOnes
>>> from leap_ec.decoder import IdentityDecoder
>>> import numpy as np
>>> genome = np.array([0, 0, 1, 0, 1])
>>> ind = Individual(genome, decoder=IdentityDecoder(),
...                  problem=MaxOnes())
>>> ind.genome
array([0, 0, 1, 0, 1])

Fitness defaults to None:

>>> ind.fitness is None
True
Parameters
  • genome – is the genome representing the solution. This can be any arbitrary type that your mutation operators, probes, etc., know how to read and manipulate—a list, class, numpy array, etc.

  • decoder – is a function or callable that converts a genome into a phenome.

  • problem – is the Problem associated with this individual.

clone()

Create a ‘clone’ of this Individual, copying the genome, but not fitness.

The fitness of the clone is set to None. A new UUID is generated and assigned to sefl.uuid. The parents set is updated to include the UUID of the parent. A shallow copy of the parent is made, too, so that ancillary state is also copied.

A deep copy of the genome will be created, so if your Individual has a custom genome type, it’s important that it implements the __deepcopy__() method.

>>> from leap_ec.binary_rep.problems import MaxOnes
>>> from leap_ec.decoder import IdentityDecoder
>>> import numpy as np
>>> genome = np.array([0, 1, 1, 0])
>>> ind = Individual(genome, IdentityDecoder(), MaxOnes())
>>> ind_copy = ind.clone()
>>> ind_copy.genome == ind.genome
array([ True,  True,  True,  True])
>>> ind_copy.problem == ind.problem
True
>>> ind_copy.decoder == ind.decoder
True
classmethod create_population(n, initialize, decoder, problem)

A convenience method for initializing a population of the appropriate subtype.

Parameters
  • n – The size of the population to generate

  • initialize – A function f(m) that initializes a genome

  • decoder – The decoder to attach individuals to

  • problem – The problem to attach individuals to

Returns

A list of n individuals of this class’s (or subclass’s) type

decode(*args, **kwargs)

Determine the indivdual’s phenome.

This is done by passing the genome self.decoder.

The result is both returned and saved to self.phenome.

Returns

the decoded value for this individual

evaluate()

determine this individual’s fitness

This is done by outsourcing the fitness evaluation to the associated Problem object since it “knows” what is good or bad for a given phenome.

See also

ScalarProblem.worse_than

Returns

the calculated fitness

evaluate_imp()

This is the evaluate ‘implementation’ called by self.evaluate(). It’s intended to be optionally over-ridden by sub-classes to give an opportunity to pass in ancillary data to the evaluate process either by tailoring the problem interface or that of the given decoder.

classmethod evaluate_population(population)

Convenience function for bulk serial evaluation of a given population

Parameters

population – to be evaluated

Returns

evaluated population

property phenome

If the phenome has not yet been decoded, do so.

class leap_ec.individual.RobustIndividual(genome, decoder=IdentityDecoder(), problem=None)

This adds exception handling for evaluations

After evaluation self.is_viable is set to True if all went well. However, if an exception is thrown during evaluation, the following happens:

  • self.is_viable is set to False

  • self.fitness is set to math.nan

  • self.exception is assigned the exception

__init__(genome, decoder=IdentityDecoder(), problem=None)

Initialize an Individual with a given genome. A UUID is generated and assigned to self.uuid. The parents set is initialized to be empty.

We also require Individual`s to maintain a reference to the `Problem:

>>> from leap_ec.binary_rep.problems import MaxOnes
>>> from leap_ec.decoder import IdentityDecoder
>>> import numpy as np
>>> genome = np.array([0, 0, 1, 0, 1])
>>> ind = Individual(genome, decoder=IdentityDecoder(),
...                  problem=MaxOnes())
>>> ind.genome
array([0, 0, 1, 0, 1])

Fitness defaults to None:

>>> ind.fitness is None
True
Parameters
  • genome – is the genome representing the solution. This can be any arbitrary type that your mutation operators, probes, etc., know how to read and manipulate—a list, class, numpy array, etc.

  • decoder – is a function or callable that converts a genome into a phenome.

  • problem – is the Problem associated with this individual.

evaluate()

determine this individual’s fitness

Note that if an exception is thrown during evaluation, the fitness is set to NaN and self.is_viable to False; also, the returned exception is assigned to self.exception for possible later inspection. If the individual was successfully evaluated, self.is_viable is set to true. NaN fitness values will figure into comparing individuals in that NaN will always be considered worse than non-NaN fitness values.

Returns

the calculated fitness

class leap_ec.individual.WholeEvaluatedIndividual(genome, decoder=IdentityDecoder(), problem=None)

An Individual that, when evaluated, passes its whole self to the evaluation function, rather than just its phenome.

In most applications, fitness evaluation requires only phenome information, so that is all that we pass from the Individual to the Problem. This is important, because during distributed evaluation, we want to pass as little information as possible across nodes.

WholeEvaluatedIndividual is used for special cases where fitness evaluation needs access to more information about an individual than its phenome. This is strange in most cases and should be avoided, but can make certain algorithms more elegant (ex. it’s helpful when interpreting cooperative coevolution as an island model).

This can dramatically slow down distributed evaluation (i.e. with dask) in some applications because the entire individual will be sent over a TCP/IP connection instead of just the phenome, so use with caution.

__init__(genome, decoder=IdentityDecoder(), problem=None)

Initialize an Individual with a given genome. A UUID is generated and assigned to self.uuid. The parents set is initialized to be empty.

We also require Individual`s to maintain a reference to the `Problem:

>>> from leap_ec.binary_rep.problems import MaxOnes
>>> from leap_ec.decoder import IdentityDecoder
>>> import numpy as np
>>> genome = np.array([0, 0, 1, 0, 1])
>>> ind = Individual(genome, decoder=IdentityDecoder(),
...                  problem=MaxOnes())
>>> ind.genome
array([0, 0, 1, 0, 1])

Fitness defaults to None:

>>> ind.fitness is None
True
Parameters
  • genome – is the genome representing the solution. This can be any arbitrary type that your mutation operators, probes, etc., know how to read and manipulate—a list, class, numpy array, etc.

  • decoder – is a function or callable that converts a genome into a phenome.

  • problem – is the Problem associated with this individual.

evaluate_imp()

This is the evaluate ‘implementation’ called by self.evaluate(). It’s intended to be optionally over-ridden by sub-classes to give an opportunity to pass in ancillary data to the evaluate process either by tailoring the problem interface or that of the given decoder.