CQD Score

This script shows an example of how to use cqd_score() to compute the Continuous QD Score metric. Introduced in Kent 2022, the CQD Score is a metric that accounts for tradeoffs between objective values and distance to desired measure values. Notably, the CQD Score does not depend on the discretization/tessellation of the archive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""Example of computing the CQD score on an archive.

Install the following dependencies before running this example:
    pip install fire
"""

import fire
import numpy as np

from ribs.archives import GridArchive, cqd_score
from ribs.emitters import EvolutionStrategyEmitter
from ribs.schedulers import Scheduler


def main(itrs: int = 1000) -> None:
    """Runs CMA-ME on a basic function, computing CQD score along the way."""
    # Set up pyribs components.
    archive = GridArchive(
        solution_dim=10,
        dims=[100, 100],
        ranges=[(-1, 1), (-1, 1)],
    )
    emitters = [
        EvolutionStrategyEmitter(
            archive,
            x0=[0.0] * 10,
            sigma0=0.1,
            batch_size=36,
        )
        for _ in range(3)
    ]
    scheduler = Scheduler(archive, emitters)

    # Needed for sampling target points.
    rng = np.random.default_rng(42)

    for itr in range(1, itrs + 1):
        solutions = scheduler.ask()
        # Negative sphere function with slight offset.
        objectives = 2.0 - np.sum(np.square(solutions), axis=1)
        measures = solutions[:, :2]
        scheduler.tell(objectives, measures)

        if itr % 100 == 0 or itr == itrs:
            cqd_iterations = 5

            # Here, 200 target points are sampled in measure space within the bounds of
            # the archive. Note that not all archives have lower and upper bounds. For
            # example, ProximityArchive is unstructured, so its lower and upper bounds
            # adjust over time to match the solutions it contains. This differs from
            # GridArchive, where the bounds are set in advance. Thus, for
            # ProximityArchive, it does not make sense to sample target points within
            # its lower and upper bounds. Instead, if using ProximityArchive, there
            # should be a predefined lower and upper bound within which to sample target
            # points. Note that target points can also be generated in other ways, i.e.,
            # they do not have to be sampled uniformly within a hyperrectangle as is
            # done here.
            target_points = rng.uniform(
                low=archive.lower_bounds,
                high=archive.upper_bounds,
                size=(cqd_iterations, 200, archive.measure_dim),
            )

            result = cqd_score(
                archive,
                iterations=cqd_iterations,
                target_points=target_points,
                penalties=5,
                obj_min=0.0,
                obj_max=2.0,
                dist_max=np.linalg.norm(archive.upper_bounds - archive.lower_bounds),
            )

            # The `result` is an instance of CQDScoreResult that contains a number of
            # attributes. The most relevant will be the mean CQD score and the scores
            # computed across the individual iterations.
            print(f"----- Iteration {itr} -----")
            print("CQD score:", result.mean)
            print("Scores on each iteration:", result.scores)


if __name__ == "__main__":
    fire.Fire(main)