From 1e412cede92b21866891a483a416a4cd987a670a Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:17:33 +0100 Subject: [PATCH 01/54] Function prototype --- pvlib/shading.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pvlib/shading.py b/pvlib/shading.py index 1533d2a013..e521a9b8e9 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -232,3 +232,26 @@ def sky_diffuse_passias(masking_angle): Available at https://www.nrel.gov/docs/fy18osti/67399.pdf """ return 1 - cosd(masking_angle/2)**2 + + +def projected_solar_zenith_angle(apparent_zenith, azimuth): + r""" + Calculate projected solar zenith angle in degrees. + + Parameters + ---------- + apparent_zenith : numeric + Sun's apparent zenith in degrees. + azimuth : numeric + Sun's azimuth in degrees. + + Returns + ------- + Projected_solar_zenith : numeric + In degrees. + """ + apparent_zenith = np.radians(apparent_zenith) + azimuth = np.radians(azimuth) + return np.degrees( + np.arctan2(np.sin(azimuth) * np.sin(apparent_zenith), np.cos(apparent_zenith)) + ) From e4a95a328f5e0c8c4204e12c67596f96be825d00 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:09:25 +0100 Subject: [PATCH 02/54] Update shading.rst --- .../source/reference/effects_on_pv_system_output/shading.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/shading.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/shading.rst index 14ac13b4ca..a0fd74a795 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/shading.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/shading.rst @@ -9,4 +9,5 @@ Shading shading.ground_angle shading.masking_angle shading.masking_angle_passias - shading.sky_diffuse_passias \ No newline at end of file + shading.sky_diffuse_passias + shading.projected_solar_zenith_angle From 54878db0f462449f0cbbc6b3c537c53215e1db98 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:10:59 +0100 Subject: [PATCH 03/54] Update shading.py --- pvlib/shading.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index e521a9b8e9..848a28c217 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -253,5 +253,6 @@ def projected_solar_zenith_angle(apparent_zenith, azimuth): apparent_zenith = np.radians(apparent_zenith) azimuth = np.radians(azimuth) return np.degrees( - np.arctan2(np.sin(azimuth) * np.sin(apparent_zenith), np.cos(apparent_zenith)) + np.arctan2(np.sin(azimuth) * np.sin(apparent_zenith), + np.cos(apparent_zenith)) ) From b79ebe63a53b84d7a8e6ad15f2e97085fd4715cd Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 6 Nov 2023 19:05:23 +0100 Subject: [PATCH 04/54] Minimal test --- pvlib/tests/test_shading.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index b7981cd02d..40bfcaab23 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -2,6 +2,7 @@ import pandas as pd from pandas.testing import assert_series_equal +from numpy.testing import assert_allclose import pytest from pvlib import shading @@ -104,3 +105,15 @@ def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss): for angle, loss in zip(average_masking_angle, shading_loss): actual_loss = shading.sky_diffuse_passias(angle) assert np.isclose(loss, actual_loss) + + +def test_projected_solar_zenith_angle(): + psz_func = shading.projected_solar_zenith_angle + for app_zenith, azimuth, expected, atolerance, type in ( + (90., 120., 90, 1e-3, float), + ([30], [100], [30], 1, np.ndarray), + (pd.Series([60]), pd.Series([135]), 50, 1, pd.Series) + ): + psz = psz_func(app_zenith, azimuth) + assert_allclose(psz, expected, atol=atolerance) + assert isinstance(psz, type) From c3c0e56e0636fdab83a3e79b41c8e5c8d652a3bc Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:28:25 +0100 Subject: [PATCH 05/54] Implementation From NREL paper --- pvlib/shading.py | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 848a28c217..a8e3af5cd9 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -234,25 +234,51 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(apparent_zenith, azimuth): +def projected_solar_zenith_angle(surface_tilt, surface_azimuth, + solar_apparent_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. + This is common in track and shadow computation [1]_ [2]_ [3]_. + Parameters ---------- - apparent_zenith : numeric + surface_tilt : numeric + Array tilt angle in degrees. From horizontal plane to array plane. + surface_azimuth : numeric + Array azimuth angle in degrees. + North = 0°; East = 90°; South = 180°; West = 270° + solar_apparent_zenith : numeric Sun's apparent zenith in degrees. - azimuth : numeric + solar_azimuth : numeric Sun's azimuth in degrees. Returns ------- Projected_solar_zenith : numeric In degrees. + + References + ---------- + .. [1] K. Anderson and M. Mikofski, ‘Slope-Aware Backtracking for + Single-Axis Trackers’, National Renewable Energy Lab. (NREL), Golden, + CO (United States); Det Norske Veritas Group, Oslo (Norway), + NREL/TP-5K00-76626, Jul. 2020. :doi:`10.2172/1660126`. + .. [2] W. F. Marion and A. P. Dobos, ‘Rotation Angle for the Optimum + Tracking of One-Axis Trackers’, National Renewable Energy Lab. (NREL), + Golden, CO (United States), NREL/TP-6A20-58891, Jul. 2013. + :doi:`10.2172/1089596`. + .. [3] E. Lorenzo, L. Narvarte, and J. Muñoz, ‘Tracking and back-tracking’, + Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, + pp. 747–753, 2011, :doi:`10.1002/pip.1085`. """ - apparent_zenith = np.radians(apparent_zenith) - azimuth = np.radians(azimuth) - return np.degrees( - np.arctan2(np.sin(azimuth) * np.sin(apparent_zenith), - np.cos(apparent_zenith)) - ) + # Notation from [1] + sx = cosd(solar_apparent_zenith) * cosd(solar_azimuth) + sy = cosd(solar_apparent_zenith) * cosd(solar_azimuth) + sz = sind(solar_apparent_zenith) + sx_prime = sx * cosd(surface_azimuth) - sy * sind(surface_azimuth) + sz_prime = (sx * sind(surface_azimuth) * sind(surface_tilt) + + sy * sind(surface_tilt) * cosd(surface_azimuth) + + sz * cosd(surface_tilt)) + theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) + return theta_T From 5d4a2b4fc0aeeb10ceab70ac1afdfe20b2be3fc1 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Nov 2023 22:46:17 +0100 Subject: [PATCH 06/54] Fix, fix, fix, fix & format --- pvlib/shading.py | 12 +++--- pvlib/tests/test_shading.py | 77 ++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index a8e3af5cd9..9ea0bbb8d7 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -235,7 +235,7 @@ def sky_diffuse_passias(masking_angle): def projected_solar_zenith_angle(surface_tilt, surface_azimuth, - solar_apparent_zenith, solar_azimuth): + solar_apparent_elevation, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. @@ -248,8 +248,8 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, surface_azimuth : numeric Array azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° - solar_apparent_zenith : numeric - Sun's apparent zenith in degrees. + solar_apparent_elevation : numeric + Sun's apparent elevation in degrees. solar_azimuth : numeric Sun's azimuth in degrees. @@ -273,9 +273,9 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, pp. 747–753, 2011, :doi:`10.1002/pip.1085`. """ # Notation from [1] - sx = cosd(solar_apparent_zenith) * cosd(solar_azimuth) - sy = cosd(solar_apparent_zenith) * cosd(solar_azimuth) - sz = sind(solar_apparent_zenith) + sx = cosd(solar_apparent_elevation) * sind(solar_azimuth) + sy = cosd(solar_apparent_elevation) * cosd(solar_azimuth) + sz = sind(solar_apparent_elevation) sx_prime = sx * cosd(surface_azimuth) - sy * sind(surface_azimuth) sz_prime = (sx * sind(surface_azimuth) * sind(surface_tilt) + sy * sind(surface_tilt) * cosd(surface_azimuth) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 40bfcaab23..340cfd7289 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -4,6 +4,7 @@ from pandas.testing import assert_series_equal from numpy.testing import assert_allclose import pytest +from datetime import timezone, timedelta from pvlib import shading @@ -38,7 +39,7 @@ def test__ground_angle_zero_gcr(): @pytest.fixture def surface_tilt(): - idx = pd.date_range('2019-01-01', freq='h', periods=3) + idx = pd.date_range("2019-01-01", freq="h", periods=3) return pd.Series([0, 20, 90], index=idx) @@ -107,13 +108,69 @@ def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss): assert np.isclose(loss, actual_loss) -def test_projected_solar_zenith_angle(): +@pytest.fixture +def true_tracking_angle_and_inputs(): + # data retrieved from NREL Slope-Aware Backtracking for Single-Axis + # Trackers + # doi.org/10.2172/1660126 ; Accessed on 2023-11-06. + tzinfo = timezone(timedelta(hours=-5)) + array_tilt_angle = 9.666 # deg + array_azimuth_angle = 195.0 # deg + timedata = pd.DataFrame( + columns=("Apparent Elevation", "Solar Azimuth", "True-Tracking"), + data=( + (2.404287, 122.791770, -84.440), + (11.263058, 133.288729, -72.604), + (18.733558, 145.285552, -59.861), + (24.109076, 158.939435, -45.578), + (26.810735, 173.931802, -28.764), + (26.482495, 189.371536, -8.475), + (23.170447, 204.136810, 15.120), + (17.296785, 217.446538, 39.562), + (9.461862, 229.102218, 61.587), + (0.524817, 239.330401, 79.530), + ), + ) + timedata.index = pd.date_range( + "2019-01-01T08", "2019-01-01T17", freq="1H", tz=tzinfo + ) + timedata["Apparent Zenith"] = 90.0 - timedata["Apparent Elevation"] + return (array_tilt_angle, array_azimuth_angle, timedata) + + +def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): + psz_func = shading.projected_solar_zenith_angle + array_tilt, array_azimuth, timedata = true_tracking_angle_and_inputs + psz = psz_func( + array_tilt, + array_azimuth, + timedata["Apparent Elevation"], + timedata["Solar Azimuth"], + ) + assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) + + +@pytest.mark.parametrize( + "cast_type, cast_func", + [ + (float, float), + (np.ndarray, lambda x: np.array([x])), + (pd.Series, lambda x: pd.Series(data=[x])), + ], +) +def test_projected_solar_zenith_angle_dataypes( + cast_type, cast_func, true_tracking_angle_and_inputs +): psz_func = shading.projected_solar_zenith_angle - for app_zenith, azimuth, expected, atolerance, type in ( - (90., 120., 90, 1e-3, float), - ([30], [100], [30], 1, np.ndarray), - (pd.Series([60]), pd.Series([135]), 50, 1, pd.Series) - ): - psz = psz_func(app_zenith, azimuth) - assert_allclose(psz, expected, atol=atolerance) - assert isinstance(psz, type) + array_tilt, array_azimuth, timedata = true_tracking_angle_and_inputs + sun_apparent_zenith = timedata["Apparent Zenith"].iloc[0] + sun_azimuth = timedata["Solar Azimuth"].iloc[0] + + array_tilt, array_azimuth, sun_apparent_zenith, sun_azimuth = ( + cast_func(array_tilt), + cast_func(array_azimuth), + cast_func(sun_apparent_zenith), + cast_func(sun_azimuth), + ) + psz = psz_func(array_tilt, array_azimuth, sun_apparent_zenith, array_azimuth) + assert isinstance(psz, cast_type) From 9ae5fa30020261e3effae6094cc6761d3744837c Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Nov 2023 22:50:19 +0100 Subject: [PATCH 07/54] Format issues --- pvlib/tests/test_shading.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 340cfd7289..ce5ea7d833 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -172,5 +172,7 @@ def test_projected_solar_zenith_angle_dataypes( cast_func(sun_apparent_zenith), cast_func(sun_azimuth), ) - psz = psz_func(array_tilt, array_azimuth, sun_apparent_zenith, array_azimuth) + psz = psz_func( + array_tilt, array_azimuth, sun_apparent_zenith, array_azimuth + ) assert isinstance(psz, cast_type) From f17379d6723ce565760a76cacdf40366f07fe2de Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Nov 2023 23:15:58 +0100 Subject: [PATCH 08/54] Extend tests (compare with singleaxis) & format with ruff --- pvlib/shading.py | 8 +++++--- pvlib/tests/test_shading.py | 9 +++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 9ea0bbb8d7..93d44e5404 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -277,8 +277,10 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, sy = cosd(solar_apparent_elevation) * cosd(solar_azimuth) sz = sind(solar_apparent_elevation) sx_prime = sx * cosd(surface_azimuth) - sy * sind(surface_azimuth) - sz_prime = (sx * sind(surface_azimuth) * sind(surface_tilt) - + sy * sind(surface_tilt) * cosd(surface_azimuth) - + sz * cosd(surface_tilt)) + sz_prime = ( + sx * sind(surface_azimuth) * sind(surface_tilt) + + sy * sind(surface_tilt) * cosd(surface_azimuth) + + sz * cosd(surface_tilt) + ) theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) return theta_T diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index ce5ea7d833..27849f7a8a 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -6,9 +6,9 @@ import pytest from datetime import timezone, timedelta +import pvlib from pvlib import shading - @pytest.fixture def test_system(): syst = {'height': 1.0, @@ -148,7 +148,12 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): timedata["Solar Azimuth"], ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) - + # test equivalence against pvlib.tracking.singleaxis + singleaxis = pvlib.tracking.singleaxis(90-timedata["Apparent Elevation"], + timedata["Solar Azimuth"], + array_tilt, array_azimuth, + backtrack=False) + assert_allclose(psz, singleaxis["tracker_theta"]) @pytest.mark.parametrize( "cast_type, cast_func", From af3d27c8cc4fd3890def20531e656e703c6b92e3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Nov 2023 23:24:51 +0100 Subject: [PATCH 09/54] Format fixes --- pvlib/tests/test_shading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 27849f7a8a..2a4352fdc8 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -9,6 +9,7 @@ import pvlib from pvlib import shading + @pytest.fixture def test_system(): syst = {'height': 1.0, @@ -155,6 +156,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): backtrack=False) assert_allclose(psz, singleaxis["tracker_theta"]) + @pytest.mark.parametrize( "cast_type, cast_func", [ From d36841a912f3d02aa9fd77a1a9753f91caf292c3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:50:16 +0100 Subject: [PATCH 10/54] Upgrade tests --- pvlib/tests/test_shading.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 2a4352fdc8..497480f8d2 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -150,11 +150,19 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) # test equivalence against pvlib.tracking.singleaxis - singleaxis = pvlib.tracking.singleaxis(90-timedata["Apparent Elevation"], + singleaxis = pvlib.tracking.singleaxis(timedata["Apparent Zenith"], timedata["Solar Azimuth"], array_tilt, array_azimuth, backtrack=False) assert_allclose(psz, singleaxis["tracker_theta"]) + # test by changing axis azimuth and tilt + psz = psz_func( + -array_tilt, + array_azimuth-180, + timedata["Apparent Elevation"], + timedata["Solar Azimuth"], + ) + assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) @pytest.mark.parametrize( From 19995e12b0118703a52729de31a4c978c74e1dfa Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:51:44 +0100 Subject: [PATCH 11/54] Array -> Axis --- pvlib/shading.py | 4 ++-- pvlib/tests/test_shading.py | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 93d44e5404..165e54e90c 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -244,9 +244,9 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, Parameters ---------- surface_tilt : numeric - Array tilt angle in degrees. From horizontal plane to array plane. + Axis tilt angle in degrees. From horizontal plane to array plane. surface_azimuth : numeric - Array azimuth angle in degrees. + Axis azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° solar_apparent_elevation : numeric Sun's apparent elevation in degrees. diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 497480f8d2..0def278789 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -115,8 +115,8 @@ def true_tracking_angle_and_inputs(): # Trackers # doi.org/10.2172/1660126 ; Accessed on 2023-11-06. tzinfo = timezone(timedelta(hours=-5)) - array_tilt_angle = 9.666 # deg - array_azimuth_angle = 195.0 # deg + axis_tilt_angle = 9.666 # deg + axis_azimuth_angle = 195.0 # deg timedata = pd.DataFrame( columns=("Apparent Elevation", "Solar Azimuth", "True-Tracking"), data=( @@ -136,15 +136,15 @@ def true_tracking_angle_and_inputs(): "2019-01-01T08", "2019-01-01T17", freq="1H", tz=tzinfo ) timedata["Apparent Zenith"] = 90.0 - timedata["Apparent Elevation"] - return (array_tilt_angle, array_azimuth_angle, timedata) + return (axis_tilt_angle, axis_azimuth_angle, timedata) def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): psz_func = shading.projected_solar_zenith_angle - array_tilt, array_azimuth, timedata = true_tracking_angle_and_inputs + axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs psz = psz_func( - array_tilt, - array_azimuth, + axis_tilt, + axis_azimuth, timedata["Apparent Elevation"], timedata["Solar Azimuth"], ) @@ -152,13 +152,13 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): # test equivalence against pvlib.tracking.singleaxis singleaxis = pvlib.tracking.singleaxis(timedata["Apparent Zenith"], timedata["Solar Azimuth"], - array_tilt, array_azimuth, + axis_tilt, axis_azimuth, backtrack=False) assert_allclose(psz, singleaxis["tracker_theta"]) # test by changing axis azimuth and tilt psz = psz_func( - -array_tilt, - array_azimuth-180, + -axis_tilt, + axis_azimuth-180, timedata["Apparent Elevation"], timedata["Solar Azimuth"], ) @@ -169,7 +169,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): "cast_type, cast_func", [ (float, float), - (np.ndarray, lambda x: np.array([x])), + (np.ndaxis, lambda x: np.axis([x])), (pd.Series, lambda x: pd.Series(data=[x])), ], ) @@ -177,17 +177,17 @@ def test_projected_solar_zenith_angle_dataypes( cast_type, cast_func, true_tracking_angle_and_inputs ): psz_func = shading.projected_solar_zenith_angle - array_tilt, array_azimuth, timedata = true_tracking_angle_and_inputs + axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs sun_apparent_zenith = timedata["Apparent Zenith"].iloc[0] sun_azimuth = timedata["Solar Azimuth"].iloc[0] - array_tilt, array_azimuth, sun_apparent_zenith, sun_azimuth = ( - cast_func(array_tilt), - cast_func(array_azimuth), + axis_tilt, axis_azimuth, sun_apparent_zenith, sun_azimuth = ( + cast_func(axis_tilt), + cast_func(axis_azimuth), cast_func(sun_apparent_zenith), cast_func(sun_azimuth), ) psz = psz_func( - array_tilt, array_azimuth, sun_apparent_zenith, array_azimuth + axis_tilt, axis_azimuth, sun_apparent_zenith, axis_azimuth ) assert isinstance(psz, cast_type) From 770e0377bab6899c84b8f5e61eeb0148629a1b59 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:52:32 +0100 Subject: [PATCH 12/54] type --- pvlib/tests/test_shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 0def278789..de28077877 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -173,7 +173,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): (pd.Series, lambda x: pd.Series(data=[x])), ], ) -def test_projected_solar_zenith_angle_dataypes( +def test_projected_solar_zenith_angle_datatypes( cast_type, cast_func, true_tracking_angle_and_inputs ): psz_func = shading.projected_solar_zenith_angle From 29bcef9249f918c244ab828f15b4bea97da89252 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:56:31 +0100 Subject: [PATCH 13/54] Whatsnew --- docs/sphinx/source/whatsnew/v0.10.3.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 8bdc2cf98f..46fb22777b 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -12,6 +12,8 @@ Enhancements * :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance` and :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance_poa` now include shaded fraction in returned variables. (:pull:`1871`) +* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, + a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) Bug fixes ~~~~~~~~~ @@ -32,3 +34,4 @@ Contributors * Miguel Sánchez de León Peque (:ghuser:`Peque`) * Will Hobbs (:ghuser:`williamhobbs`) * Anton Driesse (:ghuser:`adriesse`) +* Echedey Luis (:ghuser:`echedey-ls`) From 61c2e3bad88d56067e4c3afb5b0fbcc826a7c593 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:58:37 +0100 Subject: [PATCH 14/54] xd --- pvlib/tests/test_shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index de28077877..006237b171 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -169,7 +169,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): "cast_type, cast_func", [ (float, float), - (np.ndaxis, lambda x: np.axis([x])), + (np.ndarray, lambda x: np.axis([x])), (pd.Series, lambda x: pd.Series(data=[x])), ], ) From 965b0b44879822f356cd5c8ee49c7380bc6f518d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:01:49 +0100 Subject: [PATCH 15/54] bruh --- pvlib/tests/test_shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 006237b171..e86926a865 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -169,7 +169,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): "cast_type, cast_func", [ (float, float), - (np.ndarray, lambda x: np.axis([x])), + (np.ndarray, lambda x: np.array([x])), (pd.Series, lambda x: pd.Series(data=[x])), ], ) From 346d0606bc96997955b0c12afe9b448f88616b8d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:05:12 +0100 Subject: [PATCH 16/54] Minor Python optimization a la tracking.singleaxis --- pvlib/shading.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 165e54e90c..9ed7647218 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -276,10 +276,13 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, sx = cosd(solar_apparent_elevation) * sind(solar_azimuth) sy = cosd(solar_apparent_elevation) * cosd(solar_azimuth) sz = sind(solar_apparent_elevation) - sx_prime = sx * cosd(surface_azimuth) - sy * sind(surface_azimuth) + cosd_surface_azimuth = cosd(surface_azimuth) + sind_surface_azimuth = sind(surface_azimuth) + sind_surface_tilt = sind(surface_tilt) + sx_prime = sx * cosd_surface_azimuth - sy * sind_surface_azimuth sz_prime = ( - sx * sind(surface_azimuth) * sind(surface_tilt) - + sy * sind(surface_tilt) * cosd(surface_azimuth) + sx * sind_surface_azimuth * sind_surface_tilt + + sy * sind_surface_tilt * cosd_surface_azimuth + sz * cosd(surface_tilt) ) theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) From 085d017fa3e8295100f3555806d0f80c6df2ab71 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:19:38 +0100 Subject: [PATCH 17/54] Comment and minor optimizations --- pvlib/shading.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 9ed7647218..145f0f2590 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -272,18 +272,24 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, pp. 747–753, 2011, :doi:`10.1002/pip.1085`. """ - # Notation from [1] - sx = cosd(solar_apparent_elevation) * sind(solar_azimuth) - sy = cosd(solar_apparent_elevation) * cosd(solar_azimuth) - sz = sind(solar_apparent_elevation) + # Avoid recalculating these values + cosd_solar_apparent_elevation = cosd(solar_apparent_elevation) cosd_surface_azimuth = cosd(surface_azimuth) sind_surface_azimuth = sind(surface_azimuth) sind_surface_tilt = sind(surface_tilt) + + # Notation from [1] + # Sun's x, y, z coords + sx = cosd_solar_apparent_elevation * sind(solar_azimuth) + sy = cosd_solar_apparent_elevation * cosd(solar_azimuth) + sz = sind(solar_apparent_elevation) + # Eq. (4); sx', sz' values from sun coordinates projected onto surface sx_prime = sx * cosd_surface_azimuth - sy * sind_surface_azimuth sz_prime = ( sx * sind_surface_azimuth * sind_surface_tilt + sy * sind_surface_tilt * cosd_surface_azimuth + sz * cosd(surface_tilt) ) + # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) return theta_T From 79b5f4f78140716a489c07cda0bcdcd4d8be47ac Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:40:23 +0100 Subject: [PATCH 18/54] Typo found by Mikofski Reported at: https://github.com/pvlib/pvlib-python/pull/1725#discussion_r1190610810 Confirmed via "Slope-Aware Backtracking for Single-Axis Trackers", paragraph after Eq. 1 Co-Authored-By: Mark Mikofski --- pvlib/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 04ed5f8506..c137f2115e 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -159,7 +159,7 @@ def singleaxis(apparent_zenith, apparent_azimuth, + z*cos_axis_tilt) # The ideal tracking angle wid is the rotation to place the sun position - # vector (xp, yp, zp) in the (y, z) plane, which is normal to the panel and + # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and # contains the axis of rotation. wid = 0 indicates that the panel is # horizontal. Here, our convention is that a clockwise rotation is # positive, to view rotation angles in the same frame of reference as From dc1035abbbc45861efa1db0beaf442bf996f5dd1 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:50:19 +0100 Subject: [PATCH 19/54] Surface -> Axis Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/shading.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 145f0f2590..f48a54ad3d 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -234,7 +234,7 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(surface_tilt, surface_azimuth, +def projected_solar_zenith_angle(axis_tilt, axis_azimuth, solar_apparent_elevation, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. @@ -243,9 +243,9 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, Parameters ---------- - surface_tilt : numeric + axis_tilt : numeric Axis tilt angle in degrees. From horizontal plane to array plane. - surface_azimuth : numeric + axis_azimuth : numeric Axis azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° solar_apparent_elevation : numeric @@ -274,9 +274,9 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, """ # Avoid recalculating these values cosd_solar_apparent_elevation = cosd(solar_apparent_elevation) - cosd_surface_azimuth = cosd(surface_azimuth) - sind_surface_azimuth = sind(surface_azimuth) - sind_surface_tilt = sind(surface_tilt) + cosd_axis_azimuth = cosd(axis_azimuth) + sind_axis_azimuth = sind(axis_azimuth) + sind_axis_tilt = sind(axis_tilt) # Notation from [1] # Sun's x, y, z coords @@ -284,11 +284,11 @@ def projected_solar_zenith_angle(surface_tilt, surface_azimuth, sy = cosd_solar_apparent_elevation * cosd(solar_azimuth) sz = sind(solar_apparent_elevation) # Eq. (4); sx', sz' values from sun coordinates projected onto surface - sx_prime = sx * cosd_surface_azimuth - sy * sind_surface_azimuth + sx_prime = sx * cosd_axis_azimuth - sy * sind_axis_azimuth sz_prime = ( - sx * sind_surface_azimuth * sind_surface_tilt - + sy * sind_surface_tilt * cosd_surface_azimuth - + sz * cosd(surface_tilt) + sx * sind_axis_azimuth * sind_axis_tilt + + sy * sind_axis_tilt * cosd_axis_azimuth + + sz * cosd(axis_tilt) ) # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) From 88cbfc0b071927feea59b91a90ade76461b88724 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:13:03 +0100 Subject: [PATCH 20/54] Elevation -> Zenith Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/shading.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index f48a54ad3d..b7c6b02f4e 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -235,7 +235,7 @@ def sky_diffuse_passias(masking_angle): def projected_solar_zenith_angle(axis_tilt, axis_azimuth, - solar_apparent_elevation, solar_azimuth): + solar_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. @@ -248,8 +248,8 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, axis_azimuth : numeric Axis azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° - solar_apparent_elevation : numeric - Sun's apparent elevation in degrees. + solar_zenith : numeric + Sun's apparent zenith in degrees. solar_azimuth : numeric Sun's azimuth in degrees. @@ -272,17 +272,19 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, pp. 747–753, 2011, :doi:`10.1002/pip.1085`. """ + # Notation from [1], modified to use zenith instead of elevation + # Since elevation = 90 - zenith, sin(90-x) = cos(x) & cos(90-x) = sin(x): + # cos(elevation) = sin(zenith) and sin(elevation) = cos(zenith) # Avoid recalculating these values - cosd_solar_apparent_elevation = cosd(solar_apparent_elevation) + sind_solar_zenith = sind(solar_zenith) cosd_axis_azimuth = cosd(axis_azimuth) sind_axis_azimuth = sind(axis_azimuth) sind_axis_tilt = sind(axis_tilt) - # Notation from [1] # Sun's x, y, z coords - sx = cosd_solar_apparent_elevation * sind(solar_azimuth) - sy = cosd_solar_apparent_elevation * cosd(solar_azimuth) - sz = sind(solar_apparent_elevation) + sx = sind_solar_zenith * sind(solar_azimuth) + sy = sind_solar_zenith * cosd(solar_azimuth) + sz = cosd(solar_zenith) # Eq. (4); sx', sz' values from sun coordinates projected onto surface sx_prime = sx * cosd_axis_azimuth - sy * sind_axis_azimuth sz_prime = ( From 1afec9442c7260649f79e73757cd49e4586647a5 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 9 Nov 2023 23:27:09 +0100 Subject: [PATCH 21/54] Elev -> Zenith Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/tests/test_shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index e86926a865..0e44a42354 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -145,7 +145,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): psz = psz_func( axis_tilt, axis_azimuth, - timedata["Apparent Elevation"], + timedata["Apparent Zenith"], timedata["Solar Azimuth"], ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) @@ -159,7 +159,7 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): psz = psz_func( -axis_tilt, axis_azimuth-180, - timedata["Apparent Elevation"], + timedata["Apparent Zenith"], timedata["Solar Azimuth"], ) assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) From 4b2c0e54d85ac83fb8bbc83fb765844f7d0f1931 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Jan 2024 22:14:41 +0100 Subject: [PATCH 22/54] Update shading.py --- pvlib/shading.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index b7c6b02f4e..c19b9a4423 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -260,17 +260,17 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, References ---------- - .. [1] K. Anderson and M. Mikofski, ‘Slope-Aware Backtracking for - Single-Axis Trackers’, National Renewable Energy Lab. (NREL), Golden, + .. [1] K. Anderson and M. Mikofski, 'Slope-Aware Backtracking for + Single-Axis Trackers', National Renewable Energy Lab. (NREL), Golden, CO (United States); Det Norske Veritas Group, Oslo (Norway), NREL/TP-5K00-76626, Jul. 2020. :doi:`10.2172/1660126`. - .. [2] W. F. Marion and A. P. Dobos, ‘Rotation Angle for the Optimum - Tracking of One-Axis Trackers’, National Renewable Energy Lab. (NREL), + .. [2] W. F. Marion and A. P. Dobos, 'Rotation Angle for the Optimum + Tracking of One-Axis Trackers', National Renewable Energy Lab. (NREL), Golden, CO (United States), NREL/TP-6A20-58891, Jul. 2013. :doi:`10.2172/1089596`. - .. [3] E. Lorenzo, L. Narvarte, and J. Muñoz, ‘Tracking and back-tracking’, + .. [3] E. Lorenzo, L. Narvarte, and J. Muñoz, 'Tracking and back-tracking', Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, - pp. 747–753, 2011, :doi:`10.1002/pip.1085`. + pp. 747-753, 2011, :doi:`10.1002/pip.1085`. """ # Notation from [1], modified to use zenith instead of elevation # Since elevation = 90 - zenith, sin(90-x) = cos(x) & cos(90-x) = sin(x): From 4612442c9d8a62ad15c2036840ff43171ffb00c3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:02:39 +0100 Subject: [PATCH 23/54] Update docstring Co-Authored-By: Anton Driesse <9001027+adriesse@users.noreply.github.com> --- pvlib/shading.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index c19b9a4423..00bac7397d 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -238,8 +238,11 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, solar_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. + This is the solar zenith angle projected onto the tracker rotation plane or + the plane defined by its normal vector and the azimuth. - This is common in track and shadow computation [1]_ [2]_ [3]_. + Computing said value is common in track and shadow algorithms. + See [1]_ [2]_ [3]_. Parameters ---------- From 6372cb7ebff57618f579c25a5983c0fefdf62e75 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:25:56 +0100 Subject: [PATCH 24/54] Add comments from `tracking.singleaxis` Co-Authored-By: Will Holmgren Co-Authored-By: Mark Mikofski --- pvlib/shading.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 00bac7397d..3665bbdf35 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -275,8 +275,15 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, pp. 747-753, 2011, :doi:`10.1002/pip.1085`. """ - # Notation from [1], modified to use zenith instead of elevation + # Assume the tracker reference frame is right-handed. Positive y-axis is + # oriented along tracking axis; from north, the y-axis is rotated clockwise + # by the axis azimuth and tilted from horizontal by the axis tilt. The + # positive x-axis is 90 deg clockwise from the y-axis and parallel to + # horizontal (e.g., if the y-axis is south, the x-axis is west); the + # positive z-axis is normal to the x and y axes, pointed upward. + # Since elevation = 90 - zenith, sin(90-x) = cos(x) & cos(90-x) = sin(x): + # Notation from [1], modified to use zenith instead of elevation # cos(elevation) = sin(zenith) and sin(elevation) = cos(zenith) # Avoid recalculating these values sind_solar_zenith = sind(solar_zenith) @@ -295,6 +302,14 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, + sy * sind_axis_tilt * cosd_axis_azimuth + sz * cosd(axis_tilt) ) + # The ideal tracking angle wid is the rotation to place the sun position + # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and + # contains the axis of rotation. wid = 0 indicates that the panel is + # horizontal. Here, our convention is that a clockwise rotation is + # positive, to view rotation angles in the same frame of reference as + # azimuth. For example, for a system with tracking axis oriented south, a + # rotation toward the east is negative, and a rotation to the west is + # positive. This is a right-handed rotation around the tracker y-axis. # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) return theta_T From 3c9392b270004552eb0e8eb1a4a3db7ea9af520b Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:18:58 +0100 Subject: [PATCH 25/54] Singleaxis implementation port & test addition, based on old pvlib.tracking.singleaxis --- pvlib/shading.py | 3 +- pvlib/tests/test_shading.py | 98 ++++++++++++++++++++++++++++++------- pvlib/tracking.py | 53 +++----------------- 3 files changed, 89 insertions(+), 65 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 3665bbdf35..0bd7affa40 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -234,8 +234,7 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(axis_tilt, axis_azimuth, - solar_zenith, solar_azimuth): +def projected_solar_zenith_angle(axis_tilt, axis_azimuth, solar_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. This is the solar zenith angle projected onto the tracker rotation plane or diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 0e44a42354..1ecfb8d03a 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -110,9 +110,8 @@ def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss): @pytest.fixture -def true_tracking_angle_and_inputs(): - # data retrieved from NREL Slope-Aware Backtracking for Single-Axis - # Trackers +def true_tracking_angle_and_inputs_NREL(): + # data from NREL 'Slope-Aware Backtracking for Single-Axis Trackers' # doi.org/10.2172/1660126 ; Accessed on 2023-11-06. tzinfo = timezone(timedelta(hours=-5)) axis_tilt_angle = 9.666 # deg @@ -139,9 +138,69 @@ def true_tracking_angle_and_inputs(): return (axis_tilt_angle, axis_azimuth_angle, timedata) -def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): +@pytest.fixture +def singleaxis_psz_implementation_port_data(): + # data generated with the PSZ angle implementation in tracking.singleaxis + # See GitHub issue #1734 & PR #1904 + axis_tilt_angle = 12.224 + axis_azimuth_angle = 187.2 + + singleaxis_result = pd.DataFrame( + columns=[ + "Apparent Zenith", + "Solar Azimuth", + "tracker_theta", + "surface_azimuth", + "surface_tilt", + ], + data=[ + [88.86131915, 116.14911543, -84.67346, 98.330924, 84.794565], + [85.67558254, 119.46577753, -80.544188, 99.219659, 80.760477], + [82.4784391, 122.90558458, -76.226064, 100.171259, 76.5443], + [79.37555806, 126.48822166, -71.79054, 101.184411, 72.217365], + [76.40491865, 130.23239671, -67.237442, 102.276947, 67.781439], + [73.59273783, 134.15525777, -62.55178, 103.476096, 63.224495], + [70.96318968, 138.2715258, -57.713941, 104.819827, 58.53107], + [68.54068323, 142.59233032, -52.702658, 106.361922, 53.685798], + [66.35031258, 147.12377575, -47.496592, 108.18131, 48.676053], + [64.41759166, 151.8653323, -42.07579, 110.39903, 43.495367], + [62.76775062, 156.80824414, -36.423404, 113.210504, 38.148938], + [61.42469841, 161.9342438, -30.527799, 116.950922, 32.663696], + [60.40974474, 167.21493901, -24.385012, 122.236817, 27.108957], + [59.74022062, 172.61222482, -18.001341, 130.288224, 21.645102], + [59.42818646, 178.07994717, -11.395651, 143.610698, 16.652493], + [59.47944177, 183.56677914, -4.600779, 166.390187, 13.048796], + [59.89302187, 189.01995634, 2.336615, 198.108, 12.441979], + [60.66128258, 194.38926277, 9.358232, 225.094855, 15.351466], + [61.77055542, 199.63057627, 16.398369, 241.465486, 20.352345], + [63.20224386, 204.70842576, 23.389598, 251.116742, 26.231294], + [64.93416116, 209.59729217, 30.268795, 257.259578, 32.425598], + [66.94189859, 214.28170196, 36.982274, 261.49605, 38.674352], + [69.20004673, 218.75538494, 43.489104, 264.617474, 44.841832], + [71.68314725, 223.01986867, 49.762279, 267.042188, 50.852813], + [74.36628597, 227.08285659, 55.787916, 269.007999, 56.666604], + [77.22520074, 230.95665462, 61.562937, 270.658956, 62.264111], + [80.23550305, 234.65680797, 67.091395, 272.086933, 67.639267], + [83.3693091, 238.20102038, 72.378024, 273.352342, 72.790188], + [86.57992299, 241.60837123, 77.408775, 274.492262, 77.698775], + [89.70940444, 244.89880789, 82.045935, 275.505443, 82.227402], + ], + ) + singleaxis_result.index = pd.date_range( + "2024-01-25 08:40", + "2024-01-25 18:20", + freq="20min", + tz=timezone(timedelta(hours=1)), + ) + return (axis_tilt_angle, axis_azimuth_angle, singleaxis_result) + + +def test_projected_solar_zenith_angle_numeric( + true_tracking_angle_and_inputs_NREL, singleaxis_psz_implementation_port_data +): psz_func = shading.projected_solar_zenith_angle - axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs + axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL + # test against data provided by NREL psz = psz_func( axis_tilt, axis_azimuth, @@ -149,35 +208,40 @@ def test_projected_solar_zenith_angle_numeric(true_tracking_angle_and_inputs): timedata["Solar Azimuth"], ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) - # test equivalence against pvlib.tracking.singleaxis - singleaxis = pvlib.tracking.singleaxis(timedata["Apparent Zenith"], - timedata["Solar Azimuth"], - axis_tilt, axis_azimuth, - backtrack=False) - assert_allclose(psz, singleaxis["tracker_theta"]) # test by changing axis azimuth and tilt psz = psz_func( -axis_tilt, - axis_azimuth-180, + axis_azimuth - 180, timedata["Apparent Zenith"], timedata["Solar Azimuth"], ) assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) + # test implementation port from tracking.singleaxis + axis_tilt, axis_azimuth, singleaxis = singleaxis_psz_implementation_port_data + psz = pvlib.tracking.singleaxis( + singleaxis["Apparent Zenith"], + singleaxis["Solar Azimuth"], + axis_tilt, + axis_azimuth, + backtrack=False, + ) + assert_allclose(psz["tracker_theta"], singleaxis["tracker_theta"], atol=1e-6) + @pytest.mark.parametrize( "cast_type, cast_func", [ - (float, float), + (float, lambda x: float(x)), (np.ndarray, lambda x: np.array([x])), (pd.Series, lambda x: pd.Series(data=[x])), ], ) def test_projected_solar_zenith_angle_datatypes( - cast_type, cast_func, true_tracking_angle_and_inputs + cast_type, cast_func, true_tracking_angle_and_inputs_NREL ): psz_func = shading.projected_solar_zenith_angle - axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs + axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL sun_apparent_zenith = timedata["Apparent Zenith"].iloc[0] sun_azimuth = timedata["Solar Azimuth"].iloc[0] @@ -187,7 +251,5 @@ def test_projected_solar_zenith_angle_datatypes( cast_func(sun_apparent_zenith), cast_func(sun_azimuth), ) - psz = psz_func( - axis_tilt, axis_azimuth, sun_apparent_zenith, axis_azimuth - ) + psz = psz_func(axis_tilt, axis_azimuth, sun_apparent_zenith, axis_azimuth) assert isinstance(psz, cast_type) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index c137f2115e..faa566b2da 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -3,6 +3,7 @@ from pvlib.tools import cosd, sind, tand, acosd, asind from pvlib import irradiance +from pvlib import shading def singleaxis(apparent_zenith, apparent_azimuth, @@ -126,51 +127,13 @@ def singleaxis(apparent_zenith, apparent_azimuth, if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1: raise ValueError('Input dimensions must not exceed 1') - # Calculate sun position x, y, z using coordinate system as in [1], Eq 1. - - # NOTE: solar elevation = 90 - solar zenith, then use trig identities: - # sin(90-x) = cos(x) & cos(90-x) = sin(x) - sin_zenith = sind(apparent_zenith) - x = sin_zenith * sind(apparent_azimuth) - y = sin_zenith * cosd(apparent_azimuth) - z = cosd(apparent_zenith) - - # Assume the tracker reference frame is right-handed. Positive y-axis is - # oriented along tracking axis; from north, the y-axis is rotated clockwise - # by the axis azimuth and tilted from horizontal by the axis tilt. The - # positive x-axis is 90 deg clockwise from the y-axis and parallel to - # horizontal (e.g., if the y-axis is south, the x-axis is west); the - # positive z-axis is normal to the x and y axes, pointed upward. - - # Calculate sun position (xp, yp, zp) in tracker coordinate system using - # [1] Eq 4. - - cos_axis_azimuth = cosd(axis_azimuth) - sin_axis_azimuth = sind(axis_azimuth) - cos_axis_tilt = cosd(axis_tilt) - sin_axis_tilt = sind(axis_tilt) - xp = x*cos_axis_azimuth - y*sin_axis_azimuth - # not necessary to calculate y' - # yp = (x*cos_axis_tilt*sin_axis_azimuth - # + y*cos_axis_tilt*cos_axis_azimuth - # - z*sin_axis_tilt) - zp = (x*sin_axis_tilt*sin_axis_azimuth - + y*sin_axis_tilt*cos_axis_azimuth - + z*cos_axis_tilt) - - # The ideal tracking angle wid is the rotation to place the sun position - # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and - # contains the axis of rotation. wid = 0 indicates that the panel is - # horizontal. Here, our convention is that a clockwise rotation is - # positive, to view rotation angles in the same frame of reference as - # azimuth. For example, for a system with tracking axis oriented south, a - # rotation toward the east is negative, and a rotation to the west is - # positive. This is a right-handed rotation around the tracker y-axis. - - # Calculate angle from x-y plane to projection of sun vector onto x-z plane - # using [1] Eq. 5. - - wid = np.degrees(np.arctan2(xp, zp)) + # ideal tracking angle, does not account for row-to-row shading + wid = shading.projected_solar_zenith_angle( + axis_tilt=axis_tilt, + axis_azimuth=axis_azimuth, + solar_zenith=apparent_zenith, + solar_azimuth=apparent_azimuth, + ) # filter for sun above panel horizon zen_gt_90 = apparent_zenith > 90 From 337f7f15a584aa1e12f2d4cf229ee786e32665c0 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:21:45 +0100 Subject: [PATCH 26/54] Update v0.10.4.rst --- docs/sphinx/source/whatsnew/v0.10.4.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.10.4.rst b/docs/sphinx/source/whatsnew/v0.10.4.rst index df73d68e98..e7b92ba482 100644 --- a/docs/sphinx/source/whatsnew/v0.10.4.rst +++ b/docs/sphinx/source/whatsnew/v0.10.4.rst @@ -7,6 +7,8 @@ v0.10.4 (Anticipated March, 2024) Enhancements ~~~~~~~~~~~~ +* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, + a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) Bug fixes From 0923109d84cddb26b9c9f199f1ae1091b6f97f76 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 25 Jan 2024 17:26:16 +0100 Subject: [PATCH 27/54] Linter --- pvlib/shading.py | 3 ++- pvlib/tests/test_shading.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 5efadcd718..5cf7eedb67 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -234,7 +234,8 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(axis_tilt, axis_azimuth, solar_zenith, solar_azimuth): +def projected_solar_zenith_angle(axis_tilt, axis_azimuth, + solar_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. This is the solar zenith angle projected onto the tracker rotation plane or diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 1ecfb8d03a..85e2335bae 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -196,7 +196,8 @@ def singleaxis_psz_implementation_port_data(): def test_projected_solar_zenith_angle_numeric( - true_tracking_angle_and_inputs_NREL, singleaxis_psz_implementation_port_data + true_tracking_angle_and_inputs_NREL, + singleaxis_psz_implementation_port_data ): psz_func = shading.projected_solar_zenith_angle axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL @@ -218,7 +219,8 @@ def test_projected_solar_zenith_angle_numeric( assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) # test implementation port from tracking.singleaxis - axis_tilt, axis_azimuth, singleaxis = singleaxis_psz_implementation_port_data + axis_tilt, axis_azimuth, singleaxis = \ + singleaxis_psz_implementation_port_data psz = pvlib.tracking.singleaxis( singleaxis["Apparent Zenith"], singleaxis["Solar Azimuth"], @@ -226,7 +228,11 @@ def test_projected_solar_zenith_angle_numeric( axis_azimuth, backtrack=False, ) - assert_allclose(psz["tracker_theta"], singleaxis["tracker_theta"], atol=1e-6) + assert_allclose( + psz["tracker_theta"], + singleaxis["tracker_theta"], + atol=1e-6 + ) @pytest.mark.parametrize( From 6955f716b8295c398dbaa5349320357d60aa59b9 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:06:24 +0100 Subject: [PATCH 28/54] Code review Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- pvlib/shading.py | 25 +++++-------------------- pvlib/tracking.py | 9 ++++++++- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 5cf7eedb67..728845281e 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -238,11 +238,11 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, solar_zenith, solar_azimuth): r""" Calculate projected solar zenith angle in degrees. - This is the solar zenith angle projected onto the tracker rotation plane or - the plane defined by its normal vector and the azimuth. - Computing said value is common in track and shadow algorithms. - See [1]_ [2]_ [3]_. + This solar zenith angle is projected onto the plane whose normal vector is + defined by ``axis_tilt`` and ``axis_azimuth``. The normal vector is in the + direction of ``axis_azimuth`` (clockwise from north) and tilted from + horizontal by ``axis_tilt``. See Figure 5 in [1]_. Parameters ---------- @@ -265,15 +265,8 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, ---------- .. [1] K. Anderson and M. Mikofski, 'Slope-Aware Backtracking for Single-Axis Trackers', National Renewable Energy Lab. (NREL), Golden, - CO (United States); Det Norske Veritas Group, Oslo (Norway), + CO (United States); NREL/TP-5K00-76626, Jul. 2020. :doi:`10.2172/1660126`. - .. [2] W. F. Marion and A. P. Dobos, 'Rotation Angle for the Optimum - Tracking of One-Axis Trackers', National Renewable Energy Lab. (NREL), - Golden, CO (United States), NREL/TP-6A20-58891, Jul. 2013. - :doi:`10.2172/1089596`. - .. [3] E. Lorenzo, L. Narvarte, and J. Muñoz, 'Tracking and back-tracking', - Progress in Photovoltaics: Research and Applications, vol. 19, no. 6, - pp. 747-753, 2011, :doi:`10.1002/pip.1085`. """ # Assume the tracker reference frame is right-handed. Positive y-axis is # oriented along tracking axis; from north, the y-axis is rotated clockwise @@ -302,14 +295,6 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, + sy * sind_axis_tilt * cosd_axis_azimuth + sz * cosd(axis_tilt) ) - # The ideal tracking angle wid is the rotation to place the sun position - # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and - # contains the axis of rotation. wid = 0 indicates that the panel is - # horizontal. Here, our convention is that a clockwise rotation is - # positive, to view rotation angles in the same frame of reference as - # azimuth. For example, for a system with tracking axis oriented south, a - # rotation toward the east is negative, and a rotation to the west is - # positive. This is a right-handed rotation around the tracker y-axis. # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) return theta_T diff --git a/pvlib/tracking.py b/pvlib/tracking.py index faa566b2da..9c4103e7f0 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -127,7 +127,14 @@ def singleaxis(apparent_zenith, apparent_azimuth, if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1: raise ValueError('Input dimensions must not exceed 1') - # ideal tracking angle, does not account for row-to-row shading + # The ideal tracking angle wid is the rotation to place the sun position + # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and + # contains the axis of rotation. wid = 0 indicates that the panel is + # horizontal. Here, our convention is that a clockwise rotation is + # positive, to view rotation angles in the same frame of reference as + # azimuth. For example, for a system with tracking axis oriented south, a + # rotation toward the east is negative, and a rotation to the west is + # positive. This is a right-handed rotation around the tracker y-axis. wid = shading.projected_solar_zenith_angle( axis_tilt=axis_tilt, axis_azimuth=axis_azimuth, From 939b241f802f2d84aac096c0185f024fd4b25595 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:12:49 +0100 Subject: [PATCH 29/54] Add Fig 5 [1] (still gotta check the built output) --- .../_images/Anderson_Mikofski_2020_Fig5.jpg | Bin 0 -> 35024 bytes pvlib/shading.py | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig5.jpg diff --git a/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig5.jpg b/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1bd2ecc9960657d16e2c70608293b775a84a6dfa GIT binary patch literal 35024 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 literal 0 HcmV?d00001 diff --git a/pvlib/shading.py b/pvlib/shading.py index 728845281e..345165364f 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -244,6 +244,10 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, direction of ``axis_azimuth`` (clockwise from north) and tilted from horizontal by ``axis_tilt``. See Figure 5 in [1]_. + .. image:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg + :alt: Fig. 5: Solar coordinates projection onto tracker rotation plane. + :align: center + Parameters ---------- axis_tilt : numeric From b0d6f66db556c6207e15e74fae3975b53be51c35 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:28:42 +0100 Subject: [PATCH 30/54] Add caption, change size and describe in alternate text --- pvlib/shading.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 345165364f..7f5239d469 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -242,11 +242,14 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, This solar zenith angle is projected onto the plane whose normal vector is defined by ``axis_tilt`` and ``axis_azimuth``. The normal vector is in the direction of ``axis_azimuth`` (clockwise from north) and tilted from - horizontal by ``axis_tilt``. See Figure 5 in [1]_. + horizontal by ``axis_tilt``. See Figure 5 in [1]_: .. image:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg - :alt: Fig. 5: Solar coordinates projection onto tracker rotation plane. + :alt: Wire diagram of coordinates systems to obtain the projected angle. :align: center + :scale: 75 % + + Fig. 5, [1]_: Solar coordinates projection onto tracker rotation plane. Parameters ---------- From 3df2e71c8c020746b5fcfc6d25bc678c30134aa7 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:20:08 +0100 Subject: [PATCH 31/54] rST fixes ? --- pvlib/shading.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 7f5239d469..75e58260bd 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -245,11 +245,11 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, horizontal by ``axis_tilt``. See Figure 5 in [1]_: .. image:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg - :alt: Wire diagram of coordinates systems to obtain the projected angle. - :align: center - :scale: 75 % + :alt: Wire diagram of coordinates systems to obtain the projected angle. + :align: center + :scale: 75 % - Fig. 5, [1]_: Solar coordinates projection onto tracker rotation plane. + Fig. 5, [1]_: Solar coordinates projection onto tracker rotation plane. Parameters ---------- From 428852cb3924f922dfebec2166e42f3236ddb092 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:49:45 +0100 Subject: [PATCH 32/54] Figures have captions, images do not https://pandemic-overview.readthedocs.io/en/latest/myGuides/reStructuredText-Images-and-Figures-Examples.html#id18 --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 75e58260bd..06cdb916d1 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -244,7 +244,7 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, direction of ``axis_azimuth`` (clockwise from north) and tilted from horizontal by ``axis_tilt``. See Figure 5 in [1]_: - .. image:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg + .. figure:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg :alt: Wire diagram of coordinates systems to obtain the projected angle. :align: center :scale: 75 % From aed85a3e6deb7d2092bd7d32d945b9d32beb0d7f Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 8 Feb 2024 23:01:36 +0100 Subject: [PATCH 33/54] Flip arguments order --- pvlib/shading.py | 12 ++++++------ pvlib/tests/test_shading.py | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 06cdb916d1..0c528f1113 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -234,8 +234,8 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(axis_tilt, axis_azimuth, - solar_zenith, solar_azimuth): +def projected_solar_zenith_angle(solar_zenith, solar_azimuth, + axis_tilt, axis_azimuth): r""" Calculate projected solar zenith angle in degrees. @@ -253,15 +253,15 @@ def projected_solar_zenith_angle(axis_tilt, axis_azimuth, Parameters ---------- + solar_zenith : numeric + Sun's apparent zenith in degrees. + solar_azimuth : numeric + Sun's azimuth in degrees. axis_tilt : numeric Axis tilt angle in degrees. From horizontal plane to array plane. axis_azimuth : numeric Axis azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° - solar_zenith : numeric - Sun's apparent zenith in degrees. - solar_azimuth : numeric - Sun's azimuth in degrees. Returns ------- diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 85e2335bae..35e1605ba9 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -203,18 +203,18 @@ def test_projected_solar_zenith_angle_numeric( axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL # test against data provided by NREL psz = psz_func( - axis_tilt, - axis_azimuth, timedata["Apparent Zenith"], timedata["Solar Azimuth"], + axis_tilt, + axis_azimuth, ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) # test by changing axis azimuth and tilt psz = psz_func( - -axis_tilt, - axis_azimuth - 180, timedata["Apparent Zenith"], timedata["Solar Azimuth"], + -axis_tilt, + axis_azimuth - 180, ) assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) @@ -252,10 +252,10 @@ def test_projected_solar_zenith_angle_datatypes( sun_azimuth = timedata["Solar Azimuth"].iloc[0] axis_tilt, axis_azimuth, sun_apparent_zenith, sun_azimuth = ( - cast_func(axis_tilt), - cast_func(axis_azimuth), cast_func(sun_apparent_zenith), cast_func(sun_azimuth), + cast_func(axis_tilt), + cast_func(axis_azimuth), ) - psz = psz_func(axis_tilt, axis_azimuth, sun_apparent_zenith, axis_azimuth) + psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth) assert isinstance(psz, cast_type) From 5d37fb36a65685c8ff49791732c5b20b8c078880 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 8 Feb 2024 23:02:57 +0100 Subject: [PATCH 34/54] I forgot :skull: --- pvlib/tests/test_shading.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 35e1605ba9..f9770c0497 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -218,22 +218,6 @@ def test_projected_solar_zenith_angle_numeric( ) assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) - # test implementation port from tracking.singleaxis - axis_tilt, axis_azimuth, singleaxis = \ - singleaxis_psz_implementation_port_data - psz = pvlib.tracking.singleaxis( - singleaxis["Apparent Zenith"], - singleaxis["Solar Azimuth"], - axis_tilt, - axis_azimuth, - backtrack=False, - ) - assert_allclose( - psz["tracker_theta"], - singleaxis["tracker_theta"], - atol=1e-6 - ) - @pytest.mark.parametrize( "cast_type, cast_func", From 38c2d4d6b6b6ca233ae0df57a81e231299458e4d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 8 Feb 2024 23:04:49 +0100 Subject: [PATCH 35/54] Linter are you happy now? --- pvlib/tests/test_shading.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index f9770c0497..f1f26a2da6 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -6,7 +6,6 @@ import pytest from datetime import timezone, timedelta -import pvlib from pvlib import shading From 23aaa2aa4fa7e2188974e355bc2c675c073a2228 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:03:29 +0100 Subject: [PATCH 36/54] Remove port test and add edge cases test Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/tests/test_shading.py | 113 +++++++++++++++--------------------- 1 file changed, 46 insertions(+), 67 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index f1f26a2da6..a0fd7beac7 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -11,21 +11,22 @@ @pytest.fixture def test_system(): - syst = {'height': 1.0, - 'pitch': 2., - 'surface_tilt': 30., - 'surface_azimuth': 180., - 'rotation': -30.} # rotation of right edge relative to horizontal - syst['gcr'] = 1.0 / syst['pitch'] + syst = { + "height": 1.0, + "pitch": 2.0, + "surface_tilt": 30.0, + "surface_azimuth": 180.0, + "rotation": -30.0, + } # rotation of right edge relative to horizontal + syst["gcr"] = 1.0 / syst["pitch"] return syst def test__ground_angle(test_system): ts = test_system - x = np.array([0., 0.5, 1.0]) - angles = shading.ground_angle( - ts['surface_tilt'], ts['gcr'], x) - expected_angles = np.array([0., 5.866738789543952, 9.896090638982903]) + x = np.array([0.0, 0.5, 1.0]) + angles = shading.ground_angle(ts["surface_tilt"], ts["gcr"], x) + expected_angles = np.array([0.0, 5.866738789543952, 9.896090638982903]) assert np.allclose(angles, expected_angles) @@ -138,70 +139,36 @@ def true_tracking_angle_and_inputs_NREL(): @pytest.fixture -def singleaxis_psz_implementation_port_data(): - # data generated with the PSZ angle implementation in tracking.singleaxis - # See GitHub issue #1734 & PR #1904 - axis_tilt_angle = 12.224 - axis_azimuth_angle = 187.2 - - singleaxis_result = pd.DataFrame( - columns=[ - "Apparent Zenith", - "Solar Azimuth", - "tracker_theta", - "surface_azimuth", - "surface_tilt", - ], +def projected_solar_zenith_angle_edge_cases(): + premises_and_result_matrix = pd.DataFrame( data=[ - [88.86131915, 116.14911543, -84.67346, 98.330924, 84.794565], - [85.67558254, 119.46577753, -80.544188, 99.219659, 80.760477], - [82.4784391, 122.90558458, -76.226064, 100.171259, 76.5443], - [79.37555806, 126.48822166, -71.79054, 101.184411, 72.217365], - [76.40491865, 130.23239671, -67.237442, 102.276947, 67.781439], - [73.59273783, 134.15525777, -62.55178, 103.476096, 63.224495], - [70.96318968, 138.2715258, -57.713941, 104.819827, 58.53107], - [68.54068323, 142.59233032, -52.702658, 106.361922, 53.685798], - [66.35031258, 147.12377575, -47.496592, 108.18131, 48.676053], - [64.41759166, 151.8653323, -42.07579, 110.39903, 43.495367], - [62.76775062, 156.80824414, -36.423404, 113.210504, 38.148938], - [61.42469841, 161.9342438, -30.527799, 116.950922, 32.663696], - [60.40974474, 167.21493901, -24.385012, 122.236817, 27.108957], - [59.74022062, 172.61222482, -18.001341, 130.288224, 21.645102], - [59.42818646, 178.07994717, -11.395651, 143.610698, 16.652493], - [59.47944177, 183.56677914, -4.600779, 166.390187, 13.048796], - [59.89302187, 189.01995634, 2.336615, 198.108, 12.441979], - [60.66128258, 194.38926277, 9.358232, 225.094855, 15.351466], - [61.77055542, 199.63057627, 16.398369, 241.465486, 20.352345], - [63.20224386, 204.70842576, 23.389598, 251.116742, 26.231294], - [64.93416116, 209.59729217, 30.268795, 257.259578, 32.425598], - [66.94189859, 214.28170196, 36.982274, 261.49605, 38.674352], - [69.20004673, 218.75538494, 43.489104, 264.617474, 44.841832], - [71.68314725, 223.01986867, 49.762279, 267.042188, 50.852813], - [74.36628597, 227.08285659, 55.787916, 269.007999, 56.666604], - [77.22520074, 230.95665462, 61.562937, 270.658956, 62.264111], - [80.23550305, 234.65680797, 67.091395, 272.086933, 67.639267], - [83.3693091, 238.20102038, 72.378024, 273.352342, 72.790188], - [86.57992299, 241.60837123, 77.408775, 274.492262, 77.698775], - [89.70940444, 244.89880789, 82.045935, 275.505443, 82.227402], + # s_zen | s_azim | ax_tilt | ax_azim | psza + [0, 0, 0, 0, 0], + [0, 180, 0, 0, 0], + [0, 0, 0, 180, 0], + [0, 180, 0, 180, 0], + [45, 0, 0, 180, 0], + [45, 90, 0, 180, -45], + [45, 270, 0, 180, 45], + [45, 90, 90, 180, -90], + [45, 270, 90, 180, 90], + [45, 90, 90, 0, 90], + [45, 270, 90, 0, -90], + [45, 45, 90, 180, -135], + [45, 315, 90, 180, 135], ], + columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", "psza"], ) - singleaxis_result.index = pd.date_range( - "2024-01-25 08:40", - "2024-01-25 18:20", - freq="20min", - tz=timezone(timedelta(hours=1)), - ) - return (axis_tilt_angle, axis_azimuth_angle, singleaxis_result) + return premises_and_result_matrix def test_projected_solar_zenith_angle_numeric( - true_tracking_angle_and_inputs_NREL, - singleaxis_psz_implementation_port_data + true_tracking_angle_and_inputs_NREL, projected_solar_zenith_angle_edge_cases ): - psz_func = shading.projected_solar_zenith_angle + psza_func = shading.projected_solar_zenith_angle axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL # test against data provided by NREL - psz = psz_func( + psz = psza_func( timedata["Apparent Zenith"], timedata["Solar Azimuth"], axis_tilt, @@ -209,13 +176,25 @@ def test_projected_solar_zenith_angle_numeric( ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) # test by changing axis azimuth and tilt - psz = psz_func( + psza = psza_func( timedata["Apparent Zenith"], timedata["Solar Azimuth"], -axis_tilt, axis_azimuth - 180, ) - assert_allclose(psz, -timedata["True-Tracking"], atol=1e-3) + assert_allclose(psza, -timedata["True-Tracking"], atol=1e-3) + + # test edge cases + solar_zenith, solar_azimuth, axis_tilt, axis_azimuth, psza_expected = ( + v for _, v in projected_solar_zenith_angle_edge_cases.items() + ) + psza = psza_func( + solar_zenith, + solar_azimuth, + axis_tilt, + axis_azimuth, + ) + assert_allclose(psza, psza_expected, atol=1e-9) @pytest.mark.parametrize( From 2c0fa512c81bd2111e54935083aefaea10903cdb Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:18:13 +0100 Subject: [PATCH 37/54] Update test_shading.py Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/tests/test_shading.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index a0fd7beac7..f570c4eb89 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -142,28 +142,30 @@ def true_tracking_angle_and_inputs_NREL(): def projected_solar_zenith_angle_edge_cases(): premises_and_result_matrix = pd.DataFrame( data=[ - # s_zen | s_azim | ax_tilt | ax_azim | psza - [0, 0, 0, 0, 0], - [0, 180, 0, 0, 0], - [0, 0, 0, 180, 0], - [0, 180, 0, 180, 0], - [45, 0, 0, 180, 0], - [45, 90, 0, 180, -45], - [45, 270, 0, 180, 45], - [45, 90, 90, 180, -90], - [45, 270, 90, 180, 90], - [45, 90, 90, 0, 90], - [45, 270, 90, 0, -90], - [45, 45, 90, 180, -135], - [45, 315, 90, 180, 135], + # s_zen | s_azim | ax_tilt | ax_azim | psza + [0, 0, 0, 0, 0], + [0, 180, 0, 0, 0], + [0, 0, 0, 180, 0], + [0, 180, 0, 180, 0], + [45, 0, 0, 180, 0], + [45, 90, 0, 180, -45], + [45, 270, 0, 180, 45], + [45, 90, 90, 180, -90], + [45, 270, 90, 180, 90], + [45, 90, 90, 0, 90], + [45, 270, 90, 0, -90], + [45, 45, 90, 180, -135], + [45, 315, 90, 180, 135], ], - columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", "psza"], + columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", + "psza"], ) return premises_and_result_matrix def test_projected_solar_zenith_angle_numeric( - true_tracking_angle_and_inputs_NREL, projected_solar_zenith_angle_edge_cases +true_tracking_angle_and_inputs_NREL, + projected_solar_zenith_angle_edge_cases ): psza_func = shading.projected_solar_zenith_angle axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL From 5b49706e322bca84b1083a33e15b49b1887d9435 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:12:45 +0100 Subject: [PATCH 38/54] Indentation xd --- pvlib/tests/test_shading.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index f570c4eb89..3d398ba980 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -142,20 +142,20 @@ def true_tracking_angle_and_inputs_NREL(): def projected_solar_zenith_angle_edge_cases(): premises_and_result_matrix = pd.DataFrame( data=[ - # s_zen | s_azim | ax_tilt | ax_azim | psza - [0, 0, 0, 0, 0], - [0, 180, 0, 0, 0], - [0, 0, 0, 180, 0], - [0, 180, 0, 180, 0], - [45, 0, 0, 180, 0], - [45, 90, 0, 180, -45], - [45, 270, 0, 180, 45], - [45, 90, 90, 180, -90], - [45, 270, 90, 180, 90], - [45, 90, 90, 0, 90], - [45, 270, 90, 0, -90], - [45, 45, 90, 180, -135], - [45, 315, 90, 180, 135], + # s_zen | s_azm | ax_tilt | ax_azm | psza + [ 0, 0, 0, 0, 0], + [ 0, 180, 0, 0, 0], + [ 0, 0, 0, 180, 0], + [ 0, 180, 0, 180, 0], + [ 45, 0, 0, 180, 0], + [ 45, 90, 0, 180, -45], + [ 45, 270, 0, 180, 45], + [ 45, 90, 90, 180, -90], + [ 45, 270, 90, 180, 90], + [ 45, 90, 90, 0, 90], + [ 45, 270, 90, 0, -90], + [ 45, 45, 90, 180, -135], + [ 45, 315, 90, 180, 135], ], columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", "psza"], From 1a6839009621c2393ec921ba3b80a446e1442113 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:14:46 +0100 Subject: [PATCH 39/54] Update test_shading.py --- pvlib/tests/test_shading.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 3d398ba980..8c8dcc342e 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -142,20 +142,20 @@ def true_tracking_angle_and_inputs_NREL(): def projected_solar_zenith_angle_edge_cases(): premises_and_result_matrix = pd.DataFrame( data=[ - # s_zen | s_azm | ax_tilt | ax_azm | psza - [ 0, 0, 0, 0, 0], - [ 0, 180, 0, 0, 0], - [ 0, 0, 0, 180, 0], - [ 0, 180, 0, 180, 0], - [ 45, 0, 0, 180, 0], - [ 45, 90, 0, 180, -45], - [ 45, 270, 0, 180, 45], - [ 45, 90, 90, 180, -90], - [ 45, 270, 90, 180, 90], - [ 45, 90, 90, 0, 90], - [ 45, 270, 90, 0, -90], - [ 45, 45, 90, 180, -135], - [ 45, 315, 90, 180, 135], + # s_zen | s_azm | ax_tilt | ax_azm | psza + [ 0, 0, 0, 0, 0], + [ 0, 180, 0, 0, 0], + [ 0, 0, 0, 180, 0], + [ 0, 180, 0, 180, 0], + [ 45, 0, 0, 180, 0], + [ 45, 90, 0, 180, -45], + [ 45, 270, 0, 180, 45], + [ 45, 90, 90, 180, -90], + [ 45, 270, 90, 180, 90], + [ 45, 90, 90, 0, 90], + [ 45, 270, 90, 0, -90], + [ 45, 45, 90, 180, -135], + [ 45, 315, 90, 180, 135], ], columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", "psza"], From 58d853f36b39338e3c42aadf05ebcf4ca8f59e06 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:17:58 +0100 Subject: [PATCH 40/54] I forgot how to code --- pvlib/tests/test_shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 8c8dcc342e..a5dc03ebfc 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -164,7 +164,7 @@ def projected_solar_zenith_angle_edge_cases(): def test_projected_solar_zenith_angle_numeric( -true_tracking_angle_and_inputs_NREL, + true_tracking_angle_and_inputs_NREL, projected_solar_zenith_angle_edge_cases ): psza_func = shading.projected_solar_zenith_angle From 2ba4cf7c6511d9e256313579a2f056bb693600f5 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 9 Feb 2024 20:27:34 +0100 Subject: [PATCH 41/54] Align data --- pvlib/tests/test_shading.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index a5dc03ebfc..8d609d1e3f 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -143,19 +143,19 @@ def projected_solar_zenith_angle_edge_cases(): premises_and_result_matrix = pd.DataFrame( data=[ # s_zen | s_azm | ax_tilt | ax_azm | psza - [ 0, 0, 0, 0, 0], + [ 0, 0, 0, 0, 0], [ 0, 180, 0, 0, 0], - [ 0, 0, 0, 180, 0], + [ 0, 0, 0, 180, 0], [ 0, 180, 0, 180, 0], - [ 45, 0, 0, 180, 0], - [ 45, 90, 0, 180, -45], - [ 45, 270, 0, 180, 45], - [ 45, 90, 90, 180, -90], - [ 45, 270, 90, 180, 90], - [ 45, 90, 90, 0, 90], - [ 45, 270, 90, 0, -90], - [ 45, 45, 90, 180, -135], - [ 45, 315, 90, 180, 135], + [ 45, 0, 0, 180, 0], + [ 45, 90, 0, 180, -45], + [ 45, 270, 0, 180, 45], + [ 45, 90, 90, 180, -90], + [ 45, 270, 90, 180, 90], + [ 45, 90, 90, 0, 90], + [ 45, 270, 90, 0, -90], + [ 45, 45, 90, 180, -135], + [ 45, 315, 90, 180, 135], ], columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", "psza"], From 069688e238733b61c20489ffc5e4349d1ea9d34e Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:57:58 +0100 Subject: [PATCH 42/54] Docstring suggestion from Kevin Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- pvlib/shading.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pvlib/shading.py b/pvlib/shading.py index 0c528f1113..ecca16e705 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -268,12 +268,50 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, Projected_solar_zenith : numeric In degrees. + Notes + ----- + This projection has a variety of applications in PV. For example: + + - Projecting the sun's position onto the plane perpendicular to + the axis of a single-axis tracker (i.e. the plane + whose normal vector coincides with the tracker torque tube) + yields the tracker rotation angle that maximizes direct irradiance + capture. This tracking strategy is called + :ref:`true-tracking + `. + + - Self-shading in large PV arrays is often modeled by assuming + a simplified 2-D array geometry where the sun's position is + projected onto the plane perpendicular to the PV rows. + The projected zenith angle is then used for calculations + regarding row-to-row shading. + + Examples + -------- + + Calculate the ideal true-tracking angle for a horizontal north-south + single-axis tracker: + + >>> rotation = projected_solar_zenith_angle(solar_zenith, solar_azimuth, + >>> axis_tilt=0, axis_azimuth=180) + + Calculate the projected zenith angle in a south-facing fixed tilt array + (note: the ``axis_azimuth`` of a fixed-tilt row points along the length + of the row): + + >>> psza = projected_solar_zenith_angle(solar_zenith, solar_azimuth, + >>> axis_tilt=0, axis_azimuth=90) + References ---------- .. [1] K. Anderson and M. Mikofski, 'Slope-Aware Backtracking for Single-Axis Trackers', National Renewable Energy Lab. (NREL), Golden, CO (United States); NREL/TP-5K00-76626, Jul. 2020. :doi:`10.2172/1660126`. + + See Also + -------- + pvlib.solarposition.get_solarposition """ # Assume the tracker reference frame is right-handed. Positive y-axis is # oriented along tracking axis; from north, the y-axis is rotated clockwise From c24922492cd5db7015c0970145ce705df705a0d5 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:24:04 +0100 Subject: [PATCH 43/54] Update link to example? --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index ecca16e705..73aad6de70 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -278,7 +278,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called :ref:`true-tracking - `. + `. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From f6c245f649e1b4214600a41d6e36fbb21330470d Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:40:06 +0100 Subject: [PATCH 44/54] Link, please work --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 73aad6de70..cf443b57fe 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -278,7 +278,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called :ref:`true-tracking - `. + `. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From b52f51df38cb61f95f4f32b81241fe5c11e3c2e4 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:40:39 +0100 Subject: [PATCH 45/54] Update shading.py --- pvlib/shading.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index cf443b57fe..83317a6c42 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -288,7 +288,6 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, Examples -------- - Calculate the ideal true-tracking angle for a horizontal north-south single-axis tracker: From 96ce603c43f089c4c7511c697026b695336717e6 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:06:20 +0100 Subject: [PATCH 46/54] Update shading.py --- pvlib/shading.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 83317a6c42..363d39a262 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -277,8 +277,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called - :ref:`true-tracking - `. + :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py`. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From ff424633fd3aea040c846a8306a2e355ca223e00 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:22:36 +0100 Subject: [PATCH 47/54] Update shading.py --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 363d39a262..16ed8c702f 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -276,8 +276,8 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, the axis of a single-axis tracker (i.e. the plane whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance - capture. This tracking strategy is called - :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py`. + capture. This tracking strategy is called + :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py` - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 1c28e7f7617558f457fd6aea9efa8a32333b9bec Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 01:30:02 +0100 Subject: [PATCH 48/54] Update shading.py --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 16ed8c702f..4b7296d929 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -276,8 +276,8 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, the axis of a single-axis tracker (i.e. the plane whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance - capture. This tracking strategy is called - :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py` + capture. This tracking strategy is called true-tracking. + See :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py`. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From cd346e95df3071e49e7de46fe4bb814696067aab Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:15:44 +0100 Subject: [PATCH 49/54] Update shading.py --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 4b7296d929..046f6d3d6b 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -277,7 +277,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called true-tracking. - See :ref:`sphx_glr_gallery_solar_tracking_plot_single_axis_tracking.py`. + See :ref:`_sphx_glr_gallery_solar-tracking_plot_single_axis_tracking.py`. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 796c7a9cc627411f33a0313afd36cba78528ba2e Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:34:04 +0100 Subject: [PATCH 50/54] Update shading.py --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 046f6d3d6b..3ba98915ef 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -276,8 +276,8 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, the axis of a single-axis tracker (i.e. the plane whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance - capture. This tracking strategy is called true-tracking. - See :ref:`_sphx_glr_gallery_solar-tracking_plot_single_axis_tracking.py`. + capture. This tracking strategy is called :ref:`true-tracking + `. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 761750cc75f97ba9a428286f55a38e09eccee769 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:40:28 +0100 Subject: [PATCH 51/54] Update shading.py --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 3ba98915ef..1791295c01 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -277,7 +277,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called :ref:`true-tracking - `. + `. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 2be29f2d086d083c22788f5932767e681b9174e9 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:50:50 +0100 Subject: [PATCH 52/54] Update shading.py --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 1791295c01..970db1e870 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -276,8 +276,8 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, the axis of a single-axis tracker (i.e. the plane whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance - capture. This tracking strategy is called :ref:`true-tracking - `. + capture. This tracking strategy is called *true-tracking*. Learn more + about tracking in :ref:`sphx_glr_gallery_solar-tracking_plot_single_axis_tracking.py`. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 8beef5598c85d409edafba2c5a8e3dfa4b1cb8a0 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:58:57 +0100 Subject: [PATCH 53/54] Lintaaaaaaarrrgh Fixed the link finally --- pvlib/shading.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 970db1e870..1b75821ad7 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -277,7 +277,8 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, whose normal vector coincides with the tracker torque tube) yields the tracker rotation angle that maximizes direct irradiance capture. This tracking strategy is called *true-tracking*. Learn more - about tracking in :ref:`sphx_glr_gallery_solar-tracking_plot_single_axis_tracking.py`. + about tracking in + :ref:`sphx_glr_gallery_solar-tracking_plot_single_axis_tracking.py`. - Self-shading in large PV arrays is often modeled by assuming a simplified 2-D array geometry where the sun's position is From 5e2be60e3bc51669c49bb00759f5f36407e56375 Mon Sep 17 00:00:00 2001 From: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 4 Mar 2024 22:54:38 +0100 Subject: [PATCH 54/54] Update pvlib/shading.py Co-authored-by: Kevin Anderson --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 1b75821ad7..007e24e1b7 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -247,7 +247,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, .. figure:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg :alt: Wire diagram of coordinates systems to obtain the projected angle. :align: center - :scale: 75 % + :scale: 50 % Fig. 5, [1]_: Solar coordinates projection onto tracker rotation plane.