Evolution API¶
This section documents the evolutionary components of OKAPI.
Population¶
Functions for initializing and managing populations of trees.
from okapi.population import initialize_individuals, choose_n_best, choose_pareto, choose_pareto_then_sorted
choose_pareto(trees, fitnesses, n, objectives, minimize_node_count=True)
¶
Select up to n trees based on Pareto optimality. Optimizes for: - Maximizing fitness - Minimizing number of nodes in the tree
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trees
|
List[Tree]
|
List of Tree objects |
required |
fitnesses
|
ndarray
|
Array of fitness values for each tree |
required |
n
|
int
|
Maximum number of trees to select |
required |
Returns:
| Type | Description |
|---|---|
|
List of selected trees and their corresponding fitness values |
Source code in okapi/population.py
initialize_individuals(tensors_dict, n, exclude_ids=tuple())
¶
Initialize a population of individuals (trees) from a dictionary of tensors.
This function creates simple trees, each with a root node containing a different tensor from the provided dictionary. The tensors are selected randomly from the dictionary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tensors_dict
|
Dict[str, Tensor]
|
Dictionary mapping model IDs to their tensor representations |
required |
n
|
int
|
Number of individuals (trees) to create |
required |
exclude_ids
|
Optional tuple of model IDs to exclude from selection |
tuple()
|
Returns:
| Type | Description |
|---|---|
List[Tree]
|
List of initialized Tree objects |
Raises:
| Type | Description |
|---|---|
Exception
|
If n is greater than the number of available tensors after exclusions |
Source code in okapi/population.py
Crossover¶
Functions for performing crossover between trees.
crossover(tree1, tree2, node_type=None)
¶
Performs crossover between two parent trees to produce two offspring trees.
Crossover works by selecting a random node from each parent tree and swapping the subtrees rooted at those nodes. This creates two new offspring trees that contain genetic material from both parents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree1
|
Tree
|
First parent tree |
required |
tree2
|
Tree
|
Second parent tree |
required |
node_type
|
Type of nodes to consider for crossover points ('value_nodes' or 'op_nodes'). If None, a random suitable type will be chosen. |
None
|
Returns:
| Type | Description |
|---|---|
|
Tuple of two new Tree objects created by crossover |
Raises:
| Type | Description |
|---|---|
ValueError
|
If node_type is 'op_nodes' but one or both trees don't have operator nodes |
Source code in okapi/crossover.py
tournament_selection_indexes(fitnesses, tournament_size=5, optimal_point=None)
¶
Selects parent indices for crossover using tournament selection.
In tournament selection, a subset of individuals (of size tournament_size) is randomly selected from the population, and the one with the highest fitness is chosen as a parent. This process is repeated to select the second parent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fitnesses
|
ndarray
|
Array of fitness values for the entire population |
required |
tournament_size
|
int
|
Number of individuals to include in each tournament |
5
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Array with indices of the two selected parents |
Raises:
| Type | Description |
|---|---|
ValueError
|
If tournament_size is too large relative to population size |
Source code in okapi/crossover.py
Mutation¶
Functions for mutating trees.
from okapi.mutation import append_new_node_mutation, lose_branch_mutation, new_tree_from_branch_mutation, get_allowed_mutations
append_new_node_mutation(tree, models, ids=None, allowed_ops=(MeanNode,), **kwargs)
¶
Mutation that adds a new node to the tree.
This mutation randomly selects an existing node in the tree and appends a new node as its child. If the selected node is a ValueNode, a new OperatorNode is created as an intermediary, and the new ValueNode is added as its child. If the selected node is an OperatorNode, a new ValueNode is directly appended to it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
Tree
|
The tree to mutate |
required |
models
|
Sequence[Tensor]
|
Sequence of tensor models that can be used as values for the new ValueNode |
required |
ids
|
None | Sequence[str | int]
|
Optional sequence of identifiers for the models. If None, indices will be used |
None
|
allowed_ops
|
tuple[Type[OperatorNode], ...]
|
Tuple of OperatorNode types that can be used when creating a new operator node |
(MeanNode,)
|
**kwargs
|
Additional keyword arguments (ignored) |
{}
|
Returns:
| Type | Description |
|---|---|
|
A new Tree with the mutation applied |
Source code in okapi/mutation.py
get_allowed_mutations(tree)
¶
Determines which mutation operations are valid for a given tree.
This function checks the tree's structure and size to determine which mutations can be safely applied without violating constraints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
The tree to analyze |
required |
Returns:
| Type | Description |
|---|---|
|
A list of mutation functions that are valid for the given tree |
Source code in okapi/mutation.py
lose_branch_mutation(tree, **kwargs)
¶
Mutation that removes a branch from the tree.
This mutation randomly selects a non-root, non-leaf node in the tree and removes it along with all its descendants, effectively pruning that branch from the tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
Tree
|
The tree to mutate |
required |
**kwargs
|
Additional keyword arguments (ignored) |
{}
|
Returns:
| Type | Description |
|---|---|
|
A new Tree with the mutation applied |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the tree has fewer than 3 nodes |
Source code in okapi/mutation.py
new_tree_from_branch_mutation(tree, **kwargs)
¶
Mutation that creates a new tree from a branch of the existing tree.
This mutation randomly selects a non-root ValueNode, removes it from the tree along with its descendants, and creates a new tree with the removed node as its root.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
Tree
|
The tree to mutate |
required |
**kwargs
|
Additional keyword arguments (ignored) |
{}
|
Returns:
| Type | Description |
|---|---|
|
A new Tree created from the selected branch |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If the tree has only one ValueNode |
Source code in okapi/mutation.py
Pareto Optimization¶
Functions for Pareto optimization and visualization.
maximize(a, b)
¶
Compare two values for maximization in Pareto optimization.
This function determines if the first value (a) is at least as good as the second value (b) in the context of maximization (higher is better).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
First value to compare |
required | |
b
|
Second value to compare |
required |
Returns:
| Type | Description |
|---|---|
|
True if a is greater than or equal to b, False otherwise |
Source code in okapi/pareto.py
minimize(a, b)
¶
Compare two values for minimization in Pareto optimization.
This function determines if the first value (a) is at least as good as the second value (b) in the context of minimization (lower is better).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
First value to compare |
required | |
b
|
Second value to compare |
required |
Returns:
| Type | Description |
|---|---|
|
True if a is less than or equal to b, False otherwise |
Source code in okapi/pareto.py
paretoset(array, objectives)
¶
Identify the Pareto-optimal set from a collection of points with multiple objectives.
This function finds points that are not dominated by any other point, where dominance is determined based on the specified objective functions. A point dominates another if it is at least as good in all objectives and strictly better in at least one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
ndarray
|
2D array where each row is a point and each column represents a different objective |
required |
objectives
|
Sequence[Callable[[float, float], bool]]
|
Sequence of objective functions (maximize or minimize) for each column |
required |
Returns:
| Type | Description |
|---|---|
|
Boolean mask where True indicates a point belongs to the Pareto-optimal set |
Raises:
| Type | Description |
|---|---|
AssertionError
|
If dimensions don't match or the array is not 2D |
Source code in okapi/pareto.py
plot_pareto_frontier(array, objectives, figsize=(10, 6), title='Pareto Frontier')
¶
Visualize the Pareto frontier for a two-dimensional optimization problem.
Parameters:¶
array : np.ndarray Array of points where the first dimension is the number of points and the second dimension must be exactly 2 (two criteria to optimize). objectives : Sequence[Callable] Sequence of two objective functions, each should be either maximize or minimize. figsize : tuple, optional Size of the figure (width, height) in inches. Default is (10, 6). title : str, optional Title of the plot. Default is "Pareto Frontier".
Returns:¶
fig, ax : tuple Matplotlib figure and axes objects.