Manipulating Solutions¶
For many advanced use-cases in EvoTorch, it's essential to directly work with populations of solutions. In EvoTorch, solutions are represented through the Solution class, and groups (populations) of solutions are represented through the SolutionBatch class. This tutorial will demonstrate a variety of features of these classes. To begin, consider the simple sphere problem:
from evotorch import Problem, Solution, SolutionBatch
import torch
def sphere(x: torch.Tensor) -> torch.Tensor:
return torch.sum(x.pow(2.))
problem = Problem(
'min',
sphere,
solution_length = 2,
initial_bounds = (-1, 1),
)
Creating and using SolutionBatch instances¶
A SolutionBatch instance can be generated directly from the problem itself, with decision values sampled uniformly according to the initial_bounds
parameter. Calling
will create batch
, an instance of SolutionBatch consisting of 5 Solution instances. These solutions can be viewed in terms of their \(5 \times 2\) tensor representation using the values
attribute:
Output
Note that the decision values are in the initial range (-1, 1)
that was specified when the problem was created. The fitness values of the batch can be accessed with the evals
attribute, and will initially be filled with nan
values as the population has not yet been evaluated
Calling problem.evaluate(batch)
will fill the fitness values with solutions' computed fitnesses:
The decision values can be overidden using the set_values
function. Setting them with random gaussian values,
will update the values
Output
and clear the fitness values
The fitness values can also be set with the set_evals
method to give updated fitnesses,
The values
and evals
properties yield read-only tensors that cannot be modified. In some advanced use-cases, you can directly access the underlying tensors using the access_values
and access_evals
methods. However, care should be taken when using these methods. By default, SolutionBatch assumes that a call to access_values
means that the underlying tensor will be modified, and the fitnesses are therefore cleared and reset to nan
,
However, this behaviour can be disabled with the keep_evals
flag, e.g.
batch.set_evals(problem.make_uniform(5))
values = batch.access_values(keep_evals = True)
print(batch.evals)
If you wish to make an exact copy of a SolutionBatch instance, you can do so with
The new instance cloned_batch
will have identical values
and evals
to batch
, but will share no memory locations, and can be freely modified without modifying its parent batch
.
Slicing and Indexing¶
SolutionBatch instances can be arbitrarily sliced and indexed. For example,
will return a SolutionBatch instance last_3
which is a view of batch
e.g. memory data locations with it. Thus we can see that the decision values are exactly the decision values of the last 3 solutions of batch
and the evaluations are exactly the evaluations of the last 3 solutions of batch
A similar behaviour can be achieved with a list of indexes e.g. subbatch = batch[[0, 1, 3]]
will give a solution batch that is a view of the solutions at indices 0, 1 and 3. When a sub-batch is updated, as can be achieved using set_values
and set_evals
or access_values
and access_evals
followed by modification,
the parent solution batch batch
is also modified.
Output
SolutionBatch instances of the same Problem class can also be arbitrarily concatenated. For example, if we now create a second SolutionBatch with 3 instances,
and then concatenate last_3
with second_batch
, using either the SolutionBatch.cat
or batch.concat
methods,
Output
Creating a new instance of SolutionBatch, new_batch
, in this way is copying, so further modification of new_batch
will not modify the parent batches batch
and second_batch
.
Accessing and Manipulating individual Solutions¶
An individual Solution instance can be accessed by indexing into the batch. For example, indexing the first Solution will yield the solution at index 1 of batch
A Solution instance has the same properties values
and evals
and methods set_values
, set_evals
, access_values
and access_evals
as SolutionBatch, except that they now work in one dimension, rather than two. For example, accessing the values yields a 1-dimensional read-only tensor of decision values
A Solution instance is a view of its parent SolutionBatch, so modifying it will not only modify the solution,
but will also modify batch
,
Output
A Solution instance can also be cloned with the clone()
method e.g.
The cloned solution is no longer a view of batch
and can be freely modified. To obtain the new, single-solution, SolutionBatch instance of the cloned solution, you can use cloned_sol.to_batch()
.