def singleaxis(apparent_zenith, apparent_azimuth,<EOL>axis_tilt=<NUM_LIT:0>, axis_azimuth=<NUM_LIT:0>, max_angle=<NUM_LIT>,<EOL>backtrack=True, gcr=<NUM_LIT>/<NUM_LIT>):
<EOL>if isinstance(apparent_zenith, pd.Series):<EOL><INDENT>index = apparent_zenith.index<EOL><DEDENT>else:<EOL><INDENT>index = None<EOL><DEDENT>apparent_azimuth = np.atleast_1d(apparent_azimuth)<EOL>apparent_zenith = np.atleast_1d(apparent_zenith)<EOL>if apparent_azimuth.ndim > <NUM_LIT:1> or apparent_zenith.ndim > <NUM_LIT:1>:<EOL><INDENT>raise ValueError('<STR_LIT>')<EOL><DEDENT>az = apparent_azimuth - <NUM_LIT><EOL>apparent_elevation = <NUM_LIT> - apparent_zenith<EOL>x = cosd(apparent_elevation) * sind(az)<EOL>y = cosd(apparent_elevation) * cosd(az)<EOL>z = sind(apparent_elevation)<EOL>axis_azimuth_south = axis_azimuth - <NUM_LIT><EOL>xp = x*cosd(axis_azimuth_south) - y*sind(axis_azimuth_south)<EOL>yp = (x*cosd(axis_tilt)*sind(axis_azimuth_south) +<EOL>y*cosd(axis_tilt)*cosd(axis_azimuth_south) -<EOL>z*sind(axis_tilt))<EOL>zp = (x*sind(axis_tilt)*sind(axis_azimuth_south) +<EOL>y*sind(axis_tilt)*cosd(axis_azimuth_south) +<EOL>z*cosd(axis_tilt))<EOL><INDENT>tmp = np.degrees(np.arctan(zp/xp))<EOL>wid = pd.Series(index=times)<EOL>wid[(xp>=<NUM_LIT:0>) & (zp>=<NUM_LIT:0>)] =  <NUM_LIT> - tmp[(xp>=<NUM_LIT:0>) & (zp>=<NUM_LIT:0>)]  <EOL>wid[(xp<<NUM_LIT:0>)  & (zp>=<NUM_LIT:0>)] = -<NUM_LIT> - tmp[(xp<<NUM_LIT:0>)  & (zp>=<NUM_LIT:0>)]  <EOL>wid[(xp<<NUM_LIT:0>)  & (zp<<NUM_LIT:0>)]  = -<NUM_LIT> - tmp[(xp<<NUM_LIT:0>)  & (zp<<NUM_LIT:0>)]   <EOL>wid[(xp>=<NUM_LIT:0>) & (zp<<NUM_LIT:0>)]  =  <NUM_LIT> - tmp[(xp>=<NUM_LIT:0>) & (zp<<NUM_LIT:0>)]   <EOL><DEDENT>wid = <NUM_LIT> - np.degrees(np.arctan2(zp, xp))<EOL>zen_gt_90 = apparent_zenith > <NUM_LIT><EOL>wid[zen_gt_90] = np.nan<EOL>if backtrack:<EOL><INDENT>axes_distance = <NUM_LIT:1>/gcr<EOL>temp = np.minimum(axes_distance*cosd(wid), <NUM_LIT:1>)<EOL>wc = np.degrees(np.arccos(temp))<EOL>tracker_theta = np.where(wid < <NUM_LIT:0>, wid + wc, wid - wc)<EOL><DEDENT>else:<EOL><INDENT>tracker_theta = wid<EOL><DEDENT>tracker_theta[tracker_theta > max_angle] = max_angle<EOL>tracker_theta[tracker_theta < -max_angle] = -max_angle<EOL>panel_norm = np.array([sind(tracker_theta),<EOL>tracker_theta*<NUM_LIT:0>,<EOL>cosd(tracker_theta)])<EOL>sun_vec = np.array([xp, yp, zp])<EOL>aoi = np.degrees(np.arccos(np.abs(np.sum(sun_vec*panel_norm, axis=<NUM_LIT:0>))))<EOL>rot_x = np.array([[<NUM_LIT:1>, <NUM_LIT:0>, <NUM_LIT:0>],<EOL>[<NUM_LIT:0>, cosd(-axis_tilt), -sind(-axis_tilt)],<EOL>[<NUM_LIT:0>, sind(-axis_tilt), cosd(-axis_tilt)]])<EOL>panel_norm_earth = np.dot(rot_x, panel_norm).T<EOL>projected_normal = np.array([panel_norm_earth[:, <NUM_LIT:0>],<EOL>panel_norm_earth[:, <NUM_LIT:1>],<EOL>panel_norm_earth[:, <NUM_LIT:2>]*<NUM_LIT:0>]).T<EOL>projected_normal_mag = np.sqrt(np.nansum(projected_normal**<NUM_LIT:2>, axis=<NUM_LIT:1>))<EOL>non_zeros = projected_normal_mag != <NUM_LIT:0><EOL>projected_normal[non_zeros] = (projected_normal[non_zeros].T /<EOL>projected_normal_mag[non_zeros]).T<EOL><INDENT>surface_azimuth = pd.Series(<EOL>np.degrees(np.arctan(projected_normal[:,<NUM_LIT:1>]/projected_normal[:,<NUM_LIT:0>])),<EOL>index=times)<EOL><DEDENT>surface_azimuth =np.degrees(np.arctan2(projected_normal[:, <NUM_LIT:1>], projected_normal[:, <NUM_LIT:0>]))<EOL><INDENT>surface_azimuth[(projected_normal[:,<NUM_LIT:0>]==<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:1>]><NUM_LIT:0>)] =  <NUM_LIT><EOL>surface_azimuth[(projected_normal[:,<NUM_LIT:0>]==<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:1>]<<NUM_LIT:0>)] =  -<NUM_LIT><EOL>surface_azimuth[(projected_normal[:,<NUM_LIT:1>]==<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:0>]><NUM_LIT:0>)] =  <NUM_LIT:0><EOL>surface_azimuth[(projected_normal[:,<NUM_LIT:1>]==<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:0>]<<NUM_LIT:0>)] = <NUM_LIT><EOL>surface_azimuth[(projected_normal[:,<NUM_LIT:0>]<<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:1>]><NUM_LIT:0>)] += <NUM_LIT> <EOL>surface_azimuth[(projected_normal[:,<NUM_LIT:0>]<<NUM_LIT:0>) & (projected_normal[:,<NUM_LIT:1>]<<NUM_LIT:0>)] += <NUM_LIT> <EOL>
Determine the rotation angle of a single axis tracker using the
equations in [1] when given a particular sun zenith and azimuth
angle. backtracking may be specified, and if so, a ground coverage
ratio is required.

Rotation angle is determined in a panel-oriented coordinate system.
The tracker azimuth axis_azimuth defines the positive y-axis; the
positive x-axis is 90 degress clockwise from the y-axis and parallel
to the earth surface, and the positive z-axis is normal and oriented
towards the sun. Rotation angle tracker_theta indicates tracker
position relative to horizontal: tracker_theta = 0 is horizontal,
and positive tracker_theta is a clockwise rotation around the y axis
in the x, y, z coordinate system. For example, if tracker azimuth
axis_azimuth is 180 (oriented south), tracker_theta = 30 is a
rotation of 30 degrees towards the west, and tracker_theta = -90 is
a rotation to the vertical plane facing east.

Parameters
----------
apparent_zenith : float, 1d array, or Series
    Solar apparent zenith angles in decimal degrees.

apparent_azimuth : float, 1d array, or Series
    Solar apparent azimuth angles in decimal degrees.

axis_tilt : float, default 0
    The tilt of the axis of rotation (i.e, the y-axis defined by
    axis_azimuth) with respect to horizontal, in decimal degrees.

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.

max_angle : float, 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.

backtrack : bool, default True
    Controls whether the tracker has the capability to "backtrack"
    to avoid row-to-row shading. False denotes no backtrack
    capability. True denotes backtrack capability.

gcr : float, default 2.0/7.0
    A value denoting the ground coverage ratio of a tracker system
    which utilizes backtracking; i.e. the ratio between the PV array
    surface area to total ground area. A tracker system with modules
    2 meters wide, centered on the tracking axis, with 6 meters
    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.

Returns
-------
dict or DataFrame with the following columns:

* tracker_theta: The rotation angle of the tracker.
    tracker_theta = 0 is horizontal, and positive rotation angles are
    clockwise.
* aoi: The angle-of-incidence of direct irradiance onto the
    rotated panel surface.
* surface_tilt: The angle between the panel surface and the earth
    surface, accounting for panel rotation.
* surface_azimuth: The azimuth of the rotated panel, determined by
    projecting the vector normal to the panel's surface to the earth's
    surface.

References
----------
[1] Lorenzo, E et al., 2011, "Tracking and back-tracking", Prog. in
Photovoltaics: Research and Applications, v. 19, pp. 747-753.