import numpy as np
import xarray as xr
from foxes_opt.core.farm_objective import FarmObjective
from foxes import variables as FV
import foxes.constants as FC
[docs]
class FarmVarObjective(FarmObjective):
"""
Objectives based on farm variables.
Attributes
----------
variable: str
The variable name
minimize: bool
Switch for maximizing or minimizing
deps: list of str
The foxes variables on which the variable depends,
or None for all
rules: dict
Contraction rules. Key: coordinate name str, value
is str: min, max, sum, mean
scale: float
The scaling factor
:group: opt.objectives
"""
[docs]
def __init__(
self,
problem,
name,
variable,
contract_states,
contract_turbines,
minimize,
deps=None,
scale=1.0,
**kwargs,
):
"""
Constructor.
Parameters
----------
problem: foxes_opt.FarmOptProblem
The underlying optimization problem
name: str
The name of the objective function
variable: str
The foxes variable name
contract_states: str
Contraction rule for states: min, max, sum, mean, weights
contract_turbines: str
Contraction rule for turbines: min, max, sum, mean
minimize: bool
Switch for maximizing or minimizing
deps: list of str
The foxes variables on which the variable depends,
or None for all
scale: float
The scaling factor
kwargs: dict, optional
Additional parameters for `FarmObjective`
"""
super().__init__(problem, name, **kwargs)
self.variable = variable
self.minimize = minimize
self.deps = deps
self.scale = scale
self.rules = {FC.STATE: contract_states, FC.TURBINE: contract_turbines}
[docs]
def initialize(self, verbosity=0):
"""
Initialize the object.
Parameters
----------
verbosity: int
The verbosity level, 0 = silent
"""
super().initialize(verbosity)
[docs]
def n_components(self):
"""
Returns the number of components of the
function.
Returns
-------
int:
The number of components.
"""
return 1
[docs]
def maximize(self):
"""
Returns flag for maximization of each component.
Returns
-------
flags: np.array
Bool array for component maximization,
shape: (n_components,)
"""
return [not self.minimize]
[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)
"""
if self.deps is None:
return super().vardeps_float()
out = np.zeros((self.n_components(), self.n_vars_float), dtype=bool)
for i, tvr in enumerate(self.var_names_float):
v, ti = self.problem.parse_tvar(tvr)
if v in self.deps and ti in self.sel_turbines:
out[0, i] = True
return out
def _contract(self, data):
"""
Helper function for data contraction
"""
for dim, rule in self.rules.items():
if rule == "min":
data = data.min(dim=dim)
elif rule == "max":
data = data.max(dim=dim)
elif rule == "sum":
data = data.sum(dim=dim)
elif rule == "mean":
data = data.mean(dim=dim)
elif dim == FC.STATE and rule == "weights":
odims = data.dims
weights = self.problem._org_weights
if len(odims) > 1 and odims[:2] == (FC.STATE, FC.TURBINE):
data = np.einsum("st...,st->t...", data, weights)
data = xr.DataArray(data, dims=odims[1:])
elif len(odims) > 2 and odims[:3] == (FC.POP, FC.STATE, FC.TURBINE):
data = np.einsum("pst...,st->pt...", data, weights)
data = xr.DataArray(data, dims=(FC.POP,) + odims[2:])
else:
raise NotImplementedError(
f"Contraction error for '{rule}' for dim '{dim}': Incompatible data dims {odims}, shape {data.shape}, for weights of shape {weights.shape}"
)
elif dim == FC.STATE:
raise ValueError(
f"Objective '{self.name}': Unknown contraction for dimension '{dim}': '{rule}'. Choose: min, max, sum, mean, weights"
)
else:
raise ValueError(
f"Objective '{self.name}': Unknown contraction for dimension '{dim}': '{rule}'. Choose: min, max, sum, mean"
)
return data
[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,)
"""
data = problem_results[self.variable]
if self.n_sel_turbines < self.farm.n_turbines:
data = data[:, self.sel_turbines]
data = self._contract(data) / self.scale
return np.array([data], dtype=np.float64)
[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 = problem_results["n_pop"].values
n_states = problem_results["n_org_states"].values
n_turbines = problem_results.sizes[FC.TURBINE]
data = (
problem_results[self.variable]
.to_numpy()
.reshape(n_pop, n_states, n_turbines)
)
data = xr.DataArray(data, dims=(FC.POP, FC.STATE, FC.TURBINE))
if self.n_sel_turbines < self.farm.n_turbines:
data = data[:, self.sel_turbines]
return self._contract(data / self.scale).to_numpy()[:, None]
[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,)
"""
return (
super().finalize_individual(
vars_int, vars_float, problem_results, verbosity
)
* self.scale
)
[docs]
class MaxFarmPower(FarmVarObjective):
"""
Maximize the mean wind farm power
Parameters
----------
problem: foxes_opt.FarmOptProblem
The underlying optimization problem
name: str
The name of the objective function
kwargs: dict, optional
Additional parameters for `FarmVarObjective`
:group: opt.objectives
"""
[docs]
def __init__(self, problem, name="maximize_power", **kwargs):
if "scale" in kwargs:
scale = kwargs.pop("scale")
else:
scale = 0.0
ttypes = problem.algo.mbook.turbine_types
for t in problem.farm.turbines:
for mname in t.models:
if mname in ttypes:
scale += ttypes[mname].P_nominal
break
super().__init__(
problem,
name,
variable=FV.P,
contract_states="weights",
contract_turbines="sum",
minimize=False,
scale=scale,
**kwargs,
)
[docs]
class MinimalMaxTI(FarmVarObjective):
"""
Minimize the maximal turbine TI
Parameters
----------
problem: foxes_opt.FarmOptProblem
The underlying optimization problem
name: str
The name of the objective function
kwargs: dict, optional
Additional parameters for `FarmVarObjective`
:group: opt.objectives
"""
[docs]
def __init__(self, problem, name="minimize_TI", **kwargs):
scale = kwargs.pop("scale") if "scale" in kwargs else 1.0
super().__init__(
problem,
name,
variable=FV.TI,
contract_states="max",
contract_turbines="max",
minimize=True,
scale=scale,
**kwargs,
)