Source code for tespy.tools.optimization

try:
    import pygmo as pg
except ImportError:
    pg = None

import pandas as pd

from tespy.tools.helpers import merge_dicts


[docs] class OptimizationProblem: r""" The OptimizationProblem handles the optimization. - Set up the optimization problems by specifying constraints, upper and lower bounds for the decision variables and selection of the objective function. - Run the optimization, see :py:meth:`tespy.tools.optimization.OptimizationProblem.run`. - Provide the optimization results DataFrame in the :code:`.individuals` attribute of the :code:`OptimizationProblem` class. Parameters ---------- model : custom class Object of some class, which provides all the methods required by the optimization suite, see the Example section for a downloadable template of the implementation. variables : dict Dictionary containing the decision variables and their respective bounds. constraints : dict Dictionary containing the constraints for the model. objective : str Name of the objective. :code:`objective` is passed to the :code:`get_objective` method of your tespy model instance. Note ---- For the required structure of the input dictionaries see the example in below. Installation of pygmo via pip is not available for Windows and OSX users currently. Please use conda instead or refer to their `documentation <https://esa.github.io/pygmo2/>`_. Example ------- For an example please go to the tutorials section of TESPy's online documentation. """ def __init__(self, model, variables={}, constraints={}, objective="objective"): if pg is None: msg = ( "For this function of TESPy pygmo has to be installed. Either" " use pip (Linux users only) or conda to install the latest" " pygmo version." ) raise ImportError(msg) self.model = model default_variables = {"Connections": {}, "Components": {}} default_constraints = { "lower limits": {"Connections": {}, "Components": {}}, "upper limits": {"Connections": {}, "Components": {}} } # merge the passed values into the default dictionary structure self.variables = merge_dicts(variables, default_variables) self.constraints = merge_dicts(constraints, default_constraints) self.objective = objective self.variable_list = [] self.constraint_list = [] self.objective_list = [objective] self.nobj = len(self.objective_list) self.bounds = [[], []] for obj, data in self.variables.items(): for label, params in data.items(): if obj in ["Connections", "Components"]: for param in params: self.bounds[0] += [ self.variables[obj][label][param]['min'] ] self.bounds[1] += [ self.variables[obj][label][param]['max'] ] self.variable_list += [obj + '-' + label + '-' + param] else: self.bounds[0] += [self.variables[obj][label]['min']] self.bounds[1] += [self.variables[obj][label]['max']] self.variable_list += [obj + '-' + label] self.input_dict = self.variables.copy() self.nic = 0 self.collect_constraints("upper", build=True) self.collect_constraints("lower", build=True)
[docs] def collect_constraints(self, border, build=False): """Collect the constraints Parameters ---------- border : str "upper" or "lower", determine which constraints to collect. build : bool, optional If True, the constraints are evaluated and returned, by default False Returns ------- tuple Return the upper and lower constraints evaluation lists. """ evaluation = [] for obj, data in self.constraints[f'{border} limits'].items(): for label, constraints in data.items(): for param, constraint in constraints.items(): # to build the equations if build: self.nic += 1 if isinstance(constraint, str): right_side = '-'.join(self.constraints[constraint]) else: right_side = str(constraint) direction = '>=' if border == 'lower' else '<=' self.constraint_list += [ obj + '-' + label + '-' + param + direction + right_side ] # to get the constraints evaluation else: if isinstance(constraint, str): c = ( self.model.get_param( *self.constraints[constraint] ) - self.model.get_param(obj, label, param) ) else: c = ( constraint - self.model.get_param(obj, label, param) ) if border == 'lower': evaluation += [c] else: evaluation += [-c] if build: return None else: return evaluation
[docs] def fitness(self, x): """Evaluate the fitness function of an individual. Parameters ---------- x : list List of the decision variables' values of the current individual. Returns ------- fitness : list A list containing the fitness function evaluation as well as the evaluation of the upper and lower constraints. """ i = 0 for obj, data in self.variables.items(): for label, params in data.items(): if obj in ["Connections", "Components"]: for param in params: self.input_dict[obj][label][param] = x[i] i += 1 else: self.input_dict[obj][label] = x[i] i += 1 self.model.solve_model(**self.input_dict) f1 = [self.model.get_objective(self.objective)] cu = self.collect_constraints("upper") cl = self.collect_constraints("lower") return f1 + cu + cl
[docs] def get_nobj(self): """Return number of objectives.""" return self.nobj
# inequality constraints (equality constraints not required)
[docs] def get_nic(self): """Return number of inequality constraints.""" return self.nic
[docs] def get_bounds(self): """Return bounds of decision variables.""" return self.bounds
def _process_generation_data(self, gen, pop): """Process the data of the individuals within one generation. Parameters ---------- gen : int Generation number. pop : pygmo.population PyGMO population object. """ individual = 0 for x in pop.get_x(): self.individuals.loc[(gen, individual), self.variable_list] = x individual += 1 individual = 0 for objective in pop.get_f(): self.individuals.loc[ (gen, individual), self.objective_list + self.constraint_list ] = objective individual += 1 self.individuals['valid'] = ( self.individuals[self.constraint_list] < 0 ).all(axis='columns')
[docs] def run(self, algo, pop, num_ind, num_gen): """Run the optimization algorithm. Parameters ---------- algo : pygmo.core.algorithm PyGMO optimization algorithm. pop : pygmo.core.population PyGMO population. num_ind : int Number of individuals. num_gen : int Number of generations. """ self.individuals = pd.DataFrame( index=range(num_gen * num_ind) ) self.individuals["gen"] = [ gen for gen in range(num_gen) for ind in range(num_ind) ] self.individuals["ind"] = [ ind for gen in range(num_gen) for ind in range(num_ind) ] self.individuals.set_index(["gen", "ind"], inplace=True) # replace prints with logging gen = 0 for gen in range(num_gen - 1): self._process_generation_data(gen, pop) print('Evolution: {}'.format(gen)) for i in range(len(self.objective_list)): print( self.objective_list[i] + ': {}'.format( round(pop.champion_f[i], 4) ) ) for i in range(len(self.variable_list)): print( self.variable_list[i] + ': {}'.format( round(pop.champion_x[i], 4) ) ) pop = algo.evolve(pop) if num_gen > 1: gen += 1 self._process_generation_data(gen, pop) print('Final evolution: {}'.format(gen)) for i in range(len(self.objective_list)): print( self.objective_list[i] + ': {}'.format( round(pop.champion_f[i], 4) ) ) for i in range(len(self.variable_list)): print( self.variable_list[i] + ': {}'.format( round(pop.champion_x[i], 4) ) ) return pop