pygx.algo.evolution¶
Compositional evolutionary algorithms.
evolution
¶
Compositional evolutionary algorithms.
pygx.algo.evolution builds search algorithms from small, composable
operations: selectors choose individuals, mutators perturb them,
recombinators combine them, and pipelines connect them with the >> operator.
The top-level Evolution class wires reproduction, population
initialization, and population updates into a single DNAGenerator that
plugs into pg.sample,
pg.iter, or any other consumer.
Common algorithm constructors are exposed at this level:
Each takes a Mutator (defaulting to mutators.Uniform()) and returns a
ready-to-use Evolution object.
Choice
¶
Choice(
ops: list[tuple[Operation, float | Callable[[int], float]]],
limit: int | Callable[[int], int] | None = None,
**kwargs
)
Bases: Operation
Applying a list of operations with probabilities in a pipeline.
Example:
pg.algo.evolution.Choice([
(pg.algo.evolution.mutators.Uniform() ** 3, 0.5),
(pg.algo.evolution.mutators.Swap(), 0.4)
], limit=1)
The code above applies 3 pipelined uniform mutations with probability
0.5, and a swap of two sibling DNA branches with probability 0.4. Due to
limit=1, at most 1 of the two operations is applied.
Source code in pygx/algo/evolution/_base.py
Concatenation
¶
Concatenation(ops: list[Operation], **kwargs)
Bases: Operation
Concatenating the operations' outputs based on the same input.
A concatenate operation passes the input to all its child operations, and
concatenate their outputs. Different from Union, the duplicated items will
be kept.
Example:
pg.algo.evolution.Concatenation([
pg.algo.evolution.selectors.Top(10),
pg.algo.evolution.selectors.First(10)
])
which is equivalent to:
The code above returns top 10 DNA concatenated with the first 10 DNAs. The result may contain duplicated items.
Source code in pygx/algo/evolution/_base.py
Conditional
¶
Conditional(
predicate: Callable[..., bool],
true_op: Operation | None = None,
false_op: Operation | None = None,
**kwargs
)
Bases: Operation
Conditional operation.
A conditional operation allows sub-operations to be applied only when a condition is met.
Example:
which is equivalent to:
Source code in pygx/algo/evolution/_base.py
Difference
¶
Difference(ops: list[Operation], **kwargs)
Bases: Operation
Compute the difference of operation outputs based on the same input.
A difference operation passes the input to all its child operations, and subtracts the other operations' outputs from the first operation's output: it keeps the items of the first operation's output — in that output's order — that appear (by object identity) in no other operation's output.
Example:
pg.algo.evolution.Difference([
pg.algo.evolution.selectors.Top(10),
pg.algo.evolution.selectors.First(10)
])
which is equivalent to:
The code above returns top 10 DNA only when they are not among the first 10 elements of the input.
Source code in pygx/algo/evolution/_base.py
DNAOperation
¶
Evolution
¶
Evolution(reproduction: Operation, **kwargs)
Bases: DNAGenerator
An evolutionary algorithm based on compositional operations.
Common evolutionary algorithms can be abstracted with 3 operations:
- Select parent(s) from the population.
- Recombine the DNA from the parents into a list of child DNA.
- Mutate each child DNA.
Plus 2 population related operations:
- Initialize the population.
- Update the population when the fitness is determined for the child DNA.
All the operations above except population initialization can be described
by the Operation interface, which transforms a list of DNA into another list
of DNA. Moreover, Operation allows complex algorithms to be represented as
a composition of elementary operations. For example, Regularized Evolution
can be described as:
algo = pg.algo.evolution.Evolution(
(pg.algo.evolution.selectors.Random(10)
>> pg.algo.evolution.selectors.Top(1)
>> pg.algo.evolution.mutators.Uniform()),
population_init=(pg.geno.Random(), 50),
population_update=pg.algo.evolution.selectors.Last(50))
(Also available as the
regularized_evolution
helper.)
The algorithm can then drive a sampling loop over a search space:
search_space = pg.Dict(x=pg.oneof(range(100)), y=pg.oneof(range(100)))
best_value, best_reward = None, None
for value, feedback in pg.iter(search_space, 50, algo):
reward = value.x + value.y
if best_reward is None or reward > best_reward:
best_value, best_reward = value, reward
feedback(reward)
Source code in pygx/algo/evolution/_base.py
recover
¶
Recover states by replaying the proposal history.
Source code in pygx/algo/evolution/_base.py
Flatten
¶
GlobalStateGetter
¶
GlobalStateSetter
¶
Intersection
¶
Intersection(ops: list[Operation], **kwargs)
Bases: Operation
Intersecting operations's outputs based on the same input.
An intersect operation passes the input to all its child operations, and intersects their outputs: it keeps the items of the first operation's output — in that output's order — that also appear (by object identity) in every other operation's output.
Example:
pg.algo.evolution.Intersection([
pg.algo.evolution.selectors.Last(10),
pg.algo.evolution.selectors.Top(1),
])
which is equivalent to:
The code above returns top 1 DNA only when it's among the last 10 items of the input.
Source code in pygx/algo/evolution/_base.py
Lambda
¶
Mutator
¶
Bases: DNAOperation
Base class for mutators.
A mutator performs a mutation, i.e. a random transformation that converts a parent DNA to a child DNA. Mutations should reach the full search space through composition and should prefer local transformations individually.
mutate
¶
mutate(dna: DNA, global_state: AttributeDict, step: int = 0) -> DNA | list[DNA]
Mutates the DNA at a given step.
User should override this method or mutate_list method with optional
keyword arguments 'global_state' and 'step'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dna
|
DNA
|
DNA to mutate. |
required |
global_state
|
AttributeDict
|
An |
required |
step
|
int
|
Number of examples historically proposed, which can be used for determining a mutation schedule. |
0
|
Returns:
| Type | Description |
|---|---|
DNA | list[DNA]
|
A new DNA or a DNA list as the result of the mutation. |
Source code in pygx/algo/evolution/_base.py
mutate_list
¶
mutate_list(
dna_list: list[DNA], global_state: AttributeDict, step: int = 0
) -> list[DNA]
Mutate the DNA in the input one by one and concatenate their outputs.
User should override this method instead of mutate if mutation depends on
the list-wise information. Keyword arguments global_state and step are
optional when override.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dna_list
|
list[DNA]
|
a list of DNA to mutate. |
required |
global_state
|
AttributeDict
|
An |
required |
step
|
int
|
Number of examples historically proposed, which can be used for determining a mutation schedule. |
0
|
Returns:
| Type | Description |
|---|---|
list[DNA]
|
a list of DNA as the result of the mutation. |
Source code in pygx/algo/evolution/_base.py
Operation
¶
Bases: Object
Base class for evolutionary operations.
An evolutionary operation transforms a DNA list into another DNA list. This abstraction provides the flexibility to describe common evolutionary operations including but not limited to:
- Selection: Select M parents from a population of size N.
- Recombination: Generate M child candidates from N parents.
- Mutation: Mutate the N child candidates one by one, each can produce one or K DNA, resulting in N * K child DNA.
Operations are compositional via the following operators:
x >> y: Pipeline x and y by passing the output of x to y as input.
x + y: Concatenate the outputs of x and y, based on the same input.
Resulted list could contain duplicates.
x[m:n:p] Slice x's output from m (inclusive) to n (exclusive) with step p.
x | y: Union the outputs of x and y based on the same input.
Resulted list will not contain duplicated items.
x & y: Intersect the outputs of x and y, based on the same input.
Resulted list will not contain duplicated items.
x - y: Remove elements from x's output which also appears in y's output,
based on the same input.
x ^ y: Keep items that are either in x's output or in y's output, but
not both. Duplicated items in x's output or in y's output will
be preserved.
x * K: Repeat operation x for K times based on the same input,
concatenate their outputs.
x ** K: Pipeline operation x for K times, equivalent to `x >> .. >> x`
(`x` appears K times in the formula).
-x or ~x: Compute the inversion of x's output based on the input.
Equivalent to `Identity() - x`
x.if_true(f): Conditionally apply x if predicate f returns True.
x.if_false(f): Conditionally apply x if predicate f returns False.
Besides, the following operations are commonly used during composition:
Identity() : Returns the input DNA list.
Lambda( : Creates an Operation using lambda.
lambda x: x[1:])
Choice([ : Perform x, y based on probabilities.
(x, prob1),
(y, prob2)])
Conditional( : Perform x if input length > 5, otherwise y.
lambda x: len(x) > 5,
x, y)
UntilChange(x) : Repeat x until its output changes from the input.
ElementWise( : Perform element-wise operation and concatenate.
lambda x: sorted(x))
Flatten() : Flatten nested lists into a flat list.
GlobalStateGetter( : Fetch global state by key 'species'.
'species')
GlobalStateSetter( : Set global state by key 'species' from input.
'species')
The operand in compositional operations also supports plain callable
objects that take the input list and return the output list, with
optional keyword arguments global_state and step. For example:
input_element_type
property
¶
Returns the input element type. Subclasses can override.
output_element_type
property
¶
Returns the output element type. Subclasses can override.
operation_method
property
¶
Returns a member method as operation.
_on_input
¶
call
¶
call(
inputs: list[Any], global_state: AttributeDict, step: int = 0
) -> list[Any]
Subclasses should override this method.
The global_state and step are optional for the subclasses' call
signature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
list[Any]
|
A list of values as inputs. |
required |
global_state
|
AttributeDict
|
An |
required |
step
|
int
|
Number of examples historically proposed, which can be used for determining a cross over schedule. |
0
|
Returns:
| Type | Description |
|---|---|
list[Any]
|
A list of values as output of current operation. |
Source code in pygx/algo/evolution/_base.py
with_prob
¶
if_true
¶
if_true(predicate: Callable[..., bool]) -> Conditional
Conditionally applies current operation when predicate returns True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[..., bool]
|
The predicate that takes the outputs from the previous
operation as input, with optional keyword arguments |
required |
Returns:
| Type | Description |
|---|---|
Conditional
|
A conditional operation. |
Source code in pygx/algo/evolution/_base.py
if_false
¶
if_false(predicate: Callable[..., bool]) -> Conditional
Conditionally applies current operation when predicate returns False.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[..., bool]
|
The predicate that takes the outputs from the previous
operation as input, with optional keyword arguments |
required |
Returns:
| Type | Description |
|---|---|
Conditional
|
A conditional operation. |
Source code in pygx/algo/evolution/_base.py
for_each
¶
until_change
¶
flatten
¶
global_state
¶
as_global_state
¶
set_global_state
¶
Pipeline
¶
Pipeline(ops: list[Operation] | Any, **kwargs)
Bases: Operation
Chaining operations into a pipeline.
A pipeline operation chains multiple operations by passing the output of previous operation to the next operations.
Example:
pg.algo.evolution.Pipeline([
pg.algo.evolution.selectors.Random(10),
pg.algo.evolution.selectors.Top(1),
pg.algo.evolution.mutators.Uniform()
])
which is equivalent to:
(pg.algo.evolution.selectors.Random(10)
>> pg.algo.evolution.selectors.Top(1)
>> pg.algo.evolution.mutators.Uniform())
The code above returns a uniformly mutated DNA from the winner of 10 randomly selected DNA from the input.
Source code in pygx/algo/evolution/_base.py
Power
¶
Power(op: Operation, k: int | Callable[[int], int], **kwargs)
Bases: Operation
Pipeline a repeated operation multiple times.
Power(x, k) (or x ** k) is equivalent to x >> x >> ... >> x (k items).
Example:
which is equivalent to:
The code above creates a DNA with 3 mutated positions for each DNA in the input.
Source code in pygx/algo/evolution/_base.py
Recombinator
¶
Bases: DNAOperation
Base class for recombinators.
recombine
abstractmethod
¶
recombine(
parents: list[DNA], global_state: AttributeDict, step: int
) -> list[DNA]
Generate a list of child DNA based on the list of parents given.
User should override this method with optional keyword arguments 'global_state' and 'step'.
The parents DNA contains a metadata field 'generation', which is the generation of the parent DNA. If the Recombinator does not assign this field for the new child DNA, the child DNA will have the maximum generation from the parents plus 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parents
|
list[DNA]
|
Parent trials. |
required |
global_state
|
AttributeDict
|
An |
required |
step
|
int
|
Number of examples historically proposed, which can be used for determining a cross over schedule. |
required |
Returns:
| Type | Description |
|---|---|
list[DNA]
|
A list of generated child DNA. |
Source code in pygx/algo/evolution/_base.py
_on_input
¶
Override to check number of parents.
Source code in pygx/algo/evolution/_base.py
Repeat
¶
Repeat(op: Operation, k: int | Callable[[int], int], **kwargs)
Bases: Operation
Repeat an operation multiple times based on the same input.
A repeat operation passes the input to the child operation for K times, and concatenate their outputs.
Example:
which is equivalent to:
The code above creates 3 mutated DNA for each DNA from the input.
It's important to differentiate * and **. For instance,
Uniform() ** 3 creates one DNA (mutated on 3 positions) for each DNA
from the input.
Source code in pygx/algo/evolution/_base.py
Selector
¶
Bases: Operation
Base class for selectors.
select
abstractmethod
¶
select(inputs: list[Any], global_state: AttributeDict, step: int) -> list[Any]
Select a list of outputs from the inputs.
A selector has two use cases:
-
Used as parents selector, which selects individuals from the population as parents for recombination. It will be called before the recombination step within
Evolution'sproposemethod (inherited frompg.geno.DNAGenerator). -
Used as a population updater, which selects individuals from previous population as a new population. It will be called everytime the population is updated, triggered by
Evolution'sfeedbackmethod.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
inputs
|
list[Any]
|
a list of objects as input. |
required |
global_state
|
AttributeDict
|
An |
required |
step
|
int
|
Number of examples historically proposed, which can be used for determining a cross over schedule. |
required |
Source code in pygx/algo/evolution/_base.py
Slice
¶
Slice(
op: Operation, index: int | slice | Callable[[int], int | slice], **kwargs
)
Bases: Operation
Slice the operation's output.
A slice operation passes the input to the child operation and slices its output based on the index.
Example:
which is equivalent to:
Source code in pygx/algo/evolution/_base.py
SymmetricDifference
¶
SymmetricDifference(ops: list[Operation], **kwargs)
Bases: Operation
Compute the symmetric difference of operation outputs based on the input.
A symmetric difference operation passes the input to all its child operations, and keeps the items that appear (by object identity) in exactly one operation's output, in the order the operations emit them.
Example:
pg.algo.evolution.SymmetricDifference([
pg.algo.evolution.selectors.Top(10),
pg.algo.evolution.selectors.First(10)
])
which is equivalent to:
The code above returns items that are either among the top 10 or among the first 10 but not both.
Source code in pygx/algo/evolution/_base.py
Union
¶
Union(ops: list[Operation], **kwargs)
Bases: Operation
Unioning operations' outputs based on the same input.
A union operation passes the input to all its child operations, and unions their outputs. Items appear in the order the child operations emit them (child operations run in their declaration order), with duplicates — determined by object identity — removed.
Example:
pg.algo.evolution.Union([
pg.algo.evolution.selectors.Random(10),
pg.algo.evolution.selectors.Top(1),
])
which is equivalent to:
The code above returns 10 random DNAs and the top 1 DNA. If the top DNA is among the random ones, only one occurrence is returned.
Source code in pygx/algo/evolution/_base.py
UntilChange
¶
UntilChange(
op: Operation,
max_attempts: int | Callable[[int], int] | None = None,
**kwargs
)
Bases: Operation
Repeat an operation until its output is different from the input.
Source code in pygx/algo/evolution/_base.py
Scalar
¶
get_feedback_sequence_number
¶
get_fitness
¶
get_generation_id
¶
is_initial_population
¶
set_fitness
¶
hill_climb
¶
hill_climb(
mutator: Mutator = Uniform(),
batch_size: int = 1,
init_population_size: int = 1,
seed: int | None = None,
) -> Evolution
Hill-Climbing algorithm, with an extra batched setting.
Batched setting was shown to be effective in https://arxiv.org/pdf/1911.06317.pdf and https://arxiv.org/pdf/2003.01239.pdf, especially in noisy objective settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mutator
|
Mutator
|
Mutator to use. |
Uniform()
|
batch_size
|
int
|
Number of mutations of the current best. |
1
|
init_population_size
|
int
|
Initial population size (randomly generated). |
1
|
seed
|
int | None
|
Random seed. |
None
|
Returns:
| Type | Description |
|---|---|
Evolution
|
An |
Source code in pygx/algo/evolution/_hill_climb.py
neat
¶
neat(
mutator: Mutator = Uniform(),
population_size: int = 100,
disjoint_coefficient: float = 1.0,
matching_coefficient: float = 3.0,
compatibility_threshold: float = 0.4,
remaining_ratio: float = 0.6,
seed: int | None = None,
) -> Evolution
NEAT Algorithm.
See http://nn.cs.utexas.edu/downloads/papers/stanley.cec02.pdf for the original paper.
NOTE(daiyip): PyGX supports search spaces that do not change during exploration. Therefore we do not grow the program (e.g. Neural Architecture in the paper) from a minimal program. Also, crossover can take place on any two individuals due to a fixed program structure, which will be implemented later. This algorithm illustrates how speciation is expressed in the compositional evolution framework.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mutator
|
Mutator
|
Mutator to use. |
Uniform()
|
population_size
|
int
|
Population size for each generation. |
100
|
disjoint_coefficient
|
float
|
Coefficient for DNA disjointness. Used for distance computations. |
1.0
|
matching_coefficient
|
float
|
Coefficient for DNA matching. Used for distance computations. |
3.0
|
compatibility_threshold
|
float
|
Threshold for max distances between two DNA in the same species. |
0.4
|
remaining_ratio
|
float
|
Ratio of best individuals in a species to remain. |
0.6
|
seed
|
int | None
|
Random seed. If None, use the current system time. |
None
|
Returns:
| Type | Description |
|---|---|
Evolution
|
An |
Source code in pygx/algo/evolution/_neat.py
nsga2
¶
nsga2(
mutator: Mutator = Uniform(),
population_size: int = 100,
seed: int | None = None,
) -> Evolution
NSGA-II: A multi-objective evolutionary search algorithm.
For reference and for citations, please use: https://ieeexplore.ieee.org/document/996017
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mutator
|
Mutator
|
Mutator to use. |
Uniform()
|
population_size
|
int
|
Population size, which will be used as both the batch size for proposing new individuals and the tournament size for finding the elites among recently added populations. |
100
|
seed
|
int | None
|
Random seed for initializing the population. If None, the system time will be used. |
None
|
Returns:
| Type | Description |
|---|---|
Evolution
|
An |
Source code in pygx/algo/evolution/_nsga2.py
RegularizedEvolution
¶
RegularizedEvolution(
mutator: Mutator = Uniform(),
population_size: int = 100,
tournament_size: int = 10,
seed: int | None = None,
) -> Evolution
Regularized Evolution algorithm.
https://www.aaai.org/ojs/index.php/AAAI/article/view/4405.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mutator
|
Mutator
|
Mutator to use. |
Uniform()
|
population_size
|
int
|
Population size. Must be larger than tournament size. |
100
|
tournament_size
|
int
|
Tournament size. |
10
|
seed
|
int | None
|
Random seed. If None, the current system time is used as seed. |
None
|
Returns:
| Type | Description |
|---|---|
Evolution
|
An |
Source code in pygx/algo/evolution/_regularized_evolution.py
regularized_evolution
¶
regularized_evolution(
mutator: Mutator = Uniform(),
population_size: int = 100,
tournament_size: int = 10,
seed: int | None = None,
) -> Evolution
Regularized Evolution algorithm.
https://www.aaai.org/ojs/index.php/AAAI/article/view/4405.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mutator
|
Mutator
|
Mutator to use. |
Uniform()
|
population_size
|
int
|
Population size. Must be larger than tournament size. |
100
|
tournament_size
|
int
|
Tournament size. |
10
|
seed
|
int | None
|
Random seed. If None, the current system time is used as seed. |
None
|
Returns:
| Type | Description |
|---|---|
Evolution
|
An |
Source code in pygx/algo/evolution/_regularized_evolution.py
scalar_spec
¶
Returns the value spec for a field that accepts a scheduled value.
Use this to declare fields that accept either a plain value or a
step-based schedule for it (a Scalar or any callable taking a step).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_spec
|
ValueSpec
|
The value spec for the value produced at each step. |
required |
Returns:
| Type | Description |
|---|---|
ValueSpec
|
A value spec that accepts either the value itself or a callable that produces such a value from a step (an integer). |
Source code in pygx/algo/scalars/_base.py
scalar_value
¶
Evaluates a scheduled value at a step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
A plain value, or a callable (e.g. a |
required |
step
|
int
|
The step to evaluate the value at. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
|
Source code in pygx/algo/scalars/_base.py
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2