diff --git a/docs/sphinx/source/whatsnew/v0.10.2.rst b/docs/sphinx/source/whatsnew/v0.10.2.rst index f95f0bd93d..0a218c8f0a 100644 --- a/docs/sphinx/source/whatsnew/v0.10.2.rst +++ b/docs/sphinx/source/whatsnew/v0.10.2.rst @@ -15,7 +15,9 @@ Enhancements :py:func:`pvlib.iotools.get_pvgis_hourly`, :py:func:`pvlib.iotools.get_cams`, :py:func:`pvlib.iotools.get_bsrn`, and :py:func:`pvlib.iotools.read_midc_raw_data_from_nrel`. (:pull:`1800`) - +* Added support for asymmetric limiting angles in :py:func:`pvlib.tracking.singleaxis`, by modifying + the `min_angle` parameter to accept tuples. + (:pull:`1809`) Bug fixes ~~~~~~~~~ @@ -36,3 +38,4 @@ Requirements Contributors ~~~~~~~~~~~~ * Adam R. Jensen (:ghuser:`AdamRJensen`) +* Michal Arieli (:ghuser:`MichalArieli`) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 4fb0dc5772..08693ecec7 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1410,12 +1410,20 @@ class SingleAxisTrackerMount(AbstractMount): A value denoting the compass direction along which the axis of rotation lies, measured east of north. [degrees] - max_angle : float, default 90 - A value denoting the maximum rotation angle + 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). A max_angle of 90 degrees allows the tracker - to rotate to a vertical position to point the panel towards a - horizon. max_angle of 180 degrees allows for full rotation. [degrees] + if axis_tilt = 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. + + A rotation to 'max_angle' is a counter-clockwise rotation about the y-axis + of the tracker coordinate system. For example, for a tracker with 'axis_azimuth' + oriented to the south, a rotation to 'max_angle' is towards the west, and a + rotation toward 'min_angle' is in the opposite direction, toward the east. + Hence a max_angle of 180 degrees (equivalent to max_angle = (-180, 180)) allows + the tracker to achieve its full rotation capability. backtrack : bool, default True Controls whether the tracker has the capability to "backtrack" diff --git a/pvlib/tests/test_tracking.py b/pvlib/tests/test_tracking.py index 95c05c8df7..4febc73389 100644 --- a/pvlib/tests/test_tracking.py +++ b/pvlib/tests/test_tracking.py @@ -151,6 +151,22 @@ def test_max_angle(): assert_frame_equal(expect, tracker_data) +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, + max_angle=(-45, 50), backtrack=True, + gcr=2.0/7.0) + + expect = pd.DataFrame({'aoi': 15, 'surface_azimuth': 270, + 'surface_tilt': 45, 'tracker_theta': -45}, + index=[0], dtype=np.float64) + expect = expect[SINGLEAXIS_COL_ORDER] + + assert_frame_equal(expect, tracker_data) + + def test_backtrack(): apparent_zenith = pd.Series([80]) apparent_azimuth = pd.Series([90]) diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 4e027665ed..8f06583afa 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -44,12 +44,20 @@ def singleaxis(apparent_zenith, apparent_azimuth, A value denoting the compass direction along which the axis of rotation lies. Measured in decimal degrees east of north. - max_angle : float, default 90 + 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). A max_angle of 90 degrees allows the tracker - to rotate to a vertical position to point the panel towards a - horizon. max_angle of 180 degrees allows for full rotation. + if axis_tilt = 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. + + A rotation to 'max_angle' is a counter-clockwise rotation about the y-axis + of the tracker coordinate system. For example, for a tracker with 'axis_azimuth' + oriented to the south, a rotation to 'max_angle' is towards the west, and a + rotation toward 'min_angle' is in the opposite direction, toward the east. + Hence a max_angle of 180 degrees (equivalent to max_angle = (-180, 180)) allows + the tracker to achieve its full rotation capability. backtrack : bool, default True Controls whether the tracker has the capability to "backtrack" @@ -190,7 +198,17 @@ def singleaxis(apparent_zenith, apparent_azimuth, # NOTE: max_angle defined relative to zero-point rotation, not the # system-plane normal - tracker_theta = np.clip(tracker_theta, -max_angle, max_angle) + + + # Determine minimum and maximum rotation angles for the tracker based on max_angle. + # If max_angle is a single value, assume min_angle is the negative of max_angle. + if np.array(max_angle).size == 1: + min_angle = -max_angle + else: + min_angle, max_angle = max_angle + + # Clip tracker_theta between the minimum and maximum angles. + tracker_theta = np.clip(tracker_theta, min_angle, max_angle) # Calculate auxiliary angles surface = calc_surface_orientation(tracker_theta, axis_tilt, axis_azimuth)