Skip to content

Commit

Permalink
🚧 Working on simplyfing the workflow.
Browse files Browse the repository at this point in the history
  • Loading branch information
amarrerod committed Jul 23, 2024
1 parent 1d03852 commit cc05e2a
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 63 deletions.
17 changes: 15 additions & 2 deletions digneapy/archives/_grid_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import numpy as np

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

from ._base_archive import Archive

Expand All @@ -37,6 +38,7 @@ def __init__(
self,
dimensions: Sequence[int],
ranges: Sequence[Tuple[float, float]],
attribute: str = "features",
instances: Optional[Iterable[Instance]] = None,
eps: float = 1e-6,
dtype=np.float64,
Expand All @@ -55,6 +57,7 @@ def __init__(
:math:`[-2,2]` (inclusive). ``ranges`` should be the same length as
``dims``.
instances (Optional[Iterable[Instance]], optional): Instances to pre-initialise the archive. Defaults to None.
attribute: str = Attribute of the Instances to compute the diversity.
eps (float, optional): Due to floating point precision errors, we add a small
epsilon when computing the archive indices in the :meth:`index_of`
method -- refer to the implementation `here. Defaults to 1e-6.
Expand All @@ -73,6 +76,16 @@ def __init__(
)

self._dimensions = np.asarray(dimensions)
self._inst_attrs = attribute
if attribute not in descriptor_strategies:
msg = f"describe_by {attribute} 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 = attribute
self._descriptor_strategy = descriptor_strategies[attribute]

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

Expand All @@ -239,7 +252,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([inst.descriptor for inst in iterable])
indeces = self.index_of(self._descriptor_strategy(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
2 changes: 0 additions & 2 deletions digneapy/qd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from digneapy.qd._desc_strategies import (
DescStrategy,
descriptor_strategies,
features_strategy,
instance_strategy,
performance_strategy,
rdstrat,
Expand All @@ -24,7 +23,6 @@
__all__ = [
"NS",
"CMA_ME",
"features_strategy",
"performance_strategy",
"instance_strategy",
"descriptor_strategies",
Expand Down
21 changes: 17 additions & 4 deletions digneapy/qd/_desc_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,27 @@ def decorate(func: DescStrategy):
return decorate


def features_strategy(iterable: Iterable[Instance]) -> np.ndarray:
"""It generates the feature descriptor of an instance
def __property_strategy(attr: str):
"""Returns a np.ndarray with the information required of the instances
Args:
iterable (Iterable[Instance]): Instances to describe
Returns:
np.ndarray: Array of the feature descriptors of each instance
"""
return np.asarray([i.features for i in iterable])
try:
if attr not in ("features", "descriptor"):
raise AttributeError()
except AttributeError:
raise ValueError(
f"Object of class Instance does not have a property named {attr}"
)

def strategy(iterable: Iterable[Instance]) -> np.ndarray:
return np.asarray([getattr(i, attr) for i in iterable])

return strategy


def performance_strategy(iterable: Iterable[Instance]) -> np.ndarray:
Expand Down Expand Up @@ -85,9 +96,11 @@ def instance_strategy(iterable: Iterable[Instance]) -> np.ndarray:
- features --> Creates a np.ndarray with all the features of the instances.
- performance --> Creates a np.ndarray with the mean performance score of each solver over the instances.
- instance --> Creates a np.ndarray with the whole instance as its self descriptor.
- descriptor --> Creates a np.ndarray with all the transformed descriptors of the instances. Only when using a Transformer.
"""
descriptor_strategies: MutableMapping[str, DescStrategy] = {
"features": features_strategy,
"features": __property_strategy(attr="features"),
"performance": performance_strategy,
"instance": instance_strategy,
"descriptor": __property_strategy(attr="descriptor"),
}
5 changes: 2 additions & 3 deletions digneapy/qd/_novelty_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@

from digneapy.archives import Archive
from digneapy.core import Instance
from digneapy.qd._desc_strategies import (descriptor_strategies,
features_strategy)
from digneapy.qd._desc_strategies import descriptor_strategies
from digneapy.transformers import SupportsTransform


Expand Down Expand Up @@ -54,7 +53,7 @@ def __init__(
msg = f"describe_by {descriptor} not available in {self.__class__.__name__}.__init__. Set to features by default"
print(msg)
self._describe_by = "features"
self._descriptor_strategy = features_strategy
self._descriptor_strategy = descriptor_strategies["features"]
else:
self._describe_by = descriptor
self._descriptor_strategy = descriptor_strategies[descriptor]
Expand Down
79 changes: 27 additions & 52 deletions examples/evolve_nn_pytorch.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
@Desc : None
"""

import copy
from collections import deque
from typing import Optional

import numpy as np
import pandas as pd

from digneapy import Direction
from digneapy.archives import Archive
from digneapy.archives import Archive, GridArchive
from digneapy.domains.knapsack import KPDomain
from digneapy.generators import EIG
from digneapy.operators.replacement import first_improve_replacement
Expand Down Expand Up @@ -50,54 +50,31 @@ class NSEval:
def __init__(self, features_info, resolution: int = 20):
self.resolution = resolution
self.features_info = features_info
self.hypercube = [
np.linspace(start, stop, self.resolution) for start, stop in features_info
]
self.kp_domain = KPDomain(dimension=50, capacity_approach="percentage")
self.portfolio = deque([default_kp, map_kp, miw_kp, mpw_kp])

def __save_instances(self, filename, generated_instances):
"""Writes the generated instances into a CSV file
Args:
filename (str): Filename
generated_instances (iterable): Iterable of instances
"""
features = [
"target",
"capacity",
"max_p",
"max_w",
"min_p",
"min_w",
"avg_eff",
"mean",
"std",
]
with open(filename, "w") as file:
file.write(",".join(features) + "\n")
for solver, descriptors in generated_instances.items():
for desc in descriptors:
content = solver + "," + ",".join(str(f) for f in desc) + "\n"
file.write(content)

def __call__(self, transformer: TorchNN, filename: Optional[str] = None):
def __call__(self, transformer: TorchNN):
"""This method runs the Novelty Search using a NN as a transformer
for searching novelty. It generates KP instances for each of the solvers in
the portfolio [Default, MaP, MiW, MPW] and calculates how many bins of the
8D-feature hypercube are occupied.
Args:
transformer (TorchNN): Transformer to reduce a 8D feature vector into a 2D vector.
filename (str, optional): Filename to store the instances. Defaults to None.
Returns:
int: Number of bins occupied. The maximum value if 8 x R.
"""
gen_instances = {s.__name__: [] for s in self.portfolio}
gen_instances = {
s.__name__: GridArchive(
dimensions=(self.resolution,) * 8,
ranges=self.features_info,
attribute="features",
)
for s in self.portfolio
}
for i in range(len(self.portfolio)):
self.portfolio.rotate(i) # This allow us to change the target on the fly

eig = EIG(
pop_size=10,
generations=1000,
Expand All @@ -111,24 +88,22 @@ def __call__(self, transformer: TorchNN, filename: Optional[str] = None):
replacement=first_improve_replacement,
transformer=transformer,
)
archive, solution_set = eig()
descriptors = [list(i.descriptor) for i in solution_set]
gen_instances[self.portfolio[0].__name__].extend(descriptors)
if any(len(sequence) != 0 for sequence in gen_instances.values()):
self.__save_instances(filename, gen_instances)

# Here we gather all the instances together to calculate the metric
coverage = {k: set() for k in range(8)}
for (
solver,
descriptors,
) in gen_instances.items(): # For each set of instances
for desc in descriptors: # For each descriptor in the set
for i, f in enumerate(desc): # Location of the ith feature
coverage[i].add(np.digitize(f, self.hypercube[i]))

f = sum(len(s) for s in coverage.values())
return f
_, solution_set = eig()
print(solution_set)
if len(solution_set) != 0:
gen_instances[self.portfolio[0].__name__].extend(
copy.deepcopy(solution_set)
)

for solver, hypercube in gen_instances.items():
print(f"Solver {solver} -> {hypercube.coverage}")

combined_coverage = [
list(hypercube.filled_cells) for hypercube in gen_instances.values()
]

print(combined_coverage)
return 0


def main():
Expand Down

0 comments on commit cc05e2a

Please sign in to comment.