import pandas as pd
import numpy as np
[docs]class ValidationError(Exception):
"""Raised when an error related to the validation is encountered.
"""
[docs]def validate_ref_point_with_ideal_and_nadir(
dimensions_data: pd.DataFrame, reference_point: pd.DataFrame
):
validate_ref_point_dimensions(dimensions_data, reference_point)
validate_ref_point_data_type(reference_point)
validate_ref_point_with_ideal(dimensions_data, reference_point)
validate_with_ref_point_nadir(dimensions_data, reference_point)
[docs]def validate_ref_point_with_ideal(
dimensions_data: pd.DataFrame, reference_point: pd.DataFrame
):
validate_ref_point_dimensions(dimensions_data, reference_point)
ideal_fitness = dimensions_data.loc["ideal"] * dimensions_data.loc["minimize"]
ref_point_fitness = reference_point * dimensions_data.loc["minimize"]
if not (ideal_fitness <= ref_point_fitness).all(axis=None):
problematic_columns = ideal_fitness.index[
(ideal_fitness > ref_point_fitness).values.tolist()[0]
].values
msg = (
f"Reference point should be worse than or equal to the ideal point\n"
f"The following columns have problematic values: {problematic_columns}"
)
raise ValidationError(msg)
[docs]def validate_with_ref_point_nadir(
dimensions_data: pd.DataFrame, reference_point: pd.DataFrame
):
validate_ref_point_dimensions(dimensions_data, reference_point)
nadir_fitness = dimensions_data.loc["nadir"] * dimensions_data.loc["minimize"]
ref_point_fitness = reference_point * dimensions_data.loc["minimize"]
if not (ref_point_fitness <= nadir_fitness).all(axis=None):
problematic_columns = nadir_fitness.index[
(nadir_fitness < ref_point_fitness).values.tolist()[0]
].values
msg = (
f"Reference point should be better than or equal to the nadir point\n"
f"The following columns have problematic values: {problematic_columns}"
)
raise ValidationError(msg)
[docs]def validate_ref_point_dimensions(
dimensions_data: pd.DataFrame, reference_point: pd.DataFrame
):
if not dimensions_data.shape[1] == reference_point.shape[1]:
msg = (
f"There is a mismatch in the number of columns of the dataframes.\n"
f"Columns in dimensions data: {dimensions_data.columns}\n"
f"Columns in the reference point provided: {reference_point.columns}"
)
raise ValidationError(msg)
if not all(dimensions_data.columns == reference_point.columns):
msg = (
f"There is a mismatch in the column names of the dataframes.\n"
f"Columns in dimensions data: {dimensions_data.columns}\n"
f"Columns in the reference point provided: {reference_point.columns}"
)
raise ValidationError(msg)
[docs]def validate_ref_point_data_type(reference_point: pd.DataFrame):
for dtype in reference_point.dtypes:
if not pd.api.types.is_numeric_dtype(dtype):
msg = (
f"Type of data in reference point dataframe should be numeric.\n"
f"Provided datatype: {dtype}"
)
raise ValidationError(msg)
[docs]def validate_specified_solutions(indices: np.ndarray, n_solutions: int) -> None:
"""Validate the Decision maker's choice of preferred/non-preferred solutions.
Args:
indices (np.ndarray): Index/indices of preferred solutions specified by the Decision maker.
n_solutions (int): Number of solutions in total.
Returns:
Raises:
ValidationError: In case the preference is invalid.
"""
if indices.shape[0] < 1:
raise ValidationError("Please specify at least one (non-)preferred solution.")
if not isinstance(indices, (np.ndarray, list)):
raise ValidationError("Please specify index/indices of (non-)preferred solutions in a list, even if there is only "
"one.")
if not all(0 <= i <= (n_solutions - 1) for i in indices):
msg = "indices of (non-)preferred solutions should be between 0 and {}. Current indices are {}." \
.format(n_solutions - 1, indices)
raise ValidationError(msg)
[docs]def validate_bounds(dimensions_data: pd.DataFrame, bounds: np.ndarray, n_objectives: int) -> None:
"""Validate the Decision maker's desired lower and upper bounds for objective values.
Args:
dimensions_data (pd.DataFrame): DataFrame including information whether an objective is minimized or
maximized, for each objective. In addition, includes ideal and nadir vectors.
bounds (np.ndarray): Desired lower and upper bounds for each objective.
n_objectives (int): Number of objectives in problem.
Returns:
Raises:
ValidationError: In case desired bounds are invalid.
"""
if not isinstance(bounds, np.ndarray):
msg = "Please specify bounds as a numpy array. Current type: {}.".format(type(bounds))
raise ValidationError(msg)
if len(bounds) != n_objectives:
msg = "Length of 'bounds' ({}) must be the same as number of objectives ({}).".format(len(bounds), n_objectives)
raise ValidationError(msg)
if not all(isinstance(b, (np.ndarray, list)) for b in bounds):
print(type(bounds[0]))
msg = "Please give bounds for each objective in a list."
raise ValidationError(msg)
if any(len(b) != 2 for b in bounds):
msg = "Length of each item of 'bounds' must 2, containing the lower and upper bound for an objective."
raise ValidationError(msg)
if any(b[0] > b[1] for b in bounds):
msg = "Lower bound cannot be greater than upper bound. Please specify lower bound first, then upper bound."
raise ValidationError(msg)
# check that bounds are within ideal and nadir points for each objective
for i, b in enumerate(bounds):
if dimensions_data.loc['minimize'].values.tolist()[i] == 1: # minimized objectives
if dimensions_data.loc['ideal'].values.tolist()[i] is not None:
if b[0] < dimensions_data.loc['ideal'].values.tolist()[i]:
msg = "Lower bound cannot be lower than ideal value for objective. Ideal vector: {}." \
.format(dimensions_data.loc['ideal'].values.tolist())
raise ValidationError(msg)
if dimensions_data.loc['nadir'].values.tolist()[i] is not None:
if b[1] > dimensions_data.loc['nadir'].values.tolist()[i]:
msg = "Upper bound cannot be higher than nadir value for objective. Nadir vector: {}." \
.format(dimensions_data.loc['nadir'].values.tolist())
raise ValidationError(msg)
else: # maximized objectives:
if dimensions_data.loc['ideal'].values.tolist()[i] is not None:
if b[1] > dimensions_data.loc['ideal'].values.tolist()[i]:
msg = "Upper bound cannot be higher than ideal value for objective. Ideal vector: {}." \
.format(dimensions_data.loc['ideal'].values.tolist())
raise ValidationError(msg)
if dimensions_data.loc['nadir'].values.tolist()[i] is not None:
if b[0] < dimensions_data.loc['nadir'].values.tolist()[i]:
msg = "Lower bound cannot be lower than nadir value for objective. Nadir vector: {}." \
.format(dimensions_data.loc['nadir'].values.tolist())
raise ValidationError(msg)