Skip to content

Commit

Permalink
♻️ Reduce computation in GridArchive
Browse files Browse the repository at this point in the history
  • Loading branch information
amarrerod committed Jul 24, 2024
1 parent fb3c923 commit 6426ed0
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 37 deletions.
21 changes: 8 additions & 13 deletions digneapy/archives/_grid_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import numpy as np

from digneapy.core import Instance
from digneapy.qd._desc_strategies import descriptor_strategies

from ._base_archive import Archive

Expand All @@ -38,7 +37,7 @@ def __init__(
self,
dimensions: Sequence[int],
ranges: Sequence[Tuple[float, float]],
descriptor: str = "features",
descriptor: str,
instances: Optional[Iterable[Instance]] = None,
eps: float = 1e-6,
dtype=np.float64,
Expand Down Expand Up @@ -76,15 +75,11 @@ def __init__(
)

self._dimensions = np.asarray(dimensions)
self._inst_attrs = descriptor
if descriptor not in descriptor_strategies:
msg = f"describe_by {descriptor} not available in {self.__class__.__name__}.__init__. Set to features by default"
print(msg)
self._inst_attrs = "features"
self._descriptor_strategy = descriptor_strategies["features"]
else:
self._inst_attrs = descriptor
self._descriptor_strategy = descriptor_strategies[descriptor]
if descriptor == "":
raise ValueError(
"The descriptor must be one property available in the Instance class."
)
self._inst_attr = descriptor

ranges = list(zip(*ranges))
self._lower_bounds = np.array(ranges[0], dtype=dtype)
Expand Down Expand Up @@ -234,7 +229,7 @@ def append(self, instance: Instance):
TypeError: ``instance`` is not a instance of the class Instance.
"""
if isinstance(instance, Instance):
index = self.index_of(self._descriptor_strategy([instance]))
index = self.index_of(getattr(instance, self._inst_attr))
if index not in self._grid or instance > self._grid[index]:
self._grid[index] = copy.deepcopy(instance)

Expand All @@ -252,7 +247,7 @@ def extend(self, iterable: Iterable[Instance], *args, **kwargs):
msg = "Only objects of type Instance can be inserted into a GridArchive"
raise TypeError(msg)

indeces = self.index_of(self._descriptor_strategy(iterable))
indeces = self.index_of([getattr(i, self._inst_attr) for i in iterable])
for idx, instance in zip(indeces, iterable, strict=True):
if idx not in self._grid or instance.fitness > self._grid[idx].fitness:
self._grid[idx] = copy.deepcopy(instance)
Expand Down
5 changes: 3 additions & 2 deletions digneapy/generators/_map_elites_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ def _evaluate_population(self, population: Iterable[Instance]):
individual.portfolio_scores = tuple(solvers_scores)
individual.p = self._performance_fn(avg_p_solver)
individual.fitness = individual.p
individual.descriptor = tuple(self._descriptor_strategy(individual))

ind_features = tuple(self._descriptor_strategy(individual))
individual.features = ind_features
individual.descriptor = ind_features
return population

def _run(self, verbose: bool = False):
Expand Down
6 changes: 5 additions & 1 deletion tests/generators/test_map_elites.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ def test_map_elites_domain(
domain_cls, portfolio, desc_size, expected_str, expected_repr
):
dimension = 100
archive = GridArchive(dimensions=(10,) * desc_size, ranges=[(0, 1e4)] * desc_size)
archive = GridArchive(
dimensions=(10,) * desc_size,
ranges=[(0, 1e4)] * desc_size,
descriptor="features",
)
domain = domain_cls(dimension=dimension)
assert domain.dimension == dimension

Expand Down
74 changes: 53 additions & 21 deletions tests/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,50 +180,66 @@ def grid_5d():
(-1.0, 1.0),
(-1.0, 1.0),
],
descriptor="features",
)


def test_grid_archive_raises():
# Raises ValueError when dimension < 1
with pytest.raises(ValueError):
_ = GridArchive(dimensions=[], ranges=[])
_ = GridArchive(dimensions=[], ranges=[], descriptor="features")

# Raises because instances are not of type Instance
with pytest.raises(TypeError):
instances = np.random.randint(low=0, high=10, size=(10, 2))
_ = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], instances=instances
dimensions=(2, 2),
ranges=[(0, 10), (0, 10)],
instances=instances,
descriptor="features",
)

# Raises because instance is not of type Instance
with pytest.raises(TypeError):
instance = np.random.randint(low=0, high=10, size=2)
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
archive.append(instance)

# Raises out-of lower bound
with pytest.raises(ValueError):
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
_ = archive.lower_i(-1)

# Raises out-of lower bound
with pytest.raises(ValueError):
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
_ = archive.lower_i(100)

# Raises out-of upper bound
with pytest.raises(ValueError):
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
_ = archive.upper_i(-1)

# Raises out-of upper bound
with pytest.raises(ValueError):
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
_ = archive.upper_i(100)

# Raises index shape is not valid
with pytest.raises(ValueError):
archive = GridArchive(dimensions=(2, 2), ranges=[(0, 10), (0, 10)])
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], descriptor="features"
)
descriptors = np.random.randint(low=0, high=10, size=(10, 10))
_ = archive.index_of(descriptors)

Expand All @@ -232,10 +248,13 @@ def test_grid_archive_populated():
instances = []
for _ in range(10):
instance = Instance()
instance.descriptor = np.random.randint(low=0, high=10, size=2)
instance.features = np.random.randint(low=0, high=10, size=2)
instances.append(instance)
archive = GridArchive(
dimensions=(2, 2), ranges=[(0, 10), (0, 10)], instances=instances
dimensions=(2, 2),
ranges=[(0, 10), (0, 10)],
instances=instances,
descriptor="features",
)
assert len(archive) != 0
assert all(archive.lower_i(i) == 0 for i in range(len(archive.bounds)))
Expand Down Expand Up @@ -266,22 +285,25 @@ def test_grid_5d_storage(grid_5d):
inst = Instance(
[], fitness=0.0, p=np.random.randint(0, 100), s=np.random.random()
)
inst.descriptor = tuple(np.random.random(size=5))
inst.features = tuple(np.random.random(size=5))
instances.append(inst)

assert len(grid_5d) == 0
grid_5d.extend(instances)
assert len(grid_5d) == len(instances)

instance = Instance()
instance.descriptor = tuple(np.random.random(size=5))
instance.features = tuple(np.random.random(size=5))
grid_5d.append(instance)
assert len(grid_5d) == len(instances) + 1


def test_grid_limits():
archive = GridArchive(
dimensions=(5, 5), ranges=[(0, 100), (0, 100)], dtype=np.int32
dimensions=(5, 5),
ranges=[(0, 100), (0, 100)],
dtype=np.int32,
descriptor="features",
)

assert archive.coverage == 0.0 # Empty archive
Expand All @@ -293,7 +315,7 @@ def test_grid_limits():
inst = Instance(
[], fitness=0.0, p=np.random.randint(0, 100), s=np.random.random()
)
inst.descriptor = tuple(np.random.randint(low=0, high=100, size=2))
inst.features = tuple(np.random.randint(low=0, high=100, size=2))
instances.append(inst)

assert len(archive) == 0
Expand All @@ -305,7 +327,10 @@ def test_grid_limits():

def test_grid_extend_outside_bounds():
archive = GridArchive(
dimensions=(5, 5), ranges=[(0, 100), (0, 100)], dtype=np.int32
dimensions=(5, 5),
ranges=[(0, 100), (0, 100)],
dtype=np.int32,
descriptor="features",
)
instances = []
max_allowed = 25
Expand All @@ -317,7 +342,7 @@ def test_grid_extend_outside_bounds():
p=np.random.randint(100, 1000),
s=np.random.random(),
)
inst.descriptor = tuple(np.random.randint(low=1000, high=10000, size=2))
inst.features = tuple(np.random.randint(low=1000, high=10000, size=2))
instances.append(inst)

assert len(archive) == 0
Expand All @@ -331,7 +356,10 @@ def test_grid_extend_outside_bounds():

def test_grid_extend_under_bounds():
archive = GridArchive(
dimensions=(5, 5), ranges=[(100, 1000), (100, 1000)], dtype=np.int32
dimensions=(5, 5),
ranges=[(100, 1000), (100, 1000)],
dtype=np.int32,
descriptor="features",
)
instances = []
max_allowed = 25
Expand All @@ -343,7 +371,7 @@ def test_grid_extend_under_bounds():
p=np.random.randint(100, 1000),
s=np.random.random(),
)
inst.descriptor = tuple(np.random.randint(low=1, high=99, size=2))
inst.features = tuple(np.random.randint(low=1, high=99, size=2))
instances.append(inst)

assert len(archive) == 0
Expand All @@ -368,12 +396,13 @@ def test_grid_with_kp_instances():
(400, 610),
(240, 330),
],
descriptor="features",
)
n_instances = 1_000
domain = KPDomain(dimension=50, capacity_approach="percentage")
instances = [domain.generate_instance() for _ in range(n_instances)]
for instance in instances:
instance.descriptor = domain.extract_features(instance)
instance.features = domain.extract_features(instance)

assert len(archive) == 0
assert archive.n_cells == np.prod(np.array((20,) * 8))
Expand All @@ -386,11 +415,14 @@ def test_grid_archive_getitem():
instances = []
for _ in range(1000):
instance = Instance()
instance.descriptor = np.random.randint(low=0, high=10, size=2)
instance.features = np.random.randint(low=0, high=10, size=2)
instances.append(instance)

archive = GridArchive(
dimensions=(10, 10), ranges=[(0, 10), (0, 10)], instances=instances
dimensions=(10, 10),
ranges=[(0, 10), (0, 10)],
instances=instances,
descriptor="features",
)
results = archive[[0, 11], [0, 5]]
assert isinstance(results, dict)
Expand Down

0 comments on commit 6426ed0

Please sign in to comment.