Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions aisp/base/core/_clusterer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from abc import ABC, abstractmethod
from typing import Optional, Union
from warnings import warn

import numpy.typing as npt

Expand All @@ -26,20 +25,6 @@ class BaseClusterer(ABC, Base):

labels: Optional[npt.NDArray] = None

@property
def classes(self) -> Optional[npt.NDArray]:
"""Deprecated alias kept for backward compatibility.

Use `labels` instead of `classes`.
"""
warn(
"The `classes` attribute is deprecated and will be removed in future "
"versions. Use labels instead.",
FutureWarning,
2
)
return self.labels

@abstractmethod
def fit(self, X: Union[npt.NDArray, list], verbose: bool = True) -> BaseClusterer:
"""
Expand Down
39 changes: 37 additions & 2 deletions aisp/base/core/_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class BaseOptimizer(ABC, Base):
history, evaluated solutions, and the best solution found during the optimization process.
Subclasses must implement ``optimize`` and ``affinity_function``.

Parameters
----------
affinity_function : Optional[Callable[..., Any]], default=None
Objective function to evaluate candidate solutions the problem.

Attributes
----------
cost_history : List[float]
Expand All @@ -30,7 +35,7 @@ class BaseOptimizer(ABC, Base):
Defines whether the algorithm minimizes or maximizes the cost function.
"""

def __init__(self) -> None:
def __init__(self, affinity_function: Optional[Callable[..., Any]] = None) -> None:
self._cost_history: List[float] = []
self._solution_history: list = []
self._best_solution: Optional[Any] = None
Expand All @@ -42,6 +47,8 @@ def __init__(self) -> None:
"get_report"
]
self.mode = "min"
if callable(affinity_function):
self.register('affinity_function', affinity_function)

@property
def cost_history(self) -> List[float]:
Expand Down Expand Up @@ -156,7 +163,6 @@ def optimize(
The best solution found by the optimization algorithm.
"""

@abstractmethod
def affinity_function(self, solution: Any) -> float:
"""Evaluate the affinity of a candidate solution.

Expand All @@ -171,7 +177,15 @@ def affinity_function(self, solution: Any) -> float:
-------
affinity : float
Cost value associated with the given solution.

Raises
------
NotImplementedError
If no affinity function has been provided.
"""
raise NotImplementedError(
"No affinity function to evaluate the candidate cell was provided."
)

def register(self, alias: str, function: Callable[..., Any]) -> None:
"""Register a function dynamically in the optimizer instance.
Expand Down Expand Up @@ -207,3 +221,24 @@ def reset(self):
self._solution_history = []
self._best_solution = None
self._best_cost = None

def _affinity_function(self, solution: Any) -> float:
"""
Evaluate the affinity of a candidate cell.

Parameters
----------
solution : npt.NDArray
Candidate solution to evaluate.

Returns
-------
affinity : np.float64
Affinity value associated with the given cell.

Raises
------
NotImplementedError
If no affinity function has been provided.
"""
return float(self.affinity_function(solution))
32 changes: 3 additions & 29 deletions aisp/csa/_clonalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def __init__(
mode: Literal["min", "max"] = "min",
seed: Optional[int] = None,
):
super().__init__()
super().__init__(affinity_function)
self.problem_size = sanitize_param(problem_size, 1, lambda x: x > 0)
self.N: int = sanitize_param(N, 50, lambda x: x > 0)
self.rate_clonal: int = sanitize_param(rate_clonal, 10, lambda x: x > 0)
Expand All @@ -135,7 +135,6 @@ def __init__(
self.selection_size: int = sanitize_param(
selection_size, 5, lambda x: x > 0
)
self._affinity_function = affinity_function
self.feature_type: FeatureTypeAll = feature_type

self._bounds: Optional[Dict] = None
Expand Down Expand Up @@ -200,7 +199,7 @@ def optimize(

t = 1
antibodies = [
Antibody(antibody, self.affinity_function(antibody))
Antibody(antibody, self._affinity_function(antibody))
for antibody in self._init_population_antibodies()
]
best_cost = None
Expand All @@ -222,7 +221,7 @@ def optimize(
clones = self._clone_and_hypermutation(p_select)

p_rand = [
Antibody(antibody, self.affinity_function(antibody))
Antibody(antibody, self._affinity_function(antibody))
for antibody in self._diversity_introduction()
]
antibodies = p_select
Expand Down Expand Up @@ -279,31 +278,6 @@ def _select_top_antibodies(

return heapq.nsmallest(n, antibodies)

def affinity_function(self, solution: npt.NDArray) -> np.float64:
"""
Evaluate the affinity of a candidate cell.

Parameters
----------
solution : npt.NDArray
Candidate solution to evaluate.

Returns
-------
affinity : np.float64
Affinity value associated with the given cell.

Raises
------
NotImplementedError
If no affinity function has been provided.
"""
if not callable(self._affinity_function):
raise NotImplementedError(
"No affinity function to evaluate the candidate cell was provided."
)
return np.float64(self._affinity_function(solution))

def _init_population_antibodies(self) -> npt.NDArray:
"""Initialize the antibody set of the population randomly.

Expand Down
121 changes: 121 additions & 0 deletions aisp/ina/_opt_ai_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Artificial Immune Network for Optimization (Opt-AiNet)."""

from typing import Any, Optional, Callable, Literal, Dict

import numpy as np
import numpy.typing as npt

from ..base import BaseOptimizer
from ..utils.random import set_seed_numba
from ..utils.sanitizers import sanitize_param, sanitize_seed, sanitize_bounds
from ..utils.types import FeatureTypeAll


class OptAiNet(BaseOptimizer):
"""Artificial Immune Network for Optimization.

Parameters
----------
problem_size : int
Dimension of the problem to be minimized.
N : int, default=50
Number of memory cells (antibodies) in the population.
rate_clonal : float, default=10
Maximum number of possible clones of a cell. This value is multiplied by
cell_affinity to determine the number of clones.
n_diversity_injection : int, default=5
Number of new random memory cells injected to maintain diversity.
affinity_function : Optional[Callable[..., npt.NDArray]], default=None
Objective function to evaluate candidate solutions in minimizing the problem.
feature_type : FeatureTypeAll, default='ranged-features'
Type of problem samples: binary, continuous, or based on value ranges.
Specifies the type of features: "continuous-features", "binary-features",
"ranged-features", or "permutation-features".
bounds : Optional[Dict], default=None
Definition of search limits when ``feature_type='ranged-features'``.
Can be provided in two ways:

* Fixed values: ``{'low': float, 'high': float}``
Values are replicated across all dimensions, generating equal limits for each
dimension.
* Arrays: ``{'low': list, 'high': list}``
Each dimension has specific limits. Both arrays must be
``problem_size``.

mode : Literal["min", "max"], default="min"
Defines whether the algorithm minimizes or maximizes the cost function.
seed : Optional[int], default=None
Seed for random generation of detector values. If None, the value is random.
"""

def __init__(
self,
problem_size: int,
N: int = 50,
rate_clonal: int = 10,
n_diversity_injection: int = 5,
affinity_function: Optional[Callable[..., npt.NDArray]] = None,
feature_type: FeatureTypeAll = 'ranged-features',
bounds: Optional[Dict] = None,
mode: Literal["min", "max"] = "min",
seed: Optional[int] = None
):
super().__init__(affinity_function)
self.problem_size = sanitize_param(problem_size, 1, lambda x: x > 0)
self.N: int = sanitize_param(N, 50, lambda x: x > 0)
self.rate_clonal: int = sanitize_param(rate_clonal, 10, lambda x: x > 0)
self.n_diversity_injection: int = sanitize_param(
n_diversity_injection, 5, lambda x: x > 0
)

self.feature_type: FeatureTypeAll = feature_type

self._bounds: Optional[Dict] = None
self._bounds_extend_cache: Optional[np.ndarray] = None
self.bounds = bounds

self.mode: Literal["min", "max"] = sanitize_param(
mode,
"min",
lambda x: x == "max"
)

self.seed: Optional[int] = sanitize_seed(seed)
if self.seed is not None:
np.random.seed(self.seed)
set_seed_numba(self.seed)

@property
def bounds(self) -> Optional[Dict]:
"""Getter for the bounds attribute."""
return self._bounds

@bounds.setter
def bounds(self, value: Optional[Dict]):
"""Setter for the bounds attribute."""
if self.feature_type == 'ranged-features':
self._bounds = sanitize_bounds(value, self.problem_size)
low_bounds = np.array(self._bounds['low'])
high_bounds = np.array(self._bounds['high'])
self._bounds_extend_cache = np.array([low_bounds, high_bounds])
else:
self._bounds = None
self._bounds_extend_cache = None

def optimize(self, max_iters: int = 50, n_iter_no_change=10, verbose: bool = True) -> Any:
"""Execute the optimization process and return the population.

Parameters
----------
max_iters : int, default=50
Maximum number of interactions when searching for the best solution using clonalg.
n_iter_no_change: int, default=10
the maximum number of iterations without updating the best cell
verbose : bool, default=True
Feedback on interactions, indicating the best antibody.

Returns
-------
population : any
"""
return []
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "aisp"
version = "0.5.3"
version = "0.6.0"
authors = [
{ name="João Paulo da Silva Barros", email="jpsilvabarr@gmail.com" },
]
Expand Down