import numpy as np
from abc import abstractmethod
from foxes.models.turbine_models import SetFarmVars
from foxes.config import config
from foxes.utils import new_instance
from .farm_opt_problem import FarmOptProblem
[docs]
class FarmVarsProblem(FarmOptProblem):
"""
Abstract base class for models that optimize
farm variables.
:group: opt.core
"""
[docs]
def initialize(self, pre_rotor_vars, post_rotor_vars, verbosity=1, **kwargs):
"""
Initialize the object.
Parameters
----------
pre_rotor_vars: list of str or dict
The pre_rotor farm variables. If dict, then
key: sub-model str, value: var names as list of str
post_rotor_vars: list of str or dict
The post_rotor farm variables. If dict, then
key: sub-model str, value: var names as list of str
verbosity: int
The verbosity level, 0 = silent
kwargs: dict, optional
Additional parameters for super class init
"""
self._vars_pre = {}
self._vars_post = {}
if isinstance(pre_rotor_vars, dict):
self._vars_pre = {m: v for m, v in pre_rotor_vars.items() if len(v)}
elif len(pre_rotor_vars):
self._vars_pre = {self.name: pre_rotor_vars}
if isinstance(post_rotor_vars, dict):
self._vars_post = {m: v for m, v in post_rotor_vars.items() if len(v)}
elif len(post_rotor_vars):
self._vars_post = {self.name: post_rotor_vars}
cnt = 0
for src, pre in zip((self._vars_pre, self._vars_post), (True, False)):
for mname, vrs in src.items():
if mname in self.algo.mbook.turbine_models:
m = self.algo.mbook.turbine_models[mname]
if not isinstance(m, SetFarmVars):
raise KeyError(
f"FarmOptProblem '{self.name}': Turbine model entry '{mname}' already exists in model book, and is not of type SetFarmVars"
)
elif m.pre_rotor != pre:
raise ValueError(
f"FarmOptProblem '{self.name}': Turbine model entry '{mname}' exists in model book, and disagrees on pre_rotor = {pre}"
)
else:
self.algo.mbook.turbine_models[mname] = SetFarmVars(pre_rotor=pre)
found = False
for t in self.algo.farm.turbines:
if mname in t.models:
found = True
break
if not found:
raise ValueError(
f"FarmOptProblem '{self.name}': Missing entry '{mname}' among any of the turbine models"
)
cnt += len(vrs)
if not cnt:
raise ValueError(
f"Problem '{self.name}': Neither pre_rotor_vars not post_rotor_vars containing variables"
)
super().initialize(verbosity=verbosity, **kwargs)
[docs]
@abstractmethod
def opt2farm_vars_individual(self, vars_int, vars_float):
"""
Translates optimization variables to farm variables
Parameters
----------
vars_int: numpy.ndarray
The integer optimization variable values,
shape: (n_vars_int,)
vars_float: numpy.ndarray
The float optimization variable values,
shape: (n_vars_float,)
Returns
-------
farm_vars: dict
The foxes farm variables. Key: var name,
value: numpy.ndarray with values, shape:
(n_states, n_sel_turbines)
"""
pass
[docs]
@abstractmethod
def opt2farm_vars_population(self, vars_int, vars_float, n_states):
"""
Translates optimization variables to farm variables
Parameters
----------
vars_int: numpy.ndarray
The integer optimization variable values,
shape: (n_pop, n_vars_int)
vars_float: numpy.ndarray
The float optimization variable values,
shape: (n_pop, n_vars_float)
n_states: int
The number of original (non-pop) states
Returns
-------
farm_vars: dict
The foxes farm variables. Key: var name,
value: numpy.ndarray with values, shape:
(n_pop, n_states, n_sel_turbines)
"""
pass
[docs]
def update_problem_individual(self, vars_int, vars_float):
"""
Update the algo and other data using
the latest optimization variables.
This function is called before running the farm
calculation.
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,)
"""
super().update_problem_individual(vars_int, vars_float)
# prepare:
n_states = self._org_n_states
fvars = self.opt2farm_vars_individual(vars_int, vars_float)
# update turbine model that sets vars to opt values:
for src in (self._vars_pre, self._vars_post):
for mname, vrs in src.items():
model = self.algo.mbook.turbine_models[mname]
model.reset()
for v in vrs:
vals = fvars.pop(v)
if self.all_turbines:
model.add_var(v, vals)
else:
data = np.zeros(
(n_states, self.algo.n_turbines), dtype=config.dtype_double
)
data[:, self.sel_turbines] = vals
model.add_var(v, data)
if len(fvars):
raise KeyError(
f"Problem '{self.name}': Too many farm vars from opt2farm_vars_individual: {list(fvars.keys())}"
)
[docs]
def update_problem_population(self, vars_int, vars_float):
"""
Update the algo and other data using
the latest optimization variables.
This function is called before running the farm
calculation.
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,)
"""
super().update_problem_population(vars_int, vars_float)
# prepare:
n_pop = len(vars_float)
n_states = self._org_n_states
n_pstates = n_states * n_pop
fvars = self.opt2farm_vars_population(vars_int, vars_float, n_states)
# update turbine model that sets vars to opt values:
for src in (self._vars_pre, self._vars_post):
for mname, vrs in src.items():
model = self.algo.mbook.turbine_models[mname]
model.reset()
for v in vrs:
vals = fvars.pop(v)
shp0 = list(vals.shape)
shp1 = [n_pstates] + shp0[2:]
if self.all_turbines:
model.add_var(v, vals.reshape(shp1))
else:
data = np.zeros(
(n_pstates, self.algo.n_turbines), dtype=config.dtype_double
)
data[:, self.sel_turbines] = vals.reshape(shp1)
model.add_var(v, data)
del data
if len(fvars):
raise KeyError(
f"Problem '{self.name}': Too many farm vars from opt2farm_vars_population: {list(fvars.keys())}"
)
[docs]
@classmethod
def new(cls, problem_type, *args, **kwargs):
"""
Run-time farm vars opt problem factory.
Parameters
----------
problem_type: string
The selected derived class name
args: tuple, optional
Additional parameters for the constructor
kwargs: dict, optional
Additional parameters for the constructor
"""
return new_instance(cls, problem_type, *args, **kwargs)