import hashlib
from addict import Dict
import numpy as np
from CADETProcess import CADETProcessError
from CADETProcess.dataStructure import Structure
from CADETProcess.dataStructure import Float, Vector
def hash_array(array):
"""Compute a hash value for an array of floats using the sha256 hash function.
Parameters
----------
array : numpy.ndarray
An array of floats.
Returns
-------
str
A hash value as a string of hexadecimal characters.
Examples
--------
>>> import numpy as np
>>> hash_array(np.array([1, 2.0]))
'3dfc9d56e04dcd01590f48b1b57c9ed9fecb1e94e11d3c3f13cf0fd97b7a9f0f'
"""
array = np.asarray(array)
return hashlib.sha256(array.tobytes()).hexdigest()
[docs]
class Individual(Structure):
"""
Set of variables evaluated during Optimization.
Attributes
----------
x : np.ndarray
Variable values in untransformed space.
x_transformed : np.ndarray
Independent variable values in transformed space.
f : np.ndarray
Objective values.
f_min : np.ndarray
Minimized objective values.
g : np.ndarray
Nonlinear constraint values.
cv : np.ndarray
Nonlinear constraints violation.
cv_tol : float
Tolerance for constraints violation.
m : np.ndarray
Meta score values.
m_min : np.ndarray
Minimized meta score values.
See Also
--------
CADETProcess.optimization.Population
"""
x = Vector()
x_transformed = Vector()
f = Vector()
f_min = Vector()
g = Vector()
cv = Vector()
cv_tol = Float()
m = Vector()
m_min = Vector()
def __init__(
self,
x,
f=None,
g=None,
m=None,
x_transformed=None,
f_min=None,
cv=None,
cv_tol=0,
m_min=None,
independent_variable_names=None,
objective_labels=None,
contraint_labels=None,
meta_score_labels=None,
variable_names=None):
self.x = x
if x_transformed is None:
x_transformed = x
independent_variable_names = variable_names
self.x_transformed = x_transformed
self.f = f
if f_min is None:
f_min = f
self.f_min = f_min
self.g = g
if g is not None and cv is None:
cv = g
self.cv = cv
self.cv_tol = cv_tol
self.m = m
if m_min is None:
m_min = m
self.m_min = m_min
if isinstance(variable_names, np.ndarray):
variable_names = [s.decode() for s in variable_names]
self.variable_names = variable_names
if isinstance(independent_variable_names, np.ndarray):
independent_variable_names = [s.decode() for s in independent_variable_names]
self.independent_variable_names = independent_variable_names
if isinstance(objective_labels, np.ndarray):
objective_labels = [s.decode() for s in objective_labels]
self.objective_labels = objective_labels
if isinstance(contraint_labels, np.ndarray):
contraint_labels = [s.decode() for s in contraint_labels]
self.contraint_labels = contraint_labels
if isinstance(meta_score_labels, np.ndarray):
meta_score_labels = [s.decode() for s in meta_score_labels]
self.meta_score_labels = meta_score_labels
self.id = hash_array(self.x)
@property
def id_short(self):
return self.id[0:7]
@property
def is_evaluated(self):
"""bool: Return True if individual has been evaluated. False otherwise."""
if self.f is None:
return False
else:
return True
@property
def is_feasible(self):
"""bool: Return False if any constraint is not met. True otherwise."""
if self.cv is not None and np.any(np.array(self.cv) > self.cv_tol):
return False
else:
return True
@property
def n_x(self):
"""int: Number of variables."""
return len(self.x)
@property
def n_f(self):
"""int: Number of objectives."""
if self.f is None:
return 0
return len(self.f)
@property
def n_g(self):
"""int: Number of nonlinear constraints."""
if self.g is None:
return 0
else:
return len(self.g)
@property
def n_m(self):
"""int: Number of meta scores."""
if self.m is None:
return 0
else:
return len(self.m)
@property
def dimensions(self):
"""tuple: Individual dimensions (n_x, n_f, n_g, n_m)"""
return (self.n_x, self.n_f, self.n_g, self.n_m)
@property
def objectives_minimization_factors(self):
return self.f_min / self.f
@property
def meta_scores_minimization_factors(self):
return self.m_min / self.m
[docs]
def dominates(self, other):
"""Determine if individual dominates other.
Parameters
----------
other : Individual
Other individual
Returns
-------
dominates : bool
True if objectives of "self" are not strictly worse than the
corresponding objectives of "other" and at least one objective is
strictly better. False otherwise
"""
if not self.is_evaluated:
raise CADETProcessError("Individual needs to be evaluated first.")
if not other.is_evaluated:
raise CADETProcessError("Other individual needs to be evaluated first.")
if self.is_feasible and not other.is_feasible:
return True
if not self.is_feasible and not other.is_feasible:
if np.any(self.cv < other.cv):
return True
if self.m is not None:
self_values = self.m
other_values = other.m
else:
self_values = self.f_min
other_values = other.f_min
if np.any(self_values > other_values):
return False
if np.any(self_values < other_values):
return True
return False
[docs]
def is_similar(self, other, tol=1e-1):
"""Determine if individual is similar to other.
Parameters
----------
other : Individual
Other individual
tol : float, optional
Relative tolerance parameter.
To reduce number of entries, a rather high rtol is chosen.
Returns
-------
is_similar : bool
True if individuals are close to each other. False otherwise
"""
if tol is None:
return False
similar_x = self.is_similar_x(other, tol)
similar_f = self.is_similar_f(other, tol)
if self.g is not None:
similar_g = self.is_similar_g(other, tol)
else:
similar_g = True
if self.m is not None:
similar_m = self.is_similar_m(other, tol)
else:
similar_m = True
return similar_x and similar_f and similar_g and similar_m
[docs]
def is_similar_x(self, other, tol=1e-1, use_transformed=False):
"""Determine if individual is similar to other based on parameter values.
Parameters
----------
other : Individual
Other individual
tol : float
Relative tolerance parameter.
To reduce number of entries, a rather high rtol is chosen.
use_transformed : bool
If True, use independent transformed space.
The default is False.
Returns
-------
is_similar : bool
True if parameters are close to each other. False otherwise
"""
similar_x = np.allclose(self.x, other.x, rtol=tol)
return similar_x
[docs]
def is_similar_f(self, other, tol=1e-1):
"""Determine if individual is similar to other based on objective values.
Parameters
----------
other : Individual
Other individual
tol : float
Relative tolerance parameter.
To reduce number of entries, a rather high rtol is chosen.
Returns
-------
is_similar : bool
True if parameters are close to each other. False otherwise
"""
similar_f = np.allclose(self.f, other.f, rtol=tol)
return similar_f
[docs]
def is_similar_g(self, other, tol=1e-1):
"""Determine if individual is similar to other based on constraint values.
Parameters
----------
other : Individual
Other individual
tol : float
Relative tolerance parameter.
To reduce number of entries, a rather high rtol is chosen.
Returns
-------
is_similar : bool
True if parameters are close to each other. False otherwise
"""
similar_g = np.allclose(self.g, other.g, rtol=tol)
return similar_g
[docs]
def is_similar_m(self, other, tol=1e-1):
"""Determine if individual is similar to other based on meta score values.
Parameters
----------
other : Individual
Other individual
tol : float
Relative tolerance parameter.
To reduce number of entries, a rather high rtol is chosen.
Returns
-------
is_similar : bool
True if parameters are close to each other. False otherwise
"""
similar_m = np.allclose(self.m, other.m, rtol=tol)
return similar_m
def __str__(self):
return str(list(self.x))
def __repr__(self):
if self.g is None:
return f'{self.__class__.__name__}({self.x}, {self.f})'
else:
return f'{self.__class__.__name__}({self.x}, {self.f}, {self.g})'
[docs]
def to_dict(self):
"""Convert individual to a dictionary.
Returns
-------
dict: A dictionary representation of the individual's attributes.
"""
data = Dict()
data.x = self.x
data.f = self.f
if self.g is not None:
data.g = self.g
if self.cv is not None:
data.cv = self.cv
if self.m is not None:
data.m = self.m
data.x_transformed = self.x_transformed
data.variable_names = self.variable_names
data.independent_variable_names = self.independent_variable_names
if self.objective_labels is not None:
data.objective_labels = self.objective_labels
if self.contraint_labels is not None:
data.contraint_labels = self.contraint_labels
if self.meta_score_labels is not None:
data.meta_score_labels = self.meta_score_labels
return data
[docs]
@classmethod
def from_dict(cls, data):
"""Create Individual from dictionary representation of its attributes.
Parameters
----------
data : dict
A dictionary representation of the individual's attributes.
Returns
-------
individual
Individual idual created from the dictionary.
"""
return cls(**data)