From 9bcc9b7debb3b448183db76f5ff72fbe710e9030 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Fri, 29 Mar 2019 14:34:36 -0700 Subject: [PATCH 1/5] fix erbs near zenith=90 --- docs/sphinx/source/whatsnew.rst | 1 + docs/sphinx/source/whatsnew/v0.6.2.rst | 6 ++- pvlib/irradiance.py | 31 ++++++++------ pvlib/test/test_irradiance.py | 59 +++++++++++++++++++++----- 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/docs/sphinx/source/whatsnew.rst b/docs/sphinx/source/whatsnew.rst index a17ebcd2d4..02344c930c 100644 --- a/docs/sphinx/source/whatsnew.rst +++ b/docs/sphinx/source/whatsnew.rst @@ -6,6 +6,7 @@ What's New These are new features and improvements of note in each release. +.. include:: whatsnew/v0.6.2.rst .. include:: whatsnew/v0.6.1.rst .. include:: whatsnew/v0.6.0.rst .. include:: whatsnew/v0.5.2.rst diff --git a/docs/sphinx/source/whatsnew/v0.6.2.rst b/docs/sphinx/source/whatsnew/v0.6.2.rst index 27643b6842..6677af76ac 100644 --- a/docs/sphinx/source/whatsnew/v0.6.2.rst +++ b/docs/sphinx/source/whatsnew/v0.6.2.rst @@ -14,7 +14,10 @@ date will require Python 3. (:issue:`501`) API Changes ~~~~~~~~~~~ - +* `erbs` `doy` argument changed to `datetime_or_doy` to be consistent with + allowed types and similar functions (`disc`, `get_extra_radiation`). +* Added `min_cos_zenith` and `max_zenith` keyword arguments to `erbs`. + (:issue:`681`) Enhancements ~~~~~~~~~~~~ @@ -28,6 +31,7 @@ Bug fixes * pvwatts_ac raised ZeroDivisionError when called with scalar pdc=0 and a RuntimeWarning for array(0) input. Now correctly returns 0s of the appropriate type. (:issue:`675`) +* Fixed `erbs` behavior when zenith is near 90 degrees. (:issue:`681`) Testing diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index c79ce618a9..a6ef8e47ca 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -2152,7 +2152,7 @@ def _gti_dirint_gte_90_kt_prime(aoi, solar_zenith, solar_azimuth, times, return kt_prime_gte_90 -def erbs(ghi, zenith, doy): +def erbs(ghi, zenith, datetime_or_doy, min_cos_zenith=0.065, max_zenith=87): r""" Estimate DNI and DHI from GHI using the Erbs model. @@ -2179,8 +2179,15 @@ def erbs(ghi, zenith, doy): Global horizontal irradiance in W/m^2. zenith: numeric True (not refraction-corrected) zenith angles in decimal degrees. - doy: scalar, array or DatetimeIndex - The day of the year. + datetime_or_doy : int, float, array, pd.DatetimeIndex + Day of year or array of days of year e.g. + pd.DatetimeIndex.dayofyear, or pd.DatetimeIndex. + min_cos_zenith : numeric, default 0.065 + Minimum value of cos(zenith) to allow when calculating global + clearness index `kt`. Equivalent to zenith = 86.273 degrees. + max_zenith : numeric, default 87 + Maximum value of zenith to allow in DNI calculation. DNI will be + set to 0 for times with zenith values greater than `max_zenith`. Returns ------- @@ -2205,14 +2212,10 @@ def erbs(ghi, zenith, doy): disc """ - dni_extra = get_extra_radiation(doy) + dni_extra = get_extra_radiation(datetime_or_doy) - # This Z needs to be the true Zenith angle, not apparent, - # to get extraterrestrial horizontal radiation) - i0_h = dni_extra * tools.cosd(zenith) - - kt = ghi / i0_h - kt = np.maximum(kt, 0) + kt = clearness_index(ghi, zenith, dni_extra, min_cos_zenith=min_cos_zenith, + max_clearness_index=1) # For Kt <= 0.22, set the diffuse fraction df = 1 - 0.09*kt @@ -2229,14 +2232,18 @@ def erbs(ghi, zenith, doy): dhi = df * ghi dni = (ghi - dhi) / tools.cosd(zenith) + bad_values = (zenith > max_zenith) | (ghi < 0) | (dni < 0) + dni = np.where(bad_values, 0, dni) + # ensure that closure relationship remains valid + dhi = np.where(bad_values, ghi, dhi) data = OrderedDict() data['dni'] = dni data['dhi'] = dhi data['kt'] = kt - if isinstance(dni, pd.Series): - data = pd.DataFrame(data) + if isinstance(datetime_or_doy, pd.DatetimeIndex): + data = pd.DataFrame(data, index=datetime_or_doy) return data diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index ef6c2469ba..d4cd7271c0 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -620,21 +620,60 @@ def test_gti_dirint(): def test_erbs(): - ghi = pd.Series([0, 50, 1000, 1000]) - zenith = pd.Series([120, 85, 10, 10]) - doy = pd.Series([1, 1, 1, 180]) - expected = pd.DataFrame(np. - array([[ -0.00000000e+00, 0.00000000e+00, -0.00000000e+00], - [ 9.67127061e+01, 4.15709323e+01, 4.05715990e-01], - [ 7.94187742e+02, 2.17877755e+02, 7.18119416e-01], - [ 8.42358014e+02, 1.70439297e+02, 7.68919470e-01]]), - columns=['dni', 'dhi', 'kt']) + index = pd.DatetimeIndex(['20190101']*3 + ['20190620']) + ghi = pd.Series([0, 50, 1000, 1000], index=index) + zenith = pd.Series([120, 85, 10, 10], index=index) + expected = pd.DataFrame(np.array( + [[0.00000000e+00, 0.00000000e+00, 0.00000000e+00], + [9.67192672e+01, 4.15703604e+01, 4.05723511e-01], + [7.94205651e+02, 2.17860117e+02, 7.18132729e-01], + [8.42001578e+02, 1.70790318e+02, 7.68214312e-01]]), + columns=['dni', 'dhi', 'kt'], index=index) - out = irradiance.erbs(ghi, zenith, doy) + out = irradiance.erbs(ghi, zenith, index) assert_frame_equal(np.round(out, 0), np.round(expected, 0)) +def test_erbs_min_cos_zenith_max_zenith(): + # map out behavior under difficult conditions with various + # limiting kwargs settings + columns = ['dni', 'dhi', 'kt'] + times = pd.DatetimeIndex(['2016-07-19 06:11:00'], tz='America/Phoenix') + + # max_zenith keeps these results reasonable + out = irradiance.erbs(ghi=1.0, solar_zenith=89.99999, + datetime_or_doy=times, min_cos_zenith=0) + expected = pd.DataFrame(np.array( + [[0., 1., 1.]]), + columns=columns, index=times) + assert_frame_equal(out, expected) + + # 4-5 9s will produce bad behavior without max_zenith limit + out = irradiance.erbs(ghi=1.0, solar_zenith=89.99999, + datetime_or_doy=times, max_zenith=100) + expected = pd.DataFrame(np.array( + [[6.00115286e+03, 9.98952601e-01, 1.16377640e-02]]), + columns=columns, index=times) + assert_frame_equal(out, expected) + + # 1-2 9s will produce bad behavior without either limit + out = irradiance.erbs(ghi=1.0, solar_zenith=89.99, datetime_or_doy=times, + min_cos_zenith=0, max_zenith=100) + expected = pd.DataFrame(np.array( + [[4.78419761e+03, 1.65000000e-01, 1.00000000e+00]]), + columns=columns, index=times) + assert_frame_equal(out, expected) + + # check default behavior under hardest condition + out = irradiance.erbs(ghi=1.0, solar_zenith=90, + datetime_or_doy=times) + expected = pd.DataFrame(np.array( + [[0., 1., 0.01163776]]), + columns=columns, index=times) + assert_frame_equal(out, expected) + + def test_erbs_all_scalar(): ghi = 1000 zenith = 10 From 1baefd93bb1ab79bb0552f0653951bc065b97314 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Fri, 29 Mar 2019 14:37:43 -0700 Subject: [PATCH 2/5] document return type change --- docs/sphinx/source/whatsnew/v0.6.2.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.6.2.rst b/docs/sphinx/source/whatsnew/v0.6.2.rst index 6677af76ac..51b2cab36e 100644 --- a/docs/sphinx/source/whatsnew/v0.6.2.rst +++ b/docs/sphinx/source/whatsnew/v0.6.2.rst @@ -16,6 +16,8 @@ API Changes ~~~~~~~~~~~ * `erbs` `doy` argument changed to `datetime_or_doy` to be consistent with allowed types and similar functions (`disc`, `get_extra_radiation`). +* `erbs` DataFrame vs. OrderedDict return behavior now determined by type + of `datetime_or_doy` instead of `ghi` or `zenith`. * Added `min_cos_zenith` and `max_zenith` keyword arguments to `erbs`. (:issue:`681`) From c347ba2a0a7255ecef08947e5dde820e7059f77b Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Fri, 29 Mar 2019 14:53:37 -0700 Subject: [PATCH 3/5] fix copy/paste in test --- pvlib/test/test_irradiance.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pvlib/test/test_irradiance.py b/pvlib/test/test_irradiance.py index d4cd7271c0..a2799371c6 100644 --- a/pvlib/test/test_irradiance.py +++ b/pvlib/test/test_irradiance.py @@ -642,7 +642,7 @@ def test_erbs_min_cos_zenith_max_zenith(): times = pd.DatetimeIndex(['2016-07-19 06:11:00'], tz='America/Phoenix') # max_zenith keeps these results reasonable - out = irradiance.erbs(ghi=1.0, solar_zenith=89.99999, + out = irradiance.erbs(ghi=1.0, zenith=89.99999, datetime_or_doy=times, min_cos_zenith=0) expected = pd.DataFrame(np.array( [[0., 1., 1.]]), @@ -650,7 +650,7 @@ def test_erbs_min_cos_zenith_max_zenith(): assert_frame_equal(out, expected) # 4-5 9s will produce bad behavior without max_zenith limit - out = irradiance.erbs(ghi=1.0, solar_zenith=89.99999, + out = irradiance.erbs(ghi=1.0, zenith=89.99999, datetime_or_doy=times, max_zenith=100) expected = pd.DataFrame(np.array( [[6.00115286e+03, 9.98952601e-01, 1.16377640e-02]]), @@ -658,7 +658,7 @@ def test_erbs_min_cos_zenith_max_zenith(): assert_frame_equal(out, expected) # 1-2 9s will produce bad behavior without either limit - out = irradiance.erbs(ghi=1.0, solar_zenith=89.99, datetime_or_doy=times, + out = irradiance.erbs(ghi=1.0, zenith=89.99, datetime_or_doy=times, min_cos_zenith=0, max_zenith=100) expected = pd.DataFrame(np.array( [[4.78419761e+03, 1.65000000e-01, 1.00000000e+00]]), @@ -666,8 +666,7 @@ def test_erbs_min_cos_zenith_max_zenith(): assert_frame_equal(out, expected) # check default behavior under hardest condition - out = irradiance.erbs(ghi=1.0, solar_zenith=90, - datetime_or_doy=times) + out = irradiance.erbs(ghi=1.0, zenith=90, datetime_or_doy=times) expected = pd.DataFrame(np.array( [[0., 1., 0.01163776]]), columns=columns, index=times) From f3779bc3940051a5b5bf96f3bcc26df370754187 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 4 Apr 2019 12:53:25 -0700 Subject: [PATCH 4/5] fix docs --- docs/sphinx/source/whatsnew/v0.6.2.rst | 31 +++++++++++++++----------- pvlib/iotools/epw.py | 3 +-- pvlib/test/test_clearsky.py | 27 ++++++++++++++++------ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.2.rst b/docs/sphinx/source/whatsnew/v0.6.2.rst index 51b2cab36e..281039eb1c 100644 --- a/docs/sphinx/source/whatsnew/v0.6.2.rst +++ b/docs/sphinx/source/whatsnew/v0.6.2.rst @@ -14,26 +14,31 @@ date will require Python 3. (:issue:`501`) API Changes ~~~~~~~~~~~ -* `erbs` `doy` argument changed to `datetime_or_doy` to be consistent with - allowed types and similar functions (`disc`, `get_extra_radiation`). -* `erbs` DataFrame vs. OrderedDict return behavior now determined by type - of `datetime_or_doy` instead of `ghi` or `zenith`. -* Added `min_cos_zenith` and `max_zenith` keyword arguments to `erbs`. - (:issue:`681`) +* :py:func:`~pvlib.irradiance.erbs` *doy* argument changed to + *datetime_or_doy* to be consistent with allowed types and similar + functions (:py:func:`~pvlib.irradiance.disc`, + :py:func:`~pvlib.irradiance.get_extra_radiation`). +* :py:func:`~pvlib.irradiance.erbs` DataFrame vs. OrderedDict return + behavior now determined by type of *datetime_or_doy* instead of + *ghi* or *zenith*. +* Added *min_cos_zenith* and *max_zenith* keyword arguments to + :py:func:`~pvlib.irradiance.erbs`. (:issue:`681`) Enhancements ~~~~~~~~~~~~ -* Add US CRN data reader to `pvlib.iotools`. -* Add SOLRAD data reader to `pvlib.iotools`. -* Add EPW data reader to `pvlib.iotools`. (:issue:`591`) +* Add US CRN data reader to :ref:`iotools`. +* Add SOLRAD data reader to :ref:`iotools`. +* Add EPW data reader to :ref:`iotools`. (:issue:`591`) Bug fixes ~~~~~~~~~ * Compatibility with pandas 0.24 deprecations. (:issue:`659`) -* pvwatts_ac raised ZeroDivisionError when called with scalar pdc=0 - and a RuntimeWarning for array(0) input. Now correctly returns 0s - of the appropriate type. (:issue:`675`) -* Fixed `erbs` behavior when zenith is near 90 degrees. (:issue:`681`) +* :py:func:`~pvlib.pvsystem.pvwatts_ac` raised ``ZeroDivisionError`` + when called with scalar ``pdc=0`` + and a ``RuntimeWarning`` for ``array(0)`` input. Now correctly returns + 0s of the appropriate type. (:issue:`675`) +* Fixed :py:func:`~pvlib.irradiance.erbs` behavior when zenith is + near 90 degrees. (:issue:`681`) Testing diff --git a/pvlib/iotools/epw.py b/pvlib/iotools/epw.py index 19ca139c87..6b6c5ddd2d 100644 --- a/pvlib/iotools/epw.py +++ b/pvlib/iotools/epw.py @@ -70,8 +70,7 @@ def read_epw(filename, coerce_year=None): longitude Float site longitude TZ Float UTC offset altitude Float site elevation - =============== ====== ======================================== - + =============== ====== ========================================= ============================= ============================================================================================================================================================== diff --git a/pvlib/test/test_clearsky.py b/pvlib/test/test_clearsky.py index ec82587930..70ae88c141 100644 --- a/pvlib/test/test_clearsky.py +++ b/pvlib/test/test_clearsky.py @@ -191,15 +191,28 @@ def test_ineichen_altitude(): @requires_tables -def test_lookup_linke_turbidity(): +@pytest.mark.parametrize('lat,lon,interp_turbidity', [ + (32.125, -110.875, True), + (32.125, -110.875, False), + (np.array(32.125), np.array(-110.875), True), + (np.array(32.125), np.array(-110.875), False), + pytest.param(np.array([32.125]), np.array([-110.875]), True, + marks=pytest.mark.xfail, strict=True), + pytest.param(np.array([32.125]), np.array([-110.875]), False, + marks=pytest.mark.xfail, strict=True), +]) +def test_lookup_linke_turbidity(lat, lon, interp_turbidity): times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='12h', tz='America/Phoenix') - # expect same value on 2014-06-24 0000 and 1200, and - # diff value on 2014-06-25 - expected = pd.Series( - np.array([3.11803278689, 3.11803278689, 3.13114754098]), index=times - ) - out = clearsky.lookup_linke_turbidity(times, 32.125, -110.875) + if interp_turbidity: + # expect same value on 2014-06-24 0000 and 1200, and + # diff value on 2014-06-25 + expected = [3.11803278689, 3.11803278689, 3.13114754098] + else: + expected = [3., 3., 3.] + expected = pd.Series(expected, index=times) + out = clearsky.lookup_linke_turbidity(times, lat, lon, + interp_turbidity=interp_turbidity) assert_series_equal(expected, out) From 830603ce26f5dce0e37b2497397b0480df2a51c6 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 4 Apr 2019 14:35:41 -0700 Subject: [PATCH 5/5] revert accidental changes to test_clearsky --- pvlib/test/test_clearsky.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/pvlib/test/test_clearsky.py b/pvlib/test/test_clearsky.py index 70ae88c141..ec82587930 100644 --- a/pvlib/test/test_clearsky.py +++ b/pvlib/test/test_clearsky.py @@ -191,28 +191,15 @@ def test_ineichen_altitude(): @requires_tables -@pytest.mark.parametrize('lat,lon,interp_turbidity', [ - (32.125, -110.875, True), - (32.125, -110.875, False), - (np.array(32.125), np.array(-110.875), True), - (np.array(32.125), np.array(-110.875), False), - pytest.param(np.array([32.125]), np.array([-110.875]), True, - marks=pytest.mark.xfail, strict=True), - pytest.param(np.array([32.125]), np.array([-110.875]), False, - marks=pytest.mark.xfail, strict=True), -]) -def test_lookup_linke_turbidity(lat, lon, interp_turbidity): +def test_lookup_linke_turbidity(): times = pd.date_range(start='2014-06-24', end='2014-06-25', freq='12h', tz='America/Phoenix') - if interp_turbidity: - # expect same value on 2014-06-24 0000 and 1200, and - # diff value on 2014-06-25 - expected = [3.11803278689, 3.11803278689, 3.13114754098] - else: - expected = [3., 3., 3.] - expected = pd.Series(expected, index=times) - out = clearsky.lookup_linke_turbidity(times, lat, lon, - interp_turbidity=interp_turbidity) + # expect same value on 2014-06-24 0000 and 1200, and + # diff value on 2014-06-25 + expected = pd.Series( + np.array([3.11803278689, 3.11803278689, 3.13114754098]), index=times + ) + out = clearsky.lookup_linke_turbidity(times, 32.125, -110.875) assert_series_equal(expected, out)