Skip to content

Commit

Permalink
🧪 🔒️ Update exceptions to specific types. Includes tests for MapElite…
Browse files Browse the repository at this point in the history
…s and Autoencoders
  • Loading branch information
amarrerod committed Jun 19, 2024
1 parent c65a7e2 commit bd3f12b
Show file tree
Hide file tree
Showing 31 changed files with 223 additions and 103 deletions.
2 changes: 1 addition & 1 deletion digneapy/archives/_grid_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def append(self, i: Instance):
i (Instance): Instace to be inserted
Raises:
AttributeError: ``instance`` is not a instance of the class Instance.
TypeError: ``instance`` is not a instance of the class Instance.
"""
if isinstance(i, Instance):
index = self.index_of(np.asarray(i.descriptor))
Expand Down
8 changes: 4 additions & 4 deletions digneapy/core/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def __init__(
p = float(p)
s = float(s)
except ValueError:
raise AttributeError(
raise ValueError(
"The fitness, p and s parameters must be convertible to float"
)
self._fitness = fitness
Expand All @@ -58,7 +58,7 @@ def p(self, performance: float):
except ValueError:
# if performance != 0.0 and not float(performance):
msg = f"The performance value {performance} is not a float in 'p' setter of class {self.__class__.__name__}"
raise AttributeError(msg)
raise ValueError(msg)
self._p = performance

@property
Expand All @@ -72,7 +72,7 @@ def s(self, novelty: float):
except ValueError:
# if novelty != 0.0 and not float(novelty):
msg = f"The novelty value {novelty} is not a float in 's' setter of class {self.__class__.__name__}"
raise AttributeError(msg)
raise ValueError(msg)
self._s = novelty

@property
Expand All @@ -86,7 +86,7 @@ def fitness(self, f: float):
except ValueError:
# if f != 0.0 and not float(f):
msg = f"The fitness value {f} is not a float in fitness setter of class {self.__class__.__name__}"
raise AttributeError(msg)
raise ValueError(msg)

self._fitness = float(f)

Expand Down
2 changes: 1 addition & 1 deletion digneapy/core/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def evaluate(self, individual: Sequence | Solution) -> Tuple[float]:
individual (Sequence | Solution): Individual to evaluate
Raises:
AttributeError: Raises an error if the len(individual) != len(instance) / 2
ValueError: Raises an error if the len(individual) != len(instance) / 2
Returns:
Tuple[float]: Profit
Expand Down
6 changes: 4 additions & 2 deletions digneapy/domains/bin_packing.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def evaluate(self, individual: Sequence | Solution) -> tuple[float]:
Tuple[float]: Falkenauer Fitness
"""
if len(individual) != self._dimension:
msg = f"Mismatch between individual variables and instance variables in {self.__class__.__name__}"
msg = f"Mismatch between individual variables ({len(individual)}) and instance variables ({self._dimension}) in {self.__class__.__name__}"
raise ValueError(msg)

used_bins = np.max(individual).astype(int) + 1
Expand Down Expand Up @@ -145,7 +145,9 @@ def __init__(
else:
self._capacity_approach = capacity_approach

bounds = [(self._min_i, self._max_i) for _ in range(self._dimension)]
bounds = [(1.0, self._max_capacity)] + [
(self._min_i, self._max_i) for _ in range(self._dimension)
]
super().__init__(dimension=dimension, bounds=bounds, name="BPP")

@property
Expand Down
6 changes: 3 additions & 3 deletions digneapy/domains/knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@ def evaluate(self, individual: Sequence | Solution) -> tuple[float]:
individual (Sequence | Solution): Individual to evaluate
Raises:
AttributeError: Raises an error if the len(individual) != len(profits or weights)
ValueError: Raises an error if the len(individual) != len(profits or weights)
Returns:
Tuple[float]: Profit
"""

if len(individual) != len(self.profits):
msg = f"Mismatch between individual variables and instance variables in {self.__class__.__name__}"
raise AttributeError(msg)
raise ValueError(msg)

profit = 0.0
packed = 0
Expand Down Expand Up @@ -141,7 +141,7 @@ def __init__(
else:
self._capacity_approach = capacity_approach

bounds = [(0.0, self.max_capacity)] + [
bounds = [(1.0, self.max_capacity)] + [
(min_w, max_w) if i % 2 == 0 else (min_p, max_p)
for i in range(2 * dimension)
]
Expand Down
10 changes: 5 additions & 5 deletions digneapy/generators/_eig.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def __init__(
The target solver is the first solver in the portfolio. Defaults to True.
Raises:
AttributeError: Raises error if phi is not in the range [0.0-1.0]
ValueError: Raises error if phi is not in the range [0.0-1.0]
"""
super().__init__(archive, s_set, k, descriptor, transformer)
self.pop_size = pop_size
Expand Down Expand Up @@ -105,11 +105,11 @@ def __init__(
try:
phi = float(phi)
except ValueError:
raise AttributeError("Phi must be a float number in the range [0.0-1.0].")
raise ValueError("Phi must be a float number in the range [0.0-1.0].")

if phi < 0.0 or phi > 1.0:
msg = f"Phi must be a float number in the range [0.0-1.0]. Got: {phi}."
raise AttributeError(msg)
raise ValueError(msg)
self.phi = phi

@property
Expand Down Expand Up @@ -188,9 +188,9 @@ def __diverse_biased_filter(self, instance: Instance) -> bool:

def _run(self, verbose: bool = False):
if self.domain is None:
raise AttributeError("You must specify a domain to run the generator.")
raise ValueError("You must specify a domain to run the generator.")
if len(self.portfolio) == 0:
raise AttributeError(
raise ValueError(
"The portfolio is empty. To run the generator you must provide a valid portfolio of solvers"
)
self.population = [
Expand Down
13 changes: 8 additions & 5 deletions digneapy/generators/_map_elites_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ def __init__(
msg = f"strategy {strategy} not available in {self.__class__.__name__}.__init__. Set to features by default"
print(msg)
strategy = "features"

self._descriptor = strategy
match strategy:
case "features":
self._descriptor_strategy = self._domain.extract_features
case _:
self._descriptor_strategy = descriptor_strategies[strategy]
self._descriptor = strategy

self._stats_fitness = tools.Statistics(key=lambda ind: ind.fitness)
self._stats_fitness.register("avg", np.mean)
Expand All @@ -76,11 +75,15 @@ def archive(self):
def log(self) -> tools.Logbook:
return self._logbook

def __str__(self):
return "MapElites()"
def __str__(self) -> str:
port_names = [s.__name__ for s in self._portfolio]
domain_name = self._domain.name if self._domain is not None else "None"
return f"MapElites(descriptor={self._descriptor},pop_size={self._init_pop_size},gen={self._generations},domain={domain_name},portfolio={port_names!r})"

def __repr__(self) -> str:
return "MapElites<>"
port_names = [s.__name__ for s in self._portfolio]
domain_name = self._domain.name if self._domain is not None else "None"
return f"MapElites<descriptor={self._descriptor},pop_size={self._init_pop_size},gen={self._generations},domain={domain_name},portfolio={port_names!r}>"

def _populate_archive(self):
instances = [
Expand Down
8 changes: 4 additions & 4 deletions digneapy/operators/crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ def one_point_crossover(
ind_2 Instance or Solution: Second individual to apply crossover
Raises:
AttributeError: When the len(ind_1) != len(ind_2)
ValueError: When the len(ind_1) != len(ind_2)
Returns:
Instance or Solution: New individual
"""
if len(ind_1) != len(ind_2):
msg = f"Individual of different length in uniform_crossover. len(ind_1) = {len(ind_1)} != len(ind_2) = {len(ind_2)}"
raise AttributeError(msg)
raise ValueError(msg)

cross_point = np.random.randint(low=0, high=len(ind_1))
offspring = copy.deepcopy(ind_1)
Expand All @@ -60,14 +60,14 @@ def uniform_crossover(
cxpb (float, optional): _description_. Defaults to 0.5.
Raises:
AttributeError: When the len(ind_1) != len(ind_2)
ValueError: When the len(ind_1) != len(ind_2)
Returns:
Instance or Solution: New individual
"""
if len(ind_1) != len(ind_2):
msg = f"Individual of different length in uniform_crossover. len(ind_1) = {len(ind_1)} != len(ind_2) = {len(ind_2)}"
raise AttributeError(msg)
raise ValueError(msg)

probs = np.random.rand(len(ind_1))
offspring = copy.deepcopy(ind_1)
Expand Down
7 changes: 4 additions & 3 deletions digneapy/operators/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ def uniform_one_mutation(
ind: Instance | Solution, bounds: Sequence[Tuple]
) -> Instance | Solution:
if len(ind) != len(bounds):
msg = "The size of individual and bounds is different in uniform_one_mutation"
raise AttributeError(msg)
msg = f"The size of individual ({len(ind)}) and bounds {len(bounds)} is different in uniform_one_mutation"
raise ValueError(msg)

if not all(len(b) == 2 for b in bounds):
msg = "Error bounds in uniform_one_mutation. The bounds list must contain tuples where each tuple is (l_i, u_i)"
raise AttributeError(msg)
raise ValueError(msg)

mutation_point = np.random.randint(low=0, high=len(ind))

Expand Down
12 changes: 6 additions & 6 deletions digneapy/operators/replacement.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ def generational(
offspring ( Sequence[Instance] | Sequence[Solution],): Offspring population
Raises:
AttributeError: Raises if the sizes of the population are different
ValueError: Raises if the sizes of the population are different
Returns:
Sequence[Instance] | Sequence[Solution]: New population
"""
if len(current_population) != len(offspring):
msg = f"The size of the current population ({len(current_population)}) != size of the offspring ({len(offspring)}) in generational replacement"
raise AttributeError(msg)
raise ValueError(msg)

return copy.deepcopy(offspring)

Expand All @@ -62,14 +62,14 @@ def first_improve_replacement(
offspring ( Sequence[Instance] | Sequence[Solution],): Offspring population
Raises:
AttributeError: Raises if the sizes of the population are different
ValueError: Raises if the sizes of the population are different
Returns:
list[Instance] | list[Solution]: New population
"""
if len(current_population) != len(offspring):
msg = f"The size of the current population ({len(current_population)}) != size of the offspring ({len(offspring)}) in first_improve_replacement"
raise AttributeError(msg)
raise ValueError(msg)

return [a if a > b else b for a, b in zip(current_population, offspring)]

Expand All @@ -90,14 +90,14 @@ def elitist_replacement(
hof (int, optional): _description_. Defaults to 1.
Raises:
AttributeError: Raises if the sizes of the population are different
ValueError: Raises if the sizes of the population are different
Returns:
list[Instance] | list[Solution]:
"""
if len(current_population) != len(offspring):
msg = f"The size of the current population ({len(current_population)}) != size of the offspring ({len(offspring)}) in elitist_replacement"
raise AttributeError(msg)
raise ValueError(msg)

combined_population = sorted(
itertools.chain(current_population, offspring),
Expand Down
16 changes: 8 additions & 8 deletions digneapy/qd/_novelty_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,19 @@ def sparseness(
verbose (bool, optional): Flag to show the progress. Defaults to False.
Raises:
AttributeError: If len(d) where d is the descriptor of each instance i differs from another
AttributeError: If NoveltySearch.k >= len(instances)
ValueError: If len(d) where d is the descriptor of each instance i differs from another
ValueError: If NoveltySearch.k >= len(instances)
Returns:
list[float]: List of sparseness values, one for each instance
"""
if len(instances) == 0 or any(len(d) == 0 for d in instances):
msg = f"{self.__class__.__name__} trying to calculate sparseness on an empty Instance list"
raise AttributeError(msg)
raise ValueError(msg)

if self._k >= len(instances):
msg = f"{self.__class__.__name__} trying to calculate sparseness with k({self._k}) > len(instances)({len(instances)})"
raise AttributeError(msg)
raise ValueError(msg)

return self.__compute_sparseness(
instances, self.archive, neighbours=self._k + 1
Expand All @@ -173,19 +173,19 @@ def sparseness_solution_set(self, instances: Sequence[Instance]) -> list[float]:
instances (Sequence[Instance]): Instances to calculate their sparseness
Raises:
AttributeError: If len(d) where d is the descriptor of each instance i differs from another
AttributeError: If 2 >= len(instances)
ValueError: If len(d) where d is the descriptor of each instance i differs from another
ValueError: If 2 >= len(instances)
Returns:
list[float]: List of sparseness values, one for each instance
"""

if len(instances) == 0 or any(len(d) == 0 for d in instances):
msg = f"{self.__class__.__name__} trying to update the solution set with an empty instance list"
raise AttributeError(msg)
raise ValueError(msg)

if len(instances) <= 2:
msg = f"{self.__class__.__name__} trying to calculate sparseness_solution_set with k = 2 >= len(instances)({len(instances)})"
raise AttributeError(msg)
raise ValueError(msg)

return self.__compute_sparseness(instances, self.solution_set, neighbours=2)
8 changes: 4 additions & 4 deletions digneapy/solvers/_bpp_heuristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

def best_fit(problem: BPP, *args, **kwargs) -> list[Solution]:
if problem is None:
raise AttributeError("No problem found in best_fit heuristic")
raise ValueError("No problem found in best_fit heuristic")
assignment = np.zeros(len(problem), dtype=int)
bin_capacities = []
items = problem._items
Expand Down Expand Up @@ -49,7 +49,7 @@ def best_fit(problem: BPP, *args, **kwargs) -> list[Solution]:

def first_fit(problem: BPP, *args, **kwargs) -> list[Solution]:
if problem is None:
raise AttributeError("No problem found in first_fit heuristic")
raise ValueError("No problem found in first_fit heuristic")
assignment = np.zeros(len(problem), dtype=int)
open_bins = []
items = problem._items
Expand All @@ -73,7 +73,7 @@ def first_fit(problem: BPP, *args, **kwargs) -> list[Solution]:

def next_fit(problem: BPP, *args, **kwargs) -> list[Solution]:
if problem is None:
raise AttributeError("No problem found in next_fit heuristic")
raise ValueError("No problem found in next_fit heuristic")

assignment = np.zeros(len(problem), dtype=int)
items = problem._items
Expand All @@ -94,7 +94,7 @@ def next_fit(problem: BPP, *args, **kwargs) -> list[Solution]:

def worst_fit(problem: BPP, *args, **kwargs) -> list[Solution]:
if problem is None:
raise AttributeError("No problem found in worst_fit heuristic")
raise ValueError("No problem found in worst_fit heuristic")

assignment = np.zeros(len(problem), dtype=int)
bin_capacities: list[int] = []
Expand Down
8 changes: 4 additions & 4 deletions digneapy/solvers/_kp_heuristics.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
def default_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
if problem is None:
msg = "No problem found in args of default_kp heuristic"
raise AttributeError(msg)
raise ValueError(msg)

inside = 0
profit = 0
Expand All @@ -35,7 +35,7 @@ def default_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
def map_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
if problem is None:
msg = "No problem found in args of map_kp heuristic"
raise AttributeError(msg)
raise ValueError(msg)

indices = np.argsort(problem.profits)[::-1]

Expand All @@ -53,7 +53,7 @@ def map_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
def miw_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
if problem is None:
msg = "No problem found in args of miw_kp heuristic"
raise AttributeError(msg)
raise ValueError(msg)

indices = np.argsort(problem.weights)

Expand All @@ -74,7 +74,7 @@ def miw_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
def mpw_kp(problem: Knapsack, *args, **kwargs) -> list[Solution]:
if problem is None:
msg = "No problem found in args of mpw_kp heuristic"
raise AttributeError(msg)
raise ValueError(msg)

profits_per_weights = [(p / w) for p, w in zip(problem.profits, problem.weights)]
indices = np.argsort(profits_per_weights)[::-1]
Expand Down
Loading

0 comments on commit bd3f12b

Please sign in to comment.