Help with Tracking Misalignment for One Axis

107 views
Skip to first unread message

P. C.

unread,
Nov 29, 2024, 5:39:38 AM11/29/24
to pvlib-python
Hi everyone,

As a former developer of thermal collectors and optical performance calculations, I have recently transitioned to PV systems. After some basic learning about PVlib with Python, I am trying to calculate the performance of a tracker with an offset misalignment in the tracking angle.

However, I am unsure about these results, as they seem counterintuitive to me.

When I create a single axis tracker and simulate a true tracker, I calculate a corresponding POA for a true tracker system  (backtrack=False).

If I add angular offset  will I have a tracker misalignment to the normal irradiance orientation?
    tracker_with_offset_error = pvlib.tracking.singleaxis(
        apparent_zenith=solar_position['apparent_zenith'],  # Correct parameter name
        apparent_azimuth=solar_position['azimuth'],  # Correct parameter name
        axis_tilt=tracker_tilt,
        axis_azimuth=tracker_azimuth,
        max_angle=max_angle,
        backtrack=False,
        gcr=0.4,
    )
    # Add tracking error to the computed surface tilt for true tracker
    tracker_with_offset_error['surface_tilt'] = tracker_with_offset_error['surface_tilt'] + error
    error_poa = pvlib.irradiance.get_total_irradiance(
        surface_tilt=tracker_with_offset_error['surface_tilt'],
        surface_azimuth=tracker_with_offset_error['surface_azimuth'],
        dni=dni,
        ghi=ghi,
        dhi=dhi,
        solar_zenith=solar_position['apparent_zenith'],
        solar_azimuth=solar_position['azimuth'],
    )

Running this as a loop between +-25 degrees offset.

I have better performance in the morning and  afternoon than a true tracker around +12 degrees offset. Why this behaviour? The end result  over a full year provide better  performance with 12 degrees offset than true trackers. 

I will attach my code  and some graphs.

Figure_1_Change in Total Irradiance with Tracking Error (backtrack=False).png
Figure_2_Total Irradiance POA (backtrack=False).png
Figure_3_Total Irradiance for cases of tracking error (backtrack=False).png
Figure_4_Irradiance variations [%] for cases of tracking error (backtrack=False).png

Thank you for your assistance!

tracking error.py

Will Hobbs

unread,
Nov 29, 2024, 9:32:46 AM11/29/24
to P. C., pvlib-python
I’m just on my mobile phone, so very well could be missing something, but one comment is that I don’t think this is giving a fixed offset in one direction. It is rotating the trackers down towards the ground, i.e., the offset is in different directions between morning and afternoon. 

Plots of ideal and offset surface tilt might help clarify/confirm. 

Other things to keep in mind are that row to row shade is not included here (assuming you are modeling a site with multiple rows) and your offset allows the trackers to rotate beyond their max rotation angle. That second one could be part (or all?) of the source of the unexpected improvement you are seeing. 

Will

--
You received this message because you are subscribed to the Google Groups "pvlib-python" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pvlib-python...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/pvlib-python/42784326-09f5-4879-98f2-cfd706ed8b09n%40googlegroups.com.

Saurabh Aneja

unread,
Nov 30, 2024, 1:29:59 PM11/30/24
to pvlib-python
I think Will is correct. Ignoring the row shade issue, adding a fixed value to the surface tilt will affect the vertical profile of the tracker curve curve, whereas I think you want to give the offset to the time-axis (x). Instead of:

tracker_with_offset_error['surface_tilt'] = tracker_with_offset_error['surface_tilt'] + error

perhaps the following may work:

tracker_with_offset_error['surface_tilt'] = tracker_with_offset_error['surface_tilt'].shift(error)

But keep in mind, simply shifting the surface_azimuth may result in some errors around solar noon.

kevina...@gmail.com

unread,
Dec 2, 2024, 9:58:45 AM12/2/24
to pvlib-python
If the error is applied to the rotation angle, you re-calculate the corresponding surface_tilt and surface_azimuth values using this function:


Kevin

cwh...@sandia.gov

unread,
Dec 2, 2024, 10:30:18 AM12/2/24
to pvlib-python
The "tracking" error can't be added to 'surface_tilt' to simulate a tracker with an error in its rotation. 'surface_tilt' is the angle from horizontal and it's _always_ positive. 'surface_azimuth' tells you the direction that the surface faces. Look at the content of 'tracker_with_offset_error' in the case of error=24 and you'll see what I'm describing. For a tracker with 'axis_tilt'=0 and 'axis_azimuth'=180, 'surface_azimuth' is either 90 or 270, but by changing 'surface_tilt' and not also 'surface_azimuth' the tilt and azimuth values are no longer in sync. Drawing a picture of a few configurations before and after noon should illustrate how the 'tilt' and 'azimuth' have to work together.

As Kevin suggests, the error should be added to the rotation angle and then tilt and azimuth should be recomputed.

Cliff

P. C.

unread,
Dec 3, 2024, 4:24:39 AM12/3/24
to pvlib-python
Hi everyone,

Thank you all for your valuable opinions and suggestions on my question about tracking misalignment. First specify that my intended calculation does not imply a plant field, nor a relationship with other nearby trackers, it would be a calculation for an isolated tracker for reference only, from POA with a tracking error.

I have made the following changes, which I believe are the answer to my plan, although there may be a more correct way to use the solutions provided by PvLib.
Studying the nuances of how displacement should be applied and the importance of recalculating both surface tilt and azimuth. I have included the offset sum within the parameters of the tracking.singleaxis function.

```python
# .\pvlib\Lib\site-packages\pvlib\tracking.py

# original
def singleaxis_offset(apparent_zenith, apparent_azimuth,
               axis_tilt=0, axis_azimuth=0, max_angle=90,
               backtrack=True, gcr=2.0/7.0, cross_axis_tilt=0,
               ):
# custom
def singleaxis_offset(apparent_zenith, apparent_azimuth,
               axis_tilt=0, axis_azimuth=0, max_angle=90,
               backtrack=True, gcr=2.0/7.0, cross_axis_tilt=0,
               offset=0):

# ... Line 131
# This offset is aplied as delay-advance for 'wid' before of the surface tilt and azimuth calculation
    # The ideal tracking angle wid is the rotation to place the sun position
    # vector (xp, yp, zp) in the (x, z) plane, which is normal to the panel and
    # contains the axis of rotation.  wid = 0 indicates that the panel is
    # horizontal. Here, our convention is that a clockwise rotation is
    # positive, to view rotation angles in the same frame of reference as
    # azimuth. For example, for a system with tracking axis oriented south, a
    # rotation toward the east is negative, and a rotation to the west is
    # positive. This is a right-handed rotation around the tracker y-axis.
    wid = shading.projected_solar_zenith_angle(
        axis_tilt=axis_tilt,
        axis_azimuth=axis_azimuth,
        solar_zenith=apparent_zenith,
        solar_azimuth=apparent_azimuth,
    ) + offset
    # Add the offset that means a bad pointing, advance or delay in front of the sun.
    # modifying tracking ideal angle,

```
As a result I have a behavior in the tracker position that is as expected,
by adjusting
max_angle = 55  # Maximum tilt angle (degrees) for -55º/+55º of tracking range

example for summer day, tracking error act as delay/advance of tracker respect to solar position

Surface Tilt overtime.png



Tracker theta overtime.PNG
This day POA performance is relatively the same in the morning and afternoon with losses according to delay/advance of tracking position relative to the sun.
Change in Total Irradiance with Tracking Error .png



And for a full year calculation, a performance comparison True Tracker provides the best case scenario and minimal losses at initial steps of delay errors.
Total Irradiance POA cases of tracking error.png
POA Irradiance variations [%] for cases of tracking error.png

I look forward to your comments and considerations on whether these modifications lead to bugs or misuse of Pvlib on my part.

The code and the modified single axis module are attached.


Best regards,  
P. C.

tracking_offset.py
tracking error with offset.py

cwh...@sandia.gov

unread,
Dec 3, 2024, 9:59:02 AM12/3/24
to pvlib-python
P. C.,

Looks OK to me. Backtracking could also be considered if the line that adds the offset was moved down:

else: tracker_theta = wid

# now add error
tracker_theta += offset

Adding the offset before the backtracking adjustment means that the backtracking would "know" the tracker was misaligned.

Cliff

P. C.

unread,
Oct 6, 2025, 6:06:06 AM (9 days ago) Oct 6
to pvlib-python
Dear PVlib Community,

I wanted to follow up on our previous discussion about tracking misalignment and share the latest findings from my comprehensive study at work 
"Comprehensive Performance Evaluation of Single-Axis Solar Tracking Systems: Tracking Error Analysis and Comparison with Fixed-Tilt Systems."

Methodology

Building on our previous exchange, I implemented a robust methodology to properly simulate tracking errors:

1. Correct Error Implementation: Rather than simply adding offsets to surface tilt (which created inconsistencies), I modified the tracking angle calculation directly by applying the offset to the tracker theta value before calculating surface orientation parameters.

2. Simulation Framework: Using pvlib-python with clear-sky modeling (Ineichen) for Seville, Spain (37.58°N, 6.03°W), I systematically evaluated tracking errors from ±1° to ±25°.

3.Validation: Results were validated against professional PVsyst V8.0.15 simulations using real meteorological data, confirming the reliability of our approach.

4. GHI-Based Analysis: I developed a novel approach to analyze error sensitivity across different irradiance conditions by grouping data into 200 W/m² GHI ranges.

Key Findings

1. Robust Performance: Single axis tracking systems demonstrate remarkable tolerance to tracking errors:
  -Errors ±1-3°: Negligible losses (<0.1%)
  -Error ±5°: Only 0.31% loss vs. ideal tracking
  -Error ±10°: 1.47% loss
  -Error ±20°: 5.09% loss


2. Tracking vs. Fixed Comparison: Even with significant errors, tracking maintains substantial advantage:
  -Ideal tracking: +14.20% gain over fixed-tilt (validated with PVsyst showing +16.52%)
  -With ±5° error: Still maintains +13.80% gain
  -With ±20° error: Still maintains +8.38% gain

3.Irradiance-Dependent Sensitivity: Maximum relative sensitivity occurs in moderate irradiance conditions (200-400 W/m²) with 5.72% loss for ±20° error, while absolute energy losses peak during high-irradiance periods.

4.Technology Comparison: Flat-plate PV systems can tolerate errors approximately 10× larger than CPV systems for equivalent percentage losses, making them significantly more robust.


5. Economic Analysis: Biannual calibration targeting a precision of ±5° yields an optimal return on investment (ROI > 250%). Tracking remains economically viable even with errors up to ±20°. These figures do not include consolidated data from operations and maintenance (O&M) services.  


Practical Recommendations

- Target Precision: ±2° to ±5°
- Calibration Frequency: Annual or biannual
- Monitoring Thresholds: Alert if error exceeds ±8-10°
- Timing: Calibrate before winter (higher seasonal sensitivity)

This research provides quantitative thresholds for operational decision-making and demonstrates the robustness of single-axis tracking investment for utility-scale solar installations.


Thank you all for your valuable input that helped shape this research. The attached full "pseudo paper" includes detailed analysis, economic optimization, and comparison with other technologies.

Best regards,
Pablo Climent

P. C.

unread,
Oct 6, 2025, 6:15:42 AM (9 days ago) Oct 6
to pvlib-python

P. C.

unread,
Oct 6, 2025, 6:38:26 AM (9 days ago) Oct 6
to pvlib-python
Additional approach of Implementing Tracking Errors in Single-Axis Solar Tracking Systems,

The initial approach attempted to simulate tracking errors by directly modifying the surface_tilt parameter, which creates geometric inconsistencies:
# INCORRECT APPROACH - Don't use this def simulate_tracking_with_error_incorrect(solar_position, error_degrees): # Calculate ideal tracker position tracker_ideal = pvlib.tracking.singleaxis( apparent_zenith=solar_position['apparent_zenith'], apparent_azimuth=solar_position['azimuth'], axis_tilt=0, axis_azimuth=180, # North-South axis max_angle=55, backtrack=False, gcr=0.4 ) # INCORRECT: Simply adding error to surface_tilt # This breaks the geometric relationship between tilt and azimuth tracker_with_error = tracker_ideal.copy() tracker_with_error['surface_tilt'] = tracker_ideal['surface_tilt'] + error_degrees # Calculate POA irradiance with incorrect geometry poa_irradiance = pvlib.irradiance.get_total_irradiance( surface_tilt=tracker_with_error['surface_tilt'], surface_azimuth=tracker_with_error['surface_azimuth'], # Using original azimuth! dni=dni, ghi=ghi, dhi=dhi, solar_zenith=solar_position['apparent_zenith'], solar_azimuth=solar_position['azimuth'] ) return poa_irradiance

This approach is problematic because:
  1.It modifies only the tilt angle without updating the azimuth angle
  2.It breaks the geometric relationship between tilt and azimuth
  3.It doesn't properly represent a consistent angular offset in the tracker's rotation


Correct Implementation
The correct approach applies the error to the tracker rotation angle (tracker_theta) and then recalculates both surface tilt and azimuth to maintain proper geometry:
def simulate_tracking_with_error_correct(solar_position, error_degrees): # Calculate projected solar zenith angle projected_zenith = pvlib.tracking.projected_solar_zenith_angle( axis_tilt=0, axis_azimuth=180, # North-South axis solar_zenith=solar_position['apparent_zenith'], solar_azimuth=solar_position['azimuth'] ) # Calculate tracker rotation angle (theta) with error applied tracker_theta = projected_zenith + error_degrees # Apply max angle limits (optional) max_angle = 55 tracker_theta = np.clip(tracker_theta, -max_angle, max_angle) # Calculate surface orientation parameters from the modified theta # This maintains the correct geometric relationship between tilt and azimuth surface_orientation = pvlib.tracking.calc_surface_orientation( tracker_theta=tracker_theta, axis_tilt=0, axis_azimuth=180, cross_axis_tilt=0 ) # Extract the correctly calculated surface tilt and azimuth surface_tilt = surface_orientation['surface_tilt'] surface_azimuth = surface_orientation['surface_azimuth'] # Calculate POA irradiance with correct geometry poa_irradiance = pvlib.irradiance.get_total_irradiance( surface_tilt=surface_tilt, surface_azimuth=surface_azimuth, dni=dni, ghi=ghi, dhi=dhi, solar_zenith=solar_position['apparent_zenith'], solar_azimuth=solar_position['azimuth'] ) return poa_irradiance


Alternative Implementation (Modifying Pvlib singleaxis function) was suggested code implementation but closed,
Or modified version of the singleaxis function that accepts an error parameter:
def singleaxis_with_error(apparent_zenith, apparent_azimuth, axis_tilt=0, axis_azimuth=180, max_angle=55, backtrack=False, gcr=0.4, cross_axis_tilt=0, error_degrees=0): """ Modified singleaxis tracking function that includes tracking error. Parameters ---------- ... (standard singleaxis parameters) ... error_degrees : float, default 0 Angular error to apply to the tracker rotation angle (degrees). Positive values rotate the tracker clockwise (looking from above). Returns ------- DataFrame with columns 'tracker_theta', 'surface_tilt', 'surface_azimuth' """ # Calculate projected solar zenith angle projected_zenith = pvlib.tracking.projected_solar_zenith_angle( axis_tilt=axis_tilt, axis_azimuth=axis_azimuth, solar_zenith=apparent_zenith, solar_azimuth=apparent_azimuth ) # Calculate tracker rotation angle (theta) tracker_theta = projected_zenith # Apply backtracking if enabled if backtrack: # Apply backtracking algorithm tracker_theta = pvlib.tracking.backtrack( tracker_theta, gcr, max_angle ) # Apply error AFTER backtracking but BEFORE max angle limits # This simulates a miscalibrated tracker that still respects its physical limits tracker_theta = tracker_theta + error_degrees # Apply max angle limits tracker_theta = np.clip(tracker_theta, -max_angle, max_angle) # Calculate surface orientation from the modified theta surface_orientation = pvlib.tracking.calc_surface_orientation( tracker_theta=tracker_theta, axis_tilt=axis_tilt, axis_azimuth=axis_azimuth, cross_axis_tilt=cross_axis_tilt ) # Combine results result = pd.DataFrame({ 'tracker_theta': tracker_theta, 'surface_tilt': surface_orientation['surface_tilt'], 'surface_azimuth': surface_orientation['surface_azimuth'] }) return result


Key Insights
1.Geometric Consistency: Tracking error must be applied to the rotation angle (tracker_theta) and then both surface_tilt and surface_azimuth must be recalculated together to maintain the correct geometric relationship.

2.Error Application Timing: When using backtracking, the error should be applied after the backtracking calculation but before applying maximum angle limits, to simulate a miscalibrated tracker that still respects its physical limits.

3.Validation: This approach was validated in our study and produced results consistent with professional simulation tools like PVsyst.

Conclusion

The correct implementation of tracking errors is essential for accurate performance evaluation of single-axis tracking systems. By applying errors to the tracker rotation angle rather than directly to the surface tilt, we maintain geometric consistency and properly simulate the behavior of a miscalibrated tracker.
Reply all
Reply to author
Forward
0 new messages