Source code for iwopy.core.function_list

import numpy as np

from .function import OptFunction


[docs]class OptFunctionList(OptFunction): """ A list of functions. The main point of this class is to manage the variables and components of the added functions. Add functions to the list via the `append` function, and don't forget to initialize. Attributes ---------- func_vars_int: list of lists of int For each added function, the subset of integer variables func_vars_float: list of lists of int For each added function, the subset of float variables sizes: list of int The components of each added function :group: core """
[docs] def __init__(self, problem, name): """ Constructor Parameters ---------- problem: iwopy.Problem The underlying optimization problem name: str The function name """ super().__init__(problem, name) self._functions = [] self._cnames = [] self.func_vars_int = [] self.func_vars_float = [] self.sizes = []
[docs] def append(self, function): """ Adds a function to the list. Parameters ---------- function: iwopy.core.OptFunction The function """ if self.initialized: raise ValueError( f"FunctionList '{self.name}': Attempt to add function '{function.name}' after initialization" ) if function.problem is not self.problem: raise ValueError( f"FunctionList '{self.name}': Cannot add function '{function.name}' since problems don't match. Expected '{self.problem.name}', found '{function.problem.name}'" ) self._functions.append(function) self._cnames += list(function.component_names)
@property def functions(self): """ The list of added funtions Returns ------- funcs: list of iwopy.core.OptFunction The list of added functions """ return self._functions @property def n_functions(self): """ The number of added functions Returns ------- n: int The total number of added functions """ return len(self.functions)
[docs] def initialize(self, verbosity=0): """ Initialize the object. Parameters ---------- verbosity: int The verbosity level, 0 = silent """ self._vnamesi = [] self._vnamesf = [] self.sizes = [] for f in self.functions: if not f.initialized: f.initialize(verbosity) self._vnamesi += f.var_names_int self._vnamesf += f.var_names_float self.sizes.append(f.n_components()) self._vnamesi = list(dict.fromkeys(self._vnamesi)) self._vnamesf = list(dict.fromkeys(self._vnamesf)) def getv(vnames, fvnames): if not len(fvnames): return [] l = [vnames.index(v) for v in fvnames] return np.s_[l[0]: l[-1]] if list(range(l[0], l[-1])) == l else l self.func_vars_int = [ getv(self._vnamesi, f.var_names_int) for f in self.functions ] self.func_vars_float = [ getv(self._vnamesf, f.var_names_float) for f in self.functions ] super().initialize(verbosity)
[docs] def vardeps_int(self): """ Gets the dependencies of all components on the function int variables Returns ------- deps: numpy.ndarray of bool The dependencies of components on function variables, shape: (n_components, n_vars_int) """ deps = np.zeros((self.n_components(), self.n_vars_int), dtype=bool) i0 = 0 for f in self.functions: i1 = i0 + f.n_components() deps[i0:i1] = f.vardeps_int() i0 = i1 return deps
[docs] def vardeps_float(self): """ Gets the dependencies of all components on the function float variables Returns ------- deps: numpy.ndarray of bool The dependencies of components on function variables, shape: (n_components, n_vars_float) """ deps = np.zeros((self.n_components(), self.n_vars_float), dtype=bool) i0 = 0 for f in self.functions: i1 = i0 + f.n_components() deps[i0:i1] = f.vardeps_float() i0 = i1 return deps
[docs] def n_components(self): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return sum(self.sizes)
[docs] def split_individual(self, data): """ Splits result values or other data into individual function data. Parameters ---------- data: numpy.ndarray The data, shape: (n_components,) Returns ------- fdata: list of numpy.ndarray The data for each function, list entry shapes: (n_func_components,) """ out = [] i0 = 0 for fi in range(self.n_functions): i1 = i0 + self.sizes[fi] out.append(data[i0:i1]) i0 = i1 return out
[docs] def split_population(self, data): """ Splits result values or other data into individual function data. Parameters ---------- data: numpy.ndarray The data, shape: (n_pop, n_components) Returns ------- fdata: list of numpy.ndarray The data for each function, list entry shapes: (n_pop, n_func_components) """ out = [] i0 = 0 for fi in range(self.n_functions): i1 = i0 + self.sizes[fi] out.append(data[:, i0:i1]) i0 = i1 return out
[docs] def calc_individual(self, vars_int, vars_float, problem_results, components=None): """ Calculate values for a single individual of the underlying problem. Parameters ---------- vars_int: np.array The integer variable values, shape: (n_vars_int,) vars_float: np.array The float variable values, shape: (n_vars_float,) problem_results: Any The results of the variable application to the problem components: list of int, optional The selected components or None for all Returns ------- values: np.array The component values, shape: (n_sel_components,) """ cmpnts = np.arange(self.n_components()) if components is None else components values = np.full(len(cmpnts), np.nan, dtype=np.float64) i0 = 0 j0 = 0 for fi, f in enumerate(self.functions): i1 = i0 + self.sizes[fi] cts = ( None if components is None else [i - i0 for i in cmpnts if i >= i0 and i < i1] ) if cts is None or len(cts): j1 = j0 + (self.sizes[fi] if cts is None else len(cts)) varsi = vars_int[self.func_vars_int[fi]] varsf = vars_float[self.func_vars_float[fi]] values[j0:j1] = f.calc_individual(varsi, varsf, problem_results, cts) j0 = j1 i0 = i1 return values
[docs] def calc_population(self, vars_int, vars_float, problem_results, components=None): """ Calculate values for all individuals of a population. Parameters ---------- vars_int: np.array The integer variable values, shape: (n_pop, n_vars_int) vars_float: np.array The float variable values, shape: (n_pop, n_vars_float) problem_results: Any The results of the variable application to the problem components: list of int, optional The selected components or None for all Returns ------- values: np.array The component values, shape: (n_pop, n_sel_components,) """ n_pop = vars_float.shape[0] cmpnts = np.arange(self.n_components()) if components is None else components values = np.full((n_pop, len(cmpnts)), np.nan, dtype=np.float64) i0 = 0 j0 = 0 for fi, f in enumerate(self.functions): i1 = i0 + self.sizes[fi] cts = ( None if components is None else [i - i0 for i in cmpnts if i >= i0 and i < i1] ) if cts is None or len(cts): j1 = j0 + (self.sizes[fi] if cts is None else len(cts)) varsi = vars_int[:, self.func_vars_int[fi]] varsf = vars_float[:, self.func_vars_float[fi]] values[:, j0:j1] = f.calc_population(varsi, varsf, problem_results, cts) j0 = j1 i0 = i1 return values
[docs] def finalize_individual(self, vars_int, vars_float, problem_results, verbosity=1): """ Finalization, given the champion data. Parameters ---------- vars_int: np.array The optimal integer variable values, shape: (n_vars_int,) vars_float: np.array The optimal float variable values, shape: (n_vars_float,) problem_results: Any The results of the variable application to the problem verbosity: int The verbosity level, 0 = silent Returns ------- values: np.array The component values, shape: (n_components,) """ values = np.full(self.n_components(), np.nan, dtype=np.float64) i0 = 0 for fi, f in enumerate(self.functions): i1 = i0 + self.sizes[fi] varsi = vars_int[self.func_vars_int[fi]] varsf = vars_float[self.func_vars_float[fi]] values[i0:i1] = f.finalize_individual( varsi, varsf, problem_results, verbosity ) i0 = i1 return values
[docs] def finalize_population(self, vars_int, vars_float, problem_results, verbosity=1): """ Finalization, given the final population data. Parameters ---------- vars_int: np.array The integer variable values of the final generation, shape: (n_pop, n_vars_int) vars_float: np.array The float variable values of the final generation, shape: (n_pop, n_vars_float) problem_results: Any The results of the variable application to the problem verbosity: int The verbosity level, 0 = silent Returns ------- values: np.array The component values, shape: (n_pop, n_components) """ n_pop = vars_float.shape[0] values = np.full((n_pop, self.n_components()), np.nan, dtype=np.float64) i0 = 0 for fi, f in enumerate(self.functions): i1 = i0 + self.sizes[fi] varsi = vars_int[:, self.func_vars_int[fi]] varsf = vars_float[:, self.func_vars_float[fi]] values[:, i0:i1] = f.finalize_population( varsi, varsf, problem_results, verbosity ) i0 = i1 return values
[docs] def ana_deriv(self, vars_int, vars_float, var, components=None): """ Calculates the analytic derivative, if possible. Use `numpy.nan` if analytic derivatives cannot be calculated. Parameters ---------- vars_int: np.array The integer variable values, shape: (n_vars_int,) vars_float: np.array The float variable values, shape: (n_vars_float,) var: int The index of the differentiation float variable components: list of int The selected components, or None for all Returns ------- deriv: numpy.ndarray The derivative values, shape: (n_sel_components,) """ cmpnts = np.arange(self.n_components()) if components is None else components deriv = np.full(len(cmpnts), np.nan, dtype=np.float64) i0 = 0 j0 = 0 for fi, f in enumerate(self.functions): i1 = i0 + self.sizes[fi] if var in list(self.func_vars_float[fi]): cts = ( None if components is None else [i - i0 for i in cmpnts if i >= i0 and i < i1] ) if cts is None or len(cts): j1 = j0 + (self.sizes[fi] if cts is None else len(cts)) varsi = vars_int[self.func_vars_int[fi]] varsf = vars_float[self.func_vars_float[fi]] vi = list(self.func_vars_float[fi]).index(var) deriv[j0:j1] = f.ana_deriv(varsi, varsf, vi, components=cts) j0 = j1 i0 = i1 return deriv