From 96697f11dbba4d0a61fce465d40858649b0096c3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:41:21 +0000 Subject: [PATCH 01/25] Add cross_axis_tilt to nomenclature.rst Definition via https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.tracking.calc_cross_axis_tilt.html --- docs/sphinx/source/user_guide/nomenclature.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/sphinx/source/user_guide/nomenclature.rst b/docs/sphinx/source/user_guide/nomenclature.rst index f361690f73..61285211e8 100644 --- a/docs/sphinx/source/user_guide/nomenclature.rst +++ b/docs/sphinx/source/user_guide/nomenclature.rst @@ -36,6 +36,18 @@ There is a convention on consistent variable names throughout the library: bhi Beam/direct horizontal irradiance + cross_axis_tilt + Cross-axis tilt angle [°]. + Consider two parallel rows of modules at different height; + ``cross_axis_tilt`` is the angle formed by the line formed by the + intersection between the slope containing the tracker axes and a plane + perpendicular to the tracker axes, and the horizontal plane. + Cross-axis tilt is measured by using a right-handed convention. + For example, trackers with axis azimuth of 180 degrees (heading south) + will have a negative cross-axis tilt if the tracker axes plane slopes + down to the east and positive cross-axis tilt if the tracker axes plane + slopes up to the east. + dhi Diffuse horizontal irradiance From 8eebd5faab3c68992fb29ef9a3e7d74227b23889 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:43:44 +0000 Subject: [PATCH 02/25] Rename, cross_axis_slope -> cross_axis_tilt --- .../shading/plot_martinez_shade_loss.py | 2 +- .../plot_shaded_fraction1d_ns_hsat_example.py | 12 +++++------ pvlib/shading.py | 20 +++++++++---------- pvlib/tests/test_shading.py | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/examples/shading/plot_martinez_shade_loss.py b/docs/examples/shading/plot_martinez_shade_loss.py index 10ce77f741..b30b6e4c12 100644 --- a/docs/examples/shading/plot_martinez_shade_loss.py +++ b/docs/examples/shading/plot_martinez_shade_loss.py @@ -116,7 +116,7 @@ shading_row_rotation=tracker_theta, collector_width=width, pitch=pitch, - cross_axis_slope=cross_axis_tilt, + cross_axis_tilt=cross_axis_tilt, ) # %% diff --git a/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py b/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py index 7eae2ed7aa..9a1e942e5d 100644 --- a/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py +++ b/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py @@ -49,7 +49,7 @@ collector_width = 3.2 # m pitch = 4.15 # m gcr = collector_width / pitch -cross_axis_slope = -5 # degrees +cross_axis_tilt = -5 # degrees surface_to_axis_offset = 0.07 # m # Generate a time range for the simulation @@ -76,7 +76,7 @@ max_angle=(-50, 50), # (min, max) degrees backtrack=False, gcr=gcr, - cross_axis_tilt=cross_axis_slope, + cross_axis_tilt=cross_axis_tilt, )["tracker_theta"] # %% @@ -112,7 +112,7 @@ collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_slope=cross_axis_slope, + cross_axis_tilt=cross_axis_tilt, shading_row_rotation=rotation_angle, ), ) @@ -130,7 +130,7 @@ collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_slope=cross_axis_slope, + cross_axis_tilt=cross_axis_tilt, shading_row_rotation=rotation_angle, ), # shaded fraction in the evening @@ -143,7 +143,7 @@ collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_slope=cross_axis_slope, + cross_axis_tilt=cross_axis_tilt, shading_row_rotation=rotation_angle, ), ) @@ -161,7 +161,7 @@ collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_slope=cross_axis_slope, + cross_axis_tilt=cross_axis_tilt, shading_row_rotation=rotation_angle, ), 0, # no shaded fraction in the evening diff --git a/pvlib/shading.py b/pvlib/shading.py index 42aef892f7..0bdd40dbfb 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -354,7 +354,7 @@ def shaded_fraction1d( pitch, axis_tilt=0, surface_to_axis_offset=0, - cross_axis_slope=0, + cross_axis_tilt=0, shading_row_rotation=None, ): r""" @@ -397,7 +397,7 @@ def shaded_fraction1d( surface_to_axis_offset : numeric, default 0 Distance between the rotating axis and the collector surface. May be used to account for a torque tube offset. - cross_axis_slope : numeric, default 0 + cross_axis_tilt : numeric, default 0 Angle of the plane containing the rows' axes from horizontal. Right-handed rotation with respect to the rows axes. In degrees :math:`^{\circ}`. @@ -430,7 +430,7 @@ def shaded_fraction1d( +------------------+----------------------------+ | | :math:`\theta_2` | ``shaded_row_rotation`` | Degrees | +------------------+----------------------------+ :math:`^{\circ}` | - | :math:`\beta_c` | ``cross_axis_slope`` | | + | :math:`\beta_c` | ``cross_axis_tilt`` | | +------------------+----------------------------+---------------------+ | :math:`p` | ``pitch`` | Any consistent | +------------------+----------------------------+ length unit across | @@ -452,7 +452,7 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=135, ... axis_azimuth=90, shaded_row_rotation=30, shading_row_rotation=30, ... collector_width=2, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.05, cross_axis_slope=0) + ... surface_to_axis_offset=0.05, cross_axis_tilt=0) 0.47755694708090535 **Fixed-tilt north-facing array on sloped terrain** @@ -466,7 +466,7 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=75.5, ... axis_azimuth=270, shaded_row_rotation=50, shading_row_rotation=30, ... collector_width=2.5, pitch=4, axis_tilt=10, - ... surface_to_axis_offset=0.05, cross_axis_slope=0) + ... surface_to_axis_offset=0.05, cross_axis_tilt=0) 0.793244836197256 **N-S single-axis tracker on sloped terrain** @@ -478,7 +478,7 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=90, axis_azimuth=180, ... shaded_row_rotation=-30, collector_width=1.4, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.10, cross_axis_slope=7) + ... surface_to_axis_offset=0.10, cross_axis_tilt=7) 0.8242176864434579 Note the previous example only is valid for the shaded fraction of the @@ -493,7 +493,7 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=270, axis_azimuth=180, ... shaded_row_rotation=30, collector_width=1.4, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.10, cross_axis_slope=7) + ... surface_to_axis_offset=0.10, cross_axis_tilt=7) 0.018002567182254348 You must switch the input/output depending on the @@ -528,7 +528,7 @@ def shaded_fraction1d( # calculate repeated elements thetas_1_S_diff = shading_row_rotation - projected_solar_zenith thetas_2_S_diff = shaded_row_rotation - projected_solar_zenith - thetaS_rotation_diff = projected_solar_zenith - cross_axis_slope + thetaS_rotation_diff = projected_solar_zenith - cross_axis_tilt cos_theta_2_S_diff_abs = np.abs(cosd(thetas_2_S_diff)) @@ -548,7 +548,7 @@ def shaded_fraction1d( / collector_width * cosd(thetaS_rotation_diff) / cos_theta_2_S_diff_abs - / cosd(cross_axis_slope) + / cosd(cross_axis_tilt) ) ) @@ -660,7 +660,7 @@ def direct_martinez( >>> solar_zenith=80, solar_azimuth=180, >>> axis_azimuth=90, shaded_row_rotation=25, >>> collector_width=0.5, pitch=1, surface_to_axis_offset=0, - >>> cross_axis_slope=5.711, shading_row_rotation=50) + >>> cross_axis_tilt=5.711, shading_row_rotation=50) >>> # calculation of the number of shaded blocks >>> shaded_blocks = np.ceil(total_blocks*shaded_fraction) >>> # apply the Martinez power losses to the calculated shading diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index f83f2db47b..3887dd8fd9 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -258,7 +258,7 @@ def sf1d_premises_and_expected(): ), ) # fmt: skip - test_data["cross_axis_slope"] = atand( + test_data["cross_axis_tilt"] = atand( (test_data["z_R"] - test_data["z_L"]) / (test_data["x_L"] - test_data["x_R"]) ) @@ -314,7 +314,7 @@ def test_shaded_fraction1d_unprovided_shading_row_rotation(): test_data = pd.DataFrame( columns=[ "shaded_row_rotation", "surface_to_axis_offset", "collector_width", - "solar_zenith", "cross_axis_slope", "pitch", "solar_azimuth", + "solar_zenith", "cross_axis_tilt", "pitch", "solar_azimuth", "axis_azimuth", "expected_sf", ], data=[ From 0aa883697bab97a7e85c5e0849d1f195a93070dc Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:53:10 +0000 Subject: [PATCH 03/25] Add renamed_kwarg_warning --- pvlib/shading.py | 8 ++++++++ pvlib/tests/test_shading.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pvlib/shading.py b/pvlib/shading.py index 0bdd40dbfb..b3e9f8822e 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -7,6 +7,8 @@ import pandas as pd from pvlib.tools import sind, cosd +from pvlib._deprecation import renamed_kwarg_warning + def ground_angle(surface_tilt, gcr, slant_height): """ @@ -344,6 +346,12 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, return theta_T +@renamed_kwarg_warning( + since="0.11.3", + old_param_name="cross_axis_slope", + new_param_name="cross_axis_tilt", + removal="0.13.0", +) def shaded_fraction1d( solar_zenith, solar_azimuth, diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 3887dd8fd9..8a0529222b 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -9,6 +9,9 @@ from pvlib import shading from pvlib.tools import atand +from pvlib.tests.conftest import fail_on_pvlib_version +from pvlib._deprecation import pvlibDeprecationWarning + @pytest.fixture def test_system(): @@ -329,6 +332,22 @@ def test_shaded_fraction1d_unprovided_shading_row_rotation(): assert_allclose(sf, expected_sf, atol=1e-2) +@fail_on_pvlib_version("0.13.0") +def test_shaded_fraction1d_renamed_cross_axis_slope2cross_axis_tilt(): + # Tests shaded_fraction1d with cross_axis_slope instead of cross_axis_tilt + with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): + shading.shaded_fraction1d( + solar_zenith=60, + solar_azimuth=90, + axis_azimuth=180, + shaded_row_rotation=30, + collector_width=3, + pitch=7, + surface_to_axis_offset=0, + cross_axis_slope=0, + ) + + @pytest.fixture def direct_martinez_Table2(): """ From 01d5c0b11be881bfec44277304eb386b1d6391d3 Mon Sep 17 00:00:00 2001 From: echedey-ls <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:57:31 +0000 Subject: [PATCH 04/25] Fix typo in renamed_kwarg_warning docstring --- pvlib/_deprecation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/_deprecation.py b/pvlib/_deprecation.py index aedb4d5096..06d35959e0 100644 --- a/pvlib/_deprecation.py +++ b/pvlib/_deprecation.py @@ -334,7 +334,7 @@ def renamed_kwarg_warning(since, old_param_name, new_param_name, removal=""): Not compatible with positional-only arguments. .. note:: - Documentation for the function may updated to reflect the new parameter + Affected function docstring may be updated to reflect the new parameter name; it is suggested to add a |.. versionchanged::| directive. Parameters From e72ca3215b0cc98ac20403d5028ebbabe667a2cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:20:47 +0100 Subject: [PATCH 05/25] Deprecate in v0.13.1 / Removal in v0.15.0 --- pvlib/shading.py | 4 ++-- pvlib/tests/test_shading.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/shading.py b/pvlib/shading.py index b3e9f8822e..fa5f534701 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -347,10 +347,10 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, @renamed_kwarg_warning( - since="0.11.3", + since="0.13.1", old_param_name="cross_axis_slope", new_param_name="cross_axis_tilt", - removal="0.13.0", + removal="0.15.0", ) def shaded_fraction1d( solar_zenith, diff --git a/pvlib/tests/test_shading.py b/pvlib/tests/test_shading.py index 8a0529222b..986cab3054 100644 --- a/pvlib/tests/test_shading.py +++ b/pvlib/tests/test_shading.py @@ -332,7 +332,7 @@ def test_shaded_fraction1d_unprovided_shading_row_rotation(): assert_allclose(sf, expected_sf, atol=1e-2) -@fail_on_pvlib_version("0.13.0") +@fail_on_pvlib_version("0.15.0") def test_shaded_fraction1d_renamed_cross_axis_slope2cross_axis_tilt(): # Tests shaded_fraction1d with cross_axis_slope instead of cross_axis_tilt with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): From 08bcde47a31521e1d9487f7f6d7f619a961e27ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:47:46 +0100 Subject: [PATCH 06/25] move descriptions, link to nomenclature term --- docs/sphinx/source/user_guide/nomenclature.rst | 2 ++ pvlib/shading.py | 4 ++-- pvlib/tracking.py | 14 ++++---------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/sphinx/source/user_guide/nomenclature.rst b/docs/sphinx/source/user_guide/nomenclature.rst index 61285211e8..827fb99edb 100644 --- a/docs/sphinx/source/user_guide/nomenclature.rst +++ b/docs/sphinx/source/user_guide/nomenclature.rst @@ -47,6 +47,8 @@ There is a convention on consistent variable names throughout the library: will have a negative cross-axis tilt if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes up to the east. + Use :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate + ``cross_axis_tilt`` dhi Diffuse horizontal irradiance diff --git a/pvlib/shading.py b/pvlib/shading.py index fa5f534701..7be2b62ca5 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -406,9 +406,9 @@ def shaded_fraction1d( Distance between the rotating axis and the collector surface. May be used to account for a torque tube offset. cross_axis_tilt : numeric, default 0 - Angle of the plane containing the rows' axes from + Angle of the plane containing the rows' axes relative to horizontal. Right-handed rotation with respect to the rows axes. - In degrees :math:`^{\circ}`. + See :term:`cross_axis_tilt`. In degrees :math:`^{\circ}`. shading_row_rotation : numeric, optional Right-handed rotation of the row casting the shadow, with respect to the row axis. In degrees :math:`^{\circ}`. diff --git a/pvlib/tracking.py b/pvlib/tracking.py index afdaab2adf..3e04e7cc5f 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -9,7 +9,7 @@ def singleaxis(apparent_zenith, apparent_azimuth, axis_tilt=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0, cross_axis_tilt=0): - """ + r""" Determine the rotation angle of a single-axis tracker when given particular solar zenith and azimuth angles. @@ -75,15 +75,9 @@ def singleaxis(apparent_zenith, apparent_azimuth, 2/7 is default. ``gcr`` must be <=1. cross_axis_tilt : float, default 0.0 - The angle, relative to horizontal, of the line formed by the - intersection between the slope containing the tracker axes and a plane - perpendicular to the tracker axes. The cross-axis tilt should be - specified using a right-handed convention. For example, trackers with - axis azimuth of 180 degrees (heading south) will have a negative - cross-axis tilt if the tracker axes plane slopes down to the east and - positive cross-axis tilt if the tracker axes plane slopes down to the - west. Use :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate - ``cross_axis_tilt``. [degrees] + Angle of the plane containing the rows' axes relative to + horizontal. Right-handed rotation with respect to the rows axes. + See :term:`cross_axis_tilt`. In degrees :math:`^{\circ}`. Returns ------- From 3ca225e7352bdeaf22f26b8b9e7ba659a0b32de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:57:35 +0100 Subject: [PATCH 07/25] merge fix --- tests/test_shading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_shading.py b/tests/test_shading.py index 986cab3054..a19917774b 100644 --- a/tests/test_shading.py +++ b/tests/test_shading.py @@ -9,7 +9,7 @@ from pvlib import shading from pvlib.tools import atand -from pvlib.tests.conftest import fail_on_pvlib_version +from .conftest import fail_on_pvlib_version from pvlib._deprecation import pvlibDeprecationWarning From c74127c4138233cb5e7e6cddf7253387aa5f2101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:57:42 +0100 Subject: [PATCH 08/25] whatsnew --- docs/sphinx/source/whatsnew/v0.13.1.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 26a591e534..58bf5664a4 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -13,6 +13,7 @@ Deprecations * Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`) * Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) +* Rename parameter ``cross_axis_slope`` to ``cross_axis_tilt`` in :py:func:`pvlib.shading.shaded_fraction1d`. (:issue:`2334`, :pull:`2543`) Bug fixes ~~~~~~~~~ From 3006aa15acefb7726099effed42e76466458a553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:07:50 +0200 Subject: [PATCH 09/25] Dax review Co-Authored-By: RDaxini <143435106+RDaxini@users.noreply.github.com> --- docs/sphinx/source/user_guide/extras/nomenclature.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index e5bf41091c..6c6ad92514 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -45,13 +45,13 @@ There is a convention on consistent variable names throughout the library: Beam/direct horizontal irradiance cross_axis_tilt - Cross-axis tilt angle [°]. + Cross-axis tilt angle. [°] Consider two parallel rows of modules at different height; - ``cross_axis_tilt`` is the angle formed by the line formed by the + ``cross_axis_tilt`` is the angle formed the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes, and the horizontal plane. Cross-axis tilt is measured by using a right-handed convention. - For example, trackers with axis azimuth of 180 degrees (heading south) + For example, trackers with axis azimuth of 180° (heading south) will have a negative cross-axis tilt if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes up to the east. From 2cebae7d71cef29a87096e158c8f8103cc52f800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:41:40 +0200 Subject: [PATCH 10/25] Change: deprecate *axis_tilt in favour of *axis_slope --- benchmarks/benchmarks/infinite_sheds.py | 2 +- benchmarks/benchmarks/tracking.py | 2 +- docs/examples/bifacial/plot_bifi_model_mc.py | 4 +- .../plot_transposition_gain.py | 2 +- .../shading/plot_martinez_shade_loss.py | 14 +- .../plot_shaded_fraction1d_ns_hsat_example.py | 26 ++-- .../plot_discontinuous_tracking.py | 6 +- .../plot_single_axis_tracking.py | 4 +- ..._single_axis_tracking_on_sloped_terrain.py | 28 ++-- docs/sphinx/source/reference/tracking.rst | 4 +- .../source/user_guide/extras/nomenclature.rst | 8 +- docs/sphinx/source/whatsnew/v0.13.1.rst | 12 +- docs/sphinx/source/whatsnew/v0.8.0.rst | 6 +- pvlib/modelchain.py | 2 +- pvlib/pvsystem.py | 93 +++++++++++-- pvlib/shading.py | 83 +++++++----- pvlib/tracking.py | 121 ++++++++++++----- tests/test_pvsystem.py | 42 +++++- tests/test_shading.py | 50 ++++--- tests/test_tracking.py | 127 ++++++++++++------ 20 files changed, 441 insertions(+), 195 deletions(-) diff --git a/benchmarks/benchmarks/infinite_sheds.py b/benchmarks/benchmarks/infinite_sheds.py index 755c2ec3ef..d5a3de1033 100644 --- a/benchmarks/benchmarks/infinite_sheds.py +++ b/benchmarks/benchmarks/infinite_sheds.py @@ -35,7 +35,7 @@ def setup(self, vectorize): self.tracking = tracking.singleaxis( self.solar_position['apparent_zenith'], self.solar_position['azimuth'], - axis_tilt=0, + axis_slope=0, axis_azimuth=0, max_angle=60, backtrack=True, diff --git a/benchmarks/benchmarks/tracking.py b/benchmarks/benchmarks/tracking.py index 29f5ac4c6e..0308a65d38 100644 --- a/benchmarks/benchmarks/tracking.py +++ b/benchmarks/benchmarks/tracking.py @@ -22,7 +22,7 @@ def time_singleaxis(self): with np.errstate(invalid='ignore'): tracking.singleaxis(self.solar_position.apparent_zenith, self.solar_position.azimuth, - axis_tilt=0, + axis_slope=0, axis_azimuth=0, max_angle=60, backtrack=True, diff --git a/docs/examples/bifacial/plot_bifi_model_mc.py b/docs/examples/bifacial/plot_bifi_model_mc.py index 679ff22921..61e801277d 100644 --- a/docs/examples/bifacial/plot_bifi_model_mc.py +++ b/docs/examples/bifacial/plot_bifi_model_mc.py @@ -43,7 +43,7 @@ times = pd.date_range('2021-06-21', '2021-6-22', freq='1T', tz=tz) # create site system characteristics -axis_tilt = 0 +axis_slope = 0 axis_azimuth = 180 gcr = 0.35 max_angle = 60 @@ -65,7 +65,7 @@ cs = site_location.get_clearsky(times) # load solar position and tracker orientation for use in pvsystem object -sat_mount = pvsystem.SingleAxisTrackerMount(axis_tilt=axis_tilt, +sat_mount = pvsystem.SingleAxisTrackerMount(axis_slope=axis_slope, axis_azimuth=axis_azimuth, max_angle=max_angle, backtrack=True, diff --git a/docs/examples/irradiance-transposition/plot_transposition_gain.py b/docs/examples/irradiance-transposition/plot_transposition_gain.py index e0b7031f0b..02fe7e634a 100644 --- a/docs/examples/irradiance-transposition/plot_transposition_gain.py +++ b/docs/examples/irradiance-transposition/plot_transposition_gain.py @@ -85,7 +85,7 @@ def calculate_poa(tmy, solar_position, surface_tilt, surface_azimuth): # single-axis tracking: orientation = tracking.singleaxis(solar_position['apparent_zenith'], solar_position['azimuth'], - axis_tilt=0, # flat array + axis_slope=0, # flat array axis_azimuth=180, # south-facing azimuth max_angle=60, # a common maximum rotation backtrack=True, # backtrack for a c-Si array diff --git a/docs/examples/shading/plot_martinez_shade_loss.py b/docs/examples/shading/plot_martinez_shade_loss.py index 6423017567..d8f0d7e0f3 100644 --- a/docs/examples/shading/plot_martinez_shade_loss.py +++ b/docs/examples/shading/plot_martinez_shade_loss.py @@ -54,8 +54,8 @@ gcr = width / pitch # ground coverage ratio N_modules_per_row = 6 axis_azimuth = 180 # N-S axis -axis_tilt = 0 # flat because the axis is perpendicular to the slope -cross_axis_tilt = -7 # 7 degrees downward to the east +axis_slope = 0 # flat because the axis is perpendicular to the slope +cross_axis_slope = -7 # 7 degrees downward to the east latitude, longitude = 40.2712, -3.7277 locus = pvlib.location.Location( @@ -91,12 +91,12 @@ tracking_result = pvlib.tracking.singleaxis( apparent_zenith=solar_apparent_zenith, solar_azimuth=solar_azimuth, - axis_tilt=axis_tilt, + axis_slope=axis_slope, axis_azimuth=axis_azimuth, - max_angle=(-90 + cross_axis_tilt, 90 + cross_axis_tilt), # (min, max) + max_angle=(-90 + cross_axis_slope, 90 + cross_axis_slope), # (min, max) backtrack=False, gcr=gcr, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, ) tracker_theta, aoi, surface_tilt, surface_azimuth = ( @@ -111,12 +111,12 @@ solar_apparent_zenith, solar_azimuth, axis_azimuth, - axis_tilt=axis_tilt, + axis_slope=axis_slope, shaded_row_rotation=tracker_theta, shading_row_rotation=tracker_theta, collector_width=width, pitch=pitch, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, ) # %% diff --git a/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py b/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py index 9a1e942e5d..f216732f5d 100644 --- a/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py +++ b/docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py @@ -44,12 +44,12 @@ latitude, longitude = 28.51, -13.89 altitude = pvlib.location.lookup_altitude(latitude, longitude) -axis_tilt = 3 # degrees, positive is upwards in the axis_azimuth direction +axis_slope = 3 # degrees, positive is upwards in the axis_azimuth direction axis_azimuth = 180 # degrees, N-S tracking axis collector_width = 3.2 # m pitch = 4.15 # m gcr = collector_width / pitch -cross_axis_tilt = -5 # degrees +cross_axis_slope = -5 # degrees surface_to_axis_offset = 0.07 # m # Generate a time range for the simulation @@ -71,12 +71,12 @@ rotation_angle = pvlib.tracking.singleaxis( solar_zenith, solar_azimuth, - axis_tilt, + axis_slope, axis_azimuth, max_angle=(-50, 50), # (min, max) degrees backtrack=False, gcr=gcr, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, )["tracker_theta"] # %% @@ -95,7 +95,7 @@ # failure or with a different system configuration. psza = pvlib.shading.projected_solar_zenith_angle( - solar_zenith, solar_azimuth, axis_tilt, axis_azimuth + solar_zenith, solar_azimuth, axis_slope, axis_azimuth ) # Calculate the shaded fraction for the eastmost row @@ -108,11 +108,11 @@ solar_azimuth, axis_azimuth, shaded_row_rotation=rotation_angle, - axis_tilt=axis_tilt, + axis_slope=axis_slope, collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, shading_row_rotation=rotation_angle, ), ) @@ -126,11 +126,11 @@ solar_azimuth, axis_azimuth, shaded_row_rotation=rotation_angle, - axis_tilt=axis_tilt, + axis_slope=axis_slope, collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, shading_row_rotation=rotation_angle, ), # shaded fraction in the evening @@ -139,11 +139,11 @@ solar_azimuth, axis_azimuth, shaded_row_rotation=rotation_angle, - axis_tilt=axis_tilt, + axis_slope=axis_slope, collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, shading_row_rotation=rotation_angle, ), ) @@ -157,11 +157,11 @@ solar_azimuth, axis_azimuth, shaded_row_rotation=rotation_angle, - axis_tilt=axis_tilt, + axis_slope=axis_slope, collector_width=collector_width, pitch=pitch, surface_to_axis_offset=surface_to_axis_offset, - cross_axis_tilt=cross_axis_tilt, + cross_axis_slope=cross_axis_slope, shading_row_rotation=rotation_angle, ), 0, # no shaded fraction in the evening diff --git a/docs/examples/solar-tracking/plot_discontinuous_tracking.py b/docs/examples/solar-tracking/plot_discontinuous_tracking.py index f385675d57..fd8a03c500 100644 --- a/docs/examples/solar-tracking/plot_discontinuous_tracking.py +++ b/docs/examples/solar-tracking/plot_discontinuous_tracking.py @@ -26,7 +26,7 @@ class DiscontinuousTrackerMount(pvsystem.SingleAxisTrackerMount): # inherit from SingleAxisTrackerMount so that we get the - # constructor and tracking attributes (axis_tilt etc) automatically + # constructor and tracking attributes (axis_slope etc) automatically def get_orientation(self, solar_zenith, solar_azimuth): # Different trackers update at different rates; in this example we'll @@ -37,9 +37,9 @@ def get_orientation(self, solar_zenith, solar_azimuth): tracking_data_15min = tracking.singleaxis( zenith_subset, azimuth_subset, - self.axis_tilt, self.axis_azimuth, + self.axis_slope, self.axis_azimuth, self.max_angle, self.backtrack, - self.gcr, self.cross_axis_tilt + self.gcr, self.cross_axis_slope ) # propagate the 15-minute positions to 1-minute stair-stepped values: tracking_data_1min = tracking_data_15min.reindex(solar_zenith.index, diff --git a/docs/examples/solar-tracking/plot_single_axis_tracking.py b/docs/examples/solar-tracking/plot_single_axis_tracking.py index 4f7d62cb80..72172630c1 100644 --- a/docs/examples/solar-tracking/plot_single_axis_tracking.py +++ b/docs/examples/solar-tracking/plot_single_axis_tracking.py @@ -34,7 +34,7 @@ truetracking_angles = tracking.singleaxis( apparent_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'], - axis_tilt=0, + axis_slope=0, axis_azimuth=180, max_angle=90, backtrack=False, # for true-tracking @@ -62,7 +62,7 @@ backtracking_angles = tracking.singleaxis( apparent_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'], - axis_tilt=0, + axis_slope=0, axis_azimuth=180, max_angle=90, backtrack=True, diff --git a/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py b/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py index b3a204ce69..7131513a71 100644 --- a/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py +++ b/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py @@ -14,8 +14,8 @@ # calculating the backtracking angle requires knowledge of the relative spacing # of adjacent tracker rows. This example shows how the backtracking angle # changes based on a vertical offset between rows caused by sloped terrain. -# It uses :py:func:`pvlib.tracking.calc_axis_tilt` and -# :py:func:`pvlib.tracking.calc_cross_axis_tilt` to calculate the necessary +# It uses :py:func:`pvlib.tracking.calc_axis_slope` and +# :py:func:`pvlib.tracking.calc_cross_axis_slope` to calculate the necessary # array geometry parameters and :py:func:`pvlib.tracking.singleaxis` to # calculate the backtracking angles. # @@ -97,20 +97,20 @@ # compare the backtracking angle at various terrain slopes fig, ax = plt.subplots() -for cross_axis_tilt in [0, 5, 10]: +for cross_axis_slope in [0, 5, 10]: tracker_data = tracking.singleaxis( apparent_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'], - axis_tilt=0, # flat because the axis is perpendicular to the slope + axis_slope=0, # flat because the axis is perpendicular to the slope axis_azimuth=180, # N-S axis, azimuth facing south max_angle=90, backtrack=True, gcr=gcr, - cross_axis_tilt=cross_axis_tilt) + cross_axis_slope=cross_axis_slope) # tracker rotation is undefined at night backtracking_position = tracker_data['tracker_theta'].fillna(0) - label = 'cross-axis tilt: {}°'.format(cross_axis_tilt) + label = 'cross-axis tilt: {}°'.format(cross_axis_slope) backtracking_position.plot(label=label, ax=ax) plt.legend() @@ -141,14 +141,14 @@ axis_azimuth = 180 # tracker axis is still N-S # calculate the tracker axis tilt, assuming that the axis follows the terrain: -axis_tilt = tracking.calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth) +axis_slope = tracking.calc_axis_slope(slope_azimuth, slope_tilt, axis_azimuth) # calculate the cross-axis tilt: -cross_axis_tilt = tracking.calc_cross_axis_tilt(slope_azimuth, slope_tilt, - axis_azimuth, axis_tilt) +cross_axis_slope = tracking.calc_cross_axis_slope(slope_azimuth, slope_tilt, + axis_azimuth, axis_slope) -print('Axis tilt:', '{:0.01f}°'.format(axis_tilt)) -print('Cross-axis tilt:', '{:0.01f}°'.format(cross_axis_tilt)) +print('Axis tilt:', '{:0.01f}°'.format(axis_slope)) +print('Cross-axis tilt:', '{:0.01f}°'.format(cross_axis_slope)) # %% # And now we can pass use these values to generate the tracker curve as @@ -157,18 +157,18 @@ tracker_data = tracking.singleaxis( apparent_zenith=solpos['apparent_zenith'], solar_azimuth=solpos['azimuth'], - axis_tilt=axis_tilt, # no longer flat because the terrain imparts a tilt + axis_slope=axis_slope, # no longer flat because the terrain imparts a tilt axis_azimuth=axis_azimuth, max_angle=90, backtrack=True, gcr=gcr, - cross_axis_tilt=cross_axis_tilt) + cross_axis_slope=cross_axis_slope) backtracking_position = tracker_data['tracker_theta'].fillna(0) backtracking_position.plot() title_template = 'Axis tilt: {:0.01f}° Cross-axis tilt: {:0.01f}°' -plt.title(title_template.format(axis_tilt, cross_axis_tilt)) +plt.title(title_template.format(axis_slope, cross_axis_slope)) plt.show() # %% diff --git a/docs/sphinx/source/reference/tracking.rst b/docs/sphinx/source/reference/tracking.rst index 4e85ffa3de..1cf67bab8f 100644 --- a/docs/sphinx/source/reference/tracking.rst +++ b/docs/sphinx/source/reference/tracking.rst @@ -7,6 +7,6 @@ Tracking :toctree: generated/ tracking.singleaxis - tracking.calc_axis_tilt - tracking.calc_cross_axis_tilt + tracking.calc_axis_slope + tracking.calc_cross_axis_slope tracking.calc_surface_orientation diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index 6c6ad92514..1467b49bdd 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -44,10 +44,10 @@ There is a convention on consistent variable names throughout the library: bhi Beam/direct horizontal irradiance - cross_axis_tilt + cross_axis_slope Cross-axis tilt angle. [°] Consider two parallel rows of modules at different height; - ``cross_axis_tilt`` is the angle formed the line formed by the + ``cross_axis_slope`` is the angle formed the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes, and the horizontal plane. Cross-axis tilt is measured by using a right-handed convention. @@ -55,8 +55,8 @@ There is a convention on consistent variable names throughout the library: will have a negative cross-axis tilt if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes up to the east. - Use :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate - ``cross_axis_tilt`` + Use :func:`~pvlib.tracking.calc_cross_axis_slope` to calculate + ``cross_axis_slope`` dhi Diffuse horizontal irradiance diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 58bf5664a4..ff5089aa15 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -13,7 +13,17 @@ Deprecations * Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`) * Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) -* Rename parameter ``cross_axis_slope`` to ``cross_axis_tilt`` in :py:func:`pvlib.shading.shaded_fraction1d`. (:issue:`2334`, :pull:`2543`) +* Rename ``axis_tilt`` and ``cross_axis_tilt`` terms to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: + + - :py:func:`pvlib.shading.shaded_fraction1d` parameters ``cross_axis_tilt`` and ``axis_tilt`` + - :py:func:`pvlib.shading.projected_solar_zenith_angle` parameter ``axis_tilt`` + - :py:func:`pvlib.tracking.singleaxis` parameters ``cross_axis_tilt`` and ``axis_tilt`` + - :py:func:`pvlib.tracking.calc_surface_orientation` parameter ``axis_tilt`` + - :py:func:`!pvlib.tracking.calc_axis_tilt` function signature is renamed to :py:func:`pvlib.tracking.calc_axis_slope` + - :py:func:`!pvlib.tracking.calc_cross_axis_tilt` function signature is renamed to :py:func:`pvlib.tracking.calc_cross_axis_slope` and parameter ``axis_tilt`` + - :py:class:`pvlib.pvsystem.SingleAxisTrackerMount` dataclass fields ``axis_tilt`` and ``cross_axis_tilt`` + + (:issue:`2334`, :pull:`2543`) Bug fixes ~~~~~~~~~ diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index 86fb81574f..04e2d16765 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -110,14 +110,14 @@ Enhancements * Added ``racking_model``, ``module_type``, and ``temperature_model_parameters`` to :py:class:`~pvlib.pvsystem.PVSystem` and :py:class:`~pvlib.tracking.SingleAxisTracker` repr methods. (:issue:`1027`) -* Added :py:func:`~pvlib.tracking.calc_axis_tilt` to calculate the - tracker axes tilt and :py:func:`~pvlib.tracking.calc_cross_axis_tilt` to +* Added :py:func:`!~pvlib.tracking.calc_axis_tilt` to calculate the + tracker axes tilt and :py:func:`!~pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis tilt, which is the angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes. (:pull:`823`) * Added ``cross_axis_tilt`` argument to :py:func:`~pvlib.tracking.singleaxis` and :py:func:`~pvlib.tracking.SingleAxisTracker` which defaults to zero. Use - :py:func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis + :py:func:`!~pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis tilt angle if necessary. (:pull:`823`) * Added ability for :py:func:`pvlib.soiling.hsu` to accept arbitrary time intervals. (:pull:`980`) * Added :py:func:`pvlib.temperature.fuentes` for cell temperature modeling. (:pull:`1037`) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 09e4434e84..992399ccab 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1257,7 +1257,7 @@ def _prep_inputs_tracking(self): self.results.solar_position['azimuth']) self.results.tracking['surface_tilt'] = ( self.results.tracking['surface_tilt'] - .fillna(self.system.axis_tilt)) + .fillna(self.system.axis_slope)) self.results.tracking['surface_azimuth'] = ( self.results.tracking['surface_azimuth'] .fillna(self.system.axis_azimuth)) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 23ca1a934a..185c2d598e 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -8,6 +8,7 @@ import io import itertools from pathlib import Path +from warnings import warn import inspect from urllib.request import urlopen import numpy as np @@ -16,7 +17,7 @@ from dataclasses import dataclass from abc import ABC, abstractmethod from typing import Optional, Union -from pvlib._deprecation import renamed_kwarg_warning +from pvlib._deprecation import renamed_kwarg_warning, pvlibDeprecationWarning import pvlib # used to avoid albedo name collision in the Array class from pvlib import (atmosphere, iam, inverter, irradiance, singlediode as _singlediode, spectrum, temperature) @@ -1431,10 +1432,13 @@ class SingleAxisTrackerMount(AbstractMount): Parameters ---------- - axis_tilt : float, default 0 + axis_slope : float, default 0 The tilt of the axis of rotation (i.e, the y-axis defined by axis_azimuth) with respect to horizontal. [degrees] + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope``. + axis_azimuth : float, default 180 A value denoting the compass direction along which the axis of rotation lies, measured east of north. [degrees] @@ -1442,7 +1446,7 @@ class SingleAxisTrackerMount(AbstractMount): max_angle : float or tuple, default 90 A value denoting the maximum rotation angle, in decimal degrees, of the one-axis tracker from its horizontal position (horizontal - if axis_tilt = 0). If a float is provided, it represents the maximum + if axis_slope = 0). If a float is provided, it represents the maximum rotation angle, and the minimum rotation angle is assumed to be the opposite of the maximum angle. If a tuple of (min_angle, max_angle) is provided, it represents both the minimum and maximum rotation angles. @@ -1468,7 +1472,7 @@ class SingleAxisTrackerMount(AbstractMount): between the tracking axes has a gcr of 2/6=0.333. If gcr is not provided, a gcr of 2/7 is default. gcr must be <=1. [unitless] - cross_axis_tilt : float, default 0.0 + cross_axis_slope : float, default 0.0 The angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes. Cross-axis tilt should be specified @@ -1476,8 +1480,11 @@ class SingleAxisTrackerMount(AbstractMount): azimuth of 180 degrees (heading south) will have a negative cross-axis tilt if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes up to the east. Use - :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate - `cross_axis_tilt`. [degrees] + :func:`~pvlib.tracking.calc_cross_axis_slope` to calculate + `cross_axis_slope`. [degrees] + + .. versionchanged:: 0.13.1 + Renamed from ``cross_axis_tilt`` to ``cross_axis_slope``. racking_model : str, optional Valid strings are ``'open_rack'``, ``'close_mount'``, @@ -1493,23 +1500,89 @@ class SingleAxisTrackerMount(AbstractMount): The height above ground of the center of the module [m]. Used for the Fuentes cell temperature model. """ - axis_tilt: float = 0.0 + axis_slope: float = 0.0 axis_azimuth: float = 0.0 max_angle: Union[float, tuple] = 90.0 backtrack: bool = True gcr: float = 2.0/7.0 - cross_axis_tilt: float = 0.0 + cross_axis_slope: float = 0.0 racking_model: Optional[str] = None module_height: Optional[float] = None + # __init__, and properties getters and setters are explicit to deprecate + # field axis_tilt, renamed to axis_slope; and + # field cross_axis_tilt, renamed to cross_axis_slope; GH#2334 & GH#2543 + @renamed_kwarg_warning( + since="0.13.1", + old_param_name="axis_tilt", + new_param_name="axis_slope", + ) + @renamed_kwarg_warning( + since="0.13.1", + old_param_name="cross_axis_tilt", + new_param_name="cross_axis_slope", + ) + def __init__(self, axis_slope=0.0, axis_azimuth=0.0, max_angle=90.0, + backtrack=True, gcr=2.0/7.0, cross_axis_slope=0.0, + racking_model: Optional[str] = None, + module_height: Optional[float] = None): + self.axis_slope = axis_slope + self.axis_azimuth = axis_azimuth + self.max_angle = max_angle + self.backtrack = backtrack + self.gcr = gcr + self.cross_axis_slope = cross_axis_slope + self.racking_model = racking_model + self.module_height = module_height + + @property + def axis_tilt(self): + warn( + "'axis_tilt' is deprecated since v0.13.1. " + "Use 'axis_slope' instead.", + pvlibDeprecationWarning, + stacklevel=2, + ) + return self.axis_slope + + @axis_tilt.setter + def axis_tilt(self, new_value): + warn( + "'axis_tilt' is deprecated since v0.13.1. " + "Use 'axis_slope' instead.", + pvlibDeprecationWarning, + stacklevel=2, + ) + self.axis_slope = new_value + + @property + def cross_axis_tilt(self): + warn( + "'cross_axis_tilt' is deprecated since v0.13.1. " + "Use 'cross_axis_slope' instead.", + pvlibDeprecationWarning, + stacklevel=2, + ) + return self.cross_axis_slope + + @cross_axis_tilt.setter + def cross_axis_tilt(self, new_value): + warn( + "'cross_axis_tilt' is deprecated since v0.13.1. " + "Use 'cross_axis_slope' instead.", + pvlibDeprecationWarning, + stacklevel=2, + ) + self.cross_axis_slope = new_value + def get_orientation(self, solar_zenith, solar_azimuth): # note -- docstring is automatically inherited from AbstractMount from pvlib import tracking # avoid circular import issue tracking_data = tracking.singleaxis( solar_zenith, solar_azimuth, - self.axis_tilt, self.axis_azimuth, + self.axis_slope, self.axis_azimuth, self.max_angle, self.backtrack, - self.gcr, self.cross_axis_tilt + self.gcr, self.cross_axis_slope ) return tracking_data diff --git a/pvlib/shading.py b/pvlib/shading.py index 7be2b62ca5..e6a096e9a1 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -236,15 +236,20 @@ def sky_diffuse_passias(masking_angle): return 1 - cosd(masking_angle/2)**2 +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="axis_tilt", + new_param_name="axis_slope", +) def projected_solar_zenith_angle(solar_zenith, solar_azimuth, - axis_tilt, axis_azimuth): + axis_slope, axis_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 + defined by ``axis_slope`` 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_slope``. See Figure 5 in [1]_: .. figure:: ../../_images/Anderson_Mikofski_2020_Fig5.jpg :alt: Wire diagram of coordinates systems to obtain the projected angle. @@ -259,8 +264,12 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, Sun's apparent zenith in degrees. solar_azimuth : numeric Sun's azimuth in degrees. - axis_tilt : numeric + axis_slope : numeric Axis tilt angle in degrees. From horizontal plane to array plane. + + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope`` + axis_azimuth : numeric Axis azimuth angle in degrees. North = 0°; East = 90°; South = 180°; West = 270° @@ -294,14 +303,14 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, single-axis tracker: >>> rotation = projected_solar_zenith_angle(solar_zenith, solar_azimuth, - >>> axis_tilt=0, axis_azimuth=180) + >>> axis_slope=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) + >>> axis_slope=0, axis_azimuth=90) References ---------- @@ -328,7 +337,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, sind_solar_zenith = sind(solar_zenith) cosd_axis_azimuth = cosd(axis_azimuth) sind_axis_azimuth = sind(axis_azimuth) - sind_axis_tilt = sind(axis_tilt) + sind_axis_slope = sind(axis_slope) # Sun's x, y, z coords sx = sind_solar_zenith * sind(solar_azimuth) @@ -337,9 +346,9 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, # 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) + sx * sind_axis_azimuth * sind_axis_slope + + sy * sind_axis_slope * cosd_axis_azimuth + + sz * cosd(axis_slope) ) # Eq. (5); angle between sun's beam and surface theta_T = np.degrees(np.arctan2(sx_prime, sz_prime)) @@ -348,9 +357,13 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, @renamed_kwarg_warning( since="0.13.1", - old_param_name="cross_axis_slope", - new_param_name="cross_axis_tilt", - removal="0.15.0", + old_param_name="axis_tilt", + new_param_name="axis_slope", +) +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="cross_axis_tilt", + new_param_name="cross_axis_slope", ) def shaded_fraction1d( solar_zenith, @@ -360,9 +373,9 @@ def shaded_fraction1d( *, collector_width, pitch, - axis_tilt=0, + axis_slope=0, surface_to_axis_offset=0, - cross_axis_tilt=0, + cross_axis_slope=0, shading_row_rotation=None, ): r""" @@ -400,15 +413,23 @@ def shaded_fraction1d( is the ratio of the shadow over this value. pitch : numeric Axis-to-axis horizontal spacing of the row. - axis_tilt : numeric, default 0 + axis_slope : numeric, default 0 Tilt of the rows axis from horizontal. In degrees :math:`^{\circ}`. + + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope`` + surface_to_axis_offset : numeric, default 0 Distance between the rotating axis and the collector surface. May be used to account for a torque tube offset. - cross_axis_tilt : numeric, default 0 + cross_axis_slope : numeric, default 0 Angle of the plane containing the rows' axes relative to horizontal. Right-handed rotation with respect to the rows axes. - See :term:`cross_axis_tilt`. In degrees :math:`^{\circ}`. + See :term:`cross_axis_slope`. In degrees :math:`^{\circ}`. + + .. versionchanged:: 0.13.1 + Renamed from ``cross_axis_tilt`` to ``cross_axis_slope`` + shading_row_rotation : numeric, optional Right-handed rotation of the row casting the shadow, with respect to the row axis. In degrees :math:`^{\circ}`. @@ -438,7 +459,7 @@ def shaded_fraction1d( +------------------+----------------------------+ | | :math:`\theta_2` | ``shaded_row_rotation`` | Degrees | +------------------+----------------------------+ :math:`^{\circ}` | - | :math:`\beta_c` | ``cross_axis_tilt`` | | + | :math:`\beta_c` | ``cross_axis_slope`` | | +------------------+----------------------------+---------------------+ | :math:`p` | ``pitch`` | Any consistent | +------------------+----------------------------+ length unit across | @@ -459,8 +480,8 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=135, ... axis_azimuth=90, shaded_row_rotation=30, shading_row_rotation=30, - ... collector_width=2, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.05, cross_axis_tilt=0) + ... collector_width=2, pitch=3, axis_slope=0, + ... surface_to_axis_offset=0.05, cross_axis_slope=0) 0.47755694708090535 **Fixed-tilt north-facing array on sloped terrain** @@ -473,8 +494,8 @@ def shaded_fraction1d( >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=75.5, ... axis_azimuth=270, shaded_row_rotation=50, shading_row_rotation=30, - ... collector_width=2.5, pitch=4, axis_tilt=10, - ... surface_to_axis_offset=0.05, cross_axis_tilt=0) + ... collector_width=2.5, pitch=4, axis_slope=10, + ... surface_to_axis_offset=0.05, cross_axis_slope=0) 0.793244836197256 **N-S single-axis tracker on sloped terrain** @@ -485,8 +506,8 @@ def shaded_fraction1d( tracker is higher than the west-most tracker). >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=90, axis_azimuth=180, - ... shaded_row_rotation=-30, collector_width=1.4, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.10, cross_axis_tilt=7) + ... shaded_row_rotation=-30, collector_width=1.4, pitch=3, + ... axis_slope=0, surface_to_axis_offset=0.10, cross_axis_slope=7) 0.8242176864434579 Note the previous example only is valid for the shaded fraction of the @@ -500,8 +521,8 @@ def shaded_fraction1d( in the afternoon. >>> shaded_fraction1d(solar_zenith=80, solar_azimuth=270, axis_azimuth=180, - ... shaded_row_rotation=30, collector_width=1.4, pitch=3, axis_tilt=0, - ... surface_to_axis_offset=0.10, cross_axis_tilt=7) + ... shaded_row_rotation=30, collector_width=1.4, pitch=3, axis_slope=0, + ... surface_to_axis_offset=0.10, cross_axis_slope=7) 0.018002567182254348 You must switch the input/output depending on the @@ -529,14 +550,14 @@ def shaded_fraction1d( projected_solar_zenith = projected_solar_zenith_angle( solar_zenith, solar_azimuth, - axis_tilt, + axis_slope, axis_azimuth, ) # calculate repeated elements thetas_1_S_diff = shading_row_rotation - projected_solar_zenith thetas_2_S_diff = shaded_row_rotation - projected_solar_zenith - thetaS_rotation_diff = projected_solar_zenith - cross_axis_tilt + thetaS_rotation_diff = projected_solar_zenith - cross_axis_slope cos_theta_2_S_diff_abs = np.abs(cosd(thetas_2_S_diff)) @@ -556,7 +577,7 @@ def shaded_fraction1d( / collector_width * cosd(thetaS_rotation_diff) / cos_theta_2_S_diff_abs - / cosd(cross_axis_tilt) + / cosd(cross_axis_slope) ) ) @@ -668,7 +689,7 @@ def direct_martinez( >>> solar_zenith=80, solar_azimuth=180, >>> axis_azimuth=90, shaded_row_rotation=25, >>> collector_width=0.5, pitch=1, surface_to_axis_offset=0, - >>> cross_axis_tilt=5.711, shading_row_rotation=50) + >>> cross_axis_slope=5.711, shading_row_rotation=50) >>> # calculation of the number of shaded blocks >>> shaded_blocks = np.ceil(total_blocks*shaded_fraction) >>> # apply the Martinez power losses to the calculated shading diff --git a/pvlib/tracking.py b/pvlib/tracking.py index a30cc27822..5f261fc3eb 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -4,16 +4,26 @@ from pvlib.tools import cosd, sind, tand, acosd, asind from pvlib import irradiance from pvlib import shading -from pvlib._deprecation import renamed_kwarg_warning +from pvlib._deprecation import renamed_kwarg_warning, deprecated +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="axis_tilt", + new_param_name="axis_slope", +) +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="cross_axis_tilt", + new_param_name="cross_axis_slope", +) @renamed_kwarg_warning( since='0.13.1', old_param_name='apparent_azimuth', new_param_name='solar_azimuth') def singleaxis(apparent_zenith, solar_azimuth, - axis_tilt=0, axis_azimuth=0, max_angle=90, - backtrack=True, gcr=2.0/7.0, cross_axis_tilt=0): + axis_slope=0, axis_azimuth=0, max_angle=90, + backtrack=True, gcr=2.0/7.0, cross_axis_slope=0): r""" Determine the rotation angle of a single-axis tracker when given particular solar zenith and azimuth angles. @@ -28,7 +38,7 @@ def singleaxis(apparent_zenith, solar_azimuth, oriented skyward. Rotation angle ``tracker_theta`` is a right-handed rotation around the y-axis in the x, y, z coordinate system and indicates tracker position relative to horizontal. For example, if tracker - ``axis_azimuth`` is 180 (oriented south) and ``axis_tilt`` is zero, then a + ``axis_azimuth`` is 180 (oriented south) and ``axis_slope`` is zero, then a ``tracker_theta`` of zero is horizontal, a ``tracker_theta`` of 30 degrees is a rotation of 30 degrees towards the west, and a ``tracker_theta`` of -90 degrees is a rotation to the vertical plane facing east. @@ -41,10 +51,13 @@ def singleaxis(apparent_zenith, solar_azimuth, solar_azimuth : float, 1d array, or Series Solar apparent azimuth angles in decimal degrees. - axis_tilt : float, default 0 + axis_slope : float, default 0 The tilt of the axis of rotation (i.e, the y-axis defined by ``axis_azimuth``) with respect to horizontal. - ``axis_tilt`` must be >= 0 and <= 90. [degrees] + ``axis_slope`` must be >= 0 and <= 90. [degrees] + + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 A value denoting the compass direction along which the axis of @@ -53,7 +66,7 @@ def singleaxis(apparent_zenith, solar_azimuth, max_angle : float or tuple, default 90 A value denoting the maximum rotation angle, in decimal degrees, of the one-axis tracker from its horizontal position (horizontal - if axis_tilt = 0). If a float is provided, it represents the maximum + if axis_slope = 0). If a float is provided, it represents the maximum rotation angle, and the minimum rotation angle is assumed to be the opposite of the maximum angle. If a tuple of (min_angle, max_angle) is provided, it represents both the minimum and maximum rotation angles. @@ -79,10 +92,13 @@ def singleaxis(apparent_zenith, solar_azimuth, has a ``gcr`` of 2/6=0.333. If ``gcr`` is not provided, a ``gcr`` of 2/7 is default. ``gcr`` must be <=1. - cross_axis_tilt : float, default 0.0 + cross_axis_slope : float, default 0.0 Angle of the plane containing the rows' axes relative to horizontal. Right-handed rotation with respect to the rows axes. - See :term:`cross_axis_tilt`. In degrees :math:`^{\circ}`. + See :term:`cross_axis_slope`. In degrees :math:`^{\circ}`. + + .. versionchanged:: 0.13.1 + Renamed from ``cross_axis_tilt`` to ``cross_axis_slope``. Returns ------- @@ -100,8 +116,8 @@ def singleaxis(apparent_zenith, solar_azimuth, See also -------- - pvlib.tracking.calc_axis_tilt - pvlib.tracking.calc_cross_axis_tilt + pvlib.tracking.calc_axis_slope + pvlib.tracking.calc_cross_axis_slope pvlib.tracking.calc_surface_orientation References @@ -138,7 +154,7 @@ def singleaxis(apparent_zenith, solar_azimuth, # rotation to the west is positive. This is a right-handed rotation # around the tracker y-axis. omega_ideal = shading.projected_solar_zenith_angle( - axis_tilt=axis_tilt, + axis_slope=axis_slope, axis_azimuth=axis_azimuth, solar_zenith=apparent_zenith, solar_azimuth=solar_azimuth, @@ -152,10 +168,10 @@ def singleaxis(apparent_zenith, solar_azimuth, if backtrack: # distance between rows in terms of rack lengths relative to cross-axis # tilt - axes_distance = 1/(gcr * cosd(cross_axis_tilt)) + axes_distance = 1/(gcr * cosd(cross_axis_slope)) # NOTE: account for rare angles below array, see GH 824 - temp = np.abs(axes_distance * cosd(omega_ideal - cross_axis_tilt)) + temp = np.abs(axes_distance * cosd(omega_ideal - cross_axis_slope)) # backtrack angle using [1], Eq. 14 with np.errstate(invalid='ignore'): @@ -186,7 +202,7 @@ def singleaxis(apparent_zenith, solar_azimuth, tracker_theta = np.clip(tracker_theta, min_angle, max_angle) # Calculate auxiliary angles - surface = calc_surface_orientation(tracker_theta, axis_tilt, axis_azimuth) + surface = calc_surface_orientation(tracker_theta, axis_slope, axis_azimuth) surface_tilt = surface['surface_tilt'] surface_azimuth = surface['surface_azimuth'] aoi = irradiance.aoi(surface_tilt, surface_azimuth, @@ -204,7 +220,12 @@ def singleaxis(apparent_zenith, solar_azimuth, return out -def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0): +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="axis_tilt", + new_param_name="axis_slope", +) +def calc_surface_orientation(tracker_theta, axis_slope=0, axis_azimuth=0): """ Calculate the surface tilt and azimuth angles for a given tracker rotation. @@ -212,13 +233,17 @@ def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0): ---------- tracker_theta : numeric Tracker rotation angle as a right-handed rotation around - the axis defined by ``axis_tilt`` and ``axis_azimuth``. For example, - with ``axis_tilt=0`` and ``axis_azimuth=180``, ``tracker_theta > 0`` + the axis defined by ``axis_slope`` and ``axis_azimuth``. For example, + with ``axis_slope=0`` and ``axis_azimuth=180``, ``tracker_theta > 0`` results in ``surface_azimuth`` to the West while ``tracker_theta < 0`` results in ``surface_azimuth`` to the East. [degree] - axis_tilt : float, default 0 + axis_slope : float, default 0 The tilt of the axis of rotation with respect to horizontal. - ``axis_tilt`` must be >= 0 and <= 90. [degree] + ``axis_slope`` must be >= 0 and <= 90. [degree] + + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope``. + axis_azimuth : float, default 0 A value denoting the compass direction along which the axis of rotation lies. Measured east of north. [degree] @@ -237,7 +262,7 @@ def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0): July 2013. :doi:`10.2172/1089596` """ with np.errstate(invalid='ignore', divide='ignore'): - surface_tilt = acosd(cosd(tracker_theta) * cosd(axis_tilt)) + surface_tilt = acosd(cosd(tracker_theta) * cosd(axis_slope)) # clip(..., -1, +1) to prevent arcsin(1 + epsilon) issues: azimuth_delta = asind(np.clip(sind(tracker_theta) / sind(surface_tilt), @@ -259,13 +284,16 @@ def calc_surface_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0): return out -def calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth): +def calc_axis_slope(slope_azimuth, slope_tilt, axis_azimuth): """ Calculate tracker axis tilt in the global reference frame when on a sloped plane. Axis tilt is the inclination of the tracker rotation axis with respect to horizontal, ranging from 0 degrees (horizontal axis) to 90 degrees (vertical axis). + .. versionchanged:: 0.13.1 + Renamed function ``calc_axis_tilt`` to ``calc_axis_slope``. + Parameters ---------- slope_azimuth : float @@ -277,13 +305,13 @@ def calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth): Returns ------- - axis_tilt : float + axis_slope : float tilt of tracker [degrees] See also -------- pvlib.tracking.singleaxis - pvlib.tracking.calc_cross_axis_tilt + pvlib.tracking.calc_cross_axis_slope Notes ----- @@ -297,8 +325,8 @@ def calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth): """ delta_gamma = axis_azimuth - slope_azimuth # equations 18-19 - tan_axis_tilt = cosd(delta_gamma) * tand(slope_tilt) - return np.degrees(np.arctan(tan_axis_tilt)) + tan_axis_slope = cosd(delta_gamma) * tand(slope_tilt) + return np.degrees(np.arctan(tan_axis_slope)) def _calc_tracker_norm(ba, bg, dg): @@ -354,8 +382,13 @@ def _calc_beta_c(v, dg, ba): return beta_c -def calc_cross_axis_tilt( - slope_azimuth, slope_tilt, axis_azimuth, axis_tilt): +@renamed_kwarg_warning( + since="0.13.1", + old_param_name="axis_tilt", + new_param_name="axis_slope", +) +def calc_cross_axis_slope( + slope_azimuth, slope_tilt, axis_azimuth, axis_slope): """ Calculate the angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane @@ -368,6 +401,9 @@ def calc_cross_axis_tilt( if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes down to the west. + .. versionchanged:: 0.13.1 + Renamed function ``calc_cross_axis_tilt`` to ``calc_cross_axis_slope``. + Parameters ---------- slope_azimuth : float @@ -378,13 +414,16 @@ def calc_cross_axis_tilt( [degrees] axis_azimuth : float direction of tracker axes projected on the horizontal [degrees] - axis_tilt : float - tilt of trackers relative to horizontal. ``axis_tilt`` must be >= 0 + axis_slope : float + tilt of trackers relative to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degree] + .. versionchanged:: 0.13.1 + Renamed from ``axis_tilt`` to ``axis_slope``. + Returns ------- - cross_axis_tilt : float + cross_axis_slope : float angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes [degrees] @@ -392,7 +431,7 @@ def calc_cross_axis_tilt( See also -------- pvlib.tracking.singleaxis - pvlib.tracking.calc_axis_tilt + pvlib.tracking.calc_axis_slope Notes ----- @@ -407,7 +446,21 @@ def calc_cross_axis_tilt( # delta-gamma, difference between axis and slope azimuths delta_gamma = axis_azimuth - slope_azimuth # equation 22 - v = _calc_tracker_norm(axis_tilt, slope_tilt, delta_gamma) + v = _calc_tracker_norm(axis_slope, slope_tilt, delta_gamma) # equation 26 - beta_c = _calc_beta_c(v, delta_gamma, axis_tilt) + beta_c = _calc_beta_c(v, delta_gamma, axis_slope) return np.degrees(beta_c) + + +# allow deprecated names of calc_cross_axis_slope and calc_axis_slope +calc_axis_tilt = deprecated( + since="0.13.1", + name="calc_axis_tilt", # else it uses calc_axis_slope by introspection + alternative="pvlib.tracking.calc_axis_slope" +)(calc_axis_slope) + +calc_cross_axis_tilt = deprecated( + since="0.13.1", + name="calc_cross_axis_tilt", # else it uses calc_cross_axis_slope + alternative="pvlib.tracking.calc_cross_axis_slope" +)(calc_cross_axis_slope) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index b58f9fd9e4..364bef1dee 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -6,6 +6,7 @@ import pandas as pd import pytest +from pvlib._deprecation import pvlibDeprecationWarning from .conftest import assert_series_equal, assert_frame_equal from numpy.testing import assert_allclose import unittest.mock as mock @@ -2368,9 +2369,9 @@ def fixed_mount(): @pytest.fixture def single_axis_tracker_mount(): - return pvsystem.SingleAxisTrackerMount(axis_tilt=10, axis_azimuth=170, + return pvsystem.SingleAxisTrackerMount(axis_slope=10, axis_azimuth=170, max_angle=45, backtrack=False, - gcr=0.4, cross_axis_tilt=-5) + gcr=0.4, cross_axis_slope=-5) def test_FixedMount_constructor(fixed_mount): @@ -2384,8 +2385,8 @@ def test_FixedMount_get_orientation(fixed_mount): def test_SingleAxisTrackerMount_constructor(single_axis_tracker_mount): - expected = dict(axis_tilt=10, axis_azimuth=170, max_angle=45, - backtrack=False, gcr=0.4, cross_axis_tilt=-5) + expected = dict(axis_slope=10, axis_azimuth=170, max_angle=45, + backtrack=False, gcr=0.4, cross_axis_slope=-5) for attr_name, expected_value in expected.items(): assert getattr(single_axis_tracker_mount, attr_name) == expected_value @@ -2519,3 +2520,36 @@ def test_Array_temperature_missing_parameters(model, keys): array.temperature_model_parameters = params with pytest.raises(KeyError, match=match): array.get_cell_temperature(irrads, temps, winds, model) + + +def test_SingleAxisTrackerMount_renamed_attributes_tilt_to_slope( + single_axis_tracker_mount, +): # renamed attributes tested here are + # axis_tilt -> axis_slope and cross_axis_tilt -> cross_axis_slope + + # test constructor + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + pvsystem.SingleAxisTrackerMount(axis_tilt=10, axis_azimuth=170, + max_angle=45, backtrack=False, + gcr=0.4, cross_axis_slope=-5) + with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): + pvsystem.SingleAxisTrackerMount(axis_slope=10, axis_azimuth=170, + max_angle=45, backtrack=False, + gcr=0.4, cross_axis_tilt=-5) + with (pytest.warns(pvlibDeprecationWarning, match="axis_slope"), + pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope")): + pvsystem.SingleAxisTrackerMount(axis_tilt=10, axis_azimuth=170, + max_angle=45, backtrack=False, + gcr=0.4, cross_axis_tilt=-5) + + # read values + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + single_axis_tracker_mount.axis_tilt + with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): + single_axis_tracker_mount.cross_axis_tilt + + # set values + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + single_axis_tracker_mount.axis_tilt = 25.132 + with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): + single_axis_tracker_mount.cross_axis_tilt = 15.657 diff --git a/tests/test_shading.py b/tests/test_shading.py index a19917774b..de9e10179a 100644 --- a/tests/test_shading.py +++ b/tests/test_shading.py @@ -9,7 +9,6 @@ from pvlib import shading from pvlib.tools import atand -from .conftest import fail_on_pvlib_version from pvlib._deprecation import pvlibDeprecationWarning @@ -118,7 +117,7 @@ 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_slope_angle = 9.666 # deg axis_azimuth_angle = 195.0 # deg timedata = pd.DataFrame( columns=("Apparent Elevation", "Solar Azimuth", "True-Tracking"), @@ -139,7 +138,7 @@ def true_tracking_angle_and_inputs_NREL(): "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) + return (axis_slope_angle, axis_azimuth_angle, timedata) @pytest.fixture @@ -161,7 +160,7 @@ def projected_solar_zenith_angle_edge_cases(): [ 45, 45, 90, 180, -135], [ 45, 315, 90, 180, 135], ], - columns=["solar_zenith", "solar_azimuth", "axis_tilt", "axis_azimuth", + columns=["solar_zenith", "solar_azimuth", "axis_slope", "axis_azimuth", "psza"], ) return premises_and_result_matrix @@ -172,12 +171,12 @@ def test_projected_solar_zenith_angle_numeric( projected_solar_zenith_angle_edge_cases ): psza_func = shading.projected_solar_zenith_angle - axis_tilt, axis_azimuth, timedata = true_tracking_angle_and_inputs_NREL + axis_slope, 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_slope, axis_azimuth, ) assert_allclose(psz, timedata["True-Tracking"], atol=1e-3) @@ -185,19 +184,19 @@ def test_projected_solar_zenith_angle_numeric( psza = psza_func( timedata["Apparent Zenith"], timedata["Solar Azimuth"], - -axis_tilt, + -axis_slope, 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 = ( + solar_zenith, solar_azimuth, axis_slope, 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_slope, axis_azimuth, ) assert_allclose(psza, psza_expected, atol=1e-9) @@ -215,17 +214,17 @@ def test_projected_solar_zenith_angle_datatypes( 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_NREL + axis_slope, 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 = ( + axis_slope, axis_azimuth, sun_apparent_zenith, sun_azimuth = ( cast_func(sun_apparent_zenith), cast_func(sun_azimuth), - cast_func(axis_tilt), + cast_func(axis_slope), cast_func(axis_azimuth), ) - psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_tilt, axis_azimuth) + psz = psz_func(sun_apparent_zenith, axis_azimuth, axis_slope, axis_azimuth) assert isinstance(psz, cast_type) @@ -261,7 +260,7 @@ def sf1d_premises_and_expected(): ), ) # fmt: skip - test_data["cross_axis_tilt"] = atand( + test_data["cross_axis_slope"] = atand( (test_data["z_R"] - test_data["z_L"]) / (test_data["x_L"] - test_data["x_R"]) ) @@ -317,7 +316,7 @@ def test_shaded_fraction1d_unprovided_shading_row_rotation(): test_data = pd.DataFrame( columns=[ "shaded_row_rotation", "surface_to_axis_offset", "collector_width", - "solar_zenith", "cross_axis_tilt", "pitch", "solar_azimuth", + "solar_zenith", "cross_axis_slope", "pitch", "solar_azimuth", "axis_azimuth", "expected_sf", ], data=[ @@ -332,8 +331,7 @@ def test_shaded_fraction1d_unprovided_shading_row_rotation(): assert_allclose(sf, expected_sf, atol=1e-2) -@fail_on_pvlib_version("0.15.0") -def test_shaded_fraction1d_renamed_cross_axis_slope2cross_axis_tilt(): +def test_shaded_fraction1d_renamed_cross_axis_tilt2cross_axis_slope(): # Tests shaded_fraction1d with cross_axis_slope instead of cross_axis_tilt with pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope"): shading.shaded_fraction1d( @@ -344,7 +342,7 @@ def test_shaded_fraction1d_renamed_cross_axis_slope2cross_axis_tilt(): collector_width=3, pitch=7, surface_to_axis_offset=0, - cross_axis_slope=0, + cross_axis_tilt=0, ) @@ -408,3 +406,19 @@ def test_direct_martinez(direct_martinez_Table2): test_data, power_losses_expected = direct_martinez_Table2 power_losses = shading.direct_martinez(**test_data) assert_allclose(power_losses, power_losses_expected, atol=5e-3) + + +def test_projected_solar_zenith_angle_renamed_axis_tilt_to_axis_slope(): + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + shading.projected_solar_zenith_angle( + 10, 180, axis_tilt=20, axis_azimuth=30 + ) + + +def test_shaded_fraction1d_renamed_tilt_to_slope(): + with (pytest.warns(pvlibDeprecationWarning, match="axis_slope"), + pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope")): + shading.shaded_fraction1d( + 10, 10, 10, 10, collector_width=2, pitch=0.3, + axis_tilt=10, cross_axis_tilt=10 + ) diff --git a/tests/test_tracking.py b/tests/test_tracking.py index 8f3cb5824d..a0118870ae 100644 --- a/tests/test_tracking.py +++ b/tests/test_tracking.py @@ -7,6 +7,7 @@ import pvlib from pvlib import tracking +from pvlib._deprecation import pvlibDeprecationWarning from .conftest import TESTS_DATA_DIR, assert_frame_equal, assert_series_equal SINGLEAXIS_COL_ORDER = ['tracker_theta', 'aoi', @@ -18,7 +19,7 @@ def test_solar_noon(): apparent_zenith = pd.Series([10], index=index) apparent_azimuth = pd.Series([180], index=index) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -34,7 +35,7 @@ def test_scalars(): apparent_zenith = 10 apparent_azimuth = 180 tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) assert isinstance(tracker_data, dict) @@ -48,7 +49,7 @@ def test_arrays(): apparent_zenith = np.array([10]) apparent_azimuth = np.array([180]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) assert isinstance(tracker_data, dict) @@ -63,7 +64,7 @@ def test_nans(): apparent_azimuth = np.array([180, 180, np.nan]) with np.errstate(invalid='ignore'): tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) expect = {'tracker_theta': np.array([0, nan, nan]), @@ -78,7 +79,7 @@ def test_nans(): apparent_azimuth = pd.Series(apparent_azimuth) with np.errstate(invalid='ignore'): tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) expect = pd.DataFrame(np.array( @@ -95,7 +96,7 @@ def test_arrays_multi(): # singleaxis should fail for num dim > 1 with pytest.raises(ValueError): tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) # uncomment if we ever get singleaxis to support num dim > 1 arrays @@ -113,7 +114,7 @@ def test_azimuth_north_south(): apparent_azimuth = pd.Series([90]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=180, + axis_slope=0, axis_azimuth=180, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -125,7 +126,7 @@ def test_azimuth_north_south(): assert_frame_equal(expect, tracker_data) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -138,7 +139,7 @@ def test_max_angle(): apparent_zenith = pd.Series([60]) apparent_azimuth = pd.Series([90]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=45, backtrack=True, gcr=2.0/7.0) @@ -154,7 +155,7 @@ def test_min_angle(): apparent_zenith = pd.Series([60]) apparent_azimuth = pd.Series([270]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=(-45, 50), backtrack=True, gcr=2.0/7.0) @@ -171,7 +172,7 @@ def test_backtrack(): apparent_azimuth = pd.Series([90]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=False, gcr=2.0/7.0) @@ -183,7 +184,7 @@ def test_backtrack(): assert_frame_equal(expect, tracker_data) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=0, + axis_slope=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -195,12 +196,12 @@ def test_backtrack(): assert_frame_equal(expect, tracker_data) -def test_axis_tilt(): +def test_axis_slope(): apparent_zenith = pd.Series([30]) apparent_azimuth = pd.Series([135]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=30, axis_azimuth=180, + axis_slope=30, axis_azimuth=180, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -213,7 +214,7 @@ def test_axis_tilt(): assert_frame_equal(expect, tracker_data) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=30, axis_azimuth=0, + axis_slope=30, axis_azimuth=0, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -230,7 +231,7 @@ def test_axis_azimuth(): apparent_azimuth = pd.Series([90]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=90, + axis_slope=0, axis_azimuth=90, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -245,7 +246,7 @@ def test_axis_azimuth(): apparent_azimuth = pd.Series([180]) tracker_data = tracking.singleaxis(apparent_zenith, apparent_azimuth, - axis_tilt=0, axis_azimuth=90, + axis_slope=0, axis_azimuth=90, max_angle=90, backtrack=True, gcr=2.0/7.0) @@ -265,7 +266,7 @@ def test_horizon_flat(): solar_zenith = pd.Series(solar_zenith) # depending on platform and numpy versions this will generate # RuntimeWarning: invalid value encountered in > < >= - out = tracking.singleaxis(solar_zenith, solar_azimuth, axis_tilt=0, + out = tracking.singleaxis(solar_zenith, solar_azimuth, axis_slope=0, axis_azimuth=180, backtrack=False, max_angle=180) expected = pd.DataFrame(np.array( [[ nan, nan, nan, nan], @@ -281,7 +282,7 @@ def test_horizon_tilted(): solar_zenith = np.full_like(solar_azimuth, 45) solar_azimuth = pd.Series(solar_azimuth) solar_zenith = pd.Series(solar_zenith) - out = tracking.singleaxis(solar_zenith, solar_azimuth, axis_tilt=90, + out = tracking.singleaxis(solar_zenith, solar_azimuth, axis_slope=90, axis_azimuth=180, backtrack=False, max_angle=180) expected = pd.DataFrame(np.array( [[-180., 45., 0., 90.], @@ -294,7 +295,7 @@ def test_horizon_tilted(): def test_low_sun_angles(): # GH 656, 824 result = tracking.singleaxis( - apparent_zenith=80, apparent_azimuth=338, axis_tilt=30, + apparent_zenith=80, apparent_azimuth=338, axis_slope=30, axis_azimuth=180, max_angle=60, backtrack=True, gcr=0.35) expected = { 'tracker_theta': np.array([60.0]), @@ -305,9 +306,9 @@ def test_low_sun_angles(): assert_allclose(expected[k], v) -def test_calc_axis_tilt(): +def test_calc_axis_slope(): # expected values - expected_axis_tilt = 2.239 # [degrees] + expected_axis_slope = 2.239 # [degrees] expected_side_slope = 9.86649274360294 # [degrees] expected = TESTS_DATA_DIR / 'singleaxis_tracker_wslope.csv' expected = pd.read_csv(expected, index_col='timestamp', parse_dates=True) @@ -324,16 +325,16 @@ def test_calc_axis_tilt(): # Note: GCR is relative to horizontal distance between rows gcr = 0.33292759 # GCR = length / horizontal_pitch = 1.64 / 5 / cos(9.86) # calculate tracker axis zenith - axis_tilt = tracking.calc_axis_tilt( + axis_slope = tracking.calc_axis_slope( slope_azimuth, slope_tilt, axis_azimuth=axis_azimuth) - assert np.isclose(axis_tilt, expected_axis_tilt) + assert np.isclose(axis_slope, expected_axis_slope) # calculate cross-axis tilt and relative rotation - cross_axis_tilt = tracking.calc_cross_axis_tilt( - slope_azimuth, slope_tilt, axis_azimuth, axis_tilt) - assert np.isclose(cross_axis_tilt, expected_side_slope) + cross_axis_slope = tracking.calc_cross_axis_slope( + slope_azimuth, slope_tilt, axis_azimuth, axis_slope) + assert np.isclose(cross_axis_slope, expected_side_slope) sat = tracking.singleaxis( - solpos.apparent_zenith, solpos.azimuth, axis_tilt, axis_azimuth, - max_angle, backtrack=True, gcr=gcr, cross_axis_tilt=cross_axis_tilt) + solpos.apparent_zenith, solpos.azimuth, axis_slope, axis_azimuth, + max_angle, backtrack=True, gcr=gcr, cross_axis_slope=cross_axis_slope) np.testing.assert_allclose( sat['tracker_theta'], expected['tracker_theta'], atol=1e-7) np.testing.assert_allclose(sat['aoi'], expected['aoi'], atol=1e-7) @@ -362,28 +363,28 @@ def test_slope_aware_backtracking(): ( 0.524817, 239.330401, 79.530, 5.490), ], columns=['ApparentElevation', 'SolarAzimuth', 'TrueTracking', 'Backtracking']) - expected_axis_tilt = 9.666 + expected_axis_slope = 9.666 expected_slope_angle = -2.576 slope_azimuth, slope_tilt = 180.0, 10.0 axis_azimuth = 195.0 - axis_tilt = tracking.calc_axis_tilt( + axis_slope = tracking.calc_axis_slope( slope_azimuth, slope_tilt, axis_azimuth) - assert np.isclose(axis_tilt, expected_axis_tilt, rtol=1e-3, atol=1e-3) - cross_axis_tilt = tracking.calc_cross_axis_tilt( - slope_azimuth, slope_tilt, axis_azimuth, axis_tilt) + assert np.isclose(axis_slope, expected_axis_slope, rtol=1e-3, atol=1e-3) + cross_axis_slope = tracking.calc_cross_axis_slope( + slope_azimuth, slope_tilt, axis_azimuth, axis_slope) assert np.isclose( - cross_axis_tilt, expected_slope_angle, rtol=1e-3, atol=1e-3) + cross_axis_slope, expected_slope_angle, rtol=1e-3, atol=1e-3) sat = tracking.singleaxis( 90.0-expected_data['ApparentElevation'], expected_data['SolarAzimuth'], - axis_tilt, axis_azimuth, max_angle=90.0, backtrack=True, gcr=0.5, - cross_axis_tilt=cross_axis_tilt) + axis_slope, axis_azimuth, max_angle=90.0, backtrack=True, gcr=0.5, + cross_axis_slope=cross_axis_slope) assert_series_equal(sat['tracker_theta'], expected_data['Backtracking'].rename('tracker_theta'), check_less_precise=True) truetracking = tracking.singleaxis( 90.0-expected_data['ApparentElevation'], expected_data['SolarAzimuth'], - axis_tilt, axis_azimuth, max_angle=90.0, backtrack=False, gcr=0.5, - cross_axis_tilt=cross_axis_tilt) + axis_slope, axis_azimuth, max_angle=90.0, backtrack=False, gcr=0.5, + cross_axis_slope=cross_axis_slope) assert_series_equal(truetracking['tracker_theta'], expected_data['TrueTracking'].rename('tracker_theta'), check_less_precise=True) @@ -397,7 +398,7 @@ def test_singleaxis_aoi_gh1221(): tz='Etc/GMT+6') sp = loc.get_solarposition(dr) tr = pvlib.tracking.singleaxis( - sp['apparent_zenith'], sp['azimuth'], axis_tilt=90, axis_azimuth=180, + sp['apparent_zenith'], sp['azimuth'], axis_slope=90, axis_azimuth=180, max_angle=0.001, backtrack=False) fixed = pvlib.irradiance.aoi(90, 180, sp['apparent_zenith'], sp['azimuth']) fixed[np.isnan(tr['aoi'])] = np.nan @@ -435,7 +436,7 @@ def test_calc_surface_orientation_kwargs(): expected_tilts = np.array([22.2687445, 20.0, 22.2687445]) expected_azimuths = np.array([152.72683041, 180.0, 207.27316959]) out = tracking.calc_surface_orientation(rotations, - axis_tilt=20, + axis_slope=20, axis_azimuth=180) np.testing.assert_allclose(out['surface_tilt'], expected_tilts) np.testing.assert_allclose(out['surface_azimuth'], expected_azimuths) @@ -450,11 +451,11 @@ def test_calc_surface_orientation_special(): np.testing.assert_allclose(out['surface_tilt'], expected_tilts) np.testing.assert_allclose(out['surface_azimuth'], expected_azimuths) - # special case for axis_tilt + # special case for axis_slope rotations = np.array([-10, 0, 10]) expected_tilts = np.array([90, 90, 90], dtype=float) expected_azimuths = np.array([350, 0, 10], dtype=float) - out = tracking.calc_surface_orientation(rotations, axis_tilt=90) + out = tracking.calc_surface_orientation(rotations, axis_slope=90) np.testing.assert_allclose(out['surface_tilt'], expected_tilts) np.testing.assert_allclose(out['surface_azimuth'], expected_azimuths) @@ -471,3 +472,43 @@ 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) + + +def test_singleaxis_renamed_tilt_to_slope(): + with (pytest.warns(pvlibDeprecationWarning, match="axis_slope"), + pytest.warns(pvlibDeprecationWarning, match="cross_axis_slope")): + tracking.singleaxis( + 10, 10, axis_tilt=10, axis_azimuth=10, cross_axis_tilt=20 + ) + + +def test_calc_surface_orientation_tilt_to_slope(): + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + tracking.calc_surface_orientation(10, axis_tilt=10, axis_azimuth=10) + + +def test_deprecated_calc_axis_tilt(): + # renamed to calc_axis_slope + with pytest.warns(pvlibDeprecationWarning, match="calc_axis_slope"): + tracking.calc_axis_tilt(slope_azimuth=1, slope_tilt=1, axis_azimuth=1) + + +def test_deprecated_calc_cross_axis_tilt_and_param_tilt_to_slope(): + # 1. calc_cross_axis_tilt renamed to calc_cross_axis_slope + # 2. parameter axis_tilt renamed to axis_slope + + # just 1. + with pytest.warns(pvlibDeprecationWarning, match="calc_cross_axis_slope"): + tracking.calc_cross_axis_tilt(slope_azimuth=1, slope_tilt=1, + axis_azimuth=1, axis_slope=1) + # just 2. + with pytest.warns(pvlibDeprecationWarning, match="axis_slope"): + tracking.calc_cross_axis_slope(slope_azimuth=1, slope_tilt=1, + axis_azimuth=1, axis_tilt=1) + # both 1. and 2. + with ( + pytest.warns(pvlibDeprecationWarning, match="axis_slope"), + pytest.warns(pvlibDeprecationWarning, match="calc_cross_axis_slope"), + ): # somehow this order is important, the other way around fails + tracking.calc_cross_axis_tilt(slope_azimuth=1, slope_tilt=1, + axis_azimuth=1, axis_tilt=1) From 27b24e743aa047fa2b5a64f919771e5eff8bc0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:42:04 +0200 Subject: [PATCH 11/25] Typo in v0.13.1 whatnew --- docs/sphinx/source/whatsnew/v0.13.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index ff5089aa15..ad3f855ada 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -11,7 +11,7 @@ Breaking Changes Deprecations ~~~~~~~~~~~~ * Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`) -* Rename parameter name ``aparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. +* Rename parameter name ``apparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) * Rename ``axis_tilt`` and ``cross_axis_tilt`` terms to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: From 3625a5a5aa545922404c87d313237318437ef668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:44:46 +0200 Subject: [PATCH 12/25] flake8 may forgive me --- .../plot_single_axis_tracking_on_sloped_terrain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py b/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py index 7131513a71..64a03ce549 100644 --- a/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py +++ b/docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py @@ -145,7 +145,7 @@ # calculate the cross-axis tilt: cross_axis_slope = tracking.calc_cross_axis_slope(slope_azimuth, slope_tilt, - axis_azimuth, axis_slope) + axis_azimuth, axis_slope) print('Axis tilt:', '{:0.01f}°'.format(axis_slope)) print('Cross-axis tilt:', '{:0.01f}°'.format(cross_axis_slope)) From de940218bfb9c3a62b219e9d0c50ac16fa8fd49b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:55:24 +0200 Subject: [PATCH 13/25] whatsnew improvements --- docs/sphinx/source/whatsnew/v0.13.1.rst | 6 +++--- docs/sphinx/source/whatsnew/v0.8.0.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 535c01805d..52375aaff8 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -13,14 +13,14 @@ Deprecations * Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`) * Rename parameter name ``apparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) -* Rename ``axis_tilt`` and ``cross_axis_tilt`` terms to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: +* Rename ``axis_tilt`` and ``cross_axis_tilt`` to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: - :py:func:`pvlib.shading.shaded_fraction1d` parameters ``cross_axis_tilt`` and ``axis_tilt`` - :py:func:`pvlib.shading.projected_solar_zenith_angle` parameter ``axis_tilt`` - :py:func:`pvlib.tracking.singleaxis` parameters ``cross_axis_tilt`` and ``axis_tilt`` - :py:func:`pvlib.tracking.calc_surface_orientation` parameter ``axis_tilt`` - - :py:func:`!pvlib.tracking.calc_axis_tilt` function signature is renamed to :py:func:`pvlib.tracking.calc_axis_slope` - - :py:func:`!pvlib.tracking.calc_cross_axis_tilt` function signature is renamed to :py:func:`pvlib.tracking.calc_cross_axis_slope` and parameter ``axis_tilt`` + - :py:func:`!pvlib.tracking.calc_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_axis_slope` + - :py:func:`!pvlib.tracking.calc_cross_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_cross_axis_slope`, and its parameter ``axis_tilt`` - :py:class:`pvlib.pvsystem.SingleAxisTrackerMount` dataclass fields ``axis_tilt`` and ``cross_axis_tilt`` (:issue:`2334`, :pull:`2543`) diff --git a/docs/sphinx/source/whatsnew/v0.8.0.rst b/docs/sphinx/source/whatsnew/v0.8.0.rst index 04e2d16765..f3e9ae89be 100644 --- a/docs/sphinx/source/whatsnew/v0.8.0.rst +++ b/docs/sphinx/source/whatsnew/v0.8.0.rst @@ -110,14 +110,14 @@ Enhancements * Added ``racking_model``, ``module_type``, and ``temperature_model_parameters`` to :py:class:`~pvlib.pvsystem.PVSystem` and :py:class:`~pvlib.tracking.SingleAxisTracker` repr methods. (:issue:`1027`) -* Added :py:func:`!~pvlib.tracking.calc_axis_tilt` to calculate the - tracker axes tilt and :py:func:`!~pvlib.tracking.calc_cross_axis_tilt` to +* Added :py:func:`!pvlib.tracking.calc_axis_tilt` to calculate the + tracker axes tilt and :py:func:`!pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis tilt, which is the angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane perpendicular to the tracker axes. (:pull:`823`) * Added ``cross_axis_tilt`` argument to :py:func:`~pvlib.tracking.singleaxis` and :py:func:`~pvlib.tracking.SingleAxisTracker` which defaults to zero. Use - :py:func:`!~pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis + :py:func:`!pvlib.tracking.calc_cross_axis_tilt` to calculate the cross-axis tilt angle if necessary. (:pull:`823`) * Added ability for :py:func:`pvlib.soiling.hsu` to accept arbitrary time intervals. (:pull:`980`) * Added :py:func:`pvlib.temperature.fuentes` for cell temperature modeling. (:pull:`1037`) From 7e5223435a952bbf27dd1ed93011728b4837014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:20:32 +0200 Subject: [PATCH 14/25] move whatsnew entries --- docs/sphinx/source/whatsnew/v0.13.1.rst | 11 ----------- docs/sphinx/source/whatsnew/v0.13.2.rst | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 2e5d8fdd56..b056803b2b 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -10,17 +10,6 @@ Deprecations * Deprecate :py:func:`pvlib.modelchain.get_orientation`. (:pull:`2495`) * Rename parameter name ``apparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) -* Rename ``axis_tilt`` and ``cross_axis_tilt`` to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: - - - :py:func:`pvlib.shading.shaded_fraction1d` parameters ``cross_axis_tilt`` and ``axis_tilt`` - - :py:func:`pvlib.shading.projected_solar_zenith_angle` parameter ``axis_tilt`` - - :py:func:`pvlib.tracking.singleaxis` parameters ``cross_axis_tilt`` and ``axis_tilt`` - - :py:func:`pvlib.tracking.calc_surface_orientation` parameter ``axis_tilt`` - - :py:func:`!pvlib.tracking.calc_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_axis_slope` - - :py:func:`!pvlib.tracking.calc_cross_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_cross_axis_slope`, and its parameter ``axis_tilt`` - - :py:class:`pvlib.pvsystem.SingleAxisTrackerMount` dataclass fields ``axis_tilt`` and ``cross_axis_tilt`` - - (:issue:`2334`, :pull:`2543`) Bug fixes ~~~~~~~~~ diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index f649a93fd0..d4009ccd4e 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -6,6 +6,17 @@ v0.13.2 (Anticipated December, 2025) Breaking Changes ~~~~~~~~~~~~~~~~ +* Rename ``axis_tilt`` and ``cross_axis_tilt`` to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: + + - :py:func:`pvlib.shading.shaded_fraction1d` parameters ``cross_axis_tilt`` and ``axis_tilt`` + - :py:func:`pvlib.shading.projected_solar_zenith_angle` parameter ``axis_tilt`` + - :py:func:`pvlib.tracking.singleaxis` parameters ``cross_axis_tilt`` and ``axis_tilt`` + - :py:func:`pvlib.tracking.calc_surface_orientation` parameter ``axis_tilt`` + - :py:func:`!pvlib.tracking.calc_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_axis_slope` + - :py:func:`!pvlib.tracking.calc_cross_axis_tilt` function is renamed to :py:func:`~pvlib.tracking.calc_cross_axis_slope`, and its parameter ``axis_tilt`` + - :py:class:`pvlib.pvsystem.SingleAxisTrackerMount` dataclass fields ``axis_tilt`` and ``cross_axis_tilt`` + + (:issue:`2334`, :pull:`2543`) Deprecations From d8467bc43d376d5a096ae2b47b38d828ca64060c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:22:01 +0200 Subject: [PATCH 15/25] rm whatnew duplicated from merge entry --- docs/sphinx/source/whatsnew/v0.13.1.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index b056803b2b..00e117b86b 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -6,7 +6,6 @@ v0.13.1 (September 24, 2025) Deprecations ~~~~~~~~~~~~ -* Deprecate :py:func:`~pvlib.modelchain.get_orientation`. (:pull:`2495`) * Deprecate :py:func:`pvlib.modelchain.get_orientation`. (:pull:`2495`) * Rename parameter name ``apparent_azimuth`` to ``solar_azimuth`` in :py:func:`~pvlib.tracking.singleaxis`. (:issue:`2479`, :pull:`2480`) From b84adc68ee485de74910b901057ccd5fa7e0db2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:13:55 +0100 Subject: [PATCH 16/25] axis_azimuth description revision feat. cwhanse --- pvlib/tracking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 5f261fc3eb..96d384542d 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -60,8 +60,8 @@ def singleaxis(apparent_zenith, solar_azimuth, Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 - A value denoting the compass direction along which the axis of - rotation lies. Measured in decimal degrees east of north. + The compass direction along which the axis of rotation lies. + Measured in decimal degrees east of north. max_angle : float or tuple, default 90 A value denoting the maximum rotation angle, in decimal degrees, From 25410b3c7bf3c49868144ecc722895ca522e822a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:15:18 +0100 Subject: [PATCH 17/25] Move definition from pvsystem's SingleAxisTrackerMount to nomenclature --- .../_images/Anderson_Mikofski_2020_Fig4.png | Bin 0 -> 60348 bytes .../source/user_guide/extras/nomenclature.rst | 34 ++++++++++++------ pvlib/pvsystem.py | 13 ++----- pvlib/shading.py | 5 ++- pvlib/tracking.py | 5 ++- 5 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig4.png diff --git a/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig4.png b/docs/sphinx/source/_images/Anderson_Mikofski_2020_Fig4.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea287454c7cf4520348d8d9af092437a6acdec6 GIT binary patch literal 60348 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 literal 0 HcmV?d00001 diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index 1467b49bdd..8e94323f42 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -45,18 +45,26 @@ There is a convention on consistent variable names throughout the library: Beam/direct horizontal irradiance cross_axis_slope - Cross-axis tilt angle. [°] - Consider two parallel rows of modules at different height; - ``cross_axis_slope`` is the angle formed the line formed by the + Cross-axis slope angle. [°] + The angle, relative to horizontal, of the line formed by the intersection between the slope containing the tracker axes and a plane - perpendicular to the tracker axes, and the horizontal plane. - Cross-axis tilt is measured by using a right-handed convention. - For example, trackers with axis azimuth of 180° (heading south) - will have a negative cross-axis tilt if the tracker axes plane slopes - down to the east and positive cross-axis tilt if the tracker axes plane + perpendicular to the tracker axes. Cross-axis slope should be specified + using a right-handed convention. + + For example, trackers with axis azimuth of 180° (N-S rotation axis) + will have a negative cross-axis slope if the tracker axes plane slopes + down to the east and positive cross-axis slope if the tracker axes plane slopes up to the east. - Use :func:`~pvlib.tracking.calc_cross_axis_slope` to calculate - ``cross_axis_slope`` + + .. figure:: ../../_images/Anderson_Mikofski_2020_Fig4.png + :alt: Two rows of modules, tracker coordinate system and cross-axis slope of the second row w.r.t. the leftmost. + :align: center + :scale: 50 % + + Fig. 4, [Anderson2020]_: Cross-axis slope angle :math:`\beta_C` relative to the tracker coordinate system. + + Use :py::func:`pvlib.tracking.calc_cross_axis_slope` to calculate + ``cross_axis_slope``. dhi Diffuse horizontal irradiance @@ -229,3 +237,9 @@ units, refer to the following sources from `SoDa Service Date: Mon, 8 Dec 2025 23:16:22 +0100 Subject: [PATCH 18/25] add axis_slope definition, as per figure in cross_axis_slope --- docs/sphinx/source/user_guide/extras/nomenclature.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index 8e94323f42..bac5f45f33 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -41,6 +41,11 @@ There is a convention on consistent variable names throughout the library: Refraction-corrected solar elevation angle. This is the complement of :term:`apparent_zenith` (90 - apparent_zenith). [°] + axis_slope + Angle of a tracker axis with respect to horizontal. + This is, left-hand rotation angle of :math:`t_y` around :math:`t_x` in Fig. [4] of [Anderson2020]_. + See figure in :term:`cross_axis_slope`. + bhi Beam/direct horizontal irradiance @@ -66,6 +71,8 @@ There is a convention on consistent variable names throughout the library: Use :py::func:`pvlib.tracking.calc_cross_axis_slope` to calculate ``cross_axis_slope``. + See also :term:`axis_slope`. + dhi Diffuse horizontal irradiance From c964df40edc23c1471fc4b250b7a82fd6885abea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:16:56 +0100 Subject: [PATCH 19/25] bump deprecation version from 0.13.1 to 0.13.2 --- pvlib/pvsystem.py | 14 +++++++------- pvlib/shading.py | 12 ++++++------ pvlib/tracking.py | 24 ++++++++++++------------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index ca144c9d82..a41e4cb6c6 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1436,7 +1436,7 @@ class SingleAxisTrackerMount(AbstractMount): The tilt of the axis of rotation (i.e, the y-axis defined by axis_azimuth) with respect to horizontal. [degrees] - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 180 @@ -1506,12 +1506,12 @@ class SingleAxisTrackerMount(AbstractMount): # field axis_tilt, renamed to axis_slope; and # field cross_axis_tilt, renamed to cross_axis_slope; GH#2334 & GH#2543 @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -1531,7 +1531,7 @@ def __init__(self, axis_slope=0.0, axis_azimuth=0.0, max_angle=90.0, @property def axis_tilt(self): warn( - "'axis_tilt' is deprecated since v0.13.1. " + "'axis_tilt' is deprecated since v0.13.2. " "Use 'axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1541,7 +1541,7 @@ def axis_tilt(self): @axis_tilt.setter def axis_tilt(self, new_value): warn( - "'axis_tilt' is deprecated since v0.13.1. " + "'axis_tilt' is deprecated since v0.13.2. " "Use 'axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1551,7 +1551,7 @@ def axis_tilt(self, new_value): @property def cross_axis_tilt(self): warn( - "'cross_axis_tilt' is deprecated since v0.13.1. " + "'cross_axis_tilt' is deprecated since v0.13.2. " "Use 'cross_axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1561,7 +1561,7 @@ def cross_axis_tilt(self): @cross_axis_tilt.setter def cross_axis_tilt(self, new_value): warn( - "'cross_axis_tilt' is deprecated since v0.13.1. " + "'cross_axis_tilt' is deprecated since v0.13.2. " "Use 'cross_axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, diff --git a/pvlib/shading.py b/pvlib/shading.py index ec140d13dc..48ad75918e 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -237,7 +237,7 @@ def sky_diffuse_passias(masking_angle): @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -267,7 +267,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, axis_slope : numeric Axis tilt angle in degrees. From horizontal plane to array plane. - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope`` axis_azimuth : numeric @@ -356,12 +356,12 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -416,7 +416,7 @@ def shaded_fraction1d( axis_slope : numeric, default 0 Tilt of the rows axis from horizontal. In degrees :math:`^{\circ}`. - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope`` surface_to_axis_offset : numeric, default 0 @@ -426,7 +426,7 @@ def shaded_fraction1d( In degrees :math:`^{\circ}`. See :term:`cross_axis_slope`. - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``cross_axis_tilt`` to ``cross_axis_slope`` shading_row_rotation : numeric, optional diff --git a/pvlib/tracking.py b/pvlib/tracking.py index a50853c7a1..7b32647530 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -8,12 +8,12 @@ @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -56,7 +56,7 @@ def singleaxis(apparent_zenith, solar_azimuth, ``axis_azimuth``) with respect to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degrees] - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 @@ -96,7 +96,7 @@ def singleaxis(apparent_zenith, solar_azimuth, In degrees :math:`^{\circ}`. See :term:`cross_axis_slope`. - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``cross_axis_tilt`` to ``cross_axis_slope``. Returns @@ -220,7 +220,7 @@ def singleaxis(apparent_zenith, solar_azimuth, @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -240,7 +240,7 @@ def calc_surface_orientation(tracker_theta, axis_slope=0, axis_azimuth=0): The tilt of the axis of rotation with respect to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degree] - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 @@ -290,7 +290,7 @@ def calc_axis_slope(slope_azimuth, slope_tilt, axis_azimuth): respect to horizontal, ranging from 0 degrees (horizontal axis) to 90 degrees (vertical axis). - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed function ``calc_axis_tilt`` to ``calc_axis_slope``. Parameters @@ -382,7 +382,7 @@ def _calc_beta_c(v, dg, ba): @renamed_kwarg_warning( - since="0.13.1", + since="0.13.2", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -400,7 +400,7 @@ def calc_cross_axis_slope( if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes down to the west. - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed function ``calc_cross_axis_tilt`` to ``calc_cross_axis_slope``. Parameters @@ -417,7 +417,7 @@ def calc_cross_axis_slope( tilt of trackers relative to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degree] - .. versionchanged:: 0.13.1 + .. versionchanged:: 0.13.2 Renamed from ``axis_tilt`` to ``axis_slope``. Returns @@ -453,13 +453,13 @@ def calc_cross_axis_slope( # allow deprecated names of calc_cross_axis_slope and calc_axis_slope calc_axis_tilt = deprecated( - since="0.13.1", + since="0.13.2", name="calc_axis_tilt", # else it uses calc_axis_slope by introspection alternative="pvlib.tracking.calc_axis_slope" )(calc_axis_slope) calc_cross_axis_tilt = deprecated( - since="0.13.1", + since="0.13.2", name="calc_cross_axis_tilt", # else it uses calc_cross_axis_slope alternative="pvlib.tracking.calc_cross_axis_slope" )(calc_cross_axis_slope) From 24415fd4b6b9d17ae7624ba497ab358c7b369a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:17:38 +0100 Subject: [PATCH 20/25] Move Deprecations to its section, instead of Breaking Changes --- docs/sphinx/source/whatsnew/v0.13.2.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index aa43b964a9..0738746122 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -6,7 +6,14 @@ v0.13.2 (Anticipated December, 2025) Breaking Changes ~~~~~~~~~~~~~~~~ -* Rename ``axis_tilt`` and ``cross_axis_tilt`` to ``axis_slope`` and ``cross_axis_slope`` all through the project. Affects: +* Following the removal of the NSRDB PSM3 API, the :func:`!pvlib.iotools.get_psm3`, + :func:`!pvlib.iotools.read_psm3`, and :func:`!pvlib.iotools.parse_psm3` + functions are removed. (:issue:`2581`, :pull:`2582`) + + +Deprecations +~~~~~~~~~~~~ +* Rename ``axis_tilt`` and ``cross_axis_tilt`` to ``axis_slope`` and ``cross_axis_slope`` all through the project, while keeping backwards-compatibility. Affects: - :py:func:`pvlib.shading.shaded_fraction1d` parameters ``cross_axis_tilt`` and ``axis_tilt`` - :py:func:`pvlib.shading.projected_solar_zenith_angle` parameter ``axis_tilt`` @@ -17,14 +24,6 @@ Breaking Changes - :py:class:`pvlib.pvsystem.SingleAxisTrackerMount` dataclass fields ``axis_tilt`` and ``cross_axis_tilt`` (:issue:`2334`, :pull:`2543`) -* Following the removal of the NSRDB PSM3 API, the :func:`!pvlib.iotools.get_psm3`, - :func:`!pvlib.iotools.read_psm3`, and :func:`!pvlib.iotools.parse_psm3` - functions are removed. (:issue:`2581`, :pull:`2582`) - - -Deprecations -~~~~~~~~~~~~ - Bug fixes ~~~~~~~~~ From 03504661db7fd04b655e93d41e496c86d1b929fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:22:36 +0100 Subject: [PATCH 21/25] Description update of axis_azimuth Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- pvlib/pvsystem.py | 4 ++-- pvlib/tracking.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index a41e4cb6c6..08c3c2bb52 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1440,8 +1440,8 @@ class SingleAxisTrackerMount(AbstractMount): Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 180 - A value denoting the compass direction along which the axis of - rotation lies, measured east of north. [degrees] + The compass direction along which the axis of rotation lies. + Measured in decimal degrees east of north. [degrees] max_angle : float or tuple, default 90 A value denoting the maximum rotation angle, in decimal degrees, diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 7b32647530..4c2935648c 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -61,7 +61,7 @@ def singleaxis(apparent_zenith, solar_azimuth, axis_azimuth : float, default 0 The compass direction along which the axis of rotation lies. - Measured in decimal degrees east of north. + Measured in decimal degrees east of north. [degrees] max_angle : float or tuple, default 90 A value denoting the maximum rotation angle, in decimal degrees, @@ -244,8 +244,7 @@ def calc_surface_orientation(tracker_theta, axis_slope=0, axis_azimuth=0): Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 - A value denoting the compass direction along which the axis of - rotation lies. Measured east of north. [degree] + [degrees] [degree] Returns ------- From 3ed61721457c5027bb2a4f69784a237cf3bd07fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:26:11 +0100 Subject: [PATCH 22/25] picky linter --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 08c3c2bb52..9a4c2796a5 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1427,7 +1427,7 @@ def get_orientation(self, solar_zenith, solar_azimuth): @dataclass class SingleAxisTrackerMount(AbstractMount): - """ + r""" Single-axis tracker racking for dynamic solar tracking. Parameters From 5d0719ade726614723bfea33b42b33a5e4b74e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:10:06 +0100 Subject: [PATCH 23/25] nomenclature cross_axis_slope redaction Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- docs/sphinx/source/user_guide/extras/nomenclature.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index bac5f45f33..49ab37b9b8 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -51,10 +51,9 @@ There is a convention on consistent variable names throughout the library: cross_axis_slope Cross-axis slope angle. [°] - The angle, relative to horizontal, of the line formed by the - intersection between the slope containing the tracker axes and a plane - perpendicular to the tracker axes. Cross-axis slope should be specified - using a right-handed convention. + The angle, relative to tracker coordinate system horizontal, of the line between + the axes of two adjacent trackers, in the plane perpendicular to the tracker axes. + Cross-axis slope should be specified using a right-handed convention. For example, trackers with axis azimuth of 180° (N-S rotation axis) will have a negative cross-axis slope if the tracker axes plane slopes From 9ecf9e4a9dc4965d69a26f236501f379a4fa0d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:02:52 +0100 Subject: [PATCH 24/25] Cliff's fixing my confusion Co-Authored-By: Cliff Hansen <5393711+cwhanse@users.noreply.github.com> --- docs/sphinx/source/user_guide/extras/nomenclature.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/user_guide/extras/nomenclature.rst b/docs/sphinx/source/user_guide/extras/nomenclature.rst index 49ab37b9b8..148a6c3e50 100644 --- a/docs/sphinx/source/user_guide/extras/nomenclature.rst +++ b/docs/sphinx/source/user_guide/extras/nomenclature.rst @@ -51,8 +51,8 @@ There is a convention on consistent variable names throughout the library: cross_axis_slope Cross-axis slope angle. [°] - The angle, relative to tracker coordinate system horizontal, of the line between - the axes of two adjacent trackers, in the plane perpendicular to the tracker axes. + The angle, relative to horizontal, of the line between the axes of two + adjacent trackers, in the plane perpendicular to the tracker axes. Cross-axis slope should be specified using a right-handed convention. For example, trackers with axis azimuth of 180° (N-S rotation axis) From e96e34010727c84af49b5a11dcc55986ddc0b2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Echedey=20Luis=20=C3=81lvarez?= <80125792+echedey-ls@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:35:48 +0100 Subject: [PATCH 25/25] Bump deprecation version to 0.14.0 (from 0.13.2) --- pvlib/pvsystem.py | 16 ++++++++-------- pvlib/shading.py | 12 ++++++------ pvlib/tracking.py | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 9a4c2796a5..3df07c8ed0 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1436,7 +1436,7 @@ class SingleAxisTrackerMount(AbstractMount): The tilt of the axis of rotation (i.e, the y-axis defined by axis_azimuth) with respect to horizontal. [degrees] - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 180 @@ -1476,7 +1476,7 @@ class SingleAxisTrackerMount(AbstractMount): In degrees :math:`^{\circ}`. See :term:`cross_axis_slope`. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``cross_axis_tilt`` to ``cross_axis_slope``. racking_model : str, optional @@ -1506,12 +1506,12 @@ class SingleAxisTrackerMount(AbstractMount): # field axis_tilt, renamed to axis_slope; and # field cross_axis_tilt, renamed to cross_axis_slope; GH#2334 & GH#2543 @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -1531,7 +1531,7 @@ def __init__(self, axis_slope=0.0, axis_azimuth=0.0, max_angle=90.0, @property def axis_tilt(self): warn( - "'axis_tilt' is deprecated since v0.13.2. " + "'axis_tilt' is deprecated since v0.14.0. " "Use 'axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1541,7 +1541,7 @@ def axis_tilt(self): @axis_tilt.setter def axis_tilt(self, new_value): warn( - "'axis_tilt' is deprecated since v0.13.2. " + "'axis_tilt' is deprecated since v0.14.0. " "Use 'axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1551,7 +1551,7 @@ def axis_tilt(self, new_value): @property def cross_axis_tilt(self): warn( - "'cross_axis_tilt' is deprecated since v0.13.2. " + "'cross_axis_tilt' is deprecated since v0.14.0. " "Use 'cross_axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, @@ -1561,7 +1561,7 @@ def cross_axis_tilt(self): @cross_axis_tilt.setter def cross_axis_tilt(self, new_value): warn( - "'cross_axis_tilt' is deprecated since v0.13.2. " + "'cross_axis_tilt' is deprecated since v0.14.0. " "Use 'cross_axis_slope' instead.", pvlibDeprecationWarning, stacklevel=2, diff --git a/pvlib/shading.py b/pvlib/shading.py index 48ad75918e..c3fcbd1039 100644 --- a/pvlib/shading.py +++ b/pvlib/shading.py @@ -237,7 +237,7 @@ def sky_diffuse_passias(masking_angle): @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -267,7 +267,7 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, axis_slope : numeric Axis tilt angle in degrees. From horizontal plane to array plane. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope`` axis_azimuth : numeric @@ -356,12 +356,12 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth, @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -416,7 +416,7 @@ def shaded_fraction1d( axis_slope : numeric, default 0 Tilt of the rows axis from horizontal. In degrees :math:`^{\circ}`. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope`` surface_to_axis_offset : numeric, default 0 @@ -426,7 +426,7 @@ def shaded_fraction1d( In degrees :math:`^{\circ}`. See :term:`cross_axis_slope`. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``cross_axis_tilt`` to ``cross_axis_slope`` shading_row_rotation : numeric, optional diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 4c2935648c..ea9a3c5133 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -8,12 +8,12 @@ @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="cross_axis_tilt", new_param_name="cross_axis_slope", ) @@ -56,7 +56,7 @@ def singleaxis(apparent_zenith, solar_azimuth, ``axis_azimuth``) with respect to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degrees] - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 @@ -96,7 +96,7 @@ def singleaxis(apparent_zenith, solar_azimuth, In degrees :math:`^{\circ}`. See :term:`cross_axis_slope`. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``cross_axis_tilt`` to ``cross_axis_slope``. Returns @@ -220,7 +220,7 @@ def singleaxis(apparent_zenith, solar_azimuth, @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -240,7 +240,7 @@ def calc_surface_orientation(tracker_theta, axis_slope=0, axis_azimuth=0): The tilt of the axis of rotation with respect to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degree] - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope``. axis_azimuth : float, default 0 @@ -289,7 +289,7 @@ def calc_axis_slope(slope_azimuth, slope_tilt, axis_azimuth): respect to horizontal, ranging from 0 degrees (horizontal axis) to 90 degrees (vertical axis). - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed function ``calc_axis_tilt`` to ``calc_axis_slope``. Parameters @@ -381,7 +381,7 @@ def _calc_beta_c(v, dg, ba): @renamed_kwarg_warning( - since="0.13.2", + since="0.14.0", old_param_name="axis_tilt", new_param_name="axis_slope", ) @@ -399,7 +399,7 @@ def calc_cross_axis_slope( if the tracker axes plane slopes down to the east and positive cross-axis tilt if the tracker axes plane slopes down to the west. - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed function ``calc_cross_axis_tilt`` to ``calc_cross_axis_slope``. Parameters @@ -416,7 +416,7 @@ def calc_cross_axis_slope( tilt of trackers relative to horizontal. ``axis_slope`` must be >= 0 and <= 90. [degree] - .. versionchanged:: 0.13.2 + .. versionchanged:: 0.14.0 Renamed from ``axis_tilt`` to ``axis_slope``. Returns @@ -452,13 +452,13 @@ def calc_cross_axis_slope( # allow deprecated names of calc_cross_axis_slope and calc_axis_slope calc_axis_tilt = deprecated( - since="0.13.2", + since="0.14.0", name="calc_axis_tilt", # else it uses calc_axis_slope by introspection alternative="pvlib.tracking.calc_axis_slope" )(calc_axis_slope) calc_cross_axis_tilt = deprecated( - since="0.13.2", + since="0.14.0", name="calc_cross_axis_tilt", # else it uses calc_cross_axis_slope alternative="pvlib.tracking.calc_cross_axis_slope" )(calc_cross_axis_slope)