Source code for rex.renewable_resource

# -*- coding: utf-8 -*-
"""
Classes to handle renewable resource data
"""
import numpy as np
import os
import pandas as pd
import warnings

from rex.resource import BaseResource
from rex.sam_resource import SAMResource
from rex.utilities.exceptions import (ResourceValueError, ExtrapolationWarning,
                                      ResourceWarning, ResourceRuntimeError,
                                      MoninObukhovExtrapolationError)
from rex.utilities.parse_keys import parse_keys


[docs]class SolarResource(BaseResource): """ Class to handle Solar BaseResource .h5 files See Also -------- resource.BaseResource : Parent class """
[docs] def get_SAM_df(self, site): """ Get SAM solar resource DataFrame for given site Parameters ---------- site : int Site to extract SAM DataFrame for Returns ------- res_df : pandas.DataFrame time-series DataFrame of resource variables needed to run SAM """ if not self._unscale: raise ResourceValueError("SAM requires unscaled values") res_df = pd.DataFrame({'Year': self.time_index.year, 'Month': self.time_index.month, 'Day': self.time_index.day, 'Hour': self.time_index.hour}) if len(self) > 8784: res_df['Minute'] = self.time_index.minute time_zone = self.meta.loc[site, 'timezone'] time_interval = len(self.time_index) // 8760 for var in ['dni', 'dhi', 'wind_speed', 'air_temperature']: ds_slice = (slice(None), site) var_array = self._get_ds(var, ds_slice) var_array = SAMResource.roll_timeseries(var_array, time_zone, time_interval) res_df[var] = SAMResource.check_units(var, var_array, tech='pvwattsv7') col_map = {'dni': 'DNI', 'dhi': 'DHI', 'wind_speed': 'Wind Speed', 'air_temperature': 'Temperature'} res_df = res_df.rename(columns=col_map) res_df.name = "SAM_-{}".format(site) return res_df
def _preload_SAM(self, sites, tech='pvwattsv7', time_index_step=None, means=False, clearsky=False, bifacial=False): """ Pre-load project_points for SAM Parameters ---------- sites : list List of sites to be provided to SAM tech : str, optional SAM technology string, by default 'pvwattsv7' time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False clearsky : bool Boolean flag to pull clearsky instead of real irradiance bifacial : bool Boolean flag to pull surface albedo for bifacial modeling. Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ time_slice = slice(None, None, time_index_step) SAM_res = SAMResource(sites, tech, self['time_index', time_slice], means=means) sites = SAM_res.sites_slice SAM_res['meta'] = self['meta', sites] if clearsky: SAM_res.set_clearsky() if bifacial and 'surface_albedo' not in SAM_res.var_list: SAM_res._var_list.append('surface_albedo') SAM_res.check_irradiance_datasets(self.datasets, clearsky=clearsky) for var in SAM_res.var_list: if var in self.datasets: SAM_res[var] = self[var, time_slice, sites] SAM_res.compute_irradiance(clearsky=clearsky) return SAM_res
[docs] @classmethod def preload_SAM(cls, h5_file, sites, unscale=True, str_decode=True, group=None, hsds=False, hsds_kwargs=None, tech='pvwattsv7', time_index_step=None, means=False, clearsky=False, bifacial=False): """ Pre-load project_points for SAM Parameters ---------- h5_file : str h5_file to extract resource from sites : list List of sites to be provided to SAM unscale : bool Boolean flag to automatically unscale variables on extraction str_decode : bool Boolean flag to decode the bytestring meta data into normal strings. Setting this to False will speed up the meta data read. group : str Group within .h5 resource file to open hsds : bool, optional Boolean flag to use h5pyd to handle .h5 'files' hosted on AWS behind HSDS, by default False hsds_kwargs : dict, optional Dictionary of optional kwargs for h5pyd, e.g., bucket, username, password, by default None tech : str, optional SAM technology string, by default 'pvwattsv7' time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False clearsky : bool Boolean flag to pull clearsky instead of real irradiance bifacial : bool Boolean flag to pull surface albedo for bifacial modeling. Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ kwargs = {"unscale": unscale, "hsds": hsds, 'hsds_kwargs': hsds_kwargs, "str_decode": str_decode, "group": group} with cls(h5_file, **kwargs) as res: SAM_res = res._preload_SAM(sites, tech=tech, time_index_step=time_index_step, means=means, clearsky=clearsky, bifacial=bifacial) return SAM_res
[docs]class NSRDB(SolarResource): """ Class to handle NSRDB .h5 files See Also -------- resource.BaseResource : Parent class """ ADD_ATTR = 'psm_add_offset' SCALE_ATTR = 'psm_scale_factor' UNIT_ATTR = 'psm_units' def _preload_SAM(self, sites, tech='pvwattsv7', time_index_step=None, means=False, clearsky=False, bifacial=False, downscale=None): """ Pre-load project_points for SAM Parameters ---------- sites : list List of sites to be provided to SAM tech : str, optional SAM technology string, by default 'pvwattsv7' time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False clearsky : bool Boolean flag to pull clearsky instead of real irradiance bifacial : bool Boolean flag to pull surface albedo for bifacial modeling. downscale : NoneType | dict Option for NSRDB resource downscaling to higher temporal resolution. Expects a dict of downscaling kwargs with a minimum requirement of the desired frequency e.g. 'frequency': '5min' and an option to add "variability_kwargs". Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ time_slice = slice(None, None, time_index_step) SAM_res = SAMResource(sites, tech, self['time_index', time_slice], means=means) sites = SAM_res.sites_slice SAM_res['meta'] = self['meta', sites] if clearsky: SAM_res.set_clearsky() if bifacial and 'surface_albedo' not in SAM_res.var_list: SAM_res._var_list.append('surface_albedo') SAM_res.check_irradiance_datasets(self.datasets, clearsky=clearsky) if not downscale: for var in SAM_res.var_list: if var in self.datasets: SAM_res[var] = self[var, time_slice, sites] SAM_res.compute_irradiance(clearsky=clearsky) else: # contingent import to avoid dependencies from rex.utilities.downscale import downscale_nsrdb frequency = downscale.pop('frequency') SAM_res = downscale_nsrdb(SAM_res, self, sam_vars=SAM_res.var_list, frequency=frequency, variability_kwargs=downscale) return SAM_res
[docs] @classmethod def preload_SAM(cls, h5_file, sites, unscale=True, str_decode=True, group=None, hsds=False, hsds_kwargs=None, tech='pvwattsv7', time_index_step=None, means=False, clearsky=False, bifacial=False, downscale=None): """ Pre-load project_points for SAM Parameters ---------- h5_file : str h5_file to extract resource from sites : list List of sites to be provided to SAM unscale : bool Boolean flag to automatically unscale variables on extraction str_decode : bool Boolean flag to decode the bytestring meta data into normal strings. Setting this to False will speed up the meta data read. group : str Group within .h5 resource file to open hsds : bool, optional Boolean flag to use h5pyd to handle .h5 'files' hosted on AWS behind HSDS, by default False hsds_kwargs : dict, optional Dictionary of optional kwargs for h5pyd, e.g., bucket, username, password, by default None tech : str, optional SAM technology string, by default 'pvwattsv7' time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False clearsky : bool Boolean flag to pull clearsky instead of real irradiance bifacial : bool Boolean flag to pull surface albedo for bifacial modeling. downscale : NoneType | dict Option for NSRDB resource downscaling to higher temporal resolution. Expects a dict of downscaling kwargs with a minimum requirement of the desired frequency e.g. 'frequency': '5min' and an option to add "variability_kwargs". Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ kwargs = {"unscale": unscale, "hsds": hsds, 'hsds_kwargs': hsds_kwargs, "str_decode": str_decode, "group": group} with cls(h5_file, **kwargs) as res: SAM_res = res._preload_SAM(sites, tech=tech, time_index_step=time_index_step, means=means, clearsky=clearsky, bifacial=bifacial, downscale=downscale) return SAM_res
[docs]class WindResource(BaseResource): """ Class to handle Wind BaseResource .h5 files See Also -------- resource.BaseResource : Parent class Examples -------- >>> file = '$TESTDATADIR/wtk/ri_100_wtk_2012.h5' >>> with WindResource(file) as res: >>> print(res.datasets) ['meta', 'pressure_0m', 'pressure_100m', 'pressure_200m', 'temperature_100m', 'temperature_80m', 'time_index', 'winddirection_100m', 'winddirection_80m', 'windspeed_100m', 'windspeed_80m'] WindResource can interpolate between available hub-heights (80 & 100) >>> with WindResource(file) as res: >>> wspd_90m = res['windspeed_90m'] >>> >>> wspd_90m [[ 6.865 6.77 6.565 ... 8.65 8.62 8.415 ] [ 7.56 7.245 7.685 ... 5.9649997 5.8 6.2 ] [ 9.775 9.21 9.225 ... 7.12 7.495 7.675 ] ... [ 8.38 8.440001 8.85 ... 11.934999 12.139999 12.4 ] [ 9.900001 9.895 9.93 ... 12.825 12.86 12.965 ] [ 9.895 10.01 10.305 ... 14.71 14.79 14.764999 ]] WindResource can also extrapolate beyond available hub-heights >>> with WindResource(file) as res: >>> wspd_150m = res['windspeed_150m'] >>> >>> wspd_150m ExtrapolationWarning: 150 is outside the height range (80, 100). Extrapolation to be used. [[ 7.336291 7.2570405 7.0532546 ... 9.736436 9.713792 9.487364 ] [ 8.038219 7.687255 8.208041 ... 6.6909685 6.362647 6.668326 ] [10.5515785 9.804363 9.770399 ... 8.026898 8.468434 8.67222 ] ... [ 9.079792 9.170363 9.634542 ... 13.472508 13.7102585 14.004617 ] [10.710078 10.710078 10.698757 ... 14.468795 14.514081 14.6386175] [10.698757 10.857258 11.174257 ... 16.585903 16.676476 16.653833 ]] """ def __init__(self, h5_file, unscale=True, str_decode=True, group=None, hsds=False, hsds_kwargs=None): """ Parameters ---------- h5_file : str Path to .h5 resource file unscale : bool Boolean flag to automatically unscale variables on extraction str_decode : bool Boolean flag to decode the bytestring meta data into normal strings. Setting this to False will speed up the meta data read. group : str Group within .h5 resource file to open hsds : bool, optional Boolean flag to use h5pyd to handle .h5 'files' hosted on AWS behind HSDS, by default False hsds_kwargs : dict, optional Dictionary of optional kwargs for h5pyd, e.g., bucket, username, password, by default None """ self._heights = None super().__init__(h5_file, unscale=unscale, str_decode=str_decode, group=group, hsds=hsds, hsds_kwargs=hsds_kwargs) def __getitem__(self, keys): ds, ds_slice = parse_keys(keys) _, ds_name = os.path.split(ds) if 'SAM' in ds_name: site = ds_slice[0] if isinstance(site, (int, np.integer)): _, height = self._parse_name(ds_name) out = self.get_SAM_df(site, height) else: msg = "Can only extract SAM DataFrame for a single site" raise ResourceRuntimeError(msg) else: out = super().__getitem__(keys) return out @property def heights(self): """ Extract available heights for pressure, temperature, windspeed, precip, and winddirection variables. Used for interpolation/extrapolation. Returns ------- self._heights : dict Dictionary of available heights for: windspeed, winddirection, temperature, and pressure """ if self._heights is None: heights = {'pressure': [], 'temperature': [], 'windspeed': [], 'winddirection': []} ignore = ['meta', 'time_index', 'coordinates'] for ds in self.datasets: if ds not in ignore: ds_name, h = self._parse_name(ds) if ds_name in heights: heights[ds_name].append(h) self._heights = heights return self._heights @staticmethod def _parse_hub_height(name): """ Extract hub height from given string Parameters ---------- name : str String to parse hub height from Returns ------- h : int | float Hub Height as a numeric value """ h = name.strip('m') try: h = int(h) except ValueError: h = float(h) return h @classmethod def _parse_name(cls, ds_name): """ Extract dataset name and height from dataset name Parameters ---------- ds_name : str Dataset name Returns ------- name : str Variable name h : int | float Height of variable """ try: if ds_name.endswith('m'): name = '_'.join(ds_name.split('_')[:-1]) h = ds_name.split('_')[-1] h = cls._parse_hub_height(h) else: raise ValueError('{} does not end with "_m"' .format(ds_name)) except ValueError: name = ds_name h = None return name, h
[docs] @staticmethod def get_nearest_h(h, heights): """ Get two nearest h values in heights. Determine if h is inside or outside the range of heights (requiring extrapolation instead of interpolation) Parameters ---------- h : int | float Height value of interest heights : list List of available heights Returns ------- nearest_h : list list of 1st and 2nd nearest height in heights extrapolate : bool Flag as to whether h is inside or outside heights range """ heights_arr = np.array(heights, dtype='float32') dist = np.abs(heights_arr - h) pos = dist.argsort()[:2] nearest_h = sorted([heights[p] for p in pos]) extrapolate = np.all(h < heights_arr) or np.all(h > heights_arr) if extrapolate: h_min, h_max = np.sort(heights)[[0, -1]] msg = ('{} is outside the height range'.format(h), '({}, {}).'.format(h_min, h_max), 'Extrapolation to be used.') warnings.warn(' '.join(msg), ExtrapolationWarning) return nearest_h, extrapolate
[docs] @classmethod def monin_obukhov_extrapolation(cls, ts_1, h_1, z0, L, h): """ Monin-Obukhov extrapolation Parameters ---------- ts_1 : ndarray Time-series array at height h_1 h_1 : int | float Height corresponding to time-seris ts_1 z0: int | float | ndarray Roughness length L : ndarray time-series of Obukhov length (m; measure of stability) h : int | float Desired height Returns ------- ndarray new wind speed from MO extrapolation. """ # Non dimensional stability parameter at h zeta = cls.stability_function(h / L) # Non dimensional stability parameter at z0 zeta_0 = cls.stability_function(z0 / L) # Non dimensional stability parameter at h_1 zeta_1 = cls.stability_function(h_1 / L) # Logarithmic extrapolation equation out = (ts_1 * (np.log(h / z0) - zeta + zeta_0) / (np.log(h_1 / z0) - zeta_1 + zeta_0)) return out
[docs] @staticmethod def stability_function(zeta): """ Calculate stability function depending on sign of L (negative is unstable, positive is stable) Parameters ---------- zeta : ndarray Normalized length Returns ------- numpy.ndarray stability measurements. """ stab_fun = np.zeros(len(zeta)) zeta = zeta.astype(float) # Unstable conditions x = (np.power(1 - 16 * zeta[zeta < 0], 0.25)) paulson_func = (np.pi / 2 - 2 * np.arctan(x) + np.log(np.power(1 + x, 2) * (1 + np.power(x, 2)) / 8)) y = np.power(1 - 10 * zeta[zeta < 0], 1. / 3) conv_func = (3 / 2 * np.log(np.power(y, 2) + y + 1. / 3) - np.sqrt(3) * np.arctan(2 * y + 1 / np.sqrt(3)) + np.pi / np.sqrt(3)) o = ((paulson_func + np.power(zeta[zeta < 0], 2) * conv_func) / (1 + np.power(zeta[zeta < 0], 2))) stab_fun[np.where(zeta < 0)] = o # Stable conditions a = 6.1 b = 2.5 o = np.log(zeta[zeta >= 0] + (1 + np.power(zeta[zeta >= 0], b))**(1 / b)) o *= -a stab_fun[np.where(zeta >= 0)] = o return stab_fun
[docs] @staticmethod def power_law_interp(ts_1, h_1, ts_2, h_2, h, mean=True): """ Power-law interpolate/extrapolate time-series data to height h Parameters ---------- ts_1 : ndarray Time-series array at height h_1 h_1 : int | float Height corresponding to time-seris ts_1 ts_2 : ndarray Time-series array at height h_2 h_2 : int | float Height corresponding to time-seris ts_2 h : int | float Height of desired time-series mean : bool Calculate average alpha versus point by point alpha Returns ------- out : ndarray Time-series array at height h """ if h_1 > h_2: h_1, h_2 = h_2, h_1 ts_1, ts_2 = ts_2, ts_1 if mean: alpha = (np.log(ts_2.mean() / ts_1.mean()) / np.log(h_2 / h_1)) if alpha < 0.06: warnings.warn('Alpha is < 0.06', RuntimeWarning) elif alpha > 0.6: warnings.warn('Alpha is > 0.6', RuntimeWarning) else: # Replace zero values for alpha calculation ts_1[ts_1 == 0] = 0.001 ts_2[ts_2 == 0] = 0.001 alpha = np.log(ts_2 / ts_1) / np.log(h_2 / h_1) # The Hellmann exponent varies from 0.06 to 0.6 alpha[alpha < 0.06] = 0.06 alpha[alpha > 0.6] = 0.6 out = ts_1 * (h / h_1)**alpha return out
[docs] @staticmethod def linear_interp(ts_1, h_1, ts_2, h_2, h): """ Linear interpolate/extrapolate time-series data to height h Parameters ---------- ts_1 : ndarray Time-series array at height h_1 h_1 : int | float Height corresponding to time-seris ts_1 ts_2 : ndarray Time-series array at height h_2 h_2 : int | float Height corresponding to time-seris ts_2 h : int | float Height of desired time-series Returns ------- out : ndarray Time-series array at height h """ if h_1 > h_2: h_1, h_2 = h_2, h_1 ts_1, ts_2 = ts_2, ts_1 # Calculate slope for every posiiton in variable arrays m = (ts_2 - ts_1) / (h_2 - h_1) # Calculate intercept for every position in variable arrays b = ts_2 - m * h_2 out = m * h + b return out
[docs] @staticmethod def shortest_angle(a0, a1): """ Calculate the shortest angle distance between a0 and a1 Parameters ---------- a0 : int | float angle 0 in degrees a1 : int | float angle 1 in degrees Returns ------- da : int | float shortest angle distance between a0 and a1 """ da = (a1 - a0) % 360 return 2 * da % 360 - da
[docs] @classmethod def circular_interp(cls, ts_1, h_1, ts_2, h_2, h): """ Circular interpolate/extrapolate time-series data to height h Parameters ---------- ts_1 : ndarray Time-series array at height h_1 h_1 : int | float Height corresponding to time-seris ts_1 ts_2 : ndarray Time-series array at height h_2 h_2 : int | float Height corresponding to time-seris ts_2 h : int | float Height of desired time-series Returns ------- out : ndarray Time-series array at height h """ h_f = (h - h_1) / (h_2 - h_1) da = cls.shortest_angle(ts_1, ts_2) * h_f da = np.sign(da) * (np.abs(da) % 360) out = (ts_2 + da) % 360 return out
[docs] def get_attrs(self, dset=None): """ Get h5 attributes either from file or dataset Parameters ---------- dset : str Dataset to get attributes for, if None get file (global) attributes Returns ------- attrs : dict Dataset or file attributes """ if dset is None: attrs = dict(self.h5.attrs) else: var_name, h = self._parse_name(dset) if h is not None and var_name in self.heights: (h, _), _ = self.get_nearest_h(h, self.heights[var_name]) dset = '{}_{}m'.format(var_name, h) attrs = super().get_attrs(dset=dset) return attrs
[docs] def get_dset_properties(self, dset): """ Get dataset properties (shape, dtype, chunks) Parameters ---------- dset : str Dataset to get scale factor for Returns ------- shape : tuple Dataset array shape dtype : str Dataset array dtype chunks : tuple Dataset chunk size """ var_name, h = self._parse_name(dset) if h is not None and var_name in self.heights: (h, _), _ = self.get_nearest_h(h, self.heights[var_name]) dset = '{}_{}m'.format(var_name, h) return super().get_dset_properties(dset)
def _check_hub_height(self, h): """ Check requested hub-height against available windspeed hub-heights If only one hub-height is available change request to match available hub-height Parameters ---------- h : int | float Requested hub-height Returns ------- h : int | float Hub-height to extract """ heights = self.heights['windspeed'] if len(heights) == 1: h = heights[0] warnings.warn('Wind speed is only available at {h}m, ' 'all variables will be extracted at {h}m' .format(h=h), ResourceWarning) return h def _try_monin_obukhov_extrapolation(self, ts_1, ds_slice, h_1, h): rmol = 'inversemoninobukhovlength_2m' if rmol not in self: msg = ("{} is needed to run monin obukhov extrapolation" .format(rmol)) raise MoninObukhovExtrapolationError(msg) if 'roughness_length' in self: z0 = self._get_ds('roughness_length', ds_slice) elif 'z0' in self.meta: z0 = self.meta['z0'] else: msg = ("roughness length ('z0') is needed to run monin obukhov" "extrapolation") raise MoninObukhovExtrapolationError(msg) L = 1 / self._get_ds(rmol, ds_slice) out = self.monin_obukhov_extrapolation(ts_1, h_1, z0, L, h) return out def _get_ds_height(self, ds_name, ds_slice): """ Extract data from given dataset at desired height, interpolate or extrapolate if neede Parameters ---------- ds_name : str Variable dataset to be extracted ds_slice : tuple Tuple of (int, slice, list, ndarray) of what to extract from ds, each arg is for a sequential axis Returns ------- out : ndarray ndarray of variable timeseries data If unscale, returned in native units else in scaled units """ var_name, h = self._parse_name(ds_name) heights = self.heights[var_name] if h in heights: ds_name = '{}_{}m'.format(var_name, int(h)) out = super()._get_ds(ds_name, ds_slice) elif len(heights) == 1: h = heights[0] ds_name = '{}_{}m'.format(var_name, h) warnings.warn('Only one hub-height available, returning {}' .format(ds_name), ResourceWarning) out = super()._get_ds(ds_name, ds_slice) else: (h1, h2), extrapolate = self.get_nearest_h(h, heights) if extrapolate: msg = 'Extrapolating {}'.format(ds_name) ts1 = super()._get_ds('{}_{}m'.format(var_name, h1), ds_slice) ts2 = super()._get_ds('{}_{}m'.format(var_name, h2), ds_slice) if (var_name == 'windspeed') and extrapolate: if h < h1: try: out = self._try_monin_obukhov_extrapolation(ts1, ds_slice, h1, h) msg += ' using Monin Obukhov Extrapolation' warnings.warn(msg, ExtrapolationWarning) except MoninObukhovExtrapolationError: out = self.power_law_interp(ts1, h1, ts2, h2, h) msg += ' using Power Law Extrapolation' warnings.warn(msg, ExtrapolationWarning) else: out = self.power_law_interp(ts1, h1, ts2, h2, h) msg += ' using Power Law Extrapolation' warnings.warn(msg, ExtrapolationWarning) elif var_name == 'winddirection': out = self.circular_interp(ts1, h1, ts2, h2, h) else: out = self.linear_interp(ts1, h1, ts2, h2, h) return out def _get_ds(self, ds_name, ds_slice): """ Extract data from given dataset Parameters ---------- ds_name : str Variable dataset to be extracted ds_slice : tuple Tuple of (int, slice, list, ndarray) of what to extract from ds, each arg is for a sequential axis Returns ------- out : ndarray ndarray of variable timeseries data If unscale, returned in native units else in scaled units """ var_name, h = self._parse_name(ds_name) if h is not None and var_name in self.heights: out = self._get_ds_height(ds_name, ds_slice) else: out = super()._get_ds(ds_name, ds_slice) return out
[docs] def get_SAM_df(self, site, height, require_wind_dir=False, icing=False, add_header=False): """ Get SAM wind resource DataFrame for given site Parameters ---------- site : int Site to extract SAM DataFrame for height : int Hub height to extract SAM variables at require_wind_dir : bool, optional Boolean flag as to whether wind direction will be loaded, by default False icing : bool, optional Boolean flag to include relativehumitidy for icing calculation, by default False add_header : bool, optional Add units and hub_height below variable names, needed for SAM .csv, by default False Returns ------- res_df : pandas.DataFrame time-series DataFrame of resource variables needed to run SAM """ if not self._unscale: raise ResourceValueError("SAM requires unscaled values") height = self._check_hub_height(height) units = ['year', 'month', 'day', 'hour'] res_df = pd.DataFrame({'Year': self.time_index.year, 'Month': self.time_index.month, 'Day': self.time_index.day, 'Hour': self.time_index.hour}) if len(self) > 8784: res_df['Minute'] = self.time_index.minute time_zone = self.meta.loc[site, 'timezone'] time_interval = len(self.time_index) // 8760 variables = ['pressure', 'temperature', 'winddirection', 'windspeed'] if not require_wind_dir: variables.remove('winddirection') if icing: variables.append('relativehumidity_2m') for var in variables: var_name = "{}_{}m".format(var, height) ds_slice = (slice(None), site) var_array = self._get_ds(var_name, ds_slice) var_array = SAMResource.roll_timeseries(var_array, time_zone, time_interval) res_df[var] = SAMResource.check_units(var, var_array, tech='windpower') res_df[var] = SAMResource.enforce_arr_range( var, res_df[var], SAMResource.WIND_DATA_RANGES[var], [site]) col_map = {'pressure': 'Pressure', 'temperature': 'Temperature', 'windspeed': 'Speed', 'winddirection': 'Direction', 'relativehumidity_2m': 'Relative Humidity'} res_df = res_df.rename(columns=col_map) res_df.name = "SAM_{}m-{}".format(height, site) if add_header: header = pd.DataFrame(columns=res_df.columns) header.at[0] = units + [self.get_units(v) for v in variables] header.at[1] = height res_df = pd.concat((header, res_df)).reset_index(drop=True) return res_df
def _preload_SAM(self, sites, hub_heights, time_index_step=None, means=False, require_wind_dir=False, precip_rate=False, icing=False): """ Pre-load project_points for SAM Parameters ---------- sites : list List of sites to be provided to SAM hub_heights : int | float | list Hub heights to extract for SAM time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False require_wind_dir : bool, optional Boolean flag as to whether wind direction will be loaded, by default False precip_rate : bool, optional Boolean flag as to whether precipitationrate_0m will be preloaded, by default False icing : bool, optional Boolean flag as to whether icing is analyzed. This will preload relative humidity, by default False Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ time_slice = slice(None, None, time_index_step) SAM_res = SAMResource(sites, 'windpower', self['time_index', time_slice], hub_heights=hub_heights, require_wind_dir=require_wind_dir, means=means) sites = SAM_res.sites_slice SAM_res['meta'] = self['meta', sites] var_list = SAM_res.var_list if not require_wind_dir: var_list.remove('winddirection') h = self._check_hub_height(SAM_res.h) if isinstance(h, (int, float)): for var in var_list: ds_name = "{}_{}m".format(var, h) SAM_res[var] = self[ds_name, time_slice, sites] else: _, unq_idx = np.unique(h, return_inverse=True) unq_h = sorted(list(set(h))) site_list = np.array(SAM_res.sites) height_slices = {} for i, h_i in enumerate(unq_h): pos = np.where(unq_idx == i)[0] height_slices[h_i] = (site_list[pos], pos) for var in var_list: for h_i, (h_pos, sam_pos) in height_slices.items(): ds_name = '{}_{}m'.format(var, h_i) SAM_res[var, :, sam_pos] = self[ds_name, time_slice, h_pos] if precip_rate: var = 'precipitationrate' ds_name = '{}_0m'.format(var) SAM_res.append_var_list(var) SAM_res[var] = self[ds_name, time_slice, sites] if icing: var = 'rh' ds_name = 'relativehumidity_2m' SAM_res.append_var_list(var) SAM_res[var] = self[ds_name, time_slice, sites] return SAM_res
[docs] @classmethod def preload_SAM(cls, h5_file, sites, hub_heights, unscale=True, str_decode=True, group=None, hsds=False, hsds_kwargs=None, time_index_step=None, means=False, require_wind_dir=False, precip_rate=False, icing=False): """ Placeholder for classmethod that will pre-load project_points for SAM Parameters ---------- h5_file : str h5_file to extract resource from sites : list List of sites to be provided to SAM hub_heights : int | float | list Hub heights to extract for SAM unscale : bool Boolean flag to automatically unscale variables on extraction str_decode : bool Boolean flag to decode the bytestring meta data into normal strings. Setting this to False will speed up the meta data read. group : str Group within .h5 resource file to open hsds : bool, optional Boolean flag to use h5pyd to handle .h5 'files' hosted on AWS behind HSDS, by default False hsds_kwargs : dict, optional Dictionary of optional kwargs for h5pyd, e.g., bucket, username, password, by default None time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None means : bool, optional Boolean flag to compute mean resource when res_array is set, by default False require_wind_dir : bool, optional Boolean flag as to whether wind direction will be loaded, by default False precip_rate : bool, optional Boolean flag as to whether precipitationrate_0m will be preloaded, by default False icing : bool, optional Boolean flag as to whether icing is analyzed. This will preload relative humidity, by default False Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Solar resource for sites in project_points """ kwargs = {"unscale": unscale, "hsds": hsds, 'hsds_kwargs': hsds_kwargs, "str_decode": str_decode, "group": group} with cls(h5_file, **kwargs) as res: SAM_res = res._preload_SAM(sites, hub_heights, require_wind_dir=require_wind_dir, precip_rate=precip_rate, icing=icing, means=means, time_index_step=time_index_step) return SAM_res
[docs]class WaveResource(BaseResource): """ Class to handle Wave BaseResource .h5 files See Also -------- resource.BaseResource : Parent class """
[docs] def get_SAM_df(self, site): """ Get SAM wave resource DataFrame for given site Parameters ---------- site : int Site to extract SAM DataFrame for Returns ------- res_df : pandas.DataFrame time-series DataFrame of resource variables needed to run SAM """ if not self._unscale: raise ResourceValueError("SAM requires unscaled values") res_df = pd.DataFrame({'Year': self.time_index.year, 'Month': self.time_index.month, 'Day': self.time_index.day, 'Hour': self.time_index.hour}) if len(self) > 8784: res_df['Minute'] = self.time_index.minute time_zone = self.meta.loc[site, 'timezone'] time_interval = len(self.time_index) // 8760 for var in ['significant_wave_height', 'energy_period']: ds_slice = (slice(None), site) var_array = self._get_ds(var, ds_slice) var_array = SAMResource.roll_timeseries(var_array, time_zone, time_interval) res_df[var] = var_array col_map = {'significant_wave_height': 'wave_height', 'energy_period': 'wave_period'} res_df = res_df.rename(columns=col_map) res_df.name = "SAM_-{}".format(site) return res_df
def _preload_SAM(self, sites, means=False, time_index_step=None): """ Pre-load project_points for SAM Parameters ---------- sites : list List of sites to be provided to SAM means : bool Boolean flag to compute mean resource when res_array is set time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Wave resource for sites in project_points """ SAM_res = super()._preload_SAM(sites, 'wave', means=means, time_index_step=time_index_step) return SAM_res
[docs] @classmethod def preload_SAM(cls, h5_file, sites, unscale=True, str_decode=True, group=None, hsds=False, hsds_kwargs=None, means=False, time_index_step=None): """ Pre-load project_points for SAM Parameters ---------- h5_file : str h5_file to extract resource from sites : list List of sites to be provided to SAM unscale : bool Boolean flag to automatically unscale variables on extraction str_decode : bool Boolean flag to decode the bytestring meta data into normal strings. Setting this to False will speed up the meta data read. group : str Group within .h5 resource file to open hsds : bool, optional Boolean flag to use h5pyd to handle .h5 'files' hosted on AWS behind HSDS, by default False hsds_kwargs : dict, optional Dictionary of optional kwargs for h5pyd, e.g., bucket, username, password, by default None means : bool Boolean flag to compute mean resource when res_array is set time_index_step: int, optional Step size for time_index, used to reduce temporal resolution, by default None Returns ------- SAM_res : SAMResource Instance of SAMResource pre-loaded with Wave resource for sites in project_points """ kwargs = {"unscale": unscale, "hsds": hsds, 'hsds_kwargs': hsds_kwargs, "str_decode": str_decode, "group": group} with cls(h5_file, **kwargs) as res: SAM_res = res._preload_SAM(sites, means=means, time_index_step=time_index_step) return SAM_res