Individuals
This section covers the class Individual in more detail.
Class Summary
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
- 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.