-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
220 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
|
||
1. [Introduction](introduction.md) | ||
2. [Fitness functions](fitness-functions.md) | ||
3. [Encodings](encodings.md) | ||
4. [Algorithms](algorithms.md) | ||
5. **Genetic operators** | ||
6. [Stop conditions](stop-conditions.md) | ||
7. [Metrics](metrics.md) | ||
8. [Miscellaneous](miscellaneous.md) | ||
|
||
------------------------------------------------------------------------------------------------ | ||
|
||
# Genetic operators | ||
|
||
The genetic operators are used to create new candidate solutions from the existing | ||
ones in the population, thus providing the basic search mechanism of the genetic | ||
algorithms. The 2 main operators are the crossover and mutation, but the library | ||
also allows for specifying a repair function. | ||
|
||
The selection method is considered to be a part of the `Algorithm` in the library, | ||
so it will not be discussed here. | ||
|
||
The library contains implementations of several crossover and mutation methods | ||
that can be used. These can be found in the `gapp::crossover` and | ||
`gapp::mutation` namespaces respectively. | ||
|
||
As the genetic operators operate on candidate solutions, their | ||
implementations depend on the encoding type used for the GA. A given crossover or | ||
mutation method can only be used with the encoding types it is implemented for. | ||
Because of this, the implemented crossover and mutation methods are further broken | ||
up into multiple namespaces based on the encoding type they can be used with. | ||
For example, the crossover operators are in the following namespaces: | ||
|
||
- `crossover::binary` | ||
- `crossover::real` | ||
- `crossover::perm` | ||
- `crossover::integer` | ||
|
||
Crossover methods in the `binary` namespace can only be used for the `BinaryGA`, | ||
methods in the `real` namespace can only be used for the `RCGA`, and so on. | ||
The mutation methods are organized similarly. | ||
|
||
The library doesn't provide any repair functions since their use in the GAs | ||
is optional. These always have to be defined by the user when they are used. | ||
|
||
## Crossover | ||
|
||
The crossover operator is responsible for generating new solutions from existing | ||
ones. The operator takes 2 solutions selected from the population, and performs | ||
some operation on them with a given probability to generate 2 new solutions. | ||
When the operation is not performed, it returns copies of the parent solutions. | ||
|
||
The crossover operator used by the GA can be set either in the constructor or by | ||
using the `crossover_method` method: | ||
|
||
```cpp | ||
PermutationGA GA; | ||
GA.crossover_method(crossover::perm::Edge{}); | ||
``` | ||
|
||
The probability of performing the crossover operation is a general parameter | ||
of the crossovers and it can be set for all of the crossover operators either | ||
in their constructors or by using the `crossover_rate` method: | ||
|
||
```cpp | ||
PermutationGA GA; | ||
GA.crossover_method(crossover::perm::Edge{ /* crossover_rate = */ 0.8 }); | ||
``` | ||
The GA classes also provide a `crossover_rate` method that can be used to set | ||
the crossover probability for the current crossover operator used by the GA: | ||
```cpp | ||
PermutationGA GA; | ||
GA.crossover_method(crossover::perm::Edge{}); | ||
GA.crossover_rate(0.8); | ||
``` | ||
|
||
Some crossover operators may also have additional parameters that are specific | ||
to the given operator. | ||
|
||
## Mutation | ||
|
||
The mutation operator is applied to each of the solutions generated by the | ||
crossovers in order to promote diversity in the population. This help the GA | ||
with exploring more of the search space and avoiding convergence to local | ||
optima. | ||
|
||
The mutation operator used by the GAs can be set similar to the crossover operators, | ||
either in the constructor or by using the `mutation_method` method: | ||
|
||
```cpp | ||
RCGA GA; | ||
GA.mutation_method(mutation::real::NonUniform{}); | ||
``` | ||
|
||
Similar to the crossovers, the mutation operators also all have a | ||
mutation probability parameter, but how this probability is interpreted | ||
(either on a per-gene or per-solution basis) depends on the specific | ||
operator. | ||
|
||
The mutation probability can be set similar to the crossover probability, either | ||
in the constructor of the mutation operator, or by using the `mutation_rate` | ||
method: | ||
|
||
```cpp | ||
RCGA GA; | ||
GA.mutation_method(mutation::real::NonUniform{ /* mutation_rate = */ 0.1 }); | ||
``` | ||
The GA classes also provide a `mutation_rate` method that can be used to set | ||
the mutation probability for the current mutation operator of the GA: | ||
```cpp | ||
RCGA GA; | ||
GA.mutation_method(mutation::real::NonUniform{}); | ||
GA.mutation_rate(0.1); | ||
``` | ||
|
||
Similar to the crossovers, some mutation operators may have additional parameters | ||
that are specific to the particular operator. | ||
|
||
## Repair | ||
|
||
The repair function is an additional operator that will be applied to each | ||
solution after the mutations. Using a repair function is optional, and they are | ||
not used in the GAs by default. | ||
|
||
Repair functions can be specified using the `repair_function` method of the GAs: | ||
|
||
```cpp | ||
ga.repair_function([](const GA<RealGene>&, const Chromosome<RealGene>& chrom) | ||
{ | ||
auto new_chrom = chrom; | ||
// do something with new_chrom ... | ||
return new_chrom; | ||
}); | ||
``` | ||
|
||
If a repair function has been set previously, it can be cleared by passing | ||
a nullptr to the setter: | ||
|
||
```cpp | ||
GA.repair_function(nullptr); | ||
``` | ||
|
||
## Custom genetic operators (crossover and mutation) | ||
|
||
In addition to the operators already implemented in the library, | ||
user defined crossover and mutation operators can also be used in the GAs. | ||
|
||
The simplest way to do this is to use a lambda function: | ||
|
||
```cpp | ||
RCGA ga; | ||
|
||
ga.crossover_method([](const GA<RealGene>&, const Candidate<RealGene>& parent1, const Candidate<RealGene>& parent2) | ||
{ | ||
auto child1 = parent1; | ||
auto child2 = parent2; | ||
|
||
// perform the crossover ... | ||
|
||
return CandidatePair<RealGene>{ std::move(child1), std::move(child2) }; | ||
}); | ||
|
||
ga.mutation_method([](const GA<RealGene>& ga, const Candidate<RealGene>& sol, Chromosome<RealGene>& chrom) | ||
{ | ||
for (RealGene& gene : chrom) | ||
{ | ||
if (rng::randomReal() < ga.mutation_rate()) | ||
{ | ||
// modify the gene ... | ||
} | ||
} | ||
}); | ||
``` | ||
|
||
Alternatively, crossover and mutation operators can also be implemented as | ||
classes derived from `crossover::Crossover<GeneType>` and | ||
`mutation::Mutation<GeneType>` respectively. Crossovers must implement the | ||
`crossover` method, while mutations must implement the `mutate` method: | ||
|
||
```cpp | ||
class MyCrossover : public crossover::Crossover<RealGene> | ||
{ | ||
public: | ||
using Crossover::Crossover; | ||
|
||
CandidatePair<RealGene> crossover(const GA<RealGene>& ga, const Candidate<RealGene>& parent1, const Candidate<RealGene>& parent2) const override | ||
{ | ||
// perform the crossover ... | ||
} | ||
}; | ||
``` | ||
|
||
```cpp | ||
class MyMutation : public mutation::Mutation<RealGene> | ||
{ | ||
public: | ||
using Mutation::Mutation; | ||
|
||
void mutate(const GA<RealGene>& ga, const Candidate<RealGene>& candidate, Chromosome<RealGene>& chromosome) const override | ||
{ | ||
// perform the mutation on chromosome ... | ||
} | ||
}; | ||
``` | ||
|
||
There are a few things that should be kept in mind for the implementations | ||
of these operators regardless of how they are defined: | ||
|
||
- The crossover implementation shouldn't take the crossover rate into account. | ||
This is done elsewhere. | ||
- The mutation implementation must take the mutation rate into account, as how | ||
the mutation rate is interpreted depends on the specific mutation method. | ||
- The mutation modifies the `chrom` parameter, and does not return anything. | ||
- The implementations should be thread-safe. | ||
|
||
------------------------------------------------------------------------------------------------ |