Skip to content

Commit 9e397ff

Browse files
committed
close pvlib#1041 prune old comments
* remove commented code, especially any code relating to use of arctan2 adopted in 1fb82cc * remove comments that should be sourced directly from refernces, and instead list reference and Eqs. numbers * tidy up, combine lines, etc
1 parent 80c4491 commit 9e397ff

File tree

1 file changed

+42
-153
lines changed

1 file changed

+42
-153
lines changed

pvlib/tracking.py

Lines changed: 42 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class SingleAxisTracker(PVSystem):
2121
2222
axis_azimuth : float, default 0
2323
A value denoting the compass direction along which the axis of
24-
rotation lies. Measured in decimal degrees East of North.
24+
rotation lies. Measured in decimal degrees east of north.
2525
2626
max_angle : float, default 90
2727
A value denoting the maximum rotation angle, in decimal degrees,
@@ -291,7 +291,7 @@ def singleaxis(apparent_zenith, apparent_azimuth,
291291
292292
axis_azimuth : float, default 0
293293
A value denoting the compass direction along which the axis of
294-
rotation lies. Measured in decimal degrees East of North.
294+
rotation lies. Measured in decimal degrees east of north.
295295
296296
max_angle : float, default 90
297297
A value denoting the maximum rotation angle, in decimal degrees,
@@ -364,58 +364,24 @@ def singleaxis(apparent_zenith, apparent_azimuth,
364364
if apparent_azimuth.ndim > 1 or apparent_zenith.ndim > 1:
365365
raise ValueError('Input dimensions must not exceed 1')
366366

367-
# Calculate sun position x, y, z using coordinate system as in [1], Eq 2.
367+
# Calculate sun position x, y, z using coordinate system as in [2], Eq 1.
368368

369-
# Positive y axis is oriented parallel to earth surface along tracking axis
370-
# (for the purpose of illustration, assume y is oriented to the south);
371-
# positive x axis is orthogonal, 90 deg clockwise from y-axis, and parallel
372-
# to the earth's surface (if y axis is south, x axis is west);
373-
# positive z axis is normal to x, y axes, pointed upward.
374-
375-
# Equations in [1] assume solar azimuth is relative to reference vector
376-
# pointed south, with clockwise positive.
377-
# Here, the input solar azimuth is degrees East of North,
378-
# i.e., relative to a reference vector pointed
379-
# north with clockwise positive.
380-
381-
# NOTE: Equations in [2] agree with the reference frame used here, so
382-
# adjustments are not required
383-
384-
# Rotate sun azimuth to coordinate system as in [1, 2]
385-
# to calculate sun position.
386-
387-
# NOTE: sin(90-x) = cos(x) & cos(90-x) = sin(x)
369+
# NOTE: solar elevation = 90 - solar zenith, then use trig identities:
370+
# sin(90-x) = cos(x) & cos(90-x) = sin(x)
388371
sin_zenith = sind(apparent_zenith)
389372
x = sin_zenith * sind(apparent_azimuth)
390373
y = sin_zenith * cosd(apparent_azimuth)
391374
z = cosd(apparent_zenith)
392375

393-
# translate array azimuth from compass bearing to [1] coord system
394-
# wholmgren: strange to see axis_azimuth calculated differently from az,
395-
# (not that it matters, or at least it shouldn't...).
396-
397-
# NOTE: Coordinate system in [2] agrees with refernece frame used here, so
398-
# adjustments are not required
399-
400-
# translate input array tilt angle axis_tilt to [1] coordinate system.
401-
402-
# In [1] coordinates, axis_tilt is a rotation about the x-axis.
403-
# For a system with array azimuth (y-axis) oriented south,
404-
# the x-axis is oriented west, and a positive axis_tilt is a
405-
# counterclockwise rotation, i.e, lifting the north edge of the panel.
406-
# Thus, in [1] coordinate system, in the northern hemisphere a positive
407-
# axis_tilt indicates a rotation toward the equator,
408-
# whereas in the southern hemisphere rotation toward the equator is
409-
# indicated by axis_tilt<0. Here, the input axis_tilt is
410-
# always positive and is a rotation toward the equator.
411-
412-
# Calculate sun position (xp, yp, zp) in panel-oriented coordinate system:
413-
# positive y-axis is oriented along tracking axis at panel tilt;
414-
# positive x-axis is orthogonal, clockwise, parallel to earth surface;
415-
# positive z-axis is normal to x-y axes, pointed upward.
416-
# Calculate sun position (xp,yp,zp) in panel coordinates using [1] Eq 11
417-
# note that equation for yp (y' in Eq. 11 of Lorenzo et al 2011) is
418-
# corrected, after conversation with paper's authors.
376+
# Assume the tracker reference frame is right-handed. Positive y-axis is
377+
# oriented along tracking axis tilted by the axis tilt and rotated
378+
# clockwise from north by the axis azimuth; positive x-axis is orthogonal,
379+
# 90 deg clockwise from y-axis, and parallel to the earth's surface (if y-
380+
# axis is south, x-axis is west); positive z-axis is normal to x, y axes,
381+
# pointed upward.
382+
383+
# Calculate sun position (xp, yp, zp) in tracker coordinate system using
384+
# [2] Eq 4.
419385

420386
cos_axis_azimuth = cosd(axis_azimuth)
421387
sin_axis_azimuth = sind(axis_azimuth)
@@ -432,63 +398,36 @@ def singleaxis(apparent_zenith, apparent_azimuth,
432398
# The ideal tracking angle wid is the rotation to place the sun position
433399
# vector (xp, yp, zp) in the (y, z) plane; i.e., normal to the panel and
434400
# containing the axis of rotation. wid = 0 indicates that the panel is
435-
# horizontal. Here, our convention is that a clockwise rotation is
401+
# horizontal. Here, our convention is that a clockwise rotation is
436402
# positive, to view rotation angles in the same frame of reference as
437-
# azimuth. For example, for a system with tracking axis oriented south,
438-
# a rotation toward the east is negative, and a rotation to the west is
439-
# positive.
440-
441-
# angle from x-y plane to projection of sun vector onto x-z plane
442-
# tmp = np.degrees(np.arctan(zp/xp))
443-
444-
# Obtain wid by translating tmp to convention for rotation angles.
445-
# Have to account for which quadrant of the x-z plane in which the sun
446-
# vector lies. Complete solution here but probably not necessary to
447-
# consider QIII and QIV.
448-
# wid = pd.Series(index=times)
449-
# wid[(xp>=0) & (zp>=0)] = 90 - tmp[(xp>=0) & (zp>=0)] # QI
450-
# wid[(xp<0) & (zp>=0)] = -90 - tmp[(xp<0) & (zp>=0)] # QII
451-
# wid[(xp<0) & (zp<0)] = -90 - tmp[(xp<0) & (zp<0)] # QIII
452-
# wid[(xp>=0) & (zp<0)] = 90 - tmp[(xp>=0) & (zp<0)] # QIV
453-
454-
# NOTE: Use arctan2 and avoid the tmp corrections.
403+
# azimuth. For example, for a system with tracking axis oriented south, a
404+
# rotation toward the east is negative, and a rotation to the west is
405+
# positive. This is a right-handed rotation around the tracker y-axis.
455406

456407
# Calculate angle from x-y plane to projection of sun vector onto x-z plane
457-
# and then obtain wid by translating tmp to convention for rotation angles.
408+
# using [2] Eq. 5.
458409

459-
# NOTE: if zp/xp = tan(90 - wid) = cot(wid) then tan(wid) = xp/zp
460410
wid = np.degrees(np.arctan2(xp, zp))
461411

462412
# filter for sun above panel horizon
463413
zen_gt_90 = apparent_zenith > 90
464414
wid[zen_gt_90] = np.nan
465415

466-
# Account for backtracking; modified from [1] to account for rotation
467-
# angle convention being used here.
416+
# Account for backtracking
468417
if backtrack:
469418
# distance between rows in terms of rack lengths relative to side slope
470419
axes_distance = 1/gcr/cosd(side_slope)
471-
# clip needed for low angles. GH 656
472-
# temp = np.clip(axes_distance*cosd(wid - side_slope), -1, 1)
473420

474421
# NOTE: account for rare angles below array, see GH 824
475422
temp = np.abs(axes_distance * cosd(wid - side_slope))
476423

477-
# backtrack angle
478-
# (always positive b/c acosd returns values between 0 and 180)
479-
# wc = np.degrees(np.arccos(temp))
480-
# equation 14, ref [2]
424+
# backtrack angle using [2], Eq. 14
481425
with np.errstate(invalid='ignore'):
482426
wc = np.degrees(-np.sign(wid)*np.arccos(temp))
483427

484-
# Eq 4 applied when wid in QIV (wid < 0 evalulates True), QI
485-
# with np.errstate(invalid='ignore'):
486-
# errstate for GH 622
487-
# tracker_theta = np.where(wid < 0, wid + wc, wid - wc)
488-
489428
# NOTE: in the middle of the day, arccos(temp) is out of range because
490429
# there's no row-to-row shade to avoid, & backtracking is unnecessary
491-
# Equations 15-16, ref [2]
430+
# [2], Eqs. 15-16
492431
with np.errstate(invalid='ignore'):
493432
tracker_theta = wid + np.where(temp < 1, wc, 0)
494433
else:
@@ -498,10 +437,10 @@ def singleaxis(apparent_zenith, apparent_azimuth,
498437
# system-plane normal
499438
tracker_theta = np.clip(tracker_theta, -max_angle, max_angle)
500439

501-
# calculate panel normal vector in panel-oriented x, y, z coordinates.
502-
# y-axis is axis of tracker rotation. tracker_theta is a compass angle
440+
# Calculate panel normal vector in panel-oriented x, y, z coordinates.
441+
# y-axis is axis of tracker rotation. tracker_theta is a compass angle
503442
# (clockwise is positive) rather than a trigonometric angle.
504-
# the *0 is a trick to preserve NaN values.
443+
# NOTE: the *0 is a trick to preserve NaN values.
505444
panel_norm = np.array([sind(tracker_theta),
506445
tracker_theta*0,
507446
cosd(tracker_theta)])
@@ -512,99 +451,49 @@ def singleaxis(apparent_zenith, apparent_azimuth,
512451
# calculate angle-of-incidence on panel
513452
aoi = np.degrees(np.arccos(np.abs(np.sum(sun_vec*panel_norm, axis=0))))
514453

515-
# calculate panel tilt and azimuth
516-
# in a coordinate system where the panel tilt is the
517-
# angle from horizontal, and the panel azimuth is
518-
# the compass angle (clockwise from north) to the projection
519-
# of the panel's normal to the earth's surface.
520-
# These outputs are provided for convenience and comparison
521-
# with other PV software which use these angle conventions.
454+
# Calculate panel tilt and azimuth in a coordinate system where the panel
455+
# tilt is the angle from horizontal, and the panel azimuth is the compass
456+
# angle (clockwise from north) to the projection of the panel's normal to
457+
# the earth's surface. These outputs are provided for convenience and
458+
# comparison with other PV software which use these angle conventions.
522459

523-
# project normal vector to earth surface.
524-
# First rotate about x-axis by angle -axis_tilt so that y-axis is
525-
# also parallel to earth surface, then project.
460+
# Project normal vector to earth surface. First rotate about x-axis by
461+
# angle -axis_tilt so that y-axis is also parallel to earth surface, then
462+
# project.
526463

527464
# Calculate standard rotation matrix
528465
rot_x = np.array([[1, 0, 0],
529466
[0, cosd(-axis_tilt), -sind(-axis_tilt)],
530467
[0, sind(-axis_tilt), cosd(-axis_tilt)]])
531468

532-
# panel_norm_earth contains the normal vector
533-
# expressed in earth-surface coordinates
534-
# (z normal to surface, y aligned with tracker axis parallel to earth)
469+
# panel_norm_earth contains the normal vector expressed in earth-surface
470+
# coordinates (z normal to surface, y aligned with tracker axis parallel to
471+
# earth)
535472
panel_norm_earth = np.dot(rot_x, panel_norm).T
536473

537-
# projection to plane tangent to earth surface,
538-
# in earth surface coordinates
474+
# projection to plane tangent to earth surface, in earth surface
475+
# coordinates
539476
projected_normal = np.array([panel_norm_earth[:, 0],
540477
panel_norm_earth[:, 1],
541478
panel_norm_earth[:, 2]*0]).T
542479

543480
# calculate vector magnitudes
544481
projected_normal_mag = np.sqrt(np.nansum(projected_normal**2, axis=1))
545482

546-
# renormalize the projected vector
547-
# avoid creating nan values.
483+
# renormalize the projected vector, avoid creating nan values.
548484
non_zeros = projected_normal_mag != 0
549485
projected_normal[non_zeros] = (projected_normal[non_zeros].T /
550486
projected_normal_mag[non_zeros]).T
551487

552488
# calculation of surface_azimuth
553-
# 1. Find the angle.
554-
# surface_azimuth = pd.Series(
555-
# np.degrees(np.arctan(projected_normal[:,1]/projected_normal[:,0])),
556-
# index=times)
557489
surface_azimuth = \
558490
np.degrees(np.arctan2(projected_normal[:, 1], projected_normal[:, 0]))
559491

560-
# 2. Clean up atan when x-coord or y-coord is zero
561-
# surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]>0)] = 90
562-
# surface_azimuth[(projected_normal[:,0]==0) & (projected_normal[:,1]<0)] = -90
563-
# surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]>0)] = 0
564-
# surface_azimuth[(projected_normal[:,1]==0) & (projected_normal[:,0]<0)] = 180
565-
566-
# 3. Correct atan for QII and QIII
567-
# surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]>0)] += 180 # QII
568-
# surface_azimuth[(projected_normal[:,0]<0) & (projected_normal[:,1]<0)] += 180 # QIII
569-
570-
# 4. Skip to below
571-
572-
# at this point surface_azimuth contains angles between -90 and +270,
573-
# where 0 is along the positive x-axis,
574-
# the y-axis is in the direction of the tracker azimuth,
575-
# and positive angles are rotations from the positive x axis towards
576-
# the positive y-axis.
577-
# Adjust to compass angles
578-
# (clockwise rotation from 0 along the positive y-axis)
579-
# surface_azimuth[surface_azimuth<=90] = 90 - surface_azimuth[surface_azimuth<=90]
580-
# surface_azimuth[surface_azimuth>90] = 450 - surface_azimuth[surface_azimuth>90]
581-
582-
# finally rotate to align y-axis with true north
583-
# PVLIB_MATLAB has this latitude correction,
584-
# but I don't think it's latitude dependent if you always
585-
# specify axis_azimuth with respect to North.
586-
# if latitude > 0 or True:
587-
# surface_azimuth = surface_azimuth - axis_azimuth
588-
# else:
589-
# surface_azimuth = surface_azimuth - axis_azimuth - 180
590-
# surface_azimuth[surface_azimuth<0] = 360 + surface_azimuth[surface_azimuth<0]
591-
592-
# the commented code above is mostly part of PVLIB_MATLAB.
593-
# My (wholmgren) take is that it can be done more simply.
594-
# Say that we're pointing along the postive x axis (likely west).
595-
# We just need to rotate 90 degrees to get from the x axis
596-
# to the y axis (likely south),
597-
# and then add the axis_azimuth to get back to North.
598-
# Anything left over is the azimuth that we want,
599-
# and we can map it into the [0,360) domain.
600-
601-
# 4. Rotate 0 reference from panel's x axis to it's y axis and
602-
# then back to North.
492+
# Rotate 0 reference from panel's x-axis to its y-axis and then back to
493+
# north.
603494
surface_azimuth = 90 - surface_azimuth + axis_azimuth
604495

605-
# 5. Map azimuth into [0,360) domain.
606-
# surface_azimuth[surface_azimuth < 0] += 360
607-
# surface_azimuth[surface_azimuth >= 360] -= 360
496+
# Map azimuth into [0,360) domain.
608497
with np.errstate(invalid='ignore'):
609498
surface_azimuth = surface_azimuth % 360
610499

0 commit comments

Comments
 (0)