How to simulate power with custom diffuse capture tracking?

Introduction

SolarFarmer can simulate generated power using user specified Custom Tracker Rotations, which is described in the SolarFarmer API/Scripting documentation in the Tracker Snippets section called Custom Tracker Rotations. This feature can be used for a diffuse capture tracking algorithm, that deviates from the standard tracking algorithm, to maximize energy production when there is sufficiently more diffuse than beam. Under normal circumstances, when the fraction of diffuse light is low, trackers rotate to minimize the angle of incidence between the solar vector and a normal to the module surface. While there is a standard closed form expression for tracking on horizontal ground or even for slope-aware backtracking, there is no accepted standard for diffuse capture. One could run an optimization routine at each time step to locate the tracker rotation that yields the maximum output, but that solution would be specific to the time, location, and PV system in question, and couldn’t be extrapolated to other sites. Therefore assume that we already have the tracker rotations for each timestep for a custom diffuse capture algorithm. This post demonstrates how to use these custom tracker rotations to model the output.

Method

The following steps describe how to load custom tracker rotations.

Site setup

The first step is to setup the site. Follow the normal SolarFarmer site setup. Set the site location, download terrain or set the plane, import weather, module PAN file, and inverter OND files, specify tracker configuration, and layout the site. Refer to the the SolarFarmer User Guide for more details.

Prepare tracker rotation files

The rotations for each tracker can be read by SolarFarmer from a CSV file. The format of this file is described in Section 2: Read and Use Tracker Rotations of the API/Scripting documentation:

Year,Month,Day,Hour,Minute,Second,Azimuth,Zenith,Tracker0,Tracker1,Tracker2,...

The Azimuth and Zenith are not required and can be set to zero. Also there should be as many tracker columns as there are trackers in the site layout. For a diffuse capture tracker algorithm, all of the trackers might be set to the same rotation. Create this file with the header above, and add rows for the tracker rotations for each timestep described by the first 6 columns. Each column after the 8th, represents a specific tracker. The actual column header is ignored, and the trackers are indexed sequentially starting at zero. Since these files can get quite large, you can split them into separate files. For example, with 5 minute data, it’s okay to have a file for each month, or 12 files total. Put all of the files in a separate folder, and record the folder’s path.

Note: Each tracker rotation should correspond to the same instant given by the timestamp. This differs from weather which may be given at the same instant, or the beginning or end of a timestamp. Read the SolarFarmer documentation on importing weather for more details.

Update tracker and module indices

The SolarFarmer tracker indices and corresponding module indices in the workbook need to match the files with the tracker rotations. Regardless of the headers for the tracker columns, they are indexed from zero sequentially. However, in the SolarFarmer workbook, the indices are random. One way to update the indices used is with the Python script below. To do this you must first save and exit SolarFarmer. The workbook is stored in the .sfw file, which you can extract as a .zip file. One of the extracted files is Project.xml which contains the random tracker and module indices we want to update. Change base_dir and prj_dir in the script to match the path to the extracted Project.xml file and then run the script to generate the new file with the updated indices to match the custom tracker rotation files you created earlier.

Note: This script requires beautifulsoup4 and lxml so use pip or conda to install them first.

import pathlib
from bs4 import BeautifulSoup
import logging

logging.basicConfig()
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)

# build the path to your project file
base_dir = pathlib.Path(__file__).parent  # replace this with your base path
prj_dir = base_dir / "my project folder"  # where ever you've extracted your SolarFarmer workbook
prj = prj_dir / "Project.xml"
prj_new = prj_dir / "Project_new.xml"
prj_ugly = prj_dir / "Project_ugly.xml"

# read in as binary
with prj.open('rb') as f:
    soup = BeautifulSoup(f, "lxml-xml")
LOGGER.debug('read: %s', str(prj))

# update the tracker indices to start at zero and go in order
tracker_instances = soup.find_all('TrackerInstances')
tracker_map = {}
tracker_list = []

x = 0
for row in tracker_instances:
    trackers = row.find_all('TrackerInstance')

    x0 = x
    LOGGER.debug('tracker row: %d', x0)

    for tr in trackers:
        tracker_list.append(tr.Id.text)
        tracker_map[tr.Id.text] = str(x)  # must be a string
        x += 1

    LOGGER.debug('number of trackers: %d', x-x0)

    for x1 in range(x-x0):
        x2 = x1 + x0
        trackers[x1].Id.string = str(x2)
        left_neighbor = trackers[x1].LeftNeighbourIndex.text
        if left_neighbor != '-1':
            trackers[x1].LeftNeighbourIndex.string = tracker_map[left_neighbor]
        right_neighbor = trackers[x1].RightNeighbourIndex.text
        if right_neighbor != '-1':
            trackers[x1].RightNeighbourIndex.string = tracker_map[right_neighbor]
        LOGGER.debug(
            'tracker: %d, id: %s, left: %s, right: %s',
            x2, tracker_list[x2], left_neighbor, right_neighbor)

# now update the module indices too
module_index = soup.find_all('ModuleIndex')
for midx in module_index:
    old_midx = midx.attrs['Rack']
    midx.attrs['Rack'] = tracker_map[old_midx]
    LOGGER.debug('module index: %s', old_midx)

with prj_ugly.open('wb') as f:
    f.write(b'\xef\xbb\xbf')  # add the BOM
    f.write(soup.encode('utf-8'))  # encode as utf-8
LOGGER.debug('save: %s', str(prj_ugly))

# replace newlines with CRLF
with prj_ugly.open('rb') as g:
    src = g.read().decode('utf-8')
src_crlf = src.replace('\n', '\r\n')
with prj_new.open('wb') as h:
    h.write(src_crlf.encode('utf-8'))
LOGGER.debug('save: %s', str(prj_new))

After running the script, delete the old Project.xml and replace it with the new one, renaming it to match the old Project.xml. Then zip up the contents of the SolarFarmer workbook using 7zip, and change the extension back to .sfw.

Add new tracker position algorithm script

The last step is described in detail in parts (a) - (e) in Section 2: Read and Use Tracker Rotations of the API/Scripting documentation. Also you may choose to disable time series output, as that can take up several gigabytes, and your simulation may hang if you run out of diskspace causing you to lose work.

And more

Testing out custom diffuse capture tracking algorithms is just one of the uses for the custom tracker rotation feature. Tell us what you’re using it for in the comments.

1 Like