Source code for mealpy.utils.problem

#!/usr/bin/env python
# Created by "Thieu" at 17:28, 13/10/2021 ----------%
#       Email: nguyenthieu2102@gmail.com            %                                                    
#       Github: https://github.com/thieu1995        %                         
# --------------------------------------------------%

import numbers
import numpy as np
from typing import Union, List, Tuple, Dict
from mealpy.utils.space import BaseVar, IntegerVar, FloatVar, PermutationVar, StringVar, BinaryVar, BoolVar, MixedSetVar
from mealpy.utils.logger import Logger
from mealpy.utils.target import Target


[docs]class Problem: SUPPORTED_VARS = (IntegerVar, FloatVar, PermutationVar, StringVar, BinaryVar, BoolVar, MixedSetVar) SUPPORTED_ARRAYS = (list, tuple, np.ndarray) def __init__(self, bounds: Union[List, Tuple, np.ndarray, BaseVar], minmax: str = "min", **kwargs) -> None: self._bounds, self.lb, self.ub = None, None, None self.minmax = minmax self.seed = None self.name, self.log_to, self.log_file = "P", "console", "history.txt" self.n_objs, self.obj_weights = 1, None self.n_dims, self.save_population = None, False self.__set_keyword_arguments(kwargs) self.set_bounds(bounds) self.__set_functions() self.logger = Logger(self.log_to, log_file=self.log_file).create_logger(name=f"{__name__}.{__class__.__name__}", format_str='%(asctime)s, %(levelname)s, %(name)s [line: %(lineno)d]: %(message)s') @property def bounds(self): return self._bounds
[docs] def set_bounds(self, bounds): if isinstance(bounds, BaseVar): bounds.seed = self.seed self._bounds = [bounds, ] elif type(bounds) in self.SUPPORTED_ARRAYS: self._bounds = [] for bound in bounds: if isinstance(bound, BaseVar): bound.seed = self.seed else: raise ValueError(f"Invalid bounds. All variables in bounds should be an instance of {self.SUPPORTED_VARS}") self._bounds.append(bound) else: raise TypeError(f"Invalid bounds. It should be type of {self.SUPPORTED_ARRAYS} or an instance of {self.SUPPORTED_VARS}") self.lb = np.concatenate([bound.lb for bound in self._bounds]) self.ub = np.concatenate([bound.ub for bound in self._bounds])
[docs] def set_seed(self, seed: int = None) -> None: self.seed = seed for idx in range(len(self._bounds)): self._bounds[idx].seed = seed
def __set_keyword_arguments(self, kwargs): for key, value in kwargs.items(): setattr(self, key, value) def __set_functions(self): tested_solution = self.generate_solution(encoded=True) self.n_dims = len(tested_solution) result = self.obj_func(tested_solution) if type(result) in self.SUPPORTED_ARRAYS: result = np.array(result).flatten() self.n_objs = len(result) if self.n_objs > 1: if type(self.obj_weights) in self.SUPPORTED_ARRAYS: self.obj_weights = np.array(self.obj_weights).flatten() if self.n_objs != len(self.obj_weights): raise ValueError(f"{self.n_objs}-objective problem, but N weights = {len(self.obj_weights)}.") self.msg = f"Solving {self.n_objs}-objective optimization problem with weights: {self.obj_weights}." else: raise ValueError(f"Solving {self.n_objs}-objective optimization, need to set obj_weights list with length: {self.n_objs}") elif self.n_objs == 1: self.obj_weights = np.ones(1) self.msg = f"Solving single objective optimization problem." else: raise ValueError(f"obj_func needs to return a single value or a list of values") elif isinstance(result, numbers.Number): self.obj_weights = np.ones(1) self.msg = f"Solving single objective optimization problem." else: raise ValueError(f"obj_func needs to return a single value or a list of values")
[docs] def obj_func(self, x: np.ndarray) -> Union[List, Tuple, np.ndarray, int, float]: """Objective function Args: x (numpy.ndarray): Solution. Returns: float: Function value of `x`. """ raise NotImplementedError
[docs] def get_name(self) -> str: """ Returns: string: The name of the problem """ return self.name
[docs] def get_class_name(self) -> str: """Get class name.""" return self.__class__.__name__
[docs] @staticmethod def encode_solution_with_bounds(x, bounds): x_new = [] for idx, var in enumerate(bounds): x_new += list(var.encode(x[idx])) return np.array(x_new)
[docs] @staticmethod def decode_solution_with_bounds(x, bounds): x_new, n_vars = {}, 0 for idx, var in enumerate(bounds): temp = var.decode(x[n_vars:n_vars + var.n_vars]) if var.n_vars == 1: x_new[var.name] = temp[0] else: x_new[var.name] = temp n_vars += var.n_vars return x_new
[docs] @staticmethod def correct_solution_with_bounds(x: Union[List, Tuple, np.ndarray], bounds: List) -> np.ndarray: x_new, n_vars = [], 0 for idx, var in enumerate(bounds): x_new += list(var.correct(x[n_vars:n_vars+var.n_vars])) n_vars += var.n_vars return np.array(x_new)
[docs] @staticmethod def generate_solution_with_bounds(bounds: Union[List, Tuple, np.ndarray], encoded: bool = True) -> Union[List, np.ndarray]: x = [var.generate() for var in bounds] if encoded: return Problem.encode_solution_with_bounds(x, bounds) return x
[docs] def encode_solution(self, x: Union[List, tuple, np.ndarray]) -> np.ndarray: """ Encode the real-world solution to optimized solution (real-value solution) Args: x (Union[List, tuple, np.ndarray]): The real-world solution Returns: The real-value solution """ return self.encode_solution_with_bounds(x, self.bounds)
[docs] def decode_solution(self, x: np.ndarray) -> Dict: """ Decode the encoded solution to real-world solution Args: x (np.ndarray): The real-value solution Returns: The real-world (decoded) solution """ return self.decode_solution_with_bounds(x, self.bounds)
[docs] def correct_solution(self, x: np.ndarray) -> np.ndarray: """ Correct the solution to valid bounds Args: x (np.ndarray): The real-value solution Returns: The corrected solution """ return self.correct_solution_with_bounds(x, self.bounds)
[docs] def generate_solution(self, encoded: bool = True) -> Union[List, np.ndarray]: """ Generate the solution. Args: encoded (bool): Encode the solution or not Returns: the encoded/non-encoded solution for the problem """ return self.generate_solution_with_bounds(self.bounds, encoded)
[docs] def get_target(self, solution: np.ndarray) -> Target: """ Args: solution: The real-value solution Returns: The target object """ objs = self.obj_func(solution) return Target(objectives=objs, weights=self.obj_weights)