From 806947023b58f60909d47377baaef106049fd473 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 9 Mar 2021 18:01:36 -0700 Subject: [PATCH 1/9] clip to [-1, 1] --- pvlib/irradiance.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 3ec6b213f9..c0aebcb7bb 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -182,6 +182,9 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): tools.sind(surface_tilt) * tools.sind(solar_zenith) * tools.cosd(solar_azimuth - surface_azimuth)) + # GH 1185 + projection = np.clip(projection, -1, 1) + try: projection.name = 'aoi_projection' except AttributeError: From e0e7d9920fc2c115043310fb08eefa04f6a3b82d Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 9 Mar 2021 18:01:46 -0700 Subject: [PATCH 2/9] add test --- pvlib/tests/test_irradiance.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index ba5821f750..22253c8a6a 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -792,6 +792,14 @@ def test_aoi_and_aoi_projection(surface_tilt, surface_azimuth, solar_zenith, assert_allclose(aoi_projection, aoi_proj_expected, atol=1e-6) +def test_aoi_projection_precision(): + # GH 1185 + zenith = 89.26778228223463 + azimuth = 60.932028605997004 + projection = irradiance.aoi_projection(zenith, azimuth, zenith, azimuth) + assert projection == 1 + + @pytest.fixture def airmass_kt(): # disc algorithm stopped at am=12. test am > 12 for out of range behavior From 2a0425b786e22ecc25ebc6c260e5945812640925 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 9 Mar 2021 18:01:54 -0700 Subject: [PATCH 3/9] whatsnew --- docs/sphinx/source/whatsnew/v0.9.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.9.0.rst b/docs/sphinx/source/whatsnew/v0.9.0.rst index 22c1a3ba3d..42d3f2873f 100644 --- a/docs/sphinx/source/whatsnew/v0.9.0.rst +++ b/docs/sphinx/source/whatsnew/v0.9.0.rst @@ -109,6 +109,8 @@ Bug fixes (:issue:`1065`, :pull:`1140`) * Reindl model fixed to generate sky_diffuse=0 when GHI=0. (:issue:`1153`, :pull:`1154`) +* Fix floating point round-off issue in + :py:func:`~pvlib.irradiance.aoi_projection` (:issue:`1185`, :pull:`1191`) Testing ~~~~~~~ From 3ee6418b0ccdc42baa6c5e92a6cf9eef3a72d093 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sat, 13 Mar 2021 14:17:23 -0700 Subject: [PATCH 4/9] use new aoi formula --- pvlib/irradiance.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index c0aebcb7bb..a873ec8c48 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -193,6 +193,13 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): return projection +def _spherical_to_cartesian(zenith, azimuth): + sin_zen = tools.sind(zenith) + return np.array([sin_zen*tools.cosd(azimuth), + sin_zen*tools.sind(azimuth), + tools.cosd(zenith)]).T + + def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): """ Calculates the angle of incidence of the solar vector on a surface. @@ -200,6 +207,10 @@ def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): Input all angles in degrees. + .. versionchanged:: 0.9.0 + Updated to a slower but more reliable formula that correctly handles + cases where the surface normal and solar position vectors are parallel. + Parameters ---------- surface_tilt : numeric @@ -216,15 +227,21 @@ def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): aoi : numeric Angle of incidence in degrees. """ + # see discussion in https://github.com/pvlib/pvlib-python/issues/1185 + solar_vec = _spherical_to_cartesian(solar_zenith, solar_azimuth) + surface_vec = _spherical_to_cartesian(surface_tilt, surface_azimuth) - projection = aoi_projection(surface_tilt, surface_azimuth, - solar_zenith, solar_azimuth) - aoi_value = np.rad2deg(np.arccos(projection)) + c_vec = solar_vec - surface_vec + c_sqrd = np.sum((c_vec*c_vec).T, axis=0) + c = np.sqrt(c_sqrd) - try: - aoi_value.name = 'aoi' - except AttributeError: - pass + aoi = 2 * np.arctan(c / np.sqrt((1+(1+c))*((1-c)+1))) + aoi_value = np.rad2deg(aoi) + + for arg in [surface_tilt, surface_azimuth, solar_zenith, solar_azimuth]: + if hasattr(arg, 'index'): + aoi_value = pd.Series(aoi_value, index=arg.index) + aoi_value.name = 'aoi' return aoi_value From 8fb5070a7a7964f3262bec690711f1c2608be673 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sat, 13 Mar 2021 14:17:42 -0700 Subject: [PATCH 5/9] revert changes to aoi_projection --- pvlib/irradiance.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index a873ec8c48..13d301c279 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -160,6 +160,12 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): Input all angles in degrees. + .. warning:: + + For certain inputs, numerical round-off can cause this function + to produce values slightly greater than 1.0 when the surface + normal and sun position vectors are parallel. + Parameters ---------- surface_tilt : numeric @@ -182,9 +188,6 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): tools.sind(surface_tilt) * tools.sind(solar_zenith) * tools.cosd(solar_azimuth - surface_azimuth)) - # GH 1185 - projection = np.clip(projection, -1, 1) - try: projection.name = 'aoi_projection' except AttributeError: From 30cb9f2e698430c13988503dea249a8c4b26ac15 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Sat, 13 Mar 2021 14:17:55 -0700 Subject: [PATCH 6/9] update test and whatsnew --- docs/sphinx/source/whatsnew/v0.9.0.rst | 2 +- pvlib/tests/test_irradiance.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.0.rst b/docs/sphinx/source/whatsnew/v0.9.0.rst index 42d3f2873f..c2aefb7d0e 100644 --- a/docs/sphinx/source/whatsnew/v0.9.0.rst +++ b/docs/sphinx/source/whatsnew/v0.9.0.rst @@ -110,7 +110,7 @@ Bug fixes * Reindl model fixed to generate sky_diffuse=0 when GHI=0. (:issue:`1153`, :pull:`1154`) * Fix floating point round-off issue in - :py:func:`~pvlib.irradiance.aoi_projection` (:issue:`1185`, :pull:`1191`) + :py:func:`~pvlib.irradiance.aoi` (:issue:`1185`, :pull:`1191`) Testing ~~~~~~~ diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 22253c8a6a..a36dd47e7c 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -792,12 +792,12 @@ def test_aoi_and_aoi_projection(surface_tilt, surface_azimuth, solar_zenith, assert_allclose(aoi_projection, aoi_proj_expected, atol=1e-6) -def test_aoi_projection_precision(): +def test_aoi_precision(): # GH 1185 zenith = 89.26778228223463 azimuth = 60.932028605997004 - projection = irradiance.aoi_projection(zenith, azimuth, zenith, azimuth) - assert projection == 1 + aoi = irradiance.aoi(zenith, azimuth, zenith, azimuth) + assert aoi == 0 @pytest.fixture From 671cf1649ae8a123e2c640fa2cddbae728b3a482 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 16 Mar 2021 20:55:57 -0600 Subject: [PATCH 7/9] don't set name; break after first Series --- pvlib/irradiance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 13d301c279..90d24ac970 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -244,7 +244,7 @@ def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): for arg in [surface_tilt, surface_azimuth, solar_zenith, solar_azimuth]: if hasattr(arg, 'index'): aoi_value = pd.Series(aoi_value, index=arg.index) - aoi_value.name = 'aoi' + break return aoi_value From c2e6471bb15ad292ad70a3f5682deb2a8a8de894 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 14 May 2021 09:06:41 -0600 Subject: [PATCH 8/9] roll back to 2a0425b --- docs/sphinx/source/whatsnew/v0.9.0.rst | 2 +- pvlib/irradiance.py | 40 +++++++------------------- pvlib/tests/test_irradiance.py | 6 ++-- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.0.rst b/docs/sphinx/source/whatsnew/v0.9.0.rst index c2aefb7d0e..42d3f2873f 100644 --- a/docs/sphinx/source/whatsnew/v0.9.0.rst +++ b/docs/sphinx/source/whatsnew/v0.9.0.rst @@ -110,7 +110,7 @@ Bug fixes * Reindl model fixed to generate sky_diffuse=0 when GHI=0. (:issue:`1153`, :pull:`1154`) * Fix floating point round-off issue in - :py:func:`~pvlib.irradiance.aoi` (:issue:`1185`, :pull:`1191`) + :py:func:`~pvlib.irradiance.aoi_projection` (:issue:`1185`, :pull:`1191`) Testing ~~~~~~~ diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 90d24ac970..c0aebcb7bb 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -160,12 +160,6 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): Input all angles in degrees. - .. warning:: - - For certain inputs, numerical round-off can cause this function - to produce values slightly greater than 1.0 when the surface - normal and sun position vectors are parallel. - Parameters ---------- surface_tilt : numeric @@ -188,6 +182,9 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): tools.sind(surface_tilt) * tools.sind(solar_zenith) * tools.cosd(solar_azimuth - surface_azimuth)) + # GH 1185 + projection = np.clip(projection, -1, 1) + try: projection.name = 'aoi_projection' except AttributeError: @@ -196,13 +193,6 @@ def aoi_projection(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): return projection -def _spherical_to_cartesian(zenith, azimuth): - sin_zen = tools.sind(zenith) - return np.array([sin_zen*tools.cosd(azimuth), - sin_zen*tools.sind(azimuth), - tools.cosd(zenith)]).T - - def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): """ Calculates the angle of incidence of the solar vector on a surface. @@ -210,10 +200,6 @@ def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): Input all angles in degrees. - .. versionchanged:: 0.9.0 - Updated to a slower but more reliable formula that correctly handles - cases where the surface normal and solar position vectors are parallel. - Parameters ---------- surface_tilt : numeric @@ -230,21 +216,15 @@ def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth): aoi : numeric Angle of incidence in degrees. """ - # see discussion in https://github.com/pvlib/pvlib-python/issues/1185 - solar_vec = _spherical_to_cartesian(solar_zenith, solar_azimuth) - surface_vec = _spherical_to_cartesian(surface_tilt, surface_azimuth) - c_vec = solar_vec - surface_vec - c_sqrd = np.sum((c_vec*c_vec).T, axis=0) - c = np.sqrt(c_sqrd) - - aoi = 2 * np.arctan(c / np.sqrt((1+(1+c))*((1-c)+1))) - aoi_value = np.rad2deg(aoi) + projection = aoi_projection(surface_tilt, surface_azimuth, + solar_zenith, solar_azimuth) + aoi_value = np.rad2deg(np.arccos(projection)) - for arg in [surface_tilt, surface_azimuth, solar_zenith, solar_azimuth]: - if hasattr(arg, 'index'): - aoi_value = pd.Series(aoi_value, index=arg.index) - break + try: + aoi_value.name = 'aoi' + except AttributeError: + pass return aoi_value diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index a36dd47e7c..22253c8a6a 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -792,12 +792,12 @@ def test_aoi_and_aoi_projection(surface_tilt, surface_azimuth, solar_zenith, assert_allclose(aoi_projection, aoi_proj_expected, atol=1e-6) -def test_aoi_precision(): +def test_aoi_projection_precision(): # GH 1185 zenith = 89.26778228223463 azimuth = 60.932028605997004 - aoi = irradiance.aoi(zenith, azimuth, zenith, azimuth) - assert aoi == 0 + projection = irradiance.aoi_projection(zenith, azimuth, zenith, azimuth) + assert projection == 1 @pytest.fixture From ab7b57d81ee6a1d69eff6b154ce2b219aaac6f00 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 14 May 2021 09:49:06 -0600 Subject: [PATCH 9/9] test improvements --- pvlib/tests/test_irradiance.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 031ac92333..9ab28d83ea 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -793,11 +793,24 @@ def test_aoi_and_aoi_projection(surface_tilt, surface_azimuth, solar_zenith, def test_aoi_projection_precision(): - # GH 1185 + # GH 1185 -- test that aoi_projection does not exceed 1.0, and when + # given identical inputs, the returned projection is very close to 1.0 + + # scalars zenith = 89.26778228223463 azimuth = 60.932028605997004 projection = irradiance.aoi_projection(zenith, azimuth, zenith, azimuth) - assert projection == 1 + assert projection <= 1 + assert np.isclose(projection, 1) + + # arrays + zeniths = np.array([zenith]) + azimuths = np.array([azimuth]) + projections = irradiance.aoi_projection(zeniths, azimuths, + zeniths, azimuths) + assert all(projections <= 1) + assert all(np.isclose(projections, 1)) + assert projections.dtype == np.dtype('float64') @pytest.fixture