# -*- coding: utf-8 -*-
"""
reV configuration framework for SAM config inputs.
"""
import logging
import os
from warnings import warn
from gaps.config import load_config
from reV.utilities.exceptions import SAMInputError, SAMInputWarning
from reV.config.base_config import BaseConfig
logger = logging.getLogger(__name__)
[docs]class SAMConfig(BaseConfig):
"""Class to handle the SAM section of config input."""
def __init__(self, SAM_configs):
"""
Parameters
----------
SAM_configs : dict
Keys are config ID's, values are filepaths to the SAM configs.
"""
super().__init__(SAM_configs, check_keys=False)
self._clearsky = None
self._bifacial = None
self._icing = None
self._inputs = None
self._downscale = None
self._time_index_step = None
@property
def clearsky(self):
"""Get a boolean for whether solar resource requires clearsky irrad.
Returns
-------
clearsky : bool
Flag set in the SAM config input with key "clearsky" for solar
analysis to process generation for clearsky irradiance.
Defaults to False (normal all-sky irradiance).
"""
if self._clearsky is None:
self._clearsky = False
for v in self.inputs.values():
self._clearsky = any((self._clearsky,
bool(v.get('clearsky', False))))
if self._clearsky:
logger.debug('Solar analysis being performed on clearsky '
'irradiance.')
return self._clearsky
@property
def bifacial(self):
"""Get a boolean for whether bifacial solar analysis is being run.
Returns
-------
bifacial : bool
Flag set in the SAM config input with key "bifaciality" for solar
analysis to analyze bifacial PV panels. Will require albedo input.
Defaults to False (no bifacial panels is default).
"""
if self._bifacial is None:
self._bifacial = False
for v in self.inputs.values():
bi_flags = ('bifaciality', 'spe_is_bifacial',
'cec_is_bifacial', '6par_is_bifacial')
bi_bools = [bool(v.get(flag, 0)) for flag in bi_flags]
self._bifacial = any(bi_bools + [self._bifacial])
return self._bifacial
@property
def icing(self):
"""Get a boolean for whether wind generation is considering icing.
Returns
-------
_icing : bool
Flag for whether wind generation is considering icing effects.
Based on whether SAM input json has "en_icing_cutoff" == 1.
"""
if self._icing is None:
self._icing = False
for v in self.inputs.values():
self._icing = any((self._icing,
bool(v.get('en_icing_cutoff', False))))
if self._icing:
logger.debug('Icing analysis active for wind gen.')
return self._icing
@property
def time_index_step(self):
"""
Step size for time_index for SAM profile output resolution
Returns
-------
int | None
Step size for time_index, used to reduce temporal resolution
"""
if self._time_index_step is None:
time_index_step = []
for v in self.inputs.values():
time_index_step.append(v.get('time_index_step', None))
self._time_index_step = list(set(time_index_step))
if len(self._time_index_step) > 1:
msg = ('Expecting a single unique value for "time_index_step" but '
'received: {}'.format(self._time_index_step))
logger.error(msg)
raise SAMInputError(msg)
return self._time_index_step[0]
@property
def downscale(self):
"""
Resolution to downscale NSRDB resource to.
Returns
-------
dict | None
Option for NSRDB resource downscaling to higher temporal
resolution. The config expects a str entry in the Pandas
frequency format, e.g. '5min' or a dict of downscaling kwargs
such as {'frequency': '5min', 'variability_kwargs':
{'var_frac': 0.05, 'distribution': 'uniform'}}.
A str entry will be converted to a kwarg dict for the output
of this property e.g. '5min' -> {'frequency': '5min'}
"""
if self._downscale is None:
ds_list = []
for v in self.inputs.values():
ds_list.append(v.get('downscale', None))
self._downscale = ds_list[0]
ds_list = list({str(x) for x in ds_list})
if len(ds_list) > 1:
msg = ('Expecting a single unique value for "downscale" but '
'received: {}'.format(ds_list))
logger.error(msg)
raise SAMInputError(msg)
if isinstance(self._downscale, str):
self._downscale = {'frequency': self._downscale}
return self._downscale
@property
def inputs(self):
"""Get the SAM input file(s) (JSON/JSON5/YAML/TOML) and return
as a dictionary.
Parameters
----------
_inputs : dict
The keys of this dictionary are the "configuration ID's".
The values are the imported json SAM input dictionaries.
"""
if self._inputs is None:
self._inputs = {}
for key, config in self.items():
# key is ID (i.e. sam_param_0) that matches project points json
# fname is the actual SAM config file name (with path)
if isinstance(config, str):
if not os.path.exists(config):
raise IOError('SAM config file does not exist: "{}"'
.format(config))
else:
config = load_config(config)
if not isinstance(config, dict):
raise RuntimeError('SAM config must be a file or a '
'pre-extracted dictionary, but got: {}'
.format(config))
SAMInputsChecker.check(config)
self._inputs[key] = config
return self._inputs