From c87885c079caccd5ee65d50104ede2eec7493418 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 001/185] Update shading.py --- pvlib/shading.py | 108 +++++------------------------------------------ 1 file changed, 11 insertions(+), 97 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 007e24e1b7..848a28c217 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -85,7 +85,7 @@ def masking_angle(surface_tilt, gcr, slant_height): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - :doi:`10.1016/0379-6787(84)90017-6` + DOI: 10.1016/0379-6787(84)90017-6 .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical Reference Update", NREL Technical Report NREL/TP-6A20-67399. Available at https://www.nrel.gov/docs/fy18osti/67399.pdf @@ -167,7 +167,7 @@ def masking_angle_passias(surface_tilt, gcr): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - :doi:`10.1016/0379-6787(84)90017-6` + DOI: 10.1016/0379-6787(84)90017-6 """ # wrap it in an array so that division by zero is handled well beta = np.radians(np.array(surface_tilt)) @@ -226,7 +226,7 @@ def sky_diffuse_passias(masking_angle): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - :doi:`10.1016/0379-6787(84)90017-6` + DOI: 10.1016/0379-6787(84)90017-6 .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical Reference Update", NREL Technical Report NREL/TP-6A20-67399. Available at https://www.nrel.gov/docs/fy18osti/67399.pdf @@ -234,111 +234,25 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 -def projected_solar_zenith_angle(solar_zenith, solar_azimuth, - axis_tilt, axis_azimuth): +def projected_solar_zenith_angle(apparent_zenith, azimuth): r""" Calculate projected solar zenith angle in degrees. - 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]_: - - .. figure:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg - :alt: Wire diagram of coordinates systems to obtain the projected angle. - :align: center - :scale: 50 % - - Fig. 5, [1]_: Solar coordinates projection onto tracker rotation plane. - Parameters ---------- - solar_zenith : numeric + apparent_zenith : numeric Sun's apparent zenith in degrees. - solar_azimuth : numeric + 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° Returns ------- 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 *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 - 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 - # 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) - cosd_axis_azimuth = cosd(axis_azimuth) - sind_axis_azimuth = sind(axis_azimuth) - sind_axis_tilt = sind(axis_tilt) - - # Sun's x, y, z coords - 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 = ( - sx * sind_axis_azimuth * sind_axis_tilt - + sy * sind_axis_tilt * cosd_axis_azimuth - + sz * cosd(axis_tilt) + 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)) ) - # Eq. (5); angle between sun's beam and surface - theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) - return theta_T From 4fc6cd8e0ac327975a0a97ab237c7c58c1997439 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 002/185] Minimal test --- pvlib/tests/test_shading.py | 146 +++++------------------------------- 1 file changed, 20 insertions(+), 126 deletions(-) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 8d609d1e3f..40bfcaab23 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -4,29 +4,27 @@ from pandas.testing import assert_series_equal from numpy.testing import assert_allclose import pytest -from datetime import timezone, timedelta from pvlib import shading @pytest.fixture def test_system(): - 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"] + 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'] return syst def test__ground_angle(test_system): ts = test_system - 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]) + 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]) assert np.allclose(angles, expected_angles) @@ -40,7 +38,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) @@ -109,117 +107,13 @@ def test_sky_diffuse_passias_scalar(average_masking_angle, shading_loss): assert np.isclose(loss, actual_loss) -@pytest.fixture -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 - axis_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 (axis_tilt_angle, axis_azimuth_angle, timedata) - - -@pytest.fixture -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], - ], - 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 -): - 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 = psza_func( - 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 - psza = psza_func( - timedata["Apparent Zenith"], - timedata["Solar Azimuth"], - -axis_tilt, - axis_azimuth - 180, - ) - 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( - "cast_type, cast_func", - [ - (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_NREL -): +def test_projected_solar_zenith_angle(): psz_func = shading.projected_solar_zenith_angle - 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] - - axis_tilt, axis_azimuth, sun_apparent_zenith, sun_azimuth = ( - cast_func(sun_apparent_zenith), - cast_func(sun_azimuth), - cast_func(axis_tilt), - cast_func(axis_azimuth), - ) - psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth) - assert isinstance(psz, cast_type) + 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 2a72f331817f00c3d5d32760eca0280594123ae9 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 003/185] 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 d6e80675012cd3b15e4bbffc1496a2533bd8b109 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 004/185] 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 13a56d4806d944892a4f81604e916bcaa90c378c 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 005/185] 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 f012aae858ace2edb1ad4a43edd23b44b1a58410 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 006/185] 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 6dd9a3bb03653caa93a2448d270cbdeedcc2cbeb 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 007/185] 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 e8743dbec6f986ad96f84420cdd1139ca5ff32e1 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 008/185] 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 6030774802369eec30b33f4041007bdfc6c10b20 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 009/185] 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 62763824451bacf9fd970d9f4873a483644acb76 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 010/185] 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 509699f3fb5eb0de7a10a34f77a27d18cca9ae24 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 011/185] 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 4d222fca06..87b6c72f1f 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -23,6 +23,8 @@ Enhancements * Add :py:func:`pvlib.iotools.read_solaranywhere` and :py:func:`pvlib.iotools.get_solaranywhere` for reading and retrieving SolarAnywhere solar irradiance data. (:pull:`1497`, :discuss:`1310`) +* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, + a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) Bug fixes ~~~~~~~~~ @@ -73,3 +75,4 @@ Contributors * Mark Mikofski (:ghuser:`mikofski`) * Phoebe Pearce (:ghuser:`phoebe-p`) * Eva-Maria Grommes (:ghuser:`EwaGomez`) +* Echedey Luis (:ghuser:`echedey-ls`) From 5ff8fd8e8d5fbc199088f0c583961785acf068e0 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 012/185] 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 04126e07ff1520ea4b4d8b97f0d2b26031757157 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 013/185] 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 607a24605f7fe8532e3cd6da42eb7ff0f3d10271 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 014/185] 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 240b55190bd9542b46b3780f91d038cff9e3f2f1 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 015/185] 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 997def6afa533035a3369f0c05f7f861267271a7 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 016/185] 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 79120556b9e160556e96b2648bb22374fb51c5a2 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 017/185] 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 f068e3e7e6b8d848c1382caeb7804f6be012bd44 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 018/185] 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 a5f4a850ea83514a5d1babc167f950207103b2b4 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 019/185] 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 a854e27f01a3ba28b13626bb6e325f13b1b20d0b 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 020/185] 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 2d82375c37952acfb0785093b49ea914705bfe8a 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 021/185] 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 5f2472168c2a28fe57666ba7bd3fb276edd320c3 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 022/185] Singleaxis implementation port & test addition, based on old pvlib.tracking.singleaxis --- pvlib/shading.py | 3 +- pvlib/tests/test_shading.py | 98 ++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 20 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) From 049a8cc27c43c53828d7b0f1882f15b2b6eeff66 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 023/185] 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 30abfbfe2a..c61ce8c658 100644 --- a/docs/sphinx/source/whatsnew/v0.10.4.rst +++ b/docs/sphinx/source/whatsnew/v0.10.4.rst @@ -14,6 +14,8 @@ Enhancements the SOLRAD ground station network. (:pull:`1967`) * Added metadata parsing to :py:func:`~pvlib.iotools.read_solrad` to follow the standard iotools convention of returning a tuple of (data, meta). Previously the function only returned a dataframe. (:pull:`1968`) +* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, + a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) Bug fixes From a3185df3b4d5d42a347ff3efe6933932edbea5bb 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 024/185] 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 0bd7affa40..3665bbdf35 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 b9f1a7696f341a3a344460d16e696a4fbc261a69 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 025/185] Code review Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- pvlib/shading.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 3665bbdf35..a19e02369a 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 From f7d1d8042671b745a05668c46f2d651bf7b68951 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 026/185] Add Fig 5 [1] (still gotta check the built output) --- pvlib/shading.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pvlib/shading.py b/pvlib/shading.py index a19e02369a..cc2c41a56f 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 1880f6ac0a988f41596dc37505ca4bebaa240c4e 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 027/185] 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 cc2c41a56f..a163fcfca6 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 d9f242a36ed66ff284a66ba1b72fd240ac39c95c 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 028/185] rST fixes ? --- pvlib/shading.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index a163fcfca6..38330461ba 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 379ca9593a00a439f579e82e55c3a7bcae7bb881 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 029/185] 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 38330461ba..a11500d8c7 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 62e99ad162a201c053eedec59a0009b902dbfcf6 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 030/185] 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 a11500d8c7..04d0d66ae8 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 4f584eae1a54bb01a896e147d481c1ac72d0bc6c 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 031/185] 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 238d123669246979a33997e4cef3b68c5f862050 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 032/185] 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 98a254d409cf0c93392280ff1752b42b1ced88fd 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 033/185] 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 4616175542d0ec41bc8f06cbf3924ed05a55cba3 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 034/185] 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 33ffd284f1ddad18bb561e5a846013b34a399e48 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 035/185] 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 8526bc68e77ee0fb695595d9275681ad46b9ad55 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 036/185] 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 bde3656a5d0eeb8b2fca59953bfa3c43b3c28cc8 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 037/185] 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 51e37500b8d2bc9c0580361c42521c8d7f5cef19 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 038/185] 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 35738a486c04239e7aa4a508dae6bd56066ff158 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 039/185] 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 04d0d66ae8..474b83512c 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 8d679c3a1574fe356e3e438d3ce0b2ba60508572 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 040/185] 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 474b83512c..3172accaf4 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 4a907c1ae0c436b540b37086f2bb001c25ec78c7 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Thu, 4 May 2023 00:06:02 -0700 Subject: [PATCH 041/185] add linear shade loss for thin films --- pvlib/tracking.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 9c4103e7f0..9ea476831f 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -406,3 +406,80 @@ def calc_cross_axis_tilt( # equation 26 beta_c = _calc_beta_c(v, delta_gamma, axis_tilt) return np.degrees(beta_c) + + +def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, + cross_axis_slope): + """ + Shade fraction (FS) for trackers with a common angle on an east-west slope. + + Parameters + ---------- + tracker_theta : numeric + The tracker rotation angle in degrees from horizontal. + gcr : float + The ground coverage ratio as a fraction equal to the collector width + over the horizontal row-to-row pitch. + projected_solar_zenith : numeric + Zenith angle in degrees of the solar vector projected into the plane + perpendicular to the tracker axes. + cross_axis_slope : float + Angle of the plane containing the tracker axes in degrees from + horizontal. + + Returns + ------- + shade_fraction : numeric + The fraction of the collector width shaded by an adjacent row. A + value of 1 is completely shaded and zero is no shade. + + References + ---------- + Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," + PVPMC, 2023 + """ + theta_g_rad = np.radians(cross_axis_slope) + # angle opposite shadow cast on the ground, z + angle_z = ( + np.pi / 2 - np.radians(tracker_theta) + + np.radians(projected_solar_zenith)) + # angle opposite the collector width, L + angle_gcr = ( + np.pi / 2 - np.radians(projected_solar_zenith) + - theta_g_rad) + # ratio of shadow, z, to pitch, P + zp = gcr * np.sin(angle_z) / np.sin(angle_gcr) + # there's only row-to-row shade loss if the shadow on the ground, z, is + # longer than row-to-row pitch projected on the ground, P*cos(theta_g) + zp_cos_g = zp*np.cos(theta_g_rad) + # shade fraction + fs = 0 if zp_cos_g <= 1 else 1 - 1/zp_cos_g + return fs + + +def linear_shade_loss(shade_fraction, diffuse_fraction): + """ + Fraction of power lost to linear shade loss applicable to CdTe modules like + First Solar. + + Parameters + ---------- + shade_fraction : numeric + The fraction of the collector width shaded by an adjacent row. A + value of 1 is completely shaded and zero is no shade. + diffuse_fraction : numeric + The ratio of diffuse plane of array (poa) irradiance to global poa. + A value of 1 is completely diffuse and zero is no diffuse. + + Returns + ------- + linear_shade_loss : numeric + The fraction of power lost due to linear shading. A value of 1 is all + power lost and zero is no loss. + + References + ---------- + Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," + PVPMC, 2023 + """ + return 1 - shade_fraction * (1 - diffuse_fraction) From 234288d620e4a554b3d18d945f8b4375c7e48d76 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Thu, 4 May 2023 00:39:51 -0700 Subject: [PATCH 042/185] add tests, update docs, what's new --- docs/sphinx/source/reference/tracking.rst | 2 + docs/sphinx/source/whatsnew/v0.9.6.rst | 46 +++++++++++++++++++++++ pvlib/tests/test_tracking.py | 25 ++++++++++++ pvlib/tracking.py | 18 ++++++--- 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 docs/sphinx/source/whatsnew/v0.9.6.rst diff --git a/docs/sphinx/source/reference/tracking.rst b/docs/sphinx/source/reference/tracking.rst index 6d3f7fc4eb..b5ccd48ea3 100644 --- a/docs/sphinx/source/reference/tracking.rst +++ b/docs/sphinx/source/reference/tracking.rst @@ -13,3 +13,5 @@ Functions tracking.calc_axis_tilt tracking.calc_cross_axis_tilt tracking.calc_surface_orientation + tracking.tracker_shaded_fraction + tracking.linear_shade_loss diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst new file mode 100644 index 0000000000..0efb6231e2 --- /dev/null +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -0,0 +1,46 @@ +.. _whatsnew_0960: + + +v0.9.6 (Anticipated June 2023) +------------------------------ + + +Deprecations +~~~~~~~~~~~~ + + +Enhancements +~~~~~~~~~~~~ +* added functions `pvlib.tracking.tracker_shaded_fraction` and + `pvlib.tracking.linear_shade_loss` to calculate row-to-row shade and apply + linear shade loss for thin film CdTe modules like First Solar. + (:issue:`1689`, :pull:`1690`) + +Bug fixes +~~~~~~~~~ +* `data` can no longer be left unspecified in + :py:meth:`pvlib.modelchain.ModelChain.run_model_from_effective_irradiance`. + (:issue:`1713`, :pull:`1720`) + +Testing +~~~~~~~ + + +Documentation +~~~~~~~~~~~~~ +* Updated the description of the interval parameter in + :py:func:`pvlib.iotools.get_psm3`. (:issue:`1702`, :pull:`1712`) + +Benchmarking +~~~~~~~~~~~~~ + + +Requirements +~~~~~~~~~~~~ + + +Contributors +~~~~~~~~~~~~ +* Adam R. Jensen (:ghuser:`adamrjensen`) +* Siddharth Kaul (:ghuser:`k10blogger`) +* Mark A. Mikofski (:ghuser:`mikofski`) diff --git a/pvlib/tests/test_tracking.py b/pvlib/tests/test_tracking.py index 4febc73389..3d3153babb 100644 --- a/pvlib/tests/test_tracking.py +++ b/pvlib/tests/test_tracking.py @@ -472,3 +472,28 @@ def test_calc_surface_orientation_special(): # in a modulo-360 sense. np.testing.assert_allclose(np.round(out['surface_azimuth'], 4) % 360, expected_azimuths, rtol=1e-5, atol=1e-5) + + +@pytest.fixture +def expected_fs(): + # trivial case, 80% gcr, no slope, trackers & psz at 45-deg, + z = np.sqrt(2*0.8*0.8) + return 1 - 1/z + + +def test_tracker_shade_fraction(expected_fs): + """closes gh1690""" + fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) + assert np.isclose(fs, expected_fs) + # same trivial case with 40%, shadow is only 0.565-m long < 1-m r2r P + zero_fs = tracking.tracker_shaded_fraction(45.0, 0.4, 45.0, 0) + assert np.isclose(zero_fs, 0) + + +def test_linear_shade_loss(expected_fs): + loss = tracking.linear_shade_loss(expected_fs, 0.2) + assert np.isclose(loss, 0.09289321881345258) + loss_no_df = tracking.linear_shade_loss(expected_fs, 0) + assert np.isclose(loss_no_df, expected_fs) + no_loss = tracking.linear_shade_loss(expected_fs, 1.0) + assert np.isclose(no_loss, 0) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 9ea476831f..24d8d6a44f 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -477,9 +477,17 @@ def linear_shade_loss(shade_fraction, diffuse_fraction): The fraction of power lost due to linear shading. A value of 1 is all power lost and zero is no loss. - References - ---------- - Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," - PVPMC, 2023 + See also + -------- + pvlib.tracking.tracker_shaded_fraction + + Example + ------- + >>> from pvlib import tracking + >>> fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) + >>> loss = tracking.linear_shade_loss(fs, 0.2) + >>> P_no_shade = 100 # [kWdc] DC output from modules + >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss + # 90.71067811865476 [kWdc] """ - return 1 - shade_fraction * (1 - diffuse_fraction) + return shade_fraction * (1 - diffuse_fraction) From fb0e5d1b571bd73a10816520fa2aaf13ca6935d2 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Thu, 4 May 2023 00:51:26 -0700 Subject: [PATCH 043/185] fix what's new gh issue and pr links --- docs/sphinx/source/whatsnew/v0.9.6.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 0efb6231e2..ddc45f8c2a 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -14,7 +14,7 @@ Enhancements * added functions `pvlib.tracking.tracker_shaded_fraction` and `pvlib.tracking.linear_shade_loss` to calculate row-to-row shade and apply linear shade loss for thin film CdTe modules like First Solar. - (:issue:`1689`, :pull:`1690`) + (:issue:`1689`, :issue:`1690`, :pull:`1725`) Bug fixes ~~~~~~~~~ From aa4b6e8cb5a95ada94b776c4ebc1141012bce6ce Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Thu, 4 May 2023 01:02:17 -0700 Subject: [PATCH 044/185] fix trailing whitespace --- pvlib/tests/test_tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_tracking.py b/pvlib/tests/test_tracking.py index 3d3153babb..e21a51c1a5 100644 --- a/pvlib/tests/test_tracking.py +++ b/pvlib/tests/test_tracking.py @@ -476,7 +476,7 @@ def test_calc_surface_orientation_special(): @pytest.fixture def expected_fs(): - # trivial case, 80% gcr, no slope, trackers & psz at 45-deg, + # trivial case, 80% gcr, no slope, trackers & psz at 45-deg z = np.sqrt(2*0.8*0.8) return 1 - 1/z From 18b21878a3ed5132ecaa4f965faed04317c01df2 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Fri, 5 May 2023 23:27:56 -0700 Subject: [PATCH 045/185] responding to comments - move linear shade loss to shading module - don't use ternary, doesn't work on vectors, instead use np.where() - set cross axis default to zero - test vectors - update docs --- .../effects_on_pv_system_output/shading.rst | 3 + docs/sphinx/source/reference/tracking.rst | 2 - docs/sphinx/source/whatsnew/v0.9.6.rst | 4 +- pvlib/shading.py | 85 +++++++++++++++++++ pvlib/tests/test_shading.py | 43 ++++++++++ pvlib/tests/test_tracking.py | 25 ------ pvlib/tracking.py | 85 ------------------- 7 files changed, 133 insertions(+), 114 deletions(-) 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 a0fd74a795..189c299fa2 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 @@ -11,3 +11,6 @@ Shading shading.masking_angle_passias shading.sky_diffuse_passias shading.projected_solar_zenith_angle + shading.tracker_shaded_fraction + shading.linear_shade_loss + diff --git a/docs/sphinx/source/reference/tracking.rst b/docs/sphinx/source/reference/tracking.rst index b5ccd48ea3..6d3f7fc4eb 100644 --- a/docs/sphinx/source/reference/tracking.rst +++ b/docs/sphinx/source/reference/tracking.rst @@ -13,5 +13,3 @@ Functions tracking.calc_axis_tilt tracking.calc_cross_axis_tilt tracking.calc_surface_orientation - tracking.tracker_shaded_fraction - tracking.linear_shade_loss diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index ddc45f8c2a..65575f03bd 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -11,8 +11,8 @@ Deprecations Enhancements ~~~~~~~~~~~~ -* added functions `pvlib.tracking.tracker_shaded_fraction` and - `pvlib.tracking.linear_shade_loss` to calculate row-to-row shade and apply +* added functions `pvlib.shading.tracker_shaded_fraction` and + `pvlib.shading.linear_shade_loss` to calculate row-to-row shade and apply linear shade loss for thin film CdTe modules like First Solar. (:issue:`1689`, :issue:`1690`, :pull:`1725`) diff --git a/pvlib/shading.py b/pvlib/shading.py index 3172accaf4..8bf3b2bd2b 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -343,3 +343,88 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) return theta_T + + +def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, + cross_axis_slope=0): + """ + Shade fraction (FS) for trackers with a common angle on an east-west slope. + + Parameters + ---------- + tracker_theta : numeric + The tracker rotation angle in degrees from horizontal. + gcr : float + The ground coverage ratio as a fraction equal to the collector width + over the horizontal row-to-row pitch. + projected_solar_zenith : numeric + Zenith angle in degrees of the solar vector projected into the plane + perpendicular to the tracker axes. + cross_axis_slope : float, default 0 + Angle of the plane containing the tracker axes in degrees from + horizontal. + + Returns + ------- + shade_fraction : numeric + The fraction of the collector width shaded by an adjacent row. A + value of 1 is completely shaded and zero is no shade. + + References + ---------- + Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," + PVPMC, 2023 + """ + theta_g_rad = np.radians(cross_axis_slope) + # angle opposite shadow cast on the ground, z + angle_z = ( + np.pi / 2 - np.radians(tracker_theta) + + np.radians(projected_solar_zenith)) + # angle opposite the collector width, L + angle_gcr = ( + np.pi / 2 - np.radians(projected_solar_zenith) + - theta_g_rad) + # ratio of shadow, z, to pitch, P + zp = gcr * np.sin(angle_z) / np.sin(angle_gcr) + # there's only row-to-row shade loss if the shadow on the ground, z, is + # longer than row-to-row pitch projected on the ground, P*cos(theta_g) + zp_cos_g = zp*np.cos(theta_g_rad) + # shade fraction + fs = np.where(zp_cos_g <= 1, 0, 1 - 1/zp_cos_g) + return fs + + +def linear_shade_loss(shade_fraction, diffuse_fraction): + """ + Fraction of power lost to linear shade loss applicable to CdTe modules like + First Solar. + + Parameters + ---------- + shade_fraction : numeric + The fraction of the collector width shaded by an adjacent row. A + value of 1 is completely shaded and zero is no shade. + diffuse_fraction : numeric + The ratio of diffuse plane of array (poa) irradiance to global poa. + A value of 1 is completely diffuse and zero is no diffuse. + + Returns + ------- + linear_shade_loss : numeric + The fraction of power lost due to linear shading. A value of 1 is all + power lost and zero is no loss. + + See also + -------- + pvlib.tracking.tracker_shaded_fraction + + Example + ------- + >>> from pvlib import tracking + >>> fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) + >>> loss = tracking.linear_shade_loss(fs, 0.2) + >>> P_no_shade = 100 # [kWdc] DC output from modules + >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss + # 90.71067811865476 [kWdc] + """ + return shade_fraction * (1 - diffuse_fraction) diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 8d609d1e3f..9df6e2291b 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -223,3 +223,46 @@ def test_projected_solar_zenith_angle_datatypes( ) psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth) assert isinstance(psz, cast_type) + + +@pytest.fixture +def expected_fs(): + # trivial case, 80% gcr, no slope, trackers & psz at 45-deg + z0 = np.sqrt(2*0.8*0.8) + # another trivial case, 60% gcr, no slope, trackers & psz at 60-deg + z1 = 2*0.6 + # 30-deg isosceles, 60% gcr, no slope, 30-deg trackers, psz at 60-deg + z2 = 0.6*np.sqrt(3) + z = np.array([z0, z1, z2]) + return 1 - 1/z + + +def test_tracker_shade_fraction(expected_fs): + """closes gh1690""" + fs = shading.tracker_shaded_fraction(45.0, 0.8, 45.0) + assert np.isclose(fs, expected_fs[0]) + # same trivial case with 40%, shadow is only 0.565-m long < 1-m r2r P + zero_fs = shading.tracker_shaded_fraction(45.0, 0.4, 45.0) + assert np.isclose(zero_fs, 0) + # test vectors + tracker_theta = [45.0, 60.0, 30.0] + gcr = [0.8, 0.6, 0.6] + psz = [45.0, 60.0, 60.0] + slope = [0]*3 + fs_vec = shading.tracker_shaded_fraction( + tracker_theta, gcr, psz, slope) + assert np.allclose(fs_vec, expected_fs) + + +def test_linear_shade_loss(expected_fs): + loss = shading.linear_shade_loss(expected_fs[0], 0.2) + assert np.isclose(loss, 0.09289321881345258) + # if no diffuse, shade fraction is the loss + loss_no_df = shading.linear_shade_loss(expected_fs[0], 0) + assert np.isclose(loss_no_df, expected_fs[0]) + # if all diffuse, no shade loss + no_loss = shading.linear_shade_loss(expected_fs[0], 1.0) + assert np.isclose(no_loss, 0) + vec_loss = shading.linear_shade_loss(expected_fs, 0.2) + expected_loss = np.array([0.09289322, 0.13333333, 0.03019964]) + assert np.allclose(vec_loss, expected_loss) diff --git a/pvlib/tests/test_tracking.py b/pvlib/tests/test_tracking.py index e21a51c1a5..4febc73389 100644 --- a/pvlib/tests/test_tracking.py +++ b/pvlib/tests/test_tracking.py @@ -472,28 +472,3 @@ def test_calc_surface_orientation_special(): # in a modulo-360 sense. np.testing.assert_allclose(np.round(out['surface_azimuth'], 4) % 360, expected_azimuths, rtol=1e-5, atol=1e-5) - - -@pytest.fixture -def expected_fs(): - # trivial case, 80% gcr, no slope, trackers & psz at 45-deg - z = np.sqrt(2*0.8*0.8) - return 1 - 1/z - - -def test_tracker_shade_fraction(expected_fs): - """closes gh1690""" - fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) - assert np.isclose(fs, expected_fs) - # same trivial case with 40%, shadow is only 0.565-m long < 1-m r2r P - zero_fs = tracking.tracker_shaded_fraction(45.0, 0.4, 45.0, 0) - assert np.isclose(zero_fs, 0) - - -def test_linear_shade_loss(expected_fs): - loss = tracking.linear_shade_loss(expected_fs, 0.2) - assert np.isclose(loss, 0.09289321881345258) - loss_no_df = tracking.linear_shade_loss(expected_fs, 0) - assert np.isclose(loss_no_df, expected_fs) - no_loss = tracking.linear_shade_loss(expected_fs, 1.0) - assert np.isclose(no_loss, 0) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 24d8d6a44f..9c4103e7f0 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -406,88 +406,3 @@ def calc_cross_axis_tilt( # equation 26 beta_c = _calc_beta_c(v, delta_gamma, axis_tilt) return np.degrees(beta_c) - - -def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, - cross_axis_slope): - """ - Shade fraction (FS) for trackers with a common angle on an east-west slope. - - Parameters - ---------- - tracker_theta : numeric - The tracker rotation angle in degrees from horizontal. - gcr : float - The ground coverage ratio as a fraction equal to the collector width - over the horizontal row-to-row pitch. - projected_solar_zenith : numeric - Zenith angle in degrees of the solar vector projected into the plane - perpendicular to the tracker axes. - cross_axis_slope : float - Angle of the plane containing the tracker axes in degrees from - horizontal. - - Returns - ------- - shade_fraction : numeric - The fraction of the collector width shaded by an adjacent row. A - value of 1 is completely shaded and zero is no shade. - - References - ---------- - Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," - PVPMC, 2023 - """ - theta_g_rad = np.radians(cross_axis_slope) - # angle opposite shadow cast on the ground, z - angle_z = ( - np.pi / 2 - np.radians(tracker_theta) - + np.radians(projected_solar_zenith)) - # angle opposite the collector width, L - angle_gcr = ( - np.pi / 2 - np.radians(projected_solar_zenith) - - theta_g_rad) - # ratio of shadow, z, to pitch, P - zp = gcr * np.sin(angle_z) / np.sin(angle_gcr) - # there's only row-to-row shade loss if the shadow on the ground, z, is - # longer than row-to-row pitch projected on the ground, P*cos(theta_g) - zp_cos_g = zp*np.cos(theta_g_rad) - # shade fraction - fs = 0 if zp_cos_g <= 1 else 1 - 1/zp_cos_g - return fs - - -def linear_shade_loss(shade_fraction, diffuse_fraction): - """ - Fraction of power lost to linear shade loss applicable to CdTe modules like - First Solar. - - Parameters - ---------- - shade_fraction : numeric - The fraction of the collector width shaded by an adjacent row. A - value of 1 is completely shaded and zero is no shade. - diffuse_fraction : numeric - The ratio of diffuse plane of array (poa) irradiance to global poa. - A value of 1 is completely diffuse and zero is no diffuse. - - Returns - ------- - linear_shade_loss : numeric - The fraction of power lost due to linear shading. A value of 1 is all - power lost and zero is no loss. - - See also - -------- - pvlib.tracking.tracker_shaded_fraction - - Example - ------- - >>> from pvlib import tracking - >>> fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) - >>> loss = tracking.linear_shade_loss(fs, 0.2) - >>> P_no_shade = 100 # [kWdc] DC output from modules - >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss - # 90.71067811865476 [kWdc] - """ - return shade_fraction * (1 - diffuse_fraction) From 4163a7269869563c7df6d5c5c8c2060e46d2b444 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Fri, 5 May 2023 23:42:34 -0700 Subject: [PATCH 046/185] update docstring for linear shade loss - applicable to other monolithic thin film like CIGS, not just CdTe - only when shade is perpendicular to scribe lines --- pvlib/shading.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 8bf3b2bd2b..0f2eac49f5 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -396,8 +396,9 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, def linear_shade_loss(shade_fraction, diffuse_fraction): """ - Fraction of power lost to linear shade loss applicable to CdTe modules like - First Solar. + Fraction of power lost to linear shade loss applicable to monolithic thin + film modules like First Solar CdTe, where the shadow is perpendicular to + cell scribe lines. Parameters ---------- From 1ebf8ea61ac31ae5dc36182afb262c5eff4bb42f Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Fri, 5 May 2023 23:51:05 -0700 Subject: [PATCH 047/185] update example in linear_shade_loss --- pvlib/shading.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 0f2eac49f5..8e7cf7659b 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -417,13 +417,13 @@ def linear_shade_loss(shade_fraction, diffuse_fraction): See also -------- - pvlib.tracking.tracker_shaded_fraction + pvlib.shading.tracker_shaded_fraction Example ------- - >>> from pvlib import tracking - >>> fs = tracking.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) - >>> loss = tracking.linear_shade_loss(fs, 0.2) + >>> from pvlib import shading + >>> fs = shading.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) + >>> loss = shading.linear_shade_loss(fs, 0.2) >>> P_no_shade = 100 # [kWdc] DC output from modules >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss # 90.71067811865476 [kWdc] From de2144f8d63fae11d1edea5a02900b1f314fd5b4 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Sat, 6 May 2023 01:10:28 -0700 Subject: [PATCH 048/185] add figure and formulas to shaded fraction --- .../FSLR_irrad_shade_loss_slope_terrain.png | Bin 0 -> 127773 bytes pvlib/shading.py | 30 +++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/sphinx/source/_images/FSLR_irrad_shade_loss_slope_terrain.png diff --git a/docs/sphinx/source/_images/FSLR_irrad_shade_loss_slope_terrain.png b/docs/sphinx/source/_images/FSLR_irrad_shade_loss_slope_terrain.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f01c2e887a1f85abb952d33d02175272df15be GIT binary patch literal 127773 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�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 z zDrllFUMiUMsf%jU81Pa+Hf#@s)e-2KUe4vf<+SbY?yVywzorxR&5bM3CDB$cw7kak z@5(`Zp=at2OO^~4mb(efBMw~ikGA08z%6qeXT6mBEwy}P^}Vkf1@?KM$1TD^#VHlR zJU%`U+2eHb(L{qP`%=zl7!EJNvkD*JQ?psm))v%d0Q###Qe)T5%e|yeZuVR2iJA~I z7S&D(qvakLZZxSGzqa>$OxT(L>BRCtZWG#5Hiz`9$!HmDYvl%+g;eM&$W@P56d~Y6 ziqW6ieLvJGLf=&V#lLZG1G*x9)XLsS7d}9ko;tNA4&AB%vPc^6-1$Ps1#VJ7oJ`wGwphWTP}2fi+k}p^u?1@$&ZH6fNsjuM%-8E^7`({X2=FVl&PHM zJ}Ta+JpMGD0l}v7FV&;V@h&xq5IrH$CTKEy8|%$>d9xb=N?A0wBgm0>2PaFz^#{6f z-hb(xnlI`sAC4R)sEyZ>w?*r+f8zS^@$RS1!)f5FU3kmY4_Vhrd9-LH4TXVDajIim z#e#R^u*yl{v)xFDe279Vf>_?R{VKA|_kfRahB=lm0W;;%?V3i` zpLL}6kHgo+?;|-Bt7Z+TN1dfL>O_%|^FX3XNL6rzB}jp~og$MO!u@&=m=2YG_ho0= zGGRMUxfLjAhi`{z`UsYxVGxpEn>}>7sS&>N(`-kH?WTOl&QzdyZJr?n{`^+nQ>Ezo zaJu{Z})K3d42+&pc)#i{+6pNaQ*oRECVCshzWSE!vKEp5m#$XCGOrr2J7RA_+mt?)6IxkWiASecosGN(IB|2{ zjaP1Ght|p}8L?_qe}-7gd%V>dz^7h(qOr?RE>^@ks{{#^Gu2C#UNNr8GGn)@yv!Vl|qznA9Jho@c#&i2EVViP!5^bKVfOLqe=im4wJY_@RKH4-0m zmz9U}P|bG;1F=o$55rQ!2{;4JODo@9n7BZ&Y68lWONhG90+Ni zkR1i1_uys1vrh{}+W=u+yli^6i&|<2en&oB$f$TZuOUB-nAvX2hie5ns@AG}U-8=8 zE+i+WNGIQ*kxG_QjQZkmJO~k!q{(a%jyyxGAv$5=3`KW)E zHw({|NtFs<$Z`Fq2g?cGGdm<4YszBKrkBryp9)jX5I z?KewscEFoTQL;{b>KtGkzy#I7OK-xOMqpD_6P#Mw{k5@nY$fQU^5RRL@tY{MPdiBR~((C)0RN4E0LCJ#}170YrL0;Wd zgW#_7DmWR!Q_$L3S?%6dKsb zu!&_dl=t1KqG3S*`jlOO#8Mg(l;ylZ`h7}jkS9x3D|bp$uIg{%S}l<(wpxa7Kyfaj zk$qS=9RjPrTSg8t07Rax$?O4IRdunO8Qw{MjxHJWWU`n_3xECk0EZzimDrF`{xG~2 zxIUA>R#hOczD`%ng`3ZPJYNlw0SO3@{i#@wK=#)Rb@$0$9bavB%)^p-)0BGN!zCcA zZ|~JzoQGBWPfp$5L#<5+wIM??MG%l0m?yV$yEQi?s2rk^)I>f+b^HVXlSt}`u@9pp zPgv)W95}4k?451GqaQ7@r$l$$aCA4&1+xZ@Z2ek1Sbk7G@j-q7M@uh+C|2lBHTk?p zs?GV%@26qZQVB~6_n1c@?(^+W1@?tBLx0w~Pn}1r){WG3>7z*iDfKLzlJ44iRM8H0 zPt`lBZl`5#o1hMrI;!G7o!fYLNRZjS1j1t{maxu1!~#f`Y*zOoa4uq{WVfGQyi>eV zWUvv)vul@b4eR>~eZifbJ0DtlolM?4RAIAML%b_q%0^e9t9~*6gBw0S;f}Ugf38zx zqrobNC$5zsEM{1Kyo1z{OHbK=QU(R1_RK$c*SpqEoiMiY?>atiQbyY7uT3j4l{q+r~r%ZOz3l(lGN*t<7xW zQ}UP&fQ?NYzAJ!j*oBj_XfqlUNjP&trNlTWLM{ZS zSUAqO!0UlhxVMQge>u;D#mu6kIf)PY0|7aq!@GUD9YJvhLkBy{#i~6dne94Vy{h&` zw8BcU%}KpYl+@~8MU-wZVlC}&3Q?luV|Wr)nik4x3|ZH^y=m_#h<2qzVx04FQxS38 zYroFJ;TW*`x2(Lpyjft-oqhjfq-x&I7HovRH-3mcve#j$3h9VJ+*aC0CMY5r^9Qsc zUSu&W%qMT!>R8!NM=M4kl4efn>ez1Q_DTi!R5{fG-^gj{>0hbD)Vki!qPETABB2@b zVE@JvRJBg~O=`7`()ylsd9R#AdZFKv9(sTJ6Gh+DA;IokyCx)2BXDv&lUp>-?Y5nb zjyPI+2<aP=~%tkKG<&Frp?&5}!3Xwho{EH|iDr`&6 z96C;xAf*({v9)-7Rrv>_UQ$k@l@IFiPW8v?@bv>uwiXiW^a{mZnLKlcg?ZM;Y%Ksi zz=y1(DSg44G?1l>fJy6B!<&Tm7Vkt6VY{LPMl(-130@wu_DNCE8rEe0OwCYrhLzu% zWy+S(Q@lz9Nf6Wqu~U@Px%qu&5)=Kx@)uPYzO|TK6m%=qrT zJZ)yviE7Agcy`5+Bt|5f01z~(05(!s$2A--vfOxdiu5Uo`HJswPaN-#!cId@losgx zPw2vsH3DhyL-q;XAzX3pM|I+i+{*Hbq|B7_uO(l4-EN2cc7o(WrLJXTP@I6EEu5Kz z&^ryW%uC6U8D@-W_|Vq6W>V*4@S{d4lwReT%*-WMlu3yxcAib~Mk7sCm61ZC@qxPe zT?;eV@xqYEgF_c|mONv|Ab=ysH=`4laPJEfHJ@npx!J5b(ugB8AwVk1Q9C&?+R-X| z`Ot}hGYw!@CC0;RJJSn=w;Xu`SO3P-VVI5Hou$iHRv^5XGJZ&e`hVKH@_(w+J$|}g zU85<>s8p6lGcHNVHnN`XG}0nVO<8h?8fHoigLCLuuEHFSB}d6*(9RT+LWnq)EFlbr zvPZ^xR5*zHevax@bN_++!<-+#=es=L=ld-0&-?v;K2LvEs>PLZ5ygaYjOW46lNyGW z$oai`GA=1&mpMHZG7u;vHq+mJRqr1JSZAuF{hoSbvsuDg#miZ|@L;g8a0{s~~eR`(SLJj_qC8@hjzULEFm z_$#-f*N1w`^UJH&x7>E%?hw`KLv$pf!M*otwsXO7?vsBlEE5L9n~zqncP*IJ7jG-R z(^-nmRKmaRvnLU-qQWxz1Y8N5@fBQ4*MKg}_t&RDfzOfls1vr3dwEi>$*t_ABH zPuVv=x%n6E4CATM1rtR(1p{6wCrW>^>17>#{cTsajE=g`sC3~sP!HtvyoVtfmz2g< za=0!!XfG$4i32^jL|v*QdAwK7?$7mik5$G1#;jJc5}%3RxO^&}$(Q+xiI~ZFPGmuz zL>&h$X@sIKJDerZT;EtZB}lAZS+f6VC5M(~XEiJ;H1TxXz;Ko^dySmlFOPw`2%Oy1 zes8uqSjzN5wCDPIKn7b9)TxJ#=DwLkW((JyWDiAAS>4hq?~(cWDd9LpyN(sybBNnA ziV`&^>tF&5df2v0CNpKywNIeB7b!$SQfoAhcc^oed_gO6{ zTB_j>ed|L?7l5`(WSqVm!HjgO?iojN?E6*!Jc7V&%&UbK=kwxe?tI=*qR&h!=r3#} zWeakXnAEOM+N(aez?@WPjCYM!otZLtS+655H*{Uv&BQSL6Yi{(JyY!^e|a6R`J$Vg zn*N|_H3%o*$X`-X0@vK`V&Tyw9gRUJ1!PuufGCZtx8^vveX8|~C>0ZC^Xau3P6g;u z-LLroO|SRcR4yVO-*c?wzHXDdLp!ah2h7ry+^&tN!}G_ zjMD0jWq?Z!lGkqRHNATH(6Yu$p{hl59usL@T<2jz^JVN-5~ICA94+g#4#Du&6Mtgt z;e~TXpDfH+GxwS6|LV$4i)XP9rmG|>8OY|XbDCbts*6+uBR3v&CQhNr5L3!}|7nK_ zp-_Kbnbf7NO3xyCSnLMN^9j?%n@9=bSv6b*>Nrij|3C3Lsw+dl7>?{<$daV`sgFIA z85=Zc`e!X>s$wLm3^J$a&RyiQ6{u=^tW0DzoNBNk2>0?X@zqW-8T;qJJeQ0Ok4s!J zj$Ehh`zqx}>X6Ng0ptoQ$x-FoJi~C~j{rPgan1Zbf2*d?Yp7iAiUk*21}Jtf6|xC9 z)ogwe`+$U~S}=mTEe)F@rM0)zXrebeG;6x|oaqLYvqsCcDdlXY@X&sS+72pLTZupX z9dd|XQhqrZ+y>rBmp`n)mZ^x8eL48E+92J{^Sap3w=W!~>E1mi3MxfR1E03r-B<*| z=)4%PP>@Yc0X@Fi=I(S|z`W#GS4E4^a_O&zZ)MfC{nm8$WB6ODZ7%xU5Alpnwqk`kI z(r~tcCp+Gf{_CD=M3K)}N(qFh7~KM5kM7qt@w~d1I5!^ZYWUAh2Y)J6PW2#L;yNe# z^am~xB9gIrP|X^vRBX3sx@dTaHX|+!!H>Upakn41FpB=K4z5BL*wb?U)7=%#rJ%WI zFwTf7IRLkjxC35G$+4AqfxuW8pfx1u(OnDZ z|(dIe}FwE@jwS46`~wASx`vX&L?d#Fl?|VTs6|c&H4mTG8V}8 za>)(MYl&ey+0lj`?@p8gq~obDCYG9HePVgsUiPi7MmoevG~=8Z_e)+7Pa<+^38zkY zDKX-J*?wQ(%n3{NbYk|MH5SF@#a&UKruoghA}nRT*U%&gK7-dy2heLyg^J383g9rL zI3P{hCWM<5}rx}jiZFFp3hfp%g6jw1+JLy0P8Q=^%u;&bXFNmj6IR^%7 za=)uSpNT|dXy6TdRnuTLFUZ)??wUUWXd~waM+j^&UkY9wMSBVXy>*g<_6ks}++itV zX2?HQK-@o+=g~xER=(a4&)aw-;j?!C-;lP+4KXHfG>pjUXk#6uN&+W#zI*gE;?2mD z)ZYPmbv<8*kEIhYQz1-<4C1P!R7o#>mTMZlkasp`rv*blF3sPxA3H@7G05q|lZG6r zy01Rn#=}^03F`|5$C}SW3CjT*hagjGw4Rc&Zr}bdHUkVRsMBtj>{kFC5rQ#~1RVMrDsD{5QM9>%t cv50O#IM*ORIae*sk{sG?Wcp3|*GGc>3yT0_uK)l5 literal 0 HcmV?d00001 diff --git a/pvlib/shading.py b/pvlib/shading.py index 8e7cf7659b..5514d098c2 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -347,7 +347,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, cross_axis_slope=0): - """ + r""" Shade fraction (FS) for trackers with a common angle on an east-west slope. Parameters @@ -370,6 +370,34 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, The fraction of the collector width shaded by an adjacent row. A value of 1 is completely shaded and zero is no shade. + See also + -------- + pvlib.shading.linear_shade_loss + + + The shaded fraction is derived using trigonometery and similar triangles + from the tracker rotation :math:`\beta`, the ground slope :math:`\theta_g`, + the projected solar zenith (psz) :math:`\theta`, the collector width + :math:`L`, the row-to-row pitch :math:`P`, and the shadow length :math:`z` + as shown in the image below. + + .. image:: /_images/FSLR_irrad_shade_loss_slope_terrain.png + + The ratio of the shadow length to the pitch, :math:`z/P`, is given by the + following relation where the ground coverage ratio (GCR) is :math:`L/P`: + + .. math:: + \frac{z/P}{\sin{\left(\frac{\pi}{2}-\beta+\theta\right)}} + = \frac{GCR}{\sin{\left(\frac{\pi}{2}-\theta-\theta_g\right)}} + + Then the shaded fraction :math:`w/L` is derived from :math:`z/P` as + follows: + + .. math:: + \frac{w}{L} = 1 - \frac{P}{z\cos{\theta_g}} + + Finally, shade is zero if :math:`z\cos{\theta_g}/P \le 1`. + References ---------- Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," From 6aa43f0b749e289e560d0003ee8592b22f6a5b70 Mon Sep 17 00:00:00 2001 From: Mark Mikofski Date: Wed, 10 May 2023 21:11:02 -0700 Subject: [PATCH 049/185] shaded fraction consistently --- pvlib/shading.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 5514d098c2..fc4700a77f 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -348,7 +348,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, cross_axis_slope=0): r""" - Shade fraction (FS) for trackers with a common angle on an east-west slope. + Shaded fraction for trackers with a common angle on an east-west slope. Parameters ---------- @@ -366,7 +366,7 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, Returns ------- - shade_fraction : numeric + shaded_fraction : numeric The fraction of the collector width shaded by an adjacent row. A value of 1 is completely shaded and zero is no shade. @@ -417,12 +417,12 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, # there's only row-to-row shade loss if the shadow on the ground, z, is # longer than row-to-row pitch projected on the ground, P*cos(theta_g) zp_cos_g = zp*np.cos(theta_g_rad) - # shade fraction + # shaded fraction (fs) fs = np.where(zp_cos_g <= 1, 0, 1 - 1/zp_cos_g) return fs -def linear_shade_loss(shade_fraction, diffuse_fraction): +def linear_shade_loss(shaded_fraction, diffuse_fraction): """ Fraction of power lost to linear shade loss applicable to monolithic thin film modules like First Solar CdTe, where the shadow is perpendicular to @@ -430,7 +430,7 @@ def linear_shade_loss(shade_fraction, diffuse_fraction): Parameters ---------- - shade_fraction : numeric + shaded_fraction : numeric The fraction of the collector width shaded by an adjacent row. A value of 1 is completely shaded and zero is no shade. diffuse_fraction : numeric @@ -456,4 +456,4 @@ def linear_shade_loss(shade_fraction, diffuse_fraction): >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss # 90.71067811865476 [kWdc] """ - return shade_fraction * (1 - diffuse_fraction) + return shaded_fraction * (1 - diffuse_fraction) From c34a2f640ad43389c64e8e9640c6499c2e1f0cc2 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 3 Feb 2024 10:26:48 +0100 Subject: [PATCH 050/185] Add alternative text to image --- pvlib/shading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pvlib/shading.py b/pvlib/shading.py index fc4700a77f..8aba3aa574 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -382,6 +382,8 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, as shown in the image below. .. image:: /_images/FSLR_irrad_shade_loss_slope_terrain.png + :alt: Cross-section of two arrays on a sloped terrain and the resulting + shade. The ratio of the shadow length to the pitch, :math:`z/P`, is given by the following relation where the ground coverage ratio (GCR) is :math:`L/P`: From 6b9f4781be283d78005d3b77c793fc914b91e7a8 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:12:59 +0100 Subject: [PATCH 051/185] Update implementation based on PSZ PR. See description. Commit highlights :sparkles: : * I think I made all the code a bit more legible; sorry for the big changes @mikofski * Tests a bit more complete (not much, still consider the same test data) * Rename shaded fraction acronym from `fs` to `sf` * Asserts changed to `assert_allclose` for a more legible output in case of failure Co-Authored-By: Mark Mikofski --- pvlib/shading.py | 43 +++++++++++------- pvlib/tests/test_shading.py | 88 +++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 53 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 8aba3aa574..e37b987299 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -345,22 +345,25 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, return theta_T -def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, - cross_axis_slope=0): +def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, + tracker_azimuth, gcr, cross_axis_slope=0): r""" Shaded fraction for trackers with a common angle on an east-west slope. Parameters ---------- - tracker_theta : numeric - The tracker rotation angle in degrees from horizontal. - gcr : float + solar_zenith : numeric + Solar position zenith, in degrees. + solar_azimuth : numeric + Solar position azimuth, in degrees. + tracker_tilt : numeric + In degrees. + tracker_azimuth : numeric + In degrees. North=0º, South=180º, East=90º, West=270º. + gcr : numeric The ground coverage ratio as a fraction equal to the collector width over the horizontal row-to-row pitch. - projected_solar_zenith : numeric - Zenith angle in degrees of the solar vector projected into the plane - perpendicular to the tracker axes. - cross_axis_slope : float, default 0 + cross_axis_slope : numeric, default 0 Angle of the plane containing the tracker axes in degrees from horizontal. @@ -406,9 +409,19 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, PVPMC, 2023 """ theta_g_rad = np.radians(cross_axis_slope) + # projected solar zenith: + # consider the angle the sun direct beam has on the vertical plane which + # contains the tracker normal vector, with respect to a horizontal line + projected_solar_zenith = projected_solar_zenith_angle( + 0, # no rotation from the horizontal + # the vector that defines the projection plane for prior conditions + tracker_azimuth-90, + solar_zenith, + solar_azimuth + ) # angle opposite shadow cast on the ground, z angle_z = ( - np.pi / 2 - np.radians(tracker_theta) + np.pi / 2 - np.radians(tracker_tilt) + np.radians(projected_solar_zenith)) # angle opposite the collector width, L angle_gcr = ( @@ -419,9 +432,9 @@ def tracker_shaded_fraction(tracker_theta, gcr, projected_solar_zenith, # there's only row-to-row shade loss if the shadow on the ground, z, is # longer than row-to-row pitch projected on the ground, P*cos(theta_g) zp_cos_g = zp*np.cos(theta_g_rad) - # shaded fraction (fs) - fs = np.where(zp_cos_g <= 1, 0, 1 - 1/zp_cos_g) - return fs + # shaded fraction (sf) + sf = np.where(zp_cos_g <= 1, 0, 1 - 1/zp_cos_g) + return sf def linear_shade_loss(shaded_fraction, diffuse_fraction): @@ -452,8 +465,8 @@ def linear_shade_loss(shaded_fraction, diffuse_fraction): Example ------- >>> from pvlib import shading - >>> fs = shading.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) - >>> loss = shading.linear_shade_loss(fs, 0.2) + >>> sf = shading.tracker_shaded_fraction(45.0, 0.8, 45.0, 0) + >>> loss = shading.linear_shade_loss(sf, 0.2) >>> P_no_shade = 100 # [kWdc] DC output from modules >>> P_linear_shade = P_no_shade * (1-loss) # [kWdc] output after loss # 90.71067811865476 [kWdc] diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 9df6e2291b..13d4ac1532 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -226,43 +226,55 @@ def test_projected_solar_zenith_angle_datatypes( @pytest.fixture -def expected_fs(): - # trivial case, 80% gcr, no slope, trackers & psz at 45-deg - z0 = np.sqrt(2*0.8*0.8) - # another trivial case, 60% gcr, no slope, trackers & psz at 60-deg - z1 = 2*0.6 - # 30-deg isosceles, 60% gcr, no slope, 30-deg trackers, psz at 60-deg - z2 = 0.6*np.sqrt(3) - z = np.array([z0, z1, z2]) - return 1 - 1/z - - -def test_tracker_shade_fraction(expected_fs): - """closes gh1690""" - fs = shading.tracker_shaded_fraction(45.0, 0.8, 45.0) - assert np.isclose(fs, expected_fs[0]) - # same trivial case with 40%, shadow is only 0.565-m long < 1-m r2r P - zero_fs = shading.tracker_shaded_fraction(45.0, 0.4, 45.0) - assert np.isclose(zero_fs, 0) - # test vectors - tracker_theta = [45.0, 60.0, 30.0] - gcr = [0.8, 0.6, 0.6] - psz = [45.0, 60.0, 60.0] - slope = [0]*3 - fs_vec = shading.tracker_shaded_fraction( - tracker_theta, gcr, psz, slope) - assert np.allclose(fs_vec, expected_fs) - - -def test_linear_shade_loss(expected_fs): - loss = shading.linear_shade_loss(expected_fs[0], 0.2) - assert np.isclose(loss, 0.09289321881345258) +def sf_premises_and_expected(): + """Data comprised of solar position, tracker orientations, ground coverage + ratios and terrain slopes with respective shade fractions (sf)""" + # preserve tracker_shade_fraction's args order and append shadow depth, z + premises_and_results = pd.DataFrame( + columns=["solar_zenith", "solar_azimuth", "tracker_tilt", + "tracker_azimuth", "gcr", "cross_axis_slope", "z"], + data=( + # trivial case, 80% gcr, no slope, trackers & psz at 45-deg + (45, 90., 45, 90., 0.8, 0, np.sqrt(2*0.8*0.8)), + # another trivial case, 60% gcr, no slope, trackers & psz at 60-deg + (60, 120, 60, 120, 0.6, 0, 2*0.6), + # 30-deg isosceles, 60% gcr, no slope, 30-deg trackers, psz 60-deg + (60, 135, 30, 135, 0.6, 0, 0.6*np.sqrt(3)), + # no shading, 40% gcr, shadow is only 0.565-m long < 1-m r2r P + (45, 180, 45, 180, 0.4, 0, 1) # z := 1 means no shadow + )) + # append shaded fraction + premises_and_results["shaded_fraction"] = 1 - 1/premises_and_results["z"] + return premises_and_results + + +def test_tracker_shade_fraction(sf_premises_and_expected): + """Tests tracker_shade_fraction""" + # unwrap sf_premises_and_expected values premises and expected results + premises = sf_premises_and_expected.drop(columns=["z", "shaded_fraction"]) + expected_sf_array = sf_premises_and_expected["shaded_fraction"] + # test scalar inputs from the row iterator + # series label := corresponding index in expected_sf_array + for index, premise in premises.iterrows(): + expected_result = expected_sf_array[index] + sf = shading.tracker_shaded_fraction(**premise) + assert_allclose(sf, expected_result) + + # test vector inputs + sf_vec = shading.tracker_shaded_fraction(**premises) + assert_allclose(sf_vec, expected_sf_array) + + +def test_linear_shade_loss(sf_premises_and_expected): + expected_sf_array = sf_premises_and_expected["shaded_fraction"] + loss = shading.linear_shade_loss(expected_sf_array[0], 0.2) + assert_allclose(loss, 0.09289321881345258) # if no diffuse, shade fraction is the loss - loss_no_df = shading.linear_shade_loss(expected_fs[0], 0) - assert np.isclose(loss_no_df, expected_fs[0]) + loss_no_df = shading.linear_shade_loss(expected_sf_array[0], 0) + assert_allclose(loss_no_df, expected_sf_array[0]) # if all diffuse, no shade loss - no_loss = shading.linear_shade_loss(expected_fs[0], 1.0) - assert np.isclose(no_loss, 0) - vec_loss = shading.linear_shade_loss(expected_fs, 0.2) - expected_loss = np.array([0.09289322, 0.13333333, 0.03019964]) - assert np.allclose(vec_loss, expected_loss) + no_loss = shading.linear_shade_loss(expected_sf_array[0], 1.0) + assert_allclose(no_loss, 0) + vec_loss = shading.linear_shade_loss(expected_sf_array, 0.2) + expected_loss = np.array([0.09289322, 0.13333333, 0.03019964, 0.0]) + assert_allclose(vec_loss, expected_loss) From c3a9569b9943a96e01f20c101b4a986e383142f1 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:24:51 +0100 Subject: [PATCH 052/185] Whatsnew entries Co-Authored-By: Mark Mikofski --- docs/sphinx/source/whatsnew/v0.10.4.rst | 6 ++++ docs/sphinx/source/whatsnew/v0.9.6.rst | 46 ------------------------- 2 files changed, 6 insertions(+), 46 deletions(-) delete mode 100644 docs/sphinx/source/whatsnew/v0.9.6.rst diff --git a/docs/sphinx/source/whatsnew/v0.10.4.rst b/docs/sphinx/source/whatsnew/v0.10.4.rst index c61ce8c658..779a8dc269 100644 --- a/docs/sphinx/source/whatsnew/v0.10.4.rst +++ b/docs/sphinx/source/whatsnew/v0.10.4.rst @@ -17,6 +17,10 @@ Enhancements * Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) +* Added functions `pvlib.shading.tracker_shaded_fraction` and + `pvlib.shading.linear_shade_loss` to calculate row-to-row shade and apply + linear shade loss for thin film CdTe modules like First Solar. + (:issue:`1689`, :issue:`1690`, :pull:`1962`) Bug fixes ~~~~~~~~~ @@ -61,3 +65,5 @@ Contributors * Adam R. Jensen (:ghuser:`AdamRJensen`) * Kevin Anderson (:ghuser:`kandersolar`) * Peter Dudfield (:ghuser:`peterdudfield`) +* Mark A. Mikofski (:ghuser:`mikofski`) +* Echedey Luis (:ghuser:`echedey-ls`) diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst deleted file mode 100644 index 65575f03bd..0000000000 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ /dev/null @@ -1,46 +0,0 @@ -.. _whatsnew_0960: - - -v0.9.6 (Anticipated June 2023) ------------------------------- - - -Deprecations -~~~~~~~~~~~~ - - -Enhancements -~~~~~~~~~~~~ -* added functions `pvlib.shading.tracker_shaded_fraction` and - `pvlib.shading.linear_shade_loss` to calculate row-to-row shade and apply - linear shade loss for thin film CdTe modules like First Solar. - (:issue:`1689`, :issue:`1690`, :pull:`1725`) - -Bug fixes -~~~~~~~~~ -* `data` can no longer be left unspecified in - :py:meth:`pvlib.modelchain.ModelChain.run_model_from_effective_irradiance`. - (:issue:`1713`, :pull:`1720`) - -Testing -~~~~~~~ - - -Documentation -~~~~~~~~~~~~~ -* Updated the description of the interval parameter in - :py:func:`pvlib.iotools.get_psm3`. (:issue:`1702`, :pull:`1712`) - -Benchmarking -~~~~~~~~~~~~~ - - -Requirements -~~~~~~~~~~~~ - - -Contributors -~~~~~~~~~~~~ -* Adam R. Jensen (:ghuser:`adamrjensen`) -* Siddharth Kaul (:ghuser:`k10blogger`) -* Mark A. Mikofski (:ghuser:`mikofski`) From 4bb02ab994cc7c99c74d5b780aed97ad55024e56 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:26:53 +0100 Subject: [PATCH 053/185] Linter --- 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 13d4ac1532..8a56ad519d 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -228,7 +228,7 @@ def test_projected_solar_zenith_angle_datatypes( @pytest.fixture def sf_premises_and_expected(): """Data comprised of solar position, tracker orientations, ground coverage - ratios and terrain slopes with respective shade fractions (sf)""" + ratios and terrain slopes with respective shade fractions (sf)""" # preserve tracker_shade_fraction's args order and append shadow depth, z premises_and_results = pd.DataFrame( columns=["solar_zenith", "solar_azimuth", "tracker_tilt", From 78c481e5dbffa97386faf065821aad438ec7898a Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:02:39 +0100 Subject: [PATCH 054/185] Clear things, convert Mark's reference to a reference --- pvlib/shading.py | 4 ++-- pvlib/tests/test_shading.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index e37b987299..71fc7bee15 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -405,8 +405,8 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, References ---------- - Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," - PVPMC, 2023 + .. [1] Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped + Terrain," PVPMC, 2023 """ theta_g_rad = np.radians(cross_axis_slope) # projected solar zenith: diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 8a56ad519d..dbfae6355f 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -228,8 +228,11 @@ def test_projected_solar_zenith_angle_datatypes( @pytest.fixture def sf_premises_and_expected(): """Data comprised of solar position, tracker orientations, ground coverage - ratios and terrain slopes with respective shade fractions (sf)""" - # preserve tracker_shade_fraction's args order and append shadow depth, z + ratios and terrain slopes with respective shade fractions (sf). + Returns a 2-tuple with the premises to be used directly in + tracker_shade_fraction(...) in the first element and the expected shaded + fractions on the second element""" + # tracker_shade_fraction's args order and append shadow depth, z premises_and_results = pd.DataFrame( columns=["solar_zenith", "solar_azimuth", "tracker_tilt", "tracker_azimuth", "gcr", "cross_axis_slope", "z"], @@ -245,14 +248,14 @@ def sf_premises_and_expected(): )) # append shaded fraction premises_and_results["shaded_fraction"] = 1 - 1/premises_and_results["z"] - return premises_and_results + return (premises_and_results.drop(columns=["z", "shaded_fraction"]), + premises_and_results["shaded_fraction"]) def test_tracker_shade_fraction(sf_premises_and_expected): """Tests tracker_shade_fraction""" # unwrap sf_premises_and_expected values premises and expected results - premises = sf_premises_and_expected.drop(columns=["z", "shaded_fraction"]) - expected_sf_array = sf_premises_and_expected["shaded_fraction"] + premises, expected_sf_array = sf_premises_and_expected # test scalar inputs from the row iterator # series label := corresponding index in expected_sf_array for index, premise in premises.iterrows(): @@ -266,7 +269,7 @@ def test_tracker_shade_fraction(sf_premises_and_expected): def test_linear_shade_loss(sf_premises_and_expected): - expected_sf_array = sf_premises_and_expected["shaded_fraction"] + _, expected_sf_array = sf_premises_and_expected # Premises are not needed loss = shading.linear_shade_loss(expected_sf_array[0], 0.2) assert_allclose(loss, 0.09289321881345258) # if no diffuse, shade fraction is the loss From 0ce0d68c1503d0c9643d25c4b5d70e9c4b8cb092 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:07:24 +0100 Subject: [PATCH 055/185] Linter --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 71fc7bee15..862f070920 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -405,7 +405,7 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, References ---------- - .. [1] Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped + .. [1] Mark A. Mikofski, "First Solar Irradiance Shade Losses on Sloped Terrain," PVPMC, 2023 """ theta_g_rad = np.radians(cross_axis_slope) From 74b421538d643fd02c9998ab4f4a5297fa62e628 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:33:00 +0100 Subject: [PATCH 056/185] Update according to changes at PSZA PR --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 862f070920..ceecb25097 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -413,11 +413,11 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, # consider the angle the sun direct beam has on the vertical plane which # contains the tracker normal vector, with respect to a horizontal line projected_solar_zenith = projected_solar_zenith_angle( + solar_zenith, + solar_azimuth, 0, # no rotation from the horizontal # the vector that defines the projection plane for prior conditions tracker_azimuth-90, - solar_zenith, - solar_azimuth ) # angle opposite shadow cast on the ground, z angle_z = ( From 909cc23c6982727d830750555e13fba272f4f468 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 28 Feb 2024 00:32:58 +0100 Subject: [PATCH 057/185] Another commit, another try --- pvlib/shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index ceecb25097..874b45bd5f 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 e3e73f6c0f160cd321ebfb0081e8792370c45004 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:03:27 +0100 Subject: [PATCH 058/185] Ahhh, I rebased too fast --- pvlib/shading.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 874b45bd5f..b6cf08b69a 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -85,7 +85,7 @@ def masking_angle(surface_tilt, gcr, slant_height): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - DOI: 10.1016/0379-6787(84)90017-6 + :doi:`10.1016/0379-6787(84)90017-6` .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical Reference Update", NREL Technical Report NREL/TP-6A20-67399. Available at https://www.nrel.gov/docs/fy18osti/67399.pdf @@ -167,7 +167,7 @@ def masking_angle_passias(surface_tilt, gcr): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - DOI: 10.1016/0379-6787(84)90017-6 + :doi:`10.1016/0379-6787(84)90017-6` """ # wrap it in an array so that division by zero is handled well beta = np.radians(np.array(surface_tilt)) @@ -226,7 +226,7 @@ def sky_diffuse_passias(masking_angle): ---------- .. [1] D. Passias and B. Källbäck, "Shading effects in rows of solar cell panels", Solar Cells, Volume 11, Pages 281-291. 1984. - DOI: 10.1016/0379-6787(84)90017-6 + :doi:`10.1016/0379-6787(84)90017-6` .. [2] Gilman, P. et al., (2018). "SAM Photovoltaic Model Technical Reference Update", NREL Technical Report NREL/TP-6A20-67399. Available at https://www.nrel.gov/docs/fy18osti/67399.pdf @@ -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. @@ -276,9 +276,9 @@ 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 @@ -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 cbb0b04afd7cd7e610917c1dbaac9e3f639a6bb9 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:05:38 +0100 Subject: [PATCH 059/185] whatsnews --- docs/sphinx/source/whatsnew/v0.10.3.rst | 1 - docs/sphinx/source/whatsnew/v0.10.4.rst | 2 -- 2 files changed, 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 87b6c72f1f..786989fe26 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -75,4 +75,3 @@ Contributors * Mark Mikofski (:ghuser:`mikofski`) * Phoebe Pearce (:ghuser:`phoebe-p`) * Eva-Maria Grommes (:ghuser:`EwaGomez`) -* Echedey Luis (:ghuser:`echedey-ls`) diff --git a/docs/sphinx/source/whatsnew/v0.10.4.rst b/docs/sphinx/source/whatsnew/v0.10.4.rst index 779a8dc269..e75ead7a5c 100644 --- a/docs/sphinx/source/whatsnew/v0.10.4.rst +++ b/docs/sphinx/source/whatsnew/v0.10.4.rst @@ -16,7 +16,6 @@ Enhancements convention of returning a tuple of (data, meta). Previously the function only returned a dataframe. (:pull:`1968`) * Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) - * Added functions `pvlib.shading.tracker_shaded_fraction` and `pvlib.shading.linear_shade_loss` to calculate row-to-row shade and apply linear shade loss for thin film CdTe modules like First Solar. @@ -66,4 +65,3 @@ Contributors * Kevin Anderson (:ghuser:`kandersolar`) * Peter Dudfield (:ghuser:`peterdudfield`) * Mark A. Mikofski (:ghuser:`mikofski`) -* Echedey Luis (:ghuser:`echedey-ls`) From 96b61a807b696e2ea034832b3fda7ea4383c9e6e Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:07:08 +0100 Subject: [PATCH 060/185] Update v0.10.4.rst --- docs/sphinx/source/whatsnew/v0.10.4.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.4.rst b/docs/sphinx/source/whatsnew/v0.10.4.rst index e75ead7a5c..330c2cc92b 100644 --- a/docs/sphinx/source/whatsnew/v0.10.4.rst +++ b/docs/sphinx/source/whatsnew/v0.10.4.rst @@ -14,8 +14,6 @@ Enhancements the SOLRAD ground station network. (:pull:`1967`) * Added metadata parsing to :py:func:`~pvlib.iotools.read_solrad` to follow the standard iotools convention of returning a tuple of (data, meta). Previously the function only returned a dataframe. (:pull:`1968`) -* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, - a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) * Added functions `pvlib.shading.tracker_shaded_fraction` and `pvlib.shading.linear_shade_loss` to calculate row-to-row shade and apply linear shade loss for thin film CdTe modules like First Solar. From d2b92914d8cf5342e80721893caae2bdacb3dde4 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:11:37 +0100 Subject: [PATCH 061/185] Update v0.10.3.rst --- docs/sphinx/source/whatsnew/v0.10.3.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 786989fe26..4d222fca06 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -23,8 +23,6 @@ Enhancements * Add :py:func:`pvlib.iotools.read_solaranywhere` and :py:func:`pvlib.iotools.get_solaranywhere` for reading and retrieving SolarAnywhere solar irradiance data. (:pull:`1497`, :discuss:`1310`) -* Added function :py:func:`pvlib.shading.projected_solar_zenith_angle`, - a common calculation in shading and tracking. (:issue:`1734`, :pull:`1904`) Bug fixes ~~~~~~~~~ From bbc6115923a7c1a4b6ad8a8a4bebeaeb6bb505aa Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:20:45 +0100 Subject: [PATCH 062/185] Rename to `shaded_fraction1d`, change params to `surface_*` instead of `tracker_*` --- pvlib/shading.py | 24 ++++++++++++++---------- pvlib/tests/test_shading.py | 35 +++++++++++++++++------------------ 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index b6cf08b69a..961efad4c1 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -344,10 +344,13 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, return theta_T -def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, - tracker_azimuth, gcr, cross_axis_slope=0): +def shaded_fraction1d(solar_zenith, solar_azimuth, surface_tilt, + surface_azimuth, gcr, cross_axis_slope=0): r""" - Shaded fraction for trackers with a common angle on an east-west slope. + Shaded fraction in the vertical dimension of the rows. + + Assumes both the shaded row and the one blocking the direct beam share + the same tilt and azimuth values. Parameters ---------- @@ -355,16 +358,17 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, Solar position zenith, in degrees. solar_azimuth : numeric Solar position azimuth, in degrees. - tracker_tilt : numeric + surface_tilt : numeric In degrees. - tracker_azimuth : numeric + surface_azimuth : numeric In degrees. North=0º, South=180º, East=90º, West=270º. gcr : numeric The ground coverage ratio as a fraction equal to the collector width over the horizontal row-to-row pitch. cross_axis_slope : numeric, default 0 - Angle of the plane containing the tracker axes in degrees from - horizontal. + Angle of the plane containing the rows' axes in degrees from + horizontal. A row axis is defined by the vector product of + ``surface_tilt`` and ``surface_azimuth``. Returns ------- @@ -377,7 +381,7 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, pvlib.shading.linear_shade_loss - The shaded fraction is derived using trigonometery and similar triangles + The shaded fraction is derived using trigonometry and similar triangles from the tracker rotation :math:`\beta`, the ground slope :math:`\theta_g`, the projected solar zenith (psz) :math:`\theta`, the collector width :math:`L`, the row-to-row pitch :math:`P`, and the shadow length :math:`z` @@ -416,11 +420,11 @@ def tracker_shaded_fraction(solar_zenith, solar_azimuth, tracker_tilt, solar_azimuth, 0, # no rotation from the horizontal # the vector that defines the projection plane for prior conditions - tracker_azimuth-90, + surface_azimuth-90, ) # angle opposite shadow cast on the ground, z angle_z = ( - np.pi / 2 - np.radians(tracker_tilt) + np.pi / 2 - np.radians(surface_tilt) + np.radians(projected_solar_zenith)) # angle opposite the collector width, L angle_gcr = ( diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index dbfae6355f..077c90e461 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -227,44 +227,43 @@ def test_projected_solar_zenith_angle_datatypes( @pytest.fixture def sf_premises_and_expected(): - """Data comprised of solar position, tracker orientations, ground coverage + """Data comprised of solar position, rows orientations, ground coverage ratios and terrain slopes with respective shade fractions (sf). Returns a 2-tuple with the premises to be used directly in - tracker_shade_fraction(...) in the first element and the expected shaded + shaded_fraction1d(...) in the first element and the expected shaded fractions on the second element""" - # tracker_shade_fraction's args order and append shadow depth, z + # shaded_fraction1d's args order and append shadow depth, z premises_and_results = pd.DataFrame( - columns=["solar_zenith", "solar_azimuth", "tracker_tilt", - "tracker_azimuth", "gcr", "cross_axis_slope", "z"], + columns=["solar_zenith", "solar_azimuth", "surface_tilt", + "surface_azimuth", "gcr", "cross_axis_slope", "z"], data=( - # trivial case, 80% gcr, no slope, trackers & psz at 45-deg - (45, 90., 45, 90., 0.8, 0, np.sqrt(2*0.8*0.8)), - # another trivial case, 60% gcr, no slope, trackers & psz at 60-deg + # trivial case, 80% gcr, no slope, rows & psz at 45-deg + (45, 90., 45, 90., 0.8, 0, 0.8*np.sqrt(2)), + # another trivial case, 60% gcr, no slope, rows & psz at 60-deg (60, 120, 60, 120, 0.6, 0, 2*0.6), - # 30-deg isosceles, 60% gcr, no slope, 30-deg trackers, psz 60-deg + # 30-deg isosceles, 60% gcr, no slope, 30-deg rows, psz 60-deg (60, 135, 30, 135, 0.6, 0, 0.6*np.sqrt(3)), # no shading, 40% gcr, shadow is only 0.565-m long < 1-m r2r P (45, 180, 45, 180, 0.4, 0, 1) # z := 1 means no shadow )) # append shaded fraction premises_and_results["shaded_fraction"] = 1 - 1/premises_and_results["z"] + # return 1st: premises dataframe first and 2nd: shaded fraction series return (premises_and_results.drop(columns=["z", "shaded_fraction"]), premises_and_results["shaded_fraction"]) -def test_tracker_shade_fraction(sf_premises_and_expected): - """Tests tracker_shade_fraction""" +def test_shade_fraction1d(sf_premises_and_expected): + """Tests shaded_fraction1d""" # unwrap sf_premises_and_expected values premises and expected results premises, expected_sf_array = sf_premises_and_expected - # test scalar inputs from the row iterator - # series label := corresponding index in expected_sf_array - for index, premise in premises.iterrows(): - expected_result = expected_sf_array[index] - sf = shading.tracker_shaded_fraction(**premise) - assert_allclose(sf, expected_result) + # test scalar input + expected_result = expected_sf_array.iloc[0] + sf = shading.shaded_fraction1d(**premises.iloc[0]) + assert_allclose(sf, expected_result) # test vector inputs - sf_vec = shading.tracker_shaded_fraction(**premises) + sf_vec = shading.shaded_fraction1d(**premises) assert_allclose(sf_vec, expected_sf_array) From 39a3ba9999052b2ee7a450b22ededc78e0f89c17 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:25:53 +0100 Subject: [PATCH 063/185] Left this tracker refs behind --- pvlib/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index 961efad4c1..bf1ad3574e 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -382,7 +382,7 @@ def shaded_fraction1d(solar_zenith, solar_azimuth, surface_tilt, The shaded fraction is derived using trigonometry and similar triangles - from the tracker rotation :math:`\beta`, the ground slope :math:`\theta_g`, + from the row rotation :math:`\beta`, the ground slope :math:`\theta_g`, the projected solar zenith (psz) :math:`\theta`, the collector width :math:`L`, the row-to-row pitch :math:`P`, and the shadow length :math:`z` as shown in the image below. @@ -414,7 +414,7 @@ def shaded_fraction1d(solar_zenith, solar_azimuth, surface_tilt, theta_g_rad = np.radians(cross_axis_slope) # projected solar zenith: # consider the angle the sun direct beam has on the vertical plane which - # contains the tracker normal vector, with respect to a horizontal line + # contains the row's normal vector, with respect to a horizontal line projected_solar_zenith = projected_solar_zenith_angle( solar_zenith, solar_azimuth, From a3ccf3ef3ddfc926022d1e15734248c27730401b Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:31:35 +0100 Subject: [PATCH 064/185] Change rename in rst entries --- .../source/reference/effects_on_pv_system_output/shading.rst | 2 +- 1 file changed, 1 insertion(+), 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 189c299fa2..9acb17176a 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 @@ -11,6 +11,6 @@ Shading shading.masking_angle_passias shading.sky_diffuse_passias shading.projected_solar_zenith_angle - shading.tracker_shaded_fraction + shading.shaded_fraction1d shading.linear_shade_loss From e5fffcaaa1f382b1c2d9ede8fd6e2d3dcf4082ce Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:15:36 +0100 Subject: [PATCH 065/185] Add another testcase --- 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 077c90e461..7103831635 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -244,7 +244,9 @@ def sf_premises_and_expected(): # 30-deg isosceles, 60% gcr, no slope, 30-deg rows, psz 60-deg (60, 135, 30, 135, 0.6, 0, 0.6*np.sqrt(3)), # no shading, 40% gcr, shadow is only 0.565-m long < 1-m r2r P - (45, 180, 45, 180, 0.4, 0, 1) # z := 1 means no shadow + (45, 180, 45, 180, 0.4, 0, 1), # z := 1 means no shadow + # no shading, sun behind (~1st case, row azimuth+180, lower zenith) + (40, 180, 45, 90., 0.8, 0, 1), # z := 1 means no shadow )) # append shaded fraction premises_and_results["shaded_fraction"] = 1 - 1/premises_and_results["z"] From 457d4b4813b65d9c06a0e9798ddc4e7f13557dbb Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:16:26 +0100 Subject: [PATCH 066/185] Improve docs references, clarify nomenclature Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com> --- .../_images/Anderson_Mikofski_2020_Fig9.png | Bin 0 -> 36860 bytes .../FSLR_irrad_shade_loss_slope_terrain.png | Bin 127773 -> 0 bytes pvlib/shading.py | 12 +++++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig9.png delete mode 100644 docs/sphinx/source/_images/FSLR_irrad_shade_loss_slope_terrain.png diff --git a/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig9.png b/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig9.png new file mode 100644 index 0000000000000000000000000000000000000000..8e49952d1f7393c9129198fca97217f5ecc71de3 GIT binary patch literal 36860 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