Skip to content

Commit

Permalink
Allow specifying meta scores as maximization problems
Browse files Browse the repository at this point in the history
  • Loading branch information
schmoelder committed Mar 23, 2024
1 parent 05bc5f8 commit fa7dffd
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
13 changes: 12 additions & 1 deletion CADETProcess/optimization/individual.py
Expand Up @@ -52,6 +52,8 @@ class Individual(Structure):
Tolerance for constraints violation.
m : list
Meta score values.
m_min : list
Minimized meta score values.
See Also
--------
Expand All @@ -66,6 +68,7 @@ class Individual(Structure):
cv = Vector()
cv_tol = Float()
m = Vector()
m_min = Vector()

def __init__(
self,
Expand All @@ -77,6 +80,7 @@ def __init__(
f_min=None,
cv=None,
cv_tol=0,
m_min=None,
independent_variable_names=None,
objective_labels=None,
contraint_labels=None,
Expand All @@ -100,6 +104,9 @@ def __init__(
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]
Expand Down Expand Up @@ -169,13 +176,17 @@ def n_m(self):

@property
def dimensions(self):
"""tuple: Individual dimensions (n_x, n_f, n_g)"""
"""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

def dominates(self, other):
"""Determine if individual dominates other.
Expand Down
17 changes: 13 additions & 4 deletions CADETProcess/optimization/optimizationProblem.py
Expand Up @@ -179,6 +179,8 @@ def transform_maximization(self, s, scores):
factors = []
if scores == 'objectives':
scores = self.objectives
elif scores == 'meta_scores':
scores = self.meta_scores
else:
raise ValueError(f'Unknown scores: {scores}.')

Expand Down Expand Up @@ -1707,6 +1709,7 @@ def add_meta_score(
meta_score,
name=None,
n_meta_scores=1,
minimize=True,
evaluation_objects=-1,
requires=None):
"""Add Meta score to the OptimizationProblem.
Expand All @@ -1720,6 +1723,8 @@ def add_meta_score(
n_meta_scores : int, optional
Number of meta scores returned by callable.
The default is 1.
minimize : bool, optional
If True, meta score is treated as minimization problem. The default is True.
evaluation_objects : {EvaluationObject, None, -1, list}
EvaluationObjects which are evaluated by objective.
If None, no EvaluationObject is used.
Expand Down Expand Up @@ -1785,6 +1790,7 @@ def add_meta_score(
self._meta_scores.append(meta_score)

@untransforms
@ensures_minimization(scores='meta_scores')
def evaluate_meta_scores(self, x, force=False):
"""Evaluate meta functions at point x.
Expand Down Expand Up @@ -1815,6 +1821,7 @@ def evaluate_meta_scores(self, x, force=False):

@untransforms
@ensures2d
@ensures_minimization(scores='meta_scores')
def evaluate_meta_scores_population(self, population, force=False, parallelization_backend=None):
"""Evaluate meta score functions for each point x in population.
Expand Down Expand Up @@ -2946,12 +2953,13 @@ def create_individual(
f_min=None,
cv=None,
cv_tol=None,
m_min=None,
):
x_indep = self.get_independent_values(x)
x_transformed = self.transform(x_indep)

ind = Individual(
x, f, g, m, x_transformed, f_min, cv, cv_tol,
x, f, g, m, x_transformed, f_min, cv, cv_tol, m_min,
self.independent_variable_names,
self.objective_labels,
self.nonlinear_constraint_labels,
Expand Down Expand Up @@ -3880,11 +3888,12 @@ class MetaScore(Metric):
"""Wrapper class to evaluate meta scores."""
meta_score = Metric.func
n_meta_scores = Metric.n_metrics
minimize = Bool(default=True)

def __init__(self, *args, bounds=0, **kwargs):
self.bounds = bounds
def __init__(self, *args, n_meta_scores=1, minimize=True, **kwargs):
self.minimize = minimize

super().__init__(*args, **kwargs)
super().__init__(*args, n_metrics=n_meta_scores, **kwargs)


class MultiCriteriaDecisionFunction(Structure):
Expand Down
9 changes: 6 additions & 3 deletions CADETProcess/optimization/optimizer.py
Expand Up @@ -429,25 +429,28 @@ def _create_population(self, X_transformed, F, F_min, G, CV):
CV = np.array(CV, ndmin=2)

if self.optimization_problem.n_meta_scores > 0:
M = self.optimization_problem.evaluate_meta_scores_population(
M_min = self.optimization_problem.evaluate_meta_scores_population(
X_transformed,
untransform=True,
ensure_minimization=True,
parallelization_backend=self.parallelization_backend,
)
M = self.optimization_problem.transform_maximization(M_min, scores='meta_scores')
else:
M_min = len(X_transformed)*[None]
M = len(X_transformed)*[None]

if self.optimization_problem.n_nonlinear_constraints == 0:
G = len(X_transformed)*[None]
CV = len(X_transformed)*[None]

population = Population()
for x_transformed, f, f_min, g, cv, m in zip(X_transformed, F, F_min, G, CV, M):
for x_transformed, f, f_min, g, cv, m, m_min in zip(X_transformed, F, F_min, G, CV, M, M_min):
x = self.optimization_problem.get_dependent_values(
x_transformed, untransform=True
)
ind = Individual(
x, f, g, m, x_transformed, f_min, cv, self.cv_tol,
x, f, g, m, x_transformed, f_min, cv, self.cv_tol, m_min,
self.optimization_problem.independent_variable_names,
self.optimization_problem.objective_labels,
self.optimization_problem.nonlinear_constraint_labels,
Expand Down
30 changes: 24 additions & 6 deletions CADETProcess/optimization/population.py
Expand Up @@ -93,6 +93,10 @@ def dimensions(self):
def objectives_minimization_factors(self):
return self.individuals[0].objectives_minimization_factors

@property
def meta_scores_minimization_factors(self):
return self.individuals[0].meta_scores_minimization_factors

@property
def variable_names(self):
"""list: Names of the optimization variables."""
Expand Down Expand Up @@ -327,30 +331,44 @@ def cv_avg(self):

@property
def m(self):
"""np.array: All evaluated metas core values."""
"""np.array: All evaluated meta scores."""
if self.dimensions[3] > 0:
return np.array([ind.m for ind in self.individuals])

@property
def m_minimized(self):
"""np.array: All evaluated meta scores, transformed to be minimized."""
if self.dimensions[3] > 0:
return np.array([ind.m_min for ind in self.individuals])

@property
def m_best(self):
"""np.array: Best meta scores."""
if self.dimensions[3] > 0:
m_best = np.min(self.m_minimized, axis=0)
return np.multiply(self.meta_scores_minimization_factors, m_best)

@property
def m_min(self):
"""np.array: Minimum meta score values."""
"""np.array: Minimum meta scores."""
if self.dimensions[3] > 0:
return np.min(self.m, axis=0)

@property
def m_max(self):
"""np.array: Maximum meta score values."""
"""np.array: Maximum meta scores."""
if self.dimensions[3] > 0:
return np.max(self.m, axis=0)

@property
def m_avg(self):
"""np.array: Average meta score values."""
return np.mean(self.m, axis=0)
"""np.array: Average meta scores."""
if self.dimensions[3] > 0:
return np.mean(self.m, axis=0)

@property
def is_feasilbe(self):
"""np.array: Average meta score values."""
"""np.array: False if any constraint is not met. True otherwise."""
return np.array([ind.is_feasible for ind in self.individuals])

def setup_objectives_figure(self, include_meta=True, plot_individual=False):
Expand Down
13 changes: 9 additions & 4 deletions CADETProcess/optimization/results.py
Expand Up @@ -325,25 +325,30 @@ def cv_avg_history(self):
else:
return np.array([pop.cv_avg for pop in self.meta_fronts])

@property
def m_best_history(self):
"""np.array: Best meta scores per generation."""
return np.array([pop.m_best for pop in self.meta_fronts])

@property
def m_min_history(self):
"""np.array: Minimum meta score values per generation."""
"""np.array: Minimum meta scores per generation."""
if self.optimization_problem.n_meta_scores == 0:
return None
else:
return np.array([pop.m_min for pop in self.meta_fronts])

@property
def m_max_history(self):
"""np.array: Maximum meta score values per generation."""
"""np.array: Maximum meta scores per generation."""
if self.optimization_problem.n_meta_scores == 0:
return None
else:
return np.array([pop.m_max for pop in self.meta_fronts])

@property
def m_avg_history(self):
"""np.array: Average meta score values per generation."""
"""np.array: Average meta scores per generation."""
if self.optimization_problem.n_meta_scores == 0:
return None
else:
Expand Down Expand Up @@ -655,7 +660,7 @@ def plot_convergence(
values_avg = self.g_avg_history
elif target == 'meta_scores':
funcs = self.optimization_problem.meta_scores
values_min = self.m_min_history
values_min = self.m_best_history
values_avg = self.m_avg_history
else:
raise CADETProcessError("Unknown target.")
Expand Down

0 comments on commit fa7dffd

Please sign in to comment.