-
Mads M. Pedersen authoredMads M. Pedersen authored
wasp_grid_site.py 19.36 KiB
from py_wake.site._site import UniformWeibullSite
import numpy as np
import xarray as xr
import pickle
import os
import glob
from _collections import defaultdict
import re
from scipy.interpolate.interpolate import RegularGridInterpolator
import copy
from numpy import newaxis as na
from py_wake.site.distance import StraightDistance, TerrainFollowingDistance
from builtins import AttributeError
def WaspGridSite(ds, distance=TerrainFollowingDistance()):
if distance.__class__ == StraightDistance:
cls = type("WaspGridSiteStraightDistance", (WaspGridSiteBase,), {})
wgs = cls(ds=ds)
else:
cls = type("WaspGridSite" + type(distance).__name__, (type(distance), WaspGridSiteBase), {})
wgs = cls(ds=ds)
wgs.__dict__.update(distance.__dict__)
return wgs
class WaspGridSiteBase(UniformWeibullSite):
def __init__(self, ds):
self._ds = ds
self.interp_funcs = None
self.interp_funcs_initialization()
self.elevation_interpolator = EqDistRegGrid2DInterpolator(self._ds.coords['x'].data,
self._ds.coords['y'].data,
self._ds['elev'].data)
self.TI_data_exist = 'tke' in self.interp_funcs.keys()
super().__init__(p_wd=np.nanmean(self._ds['f'].data, (0, 1, 2)),
a=np.nanmean(self._ds['A'].data, (0, 1, 2)),
k=np.nanmean(self._ds['k'].data, (0, 1, 2)),
ti=0)
def local_wind(self, x_i, y_i, h_i, wd=None, ws=None, wd_bin_size=None, ws_bin_size=None):
if wd is None:
wd = self.default_wd
if ws is None:
ws = self.default_ws
wd, ws = np.asarray(wd), np.asarray(ws)
self.wd = wd
h_i = np.asarray(h_i)
x_il, y_il, h_il = [np.repeat([v], len(wd), 0).T for v in [x_i, y_i, h_i]]
ws_bin_size = self.ws_bin_size(ws, ws_bin_size)
wd_bin_size = self.wd_bin_size(wd, wd_bin_size)
wd_il = np.repeat([wd], len(x_i), 0)
speed_up_il, turning_il = \
[self.interp_funcs[n]((x_il, y_il, h_il, wd_il)) for n in
['spd', 'orog_trn']]
WS_ilk = ws[na, na, :] * speed_up_il[:, :, na]
WD_ilk = (wd_il + turning_il)[..., na]
if self.TI_data_exist:
TI_il = self.interp_funcs['tke']((x_il, y_il, h_il, wd_il))
TI_ilk = (TI_il[:, :, na] * (0.75 + 3.8 / WS_ilk))
WD_lk, WS_lk = np.meshgrid(wd, ws, indexing='ij')
P_ilk = self.probability(x_i, y_i, h_i, WD_lk, WS_lk, wd_bin_size, ws_bin_size)
return WD_ilk, WS_ilk, TI_ilk, P_ilk
# def probability(self, x_i, y_i, h_i, WD_lk, WS_ilk, wd_bin_size, ws_bin_size):
def probability(self, x_i, y_i, h_i, WD_lk, WS_lk, wd_bin_size, ws_bin_size):
"""See Site.probability
"""
x_il, y_il, h_il = [np.repeat([v], WD_lk.shape[0], 0).T for v in [x_i, y_i, h_i]]
wd_il = np.repeat([WD_lk.mean(1)], len(x_i), 0)
Weibull_A_il, Weibull_k_il, freq_il = \
[self.interp_funcs[n]((x_il, y_il, h_il, wd_il)) for n in
['A', 'k', 'f']]
P_wd_il = freq_il * wd_bin_size
WS_ilk = WS_lk[na]
# TODO: The below probability does not sum to 1 due to the speed ups. New
# calculation of the weibull weights are needed.
P_ilk = self.weibull_weight(WS_ilk, Weibull_A_il[:, :, na],
Weibull_k_il[:, :, na], ws_bin_size) * P_wd_il[:, :, na]
# P_ilk = self.weibull_weight(WS_ilk, Weibull_A_il[:, :, na],
# Weibull_k_il[:, :, na], ws_bin_size) * P_wd_il[:, :, na]
self.freq_il = freq_il
self.pdf_ilk = self.weibull_weight(WS_ilk, Weibull_A_il[:, :, na],
Weibull_k_il[:, :, na], ws_bin_size)
self.Weibull_k = Weibull_k_il[:, :, na]
self.Weibull_A = Weibull_A_il[:, :, na]
self.Weibull_ws = WS_ilk
return P_ilk
def elevation(self, x_i, y_i):
return self.elevation_interpolator(x_i, y_i)
@classmethod
def from_pickle(cls, file_name, distance):
with open(file_name, 'rb') as f:
ds = pickle.load(f)
return WaspGridSite(ds, distance)
def to_pickle(self, file_name):
with open(file_name, 'wb') as f:
pickle.dump(self._ds, f, protocol=-1)
def interp_funcs_initialization(self,
interp_keys=['A', 'k', 'f', 'tke', 'spd', 'orog_trn', 'elev']):
""" Initialize interpolating functions using RegularGridInterpolator
for specified variables defined in interp_keys.
"""
interp_funcs = {}
for key in interp_keys:
try:
dr = self._ds.data_vars[key]
except KeyError:
print('Warning! {0} is not included in the current'.format(key) +
' WindResourceGrid object.\n')
continue
coords = []
data = dr.data
for dim in dr.dims:
# change sector index into wind direction (deg)
if dim == 'sec':
num_sec = len(dr.coords[dim].data) # number of sectors
coords.append(
np.linspace(0, 360, num_sec + 1))
data = np.concatenate((data, data[:, :, :, :1]), axis=3)
elif dim == 'z' and len(dr.coords[dim].data) == 1:
# special treatment for only one layer of data
height = dr.coords[dim].data[0]
coords.append(np.array([height - 1.0, height + 1.0]))
data = np.concatenate((data, data), axis=2)
else:
coords.append(dr.coords[dim].data)
interp_funcs[key] = RegularGridInterpolator(
coords,
data, bounds_error=False)
self.interp_funcs = interp_funcs
def plot_map(self, data_name, height=None, sector=None, xlim=[None, None], ylim=[None, None], ax=None):
if ax is None:
import matplotlib.pyplot as plt
ax = plt.gca()
if data_name not in self._ds:
raise AttributeError("%s not found in dataset. Available data variables are:\n%s" %
(data_name, ",".join(list(self._ds.data_vars.keys()))))
data = self._ds[data_name].sel(x=slice(*xlim), y=slice(*ylim))
if 'sec' in data.coords:
if sector not in data.coords['sec'].values:
raise AttributeError("Sector %s not found. Available sectors are: %s" %
(sector, data.coords['sec'].values))
data = data.sel(sec=sector)
if 'z' in data.coords:
if height is None:
raise AttributeError("Height missing for '%s'" % data_name)
data = data.interp(z=height)
data.plot(x='x', y='y', ax=ax)
ax.axis('equal')
@classmethod
def from_wasp_grd(cls, path, globstr='*.grd', distance=TerrainFollowingDistance(), speedup_using_pickle=True):
'''
Reader for WAsP .grd resource grid files.
Parameters
----------
path: str
path to file or directory containing goldwind excel files
globstr: str
string that is used to glob files if path is a directory.
Returns
-------
WindResourceGrid: :any:`WindResourceGrid`:
Examples
--------
>>> from mowflot.wind_resource import WindResourceGrid
>>> path = '../mowflot/tests/data/WAsP_grd/'
>>> wrg = WindResourceGrid.from_wasp_grd(path)
>>> print(wrg)
<xarray.Dataset>
Dimensions: (sec: 12, x: 20, y: 20, z: 3)
Coordinates:
* sec (sec) int64 1 2 3 4 5 6 7 8 9 10 11 12
* x (x) float64 5.347e+05 5.348e+05 5.349e+05 5.35e+05 ...
* y (y) float64 6.149e+06 6.149e+06 6.149e+06 6.149e+06 ...
* z (z) float64 10.0 40.0 80.0
Data variables:
flow_inc (x, y, z, sec) float64 1.701e+38 1.701e+38 1.701e+38 ...
ws_mean (x, y, z, sec) float64 3.824 3.489 5.137 5.287 5.271 ...
meso_rgh (x, y, z, sec) float64 0.06429 0.03008 0.003926 ...
obst_spd (x, y, z, sec) float64 1.701e+38 1.701e+38 1.701e+38 ...
orog_spd (x, y, z, sec) float64 1.035 1.039 1.049 1.069 1.078 ...
orog_trn (x, y, z, sec) float64 -0.1285 0.6421 0.7579 0.5855 ...
power_density (x, y, z, sec) float64 77.98 76.96 193.5 201.5 183.9 ...
rix (x, y, z, sec) float64 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ...
rgh_change (x, y, z, sec) float64 6.0 10.0 10.0 10.0 6.0 4.0 0.0 ...
rgh_spd (x, y, z, sec) float64 1.008 0.9452 0.8578 0.9037 ...
f (x, y, z, sec) float64 0.04021 0.04215 0.06284 ...
tke (x, y, z, sec) float64 1.701e+38 1.701e+38 1.701e+38 ...
A (x, y, z, sec) float64 4.287 3.837 5.752 5.934 5.937 ...
k (x, y, z, sec) float64 1.709 1.42 1.678 1.74 1.869 ...
flow_inc_tot (x, y, z) float64 1.701e+38 1.701e+38 1.701e+38 ...
ws_mean_tot (x, y, z) float64 5.16 6.876 7.788 5.069 6.85 7.785 ...
power_density_tot (x, y, z) float64 189.5 408.1 547.8 178.7 402.2 546.6 ...
rix_tot (x, y, z) float64 0.0 0.0 0.0 9.904e-05 9.904e-05 ...
tke_tot (x, y, z) float64 1.701e+38 1.701e+38 1.701e+38 ...
A_tot (x, y, z) float64 5.788 7.745 8.789 5.688 7.716 8.786 ...
k_tot (x, y, z) float64 1.725 1.869 2.018 1.732 1.877 2.018 ...
elev (x, y) float64 37.81 37.42 37.99 37.75 37.46 37.06 ...
'''
var_name_dict = {
'Flow inclination': 'flow_inc',
'Mean speed': 'ws_mean',
'Meso roughness': 'meso_rgh',
'Obstacles speed': 'obst_spd',
'Orographic speed': 'orog_spd',
'Orographic turn': 'orog_trn',
'Power density': 'power_density',
'RIX': 'rix',
'Roughness changes': 'rgh_change',
'Roughness speed': 'rgh_spd',
'Sector frequency': 'f',
'Turbulence intensity': 'tke',
'Weibull-A': 'A',
'Weibull-k': 'k',
'Elevation': 'elev',
'AEP': 'aep'}
def _read_grd(filename):
def _parse_line_floats(f):
return [float(i) for i in f.readline().strip().split()]
def _parse_line_ints(f):
return [int(i) for i in f.readline().strip().split()]
with open(filename, 'rb') as f:
file_id = f.readline().strip().decode()
nx, ny = _parse_line_ints(f)
xl, xu = _parse_line_floats(f)
yl, yu = _parse_line_floats(f)
zl, zu = _parse_line_floats(f)
values = np.array([l.split() for l in f.readlines() if l.strip() != b""],
dtype=np.float) # around 8 times faster
xarr = np.linspace(xl, xu, nx)
yarr = np.linspace(yl, yu, ny)
# note that the indexing of WAsP grd file is 'xy' type, i.e.,
# values.shape == (xarr.shape[0], yarr.shape[0])
# we need to transpose values to match the 'ij' indexing
values = values.T
#############
# note WAsP denotes unavailable values using very large numbers, here
# we change them into np.nan, to avoid strange results.
values[values > 1e20] = np.nan
return xarr, yarr, values
if speedup_using_pickle:
if os.path.isdir(path):
pkl_fn = os.path.join(path, os.path.split(os.path.dirname(path))[1] + '.pkl')
if os.path.isfile(pkl_fn):
return WaspGridSiteBase.from_pickle(pkl_fn, distance=distance)
else:
site_conditions = WaspGridSiteBase.from_wasp_grd(path, globstr,
distance=distance, speedup_using_pickle=False)
site_conditions.to_pickle(pkl_fn)
return site_conditions
else:
raise NotImplementedError
if os.path.isdir(path):
files = sorted(glob.glob(os.path.join(path, globstr)))
else:
raise Exception('Path was not a directory...')
file_height_dict = defaultdict(list)
pattern = re.compile(r'Sector (\w+|\d+) \s+ Height (\d+)m \s+ ([a-zA-Z0-9- ]+)')
for f in files:
sector, height, var_name = re.findall(pattern, f)[0]
# print(sector, height, var_name)
name = var_name_dict.get(var_name, var_name)
file_height_dict[height].append((f, sector, name))
elev_avail = False
first = True
for height, files_subset in file_height_dict.items():
first_at_height = True
for file, sector, var_name in files_subset:
xarr, yarr, values = _read_grd(file)
if sector == 'All':
# Only 'All' sector has the elevation files.
# So here we make sure that, when the elevation file
# is read, it gets the right (x,y) coords/dims.
if var_name == 'elev':
elev_avail = True
elev_vals = values
elev_coords = {'x': xarr,
'y': yarr}
elev_dims = ('x', 'y')
continue
else:
var_name += '_tot'
coords = {'x': xarr,
'y': yarr,
'z': np.array([float(height)])}
dims = ('x', 'y', 'z')
da = xr.DataArray(values[..., np.newaxis],
coords=coords,
dims=dims)
else:
coords = {'x': xarr,
'y': yarr,
'z': np.array([float(height)]),
'sec': np.array([int(sector)])}
dims = ('x', 'y', 'z', 'sec')
da = xr.DataArray(values[..., np.newaxis, np.newaxis],
coords=coords,
dims=dims)
if first_at_height:
ds_tmp = xr.Dataset({var_name: da})
first_at_height = False
else:
ds_tmp = xr.merge([ds_tmp, xr.Dataset({var_name: da})])
if first:
ds = ds_tmp
first = False
else:
ds = xr.concat([ds, ds_tmp], dim='z')
if elev_avail:
ds['elev'] = xr.DataArray(elev_vals,
coords=elev_coords,
dims=elev_dims)
############
# Calculate the compund speed-up factor based on orog_spd, rgh_spd
# and obst_spd
spd = 1
for dr in ds.data_vars:
if dr in ['orog_spd', 'obst_spd', 'rgh_spd']:
# spd *= np.where(ds.data_vars[dr].data > 1e20, 1, ds.data_vars[dr].data)
spd *= np.where(np.isnan(ds.data_vars[dr].data), 1, ds.data_vars[dr].data)
ds['spd'] = copy.deepcopy(ds['orog_spd'])
ds['spd'].data = spd
#############
# change the frequency from per sector to per deg
ds['f'].data = ds['f'].data * len(ds['f']['sec'].data) / 360.0
# #############
# # note WAsP denotes unavailable values using very large numbers, here
# # we change them into np.nan, to avoid strange results.
# for var in ds.data_vars:
# ds[var].data = np.where(ds[var].data > 1e20, np.nan, ds[var].data)
# make sure coords along z is asending order
ds = ds.loc[{'z': sorted(ds.coords['z'].values)}]
######################################################################
if 'tke' in ds and np.mean(ds['tke']) > 1:
ds['tke'] *= 0.01
return WaspGridSite(ds, distance)
class EqDistRegGrid2DInterpolator():
def __init__(self, x, y, Z):
self.x = x
self.y = y
self.Z = Z
self.dx, self.dy = [xy[1] - xy[0] for xy in [x, y]]
self.x0 = x[0]
self.y0 = y[0]
def __call__(self, x, y, mode='valid'):
xp, yp = x, y
xi = (xp - self.x0) / self.dx
xif, xi0 = np.modf(xi)
xi0 = xi0.astype(np.int)
xi1 = xi0 + 1
yi = (yp - self.y0) / self.dy
yif, yi0 = np.modf(yi)
yi0 = yi0.astype(np.int)
yi1 = yi0 + 1
valid = (xif >= 0) & (yif >= 0) & (xi1 < len(self.x)) & (yi1 < len(self.y))
z = np.empty_like(xp) + np.nan
xi0, xi1, xif, yi0, yi1, yif = [v[valid] for v in [xi0, xi1, xif, yi0, yi1, yif]]
z00 = self.Z[xi0, yi0]
z10 = self.Z[xi1, yi0]
z01 = self.Z[xi0, yi1]
z11 = self.Z[xi1, yi1]
z0 = z00 + (z10 - z00) * xif
z1 = z01 + (z11 - z01) * xif
z[valid] = z0 + (z1 - z0) * yif
if mode == 'extrapolate':
valid = valid & ~np.isnan(z)
if (valid[0] == False) | (valid[-1] == False): # noqa
nonnan_index = np.where(~np.isnan(z))[0]
if valid[0] == False: # noqa
first_valid = nonnan_index[0]
z[:first_valid] = z[first_valid]
if valid[-1] == False: # noqa
last_valid = nonnan_index[-1]
z[last_valid + 1:] = z[last_valid]
return z
def main():
if __name__ == '__main__':
from py_wake.examples.data.ParqueFicticio import ParqueFicticio_path
import matplotlib.pyplot as plt
site = WaspGridSiteBase.from_wasp_grd(ParqueFicticio_path, speedup_using_pickle=False)
x, y = site._ds.coords['x'].data, site._ds.coords['y'].data,
Y, X = np.meshgrid(y, x)
Z = site._ds['elev'].data
if 1:
Z = site.elevation(X.flatten(), Y.flatten()).reshape(X.shape)
c = plt.contourf(X, Y, Z, 100)
plt.colorbar(c)
i, j = 15, 15
plt.plot(X[i], Y[i], 'b')
plt.plot(X[:, j], Y[:, j], 'r')
plt.axis('equal')
plt.figure()
Z = site.elevation_interpolator(X[:, j], Y[:, j], mode='extrapolate')
plt.plot(X[:, j], Z, 'r')
plt.figure()
Z = site.elevation_interpolator(X[i], Y[i], mode='extrapolate')
plt.plot(Y[i], Z, 'b')
z = np.arange(35, 200, 1)
u_z = site.local_wind([x[i]] * len(z), y_i=[y[i]] * len(z),
h_i=z, wd=[0], ws=[10])[1][:, 0, 0]
plt.figure()
Z = site.elevation_interpolator(X[i], Y[i], mode='extrapolate')
plt.plot(Y[i], Z, 'b')
plt.figure()
plt.plot(u_z, z)
plt.show()
main()