from foxes.algorithms.downwind.downwind import Downwind
from foxes.core import FarmDataModelList
from foxes.utils import Dict
from . import models as mdls
[docs]
class Iterative(Downwind):
"""
Iterative calculation of farm data.
Attributes
----------
max_it: int
The maximal number of iterations
conv_crit: foxes.algorithms.iterative.ConvCrit
The convergence criteria
prev_farm_results: xarray.Dataset
Results from the previous iteration
:group: algorithms.iterative
"""
[docs]
@classmethod
def get_model(cls, name):
"""
Get the algorithm specific model
Parameters
----------
name: str
The model name
Returns
-------
model: foxes.core.model
The model
"""
try:
return getattr(mdls, name)
except AttributeError:
return super().get_model(name)
[docs]
def __init__(
self,
*args,
max_it=None,
conv_crit="default",
mod_cutin={},
**kwargs,
):
"""
Constructor.
Parameters
----------
args: tuple, optional
Arguments for Downwind
max_it: int, optional
The maximal number of iterations
conv_crit: foxes.algorithms.iterative.ConvCrit, optional
The convergence criteria
mod_cutin: dict, optional
Parameters for cutin modification
kwargs: dict, optional
Keyword arguments for Downwind
"""
super().__init__(*args, **kwargs)
self.max_it = 2 + self.farm.n_turbines**2 if max_it is None else max_it
self.conv_crit = (
self.get_model("DefaultConv")() if conv_crit == "default" else conv_crit
)
self.__prev_farm_results = None
self._it = None
self._mlist = None
self._reamb = False
self._urelax = None
self._mod_cutin = dict(modify_ct=True, modify_P=False)
self._mod_cutin.update(mod_cutin)
self.verbosity = self.verbosity - 1
@property
def farm_results_downwind(self):
"""
Gets the all-chunks farm results in downwind order
from the previous iteration
Returns
-------
fres: xarray.Datatset
The all-chunks farm results during calculations
"""
return self.__prev_farm_results
[docs]
def set_urelax(self, entry_point, **urel):
"""
Sets under-relaxation parameters.
Parameters
----------
entry_point: str
The entry point: first, pre_rotor, post_rotor,
pre_wake, last
urel: dict
The variables and their under-relaxation values
"""
if self.initialized:
raise ValueError(f"Attempt to set_urelax after initialization")
if self._urelax is None:
self._urelax = Dict(
first={},
pre_rotor={},
post_rotor={},
pre_wake={},
last={},
)
self._urelax[entry_point].update(urel)
[docs]
def initialize(self):
"""
Initializes the algorithm.
"""
super().initialize()
if len(self._mod_cutin):
for t in self.farm_controller.turbine_types:
t.modify_cutin(**self._mod_cutin)
@property
def urelax(self):
"""
Returns the under-relaxation parameters
Returns
-------
urlx: foxes.utils.Dict
The under-relaxation parameters
"""
return self._urelax
@property
def iterations(self):
"""
The current iteration number
Returns
-------
it: int
The current iteration number
"""
return self._it
def _collect_farm_models(
self,
outputs,
calc_parameters,
ambient,
):
"""
Helper function that creates model list
"""
if self._it == 0:
self._mlist0, self._calc_pars0 = super()._collect_farm_models(
outputs=False,
calc_parameters=calc_parameters,
ambient=ambient,
)
n = 0
if self._urelax is not None:
if len(self._urelax["first"]):
self._mlist0.insert(0, mdls.URelax(**self._urelax["first"]))
self._calc_pars0.insert(0, {})
self._reamb = True
n += 1
if len(self._urelax["pre_rotor"]):
self._mlist0.insert(2 + n, mdls.URelax(**self._urelax["pre_rotor"]))
self._calc_pars0.insert(2 + n, {})
self._reamb = True
n += 1
if len(self._urelax["post_rotor"]):
self._mlist0.insert(
4 + n, mdls.URelax(**self._urelax["post_rotor"])
)
self._calc_pars0.insert(4 + n, {})
self._reamb = True
n += 1
if len(self._urelax["pre_wake"]):
self._mlist0.models[5 + n].urelax = mdls.URelax(
**self._urelax["pre_wake"]
)
if len(self._urelax["last"]):
self._mlist0.append(mdls.URelax(**self._urelax["last"]))
self._calc_pars0.append({})
return self._mlist0, self._calc_pars0
elif ambient or self._reamb:
return self._mlist0, self._calc_pars0
else:
# prepare:
calc_pars = []
mlist = FarmDataModelList(models=[])
# do not rotate back from downwind order:
if not self._final_run:
# add under-relaxation during wake calculation:
urelax = None
if self._urelax is not None and len(self._urelax["pre_wake"]):
urelax = mdls.URelax(**self._urelax["pre_wake"])
# add model that calculates wake effects:
mlist.models.append(
self.get_model("FarmWakesCalculation")(urelax=urelax)
)
calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
# add under-relaxation:
if self._urelax is not None and len(self._urelax["last"]):
mlist.append(mdls.URelax(**self._urelax["last"]))
calc_pars.append({})
# rotate back from downwind order:
else:
mlist.models.append(self.get_model("ReorderFarmOutput")(outputs))
calc_pars.append(calc_parameters.get(mlist.models[-1].name, {}))
return mlist, calc_pars
def _calc_farm_vars(self, mlist):
"""Helper function that gathers the farm variables"""
if self._it == 0:
super()._calc_farm_vars(mlist)
def _launch_parallel_farm_calc(self, mlist, *data, **kwargs):
"""Helper function for running the main farm calculation"""
return super()._launch_parallel_farm_calc(
mlist, *data, farm_data=self.__prev_farm_results, iterative=True, **kwargs
)
@property
def final_iteration(self):
"""
Flag for the final iteration
Returns
-------
flag: bool
Flag for the final iteration
"""
return self._final_run
[docs]
def calc_farm(self, finalize=True, ret_dwnd_order=False, **kwargs):
"""
Calculate farm data.
Parameters
----------
finalize: bool
Flag for finalization after calculation
ret_dwnd_order: bool
Also return the results in downwind order
kwargs: dict, optional
Arguments for calc_farm in the base class.
Returns
-------
farm_results: xarray.Dataset
The farm results. The calculated variables have
dimensions (state, turbine)
"""
outputs = kwargs.pop("outputs", None)
if outputs == "default":
outputs = self.DEFAULT_FARM_OUTPUTS
fres = None
self._it = -1
self._final_run = False
fres_dwnd = None
while self._it < self.max_it:
self._it += 1
self.print(f"\nAlgorithm {self.name}: Iteration {self._it}\n", vlim=0)
self.__prev_farm_results = fres
fres = super().calc_farm(outputs=None, finalize=False, **kwargs)
fres_dwnd = fres
if self.conv_crit is not None:
if self.eval_conv_block():
self.print(f"{self.name}: Convergence blocked", vlim=0)
else:
conv = self.conv_crit.check_converged(
self,
self.__prev_farm_results,
fres,
verbosity=self.verbosity + 1,
)
if conv:
self.print(
f"\nAlgorithm {self.name}: Convergence reached.\n", vlim=0
)
break
# final run, recovers farm order of results:
self.print("Starting final run", vlim=0)
self._final_run = True
fres = super().calc_farm(outputs=outputs, finalize=False, **kwargs)
# finalize models:
if finalize:
self.print("\n", vlim=0)
self.finalize()
for m in self._mlist0.models:
if m not in self.sub_models():
m.finalize(self, self.verbosity - 1)
del self._mlist0, self._calc_pars0
if ret_dwnd_order:
return fres, fres_dwnd
else:
return fres