Newer
Older

Mads M. Pedersen
committed
from py_wake import np
from py_wake.deficit_models.deficit_model import WakeDeficitModel, BlockageDeficitModel

Mads M. Pedersen
committed
from py_wake.tests.test_files import tfp
from py_wake.utils.fuga_utils import FugaUtils, LUTInterpolator
from py_wake.wind_farm_models.engineering_models import PropagateDownwind, All2AllIterative

Mads M. Pedersen
committed
from scipy.interpolate import RectBivariateSpline
from py_wake.utils import fuga_utils

Mads M. Pedersen
committed
from py_wake.utils.gradients import cabs
from py_wake.utils.grid_interpolator import GridInterpolator
class FugaDeficit(WakeDeficitModel, BlockageDeficitModel, FugaUtils):
def __init__(self, LUT_path=tfp + 'fuga/2MW/Z0=0.03000000Zi=00401Zeta0=0.00E+00.nc', remove_wriggles=False,
method='linear', rotorAvgModel=None, groundModel=None):
"""
Parameters
----------
LUT_path : str
Path to folder containing 'CaseData.bin', input parameter file (*.par) and loop-up tables
remove_wriggles : bool
The current Fuga loop-up tables have significan wriggles.
If True, all deficit values after the first zero crossing (when going from the center line
and out in the lateral direction) is set to zero.
This means that all speed-up regions are also removed
"""
BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)
FugaUtils.__init__(self, LUT_path, on_mismatch='input_par')
self.remove_wriggles = remove_wriggles
x, y, z, du = self.load()
err_msg = "Method must be 'linear' or 'spline'. Spline is supports only height level only"
assert method == 'linear' or (method == 'spline' and len(z) == 1), err_msg
if method == 'linear':
self.lut_interpolator = LUTInterpolator(x, y, z, du)
else:
du_interpolator = RectBivariateSpline(x, y, du[0].T)
def interp(xyz):
x, y, z = xyz
assert np.all(z == self.z[0]), f'LUT table contains z={self.z} only'
return du_interpolator.ev(x, y)
self.lut_interpolator = interp
du = self.init_lut(self.load_luts(['UL'])[0], self.zHub, smooth2zero_x=250, smooth2zero_y=50,
remove_wriggles=self.remove_wriggles)
return self.x, self.y, self.z, du
# self.grid_interplator(np.array([zyx.flatten() for zyx in [z, y, x]]).T, check_bounds=False).reshape(x.shape)

Mads M. Pedersen
committed
return self.lut_interpolator((x, y, z))
def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, h_il, dh_ijlk, D_src_il, **_):

Mads M. Pedersen
committed
self.mdu_ijlk = self.interpolate(dw_ijlk, cabs(hcw_ijlk), (h_il[:, na, :, na] + dh_ijlk)) * \

Mads M. Pedersen
committed
~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na]) # avoid wake on itself
)
def calc_deficit(self, WS_ilk, WS_eff_ilk, dw_ijlk, hcw_ijlk, dh_ijlk, h_il, ct_ilk, D_src_il, **kwargs):
if not self.deficit_initalized:
self._calc_layout_terms(dw_ijlk, hcw_ijlk, h_il, dh_ijlk, D_src_il, **kwargs)

Mads M. Pedersen
committed
return self.mdu_ijlk * (ct_ilk * WS_eff_ilk**2 / WS_ilk)[:, na]

Mads M. Pedersen
committed
# Set at twice the source radius for now
return np.zeros_like(dw_ijlk) + D_src_il[:, na, :, na]

Mads M. Pedersen
committed
def __init__(self, LUT_path=tfp + 'fuga/2MW/Z0=0.00408599Zi=00400Zeta0=0.00E+00.nc',
remove_wriggles=False, method='linear', rotorAvgModel=None, groundModel=None):
"""
Parameters
----------
LUT_path : str
Path to folder containing 'CaseData.bin', input parameter file (*.par) and loop-up tables
remove_wriggles : bool
The current Fuga loop-up tables have significan wriggles.
If True, all deficit values after the first zero crossing (when going from the center line
and out in the lateral direction) is set to zero.
This means that all speed-up regions are also removed
"""
BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)
FugaUtils.__init__(self, LUT_path, on_mismatch='input_par')
self.remove_wriggles = remove_wriggles
x, y, z, dUL = self.load()
mdUT = self.load_luts(['UT'])[0]
dUT = np.array(mdUT, dtype=np.float32) * self.zeta0_factor(self.zHub)
dU = np.concatenate([dUL[:, :, :, na], dUT[:, :, :, na]], 3)
err_msg = "Method must be 'linear' or 'spline'. Spline is supports only height level only"
assert method == 'linear' or (method == 'spline' and len(z) == 1), err_msg
if method == 'linear':
self.lut_interpolator = LUTInterpolator(x, y, z, dU)
else:
UL_interpolator = RectBivariateSpline(x, y, dU[0, :, :, 0].T)
UT_interpolator = RectBivariateSpline(x, y, dU[0, :, :, 1].T)
def interp(xyz):
x, y, z = xyz
assert np.all(z == self.z[0]), f'LUT table contains z={self.z} only'
return np.moveaxis([UL_interpolator.ev(x, y), UT_interpolator.ev(x, y)], 0, -1)
self.lut_interpolator = interp
def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, h_il, dh_ijlk, D_src_il, **_):

Mads M. Pedersen
committed
self.mdu_ijlk = (self.interpolate(dw_ijlk, cabs(hcw_ijlk), (h_il[:, na, :, na] + dh_ijlk)) *
~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na]))[..., na] # avoid wake on itself
)
def calc_deficit_downwind(self, WS_ilk, WS_eff_ilk, dw_ijlk, hcw_ijlk,
dh_ijlk, h_il, ct_ilk, D_src_il, yaw_ilk, **_):

Mads M. Pedersen
committed
dw_ijlk, cabs(hcw_ijlk), (h_il[:, na, :, na] + dh_ijlk)), -1, 0)
mdUT_ijlk = np.negative(mdUT_ijlk, out=mdUT_ijlk, where=hcw_ijlk < 0) # UT is antisymmetric

Mads M. Pedersen
committed
mdu_ijlk = (mdUL_ijlk * np.cos(theta_ilk)[:, na] - mdUT_ijlk * np.sin(theta_ilk)[:, na])
# avoid wake on itself
mdu_ijlk *= ~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na]))
return mdu_ijlk * (ct_ilk * WS_eff_ilk**2 / WS_ilk)[:, na]
def calc_deficit(self, **kwargs):
# fuga result is already downwind
return self.calc_deficit_downwind(**kwargs)

Mads M. Pedersen
committed
class Fuga(PropagateDownwind):
def __init__(self, LUT_path, site, windTurbines,
rotorAvgModel=None, deflectionModel=None, turbulenceModel=None, remove_wriggles=False):

Mads M. Pedersen
committed
"""
Parameters
----------
LUT_path : str
path to look up tables
site : Site
Site object
windTurbines : WindTurbines
WindTurbines object representing the wake generating wind turbines
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
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
"""
PropagateDownwind.__init__(self, site, windTurbines,
wake_deficitModel=FugaDeficit(LUT_path, remove_wriggles=remove_wriggles),
rotorAvgModel=rotorAvgModel, superpositionModel=LinearSum(),

Mads M. Pedersen
committed
deflectionModel=deflectionModel, turbulenceModel=turbulenceModel)
class FugaBlockage(All2AllIterative):
def __init__(self, LUT_path, site, windTurbines, rotorAvgModel=None,
deflectionModel=None, turbulenceModel=None, convergence_tolerance=1e-6, remove_wriggles=False):

Mads M. Pedersen
committed
"""
Parameters
----------
LUT_path : str
path to look up tables
site : Site
Site object
windTurbines : WindTurbines
WindTurbines object representing the wake generating wind turbines
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
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
"""
fuga_deficit = FugaDeficit(LUT_path, remove_wriggles=remove_wriggles)

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

Mads M. Pedersen
committed
class FugaMultiLUTDeficit(FugaDeficit):
def __init__(self, LUT_path_lst=tfp + 'fuga/*.nc', remove_wriggles=False,
method='linear', rotorAvgModel=None, groundModel=None):
BlockageDeficitModel.__init__(self, upstream_only=True, rotorAvgModel=rotorAvgModel, groundModel=groundModel)
import glob
def open_dataset(f):
ds = xr.open_dataset(f).transpose('z', 'y', 'x')
ds['TI'] = fuga_utils.ti(ds.z0, ds.hubheight)
return ds
if isinstance(LUT_path_lst, str):
self.ds_lst = [open_dataset(f) for f in glob.glob(LUT_path_lst)]
else:
self.ds_lst = [open_dataset(f) for f in LUT_path_lst]
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
x_lst, y_lst, z_lst = [self.ds_lst[0][k].values for k in 'xyz']
assert np.all([np.all(ds.x == self.ds_lst[0].x) for ds in self.ds_lst])
assert np.all([np.all(ds.y == self.ds_lst[0].y) for ds in self.ds_lst])
assert np.all([np.all(ds.z == self.ds_lst[0].z) for ds in self.ds_lst])
assert np.all([np.all(ds.z0 == self.ds_lst[0].z0) for ds in self.ds_lst])
assert np.all([np.all(ds.zeta0 == self.ds_lst[0].zeta0) for ds in self.ds_lst])
self.z0 = self.ds_lst[0].z0.item()
self.zeta0 = self.ds_lst[0].zeta0.item()
data = np.concatenate([self.init_lut(ds.UL.values, ds.hubheight.item(), remove_wriggles=remove_wriggles)[na]
for ds in self.ds_lst], 0)
i_lst = np.arange(len(self.ds_lst))
self.interpolator = GridInterpolator([i_lst, z_lst, y_lst, x_lst], data,
method=['nearest', 'linear', 'linear', 'linear'])
def list_indexer(lst):
return lambda x, lst=lst: np.searchsorted(lst, np.minimum(x, lst[-1]))
d_lst = np.sort(np.unique([ds.diameter.item() for ds in self.ds_lst]))
h_lst = np.sort(np.unique([ds.hubheight.item() for ds in self.ds_lst]))
# ti_lst = np.sort(np.unique([ds.TI.item() for ds in self.ds_lst]))
d_index, h_index = [list_indexer(lst) for lst in [d_lst, h_lst]]
# ti_searcher =
index_arr = np.full((len(d_lst), len(h_lst)), -1)
for i, ds in enumerate(self.ds_lst):
index_arr[d_index(ds.diameter.item()), h_index(ds.hubheight.item())] = i
self.index_arr = index_arr
self.d_index, self.h_index = d_index, h_index
def _calc_layout_terms(self, dw_ijlk, hcw_ijlk, h_il, dh_ijlk, D_src_il, **kwargs):
i_il = self.index_arr[self.d_index(D_src_il), self.h_index(h_il)]
i_ijlk = np.broadcast_to(i_il[:, na, :, na], dw_ijlk.shape)
xp = np.array([i_ijlk, h_il[:, na, :, na] + dh_ijlk, cabs(hcw_ijlk), dw_ijlk])
self.mdu_ijlk = self.interpolator(xp.reshape((4, -1)).T, bounds='limit').reshape(dw_ijlk.shape)
self.mdu_ijlk *= ~((dw_ijlk == 0) & (hcw_ijlk <= D_src_il[:, na, :, na])) # avoid wake on itself
if __name__ == '__main__':
from py_wake.examples.data.iea37._iea37 import IEA37Site
from py_wake.examples.data.iea37._iea37 import IEA37_WindTurbines

Mads M. Pedersen
committed
import matplotlib.pyplot as plt

Mads M. Pedersen
committed
# setup site, turbines and wind farm model
site = IEA37Site(16)
x, y = site.initial_position.T
windTurbines = IEA37_WindTurbines()
path = tfp + 'fuga/2MW/Z0=0.03000000Zi=00401Zeta0=0.00E+00.nc'

Mads M. Pedersen
committed
for wf_model in [Fuga(path, site, windTurbines),
FugaBlockage(path, site, windTurbines)]:
plt.figure()
print(wf_model)

Mads M. Pedersen
committed
# run wind farm simulation
sim_res = wf_model(x, y)
# calculate AEP

Mads M. Pedersen
committed
# plot wake map
flow_map = sim_res.flow_map(wd=30, ws=9.8)
flow_map.plot_wake_map()
flow_map.plot_windturbines()
plt.title('AEP: %.2f GWh' % aep)
plt.show()
main()