Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/h5pyd_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ jobs:
shell: bash
run: |
python -m pip install --upgrade pip
pip install pytest
pip install pytest-cov
pip install -e .[hsds]
pip install -e .[test,hsds]

- name: Start HSDS and run tests
shell: bash
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ test = [
"pytest>=8.3.5,<9",
"pytest-timeout>=2.3.1,<3",
"flaky>=3.8.1,<4",
"NREL-PySAM>=7.0.0",
]
dev = [
"flake8",
Expand Down
11 changes: 7 additions & 4 deletions rex/renewable_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,8 @@ def get_SAM_df(self, site, height, require_wind_dir=False, icing=False,
variables.append('relativehumidity_2m')

for var in variables:
var_name = "{}_{}m".format(var, height)
var_name = ("{}_{}m".format(var, height)
if var != 'relativehumidity_2m' else var)
ds_slice = (slice(None), site)
var_array = self._get_ds(var_name, ds_slice)
var_array = SAMResource.roll_timeseries(var_array, time_zone,
Expand All @@ -1371,9 +1372,11 @@ def get_SAM_df(self, site, height, require_wind_dir=False, icing=False,
var, res_df[var],
SAMResource.WIND_DATA_RANGES[var], [site])

col_map = {'pressure': 'Pressure', 'temperature': 'Temperature',
'windspeed': 'Speed', 'winddirection': 'Direction',
'relativehumidity_2m': 'Relative Humidity'}
col_map = {'pressure': f'Pressure {height}m',
'temperature': f'Temperature {height}m',
'windspeed': f'Wind Speed {height}m',
'winddirection': f'Wind Direction {height}m',
'relativehumidity_2m': 'Relative Humidity 2m'}
res_df = res_df.rename(columns=col_map)
res_df.name = "SAM_{}m-{}".format(height, site)

Expand Down
43 changes: 35 additions & 8 deletions rex/resource_extraction/resource_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,7 @@ def _get_ds_slice(dset, gids, ds_ndim):

return ds_slice

@staticmethod
def _to_SAM_csv(sam_df, site_meta, out_path, write_time=True):
def _to_SAM_csv(self, sam_df, site_meta, out_path, write_time=True):
"""
Save SAM dataframe to disk and add meta data to header to make
SAM compliant
Expand Down Expand Up @@ -489,6 +488,10 @@ def _to_SAM_csv(sam_df, site_meta, out_path, write_time=True):
cols = [c for c in sam_df if c.lower() not in time_cols]
sam_df[cols].to_csv(out_path, index=False)

self._add_sam_csv_header(out_path, site_meta)

def _add_sam_csv_header(self, out_path, site_meta):
"""Add site meta info header to CSV file"""
if 'gid' not in site_meta:
site_meta.index.name = 'gid'
site_meta = site_meta.reset_index()
Expand Down Expand Up @@ -1749,18 +1752,42 @@ def get_SAM_gid(self, gid, hub_height, out_path=None, write_time=True,
If multiple lat, lon pairs are given a list of DatFrames is
returned
"""
kwargs['height'] = hub_height
if out_path is not None:
write_time = False
kwargs.update({'add_header': True})

# SAM CSV requires wind direction, so leave it on by default but
# allow users to switch off explicitly
get_sam_df_kwargs = {"require_wind_dir": True, "height": hub_height}
get_sam_df_kwargs.update(kwargs)
SAM_df = super().get_SAM_gid(gid, out_path=out_path,
write_time=write_time,
extra_meta_data=extra_meta_data,
**kwargs)
**get_sam_df_kwargs)

return SAM_df

def _add_sam_csv_header(self, out_path, site_meta):
"""Add site meta info header (like WTK) to CSV file"""
if 'gid' not in site_meta:
site_meta.index.name = 'gid'
site_meta = site_meta.reset_index()

col_map = {}
for c in site_meta.columns:
if c.lower() == 'timezone':
col_map[c] = 'Time Zone'
elif c.lower() == 'gid':
col_map[c] = 'Location ID'
elif c.islower():
col_map[c] = c.capitalize()

site_meta = site_meta.rename(columns=col_map)
meta_line = ','.join(
f"{col},{value}" for col, value in site_meta.iloc[0].items()
)

with open(out_path, 'r+') as f:
content = f.read()
f.seek(0, 0)
f.write(meta_line + '\n' + content)

def get_SAM_lat_lon(self, lat_lon, hub_height, check_lat_lon=True,
out_path=None, **kwargs):
"""
Expand Down
3 changes: 2 additions & 1 deletion rex/sam_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ class SAMResource:
'winddirection': (0, 360),
'pressure': (0.5, 1.099),
'temperature': (-200, 100),
'rh': (0.1, 99.9)}
'rh': (0.1, 99.9),
'relativehumidity_2m': (0.1, 99.9)}

# prevent negative wave data; some negative periods are observed on the
# west coast along the shore. These are small wave areas and should be fine
Expand Down
48 changes: 48 additions & 0 deletions tests/test_resource_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import pytest
from click.testing import CliRunner
from pandas.testing import assert_frame_equal
import PySAM.Windpower as PySamWindPower
import PySAM.Pvwattsv8 as PySamPV8

from rex import TESTDATADIR
from rex.resource_extraction.resource_extraction import (
NSRDBX,
Expand Down Expand Up @@ -895,6 +898,33 @@ def test_windx_make_SAM_files(WindX_cls):
LOGGERS.clear()


def test_windx_run_SAM_files():
"""
Test running WindX files through SAM
"""

h5_path = os.path.join(TESTDATADIR, 'wtk/ri_100_wtk_2012.h5')
with tempfile.TemporaryDirectory() as td:
out_path = os.path.join(td, 'truth.csv')
WindX.make_SAM_files(h5_path, 0, hub_height=80, out_path=out_path)

obj = PySamWindPower.default('WindPowerNone')
obj.Resource.wind_resource_filename = out_path
obj.Resource.wind_resource_model_choice = 0
obj.execute()
energy_no_icing = obj.Outputs.annual_energy
assert energy_no_icing > 1e8

obj.Losses.env_icing_loss = 1
obj.Losses.icing_cutoff_rh = 80
obj.Losses.icing_cutoff_temp = 10
obj.execute()
assert obj.Outputs.annual_energy < energy_no_icing


LOGGERS.clear()


def test_nsrdbx_make_SAM_files(NSRDBX_cls):
"""
Test nsrdbx make_SAM_files method
Expand All @@ -921,6 +951,24 @@ def test_nsrdbx_make_SAM_files(NSRDBX_cls):
LOGGERS.clear()


def test_nsrdbx_run_SAM_files():
"""
Test running nsrdbx files through SAM
"""

h5_path = os.path.join(TESTDATADIR, 'nsrdb/ri_100_nsrdb_2012.h5')
with tempfile.TemporaryDirectory() as td:
out_path = os.path.join(td, 'truth.csv')
NSRDBX.make_SAM_files(h5_path, 0, out_path=out_path)

obj = PySamPV8.default('PVWattsNone')
obj.SolarResource.solar_resource_file = out_path
obj.execute()
assert obj.Outputs.annual_energy > 1.3e8

LOGGERS.clear()


def test_cli_region(runner, WindX_cls):
"""
Test rex CLI region get
Expand Down
2 changes: 1 addition & 1 deletion tests/test_resource_hh.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def test_sam_df_hh():

arr1 = wind['pressure_100m', :, 0] * 9.86923e-6
arr1 = SAMResource.roll_timeseries(arr1, -5, 1)
arr2 = sam_df['Pressure'].values
arr2 = sam_df['Pressure 100m'].values

msg1 = ('Error: pressure should have been loaded at 100m '
'b/c there is only windspeed at 100m.')
Expand Down
6 changes: 3 additions & 3 deletions tests/test_resource_invalid.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_min_pressure():
with WindResource(h5) as wind:
og_min = np.min(wind['pressure_100m']) * 9.86923e-6
sam_df = wind.get_SAM_df(site, 100)
patched_min = np.min(sam_df['Pressure'].values)
patched_min = np.min(sam_df['Pressure 100m'].values)

msg1 = 'Not a good test set. Min pressure is {}'.format(og_min)
msg2 = ('Physical range enforcement failed. '
Expand All @@ -43,7 +43,7 @@ def test_min_temp():
with WindResource(h5) as wind:
og_min = np.min(wind['temperature_100m'])
sam_df = wind.get_SAM_df(site, 100)
patched_min = np.min(sam_df['Temperature'].values)
patched_min = np.min(sam_df['Temperature 100m'].values)

msg1 = 'Not a good test set. Min temp is {}'.format(og_min)
msg2 = ('Physical range enforcement failed. '
Expand All @@ -65,7 +65,7 @@ def test_max_ws():
with WindResource(h5) as wind:
og_max = np.max(wind['windspeed_100m'])
sam_df = wind.get_SAM_df(site, 100)
patched_max = np.max(sam_df['Speed'].values)
patched_max = np.max(sam_df['Wind Speed 100m'].values)

msg1 = 'Not a good test set. Min wind speed is {}'.format(og_max)
msg2 = ('Physical range enforcement failed. '
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sam_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_roll():
if 'Minute' in sam_df:
mask &= sam_df['Minute'] == time_index.minute

assert np.isclose(sam_df.loc[mask, 'Speed'], wspd)
assert np.isclose(sam_df.loc[mask, 'Wind Speed 100m'], wspd)


def test_roll_timeseries():
Expand Down