Skip to content

Functional

Functional implementations of the genetic algorithm operators.

Instead of the object-oriented genetic algorithm API (GeneticAlgorithm), one might wish to adopt a style that is more compatible with the functional programming paradigm. For such cases, the functional operators within this namespace can be used.

The operators within this namespace are designed to be called directly, allowing one to implement a genetic algorithm according to which, how, and when these operators are used.

Reasons for using the functional operators.

  • Flexibility. This API provides various genetic-algorithm-related operators and gets out of the picture. The user has the complete control over what happens between this operator calls, and in what order these operators are used.
  • Batched search. These functional operators are designed in such a way that, if they receive a batched population instead of a single population (i.e. if they receive a 3-or-more-dimensional tensor instead of a 2-dimensional tensor), they will broadcast their operations across the extra leftmost dimensions. This allows one to implement a genetic algorithm that works across many populations at once, in a vectorized manner.
  • Nested optimization. It could be the case that the optimization problem at hand has an inner optimization problem within its fitness function. This inner optimization problem could be tackled with the help of a genetic algorithm built upon these functional operators. Such an approach would allow the user to run a search for each inner optimization problem across the entire population of the outer problem, in a vectorized manner (see the previous point titled "batched search").

Example usage. Let us assume that we have the following cost function that to be minimized:

import torch


def f(x: torch.Tensor) -> torch.Tensor:
    return torch.linalg.norm(x - 1, dim=-1)

A genetic algorithm could be designed with the help of these functional operators as follows:

import torch
from evotorch.operators.functional import two_point_cross_over, combine, take_best


def f(x: torch.Tensor) -> torch.Tensor:
    return torch.linalg.norm(x - 1, dim=-1)


popsize = 100  # population size
solution_length = 20  # length of a solution
num_generations = 200  # number of generations
mutation_stdev = 0.01  # standard deviation for mutation
tournament_size = 4  # size for the tournament selection

# Randomly initialize a population, and compute the solution costs
population = torch.randn(popsize, solution_length)
costs = f(population)

# Initialize the variables that will store the decision values and the cost
# for the last population's best solution.
pop_best_values = None
pop_best_cost = None

# main loop of the optimization
for generation in range(1, 1 + num_generations):
    # Given the population and the solution costs, pick parents and apply
    # cross-over on them.
    candidates = two_point_cross_over(
        population,
        costs,
        tournament_size=tournament_size,
        objective_sense="min",
    )

    # Apply Gaussian mutation on the candidates
    candidates = candidates + (torch.randn(popsize, solution_length) * mutation_stdev)

    # Compute the solution costs of the candidates
    candidate_costs = f(candidates)

    # Combine the parents and the candidates into an extended population
    extended_values, extended_costs = combine(
        (population, costs),
        (candidates, candidate_costs),
    )

    # Take the best `popsize` number of solutions from the extended population
    population, costs = take_best(
        extended_values,
        extended_costs,
        popsize,
        objective_sense="min",
    )

    # Take the best solution and its cost
    pop_best_values, pop_best_cost = take_best(population, costs, objective_sense="min")

    # Print the status
    print("Generation:", generation, "  Best cost within population:", best_cost)

# Print the result
print()
print("Best solution of the last population:")
print(pop_best_values)
print("Cost of the best solution of the last population:")
print(pop_best_cost)

combine(a, b, *, objective_sense=None)

Combine two populations into one.

Usage 1: without evaluation results. Let us assume that we have two decision values matrices, values1 values2. The shapes of these matrices are (n1, L) and (n2, L) respectively, where L represents the length of a solution. Let us assume that the solutions that these decision values represent are not evaluated yet. Therefore, we do not have evaluation results (i.e. we do not have fitnesses). To combine these two unevaluated populations, we use this function as follows:

combined_population = combine(values1, values2)

# We now have a combined decision values matrix, shaped (n1+n2, L).

Usage 2: with evaluation results, single-objective. We again assume that we have two decision values matrices, values1 and values2. Like in our previous example, these matrices are shaped (n1, L) and (n2, L), respectively. Additionally, let us assume that we know the evaluation results for the solutions represented by values1 and values2. These evaluation results are represented by the tensors evals1 and evals2, shaped (n1,) and (n2,), respectively. To combine these two evaluated populations, we use this function as follows:

c_values, c_evals = combine((values1, evals1), (values2, evals2))

# We now have a combined decision values matrix and a combined evaluations
# vector.
# `c_values` is shaped (n1+n2, L), and `c_evals` is shaped (n1+n2,).

Usage 3: with evaluation results, multi-objective. We again assume that we have two decision values matrices, values1 and values2. Like in our previous example, these matrices are shaped (n1, L) and (n2, L), respectively. Additionally, we assume that we know the evaluation results for these solutions. The evaluation results are stored within the tensors evals1 and evals2, whose shapes are (n1, M) and (n2, M), where M is the number of objectives. To combine these two evaluated populations, we use this function as follows:

c_values, c_evals = combine(
    (values1, evals1),
    (values2, evals2),
    objective_sense=["min", "min"],  # Assuming we have 2 min objectives
)

# We now have a combined decision values matrix and a combined evaluations
# vector.
# `c_values` is shaped (n1+n2, L), and `c_evals` is shaped (n1+n2,).

Support for ObjectArray. This function supports decision values that are expressed via instances of ObjectArray.

Parameters:

Name Type Description Default
a Union[Tensor, ObjectArray, tuple]

A decision values tensor with at least 2 dimensions, or an ObjectArray of decision values, or a tuple of the form (values, evals) where values is the decision values and evals is a tensor with at least 1 dimension. Additional leftmost dimensions within tensors are interpreted as batch dimensions. If this positional argument is a tensor, the second positional argument must also be a tensor. If this positional argument is an ObjectArray, the second positional argument must also be an ObjectArray. If this positional argument is a tuple, the second positional argument must also be a tuple.

required
b Union[Tensor, ObjectArray, tuple]

A decision values tensor with at least 2 dimensions, or an ObjectArray of decision values, or a tuple of the form (values, evals) where values is the decision values and evals is a tensor with at least 1 dimension. Additional leftmost dimensions within tensors are interpreted as batch dimensions. If this positional argument is a tensor, the first positional argument must also be a tensor. If this positional argument is an ObjectArray, the first positional argument must also be an ObjectArray. If this positional argument is a tuple, the first positional argument must also be a tuple.

required
objective_sense Optional[Union[str, Iterable]]

In the case of single-objective optimization, objective_sense can be left as None, or can be 'min' or 'max', representing the direction of the optimization. In the case of multi-objective optimization, objective_sense is expected as a list of strings, each string being 'min' or 'max', representing the direction for each objective. Please also note that, if this combination operation is done without evaluation results (i.e. if the first two positional arguments are given as tensors, not tuples), objective_sense is not needed, and can be omitted, regardless of whether or not the problem at hand is single-objective.

None
Source code in evotorch/operators/functional.py
def combine(
    a: Union[torch.Tensor, ObjectArray, tuple],
    b: Union[torch.Tensor, ObjectArray, tuple],
    *,
    objective_sense: Optional[Union[str, Iterable]] = None,
) -> Union[torch.Tensor, tuple]:
    """
    Combine two populations into one.

    **Usage 1: without evaluation results.**
    Let us assume that we have two decision values matrices, `values1`
    `values2`. The shapes of these matrices are (n1, L) and (n2, L)
    respectively, where L represents the length of a solution.
    Let us assume that the solutions that these decision values
    represent are not evaluated yet. Therefore, we do not have evaluation
    results (i.e. we do not have fitnesses). To combine these two
    unevaluated populations, we use this function as follows:

    ```python
    combined_population = combine(values1, values2)

    # We now have a combined decision values matrix, shaped (n1+n2, L).
    ```

    **Usage 2: with evaluation results, single-objective.**
    We again assume that we have two decision values matrices, `values1`
    and `values2`. Like in our previous example, these matrices are shaped
    (n1, L) and (n2, L), respectively. Additionally, let us assume that we
    know the evaluation results for the solutions represented by `values1`
    and `values2`. These evaluation results are represented by the tensors
    `evals1` and `evals2`, shaped (n1,) and (n2,), respectively. To combine
    these two evaluated populations, we use this function as follows:

    ```python
    c_values, c_evals = combine((values1, evals1), (values2, evals2))

    # We now have a combined decision values matrix and a combined evaluations
    # vector.
    # `c_values` is shaped (n1+n2, L), and `c_evals` is shaped (n1+n2,).
    ```

    **Usage 3: with evaluation results, multi-objective.**
    We again assume that we have two decision values matrices, `values1`
    and `values2`. Like in our previous example, these matrices are shaped
    (n1, L) and (n2, L), respectively. Additionally, we assume that we know
    the evaluation results for these solutions. The evaluation results are
    stored within the tensors `evals1` and `evals2`, whose shapes are
    (n1, M) and (n2, M), where M is the number of objectives. To combine
    these two evaluated populations, we use this function as follows:

    ```python
    c_values, c_evals = combine(
        (values1, evals1),
        (values2, evals2),
        objective_sense=["min", "min"],  # Assuming we have 2 min objectives
    )

    # We now have a combined decision values matrix and a combined evaluations
    # vector.
    # `c_values` is shaped (n1+n2, L), and `c_evals` is shaped (n1+n2,).
    ```

    **Support for ObjectArray.**
    This function supports decision values that are expressed via instances
    of `ObjectArray`.

    Args:
        a: A decision values tensor with at least 2 dimensions, or an
            `ObjectArray` of decision values, or a tuple of the form
            `(values, evals)` where `values` is the decision values
            and `evals` is a tensor with at least 1 dimension.
            Additional leftmost dimensions within tensors are interpreted
            as batch dimensions.
            If this positional argument is a tensor, the second positional
            argument must also be a tensor.
            If this positional argument is an `ObjectArray`, the second
            positional argument must also be an `ObjectArray`.
            If this positional argument is a tuple, the second positional
            argument must also be a tuple.
        b: A decision values tensor with at least 2 dimensions, or an
            `ObjectArray` of decision values, or a tuple of the form
            `(values, evals)` where `values` is the decision values
            and `evals` is a tensor with at least 1 dimension.
            Additional leftmost dimensions within tensors are interpreted
            as batch dimensions.
            If this positional argument is a tensor, the first positional
            argument must also be a tensor.
            If this positional argument is an `ObjectArray`, the first
            positional argument must also be an `ObjectArray`.
            If this positional argument is a tuple, the first positional
            argument must also be a tuple.
        objective_sense: In the case of single-objective optimization,
            `objective_sense` can be left as None, or can be 'min' or 'max',
            representing the direction of the optimization.
            In the case of multi-objective optimization, `objective_sense`
            is expected as a list of strings, each string being 'min' or
            'max', representing the direction for each objective.
            Please also note that, if this combination operation is done
            without evaluation results (i.e. if the first two positional
            arguments are given as tensors, not tuples), `objective_sense`
            is not needed, and can be omitted, regardless of whether or
            not the problem at hand is single-objective.
    Returns:
        The combined decision values tensor, or a tuple of the form
        `(values, evals)` where `values` is the combined decision values
        tensor, and `evals` is the combined evaluation results tensor.
    """
    if isinstance(a, tuple):
        values1, evals1 = a
        if not isinstance(b, tuple):
            raise TypeError(
                "The first positional argument was received as a tuple."
                " Therefore, the second positional argument was also expected as a tuple."
                f" However, the second argument is {repr(b)} (of type {type(b)})."
            )
        values2, evals2 = b
        if (objective_sense is None) or isinstance(objective_sense, str):
            if _both_are_tensors(values1, values2):
                return _combine_values_and_evals(values1, evals1, values2, evals2)
            elif _both_are_object_arrays(values1, values2):
                return _combine_object_arrays_and_evals(values1, evals1, values2, evals2)
            else:
                raise TypeError(
                    "Both decision values arrays must be `Tensor`s or `ObjectArray`s."
                    f" However, their types are: {type(values1)}, {type(values2)}."
                )
        elif isinstance(objective_sense, Iterable):
            if _both_are_tensors(values1, values2):
                return _combine_values_and_multiobjective_evals(values1, evals1, values2, evals2)
            elif _both_are_object_arrays(values1, values2):
                return _combine_object_arrays_and_multiobjective_evals(values1, evals1, values2, evals2)
            else:
                raise TypeError(
                    "Both decision values arrays must be `Tensor`s or `ObjectArray`s."
                    f" However, their types are: {type(values1)}, {type(values2)}."
                )
        else:
            raise TypeError(f"Unrecognized `objective_sense`: {repr(objective_sense)}")
    elif isinstance(a, (torch.Tensor, ObjectArray)):
        if not isinstance(b, (torch.Tensor, ObjectArray)):
            raise TypeError(
                "The first positional argument was received as a tensor or ObjectArray."
                " Therefore, the second positional argument was also expected as a tensor or ObjectArray."
                f" However, the second argument is {repr(b)} (of type {type(b)})."
            )
        if _both_are_tensors(a, b):
            return _combine_values(a, b)
        elif _both_are_object_arrays(a, b):
            return _combine_object_arrays(a, b)
        else:
            raise TypeError(
                "Both decision values arrays must be `Tensor`s or `ObjectArray`s."
                f" However, their types are: {type(values1)}, {type(values2)}."
            )
    else:
        raise TypeError(
            "Expected both positional arguments as tensors, or as tuples."
            f" However, the first positional argument is {repr(a)} (of type {type(a)})."
        )

cosyne_permutation(values, evals=None, *, permute_all=True, objective_sense=None)

Return the permuted (i.e. shuffled) version of the given decision values.

Shuffling of the decision values is done columnwise.

If permute_all is given as True, each item within each column will be subject to permutation. In this mode, the arguments evals and objective_sense can be omitted (i.e. can be left as None).

If permute_all is given as False, each item within each column is given a probability of staying the same. This probability is higher for items that belong to solutions with better fitnesses. In this mode, the arguments evals and objective_sense are mandatory.

Reference:

Gomez, F., Schmidhuber, J., Miikkulainen, R., & Mitchell, M. (2008).
Accelerated Neural Evolution through Cooperatively Coevolved Synapses.
Journal of Machine Learning Research, 9(5).

Parameters:

Name Type Description Default
population

A tensor with at least 2 dimensions, representing the decision values of the solutions. Extra leftmost dimensions will be considered as batch dimensions.

required
evals Optional[Tensor]

Evaluation results (i.e. fitnesses), as a tensor with at least one dimension. Extra leftmost dimensions will be considered as batch dimensions. If permute_all is True, this argument can be left as None.

None
permute_all bool

Whether or not each item within each column will be subject to permutation operation. If given as False, items with better fitnesses have greater probabilities of staying the same. The default is True.

True
objective_sense Optional[str]

A string whose value is either 'min' or 'max', representing the goal of the optimization. If permute_all is True, this argument can be left as None.

None
Source code in evotorch/operators/functional.py
def cosyne_permutation(
    values: torch.Tensor,
    evals: Optional[torch.Tensor] = None,
    *,
    permute_all: bool = True,
    objective_sense: Optional[str] = None,
) -> torch.Tensor:
    """
    Return the permuted (i.e. shuffled) version of the given decision values.

    Shuffling of the decision values is done columnwise.

    If `permute_all` is given as True, each item within each column will be
    subject to permutation. In this mode, the arguments `evals` and
    `objective_sense` can be omitted (i.e. can be left as None).

    If `permute_all` is given as False, each item within each column is given
    a probability of staying the same. This probability is higher for items
    that belong to solutions with better fitnesses. In this mode, the
    arguments `evals` and `objective_sense` are mandatory.

    Reference:

    ```
    Gomez, F., Schmidhuber, J., Miikkulainen, R., & Mitchell, M. (2008).
    Accelerated Neural Evolution through Cooperatively Coevolved Synapses.
    Journal of Machine Learning Research, 9(5).
    ```

    Args:
        population: A tensor with at least 2 dimensions, representing the
            decision values of the solutions. Extra leftmost dimensions will
            be considered as batch dimensions.
        evals: Evaluation results (i.e. fitnesses), as a tensor with at least
            one dimension. Extra leftmost dimensions will be considered as
            batch dimensions. If `permute_all` is True, this argument can be
            left as None.
        permute_all: Whether or not each item within each column will be
            subject to permutation operation. If given as False, items
            with better fitnesses have greater probabilities of staying the
            same. The default is True.
        objective_sense: A string whose value is either 'min' or 'max',
            representing the goal of the optimization. If `permute_all` is
            True, this argument can be left as None.
    Returns:
        The shuffled counterpart of the given population, as a new tensor.
    """
    if permute_all:
        return _cosyne_permutation_for_entire_population(values)
    else:
        if evals is None:
            raise ValueError("When `permute_all` is False, `evals` is required")
        if objective_sense is None:
            raise ValueError("When `permute_all` is False, `objective_sense` is required")
        return _partial_cosyne_permutation_for_population(values, evals, objective_sense)

dominates(evals1, evals2, *, objective_sense)

Return whether or not the first solution pareto-dominates the second one.

Parameters:

Name Type Description Default
evals1 Tensor

Evaluation results of the first solution. Expected as an at-least-1-dimensional tensor, the length of which must be equal to the number of objectives. Extra leftmost dimensions will be considered as batch dimensions.

required
evals2 Tensor

Evaluation results of the second solution. Expected as an at-least-1-dimensional tensor, the length of which must be equal to the number of objectives. Extra leftmost dimensions will be considered as batch dimensions.

required
objective_sense list

Expected as a list of strings, where each string is either 'min' or 'max', expressing the direction of optimization regarding each objective.

required
Source code in evotorch/operators/functional.py
def dominates(
    evals1: torch.Tensor,
    evals2: torch.Tensor,
    *,
    objective_sense: list,
) -> torch.Tensor:
    """
    Return whether or not the first solution pareto-dominates the second one.

    Args:
        evals1: Evaluation results of the first solution. Expected as an
            at-least-1-dimensional tensor, the length of which must be
            equal to the number of objectives. Extra leftmost dimensions
            will be considered as batch dimensions.
        evals2: Evaluation results of the second solution. Expected as an
            at-least-1-dimensional tensor, the length of which must be
            equal to the number of objectives. Extra leftmost dimensions
            will be considered as batch dimensions.
        objective_sense: Expected as a list of strings, where each
            string is either 'min' or 'max', expressing the direction of
            optimization regarding each objective.
    Returns:
        A tensor of boolean(s), indicating whether or not the first
        solution(s) dominate(s) the second solution(s).
    """
    if isinstance(objective_sense, str):
        raise ValueError(
            "`objective_sense` was received as a string, implying that the problem at hand has a single objective."
            " However, this `dominates(...)` function does not support single-objective cases."
        )
    elif isinstance(objective_sense, Iterable):
        return _dominates(evals1, evals2, objective_sense)
    else:
        raise TypeError(f"Unrecognized `objective_sense`: {repr(objective_sense)}")

domination_counts(evals, *, objective_sense)

Return a tensor expressing how many times each solution gets dominated

In this returned tensor, the i-th item is an integer which specifies how many times the i-th solution is dominated.

Parameters:

Name Type Description Default
evals Tensor

Expected as an at-least-2-dimensional tensor. In such a 2-dimensional evaluation tensor, the item i,j represents the evaluation result of the i-th solution according to the j-th objective. Extra leftmost dimensions are interpreted as batch dimensions.

required
objective_sense list

A list of strings, where each string is either 'min' or 'max', expressing the direction of optimization regarding each objective.

required
Source code in evotorch/operators/functional.py
def domination_counts(evals: torch.Tensor, *, objective_sense: list) -> torch.Tensor:
    """
    Return a tensor expressing how many times each solution gets dominated

    In this returned tensor, the `i`-th item is an integer which specifies how
    many times the `i`-th solution is dominated.

    Args:
        evals: Expected as an at-least-2-dimensional tensor. In such a
            2-dimensional evaluation tensor, the item `i,j` represents the
            evaluation result of the `i`-th solution according to the `j`-th
            objective. Extra leftmost dimensions are interpreted as batch
            dimensions.
        objective_sense: A list of strings, where each string is either
            'min' or 'max', expressing the direction of optimization regarding
            each objective.
    Returns:
        An integer tensor of length `n`, where `n` is the number of solutions.
    """
    return _domination_counts(evals, objective_sense)

domination_matrix(evals, *, objective_sense)

Compute and return a pareto-domination matrix.

In this pareto-domination matrix P, the item P[i,j] is True if the i-th solution is dominated by the j-th solution.

Parameters:

Name Type Description Default
evals Tensor

Evaluation results of the solutions, expected as a tensor with at least 2 dimensions. In a 2-dimensional evals tensor, the item i,j represents the evaluation result of the i-th solution according to the j-th objective. Extra leftmost dimensions are interpreted as batch dimensions.

required
objective_sense list

A list of strings, where each string is either 'min' or 'max', expressing the direction of optimization regarding each objective.

required
Source code in evotorch/operators/functional.py
def domination_matrix(evals: torch.Tensor, *, objective_sense: list) -> torch.Tensor:
    """
    Compute and return a pareto-domination matrix.

    In this pareto-domination matrix `P`, the item `P[i,j]` is True if the
    `i`-th solution is dominated by the `j`-th solution.

    Args:
        evals: Evaluation results of the solutions, expected as a tensor
            with at least 2 dimensions. In a 2-dimensional `evals` tensor,
            the item `i,j` represents the evaluation result of the
            `i`-th solution according to the `j`-th objective.
            Extra leftmost dimensions are interpreted as batch dimensions.
        objective_sense: A list of strings, where each string is either
            'min' or 'max', expressing the direction of optimization regarding
            each objective.
    Returns:
        A boolean tensor of size `(n,n)`, where `n` is the number of solutions.
    """
    return _domination_matrix(evals, objective_sense)

multi_point_cross_over(parents, evals=None, *, num_points, tournament_size=None, num_children=None, objective_sense=None)

Apply multi-point cross-over on the given parents.

If tournament_size is given, parents for the cross-over operation will be picked with the help of a tournament. Otherwise, the first half of the given parents will be the first set of parents, and the second half of the given parents will be the second set of parents.

The return value of this function is a new tensor containing the decision values of the child solutions.

Parameters:

Name Type Description Default
parents Tensor

A tensor with at least 2 dimensions, representing the decision values of the parent solutions. If this tensor has more than 2 dimensions, the extra leftmost dimension(s) will be considered as batch dimensions.

required
evals Optional[Tensor]

A tensor with at least 1 dimension, representing the evaluation results (i.e. fitnesses) of the parent solutions. If this tensor has more than 1 dimension, the extra leftmost dimension(s) will be considered as batch dimensions. If tournament_size is not given, evals can be left as None.

None
num_points int

Number of points at which the decision values of the parent solutions will be cut and recombined to form the child solutions.

required
tournament_size Optional[int]

If given as an integer that is greater than or equal to 1, the parents for the cross-over operation will be picked with the help of a tournament. In more details, each parent will be picked as the result of comparing multiple competing solutions, the number of these competing solutions being equal to this tournament_size. Please note that, if tournament_size is given as an integer, the arguments evals and objective_sense are also required. If tournament_size is left as None, the first half of parents will be the first set of parents, and the second half of parents will be the second set of parents.

None
num_children Optional[int]

Optionally the number of children to produce as the result of tournament selection and cross-over, as an even integer. If tournament selection is enabled (i.e. if tournament_size is an integer) but num_children is omitted, the number of children will be equal to the number of parents. If there is no tournament selection (i.e. if tournament_size is None), num_children is expected to be None.

None
objective_sense Optional[Union[str, list]]

Mandatory if tournament_size is not None. For when there is only one objective, objective_sense is expected as 'max' for when the goal of the optimization is to maximize evals, 'min' for when the goal of the optimization is to minimize evals. For when there are multiple objectives, objective_sense is expected as a list of strings, where each string is either 'min' or 'max'. If tournament_size is None, objective_sense can also be left as None.

None
Source code in evotorch/operators/functional.py
def multi_point_cross_over(
    parents: torch.Tensor,
    evals: Optional[torch.Tensor] = None,
    *,
    num_points: int,
    tournament_size: Optional[int] = None,
    num_children: Optional[int] = None,
    objective_sense: Optional[Union[str, list]] = None,
) -> torch.Tensor:
    """
    Apply multi-point cross-over on the given `parents`.

    If `tournament_size` is given, parents for the cross-over operation will
    be picked with the help of a tournament. Otherwise, the first half of the
    given `parents` will be the first set of parents, and the second half
    of the given `parents` will be the second set of parents.

    The return value of this function is a new tensor containing the decision
    values of the child solutions.

    Args:
        parents: A tensor with at least 2 dimensions, representing the decision
            values of the parent solutions. If this tensor has more than 2
            dimensions, the extra leftmost dimension(s) will be considered as
            batch dimensions.
        evals: A tensor with at least 1 dimension, representing the evaluation
            results (i.e. fitnesses) of the parent solutions. If this tensor
            has more than 1 dimension, the extra leftmost dimension(s) will be
            considered as batch dimensions. If `tournament_size` is not given,
            `evals` can be left as None.
        num_points: Number of points at which the decision values of the
            parent solutions will be cut and recombined to form the child
            solutions.
        tournament_size: If given as an integer that is greater than or equal
            to 1, the parents for the cross-over operation will be picked
            with the help of a tournament. In more details, each parent will
            be picked as the result of comparing multiple competing solutions,
            the number of these competing solutions being equal to this
            `tournament_size`. Please note that, if `tournament_size` is given
            as an integer, the arguments `evals` and `objective_sense` are
            also required. If `tournament_size` is left as None, the first half
            of `parents` will be the first set of parents, and the second half
            of `parents` will be the second set of parents.
        num_children: Optionally the number of children to produce as the
            result of tournament selection and cross-over, as an even integer.
            If tournament selection is enabled (i.e. if `tournament_size` is
            an integer) but `num_children` is omitted, the number of children
            will be equal to the number of `parents`.
            If there is no tournament selection (i.e. if `tournament_size` is
            None), `num_children` is expected to be None.
        objective_sense: Mandatory if `tournament_size` is not None.
            For when there is only one objective, `objective_sense` is
            expected as 'max' for when the goal of the optimization is to
            maximize `evals`, 'min' for when the goal of the optimization is
            to minimize `evals`. For when there are multiple objectives,
            `objective_sense` is expected as a list of strings, where each
            string is either 'min' or 'max'.
            If `tournament_size` is None, `objective_sense` can also be left
            as None.
    Returns:
        Decision values of the child solutions, as a new tensor.
    """

    if tournament_size is None:
        if num_children is not None:
            raise ValueError(
                "`num_children` was received as something other than None."
                " However, `num_children` is expected only when a `tournament_size` is given,"
                " which seems to be omitted (i.e. which is None)."
            )
    else:
        # This is the case where the tournament selection feature is enabled.
        # We first ensure that the required arguments `evals` and `objective_sense` are available.
        if evals is None:
            raise ValueError(
                "When a `tournament_size` is given, the argument `evals` is also required."
                " However, it was received as None."
            )
        if num_children is None:
            # If `num_children` is not given, we make it equal to the number of `parents`.
            num_children = parents.shape[-2]
        if objective_sense is None:
            raise ValueError(
                "When a `tournament_size` is given, the argument `objective_sense` is also required."
                " However, it was received as None."
            )

        # Apply tournament selection on the original `parents`
        parents = tournament(
            parents,
            evals,
            num_tournaments=num_children,
            tournament_size=tournament_size,
            objective_sense=objective_sense,
            with_evals=False,
        )

    # Apply the cross-over operation on `parents`, and return the recombined decision values tensor.
    return _do_cross_over(parents, num_points)

one_point_cross_over(parents, evals=None, *, tournament_size=None, num_children=None, objective_sense=None)

Apply one-point cross-over on the given parents.

Let us assume that we have the following two parent solutions:

         ________________________
parentA | a1  a2  a3  a4  a5  a6 |
parentB | b1  b2  b3  b4  b5  b6 |
        |________________________|

This cross-over operation will first randomly decide a cutting point:

         ________|________________
parentA | a1  a2 | a3  a4  a5  a6 |
parentB | b1  b2 | b3  b4  b5  b6 |
        |________|________________|
                 |

...and then form the following child solutions by recombining the decision values of the parents:

         ________|________________
child1  | a1  a2 | b3  b4  b5  b6 |
child2  | b1  b2 | a3  a4  a5  a6 |
        |________|________________|
                 |

If tournament_size is given, parents for the cross-over operation will be picked with the help of a tournament. Otherwise, the first half of the given parents will be the first set of parents, and the second half of the given parents will be the second set of parents.

The return value of this function is a new tensor containing the decision values of the child solutions.

Parameters:

Name Type Description Default
parents Tensor

A tensor with at least 2 dimensions, representing the decision values of the parent solutions. If this tensor has more than 2 dimensions, the extra leftmost dimension(s) will be considered as batch dimensions.

required
evals Optional[Tensor]

A tensor with at least 1 dimension, representing the evaluation results (i.e. fitnesses) of the parent solutions. If this tensor has more than 1 dimension, the extra leftmost dimension(s) will be considered as batch dimensions. If tournament_size is not given, evals can be left as None.

None
tournament_size Optional[int]

If given as an integer that is greater than or equal to 1, the parents for the cross-over operation will be picked with the help of a tournament. In more details, each parent will be picked as the result of comparing multiple competing solutions, the number of these competing solutions being equal to this tournament_size. Please note that, if tournament_size is given as an integer, the arguments evals and objective_sense are also required. If tournament_size is left as None, the first half of parents will be the first set of parents, and the second half of parents will be the second set of parents.

None
num_children Optional[int]

Optionally the number of children to produce as the result of tournament selection and cross-over, as an even integer. If tournament selection is enabled (i.e. if tournament_size is an integer) but num_children is omitted, the number of children will be equal to the number of parents. If there is no tournament selection (i.e. if tournament_size is None), num_children is expected to be None.

None
objective_sense Optional[str]

Mandatory if tournament_size is not None. For when there is only one objective, objective_sense is expected as 'max' for when the goal of the optimization is to maximize evals, 'min' for when the goal of the optimization is to minimize evals. For when there are multiple objectives, objective_sense is expected as a list of strings, where each string is either 'min' or 'max'. If tournament_size is None, objective_sense can also be left as None.

None
Source code in evotorch/operators/functional.py
def one_point_cross_over(
    parents: torch.Tensor,
    evals: Optional[torch.Tensor] = None,
    *,
    tournament_size: Optional[int] = None,
    num_children: Optional[int] = None,
    objective_sense: Optional[str] = None,
) -> torch.Tensor:
    """
    Apply one-point cross-over on the given `parents`.

    Let us assume that we have the following two parent solutions:

    ```text
             ________________________
    parentA | a1  a2  a3  a4  a5  a6 |
    parentB | b1  b2  b3  b4  b5  b6 |
            |________________________|
    ```

    This cross-over operation will first randomly decide a cutting point:

    ```text
             ________|________________
    parentA | a1  a2 | a3  a4  a5  a6 |
    parentB | b1  b2 | b3  b4  b5  b6 |
            |________|________________|
                     |
    ```

    ...and then form the following child solutions by recombining the decision
    values of the parents:

    ```text
             ________|________________
    child1  | a1  a2 | b3  b4  b5  b6 |
    child2  | b1  b2 | a3  a4  a5  a6 |
            |________|________________|
                     |
    ```

    If `tournament_size` is given, parents for the cross-over operation will
    be picked with the help of a tournament. Otherwise, the first half of the
    given `parents` will be the first set of parents, and the second half
    of the given `parents` will be the second set of parents.

    The return value of this function is a new tensor containing the decision
    values of the child solutions.

    Args:
        parents: A tensor with at least 2 dimensions, representing the decision
            values of the parent solutions. If this tensor has more than 2
            dimensions, the extra leftmost dimension(s) will be considered as
            batch dimensions.
        evals: A tensor with at least 1 dimension, representing the evaluation
            results (i.e. fitnesses) of the parent solutions. If this tensor
            has more than 1 dimension, the extra leftmost dimension(s) will be
            considered as batch dimensions. If `tournament_size` is not given,
            `evals` can be left as None.
        tournament_size: If given as an integer that is greater than or equal
            to 1, the parents for the cross-over operation will be picked
            with the help of a tournament. In more details, each parent will
            be picked as the result of comparing multiple competing solutions,
            the number of these competing solutions being equal to this
            `tournament_size`. Please note that, if `tournament_size` is given
            as an integer, the arguments `evals` and `objective_sense` are
            also required. If `tournament_size` is left as None, the first half
            of `parents` will be the first set of parents, and the second half
            of `parents` will be the second set of parents.
        num_children: Optionally the number of children to produce as the
            result of tournament selection and cross-over, as an even integer.
            If tournament selection is enabled (i.e. if `tournament_size` is
            an integer) but `num_children` is omitted, the number of children
            will be equal to the number of `parents`.
            If there is no tournament selection (i.e. if `tournament_size` is
            None), `num_children` is expected to be None.
        objective_sense: Mandatory if `tournament_size` is not None.
            For when there is only one objective, `objective_sense` is
            expected as 'max' for when the goal of the optimization is to
            maximize `evals`, 'min' for when the goal of the optimization is
            to minimize `evals`. For when there are multiple objectives,
            `objective_sense` is expected as a list of strings, where each
            string is either 'min' or 'max'.
            If `tournament_size` is None, `objective_sense` can also be left
            as None.
    Returns:
        Decision values of the child solutions, as a new tensor.
    """
    return multi_point_cross_over(
        parents,
        evals,
        num_points=1,
        num_children=num_children,
        tournament_size=tournament_size,
        objective_sense=objective_sense,
    )

pareto_utility(evals, *, objective_sense, crowdsort=True)

Compute utility values for the solutions of a multi-objective problem.

A solution on a better pareto-front is assigned a higher utility value. Additionally, if crowdsort is given as True crowding distances will also be taken into account. In more details, in the same pareto-front, solutions with higher crowding distances will have increased utility values.

Parameters:

Name Type Description Default
evals Tensor

Evaluation results, expected as a tensor with at least two dimensions. A 2-dimensional evals tensor is expected to be shaped as (numberOfSolutions, numberOfObjectives). Extra leftmost dimensions will be interpreted as batch dimensions.

required
objective_sense list

Expected as a list of strings, where each string is either 'min' or 'max'. The i-th item within this list represents the direction of the optimization for the i-th objective.

required
Source code in evotorch/operators/functional.py
def pareto_utility(evals: torch.Tensor, *, objective_sense: list, crowdsort: bool = True) -> torch.Tensor:
    """
    Compute utility values for the solutions of a multi-objective problem.

    A solution on a better pareto-front is assigned a higher utility value.
    Additionally, if `crowdsort` is given as True crowding distances will also
    be taken into account. In more details, in the same pareto-front,
    solutions with higher crowding distances will have increased utility
    values.

    Args:
        evals: Evaluation results, expected as a tensor with at least two
            dimensions. A 2-dimensional `evals` tensor is expected to be
            shaped as (numberOfSolutions, numberOfObjectives). Extra
            leftmost dimensions will be interpreted as batch dimensions.
        objective_sense: Expected as a list of strings, where each string
            is either 'min' or 'max'. The i-th item within this list
            represents the direction of the optimization for the i-th
            objective.
    Returns:
        A utility tensor. Considering the non-batched case (i.e. considering
        that `evals` was given as a 2-dimensional tensor), the i-th item
        within the returned utility tensor represents the utility value
        assigned to the i-th solution.
    """
    return _pareto_utility(evals, objective_sense, crowdsort)

simulated_binary_cross_over(parents, evals=None, *, eta, tournament_size=None, num_children=None, objective_sense=None)

Apply simulated binary cross-over (SBX) on the given parents.

If tournament_size is given, parents for the cross-over operation will be picked with the help of a tournament. Otherwise, the first half of the given parents will be the first set of parents, and the second half of the given parents will be the second set of parents.

The return value of this function is a new tensor containing the decision values of the child solutions.

Parameters:

Name Type Description Default
parents Tensor

A tensor with at least 2 dimensions, representing the decision values of the parent solutions. If this tensor has more than 2 dimensions, the extra leftmost dimension(s) will be considered as batch dimensions.

required
evals Optional[Tensor]

A tensor with at least 1 dimension, representing the evaluation results (i.e. fitnesses) of the parent solutions. If this tensor has more than 1 dimension, the extra leftmost dimension(s) will be considered as batch dimensions. If tournament_size is not given, evals can be left as None.

None
eta Union[float, Tensor]

The crowding index, expected as a real number. Bigger eta values result in children closer to their parents. If eta is given as an n-dimensional tensor instead of a scalar, those extra dimensions will be considered as batch dimensions.

required
tournament_size Optional[int]

If given as an integer that is greater than or equal to 1, the parents for the cross-over operation will be picked with the help of a tournament. In more details, each parent will be picked as the result of comparing multiple competing solutions, the number of these competing solutions being equal to this tournament_size. Please note that, if tournament_size is given as an integer, the arguments evals and objective_sense are also required. If tournament_size is left as None, the first half of parents will be the first set of parents, and the second half of parents will be the second set of parents.

None
num_children Optional[int]

Optionally the number of children to produce as the result of tournament selection and cross-over, as an even integer. If tournament selection is enabled (i.e. if tournament_size is an integer) but num_children is omitted, the number of children will be equal to the number of parents. If there is no tournament selection (i.e. if tournament_size is None), num_children is expected to be None.

None
objective_sense Optional[str]

Mandatory if tournament_size is not None. For when there is only one objective, objective_sense is expected as 'max' for when the goal of the optimization is to maximize evals, 'min' for when the goal of the optimization is to minimize evals. For when there are multiple objectives, objective_sense is expected as a list of strings, where each string is either 'min' or 'max'. If tournament_size is None, objective_sense can also be left as None.

None
Source code in evotorch/operators/functional.py
def simulated_binary_cross_over(
    parents: torch.Tensor,
    evals: Optional[torch.Tensor] = None,
    *,
    eta: Union[float, torch.Tensor],
    tournament_size: Optional[int] = None,
    num_children: Optional[int] = None,
    objective_sense: Optional[str] = None,
) -> torch.Tensor:
    """
    Apply simulated binary cross-over (SBX) on the given `parents`.

    If `tournament_size` is given, parents for the cross-over operation will
    be picked with the help of a tournament. Otherwise, the first half of the
    given `parents` will be the first set of parents, and the second half
    of the given `parents` will be the second set of parents.

    The return value of this function is a new tensor containing the decision
    values of the child solutions.

    Args:
        parents: A tensor with at least 2 dimensions, representing the decision
            values of the parent solutions. If this tensor has more than 2
            dimensions, the extra leftmost dimension(s) will be considered as
            batch dimensions.
        evals: A tensor with at least 1 dimension, representing the evaluation
            results (i.e. fitnesses) of the parent solutions. If this tensor
            has more than 1 dimension, the extra leftmost dimension(s) will be
            considered as batch dimensions. If `tournament_size` is not given,
            `evals` can be left as None.
        eta: The crowding index, expected as a real number. Bigger eta values
            result in children closer to their parents. If `eta` is given as
            an `n`-dimensional tensor instead of a scalar, those extra
            dimensions will be considered as batch dimensions.
        tournament_size: If given as an integer that is greater than or equal
            to 1, the parents for the cross-over operation will be picked
            with the help of a tournament. In more details, each parent will
            be picked as the result of comparing multiple competing solutions,
            the number of these competing solutions being equal to this
            `tournament_size`. Please note that, if `tournament_size` is given
            as an integer, the arguments `evals` and `objective_sense` are
            also required. If `tournament_size` is left as None, the first half
            of `parents` will be the first set of parents, and the second half
            of `parents` will be the second set of parents.
        num_children: Optionally the number of children to produce as the
            result of tournament selection and cross-over, as an even integer.
            If tournament selection is enabled (i.e. if `tournament_size` is
            an integer) but `num_children` is omitted, the number of children
            will be equal to the number of `parents`.
            If there is no tournament selection (i.e. if `tournament_size` is
            None), `num_children` is expected to be None.
        objective_sense: Mandatory if `tournament_size` is not None.
            For when there is only one objective, `objective_sense` is
            expected as 'max' for when the goal of the optimization is to
            maximize `evals`, 'min' for when the goal of the optimization is
            to minimize `evals`. For when there are multiple objectives,
            `objective_sense` is expected as a list of strings, where each
            string is either 'min' or 'max'.
            If `tournament_size` is None, `objective_sense` can also be left
            as None.
    Returns:
        Decision values of the child solutions, as a new tensor.
    """
    if tournament_size is None:
        if num_children is not None:
            raise ValueError(
                "`num_children` was received as something other than None."
                " However, `num_children` is expected only when a `tournament_size` is given,"
                " which seems to be omitted (i.e. which is None)."
            )
    else:
        # This is the case where the tournament selection feature is enabled.
        # We first ensure that the required arguments `evals` and `objective_sense` are available.
        if evals is None:
            raise ValueError(
                "When a `tournament_size` is given, the argument `evals` is also required."
                " However, it was received as None."
            )
        if num_children is None:
            # If `num_children` is not given, we make it equal to the number of `parents`.
            num_children = parents.shape[-2]
        if objective_sense is None:
            raise ValueError(
                "When a `tournament_size` is given, the argument `objective_sense` is also required."
                " However, it was received as None."
            )

        # Apply tournament selection on the original `parents`
        parents = tournament(
            parents,
            evals,
            num_tournaments=num_children,
            tournament_size=tournament_size,
            objective_sense=objective_sense,
            with_evals=False,
        )

    return _do_sbx(parents, eta)

take_best(values, evals, n=None, *, objective_sense, crowdsort=True)

Take the best solution, or the best n number of solutions.

Single-objective case. If the positional argument n is omitted (i.e. is left as None), the decision values and the evaluation result of the single best solution will be returned. If the positional argument n is provided, top-n solutions, together with their evaluation results, will be returned.

Multi-objective case. In the multi-objective case, the positional argument n is mandatory. With a valid value for n given, n number of solutions will be taken from the best pareto-fronts. If crowdsort is given as True (which is the default), crowding distances of the solutions within the same pareto-fronts will be an additional criterion when deciding which solutions to take. Like in the single-objective case, the decision values and the evaluation results of the taken solutions will be returned.

Support for ObjectArray. This function supports decision values expressed via instances of ObjectArray.

Parameters:

Name Type Description Default
values Union[Tensor, ObjectArray]

Decision values, expressed via a tensor with at least 2 dimensions or via an ObjectArray. If given as a tensor, extra leftmost dimensions will be interpreted as batch dimensions.

required
evals Tensor

Evaluation results tensor, with at least 1 dimension. Extra leftmost dimensions will be taken as batch dimensions.

required
n Optional[int]

If left as None, the single best solution will be taken. If given as an integer, this number of best solutions will be taken. Please note that, if the problem at hand has multiple objectives, this argument cannot be omitted.

None
objective_sense Union[str, list]

In the single-objective case, objective_sense is expected as a string 'min' or 'max', representing the direction of the optimization. In the multi-objective case, objective_sense is expected as a list of strings, each string being 'min' or 'max', representing the goal of optimization for each objective.

required
crowdsort bool

Relevant only when there are multiple objectives. If crowdsort is True, the crowding distances of the solutions within the given population will be an additional criterion when choosing the best n solution. If crowdsort is False, how many times a solution was dominated will be the only factor when deciding whether or not it is among the best n solutions.

True
Source code in evotorch/operators/functional.py
def take_best(
    values: Union[torch.Tensor, ObjectArray],
    evals: torch.Tensor,
    n: Optional[int] = None,
    *,
    objective_sense: Union[str, list],
    crowdsort: bool = True,
) -> tuple:
    """
    Take the best solution, or the best `n` number of solutions.

    **Single-objective case.**
    If the positional argument `n` is omitted (i.e. is left as None), the
    decision values and the evaluation result of the single best solution
    will be returned.
    If the positional argument `n` is provided, top-`n` solutions, together
    with their evaluation results, will be returned.

    **Multi-objective case.**
    In the multi-objective case, the positional argument `n` is mandatory.
    With a valid value for `n` given, `n` number of solutions will be taken
    from the best pareto-fronts. If `crowdsort` is given as True (which is
    the default), crowding distances of the solutions within the same
    pareto-fronts will be an additional criterion when deciding which
    solutions to take. Like in the single-objective case, the decision values
    and the evaluation results of the taken solutions will be returned.

    **Support for ObjectArray.**
    This function supports decision values expressed via instances of
    `ObjectArray`.

    Args:
        values: Decision values, expressed via a tensor with at least
            2 dimensions or via an `ObjectArray`. If given as a tensor,
            extra leftmost dimensions will be interpreted as batch
            dimensions.
        evals: Evaluation results tensor, with at least 1 dimension.
            Extra leftmost dimensions will be taken as batch dimensions.
        n: If left as None, the single best solution will be taken.
            If given as an integer, this number of best solutions will be
            taken. Please note that, if the problem at hand has multiple
            objectives, this argument cannot be omitted.
        objective_sense: In the single-objective case, `objective_sense` is
            expected as a string 'min' or 'max', representing the direction
            of the optimization. In the multi-objective case,
            `objective_sense` is expected as a list of strings, each string
            being 'min' or 'max', representing the goal of optimization for
            each objective.
        crowdsort: Relevant only when there are multiple objectives.
            If `crowdsort` is True, the crowding distances of the solutions
            within the given population will be an additional criterion
            when choosing the best `n` solution. If `crowdsort` is False,
            how many times a solution was dominated will be the only factor
            when deciding whether or not it is among the best `n` solutions.
    Returns:
        A tuple of the form `(decision_values, evaluation_results)`, where
        `decision_values` is the decision values (as a tensor or as an
        `ObjectArray`) for the taken solution(s), and `evaluation_results`
        is the evaluation results tensor for the taken solution(s).
    """
    if isinstance(values, ObjectArray):
        return _take_best_considering_objects(values, evals, n, objective_sense, crowdsort)

    if isinstance(objective_sense, str):
        multi_objective = False
    elif isinstance(objective_sense, Iterable):
        multi_objective = True
    else:
        raise TypeError("Unrecognized `objective_sense`: {repr(objective_sense)}")

    if n is None:
        if multi_objective:
            raise ValueError(
                "`objective_sense` not given as a string, implying that there are multiple objectives."
                " When there are multiple objectives, the argument `n` (i.e. number of solutions to take)"
                " must not be omitted. However, `n` was encountered as None."
            )
        return _take_single_best(values, evals, objective_sense)
    else:
        if multi_objective:
            return _take_multiple_best_with_multiobjective(values, evals, n, objective_sense, crowdsort)
        else:
            return _take_multiple_best(values, evals, n, objective_sense)

tournament(solutions, evals, *, num_tournaments, tournament_size, objective_sense, return_indices=False, with_evals=False, split_results=False)

Randomly organize pairs of tournaments and pick the winning solutions.

Hyperparameters regarding the tournament selection are num_tournaments (number of tournaments), and tournament_size (size of each tournament).

How does each tournament work? tournament_size number of solutions are randomly sampled from the given solutions, and then, the best solution among the sampled solutions is declared the winner. In the case of single-objective optimization, the best solution is the one with the best evaluation result (i.e. best fitness). In the case of multi-objective optimization, the best solution is the one within the best pareto-front.

How are multiple tournaments are organized? Two sets of tournaments are organized. Each set contains n number of tournaments, n being the half of num_tournaments. For example, let us assume that num_tournaments is 6. Then, we have:

First set of tournaments  : tournamentA, tournamentB, tournamentC
Second set of tournaments : tournamentD, tournamentE, tournamentF

In this organization of tournaments, the winner of tournamentA is meant for cross-over with the winner of tournamentD; the winner of tournamentB is meant for cross-over with the winner of tournamentE; and the winner of tournamentC is meant for cross-over with the winner of tournamentF.

While sampling the participants for these tournaments, it is ensured that the winner of tournamentA does not participate into tournamentD; the winner of tournamentB does not participate into tournamentE; and the winner of tournamentC does not participate into tournamentF. Therefore, each cross-over operation is applied on two different parent solutions.

How are the tournament results represented? The tournament results are returned in various forms. These various forms are as follows.

Results in the form of decision values. This is the default form of results (with return_indices=False, with_evals=False, split_results=False). Here, the results are expressed as a single tensor (or ObjectArray) of decision values. The first half of these decision values represent the first set of parents, and the second half of these decision values represent the second half of these decision values represent the second set of parents. For example, let us assume that the number of tournaments (num_tournaments) is configured as 6. In this case, the result is a decision values tensor with 6 rows (or an ObjectArray of length 6). In these results (let us call them resulting_values), the pairings for the cross-over operations are as follows: - resulting_values[0] and resulting_values[3]; - resulting_values[1] and resulting_values[4]; - resulting_values[2] and resulting_values[5].

Results in the form of indices. This form of results can be taken with arguments return_indices=True, with_evals=False, split_results=False. Here, the results are expressed as a single tensor of integers, each integer being the index of a solution within solutions. For example, let us assume that the number of tournaments (num_tournaments) is configured as 6. In this case, the result is a tensor of indices of length 6. In these results (let us call them resulting_indices), the pairings for the cross-over operations are as follows: - resulting_indices[0] and resulting_indices[3]; - resulting_indices[1] and resulting_indices[4]; - resulting_indices[2] and resulting_indices[5].

Results in the form of decision values and evaluations. This form of results can be taken with arguments return_indices=False, with_evals=True, split_results=False. Here, the results are expressed via a named tuple in the form (parent_values=..., parent_evals=...). In this tuple, parent_values stores a tensor (or an ObjectArray) representing the decision values of the picked solutions, and parent_evals stores the evaluation results as a tensor. For example, let us assume that the number of tournaments (num_tournaments) is 6. With this assumption, in the returned named tuple (let us call it result), the pairings for the cross-over operations are as follows: - result.parent_values[0] and result.parent_values[3]; - result.parent_values[1] and result.parent_values[4]; - result.parent_values[2] and result.parent_values[5]. For any solution result.parent_values[i], the evaluation result is stored by result.parent_evals[i].

Results with split parent solutions. This form of results can be taken with arguments return_indices=False, with_evals=False, split_results=True. The returned object is a named tuple in the form (parent1_values=..., parent2_values=...). In the returned named tuple (let us call it result), the pairings for the cross-over operations are as follows: - result.parent1_values[0] and result.parent2_values[0]; - result.parent1_values[1] and result.parent2_values[1]; - result.parent1_values[2] and result.parent2_values[2]; - and so on...

Results with split parent solutions and evaluations. This form of results can be taken with arguments return_indices=False, with_evals=True, split_results=True. The returned object is a named tuple, its attributes being parent1_values, parent1_evals, parent2_values, and parent2_evals. In the returned named tuple (let us call it result), the pairings for the cross-over operations are as follows: - result.parent1_values[0] and result.parent2_values[0]; - result.parent1_values[1] and result.parent2_values[1]; - result.parent1_values[2] and result.parent2_values[2]; - and so on... For any solution result.parent_values[i], the evaluation result is stored by result.parent_evals[i].

Parameters:

Name Type Description Default
solutions Union[Tensor, ObjectArray]

Decision values of the solutions. Can be a tensor with at least 2 dimensions (where extra leftmost dimensions are to be interpreted as batch dimensions), or an ObjectArray.

required
evals Tensor

Evaluation results of the solutions. In the single-objective case, this is expected as an at-least-1-dimensional tensor, the i-th item expressing the evaluation result of the i-th solution. In the multi-objective case, this is expected as an at-least-2-dimensional tensor, the (i,j)-th item expressing the evaluation result of the i-th solution according to the j-th objective. Extra leftmost dimensions are interpreted as batch dimensions.

required
num_tournaments int

Number of tournaments that will be applied. In other words, number of winners that will be picked.

required
tournament_size int

Number of solutions to be picked for the tournament

required
objective_sense Union[str, list]

A string or a list of strings, where (each) string has either the value 'min' for minimization or 'max' for maximization.

required
return_indices bool

If this is given as True, indices of the selected solutions will be returned, instead of their decision values.

False
with_evals bool

If this is given as True, evaluations of the selected solutions will be returned in addition to their decision values.

False
split_results bool

If this is given as True, tournament results will be split as first parents and second parents. If this is given as False, results will be stacked vertically, in the sense that the first half of the results are the first parents and the second half of the results are the second parents.

False
Source code in evotorch/operators/functional.py
def tournament(
    solutions: Union[torch.Tensor, ObjectArray],
    evals: torch.Tensor,
    *,
    num_tournaments: int,
    tournament_size: int,
    objective_sense: Union[str, list],
    return_indices: bool = False,
    with_evals: bool = False,
    split_results: bool = False,
) -> TournamentResult:
    """
    Randomly organize pairs of tournaments and pick the winning solutions.

    Hyperparameters regarding the tournament selection are
    `num_tournaments` (number of tournaments), and `tournament_size`
    (size of each tournament).

    **How does each tournament work?**
    `tournament_size` number of solutions are randomly sampled from the given
    `solutions`, and then, the best solution among the sampled solutions is
    declared the winner. In the case of single-objective optimization, the
    best solution is the one with the best evaluation result (i.e. best
    fitness). In the case of multi-objective optimization, the best solution
    is the one within the best pareto-front.

    **How are multiple tournaments are organized?**
    Two sets of tournaments are organized. Each set contains `n` number of
    tournaments, `n` being the half of `num_tournaments`.
    For example, let us assume that `num_tournaments` is 6. Then, we have:

    ```text
    First set of tournaments  : tournamentA, tournamentB, tournamentC
    Second set of tournaments : tournamentD, tournamentE, tournamentF
    ```

    In this organization of tournaments, the winner of tournamentA is meant
    for cross-over with the winner of tournamentD; the winner of tournamentB
    is meant for cross-over with the winner of tournamentE; and the winner of
    tournamentC is meant for cross-over with the winner of tournamentF.

    While sampling the participants for these tournaments, it is ensured that
    the winner of tournamentA does not participate into tournamentD; the
    winner of tournamentB does not participate into tournamentE; and the
    winner of tournamentC does not participate into tournamentF. Therefore,
    each cross-over operation is applied on two different parent solutions.

    **How are the tournament results represented?**
    The tournament results are returned in various forms. These various forms
    are as follows.

    **Results in the form of decision values.**
    This is the default form of results (with `return_indices=False`,
    `with_evals=False`, `split_results=False`). Here, the results are
    expressed as a single tensor (or `ObjectArray`) of decision values.
    The first half of these decision values represent the first set of
    parents, and the second half of these decision values represent the second
    half of these decision values represent the second set of parents.
    For example, let us assume that the number of tournaments
    (`num_tournaments`) is configured as 6. In this case, the result is a
    decision values tensor with 6 rows (or an `ObjectArray` of length 6).
    In these results (let us call them `resulting_values`), the pairings
    for the cross-over operations are as follows:
    - `resulting_values[0]` and `resulting_values[3]`;
    - `resulting_values[1]` and `resulting_values[4]`;
    - `resulting_values[2]` and `resulting_values[5]`.

    **Results in the form of indices.**
    This form of results can be taken with arguments `return_indices=True`,
    `with_evals=False`, `split_results=False`. Here, the results are
    expressed as a single tensor of integers, each integer being the index
    of a solution within `solutions`.
    For example, let us assume that the number of tournaments
    (`num_tournaments`) is configured as 6. In this case, the result is a
    tensor of indices of length 6.
    In these results (let us call them `resulting_indices`), the pairings
    for the cross-over operations are as follows:
    - `resulting_indices[0]` and `resulting_indices[3]`;
    - `resulting_indices[1]` and `resulting_indices[4]`;
    - `resulting_indices[2]` and `resulting_indices[5]`.

    **Results in the form of decision values and evaluations.**
    This form of results can be taken with arguments `return_indices=False`,
    `with_evals=True`, `split_results=False`. Here, the results are expressed
    via a named tuple in the form `(parent_values=..., parent_evals=...)`.
    In this tuple, `parent_values` stores a tensor (or an `ObjectArray`)
    representing the decision values of the picked solutions, and
    `parent_evals` stores the evaluation results as a tensor.
    For example, let us assume that the number of tournaments
    (`num_tournaments`) is 6. With this assumption, in the returned named
    tuple (let us call it `result`), the pairings for the cross-over
    operations are as follows:
    - `result.parent_values[0]` and `result.parent_values[3]`;
    - `result.parent_values[1]` and `result.parent_values[4]`;
    - `result.parent_values[2]` and `result.parent_values[5]`.
    For any solution `result.parent_values[i]`, the evaluation result
    is stored by `result.parent_evals[i]`.

    **Results with split parent solutions.**
    This form of results can be taken with arguments `return_indices=False`,
    `with_evals=False`, `split_results=True`. The returned object is a
    named tuple in the form `(parent1_values=..., parent2_values=...)`.
    In the returned named tuple (let us call it `result`), the pairings for
    the cross-over operations are as follows:
    - `result.parent1_values[0]` and `result.parent2_values[0]`;
    - `result.parent1_values[1]` and `result.parent2_values[1]`;
    - `result.parent1_values[2]` and `result.parent2_values[2]`;
    - and so on...

    **Results with split parent solutions and evaluations.**
    This form of results can be taken with arguments `return_indices=False`,
    `with_evals=True`, `split_results=True`. The returned object is a
    named tuple, its attributes being `parent1_values`, `parent1_evals`,
    `parent2_values`, and `parent2_evals`.
    In the returned named tuple (let us call it `result`), the pairings for
    the cross-over operations are as follows:
    - `result.parent1_values[0]` and `result.parent2_values[0]`;
    - `result.parent1_values[1]` and `result.parent2_values[1]`;
    - `result.parent1_values[2]` and `result.parent2_values[2]`;
    - and so on...
    For any solution `result.parent_values[i]`, the evaluation result
    is stored by `result.parent_evals[i]`.

    Args:
        solutions: Decision values of the solutions. Can be a tensor with
            at least 2 dimensions (where extra leftmost dimensions are to be
            interpreted as batch dimensions), or an `ObjectArray`.
        evals: Evaluation results of the solutions.
            In the single-objective case, this is expected as an
            at-least-1-dimensional tensor, the `i`-th item expressing
            the evaluation result of the `i`-th solution.
            In the multi-objective case, this is expected as an
            at-least-2-dimensional tensor, the `(i,j)`-th item
            expressing the evaluation result of the `i`-th solution
            according to the `j`-th objective.
            Extra leftmost dimensions are interpreted as batch dimensions.
        num_tournaments: Number of tournaments that will be applied.
            In other words, number of winners that will be picked.
        tournament_size: Number of solutions to be picked for the tournament
        objective_sense: A string or a list of strings, where (each) string
            has either the value 'min' for minimization or 'max' for
            maximization.
        return_indices: If this is given as True, indices of the selected
            solutions will be returned, instead of their decision values.
        with_evals: If this is given as True, evaluations of the selected
            solutions will be returned in addition to their decision values.
        split_results: If this is given as True, tournament results will be
            split as first parents and second parents. If this is given as
            False, results will be stacked vertically, in the sense that
            the first half of the results are the first parents and the
            second half of the results are the second parents.
    Returns:
        Selected solutions (or their indices, with or without their
        evaluation results).
    """
    if return_indices and with_evals:
        raise ValueError(
            "When `return_indices` is given as True, `with_evals` must be False."
            " However, `with_evals` was encountered as True."
        )

    if isinstance(solutions, ObjectArray):
        pick_fn = _pick_pairs_via_tournament_considering_objects
    elif isinstance(solutions, torch.Tensor):
        if isinstance(objective_sense, str):
            pick_fn = _pick_pairs_via_tournament_with_single_objective
        elif isinstance(objective_sense, Iterable):
            pick_fn = _pick_pairs_via_tournament_with_multi_objective
        else:
            raise TypeError(f"Unrecognized `objective_sense`: {repr(objective_sense)}")
    return pick_fn(
        solutions, evals, num_tournaments, tournament_size, objective_sense, return_indices, with_evals, split_results
    )

two_point_cross_over(parents, evals=None, *, tournament_size=None, num_children=None, objective_sense=None)

Apply two-point cross-over on the given parents.

Let us assume that we have the following two parent solutions:

         ________________________
parentA | a1  a2  a3  a4  a5  a6 |
parentB | b1  b2  b3  b4  b5  b6 |
        |________________________|

This cross-over operation will first randomly decide two cutting points:

         ________|____________|____
parentA | a1  a2 | a3  a4  a5 | a6 |
parentB | b1  b2 | b3  b4  b5 | b6 |
        |________|____________|____|
                 |            |

...and then form the following child solutions by recombining the decision values of the parents:

         ________|____________|____
child1  | a1  a2 | b3  b4  b5 | a6 |
child2  | b1  b2 | a3  a4  a5 | b6 |
        |________|____________|____|
                 |            |

If tournament_size is given, parents for the cross-over operation will be picked with the help of a tournament. Otherwise, the first half of the given parents will be the first set of parents, and the second half of the given parents will be the second set of parents.

The return value of this function is a new tensor containing the decision values of the child solutions.

Parameters:

Name Type Description Default
parents Tensor

A tensor with at least 2 dimensions, representing the decision values of the parent solutions. If this tensor has more than 2 dimensions, the extra leftmost dimension(s) will be considered as batch dimensions.

required
evals Optional[Tensor]

A tensor with at least 1 dimension, representing the evaluation results (i.e. fitnesses) of the parent solutions. If this tensor has more than 1 dimension, the extra leftmost dimension(s) will be considered as batch dimensions. If tournament_size is not given, evals can be left as None.

None
tournament_size Optional[int]

If given as an integer that is greater than or equal to 1, the parents for the cross-over operation will be picked with the help of a tournament. In more details, each parent will be picked as the result of comparing multiple competing solutions, the number of these competing solutions being equal to this tournament_size. Please note that, if tournament_size is given as an integer, the arguments evals and objective_sense are also required. If tournament_size is left as None, the first half of parents will be the first set of parents, and the second half of parents will be the second set of parents.

None
num_children Optional[int]

Optionally the number of children to produce as the result of tournament selection and cross-over, as an even integer. If tournament selection is enabled (i.e. if tournament_size is an integer) but num_children is omitted, the number of children will be equal to the number of parents. If there is no tournament selection (i.e. if tournament_size is None), num_children is expected to be None.

None
objective_sense Optional[str]

Mandatory if tournament_size is not None. For when there is only one objective, objective_sense is expected as 'max' for when the goal of the optimization is to maximize evals, 'min' for when the goal of the optimization is to minimize evals. For when there are multiple objectives, objective_sense is expected as a list of strings, where each string is either 'min' or 'max'. If tournament_size is None, objective_sense can also be left as None.

None
Source code in evotorch/operators/functional.py
def two_point_cross_over(
    parents: torch.Tensor,
    evals: Optional[torch.Tensor] = None,
    *,
    tournament_size: Optional[int] = None,
    num_children: Optional[int] = None,
    objective_sense: Optional[str] = None,
) -> torch.Tensor:
    """
    Apply two-point cross-over on the given `parents`.

    Let us assume that we have the following two parent solutions:

    ```text
             ________________________
    parentA | a1  a2  a3  a4  a5  a6 |
    parentB | b1  b2  b3  b4  b5  b6 |
            |________________________|
    ```

    This cross-over operation will first randomly decide two cutting points:

    ```text
             ________|____________|____
    parentA | a1  a2 | a3  a4  a5 | a6 |
    parentB | b1  b2 | b3  b4  b5 | b6 |
            |________|____________|____|
                     |            |
    ```

    ...and then form the following child solutions by recombining the decision
    values of the parents:

    ```text
             ________|____________|____
    child1  | a1  a2 | b3  b4  b5 | a6 |
    child2  | b1  b2 | a3  a4  a5 | b6 |
            |________|____________|____|
                     |            |
    ```

    If `tournament_size` is given, parents for the cross-over operation will
    be picked with the help of a tournament. Otherwise, the first half of the
    given `parents` will be the first set of parents, and the second half
    of the given `parents` will be the second set of parents.

    The return value of this function is a new tensor containing the decision
    values of the child solutions.

    Args:
        parents: A tensor with at least 2 dimensions, representing the decision
            values of the parent solutions. If this tensor has more than 2
            dimensions, the extra leftmost dimension(s) will be considered as
            batch dimensions.
        evals: A tensor with at least 1 dimension, representing the evaluation
            results (i.e. fitnesses) of the parent solutions. If this tensor
            has more than 1 dimension, the extra leftmost dimension(s) will be
            considered as batch dimensions. If `tournament_size` is not given,
            `evals` can be left as None.
        tournament_size: If given as an integer that is greater than or equal
            to 1, the parents for the cross-over operation will be picked
            with the help of a tournament. In more details, each parent will
            be picked as the result of comparing multiple competing solutions,
            the number of these competing solutions being equal to this
            `tournament_size`. Please note that, if `tournament_size` is given
            as an integer, the arguments `evals` and `objective_sense` are
            also required. If `tournament_size` is left as None, the first half
            of `parents` will be the first set of parents, and the second half
            of `parents` will be the second set of parents.
        num_children: Optionally the number of children to produce as the
            result of tournament selection and cross-over, as an even integer.
            If tournament selection is enabled (i.e. if `tournament_size` is
            an integer) but `num_children` is omitted, the number of children
            will be equal to the number of `parents`.
            If there is no tournament selection (i.e. if `tournament_size` is
            None), `num_children` is expected to be None.
        objective_sense: Mandatory if `tournament_size` is not None.
            For when there is only one objective, `objective_sense` is
            expected as 'max' for when the goal of the optimization is to
            maximize `evals`, 'min' for when the goal of the optimization is
            to minimize `evals`. For when there are multiple objectives,
            `objective_sense` is expected as a list of strings, where each
            string is either 'min' or 'max'.
            If `tournament_size` is None, `objective_sense` can also be left
            as None.
    Returns:
        Decision values of the child solutions, as a new tensor.
    """
    return multi_point_cross_over(
        parents,
        evals,
        num_points=2,
        num_children=num_children,
        tournament_size=tournament_size,
        objective_sense=objective_sense,
    )

utility(evals, *, objective_sense, ranking_method='centered')

Return utility values representing how good the evaluation results are.

A utility number is different from evals in the sense that, worst solution has the lowest utility, and the best solution has the highest utility, regardless of the objective sense ('min' or 'max'). On the other hand, the lowest number within evals could represent the fitness of the best solution or of the worst solution, depending on the objective sense.

The "centered" ranking is the same ranking method that was used within:

Tim Salimans, Jonathan Ho, Xi Chen, Szymon Sidor, Ilya Sutskever (2017).
Evolution Strategies as a Scalable Alternative to Reinforcement Learning

Parameters:

Name Type Description Default
evals Tensor

An at least 1-dimensional tensor that stores evaluation results (i.e. fitness values). Extra leftmost dimensions will be taken as batch dimensions.

required
objective_sense str

A string whose value is either 'min' or 'max', which represents the goal of the optimization (minimization or maximization).

required
ranking_method Optional[str]

Ranking method according to which the utilities will be computed. Currently, this function supports: 'centered' (worst one gets -0.5, best one gets 0.5); 'linear' (worst one gets 0.0, best one gets 1.0); 'raw' (evaluation results themselves are returned, with the additional behavior of flipping the signs if objective_sense is 'min', ensuring that the worst evaluation result gets the lowest value, and the best evaluation result gets the highest value). None also means 'raw'.

'centered'
Source code in evotorch/operators/functional.py
def utility(
    evals: torch.Tensor,
    *,
    objective_sense: str,
    ranking_method: Optional[str] = "centered",
) -> torch.Tensor:
    """
    Return utility values representing how good the evaluation results are.

    A utility number is different from `evals` in the sense that, worst
    solution has the lowest utility, and the best solution has the highest
    utility, regardless of the objective sense ('min' or 'max').
    On the other hand, the lowest number within `evals` could represent
    the fitness of the best solution or of the worst solution, depending
    on the objective sense.

    The "centered" ranking is the same ranking method that was used within:

    ```
    Tim Salimans, Jonathan Ho, Xi Chen, Szymon Sidor, Ilya Sutskever (2017).
    Evolution Strategies as a Scalable Alternative to Reinforcement Learning
    ```

    Args:
        evals: An at least 1-dimensional tensor that stores evaluation results
            (i.e. fitness values). Extra leftmost dimensions will be taken as
            batch dimensions.
        objective_sense: A string whose value is either 'min' or 'max', which
            represents the goal of the optimization (minimization or
            maximization).
        ranking_method: Ranking method according to which the utilities will
            be computed. Currently, this function supports:
            'centered' (worst one gets -0.5, best one gets 0.5);
            'linear' (worst one gets 0.0, best one gets 1.0);
            'raw' (evaluation results themselves are returned, with the
            additional behavior of flipping the signs if `objective_sense`
            is 'min', ensuring that the worst evaluation result gets the
            lowest value, and the best evaluation result gets the highest
            value). None also means 'raw'.
    Returns:
        Utility values, as a tensor whose shape is the same with the shape of
        `evals`.
    """
    if isinstance(objective_sense, str):
        return _utility(evals, objective_sense, ranking_method)
    elif isinstance(objective_sense, Iterable):
        raise ValueError(
            "The argument `objective_sense` was received as an iterable other than string,"
            " implying that the problem at hand has multiple objectives."
            " However, this `utility(...)` function does not support multiple objectives."
            " Consider using `pareto_utility(...)`."
        )
    else:
        raise TypeError(f"Unrecognized `objective_sense`: {repr(objective_sense)}")