From 94e4db244f1e7e94fa20164dcc77694d38641a5c Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 2 May 2019 16:10:28 -0700 Subject: [PATCH 1/2] better nan handling in singleaxis --- docs/sphinx/source/whatsnew/v0.6.2.rst | 9 +++- pvlib/test/test_tracking.py | 60 +++++++++++++++----------- pvlib/tracking.py | 14 +++--- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.2.rst b/docs/sphinx/source/whatsnew/v0.6.2.rst index ec3fa495f6..5b11119408 100644 --- a/docs/sphinx/source/whatsnew/v0.6.2.rst +++ b/docs/sphinx/source/whatsnew/v0.6.2.rst @@ -40,7 +40,11 @@ Bug fixes * Fixed :py:func:`~pvlib.irradiance.erbs` behavior when zenith is near 90 degrees. (:issue:`681`) * :py:func:`~pvlib.irradiance.dni` now referenced in API under - Decomposing and Combining irradiance header. (:issue:`686`) + Decomposing and Combining irradiance header. (:issue:`686`) +* Fixed NaN output from :py:func:`~pvlib.tracking.singleaxis` when sun + near horizon. (:issue:`656`) +* Fixed numpy warnings in :py:func:`~pvlib.tracking.singleaxis` when + comparing NaN values to limits. (:issue:`622`) Testing @@ -53,4 +57,5 @@ Contributors * Will Holmgren (:ghuser:`wholmgren`) * Roel Loonen (:ghuser:`roelloonen`) * Todd Hendricks (:ghuser:`tahentx`) - +* Kevin Anderson (:ghuser:`kevinsa5`) +* :ghuser:`bentomlinson` diff --git a/pvlib/test/test_tracking.py b/pvlib/test/test_tracking.py index c1d31c3db9..872076eb9d 100644 --- a/pvlib/test/test_tracking.py +++ b/pvlib/test/test_tracking.py @@ -1,5 +1,3 @@ -import datetime - import numpy as np from numpy import nan import pandas as pd @@ -96,10 +94,10 @@ def test_arrays_multi(): apparent_azimuth = np.array([[180, 180], [180, 180]]) # singleaxis should fail for num dim > 1 with pytest.raises(ValueError): - tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, - max_angle=90, backtrack=True, - gcr=2.0/7.0) + tracking.singleaxis(apparent_zenith, apparent_azimuth, + axis_tilt=0, axis_azimuth=0, + max_angle=90, backtrack=True, + gcr=2.0/7.0) # uncomment if we ever get singleaxis to support num dim > 1 arrays # assert isinstance(tracker_data, dict) # expect = {'tracker_theta': np.full_like(apparent_zenith, 0), @@ -121,7 +119,7 @@ def test_azimuth_north_south(): expect = pd.DataFrame({'tracker_theta': -60, 'aoi': 0, 'surface_azimuth': 90, 'surface_tilt': 60}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -146,7 +144,7 @@ def test_max_angle(): expect = pd.DataFrame({'aoi': 15, 'surface_azimuth': 90, 'surface_tilt': 45, 'tracker_theta': 45}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -163,7 +161,7 @@ def test_backtrack(): expect = pd.DataFrame({'aoi': 0, 'surface_azimuth': 90, 'surface_tilt': 80, 'tracker_theta': 80}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -175,7 +173,7 @@ def test_backtrack(): expect = pd.DataFrame({'aoi': 52.5716, 'surface_azimuth': 90, 'surface_tilt': 27.42833, 'tracker_theta': 27.4283}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -193,7 +191,7 @@ def test_axis_tilt(): expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730, 'surface_tilt': 35.98741, 'tracker_theta': -20.88121}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -205,7 +203,7 @@ def test_axis_tilt(): expect = pd.DataFrame({'aoi': 47.6632, 'surface_azimuth': 50.96969, 'surface_tilt': 42.5152, 'tracker_theta': 31.6655}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -222,7 +220,7 @@ def test_axis_azimuth(): expect = pd.DataFrame({'aoi': 30, 'surface_azimuth': 180, 'surface_tilt': 0, 'tracker_theta': 0}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -237,7 +235,7 @@ def test_axis_azimuth(): expect = pd.DataFrame({'aoi': 0, 'surface_azimuth': 180, 'surface_tilt': 30, 'tracker_theta': 30}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) @@ -277,6 +275,20 @@ def test_horizon_tilted(): assert_frame_equal(out, expected) +def test_low_sun_angles(): + # GH 656 + result = tracking.singleaxis( + apparent_zenith=80, apparent_azimuth=338, axis_tilt=30, + axis_azimuth=180, max_angle=60, backtrack=True, gcr=0.35) + expected = { + 'tracker_theta': np.array([-50.31051385]), + 'aoi': np.array([61.35300178]), + 'surface_azimuth': np.array([112.53615425]), + 'surface_tilt': np.array([56.42233095])} + for k, v in result.items(): + assert_allclose(expected[k], v) + + def test_SingleAxisTracker_creation(): system = tracking.SingleAxisTracker(max_angle=45, gcr=.25, @@ -299,37 +311,35 @@ def test_SingleAxisTracker_tracking(): tracker_data = system.singleaxis(apparent_zenith, apparent_azimuth) - expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730 , + expect = pd.DataFrame({'aoi': 7.286245, 'surface_azimuth': 142.65730, 'surface_tilt': 35.98741, 'tracker_theta': -20.88121}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) - ### results calculated using PVsyst + # results calculated using PVsyst pvsyst_solar_azimuth = 7.1609 pvsyst_solar_height = 27.315 pvsyst_axis_tilt = 20. pvsyst_axis_azimuth = 20. - pvsyst_system = tracking.SingleAxisTracker(max_angle=60., - axis_tilt=pvsyst_axis_tilt, - axis_azimuth=180+pvsyst_axis_azimuth, - backtrack=False) + pvsyst_system = tracking.SingleAxisTracker( + max_angle=60., axis_tilt=pvsyst_axis_tilt, + axis_azimuth=180+pvsyst_axis_azimuth, backtrack=False) # the definition of azimuth is different from PYsyst apparent_azimuth = pd.Series([180+pvsyst_solar_azimuth]) apparent_zenith = pd.Series([90-pvsyst_solar_height]) tracker_data = pvsyst_system.singleaxis(apparent_zenith, apparent_azimuth) - expect = pd.DataFrame({'aoi': 41.07852 , 'surface_azimuth': 180-18.432, - 'surface_tilt': 24.92122 , + expect = pd.DataFrame({'aoi': 41.07852, 'surface_azimuth': 180-18.432, + 'surface_tilt': 24.92122, 'tracker_theta': -15.18391}, - index=[0], dtype=np.float64) + index=[0], dtype=np.float64) expect = expect[SINGLEAXIS_COL_ORDER] assert_frame_equal(expect, tracker_data) - def test_LocalizedSingleAxisTracker_creation(): localized_system = tracking.LocalizedSingleAxisTracker(latitude=32, longitude=-111, diff --git a/pvlib/tracking.py b/pvlib/tracking.py index a69b707e82..efb183c464 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -441,19 +441,20 @@ def singleaxis(apparent_zenith, apparent_azimuth, # angle convention being used here. if backtrack: axes_distance = 1/gcr - temp = np.minimum(axes_distance*cosd(wid), 1) + temp = np.clip(axes_distance*cosd(wid), -1, 1) # backtrack angle # (always positive b/c acosd returns values between 0 and 180) wc = np.degrees(np.arccos(temp)) # Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI - tracker_theta = np.where(wid < 0, wid + wc, wid - wc) + with np.errstate(invalid='ignore'): + tracker_theta = np.where(wid < 0, wid + wc, wid - wc) else: tracker_theta = wid - tracker_theta[tracker_theta > max_angle] = max_angle - tracker_theta[tracker_theta < -max_angle] = -max_angle + tracker_theta = np.minimum(tracker_theta, max_angle) + tracker_theta = np.maximum(tracker_theta, -max_angle) # calculate panel normal vector in panel-oriented x, y, z coordinates. # y-axis is axis of tracker rotation. tracker_theta is a compass angle @@ -560,8 +561,9 @@ def singleaxis(apparent_zenith, apparent_azimuth, surface_azimuth = 90 - surface_azimuth + axis_azimuth # 5. Map azimuth into [0,360) domain. - surface_azimuth[surface_azimuth < 0] += 360 - surface_azimuth[surface_azimuth >= 360] -= 360 + # surface_azimuth[surface_azimuth < 0] += 360 + # surface_azimuth[surface_azimuth >= 360] -= 360 + surface_azimuth = surface_azimuth % 360 # Calculate surface_tilt dotproduct = (panel_norm_earth * projected_normal).sum(axis=1) From c1e1c9e4d01155b08c6389f7d79bd0a20facad20 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Thu, 2 May 2019 16:12:46 -0700 Subject: [PATCH 2/2] a couple of comments --- pvlib/tracking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index efb183c464..f81b0e8571 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -441,6 +441,7 @@ def singleaxis(apparent_zenith, apparent_azimuth, # angle convention being used here. if backtrack: axes_distance = 1/gcr + # clip needed for low angles. GH 656 temp = np.clip(axes_distance*cosd(wid), -1, 1) # backtrack angle @@ -449,6 +450,7 @@ def singleaxis(apparent_zenith, apparent_azimuth, # Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI with np.errstate(invalid='ignore'): + # errstate for GH 622 tracker_theta = np.where(wid < 0, wid + wc, wid - wc) else: tracker_theta = wid