Newer
Older

Mads M. Pedersen
committed
from abc import abstractmethod
from numpy import newaxis as na

Mads M. Pedersen
committed
from py_wake import np
from py_wake.superposition_models import SuperpositionModel, LinearSum, WeightedSum

Mads M. Pedersen
committed
from py_wake.wind_farm_models.wind_farm_model import WindFarmModel
from py_wake.deflection_models.deflection_model import DeflectionModel
from py_wake.rotor_avg_models.rotor_avg_model import RotorAvgModel, RotorCenter
from py_wake.turbulence_models.turbulence_model import TurbulenceModel
from py_wake.deficit_models.deficit_model import ConvectionDeficitModel, BlockageDeficitModel, WakeDeficitModel
from tqdm import tqdm
from py_wake.wind_turbines._wind_turbines import WindTurbines
from py_wake.utils.model_utils import check_model, fix_shape

Mads M. Pedersen
committed
from py_wake.utils.functions import mean_deg
import warnings

Mads M. Pedersen
committed
from py_wake.input_modifier_models.input_modifier_model import InputModifierModel

Mads M. Pedersen
committed
class EngineeringWindFarmModel(WindFarmModel):
"""
Base class for engineering wake models
General suffixes:
- i: turbines ordered by id
- j: downstream points/turbines
- k: wind speeds
- l: wind directions
Arguments available for calc_deficit (specifiy in args4deficit):
- WS_ilk: Local wind speed without wake effects
- TI_ilk: Local turbulence intensity without wake effects
- TI_std_ilk: Standard deviation of local turbulence intensity

Mads M. Pedersen
committed
- WS_eff_ilk: Local wind speed with wake effects
- TI_eff_ilk: Local turbulence intensity with wake effects

Mads M. Pedersen
committed
- D_src_il: Diameter of source turbine
- D_dst_ijl: Diameter of destination turbine
- dw_ijlk: Downwind distance from turbine i to point/turbine j
- hcw_ijlk: Horizontal cross wind distance from turbine i to point/turbine j
- dh_ijl: vertical distance from turbine i to point/turbine j
- cw_ijlk: Cross wind(horizontal and vertical) distance from turbine i to point/turbine j

Mads M. Pedersen
committed
- ct_ilk: Thrust coefficient
"""
default_grid_resolution = 500
def __init__(self, site, windTurbines: WindTurbines, wake_deficitModel, superpositionModel, rotorAvgModel=None,

Mads M. Pedersen
committed
blockage_deficitModel=None, deflectionModel=None, turbulenceModel=None, inputModifierModels=[]):

Mads M. Pedersen
committed
WindFarmModel.__init__(self, site, windTurbines)

Mads M. Pedersen
committed
if not isinstance(inputModifierModels, (list, tuple)):
inputModifierModels = [inputModifierModels]
for model, cls, name in ([(wake_deficitModel, WakeDeficitModel, 'wake_deficitModel'),
(superpositionModel, SuperpositionModel, 'superpositionModel'),
(blockage_deficitModel, BlockageDeficitModel, 'blockage_deficitModel'),
(deflectionModel, DeflectionModel, 'deflectionModel'),
(turbulenceModel, TurbulenceModel, 'turbulenceModel')] +
[(imm, InputModifierModel, 'inputModificierModels') for imm in inputModifierModels]):
check_model(model, cls, name)
if model is not None:
setattr(model, 'windFarmModel', self)
setattr(self, name, model)

Mads M. Pedersen
committed
self.inputModifierModels = inputModifierModels
if isinstance(superpositionModel, WeightedSum):
assert isinstance(wake_deficitModel, ConvectionDeficitModel)
assert rotorAvgModel is None or isinstance(rotorAvgModel, RotorCenter), \
"WeightedSum only works with RotorCenter"
# TI_eff requires a turbulence model
assert 'TI_eff_ilk' not in wake_deficitModel.args4deficit or turbulenceModel

Mads M. Pedersen
committed
self.wake_deficitModel = wake_deficitModel
if rotorAvgModel is not None:
warnings.warn("""The rotorAvgModel-argument of WindFarmModel is ambiguous and therefore deprecated.
Set the rotorAvgModel of the wake_deficitModel, the blockage_deficitModel and/or turbulenceModel instead.
Until removed, the rotorAvgModel of WindFarmModel will apply the rotorAvgModel to the wake_deficitModel only
if a rotorAvgModel has not already been specified for the wake_deficitModel""",
DeprecationWarning, stacklevel=2)
check_model(rotorAvgModel, RotorAvgModel, 'rotorAvgModel')

Mads M. Pedersen
committed
self.wake_deficitModel.rotorAvgModel = self.wake_deficitModel.rotorAvgModel or rotorAvgModel

Mads M. Pedersen
committed
self.superpositionModel = superpositionModel
self.blockage_deficitModel = blockage_deficitModel
self.deflectionModel = deflectionModel
self.turbulenceModel = turbulenceModel
# wake expansion continuation (wake-width scale factor) see
self.wec = 1

Mads M. Pedersen
committed
# Thomas, J. J. and Ning, A., "A Method for Reducing Multi-Modality in the Wind Farm Layout Optimization Problem,"

Mads M. Pedersen
committed
# Journal of Physics: Conference Series, Vol. 1037, The Science of Making
# Torque from Wind, Milano, Italy, jun 2018, p. 10.
self.deficit_initalized = False
self.args4deficit = self.wake_deficitModel.args4deficit

Mads M. Pedersen
committed
# self.args4deficit = set(self.args4deficit) | {'yaw_ilk'}

Mads M. Pedersen
committed
if self.blockage_deficitModel:
self.args4deficit = set(self.args4deficit) | set(self.blockage_deficitModel.args4deficit)
self.args4all = set(self.args4deficit)
if self.turbulenceModel:
self.args4all |= set(self.turbulenceModel.args4model)
self.args4all |= set(self.deflectionModel.args4deflection)

Mads M. Pedersen
committed
for input_modifier in self.inputModifierModels:
self.args4all |= set(input_modifier.args4model)

Mads M. Pedersen
committed
def __str__(self):
def name(o):
return o.__class__.__name__
models = [self.__class__.__bases__[0].__name__, "%s-wake" % name(self.wake_deficitModel)]

Mads M. Pedersen
committed
if self.blockage_deficitModel:
models.append("%s-blockage" % name(self.blockage_deficitModel))
models.append("%s-superposition" % (name(self.superpositionModel)))
if self.deflectionModel:
models.append("%s-deflection" % name(self.deflectionModel))
if self.turbulenceModel:
models.append("%s-turbulence" % name(self.turbulenceModel))
return "%s(%s)" % (name(self), ", ".join(models))
def _init_deficit(self, **kwargs):
"""Calculate layout dependent wake (and blockage) deficit terms"""
self.wake_deficitModel.calc_layout_terms(**kwargs)

Mads M. Pedersen
committed
self.wake_deficitModel.deficit_initalized = True
if self.blockage_deficitModel:
if self.blockage_deficitModel != self.wake_deficitModel:
self.blockage_deficitModel.calc_layout_terms(**kwargs)

Mads M. Pedersen
committed
self.blockage_deficitModel.deficit_initalized = True
def _reset_deficit(self):
self.wake_deficitModel.deficit_initalized = False
if self.blockage_deficitModel:
self.blockage_deficitModel.deficit_initalized = False
def _add_blockage(self, deficit, dw_ijlk, **kwargs):

Mads M. Pedersen
committed
# the split line between wake and blockage is set slightly upstream to handle

Mads M. Pedersen
committed
# numerical inaccuracy in the trigonometric functions that calculates dw_ijlk

Mads M. Pedersen
committed
if self.blockage_deficitModel is None:

Mads M. Pedersen
committed
deficit *= (dw_ijlk > rotor_pos)
blockage = self.blockage_deficitModel.calc_blockage_deficit(dw_ijlk=dw_ijlk, **kwargs)

Mads M. Pedersen
committed
else:
# Same model for both wake and blockage
# keep blockage in deficit and set blockage to zero
blockage = np.zeros_like(deficit)

Mads M. Pedersen
committed
def _calc_deficit(self, dw_ijlk, **kwargs):
"""Calculate wake (and blockage) deficit"""
deficit = self.wake_deficitModel(dw_ijlk=dw_ijlk, **kwargs)
deficit, blockage = self._add_blockage(deficit, dw_ijlk, **kwargs)

Mads M. Pedersen
committed
return deficit, blockage
def _calc_deficit_convection(self, dw_ijlk, **kwargs):
"""Calculate wake convection deficit (and blockage)"""
deficit, uc, sigma_sqr = self.wake_deficitModel.calc_deficit_convection(dw_ijlk=dw_ijlk, **kwargs)
deficit, blockage = self._add_blockage(deficit, dw_ijlk, **kwargs)
return deficit, uc, sigma_sqr, blockage
def _calc_wt_interaction_args(self, kwargs):
"""Used for parallel execution"""
return self.calc_wt_interaction(**kwargs)

Mads M. Pedersen
committed
def calc_wt_interaction(self, x_ilk, y_ilk, h_i=None, type_i=0, wd=None, ws=None, time=False,
WS_eff_ilk=None,
n_cpu=1, wd_chunks=None, ws_chunks=1,
**kwargs):
"""See WindFarmModel.calc_wt_interaction and additional parameters below
Parameters
----------
n_cpu : int or None, optional
Number of CPUs to be used for execution.
If 1 (default), the execution is not parallized
If None, the available number of CPUs are used
wd_chunks : int or None, optional
If n_cpu>1, the wind directions are divided into <wd_chunks> chunks and executed in parallel.
If wd_chunks is None, wd_chunks is set to the available number of CPUs
ws_chunks : int or None, optional
If n_cpu>1, the wind speeds are divided into <ws_chunks> chunks and executed in parallel.
If ws_chunks is None, ws_chunks is set to 1
"""

Mads M. Pedersen
committed
h_i, D_i = self.windTurbines.get_defaults(len(x_ilk), type_i, h_i)

Mads M. Pedersen
committed
wd, ws = self.site.get_defaults(wd, ws)

Mads M. Pedersen
committed
I, L, K, = len(x_ilk), len(wd), (1, len(ws))[time is False]
kwargs.update(dict(x_ilk=x_ilk, y_ilk=y_ilk, h_ilk=h_i[:, na, na], wd=wd, ws=ws, time=time,
type_i=np.zeros_like(D_i) + type_i))
for inputModifierModel in self.inputModifierModels:
kwargs.update(inputModifierModel.setup(**kwargs))

Mads M. Pedersen
committed
# Find local wind speed, wind direction, turbulence intensity and probability

Mads M. Pedersen
committed
lw = self.site.local_wind(x=np.mean(x_ilk, (1, 2)), y=np.mean(y_ilk, (1, 2)), h=h_i,
wd=kwargs['wd'], ws=kwargs['ws'], time=kwargs['time'])
for k in ['WS_ilk', 'WD_ilk', 'TI_ilk']:
if k in kwargs:

Mads M. Pedersen
committed
lw.add_ilk(k, kwargs.pop(k))

Mads M. Pedersen
committed

Mads M. Pedersen
committed
ri, oi = self.windTurbines.function_inputs

Mads M. Pedersen
committed
if n_cpu != 1 or wd_chunks or ws_chunks > 1:
# parallel execution

Mads M. Pedersen
committed
map_func, arg_lst, wd_chunks, ws_chunks = self._multiprocessing_chunks(
n_cpu=n_cpu, wd_chunks=wd_chunks, ws_chunks=ws_chunks,

Mads M. Pedersen
committed
WS_eff_ilk=WS_eff_ilk, **kwargs)

Mads M. Pedersen
committed
WS_eff_ilk, TI_eff_ilk, power_ilk, ct_ilk, _, kwargs = list(
zip(*map_func(self._calc_wt_interaction_args, arg_lst)))
def concatenate(v_ilk):
v_ilk = [fix_shape(v, WS_eff.shape) for v, WS_eff in zip(v_ilk, WS_eff_ilk)]

Mads M. Pedersen
committed
if kwargs[0]['time'] is False:

Mads M. Pedersen
committed
return np.concatenate([np.concatenate(v_ilk[i::ws_chunks], axis=1)
for i in range(ws_chunks)], axis=2)
else:
return np.concatenate(v_ilk, axis=1)
return ([concatenate(v) for v in [WS_eff_ilk, TI_eff_ilk, power_ilk, ct_ilk]] +

Mads M. Pedersen
committed
[lw,
{'type_i': kwargs[0]['type_i'],
**{k: concatenate([wt_i[k] for wt_i in kwargs]) for k in kwargs[0] if k.endswith('_ilk')}}])
kwargs.update({
'WD_ilk': lw.WD_ilk,
'WS_ilk': lw.WS_ilk,
'TI_ilk': lw.TI_ilk,
'WS_eff_ilk': WS_eff_ilk,
'TI_eff_ilk': lw.TI_ilk + 0., # autograd-friendly copy
'D_i': D_i,
'I': I, 'L': L, 'K': K,
**{k + '_ilk': self.site.interp(self.site.ds[k], lw) for k in ri + oi if k in self.site.ds},
})
self._check_input(kwargs)
# Calculate down-wind and cross-wind distances

Mads M. Pedersen
committed
self.site.distance.setup(kwargs['x_ilk'], kwargs['y_ilk'], kwargs['h_ilk'])
WS_eff_ilk, TI_eff_ilk, ct_ilk, kwargs = self._calc_wt_interaction(**kwargs)
power_ilk = self.windTurbines.power(ws=WS_eff_ilk, **self.get_wt_kwargs(TI_eff_ilk, kwargs))
kwargs.update({'time': time, 'type_i': type_i})
return WS_eff_ilk, TI_eff_ilk, power_ilk, ct_ilk, lw, kwargs

Mads M. Pedersen
committed
@abstractmethod
def _calc_wt_interaction(self, **kwargs):
"""calculate WT interaction"""
wt_d_i = self.windTurbines.diameter(sim_res_data.type)
wd, ws = [np.atleast_1d(sim_res_data[k].values) for k in ['wd', 'ws']]
time = sim_res_data.get('time', False)

Mads M. Pedersen
committed
wt_x_ilk = sim_res_data['x'].ilk()

Mads M. Pedersen
committed

Mads M. Pedersen
committed
lw_j = self.site.local_wind(x=x_j, y=y_j, h=h_j, wd=wd, ws=ws, time=time)
I, J, L, K = [len(x) for x in [wt_x_ilk, x_j, wd, ws]]
def get_ilk(k):
v = sim_res_data[k].ilk()
def wrap(l):
l_ = [l, slice(0, 1)][v.shape[1] == 1]
return v[:, l_]
return wrap

Mads M. Pedersen
committed
map_arg_funcs = {k.replace('CT', 'ct') + '_ilk': get_ilk(k)
for k in sim_res_data if k not in ['wd_bin_size', 'ws_l', 'ws_u']}
map_arg_funcs.update({
'D_src_il': lambda l: wt_d_i[:, na],
'D_dst_ijl': lambda l: np.zeros((I, J, 1)),
'IJLK': lambda l=slice(None), I=I, J=J, L=L, K=K: (I, J, len(np.arange(L)[l]), K)})
return map_arg_funcs, lw_j, wd, WD_il
def _get_flow_l(self, model_kwargs, l, wt_x_ilk, wt_y_ilk, wt_h_ilk, lw_j, wd, WD_ilk):
self.site.distance.setup(wt_x_ilk, wt_y_ilk, wt_h_ilk, (lw_j.x, lw_j.y, lw_j.h))
dw_ijlk, hcw_ijlk, dh_ijlk = self.site.distance(wd_l=wd, WD_ilk=WD_ilk)
WS_jlk = lw_j.WS_ilk[:, [l, slice(0, 1)][lw_j.WS_ilk.shape[1] == 1]]
TI_jlk = lw_j.TI_ilk[:, [l, slice(0, 1)][lw_j.TI_ilk.shape[1] == 1]]
hcw_ijlk = hcw_ijlk / self.wec
if self.deflectionModel:
dw_ijlk, hcw_ijlk, dh_ijlk = self.deflectionModel.calc_deflection(
dw_ijlk=dw_ijlk, hcw_ijlk=hcw_ijlk, dh_ijlk=dh_ijlk,
**model_kwargs)

Mads M. Pedersen
committed
model_kwargs.update({'dw_ijlk': dw_ijlk, 'hcw_ijlk': hcw_ijlk, 'dh_ijlk': dh_ijlk,
'x_ilk': wt_x_ilk, 'y_ilk': wt_y_ilk})
if 'cw_ijlk' in self.args4all:
model_kwargs['cw_ijlk'] = hypot(dh_ijlk, hcw_ijlk)
if 'wake_radius_ijlk' in self.args4all:
model_kwargs['wake_radius_ijlk'] = self.wake_deficitModel.wake_radius(**model_kwargs)
if 'wake_radius_ijl' in self.args4all:
model_kwargs['wake_radius_ijl'] = self.wake_deficitModel.wake_radius(**model_kwargs)[..., 0]
deficit_ijlk, uc_ijlk, sigma_sqr_ijlk, blockage_ijlk = self._calc_deficit_convection(**model_kwargs)
deficit_ijlk, blockage_ijlk = self._calc_deficit(**model_kwargs)
add_turb_ijlk = self.turbulenceModel.calc_added_turbulence(**model_kwargs)
cw_ijlk = hypot(dh_ijlk, hcw_ijlk)
WS_eff_jlk = WS_jlk - self.superpositionModel(WS_jlk, deficit_ijlk, uc_ijlk,
sigma_sqr_ijlk, cw_ijlk, hcw_ijlk, dh_ijlk)
if self.blockage_deficitModel:
blockage_superpositionModel = self.blockage_deficitModel.superpositionModel or LinearSum()
WS_eff_jlk -= blockage_superpositionModel(blockage_ijlk)
else:
WS_eff_jlk = WS_jlk - self.superpositionModel(deficit_ijlk)
if self.blockage_deficitModel:
blockage_superpositionModel = self.blockage_deficitModel.superpositionModel or self.superpositionModel
WS_eff_jlk -= blockage_superpositionModel(blockage_ijlk)
if self.turbulenceModel:
TI_eff_jlk = self.turbulenceModel.calc_effective_TI(TI_jlk, add_turb_ijlk)
else:
TI_eff_jlk = None
return WS_eff_jlk, TI_eff_jlk
def _aep_map(self, x_j, y_j, h_j, type_j, sim_res_data):

Mads M. Pedersen
committed
lw_j, WS_eff_jlk, _ = self._flow_map(x_j, y_j, h_j, sim_res_data)
power_kwargs = {}
if 'type' in (self.windTurbines.powerCtFunction.required_inputs +
self.windTurbines.powerCtFunction.optional_inputs):
power_kwargs['type'] = type_j
power_jlk = self.windTurbines.power(WS_eff_jlk, **power_kwargs)
aep_j = (power_jlk * lw_j.P_ilk).sum((1, 2))
return aep_j * 365 * 24 * 1e-9
def _flow_map(self, x_j, y_j, h_j, sim_res_data):
"""call this function via SimulationResult.flow_map"""
arg_funcs, lw_j, wd, WD_il = self.get_map_args(x_j, y_j, h_j, sim_res_data)
I, J, L, K = arg_funcs['IJLK']()
return (lw_j, np.broadcast_to(lw_j.WS_ilk, (len(x_j), L, K)).astype(float),
np.broadcast_to(lw_j.TI_ilk, (len(x_j), L, K)).astype(float))

Mads M. Pedersen
committed
size_gb = I * J * L * K * 8 / 1024**3
wd_chunks = np.minimum(np.maximum(int(size_gb // 1), 1), L)
wd_i = np.round(np.linspace(0, L, wd_chunks + 1)).astype(int)
l_iter = tqdm([slice(i0, i1) for i0, i1 in zip(wd_i[:-1], wd_i[1:])], disable=L <= 1 or not self.verbose,
desc='Calculate flow map', unit='wd')
wt_x_ilk, wt_y_ilk, wt_h_ilk = [sim_res_data[k].ilk() for k in ['x', 'y', 'h']]
WS_eff_jlk, TI_eff_jlk = zip(*[self._get_flow_l(
{k: arg_funcs[k](l) for k in arg_funcs},
l,
*[(v, v[:, l])[np.shape(v)[1] == L] for v in [wt_x_ilk, wt_y_ilk, wt_h_ilk]],
lw_j, wd[l], WD_il[:, l])
for l in l_iter])
WS_eff_jlk = np.concatenate(WS_eff_jlk, 1)
if self.turbulenceModel:
TI_eff_jlk = np.concatenate(TI_eff_jlk, 1)
else:
TI_eff_jlk = np.zeros_like(WS_eff_jlk) + lw_j.TI_ilk

Mads M. Pedersen
committed
return lw_j, WS_eff_jlk, TI_eff_jlk

Mads M. Pedersen
committed
def _check_input(self, kwargs):
x_ilk, y_ilk, h_ilk = kwargs['x_ilk'], kwargs['y_ilk'], kwargs['h_ilk']
i1, i2, *_ = np.where((cabs(x_ilk[:, na] - x_ilk[na]) +
cabs(y_ilk[:, na] - y_ilk[na]) +
cabs(h_ilk[:, na] - h_ilk[na]) +
np.eye(len(x_ilk))[:, :, na, na]) == 0)

Mads M. Pedersen
committed
if len(i1):
msg = "\n".join(["Turbines %d and %d are at the same position" % (i1[i], i2[i]) for i in range(len(i1))])

Mads M. Pedersen
committed
raise ValueError(msg)

Mads M. Pedersen
committed
for k in self.args4all:
if k.endswith('_ilk') and k not in ['ct_ilk'] and k not in kwargs:
n = k.replace('_ilk', '')
needed_by = str(self)
for model in [self.wake_deficitModel, self.superpositionModel, self.blockage_deficitModel,
self.deflectionModel, self.turbulenceModel] + self.inputModifierModels:
if ((hasattr(model, 'args4model') and k in model.args4model) or
(hasattr(model, 'args4deficit') and k in model.args4deficit)):
needed_by = model.__class__.__name__
break
raise ValueError(f"'{n}' needed by {needed_by} is missing")
ri, oi = self.windTurbines.function_inputs
for k in kwargs:
n = k.replace('_ilk', '').replace('_i', '')
if (n not in ri + oi and k not in self.args4all and
n not in {'x', 'y', 'h', 'wd', 'ws', 'time', 'type', 'D', 'WD', 'WS',
'WS_eff', 'TI', 'TI_eff', 'I', 'L', 'K'}):
raise ValueError(f"WindFarmModel an got unexpected keyword argument: '{n}'")

Mads M. Pedersen
committed
class PropagateDownwind(EngineeringWindFarmModel):
"""Downstream wake deficits calculated and propagated in downstream direction.
Very fast, but ignoring blockage effects
"""
def __init__(self, site, windTurbines, wake_deficitModel,
superpositionModel=LinearSum(),

Mads M. Pedersen
committed
deflectionModel=None, turbulenceModel=None, rotorAvgModel=None,
inputModifierModels=[]):

Mads M. Pedersen
committed
"""Initialize flow model
Parameters
----------
site : Site
Site object
windTurbines : WindTurbines
WindTurbines object representing the wake generating wind turbines
wake_deficitModel : DeficitModel
Model describing the wake(downstream) deficit
rotorAvgModel : RotorAvgModel, optional
Model defining one or more points at the down stream rotors to
calculate the rotor average wind speeds from.\n
if None, default, the wind speed at the rotor center is used

Mads M. Pedersen
committed
superpositionModel : SuperpositionModel
Model defining how deficits sum up
deflectionModel : DeflectionModel
Model describing the deflection of the wake due to yaw misalignment, sheared inflow, etc.
turbulenceModel : TurbulenceModel
Model describing the amount of added turbulence in the wake
"""
EngineeringWindFarmModel.__init__(self, site, windTurbines, wake_deficitModel, superpositionModel, rotorAvgModel,
blockage_deficitModel=None, deflectionModel=deflectionModel,

Mads M. Pedersen
committed
turbulenceModel=turbulenceModel, inputModifierModels=inputModifierModels)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
def _calc_wt_interaction(self, wd,

Mads M. Pedersen
committed
WS_eff_ilk, TI_eff_ilk,

Mads M. Pedersen
committed
D_i,

Mads M. Pedersen
committed
"""
Additional suffixes:
- m: turbines and wind directions (il.flatten())
- n: from_turbines, to_turbines and wind directions (iil.flatten())
"""
uc_nk = []
sigma_sqr_nk = []
cw_nk = []
hcw_nk = []
dh_nk = []

Mads M. Pedersen
committed

Mads M. Pedersen
committed
def ilk2mk(v_ilk):
dtype = (float, np.complex128)[np.iscomplexobj(v_ilk)]
_K = np.shape(v_ilk)[2]
return np.broadcast_to(np.asarray(v_ilk).astype(dtype), (I, L, _K)).reshape((I * L, _K))
WS_ilk, WD_ilk = [kwargs[k + '_ilk'] for k in ['WS', 'WD']]
WS_mk, TI_mk, h_mk = [ilk2mk(kwargs[k + '_ilk']) for k in ['WS', 'TI', 'h']]

Mads M. Pedersen
committed

Mads M. Pedersen
committed
yaw_mk = ilk2mk(kwargs.get('yaw_ilk', [[[0]]]))
tilt_mk = ilk2mk(kwargs.get('tilt_ilk', [[[0]]]))
modified_input_dict_mk = []

Mads M. Pedersen
committed
add_turb_nk = []

Mads M. Pedersen
committed
i_wd_l = np.arange(L).astype(int)
dw_order_indices_ld = self.site.distance.dw_order_indices(wd)[:, 0]

Mads M. Pedersen
committed

Mads M. Pedersen
committed
wt_kwargs = self.get_wt_kwargs(TI_eff_ilk, kwargs)

Mads M. Pedersen
committed
# Iterate over turbines in down wind order
for j in tqdm(range(I), disable=I <= 1 or not self.verbose, desc="Calculate flow interaction", unit="wt"):
i_wt_l = dw_order_indices_ld[:, j]
# current wt (j'th most upstream wts for all wdirs)
m = i_wt_l * L + i_wd_l

Mads M. Pedersen
committed
# Calculate effectiv wind speed at current turbines(all wind directions and wind speeds) and
# look up power and thrust coefficient
if j == 0: # Most upstream turbines (no wake)
WS_eff_lk = WS_mk[m]

Mads M. Pedersen
committed
TI_eff_lk = TI_mk[m]

Mads M. Pedersen
committed
TI_eff_mk.append(np.broadcast_to(TI_eff_lk, (L, K)))

Mads M. Pedersen
committed
else: # 2..n most upstream turbines (wake)
if isinstance(self.superpositionModel, WeightedSum):
deficit2WT = np.array([d_nk2[i] for d_nk2, i in zip(deficit_nk, range(j)[::-1])])
uc2WT = np.array([d_nk2[i] for d_nk2, i in zip(uc_nk, range(j)[::-1])])
sigmasqr2WT = np.array([d_nk2[i] for d_nk2, i in zip(sigma_sqr_nk, range(j)[::-1])])
cw2WT = np.array([d_nk2[i] for d_nk2, i in zip(cw_nk, range(j)[::-1])])
hcw2WT = np.array([d_nk2[i] for d_nk2, i in zip(hcw_nk, range(j)[::-1])])
dh2WT = np.array([d_nk2[i] for d_nk2, i in zip(dh_nk, range(j)[::-1])])
WS_eff_lk = WS_mk[m] - self.superpositionModel(WS_mk[m],
deficit2WT, uc2WT, sigmasqr2WT, cw2WT, hcw2WT, dh2WT)
else:
deficit2WT = np.array([d_nk2[i] for d_nk2, i in zip(deficit_nk, range(j)[::-1])])

Mads M. Pedersen
committed
WS_eff_lk = WS_mk[m] - self.superpositionModel(deficit2WT)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
if self.turbulenceModel:

Mads M. Pedersen
committed
add_turb2WT = np.array([d_nk2[i] for d_nk2, i in zip(add_turb_nk, range(j)[::-1])])
TI_eff_lk = self.turbulenceModel.calc_effective_TI(TI_mk[m], add_turb2WT)
TI_eff_mk.append(TI_eff_lk)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
def mask(k, v):
if len(np.squeeze(v).shape) == 0:
return np.squeeze(v)
v = np.asarray(v)
elif v.shape[0] == I:
return v[i_wt_l].flatten()
else:
assert v.shape[1] == L
return v[0, i_wd_l]

Mads M. Pedersen
committed

Mads M. Pedersen
committed
_wt_kwargs = {k: mask(k, v) for k, v in wt_kwargs.items()}
if 'TI_eff' in _wt_kwargs:
_wt_kwargs['TI_eff'] = TI_eff_mk[-1]

Mads M. Pedersen
committed
ct_lk = self.windTurbines.ct(ws=WS_eff_mk[-1], **_wt_kwargs)

Mads M. Pedersen
committed

Mads M. Pedersen
committed

Mads M. Pedersen
committed
if j < I - 1 or len(self.inputModifierModels):
i_dw = dw_order_indices_ld[:, j + 1:]

Mads M. Pedersen
committed

Mads M. Pedersen
committed
# Calculate required args4deficit parameters
arg_funcs = {'WS_ilk': lambda: WS_mk[m][na],
'WS_jlk': lambda: np.moveaxis([WS_ilk[(slice(0, 1), j)[WS_ilk.shape[0] > 1],
(0, l)[WS_ilk.shape[1] > 1]]
for j, l in zip(i_dw, i_wd_l)], 0, 1),

Mads M. Pedersen
committed
'TI_ilk': lambda: TI_mk[m][na],

Mads M. Pedersen
committed
'D_src_il': lambda: D_i[i_wt_l][na],
'yaw_ilk': lambda: yaw_mk[m][na],
'tilt_ilk': lambda: tilt_mk[m][na],
'D_dst_ijl': lambda: D_i[dw_order_indices_ld[:, j + 1:]].T[na],

Mads M. Pedersen
committed
'h_ilk': lambda: h_mk[m][na],

Mads M. Pedersen
committed
'ct_ilk': lambda: ct_lk[na],

Mads M. Pedersen
committed
'WD_ilk': lambda: ilk2mk(WD_ilk)[m][na],
**{k + '_ilk': lambda k=k: ilk2mk(kwargs[k + '_ilk'])[m][na] for k in 'xyh'},
'type_il': lambda: kwargs['type_i'][i_wt_l][na]

Mads M. Pedersen
committed
}
model_kwargs = {k: arg_funcs[k]() for k in self.args4all if k in arg_funcs}

Mads M. Pedersen
committed
dw_ijlk, hcw_ijlk, dh_ijlk = self.site.distance(wd_l=wd, WD_ilk=WD_ilk, src_idx=i_wt_l, dst_idx=i_dw.T)

Mads M. Pedersen
committed
for inputModidifierModel in self.inputModifierModels:
modified_input_dict = inputModidifierModel(**model_kwargs)
modified_input_dict_mk.append(modified_input_dict)
model_kwargs.update(modified_input_dict)
hcw_ijlk = hcw_ijlk / self.wec

Mads M. Pedersen
committed
if self.deflectionModel:
dw_ijlk, hcw_ijlk, dh_ijlk = self.deflectionModel.calc_deflection(
dw_ijlk=dw_ijlk, hcw_ijlk=hcw_ijlk, dh_ijlk=dh_ijlk, **model_kwargs)

Mads M. Pedersen
committed
model_kwargs.update({'dw_ijlk': dw_ijlk, 'hcw_ijlk': hcw_ijlk, 'dh_ijlk': dh_ijlk})
if 'cw_ijlk' in self.args4all:
# sqrt(a**2+b**2) as hypot does not support complex numbers
model_kwargs['cw_ijlk'] = np.sqrt(dh_ijlk**2 + hcw_ijlk**2)
cw_nk.append(model_kwargs['cw_ijlk'][0])
if 'wake_radius_ijl' in self.args4all:
model_kwargs['wake_radius_ijl'] = self.wake_deficitModel.wake_radius(**model_kwargs)[..., 0]
if 'wake_radius_ijlk' in self.args4all:
model_kwargs['wake_radius_ijlk'] = self.wake_deficitModel.wake_radius(**model_kwargs)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
# Calculate deficit
if isinstance(self.superpositionModel, WeightedSum):
deficit, uc, sigma_sqr, _ = self._calc_deficit_convection(**model_kwargs)
uc_nk.append(uc[0])
sigma_sqr_nk.append(sigma_sqr[0])
else:
deficit, _ = self._calc_deficit(**model_kwargs)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
if self.turbulenceModel:

Mads M. Pedersen
committed

Mads M. Pedersen
committed
# Calculate added turbulence
add_turb_nk.append(self.turbulenceModel(**model_kwargs)[0])

Mads M. Pedersen
committed
WS_eff_jlk, ct_jlk = np.array(WS_eff_mk), np.array(ct_jlk)
dw_inv_indices = (np.argsort(dw_order_indices_ld, 1).T * L + np.arange(L).astype(int)[na]).flatten()
WS_eff_ilk = WS_eff_jlk.reshape((I * L, K))[dw_inv_indices].reshape((I, L, K))
ct_ilk = ct_jlk.reshape((I * L, K))[dw_inv_indices].reshape((I, L, K))

Mads M. Pedersen
committed
if self.turbulenceModel:

Mads M. Pedersen
committed
TI_eff_jlk = np.array(TI_eff_mk)
TI_eff_ilk = TI_eff_jlk.reshape((I * L, K))[dw_inv_indices].reshape((I, L, K))

Mads M. Pedersen
committed

Mads M. Pedersen
committed
if len(self.inputModifierModels):
for k in modified_input_dict_mk[0].keys():
mi_jlk = np.array([mi_dict[k] for mi_dict in modified_input_dict_mk])
kwargs[k] = mi_jlk.reshape((I * L, K))[dw_inv_indices].reshape((I, L, K))
return WS_eff_ilk, TI_eff_ilk, ct_ilk, kwargs

Mads M. Pedersen
committed
class All2AllIterative(EngineeringWindFarmModel):
"""Wake and blockage deficits calculated from all wt to all points of interest (wt/map points).
The calculations are iteratively repeated until convergence (change of effective wind speed < convergence_tolerance)"""
def __init__(self, site, windTurbines, wake_deficitModel,
blockage_deficitModel=None, deflectionModel=None, turbulenceModel=None,

Mads M. Pedersen
committed
convergence_tolerance=1e-6, rotorAvgModel=None, inputModifierModels=[]):

Mads M. Pedersen
committed
"""Initialize flow model
Parameters
----------
site : Site
Site object
windTurbines : WindTurbines
WindTurbines object representing the wake generating wind turbines
wake_deficitModel : DeficitModel
Model describing the wake(downstream) deficit
rotorAvgModel : RotorAvgModel, optional
Model defining one or more points at the down stream rotors to
calculate the rotor average wind speeds from.\n
if None, default, the wind speed at the rotor center is used

Mads M. Pedersen
committed
superpositionModel : SuperpositionModel
Model defining how deficits sum up
blockage_deficitModel : DeficitModel
Model describing the blockage(upstream) deficit
deflectionModel : DeflectionModel
Model describing the deflection of the wake due to yaw misalignment, sheared inflow, etc.
turbulenceModel : TurbulenceModel
Model describing the amount of added turbulence in the wake
convergence_tolerance : float or None
if float: maximum accepted change in WS_eff_ilk [m/s]
if None: return after first iteration. This only makes sense for benchmark studies where CT,
wakes and blockage are independent of effective wind speed WS_eff_ilk

Mads M. Pedersen
committed
"""
EngineeringWindFarmModel.__init__(self, site, windTurbines, wake_deficitModel, superpositionModel, rotorAvgModel,
blockage_deficitModel=blockage_deficitModel, deflectionModel=deflectionModel,

Mads M. Pedersen
committed
turbulenceModel=turbulenceModel, inputModifierModels=inputModifierModels)

Mads M. Pedersen
committed
self.convergence_tolerance = convergence_tolerance
def _calc_wt_interaction(self, ws, wd, WD_ilk, WS_ilk, TI_ilk,

Mads M. Pedersen
committed
WS_eff_ilk, TI_eff_ilk,

Mads M. Pedersen
committed
D_i, time,

Mads M. Pedersen
committed
if any([np.iscomplexobj(v) for v in ([kwargs.get(k, 0)
for k in ['x_ilk', 'y_ilk', 'h_ilk', 'D_i', 'yaw_ilk', 'tilt_ilk']] +
[ws, wd])]):

Mads M. Pedersen
committed
dtype = np.complex128
else:
dtype = float
WS_ILK = np.broadcast_to(WS_ilk, (I, L, K))
# calculate WS_eff without blockage as a first guess
if WS_eff_ilk is None:
# Initialize with PropagateDownwind

Mads M. Pedersen
committed
blockage_deficitModel = self.blockage_deficitModel
self.blockage_deficitModel = None
WS_eff_ilk = PropagateDownwind._calc_wt_interaction(

Mads M. Pedersen
committed
self, wd=wd, WD_ilk=WD_ilk, WS_ilk=WS_ilk, TI_ilk=TI_ilk, WS_eff_ilk=WS_eff_ilk, TI_eff_ilk=TI_eff_ilk,
D_i=D_i, I=I, L=L, K=K, **kwargs)[0]

Mads M. Pedersen
committed
self.blockage_deficitModel = blockage_deficitModel

Mads M. Pedersen
committed
elif WS_eff_ilk == 0:
WS_eff_ilk = WS_ILK + 0.

Mads M. Pedersen
committed
WS_eff_ilk = WS_eff_ilk.astype(dtype)

Mads M. Pedersen
committed
WS_eff_ilk_last = WS_eff_ilk + 0 # fast autograd-friendly copy
diff_lk = np.zeros((L, K))

Mads M. Pedersen
committed
diff_lk_last = None
dw_iilk, hcw_iilk, dh_iilk = self.site.distance(wd_l=wd, WD_ilk=WD_ilk)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
wt_kwargs = self.get_wt_kwargs(TI_eff_ilk, kwargs)
ct_ilk = self.windTurbines.ct(ws=WS_ILK, **wt_kwargs)
ct_ilk_idle = self.windTurbines.ct(ws=0.1 * np.ones_like(WS_ILK), **wt_kwargs)
unstable_lk = np.zeros((L, K), dtype=bool)
ioff = np.broadcast_to(ct_ilk, (I, L, K)) < -1 # index of off/idling turbines

Mads M. Pedersen
committed
D_src_il = D_i[:, na]
model_kwargs = {'WS_ilk': WS_ilk,
'WS_eff_ilk': WS_eff_ilk,

Mads M. Pedersen
committed
'WD_ilk': WD_ilk,
'TI_ilk': TI_ilk,

Mads M. Pedersen
committed
'TI_eff_ilk': TI_eff_ilk,
'D_src_il': D_src_il,
'D_dst_ijl': D_src_il[na],
'dw_ijlk': dw_iilk,
'hcw_ijlk': hcw_iilk,
'cw_ijlk': np.sqrt(hcw_iilk**2 + dh_iilk**2),
'dh_ijlk': dh_iilk,
'IJLK': (I, I, L, K),

Mads M. Pedersen
committed
'type_il': kwargs['type_i'][:, na],
** kwargs,
}
if 'wake_radius_ijl' in self.args4all:
model_kwargs['wake_radius_ijl'] = self.wake_deficitModel.wake_radius(**model_kwargs)[:, :, :, 0]

Mads M. Pedersen
committed
if not self.deflectionModel:
self._init_deficit(**model_kwargs)

Mads M. Pedersen
committed
cw_iilk = np.sqrt(hcw_iilk**2 + dh_iilk**2)

Mads M. Pedersen
committed
# Iterate until convergence
for j in tqdm(range(I), disable=I <= 1 or not self.verbose,
desc="Calculate flow interaction", unit="Iteration"):

Mads M. Pedersen
committed

Mads M. Pedersen
committed
ct_ilk = self.windTurbines.ct(np.maximum(WS_eff_ilk, 0), **wt_kwargs)
ioff |= (unstable_lk)[na] & (ct_ilk <= ct_ilk_idle)

Mads M. Pedersen
committed
model_kwargs.update(dict(
ct_ilk=ct_ilk,
WS_eff_ilk=WS_eff_ilk,
# x_ilk, y_ilk and h_ilk is may be updated by an inputModifierModel and
# must be reset in every iterations
x_ilk=kwargs['x_ilk'],
y_ilk=kwargs['y_ilk'],
h_ilk=kwargs['h_ilk'],
# dw_ijlk, hcw_ijlk and dh_ijlk is updated by deflection model and must be reset in every iterations
dw_ijlk=dw_iilk,
hcw_ijlk=hcw_iilk,
cw_ijlk=cw_iilk,
dh_ijlk=dh_iilk))
for inputModidifierModel in self.inputModifierModels:
modified_input_dict = inputModidifierModel(**model_kwargs)
model_kwargs.update(modified_input_dict)
if any([k in modified_input_dict for k in ['x_ilk', 'y_ilk']]):
self.site.distance.setup(model_kwargs['x_ilk'], model_kwargs['y_ilk'], model_kwargs['h_ilk'])
model_kwargs.update({k: v for k, v in zip(['dw_ijlk', 'hcw_ijlk', 'dh_ijlk'],
self.site.distance(wd_l=wd, WD_ilk=WD_ilk))})
model_kwargs['cw_ijlk'] = hypot(model_kwargs['dh_ijlk'], model_kwargs['hcw_ijlk'])
if not self.deflectionModel:
self._init_deficit(**model_kwargs)

Mads M. Pedersen
committed
if self.deflectionModel:
dw_ijlk, hcw_ijlk, dh_ijlk = self.deflectionModel.calc_deflection(**model_kwargs)
model_kwargs.update({'dw_ijlk': dw_ijlk, 'hcw_ijlk': hcw_ijlk, 'dh_ijlk': dh_ijlk,

Mads M. Pedersen
committed
'cw_ijlk': hypot(dh_ijlk, hcw_ijlk)})

Mads M. Pedersen
committed
self._reset_deficit()
if 'wake_radius_ijlk' in self.args4all:
model_kwargs['wake_radius_ijlk'] = self.wake_deficitModel.wake_radius(**model_kwargs)

Mads M. Pedersen
committed

Mads M. Pedersen
committed
if self.turbulenceModel:
model_kwargs['TI_eff_ilk'] = TI_eff_ilk

Mads M. Pedersen
committed
# Calculate deficit
deficit_iilk, uc_iilk, sigmasqr_iilk, blockage_iilk = self._calc_deficit_convection(**model_kwargs)
deficit_iilk, blockage_iilk = self._calc_deficit(**model_kwargs)

Mads M. Pedersen
committed
# Calculate effective wind speed
WS_eff_ilk = WS_ilk - self.superpositionModel(WS_ilk, deficit_iilk,
uc_iilk, sigmasqr_iilk,
model_kwargs['cw_ijlk'],
model_kwargs['hcw_ijlk'],
dh_iilk)

Mads M. Pedersen
committed
if self.blockage_deficitModel:
WS_eff_ilk -= (self.blockage_deficitModel.superpositionModel or LinearSum())(blockage_iilk)
WS_eff_ilk = WS_ilk.astype(dtype) - self.superpositionModel(deficit_iilk)

Mads M. Pedersen
committed
if self.blockage_deficitModel:
WS_eff_ilk -= (self.blockage_deficitModel.superpositionModel or self.superpositionModel)(blockage_iilk)
# ensure idling wt in unstable flow cases do not cutin even if ws increases due to speedup
# this helps to converge

Mads M. Pedersen
committed
# WS_eff_ilk[ioff] = np.minimum(WS_eff_ilk[ioff], WS_eff_ilk_last[ioff])
WS_eff_ilk = np.minimum(WS_eff_ilk, WS_eff_ilk_last, out=WS_eff_ilk, where=ioff)

Mads M. Pedersen
committed
if self.turbulenceModel:
add_turb_ijlk = self.turbulenceModel(**model_kwargs)
TI_eff_ilk = self.turbulenceModel.calc_effective_TI(TI_ilk, add_turb_ijlk)

Mads M. Pedersen
committed
# Check if converged

Mads M. Pedersen
committed
diff_ilk = cabs(WS_eff_ilk_last - WS_eff_ilk)

Mads M. Pedersen
committed
diff_lk = diff_ilk.mean(0)
max_diff = np.max(diff_ilk.max(0))
if (self.convergence_tolerance is None or
(self.convergence_tolerance and max_diff < self.convergence_tolerance)):

Mads M. Pedersen
committed
break
# i_, l_, k_ = list(zip(*np.where(diff_ilk == max_diff)))[0]

Mads M. Pedersen
committed
# wsi, wsl, wsk = WS_ilk.shape
# print("Iteration: %d, max diff_ilk: %.8f, WT: %d, WD: %d, WS: %f, WS_eff: %f" %

Mads M. Pedersen
committed
# (j, max_diff, i_, wd[l_],
# WS_ilk[min(i_, wsi - 1), min(l_, wsl - 1), min(k_, wsk - 1)],
# WS_eff_ilk[i_, l_, k_]))
# print(j, diff_ilk.mean(0), WS_eff_ilk.squeeze())

Mads M. Pedersen
committed
# assume flow case to be unstable if mean difference of two iterations increases

Mads M. Pedersen
committed
unstable_lk |= diff_lk_last < diff_lk

Mads M. Pedersen
committed
WS_eff_ilk_last = WS_eff_ilk + 0 # fast autograd-friendly copy
diff_lk_last = diff_lk

Mads M. Pedersen
committed
# print("All2AllIterative converge after %d iterations" % (j + 1))
self.iterations = j + 1
self.WS_eff_ilk_last = getattr(WS_eff_ilk, '_value', WS_eff_ilk)

Mads M. Pedersen
committed
self._reset_deficit()

Mads M. Pedersen
committed
if len(self.inputModifierModels):
kwargs.update({k: modified_input_dict[k] for k in modified_input_dict})
return WS_eff_ilk, np.broadcast_to(TI_eff_ilk, (I, L, K)), ct_ilk, kwargs

Mads M. Pedersen
committed
def __init__(self, site, windTurbines, wake_deficitModel,
superpositionModel=LinearSum(),
blockage_deficitModel=None, deflectionModel=None, turbulenceModel=None,
rotorAvgModel=None):
All2AllIterative.__init__(self, site, windTurbines, wake_deficitModel, superpositionModel=superpositionModel,
blockage_deficitModel=blockage_deficitModel, deflectionModel=deflectionModel,
turbulenceModel=turbulenceModel, convergence_tolerance=None,
rotorAvgModel=rotorAvgModel)

Mads M. Pedersen
committed
def _calc_wt_interaction(self, WS_eff_ilk, **kwargs):
return All2AllIterative._calc_wt_interaction(self, WS_eff_ilk=0, **kwargs)

Mads M. Pedersen
committed
def main():
if __name__ == '__main__':
from py_wake.examples.data.iea37 import IEA37Site, IEA37_WindTurbines
from py_wake.deficit_models.selfsimilarity import SelfSimilarityDeficit
from py_wake.deficit_models.gaussian import ZongGaussianDeficit
from py_wake.turbulence_models.stf import STF2017TurbulenceModel
from py_wake.flow_map import XYGrid

Mads M. Pedersen
committed
import matplotlib.pyplot as plt
site = IEA37Site(16)
x, y = site.initial_position.T
windTurbines = IEA37_WindTurbines()
from py_wake.deficit_models.noj import NOJDeficit
from py_wake.superposition_models import SquaredSum
# NOJ wake model
noj = PropagateDownwind(site, windTurbines, wake_deficitModel=NOJDeficit(), superpositionModel=SquaredSum())
# NOJ wake and selfsimilarity blockage
noj_ss = All2AllIterative(site, windTurbines, wake_deficitModel=NOJDeficit(), superpositionModel=SquaredSum(),
blockage_deficitModel=SelfSimilarityDeficit())
# Zong convection superposition
zongp_ss = PropagateDownwind(site, windTurbines, wake_deficitModel=ZongGaussianDeficit(), superpositionModel=WeightedSum(),
turbulenceModel=STF2017TurbulenceModel())
# Zong convection superposition
zong_ss = All2AllIterative(site, windTurbines, wake_deficitModel=ZongGaussianDeficit(), superpositionModel=WeightedSum(),
blockage_deficitModel=SelfSimilarityDeficit(), turbulenceModel=STF2017TurbulenceModel())
for wm in [noj, noj_ss, zongp_ss, zong_ss]:
sim = wm(x=x, y=y, wd=[30], ws=[9])

Mads M. Pedersen
committed
plt.figure()
sim.flow_map(XYGrid(resolution=200)).plot_wake_map()
plt.title(' AEP: %.3f GWh' % sim.aep().sum())

Mads M. Pedersen
committed
plt.show()
main()