pygx.algo.evolution.recombinators¶
Common recombinators for evolutionary algorithms.
recombinators
¶
Common recombinators for evolutionary algorithms.
Types of recombinators
This file implements 3 types of recombinators: point-wise, segment-wise and permutation.
Point-wise recombinators
A point-wise recombinator works for an arbitrary number of parents, it
crossovers the parents' DNA into offspring's DNA point-by-point. For example,
Uniform takes a random parent's decisions per decision point, Sample samples
one of the parents' decisions with probabilities computed from a weighting
function provided by the user, and Average works for float decision points
by averaging parents' values into the child's. When dealing with categorical
decision points (e.g. pg.oneof, pg.manyof), Sample can be very
sample-efficient when the decision points are orthogonal to each other.
Segment-wise recombinators A segment-wise recombinator works on 2 parents, it cuts the parents' DNA into multiple segments, and chooses alternating segments from the parents. For example, if the i'th segment of one parent is taken as the i'th segment of a child, the (i + 1)'th segment of the child will be taken from another parent. Each recombination produces 2 children which start with a segment from different parents. For example:
parent 1: 1 2 | 3 4 5 6 | 7 8 9
parent 2: 10 20 | 30 40 50 60 | 70 80 90
child 1: 1 2 | 30 40 50 60 | 7 8 9
child 2: 10 20 | 3 4 5 6 | 70 80 90
Well-known segment-wise recombinators are single-point crossover (SPX), two-point crossover (TPX), K-point crossover and more generally, the segmented crossover. While each of the former three chooses a fixed number of cutting points randomly, the segmented crossover allows the user to specify a function to produce the cutting points, which can be done dynamically based on the global state and step. Also the customized cutting point can be effective when the user knows how the search space should be partitioned based on the application.
Permutations
A permutation recombinator works on 2 parents by permutating the order of the subchoices of multi-choice decision points, which can be useful in applications in which the order of choices matters (e.g. the traveling salesman problem). Well-known permutation crossovers are partially mapped crossover (PMX), order crossover (OX) and cycle crossover (CX).
Which recombinators to use?
Semantics on hyper primitives
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
| | | # | | | manyof |
| | | parents | floatv | oneof | sorted Y | sorted N |
| | | | | | distinct | distinct |
| | | | | | Y | N | Y | N |
+=============+===========+=========+========+=======+=====+=====+=====+=====+
|Point-wise | Uniform | > 0 | X | X | X | X | X | X |
+ +-----------+---------+--------+-------+-----+-----+-----+-----+
| | Sample | > 0 | X | X | X | X | X | X |
+ +-----------+---------+--------+-------+-----+-----+-----+-----+
| | Average | > 0 | X | | | | | |
+ +-----------+---------+--------+-------+-----+-----+-----+-----+
| | W-Average | > 0 | X | | | | | |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
|Segment-wise | KPoint | 2 | X | X | X (treated as a | X |
| | | | | | single decision | |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
| | Segmented | 2 | X | X | point) | X |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
|Permutation | PMX | 2 | | | | | X | |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
| | Order | 2 | | | | | X | |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
| | Cycle | 2 | | | | | X | |
+-------------+-----------+---------+--------+-------+-----+-----+-----+-----+
A blank cell means the recombinator works as a no-op on the hyper primitive.
Disruptiveness of recombinations
A recombinator is more disruptive if it produces children that have more
differences from the parents. In that regard, Uniform is more disruptive than
Sample when a fitness-based weighting function is used. KPoint with a larger
K is more disruptive than with a smaller K.
The more disruptive an operation is, the more diversity the operation produces.
Historical studies show that with small populations, more disruptive
recombination such as Uniform or K-Point (k >> 2) may yield better results
because they help overcome the limited information capacity of smaller
populations and the tendency for more homogeneity. With larger populations, less
disruptive recombinations like 2-point are more likely to work better.
(Reference: Holland, John H. (1975). Adaptation in Natural and Artificial
Systems, The University of Michigan Press.)
When we need faster convergence, Sample with a fitness-based weighting
function will be very effective, which can be used to implement a point-wise
greedy strategy.
On recombination for float values
Add an Average or WeightedAverage when you have float decision points in
the search space (created by pg.floatv). They are no-ops when there are no
float decision points in the space.
PointWise
¶
Bases: Recombinator
Base class for point-wise recombinators.
A point-wise recombinator operates on decision points (abbr. points) from the search space one by one. It crossovers the values from the parents into the offspring's decision on each target point. For those points that are not targeted, the decisions from the parents will be copied to the offsprings. Therefore, N parents can result in N children when at least 1 decision point is not applicable for crossover; otherwise 1 child will be produced.
Example (parent DNAs must carry a DNASpec, e.g. via use_spec):
space = pg.Dict(x=pg.oneof(range(5)), y=pg.floatv(0.0, 1.0))
spec = pg.dna_spec(space)
parents = [pg.DNA([0, 0.25]), pg.DNA([2, 0.75])]
for p in parents:
p.use_spec(spec)
# The float decision is averaged; the choice decision is not applicable
# for `Average`, so each parent contributes a child that keeps its own
# choice decision.
children = pg.algo.evolution.recombinators.Average()(parents)
assert sorted(d.to_numbers() for d in children) == [[0, 0.5], [2, 0.5]]
Targeted decision points are points that are applicable for current
recombinator according to its semantics, as well as passing the where
statement if it's specified. The PointWise base class depends on the
applicable_decision_points method to select all applicable points from the
search space. If not overridden by subclasses, the method returns all decision
points in the search space, with multi-choice folded into a single decision
point. When the where argument is provided by the user, it further filters
out unwanted decision points, which is useful when we want to limit the range
of points for crossover.
Example:
space = pg.Dict(
x=pg.oneof(range(5)),
y=pg.oneof(range(5), hints='excluded'))
spec = pg.dna_spec(space)
parents = [pg.DNA([0, 1]), pg.DNA([2, 3])]
for p in parents:
p.use_spec(spec)
# Only 'x' takes part in the crossover: one of the parents' x-decisions
# is chosen for all children, while each child keeps its own y-decision.
# The children will be either [DNA([0, 1]), DNA([0, 3])]
# or [DNA([2, 1]), DNA([2, 3])].
children = pg.algo.evolution.recombinators.Uniform(
where=lambda xs: [x for x in xs if x.hints != 'excluded'])(parents)
applicable_decision_points
¶
applicable_decision_points(
dna_spec: DNASpec, global_state: AttributeDict, step: int
) -> list[DecisionPoint]
Returns applicable decision points for this recombinator.
The default behavior is to return all decision points in the search space, with multi-choice subchoices folded into a single decision point. Subclasses can override this method to select applicable points according to their semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dna_spec
|
DNASpec
|
The root DNASpec. |
required |
global_state
|
AttributeDict
|
An optional keyword argument as the global state. Subclass can omit. |
required |
step
|
int
|
An optional keyword argument as current step. Subclass can omit. |
required |
Returns:
| Type | Description |
|---|---|
list[DecisionPoint]
|
A list of targeted decision points for point-wise recombination, which
will be further filtered by the |
Source code in pygx/algo/evolution/_recombinators.py
merge
abstractmethod
¶
merge(
decision_point: DecisionPoint,
parent_decisions: list[int | list[int] | float | None],
global_state: AttributeDict,
step: int,
) -> int | list[int] | float
Implementation of point-wise decision making.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
decision_point
|
DecisionPoint
|
Decision point for recombination. |
required |
parent_decisions
|
list[int | list[int] | float | None]
|
A list of parent's decisions. Each item should be an int as an active single-choice decision, a list of int as active multi- choice decisions, a float as an active float decision, or None for inactive decision point (whose parent space is not chosen). |
required |
global_state
|
AttributeDict
|
An optional keyword argument as the global state. Subclass can omit. |
required |
step
|
int
|
An optional keyword argument as the current step. Subclass can omit. |
required |
Returns:
| Type | Description |
|---|---|
int | list[int] | float
|
An int, list of int or float as the decision made for the decision point. |
Source code in pygx/algo/evolution/_recombinators.py
Uniform
¶
Bases: PointWise
Uniform crossover (UX) with equal probability.
The uniform recombinator picks a random parent's decision with equal
probability at each applicable decision point. The merged decisions are
applied to every parent's DNA to produce the children (duplicates
removed): when every decision point takes part, a single child is
produced; points excluded by where or inapplicable ones keep each
parent's own decision, yielding up to one child per parent.
Reference:
G. Syswerda. 1989. Uniform crossover in genetic algorithms. In Proceedings of the 3rd International Conference on Genetic Algorithms. Morgan Kaufman, 2–9.
https://ci.nii.ac.jp/naid/10000012509/
Source code in pygx/algo/evolution/_recombinators.py
Sample
¶
Sample(weights: Operation | Callable[..., list[float]], **kwargs)
Bases: PointWise
Point-wise crossover that samples values from parents by weights.
The Sample recombinator works similarly as the Uniform recombinator,
except that it takes a user function to compute the weights, based on which
each parent's decision will be sampled. Uniform can be represented as
Sample(lambda xs: [1] * len(xs)), whose weights function generates the
sampling weights in uniform distribution.
Source code in pygx/algo/evolution/_recombinators.py
Numeric
¶
Bases: PointWise
Base class for numerical recombinators.
A numeric recombinator operates on pg.geno.Float decision points, by
recombining parents' values into child values.
Decisions of pg.geno.Choices will be copied over from N parents to N
children on a one-to-one basis. If there are no choice decision points
in the search space and the where clause does not filter any float
points out, there will be a single child produced.
Example (parent DNAs must carry a DNASpec, e.g. via use_spec):
space = pg.Dict(x=pg.floatv(0.0, 1.0), y=pg.floatv(0.0, 1.0))
spec = pg.dna_spec(space)
parents = [pg.DNA([0.25, 0.5]), pg.DNA([0.75, 0.5])]
for p in parents:
p.use_spec(spec)
# All decision points are floats, so a single child is produced.
children = pg.algo.evolution.recombinators.Average()(parents)
assert [d.to_numbers() for d in children] == [[0.5, 0.5]]
Average
¶
Bases: Numeric
Average crossover.
Average crossover performs point-wise average of the parents' decisions on float decision points. We include this recombinator to help float decisions converge.
References: https://link.springer.com/content/pdf/10.1007/s00500-006-0049-7.pdf
WeightedAverage
¶
WeightedAverage(weights: Operation | Callable[..., list[float]], **kwargs)
Bases: Numeric
Weighted-average crossover.
Similar as the Average crossover, a weighted-average crossover operates on
all float decision points and carries over other parts of the chromosome from
each parent to a child. Thus it produces the same number of children as the
parents.
It uses formula cv[i] = sum(pw[j] * pv[j][i]) / sum(pw[j]) to compute the
values for all or selected float points. cv[i] is the value of the i-th
float point of the child DNA. pw[j] is the weight computed from the j-th
parent, and pv[j][i] is the j-th parent's value on the i-th float point.
References: https://link.springer.com/content/pdf/10.1007/s00500-006-0049-7.pdf
Source code in pygx/algo/evolution/_recombinators.py
SegmentWise
¶
Bases: Recombinator
Base for recombinators that interleavingly take segments from parents' DNA.
A segment-wise recombinator operates on 2 parents. It cuts both parents' DNA at K positions, which forms K + 1 segments. Then it takes the K + 1 segments from both parents in an interleaving manner. For example, a two-point crossover that takes places at cutting points 2 and 5 will result in 2 children as follows:
Parent 1: [1, 2, | 3, 4, 5, | 6, 7]
Parent 2: [10, 20, | 30, 40, 50, | 60, 70]
| |
Child 1: [1, 2, | 30, 40, 50, | 6, 7]
Child 2: [10, 20, | 3, 4, 5, | 60, 70]
One important aspect of recombination is that the children produced from recombination should be valid. Decisions can be moved around when and only when they are independent from other decisions. In PyGX, there are multiple interdependent DNA sequences: 1) conditional space, whose DNA is represented by tuples (e.g. (1, 2) means inner-choice 2 is made under outer-choice 1). 2) subchoices of a multi-choice which has sorted and/or distinct constraint. Each interdependent DNA group, like a sub-tree for conditional space, or a list of subchoices for constrained multi-choice will be treated as a single position when the DNA is being cut into segments. For example, DNA([0, 1, [1, 2, 0, 3]]) has length 3 if the multi-choice is sorted or distinct, otherwise its length is 6.
The variations of segment-wise recombination differ from each other in how
they choose the cutting strategies. KPoint randomly chooses K cutting
points in the DNA sequence; a KPoint whose K reaches the sequence length
cuts the DNA at every position, forming a list of length-1 segments.
Segmented lets the user customize cutting strategies based on a list of
applicable decision points.
cutting_indices
abstractmethod
¶
cutting_indices(
independent_decision_points: list[DecisionPoint],
global_state: AttributeDict,
step: int,
) -> list[int]
Implementation of getting the indices of the cutting points.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
independent_decision_points
|
list[DecisionPoint]
|
A list of independent decision points. |
required |
global_state
|
AttributeDict
|
An optional keyword argument as the global state. Subclass can omit. |
required |
step
|
int
|
An optional keyword argument as the curent step. Subclass can omit. |
required |
Returns:
| Type | Description |
|---|---|
list[int]
|
A list of integers as the cutting points. |
Source code in pygx/algo/evolution/_recombinators.py
KPoint
¶
Bases: SegmentWise
K-point crossover.
K-point crossover is one of the basic crossovers in evolutionary algorithms. It cuts both parents' DNA at K positions, which forms K + 1 segments. Then it takes the K + 1 segments from both parents in an interleaving manner. For example, a two-point crossover that takes places at cutting points 2 and 5 will result in 2 children as follows:
Parent 1: [1, 2, | 3, 4, 5, | 6, 7]
Parent 2: [10, 20, | 30, 40, 50, | 60, 70]
| |
Child 1: [1, 2, | 30, 40, 50, | 6, 7]
Child 2: [10, 20, | 3, 4, 5, | 60, 70]
When K=1, we get a single-point crossover. Similarly, when K=2, we get a two-point crossover.
When K equals or is greater than the length of DNA sequence, we get an alternating-position (APX) crossover.
Reference: https://mitpress.mit.edu/books/introduction-genetic-algorithms https://dl.acm.org/doi/abs/10.5555/93126.93134 https://www.intechopen.com/chapters/335
Source code in pygx/algo/evolution/_recombinators.py
cutting_indices
¶
cutting_indices(
independent_decision_points: list[DecisionPoint],
global_state: AttributeDict,
step: int,
) -> list[int]
Returns the indices of cutting points for a list decision points.
Source code in pygx/algo/evolution/_recombinators.py
Segmented
¶
Segmented(cutting_points: Callable[[list[DecisionPoint]], list[int]], **kwargs)
Bases: SegmentWise
Segmented crossover.
Instead of using a predefined cutting strategy, the segmented recombinator allows the user to customize how the cutting points should be chosen, which can be used to implement fixed cutting point strategies (e.g nodes + edges) as well as decision points' based cutting point strategies.
Example:
# A fixed single-point crossover at the middle of the chromosome:
pg.algo.evolution.recombinators.Segmented(lambda xs: [len(xs) // 2])
# A multi-point crossover that cuts after decision points carrying a
# 'block_end' hint.
pg.algo.evolution.recombinators.Segmented(
lambda xs: [i + 1 for i, x in enumerate(xs)
if x.hints == 'block_end'])
Source code in pygx/algo/evolution/_recombinators.py
cutting_indices
¶
cutting_indices(
independent_decision_points: list[DecisionPoint],
global_state: AttributeDict,
step: int,
) -> list[int]
Returns the indices of cutting points for a list decision points.
Source code in pygx/algo/evolution/_recombinators.py
Permutation
¶
Bases: Recombinator
Base for recombinators that permutate the multi-choice subchoices.
A permutation recombinator operates on target permutation decision points.
A permutation point is a multi-choice decision point that has distinct but
unsorted subchoices, also with a num_choices equals to the number of its
candidates. A permutation decision point can be created via pg.permutate
or pg.manyof(len(candidates), candidates).
Example:
contains 1 permutation decision point, as the first pg.manyof has only
2 subchoices while the number of candidates is 5.
A permutation point is targeted if it's included in the return value of the
where function when it's specified. By default, the where filter
returns 1 random point among all the permutation points in the search
space. When users specify the where argument to select more
than 1 permutation points, the original DNA from each parent will be merged
with each permutation proposal to generate a child. There will be a multiply
effect between the number of parents and the number of proposals for each
permutation, but the proposals generated from different permutation points
will not be multiplied. That being said, if there are N parents, M proposals
per crossover and K crossovers (K=1 by default), the max number of children
will be N * M * K. Most permutation recombinators (PMX, OX, CX, etc.)
operate on 2 parents and produce 2 proposals in a single crossover,
resulting in N * M * K = 2 * 2 * 1 = 4 children.
For example, if DNA([0, 1, [1, 2, 3, 0]]) and DNA([2, 3, [0, 1, 2, 3]]) are recombinated on [1, 2, 3, 0] and [0, 1, 2, 3], which outputs [0, 2, 3, 1] and [3, 1, 2, 0] as recombined results. Then there will be 4 DNA in the output:
DNA([0, 1, [0, 2, 3, 1]])
DNA([0, 1, [3, 1, 2, 0]])
DNA([2, 3, [0, 2, 3, 1]])
DNA([2, 3, [3, 1, 2, 0]])
It's worth noting that though common permutation operations take 2 parents,
the Permutation base class is designed to support an arbitrary number of
parents. Use the NUM_PARENTS class attribute to specify the intended
parent number if a subclass needs a fixed parent number.
Source code in pygx/algo/evolution/_recombinators.py
PartiallyMapped
¶
Bases: Permutation
Partially mapped crossover (PMX).
The partially mapped crossover (PMX) was proposed by D. Goldberg and R. Lingle, “Alleles, Loci and the Traveling Salesman Problem,” in Proceedings of the 1st International Conference on Genetic Algorithms and Their Applications, vol. 1985, pp. 154–159, Los Angeles, USA.
Reference: https://dl.acm.org/doi/10.5555/645511.657095
Source code in pygx/algo/evolution/_recombinators.py
partially_mapped_crossover
¶
Cross over and remap the rest elements at given cutting points.
Source code in pygx/algo/evolution/_recombinators.py
Order
¶
Bases: Permutation
Order crossover (OX).
The order crossover (OX) was proposed by L. Davis, “Applying adaptive algorithms to epistatic domains,” IJCAI, vol. 85, pp. 162–164, 1985.
It builds offspring by choosing a subtour of a parent and preserving the relative order of bits of the other parent.
Reference: https://dl.acm.org/doi/10.5555/1625135.1625164
Source code in pygx/algo/evolution/_recombinators.py
order_crossover
¶
Cross over and remap the rest elements at given cutting points.
Source code in pygx/algo/evolution/_recombinators.py
Cycle
¶
Bases: Permutation
Cycle crossover (CX).
The cycle crossover (CX) operator was first proposed by I. M. Oliver, D. J. d. Smith, and R. C. J. Holland, “Study of permutation crossover operators on the traveling salesman problem,” in Genetic algorithms and their applications: proceedings of the second International Conference on Genetic Algorithms: July 28-31, 1987 at the Massachusetts Institute of Technology, Cambridge, MA, USA, 1987.
Reference: https://dl.acm.org/doi/10.5555/42512.42542.
Source code in pygx/algo/evolution/_recombinators.py
cycle_crossover
¶
Cycle crossover.
Source code in pygx/algo/evolution/_recombinators.py
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2