from math import sqrt
import foxes.models as fm
import foxes.variables as FV
from foxes.utils import FDict
from foxes.core import (
PointDataModel,
FarmDataModel,
FarmController,
RotorModel,
TurbineType,
TurbineModel,
PartialWakesModel,
WakeFrame,
WakeSuperposition,
WakeModel,
AxialInductionModel,
TurbineInductionModel,
GroundModel,
)
[docs]
class ModelBook:
"""
Container for all kinds of models.
Attributes
----------
point_models: foxes.utils.FDict
The point models. Keys: model name str,
values: foxes.core.PointDataModel
rotor_models: foxes.utils.FDict
The rotor models. Keys: model name str,
values: foxes.core.RotorModel
turbine_types: foxes.utils.FDict
The turbine type models. Keys: model name str,
values: foxes.core.TurbineType
turbine_models: foxes.utils.FDict
The turbine models. Keys: model name str,
values: foxes.core.TurbineModel
farm_models: foxes.utils.FDict
The farm models. Keys: model name str,
values: foxes.core.FarmModel
farm_controllers: foxes.utils.FDict
The farm controllers. Keys: model name str,
values: foxes.core.FarmController
partial_wakes: foxes.utils.FDict
The partial wakes. Keys: model name str,
values: foxes.core.PartialWakeModel
wake_frames: foxes.utils.FDict
The wake frames. Keys: model name str,
values: foxes.core.WakeFrame
wake_superpositions: foxes.utils.FDict
The wake superposition models. Keys: model name str,
values: foxes.core.WakeSuperposition
wake_models: foxes.utils.FDict
The wake models. Keys: model name str,
values: foxes.core.WakeModel
induction_models: foxes.utils.FDict
The induction models. Keys: model name str,
values: foxes.core.AxialInductionModel
ground_models: foxes.utils.FDict
The ground models. Keys: model name str,
values: foxes.core.GroundModel
sources: foxes.utils.FDict
All sources dict
base_classes: foxes.utils.FDict
The base classes for all model types
:group: models
"""
[docs]
def __init__(self, Pct_file=None):
"""
Constructor.
Parameters
----------
Pct_file: str, optional
Path to power/ct curve file, for creation
of default turbine type model
"""
self.point_models = FDict(name="point_models")
self.point_models["tke2ti"] = fm.point_models.TKE2TI()
self.point_models["ustar2ti"] = fm.point_models.Ustar2TI()
self.rotor_models = FDict(name="rotor_models")
self.rotor_models["centre"] = fm.rotor_models.CentreRotor()
def _n2n(n2):
n2 = float(n2)
n = int(sqrt(n2))
if n**2 != n2:
raise Exception(
f"GridRotor factory: Value {n2} is not the square of an integer"
)
return n
self.rotor_models.add_factory(
fm.rotor_models.GridRotor,
"grid<n2>",
kwargs=dict(reduce=True),
var2arg={"n2": "n"},
n2=_n2n,
hints={"n2": "(Number of points in square grid)"},
)
self.rotor_models.add_factory(
fm.rotor_models.GridRotor,
"grid<n2>_raw",
kwargs=dict(reduce=False),
var2arg={"n2": "n"},
n2=_n2n,
hints={"n2": "(Number of points in square grid)"},
)
self.rotor_models.add_factory(
fm.rotor_models.LevelRotor,
"level<n>",
kwargs=dict(reduce=True),
n=lambda x: int(x),
hints={"n": "(Number of vertical levels)"},
)
self.rotor_models.add_factory(
fm.rotor_models.LevelRotor,
"level<n>_raw",
kwargs=dict(reduce=False),
n=lambda x: int(x),
hints={"n": "(Number of vertical levels)"},
)
self.turbine_types = FDict(name="turbine_types")
self.turbine_types["null_type"] = fm.turbine_types.NullType()
self.turbine_types["NREL5MW"] = fm.turbine_types.PCtFile(
"NREL-5MW-D126-H90.csv", rho=1.225
)
self.turbine_types["DTU10MW"] = fm.turbine_types.PCtFile(
"DTU-10MW-D178d3-H119.csv", rho=1.225
)
self.turbine_types["IEA15MW"] = fm.turbine_types.PCtFile(
"IEA-15MW-D240-H150.csv", rho=1.225
)
self.turbine_types["IWT7.5MW"] = fm.turbine_types.PCtFile(
"IWT-7d5MW-D164-H100.csv", rho=1.225
)
if Pct_file is not None:
self.turbine_types["Pct"] = fm.turbine_types.PCtFile(Pct_file)
self.turbine_models = FDict(
name="turbine_models",
kTI=fm.turbine_models.kTI(),
kTI_amb=fm.turbine_models.kTI(ti_var=FV.AMB_TI),
thrust2ct=fm.turbine_models.Thrust2Ct(),
PMask=fm.turbine_models.PowerMask(),
yaw2yawm=fm.turbine_models.YAW2YAWM(),
yawm2yaw=fm.turbine_models.YAWM2YAW(),
)
self.turbine_models.add_factory(
fm.turbine_models.kTI,
"kTI_<kTI>",
kTI=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
hints={"kTI": "(Value, e.g. 02 for 0.2)"},
)
self.turbine_models.add_factory(
fm.turbine_models.kTI,
"kTI_amb_<kTI>",
kwargs=dict(ti_var=FV.AMB_TI),
kTI=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
hints={"kTI": "(Value, e.g. 04 for 0.4)"},
)
self.turbine_models.add_factory(
fm.turbine_models.kTI,
"kTI_<kTI>_<kb>",
kTI=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
hints={
"kTI": "(Value, e.g. 04 for 0.4)",
"kb": "(Value, e.g. 004 for 0.04)",
},
)
self.turbine_models.add_factory(
fm.turbine_models.kTI,
"kTI_amb_<kTI>_<kb>",
kwargs=dict(ti_var=FV.AMB_TI),
kTI=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
hints={
"kTI": "(Value, e.g. 04 for 0.4)",
"kb": "(Value, e.g. 004 for 0.04)",
},
)
self.turbine_models["hubh_data"] = fm.turbine_models.RotorCentreCalc(
{
f"{FV.WD}_HH": FV.WD,
f"{FV.WS}_HH": FV.WS,
f"{FV.TI}_HH": FV.TI,
f"{FV.RHO}_HH": FV.RHO,
}
)
self.farm_models = FDict(
name="farm_models",
**{
f"farm_{mname}": fm.farm_models.Turbine2FarmModel(m)
for mname, m in self.turbine_models.items()
},
)
self.farm_controllers = FDict(
name="farm_controllers",
basic_ctrl=fm.farm_controllers.BasicFarmController(),
)
self.partial_wakes = FDict(
name="partial_wakes",
rotor_points=fm.partial_wakes.RotorPoints(),
top_hat=fm.partial_wakes.PartialTopHat(),
centre=fm.partial_wakes.PartialCentre(),
)
self.partial_wakes.add_factory(
fm.partial_wakes.PartialAxiwake,
"axiwake<n>",
n=lambda x: int(x),
hints={"n": "(Number of evaluation points)"},
)
self.partial_wakes.add_factory(
fm.partial_wakes.PartialGrid,
"grid<n2>",
var2arg={"n2": "n"},
n2=_n2n,
hints={"n2": "(Number of points in square grid)"},
)
self.wake_frames = FDict(
name="wake_frames",
rotor_wd=fm.wake_frames.RotorWD(var_wd=FV.WD),
rotor_wd_farmo=fm.wake_frames.FarmOrder(),
)
self.wake_frames.add_k_factory(
fm.wake_frames.YawedWakes,
"yawed_[wake_k]",
)
self.wake_frames.add_factory(
fm.wake_frames.Streamlines2D,
"streamlines_<step>",
step=lambda x: float(x),
hints={"step": "(Step size in m)"},
)
self.wake_frames.add_factory(
fm.wake_frames.Streamlines2D,
"streamlines_<step>",
step=lambda x: float(x),
hints={"step": "(Step size in m)"},
)
self.wake_frames["timelines"] = fm.wake_frames.Timelines()
self.wake_frames["dyn_wakes"] = fm.wake_frames.DynamicWakes()
self.wake_frames["seq_dyn_wakes"] = fm.wake_frames.SeqDynamicWakes()
def _todt(x):
if x[-1] == "s":
return float(x[:-1]) / 60
elif x[-3:] == "min":
return float(x[:-3])
else:
raise NotImplementedError(f"Cannot translate '{x}' into minutes")
def _tokm(x):
if x[-2:] == "km":
return float(x[:-2])
elif x[-1] == "m":
return float(x[:-1]) / 1e3
else:
raise NotImplementedError(f"Cannot translate '{x}' into km")
self.wake_frames.add_factory(
fm.wake_frames.Timelines,
"timelines_<dt>",
dt=_todt,
var2arg={"dt": "dt_min"},
hints={"dt": "(Time step, e.g '10s', '1min' etc.)"},
)
self.wake_frames.add_factory(
fm.wake_frames.DynamicWakes,
"dyn_wakes_<length>",
length=_tokm,
var2arg={"length": "max_length_km"},
hints={"length": "(Maximal wake length, e.g. '5km' or '5000m')"},
)
self.wake_frames.add_factory(
fm.wake_frames.SeqDynamicWakes,
"seq_dyn_wakes_<dt>",
dt=_todt,
var2arg={"dt": "dt_min"},
hints={"dt": "(Time step, e.g '10s', '1min' etc.)"},
)
self.wake_superpositions = FDict(
name="wake_superpositions",
ws_linear=fm.wake_superpositions.WSLinear(scale_amb=False),
ws_linear_lim=fm.wake_superpositions.WSLinear(
scale_amb=False, lim_low=1e-4
),
ws_linear_amb=fm.wake_superpositions.WSLinear(scale_amb=True),
ws_linear_amb_lim=fm.wake_superpositions.WSLinear(
scale_amb=True, lim_low=1e-4
),
ws_linear_loc=fm.wake_superpositions.WSLinearLocal(),
ws_linear_loc_lim=fm.wake_superpositions.WSLinearLocal(lim_low=1e-4),
ws_quadratic=fm.wake_superpositions.WSQuadratic(scale_amb=False),
ws_quadratic_lim=fm.wake_superpositions.WSQuadratic(
scale_amb=False, lim_low=1e-4
),
ws_quadratic_amb=fm.wake_superpositions.WSQuadratic(scale_amb=True),
ws_quadratic_amb_lim=fm.wake_superpositions.WSQuadratic(
scale_amb=True, lim_low=1e-4
),
ws_quadratic_loc=fm.wake_superpositions.WSQuadraticLocal(),
ws_quadratic_loc_lim=fm.wake_superpositions.WSQuadraticLocal(lim_low=1e-4),
ws_cubic=fm.wake_superpositions.WSPow(pow=3, scale_amb=False),
ws_cubic_amb=fm.wake_superpositions.WSPow(pow=3, scale_amb=True),
ws_cubic_loc=fm.wake_superpositions.WSPowLocal(pow=3),
ws_cubic_loc_lim=fm.wake_superpositions.WSPowLocal(pow=3, lim_low=1e-4),
ws_quartic=fm.wake_superpositions.WSPow(pow=4, scale_amb=False),
ws_quartic_amb=fm.wake_superpositions.WSPow(pow=4, scale_amb=True),
ws_quartic_loc=fm.wake_superpositions.WSPowLocal(pow=4),
ws_quartic_loc_lim=fm.wake_superpositions.WSPowLocal(pow=4, lim_low=1e-4),
ws_max=fm.wake_superpositions.WSMax(scale_amb=False),
ws_max_amb=fm.wake_superpositions.WSMax(scale_amb=True),
ws_max_loc=fm.wake_superpositions.WSMaxLocal(),
ws_max_loc_lim=fm.wake_superpositions.WSMaxLocal(lim_low=1e-4),
ws_product=fm.wake_superpositions.WSProduct(),
ws_product_lim=fm.wake_superpositions.WSProduct(lim_low=1e-4),
ti_linear=fm.wake_superpositions.TILinear(superp_to_amb="quadratic"),
ti_quadratic=fm.wake_superpositions.TIQuadratic(superp_to_amb="quadratic"),
ti_cubic=fm.wake_superpositions.TIPow(pow=3, superp_to_amb="quadratic"),
ti_quartic=fm.wake_superpositions.TIPow(pow=4, superp_to_amb="quadratic"),
ti_max=fm.wake_superpositions.TIMax(superp_to_amb="quadratic"),
)
self.axial_induction = FDict(name="induction_models")
self.axial_induction["Betz"] = fm.axial_induction.BetzAxialInduction()
self.axial_induction["Madsen"] = fm.axial_induction.MadsenAxialInduction()
self.wake_models = FDict(name="wake_models")
self.wake_models.add_k_factory(
fm.wake_models.wind.JensenWake,
"Jensen_<superposition>_[wake_k]",
kwargs=dict(induction="Betz"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2014,
"Bastankhah2014_<superposition>_[wake_k]",
kwargs=dict(sbeta_factor=0.2, induction="Madsen"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2014,
"Bastankhah2014B_<superposition>_[wake_k]",
kwargs=dict(sbeta_factor=0.2, induction="Betz"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2014,
"Bastankhah025_<superposition>_[wake_k]",
kwargs=dict(sbeta_factor=0.25, induction="Madsen"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2014,
"Bastankhah025B_<superposition>_[wake_k]",
kwargs=dict(sbeta_factor=0.25, induction="Betz"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2016,
"Bastankhah2016_<superposition>_[wake_k]",
kwargs=dict(induction="Madsen"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.Bastankhah2016,
"Bastankhah2016B_<superposition>_[wake_k]",
kwargs=dict(induction="Betz"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.TurbOParkWake,
"TurbOPark_<superposition>_[wake_k]",
kwargs=dict(induction="Madsen"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.TurbOParkWake,
"TurbOParkB_<superposition>_[wake_k]",
kwargs=dict(induction="Betz"),
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.wind.TurbOParkWakeIX,
"TurbOParkIX_<superposition>_[wake_k]_dx<dx>",
superposition=lambda s: f"ws_{s}",
dx=lambda x: float(x),
hints={
"superposition": "(Superposition, e.g. linear for ws_linear)",
"dx": "(Integration step in m)",
},
)
self.wake_models.add_k_factory(
fm.wake_models.ti.CrespoHernandezTIWake,
"CrespoHernandez_<superposition>_[wake_k]",
kwargs=dict(use_ambti=False),
superposition=lambda s: f"ti_{s}",
hints={"superposition": "(Superposition, e.g. linear for ti_linear)"},
)
self.wake_models.add_factory(
fm.wake_models.ti.IECTIWake,
"IECTI2019_<superposition>",
kwargs=dict(iec_type="2019"),
superposition=lambda s: f"ti_{s}",
hints={"superposition": "(Superposition, e.g. linear for ti_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.ti.IECTIWake,
"IECTI2019k_<superposition>_[wake_k]",
kwargs=dict(iec_type="2019", opening_angle=None),
superposition=lambda s: f"ti_{s}",
hints={"superposition": "(Superposition, e.g. linear for ti_linear)"},
)
self.wake_models.add_factory(
fm.wake_models.ti.IECTIWake,
"IECTI2005_<superposition>",
kwargs=dict(iec_type="2005"),
superposition=lambda s: f"ti_{s}",
hints={"superposition": "(Superposition, e.g. linear for ti_linear)"},
)
self.wake_models.add_k_factory(
fm.wake_models.ti.IECTIWake,
"IECTI2005k_<superposition>_[wake_k]",
kwargs=dict(iec_type="2005", opening_angle=None),
superposition=lambda s: f"ti_{s}",
hints={"superposition": "(Superposition, e.g. linear for ti_linear)"},
)
self.wake_models[f"RHB"] = fm.wake_models.induction.RankineHalfBody()
self.wake_models[f"VortexSheet"] = fm.wake_models.induction.VortexSheet()
self.wake_models.add_factory(
fm.wake_models.induction.VortexSheet,
"VortexSheet_<superposition>",
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models[f"Rathmann"] = fm.wake_models.induction.Rathmann()
self.wake_models.add_factory(
fm.wake_models.induction.Rathmann,
"Rathmann_<superposition>",
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models[f"SelfSimilar"] = fm.wake_models.induction.SelfSimilar()
self.wake_models[f"SelfSimilar2020"] = (
fm.wake_models.induction.SelfSimilar2020()
)
self.wake_models.add_factory(
fm.wake_models.induction.SelfSimilar,
"SelfSimilar_<superposition>",
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.wake_models.add_factory(
fm.wake_models.induction.SelfSimilar2020,
"SelfSimilar2020_<superposition>",
superposition=lambda s: f"ws_{s}",
hints={"superposition": "(Superposition, e.g. linear for ws_linear)"},
)
self.ground_models = FDict(name="ground_models")
self.ground_models["no_ground"] = fm.ground_models.NoGround()
self.ground_models["ground_mirror"] = fm.ground_models.GroundMirror()
self.ground_models.add_factory(
fm.ground_models.WakeMirror,
"blh_mirror_h<height>",
var2arg={"height": "heights"},
height=lambda h: [0.0, float(h)],
hints={"height": "(Boundary layer wake reflection height)"},
example_vars={"height": 500},
)
self.sources = FDict(
name="sources",
point_models=self.point_models,
rotor_models=self.rotor_models,
turbine_types=self.turbine_types,
turbine_models=self.turbine_models,
farm_models=self.farm_models,
farm_controllers=self.farm_controllers,
partial_wakes=self.partial_wakes,
wake_frames=self.wake_frames,
wake_superpositions=self.wake_superpositions,
wake_models=self.wake_models,
axial_induction=self.axial_induction,
ground_models=self.ground_models,
)
self.base_classes = FDict(
name="base_classes",
point_models=PointDataModel,
rotor_models=RotorModel,
turbine_types=TurbineType,
turbine_models=TurbineModel,
farm_models=FarmDataModel,
farm_controllers=FarmController,
partial_wakes=PartialWakesModel,
wake_frames=WakeFrame,
wake_superpositions=WakeSuperposition,
wake_models=WakeModel,
axial_induction=AxialInductionModel,
ground_models=GroundModel,
)
for s in self.sources.values():
for k, m in s.items():
m.name = k
[docs]
def __getitem__(self, key):
return self.sources.get_item(key)
[docs]
def print_toc(self, subset=None, search=None):
"""
Print the contents.
Parameters
----------
subset: list of str, optional
Selection of model types
search: str, optional
String that has to be part of the model name
"""
for k in sorted(list(self.sources.keys())):
ms = self.sources[k]
if subset is None or k in subset:
print(k)
print("-" * len(k))
if len(ms):
for mname in sorted(list(ms.keys())):
if search is None or search in mname:
print(f"{mname}: {ms[mname]}")
if isinstance(ms, FDict):
for f in ms.factories:
if search is None or search in f.name_template:
print()
print(f)
else:
print("(none)")
print()
[docs]
def get(self, model_type, name, class_name=None, *args, **kwargs):
"""
Gets a model object.
If not found, dynamically creates it (given the class name)
Parameters
----------
model_type: str
The model type
name: str
The model name
class_name: str, optinal
Name of the model class
args: tuple, optional
Arguments for the model class
kwargs: dict, optional
Arguments for the model class
Returns
-------
model: mclass
The model object
"""
if name not in self.sources[model_type]:
if class_name is None:
raise KeyError(
f"Model '{name}' of type '{model_type}' not found in model book. Available: {sorted(list(self.sources[model_type].keys()))}"
)
bclass = self.base_classes[model_type]
self.sources[model_type][name] = bclass.new(class_name, *args, **kwargs)
return self.sources[model_type][name]
[docs]
def default_partial_wakes(self, wake_model):
"""
Gets a default partial wakes model name
for a given wake model
Parameters
----------
wake_model: foxes.core.WakeModel
The wake model
Returns
-------
pwake: str
The partial wake model name
"""
if isinstance(wake_model, TurbineInductionModel):
return "grid9"
elif isinstance(wake_model, fm.wake_models.TopHatWakeModel):
return "top_hat"
elif isinstance(wake_model, fm.wake_models.AxisymmetricWakeModel):
return "axiwake6"
elif isinstance(wake_model, fm.wake_models.DistSlicedWakeModel):
return "grid9"
else:
raise TypeError(
f"No default partial wakes model defined for wake model type '{type(wake_model).__name__}'"
)
[docs]
def finalize(self, algo, verbosity=0):
"""
Finalizes the model.
Parameters
----------
algo: foxes.core.Algorithm
The calculation algorithm
verbosity: int
The verbosity level, 0 = silent
"""
for ms in self.sources.values():
if isinstance(ms, FDict):
for m in ms.values():
if m.initialized:
m.finalize(algo, verbosity)