Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/examples/shading/plot_martinez_shade_loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

# %%
Expand Down
12 changes: 6 additions & 6 deletions docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]

# %%
Expand Down Expand Up @@ -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,
),
)
Expand All @@ -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
Expand All @@ -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,
),
)
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions docs/sphinx/source/user_guide/extras/nomenclature.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ 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.
Use :func:`~pvlib.tracking.calc_cross_axis_tilt` to calculate
``cross_axis_tilt``

dhi
Diffuse horizontal irradiance

Expand Down
1 change: 1 addition & 0 deletions docs/sphinx/source/whatsnew/v0.13.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~
Expand Down
2 changes: 1 addition & 1 deletion pvlib/_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 20 additions & 12 deletions pvlib/shading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -344,6 +346,12 @@ def projected_solar_zenith_angle(solar_zenith, solar_azimuth,
return theta_T


@renamed_kwarg_warning(
since="0.13.1",
old_param_name="cross_axis_slope",
new_param_name="cross_axis_tilt",
removal="0.15.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
removal="0.15.0",

We usually don't specify a removal version as there's not standard timeline for when minor versions come out. If we really wanted to specify a removal, I think this should be in the form of a date, e.g., removal="Earliest September 2026".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never thought of that, but looks a promising idea

Copy link
Member

@AdamRJensen AdamRJensen Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm interested in what @wholmgren thinks of this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this for a few weeks. I don't think timeline is particularly relevant so long as we are using SemVer (or something close to it). I personally think we should specify removal versions, they should default to the 0.(m+2).0 release, and we should use our existing test machinery to enforce that. If we had several minor releases in a very short period of time (a few weeks) then I'd be ok pushing out the removal to a future release.

)
def shaded_fraction1d(
solar_zenith,
solar_azimuth,
Expand All @@ -354,7 +362,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"""
Expand Down Expand Up @@ -397,10 +405,10 @@ 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
Angle of the plane containing the rows' axes from
cross_axis_tilt : numeric, default 0
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}`.
Expand Down Expand Up @@ -430,7 +438,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 |
Expand All @@ -452,7 +460,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**
Expand All @@ -466,7 +474,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**
Expand All @@ -478,7 +486,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
Expand All @@ -493,7 +501,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
Expand Down Expand Up @@ -528,7 +536,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))

Expand All @@ -548,7 +556,7 @@ def shaded_fraction1d(
/ collector_width
* cosd(thetaS_rotation_diff)
/ cos_theta_2_S_diff_abs
/ cosd(cross_axis_slope)
/ cosd(cross_axis_tilt)
)
)

Expand Down Expand Up @@ -660,7 +668,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
Expand Down
14 changes: 4 additions & 10 deletions pvlib/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
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):
"""
r"""
Determine the rotation angle of a single-axis tracker when given particular
solar zenith and azimuth angles.

Expand Down Expand Up @@ -80,15 +80,9 @@ def singleaxis(apparent_zenith, solar_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
-------
Expand Down
23 changes: 21 additions & 2 deletions tests/test_shading.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from pvlib import shading
from pvlib.tools import atand

from .conftest import fail_on_pvlib_version
from pvlib._deprecation import pvlibDeprecationWarning


@pytest.fixture
def test_system():
Expand Down Expand Up @@ -258,7 +261,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"])
)
Expand Down Expand Up @@ -314,7 +317,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=[
Expand All @@ -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.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"):
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():
"""
Expand Down
Loading