Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Bug fixes
values were returned when the sun is behind the plane of array (:issue:`1348`, :pull:`1349`)
* Fixed bug in :py:func:`pvlib.iotools.get_pvgis_hourly` where the ``optimal_surface_tilt``
argument was not being passed to the ``optimalinclination`` request parameter (:pull:`1356`)
* Fixed bug in :py:func:`pvlib.bifacial.pvfactors_timeseries` where scalar ``surface_tilt``
and ``surface_azimuth`` inputs caused an error (:issue:`1127`, :issue:`1332`, :pull:`1361`)


Testing
Expand Down
37 changes: 12 additions & 25 deletions pvlib/bifacial.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,31 +82,18 @@ def pvfactors_timeseries(
Bifacial PV and Diffuse Shade on Single-Axis Trackers." 44th IEEE
Photovoltaic Specialist Conference. 2017.
"""
# Convert pandas Series inputs (and some lists) to numpy arrays
if isinstance(solar_azimuth, pd.Series):
solar_azimuth = solar_azimuth.values
elif isinstance(solar_azimuth, list):
solar_azimuth = np.array(solar_azimuth)
if isinstance(solar_zenith, pd.Series):
solar_zenith = solar_zenith.values
elif isinstance(solar_zenith, list):
solar_zenith = np.array(solar_zenith)
if isinstance(surface_azimuth, pd.Series):
surface_azimuth = surface_azimuth.values
elif isinstance(surface_azimuth, list):
surface_azimuth = np.array(surface_azimuth)
if isinstance(surface_tilt, pd.Series):
surface_tilt = surface_tilt.values
elif isinstance(surface_tilt, list):
surface_tilt = np.array(surface_tilt)
if isinstance(dni, pd.Series):
dni = dni.values
elif isinstance(dni, list):
dni = np.array(dni)
if isinstance(dhi, pd.Series):
dhi = dhi.values
elif isinstance(dhi, list):
dhi = np.array(dhi)
# Convert Series, list, float inputs to numpy arrays
solar_azimuth = np.array(solar_azimuth)
solar_zenith = np.array(solar_zenith)
dni = np.array(dni)
dhi = np.array(dhi)
# GH 1127, GH 1332
if np.isscalar(surface_tilt):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use numpy.atleast_1d? It would avoid the if but otherwise I don't know if it's better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the requirement is that surface_tilt be an array of the same length as the meteo inputs, so np.atleast_1d would still need to be followed by a conditional np.repeat or something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

full_like docs say it requires a scalar input, but this works:

a = np.array([1,2,3])

np.full_like(a, 7)

array([7, 7, 7])

np.full_like(a, np.array([7,8,9]))

array([7, 8, 9])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work with lists and Series too. Always happy to learn a new numpy function :)

surface_tilt = [surface_tilt] * len(solar_zenith)
if np.isscalar(surface_azimuth):
surface_azimuth = [surface_azimuth] * len(solar_zenith)
surface_azimuth = np.array(surface_azimuth)
surface_tilt = np.array(surface_tilt)

# Import pvfactors functions for timeseries calculations.
from pvfactors.run import run_timeseries_engine
Expand Down
151 changes: 66 additions & 85 deletions pvlib/tests/test_bifacial.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,97 +5,78 @@
import pytest


@requires_pvfactors
def test_pvfactors_timeseries():
""" Test that pvfactors is functional, using the TLDR section inputs of the
package github repo README.md file:
https://github.com/SunPower/pvfactors/blob/master/README.md#tldr---quick-start"""

# Create some inputs
timestamps = pd.DatetimeIndex([datetime(2017, 8, 31, 11),
datetime(2017, 8, 31, 12)]
).set_names('timestamps')
solar_zenith = [20., 10.]
solar_azimuth = [110., 140.]
surface_tilt = [10., 0.]
surface_azimuth = [90., 90.]
axis_azimuth = 0.
dni = [1000., 300.]
dhi = [50., 500.]
gcr = 0.4
pvrow_height = 1.75
pvrow_width = 2.44
albedo = 0.2
n_pvrows = 3
index_observed_pvrow = 1
rho_front_pvrow = 0.03
rho_back_pvrow = 0.05
horizon_band_angle = 15.

# Expected values
expected_ipoa_front = pd.Series([1034.95474708997, 795.4423259036623],
index=timestamps,
name=('total_inc_front'))
expected_ipoa_back = pd.Series([92.12563846416197, 78.05831585685098],
index=timestamps,
name=('total_inc_back'))
@pytest.fixture
def example_values():
"""
Example values from the pvfactors github repo README file:
https://github.com/SunPower/pvfactors/blob/master/README.rst#quick-start
"""
inputs = dict(
timestamps=pd.DatetimeIndex([datetime(2017, 8, 31, 11),
datetime(2017, 8, 31, 12)]),
solar_zenith=[20., 10.],
solar_azimuth=[110., 140.],
surface_tilt=[10., 0.],
surface_azimuth=[90., 90.],
axis_azimuth=0.,
dni=[1000., 300.],
dhi=[50., 500.],
gcr=0.4,
pvrow_height=1.75,
pvrow_width=2.44,
albedo=0.2,
n_pvrows=3,
index_observed_pvrow=1,
rho_front_pvrow=0.03,
rho_back_pvrow=0.05,
horizon_band_angle=15.,
)
outputs = dict(
expected_ipoa_front=pd.Series([1034.95474708997, 795.4423259036623],
index=inputs['timestamps'],
name=('total_inc_front')),
expected_ipoa_back=pd.Series([92.12563846416197, 78.05831585685098],
index=inputs['timestamps'],
name=('total_inc_back')),
)
return inputs, outputs

# Run calculation
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(
solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
axis_azimuth,
timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
n_pvrows=n_pvrows, index_observed_pvrow=index_observed_pvrow,
rho_front_pvrow=rho_front_pvrow, rho_back_pvrow=rho_back_pvrow,
horizon_band_angle=horizon_band_angle)

assert_series_equal(ipoa_inc_front, expected_ipoa_front)
assert_series_equal(ipoa_inc_back, expected_ipoa_back)
@requires_pvfactors
def test_pvfactors_timeseries_list(example_values):
"""Test basic pvfactors functionality with list inputs"""
inputs, outputs = example_values
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(**inputs)
assert_series_equal(ipoa_inc_front, outputs['expected_ipoa_front'])
assert_series_equal(ipoa_inc_back, outputs['expected_ipoa_back'])


@requires_pvfactors
def test_pvfactors_timeseries_pandas_inputs():
""" Test that pvfactors is functional, using the TLDR section inputs of the
package github repo README.md file, but converted to pandas Series:
https://github.com/SunPower/pvfactors/blob/master/README.md#tldr---quick-start"""
def test_pvfactors_timeseries_pandas(example_values):
"""Test basic pvfactors functionality with Series inputs"""

inputs, outputs = example_values
for key in ['solar_zenith', 'solar_azimuth', 'surface_tilt',
'surface_azimuth', 'dni', 'dhi']:
inputs[key] = pd.Series(inputs[key], index=inputs['timestamps'])

# Create some inputs
timestamps = pd.DatetimeIndex([datetime(2017, 8, 31, 11),
datetime(2017, 8, 31, 12)]
).set_names('timestamps')
solar_zenith = pd.Series([20., 10.])
solar_azimuth = pd.Series([110., 140.])
surface_tilt = pd.Series([10., 0.])
surface_azimuth = pd.Series([90., 90.])
axis_azimuth = 0.
dni = pd.Series([1000., 300.])
dhi = pd.Series([50., 500.])
gcr = 0.4
pvrow_height = 1.75
pvrow_width = 2.44
albedo = 0.2
n_pvrows = 3
index_observed_pvrow = 1
rho_front_pvrow = 0.03
rho_back_pvrow = 0.05
horizon_band_angle = 15.
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(**inputs)
assert_series_equal(ipoa_inc_front, outputs['expected_ipoa_front'])
assert_series_equal(ipoa_inc_back, outputs['expected_ipoa_back'])

# Expected values
expected_ipoa_front = pd.Series([1034.95474708997, 795.4423259036623],
index=timestamps,
name=('total_inc_front'))
expected_ipoa_back = pd.Series([92.12563846416197, 78.05831585685098],
index=timestamps,
name=('total_inc_back'))

# Run calculation
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(
solar_azimuth, solar_zenith, surface_azimuth, surface_tilt,
axis_azimuth,
timestamps, dni, dhi, gcr, pvrow_height, pvrow_width, albedo,
n_pvrows=n_pvrows, index_observed_pvrow=index_observed_pvrow,
rho_front_pvrow=rho_front_pvrow, rho_back_pvrow=rho_back_pvrow,
horizon_band_angle=horizon_band_angle)
@requires_pvfactors
def test_pvfactors_scalar_orientation(example_values):
"""test that surface_tilt and surface_azimuth inputs can be scalars"""
# GH 1127, GH 1332
inputs, outputs = example_values
inputs['surface_tilt'] = 10.
inputs['surface_azimuth'] = 90.
# the second tilt is supposed to be zero, so we need to
# update the expected irradiances too:
outputs['expected_ipoa_front'].iloc[1] = 800.6524022701132
outputs['expected_ipoa_back'].iloc[1] = 81.72135884745822

assert_series_equal(ipoa_inc_front, expected_ipoa_front)
assert_series_equal(ipoa_inc_back, expected_ipoa_back)
ipoa_inc_front, ipoa_inc_back, _, _ = pvfactors_timeseries(**inputs)
assert_series_equal(ipoa_inc_front, outputs['expected_ipoa_front'])
assert_series_equal(ipoa_inc_back, outputs['expected_ipoa_back'])