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
import foxes.variables as FV
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, model_vars, verbosity=1, **kwargs):
"""
Initialize the object.
Parameters
----------
model_vars: dict or list
The variables to optimize. If dict, key: model name, value: list of variable names.
verbosity: int
The verbosity level, 0 = silent
kwargs: dict, optional
Additional parameters for super class init
"""
self._model_vars = {}
if isinstance(model_vars, dict):
self._model_vars = {m: v for m, v in model_vars.items() if len(v)}
elif len(model_vars):
self._model_vars = {self.name: model_vars}
cnt = 0
for mname, vrs in self._model_vars.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"
)
else:
self.algo.mbook.turbine_models[mname] = SetFarmVars()
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}': No variables to optimize")
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 mname, vrs in self._model_vars.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 mname, vrs in self._model_vars.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
# special case (x, y) needs to reshape turbine property. Value will be set by model
if v in [FV.X, FV.Y]:
for ti in self.sel_turbines:
xy = self.algo.farm.turbines[ti].xy
if len(xy.shape) > 1 and xy.shape[0] != n_pstates:
self.algo.farm.turbines[ti].xy = np.full(
(n_pstates, 2), np.nan, dtype=config.dtype_double
)
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)