Skip to content

Commit

Permalink
🚚 Renaming and moving things around. Better import style now.
Browse files Browse the repository at this point in the history
  • Loading branch information
amarrerod committed Sep 18, 2024
1 parent bb7ecd0 commit 710b200
Show file tree
Hide file tree
Showing 32 changed files with 385 additions and 688 deletions.
55 changes: 20 additions & 35 deletions digneapy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
__email__ = "amarrerd@ull.edu.es"
__version__ = "0.2.3"

from digneapy._core import (

from . import _core, archives, operators
from ._core import (
NS,
Direction,
Domain,
Expand All @@ -15,54 +17,37 @@
Solution,
Solver,
SupportsSolve,
descriptors,
scores,
)
from digneapy.archives import Archive, GridArchive
from digneapy.operators import crossover, mutation, replacement, selection

from .descriptors import DescStrategy, descriptor_strategies, rdstrat
from .metrics import (
PerformanceFn,
default_performance_metric,
pisinger_performance_metric,
)
from ._core.descriptors import DESCRIPTORS, DescStrategy, descriptor
from ._core.scores import PerformanceFn, max_gap_target, runtime_score
from .archives import Archive, GridArchive
from .operators import crossover, mutation, replacement, selection

__dignea_submodules = {"utils", "domains", "generators", "solvers"}
__dignea_submodules = {"utils", "domains", "generators", "solvers", "visualize"}


__all__ = list(
__dignea_submodules
| set(_core.__all__)
| set(operators.__all__)
| set(archives.__all__)
| set(metrics.__all__)
| set(descriptors.__all__)
| set(scores.__all__)
)


# Lazy import function
def __getattr__(name):
if name == "transformers":
import digneapy.transformers as transformers

return transformers
elif name == "domains":
import digneapy.domains as domains

return domains
elif name == "generators":
import digneapy.generators as generators

return generators

elif name == "solvers":
import digneapy.solvers as solvers

return solvers

elif name == "utils":
import digneapy.utils as utils
def __getattr__(attr_name):
import importlib
import sys

return utils
if attr_name in __dignea_submodules:
full_name = f"digneapy.{attr_name}"
submodule = importlib.import_module(full_name)
sys.modules[full_name] = submodule
return submodule

else:
raise ImportError(f"module {__name__} has no attribute {name}")
raise ImportError(f"module {__name__} has no attribute {attr_name}")
14 changes: 7 additions & 7 deletions digneapy/_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
@Desc : None
"""

from digneapy._core._constants import Direction, IndType
from digneapy._core._domain import Domain
from digneapy._core._instance import Instance
from digneapy._core._novelty_search import NS
from digneapy._core._problem import P, Problem
from digneapy._core._solution import Solution
from digneapy._core._solver import Solver, SupportsSolve
from ._constants import Direction, IndType
from ._domain import Domain
from ._instance import Instance
from ._novelty_search import NS
from ._problem import P, Problem
from ._solution import Solution
from ._solver import Solver, SupportsSolve

__all__ = [
"Domain",
Expand Down
11 changes: 6 additions & 5 deletions digneapy/_core/_novelty_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
import numpy as np
from sklearn.neighbors import NearestNeighbors

from digneapy._core import Instance
from digneapy.archives import Archive
from digneapy.descriptors import descriptor_strategies
from digneapy.transformers import SupportsTransform

from ._instance import Instance
from .descriptors import DESCRIPTORS


class NS:
"""Descriptor strategies for the Novelty Search algorithm.
Expand Down Expand Up @@ -49,14 +50,14 @@ def __init__(
self._k = k
self._transformer = transformer

if descriptor not in descriptor_strategies:
if descriptor not in DESCRIPTORS:
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 = descriptor_strategies["features"]
self._descriptor_strategy = DESCRIPTORS["features"]
else:
self._describe_by = descriptor
self._descriptor_strategy = descriptor_strategies[descriptor]
self._descriptor_strategy = DESCRIPTORS[descriptor]

@property
def archive(self):
Expand Down
12 changes: 6 additions & 6 deletions digneapy/descriptors.py → digneapy/_core/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@

__all__ = [
"DescStrategy",
"rdstrat",
"descriptor_strategies",
"descriptor",
"DESCRIPTORS",
]

from collections.abc import Callable, Iterable, MutableMapping

import numpy as np

from ._core._instance import Instance
from digneapy._core._instance import Instance

""" DescStrategy defines the type for a Descriptor Strategy.
A descriptor strategy is any callable able to extract the
Expand All @@ -33,7 +33,7 @@
DescStrategy = Callable[[Iterable[Instance]], np.ndarray]


def rdstrat(key: str, verbose: bool = False):
def descriptor(key: str, verbose: bool = False):
"""Decorator to create new descriptor strategies
Args:
Expand All @@ -44,7 +44,7 @@ def rdstrat(key: str, verbose: bool = False):
def decorate(func: DescStrategy):
if verbose:
print(f"Registering descriptor function: {func.__name__} with key: {key}")
descriptor_strategies[key] = func
DESCRIPTORS[key] = func
return func

return decorate
Expand Down Expand Up @@ -104,7 +104,7 @@ def instance_strategy(iterable: Iterable[Instance]) -> np.ndarray:
- instance --> Creates a np.ndarray with the whole instance as its self descriptor.
- transformed --> Creates a np.ndarray with all the transformed descriptors of the instances. Only when using a Transformer.
"""
descriptor_strategies: MutableMapping[str, DescStrategy] = {
DESCRIPTORS: MutableMapping[str, DescStrategy] = {
"features": __property_strategy(attr="features"),
"performance": performance_strategy,
"instance": instance_strategy,
Expand Down
18 changes: 10 additions & 8 deletions digneapy/metrics.py → digneapy/_core/scores.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
#!/usr/bin/env python
# -*-coding:utf-8 -*-
"""
@File : metrics.py
@Time : 2024/09/17 14:51:11
@File : scores.py
@Time : 2024/09/18 10:43:17
@Author : Alejandro Marrero
@Version : 1.0
@Contact : amarrerd@ull.edu.es
@License : (C)Copyright 2024, Alejandro Marrero
@Desc : None
"""

__all__ = ["PerformanceFn", "default_performance_metric", "pisinger_performance_metric"]
__all__ = ["PerformanceFn", "max_gap_target", "runtime_score"]

from collections.abc import Callable, Sequence

Expand All @@ -21,10 +21,11 @@
PerformanceFn = Callable[[Sequence], float]


def default_performance_metric(scores: Sequence[float]) -> float:
"""Default performace metric for the instances.
def max_gap_target(scores: Sequence[float]) -> float:
"""Maximum gap to target.
It tries to maximise the gap between the target solver
and the other solvers in the portfolio.
Use this metric to generate instances that are EASY to solve by the target algorithm
Args:
scores (Iterable[float]): Scores of each solver over an instance. It is expected
Expand All @@ -36,10 +37,11 @@ def default_performance_metric(scores: Sequence[float]) -> float:
return scores[0] - max(scores[1:])


def pisinger_performance_metric(scores: Sequence[float]) -> float:
"""Pisinger Solvers performace metric for the instances.
def runtime_score(scores: Sequence[float]) -> float:
"""Runtime based metric.
It tries to maximise the gap between the runing time of the target solver
and the other solvers in the portfolio.
and the other solvers in the portfolio. Use this metric with exact solvers
which provide the same objective values for an instance.
Args:
scores (Iterable[float]): Running time of each solver over an instance. It is expected
Expand Down
37 changes: 29 additions & 8 deletions digneapy/archives/_grid_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(
self._upper_bounds = np.array(ranges[1], dtype=dtype)
self._interval = self._upper_bounds - self._lower_bounds
self._eps = eps
self._cells = np.prod(self._dimensions, dtype=int)
self._cells = np.prod(self._dimensions, dtype=object)
self._grid: Dict[int, Instance] = {}

_bounds = []
Expand Down Expand Up @@ -281,16 +281,37 @@ def index_of(self, descriptors):

def _grid_to_int_index(self, grid_indices) -> np.ndarray:
grid_indices = np.asarray(grid_indices)
return np.ravel_multi_index(grid_indices.T, self._dimensions).astype(int)
if len(self._dimensions) > 64:
strides = np.cumprod((1,) + tuple(self._dimensions[::-1][:-1]))[::-1]
# Reshape strides to (1, num_dimensions) to make it broadcastable with grid_indices
strides = strides.reshape(1, -1)
flattened_indeces = np.sum(grid_indices * strides, axis=1, dtype=object)
else:
flattened_indeces = np.ravel_multi_index(
grid_indices.T, self._dimensions
).astype(int)
return flattened_indeces

def int_to_grid_index(self, int_indices) -> np.ndarray:
int_indices = np.asarray(int_indices)
return np.asarray(
np.unravel_index(
int_indices,
self._dimensions,
)
).T.astype(int)
if len(self._dimensions) > 64:
# Manually unravel the index for dimensions > 64
unravel_indices = []
remaining_indices = int_indices.astype(object)

for dim_size in self._dimensions[::-1]:
unravel_indices.append(remaining_indices % dim_size)
remaining_indices //= dim_size

unravel_indices = np.array(unravel_indices[::-1]).T
else:
unravel_indices = np.asarray(
np.unravel_index(
int_indices,
self._dimensions,
)
).T.astype(int)
return unravel_indices

def to_json(self):
data = {
Expand Down
Loading

0 comments on commit 710b200

Please sign in to comment.