SINGLEAXIS

This function calculates the optimal rotation angle for a single-axis tracker to minimize the angle of incidence (AOI) with the sun.

It can optionally account for axis tilt, axis azimuth, and backtracking to avoid row-to-row shading. The function returns the tracker rotation angle, AOI, and the resulting surface orientation (tilt and azimuth).

Excel Usage

=SINGLEAXIS(apparent_zenith, solar_azimuth, axis_tilt, axis_azimuth, max_angle, backtrack, gcr, cross_axis_tilt)
  • apparent_zenith (list[list], required): Solar apparent zenith angles (degrees).
  • solar_azimuth (list[list], required): Solar apparent azimuth angles (degrees).
  • axis_tilt (float, optional, default: 0): Tilt of the rotation axis (degrees).
  • axis_azimuth (float, optional, default: 0): Compass direction of the rotation axis (degrees).
  • max_angle (float, optional, default: 90): Maximum rotation angle in either direction (degrees).
  • backtrack (bool, optional, default: true): Enable backtracking to avoid row shading.
  • gcr (float, optional, default: 0.2857): Ground coverage ratio (ratio of panel area to ground area).
  • cross_axis_tilt (float, optional, default: 0): Terrain slope perpendicular to the tracker axis (degrees).

Returns (list[list]): 2D list [[tracker_theta, aoi, surface_tilt, surface_azimuth]], or an error string.

Example 1: Perfect south tracker at noon

Inputs:

apparent_zenith solar_azimuth axis_tilt axis_azimuth max_angle backtrack gcr
30 180 0 180 60 true 0.3

Excel formula:

=SINGLEAXIS({30}, {180}, 0, 180, 60, TRUE, 0.3)

Expected output:

Result
0 30 0 270
Example 2: Morning sun rotates tracker eastward

Inputs:

apparent_zenith solar_azimuth axis_tilt axis_azimuth max_angle backtrack gcr
45 120 0 180 60 true 0.3

Excel formula:

=SINGLEAXIS({45}, {120}, 0, 180, 60, TRUE, 0.3)

Expected output:

Result
-40.8934 20.7048 40.8934 90
Example 3: Vectorized tracker results for two timesteps

Inputs:

apparent_zenith solar_azimuth axis_tilt axis_azimuth max_angle backtrack gcr
30 60 180 220 0 180 60 true 0.3

Excel formula:

=SINGLEAXIS({30,60}, {180,220}, 0, 180, 60, TRUE, 0.3)

Expected output:

Result
0 30 0 270
48.0699 41.5608 48.0699 270
Example 4: True-tracking without backtracking

Inputs:

apparent_zenith solar_azimuth axis_tilt axis_azimuth max_angle backtrack gcr
50 200 0 180 75 false 0.3

Excel formula:

=SINGLEAXIS({50}, {200}, 0, 180, 75, FALSE, 0.3)

Expected output:

Result
22.176 46.0418 22.176 270

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.tracking import singleaxis as result_func

def singleaxis(apparent_zenith, solar_azimuth, axis_tilt=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=0.2857, cross_axis_tilt=0):
    """
    Determine the rotation angle and incidence angle for a single-axis tracker.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.tracking.singleaxis.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        apparent_zenith (list[list]): Solar apparent zenith angles (degrees).
        solar_azimuth (list[list]): Solar apparent azimuth angles (degrees).
        axis_tilt (float, optional): Tilt of the rotation axis (degrees). Default is 0.
        axis_azimuth (float, optional): Compass direction of the rotation axis (degrees). Default is 0.
        max_angle (float, optional): Maximum rotation angle in either direction (degrees). Default is 90.
        backtrack (bool, optional): Enable backtracking to avoid row shading. Default is True.
        gcr (float, optional): Ground coverage ratio (ratio of panel area to ground area). Default is 0.2857.
        cross_axis_tilt (float, optional): Terrain slope perpendicular to the tracker axis (degrees). Default is 0.

    Returns:
        list[list]: 2D list [[tracker_theta, aoi, surface_tilt, surface_azimuth]], or an error string.
    """
    try:
        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        zen_list = flatten_num(apparent_zenith)
        az_list = flatten_num(solar_azimuth)

        n = len(zen_list)
        if n == 0 or len(az_list) != n:
            return "Error: zenith and azimuth arrays must have the same non-zero length"

        at = float(axis_tilt) if axis_tilt is not None else 0.0
        aa = float(axis_azimuth) if axis_azimuth is not None else 0.0
        ma = float(max_angle) if max_angle is not None else 90.0
        bt = bool(backtrack) if backtrack is not None else True
        gc = float(gcr) if gcr is not None else 0.2857
        cat = float(cross_axis_tilt) if cross_axis_tilt is not None else 0.0

        res = result_func(
            apparent_zenith=np.array(zen_list),
            solar_azimuth=np.array(az_list),
            axis_tilt=at,
            axis_azimuth=aa,
            max_angle=ma,
            backtrack=bt,
            gcr=gc,
            cross_axis_tilt=cat
        )

        # res is a DataFrame with columns: tracker_theta, aoi, surface_tilt, surface_azimuth
        out = []
        tt = res['tracker_theta']
        aoi = res['aoi']
        st = res['surface_tilt']
        sa = res['surface_azimuth']

        for i in range(n):
            out.append([
                float(tt[i]) if not pd.isna(tt[i]) else "",
                float(aoi[i]) if not pd.isna(aoi[i]) else "",
                float(st[i]) if not pd.isna(st[i]) else "",
                float(sa[i]) if not pd.isna(sa[i]) else ""
            ])
        return out
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Solar apparent zenith angles (degrees).
Solar apparent azimuth angles (degrees).
Tilt of the rotation axis (degrees).
Compass direction of the rotation axis (degrees).
Maximum rotation angle in either direction (degrees).
Enable backtracking to avoid row shading.
Ground coverage ratio (ratio of panel area to ground area).
Terrain slope perpendicular to the tracker axis (degrees).