QDax Overview¶
QDax has been designed to be modular yet flexible so it's easy for anyone to use and extend on the different state-of-the-art QD algortihms available.
For instance, MAP-Elites is designed to work with a few modular and simple components: container
, emitter
, and scoring_function
.
Key concepts¶
Container¶
The container
specifies the structure of archive of solutions to keep and the addition conditions associated with the archive.
Emitter¶
The emitter
component is responsible for generating new solutions to be evaluated. For example, new solutions can be generated with random mutations, gradient descent, or sampling from distributions as in evolutionary strategies.
Scoring Function¶
The scoring_function
defines the problem/task we want to solve and functions to evaluate the solutions. For example, the scoring_function
can be used to represent standard black-box optimization tasks such as rastrigin or RL tasks.
Design Choices¶
With this modularity, a user can easily swap out any one of the components and pass it to the MAPElites
class, avoiding having to re-implement all the steps of the algorithm.
Under one layer of abstraction, users have a bit more flexibility. QDax has similarities to the simple and commonly found ask
/tell
interface. The ask
function is similar to the emit
function in QDax and the tell
function is similar to the update
function in QDax. Likewise, the eval
of solutions is analogous to the scoring function
in QDax.
More importantly, QDax handles the archive management which is the key idea of QD algorihtms and not present or needed in standard optimization algorihtms or evolutionary strategies.
Code Example¶
# Initializes repertoire and emitter state
repertoire, emitter_state, random_key = map_elites.init(init_variables, centroids, random_key)
for i in range(num_iterations):
# generate new population with the emitter
genotypes, random_key = map_elites._emitter.emit(
repertoire, emitter_state, random_key
)
# scores/evaluates the population
fitnesses, descriptors, extra_scores, random_key = map_elites._scoring_function(
genotypes, random_key
)
# update repertoire
repertoire = repertoire.add(genotypes, descriptors, fitnesses)
# update emitter state
emitter_state = map_elites._emitter.state_update(
emitter_state=emitter_state,
repertoire=repertoire,
genotypes=genotypes,
fitnesses=fitnesses,
descriptors=descriptors,
extra_scores=extra_scores,
)