Skip to content

Commit

Permalink
Merge pull request #1071 from NREL/user-tilt-angles
Browse files Browse the repository at this point in the history
Allow for custom tracker rotation angles timeseries input for subarrays
  • Loading branch information
mjprilliman authored Oct 27, 2023
2 parents f3672b5 + 5018ac2 commit 6c9fda0
Show file tree
Hide file tree
Showing 12 changed files with 17,701 additions and 40 deletions.
28 changes: 21 additions & 7 deletions shared/lib_irradproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ solarpos_spa(int year, int month, int day, int hour, double minute, double secon

void incidence(int mode, double tilt, double sazm, double rlim, double zen,
double azm, bool en_backtrack, double gcr, double slope_tilt, double slope_azm,
bool force_to_stow, double stow_angle_deg, double angle[5]) {
bool force_to_stow, double stow_angle_deg, bool useCustomAngle, double customAngle, double angle[5]) {
/*
Calculate panel orientation, angle of incidence with beam radiation, and
tracker rotation angles (where applicable).
Expand Down Expand Up @@ -1224,6 +1224,11 @@ void incidence(int mode, double tilt, double sazm, double rlim, double zen,
rot = backtracking_rotation;
}

/*Check if custom tracker rotation angles enabled, apply timeseries value*/
if (useCustomAngle) {
rot = customAngle * DTOR; //overwrite rotation angle with input from array
}

/* Find tilt angle for the tracking surface */
arg = cos(xtilt) * cos(rot);
if (arg < -1.0)
Expand Down Expand Up @@ -1751,6 +1756,7 @@ void irrad::setup() {
poaRearDirectDiffuse = 0.;
poaRearRowReflections = 0.;
poaRearSelfShaded = 0.;
useCustomRotAngles = 0.;

}

Expand All @@ -1766,7 +1772,7 @@ irrad::irrad(weather_record wf, weather_header hdr,
double groundCoverageRatioIn, double slopeTiltIn, double slopeAzmIn, std::vector<double> monthlyTiltDegrees,
std::vector<double> userSpecifiedAlbedo,
poaDecompReq *poaAllIn,
bool useSpatialAlbedos, const util::matrix_t<double>* userSpecifiedSpatialAlbedos, bool enableSubhourlyClipping) :
bool useSpatialAlbedos, const util::matrix_t<double>* userSpecifiedSpatialAlbedos, bool enableSubhourlyClipping, bool useCustomRotAngles, double customRotAngle) :
skyModel(skyModelIn), radiationMode(radiationModeIn), trackingMode(trackModeIn),
enableBacktrack(backtrackingEnabled), forceToStow(forceToStowIn),
delt(dtHour), tiltDegrees(tiltDegreesIn), surfaceAzimuthDegrees(azimuthDegreesIn),
Expand Down Expand Up @@ -1795,6 +1801,8 @@ irrad::irrad(weather_record wf, weather_header hdr,

set_subhourly_clipping(enableSubhourlyClipping);

set_custom_rot_angles(useCustomRotAngles, customRotAngle);

if (radiationMode == irrad::DN_DF) set_beam_diffuse(wf.dn, wf.df);
else if (radiationMode == irrad::DN_GH) set_global_beam(wf.gh, wf.dn);
else if (radiationMode == irrad::GH_DF) set_global_diffuse(wf.gh, wf.df);
Expand Down Expand Up @@ -1988,6 +1996,12 @@ void irrad::set_subhourly_clipping(bool enable)
if (enable) this->enableSubhourlyClipping = true;
}

void irrad::set_custom_rot_angles(bool enable, double angle)
{
this->useCustomRotAngles = enable;
this->customRotAngle = angle;
}

void irrad::set_sky_model(int sm, double alb, const std::vector<double> &albSpatial) {
this->skyModel = sm;
this->albedo = alb;
Expand Down Expand Up @@ -2176,7 +2190,7 @@ int irrad::calc() {
// compute incidence angles onto fixed or tracking surface
incidence(trackingMode, tiltDegrees, surfaceAzimuthDegrees, rotationLimitDegrees, sunAnglesRadians[1],
sunAnglesRadians[0],
enableBacktrack, groundCoverageRatio, slopeTilt, slopeAzm, forceToStow, stowAngleDegrees, surfaceAnglesRadians);
enableBacktrack, groundCoverageRatio, slopeTilt, slopeAzm, forceToStow, stowAngleDegrees, useCustomRotAngles, customRotAngle, surfaceAnglesRadians);
if (radiationMode < irrad::POA_R) {
double hextra = sunAnglesRadians[8];
double hbeam = directNormal *
Expand Down Expand Up @@ -2576,7 +2590,7 @@ void irrad::getFrontSurfaceIrradiances(double pvFrontShadeFraction, double rowTo
// Calculate irradiance components for a 90 degree tilt to get horizon brightening
double angleTmp[5] = {0, 0, 0, 0, 0}; // ([0] = incidence angle, [1] = tilt)
incidence(0, 90.0, 180.0, 45.0, solarZenithRadians, solarAzimuthRadians, this->enableBacktrack,
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, angleTmp);
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, this->useCustomRotAngles, this->customRotAngle, angleTmp);
perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, angleTmp[0], angleTmp[1], solarZenithRadians,
poa, diffc);
double horizonDiffuse = diffc[2];
Expand Down Expand Up @@ -2712,7 +2726,7 @@ void irrad::getFrontSurfaceIrradiances(double pvFrontShadeFraction, double rowTo
// Calculate and add direct and circumsolar irradiance components
incidence(0, tiltRadians * RTOD, surfaceAzimuthRadians * RTOD, 45.0, solarZenithRadians, solarAzimuthRadians,
this->enableBacktrack, this->groundCoverageRatio, this->slopeTilt, this->slopeAzm,
this->forceToStow, this->stowAngleDegrees, surfaceAnglesRadians);
this->forceToStow, this->stowAngleDegrees, this->useCustomRotAngles, this->customRotAngle, surfaceAnglesRadians);
perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians[0],
surfaceAnglesRadians[1], solarZenithRadians, poa, diffc);

Expand Down Expand Up @@ -2757,7 +2771,7 @@ void irrad::getBackSurfaceIrradiances(double pvBackShadeFraction, double rowToRo
// Calculate components for a 90 degree tilt to get horizon brightening
double surfaceAnglesRadians90[5] = {0, 0, 0, 0, 0};
incidence(0, 90.0, 180.0, 45.0, solarZenithRadians, solarAzimuthRadians, this->enableBacktrack,
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, surfaceAnglesRadians90);
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, this->useCustomRotAngles, this->customRotAngle, surfaceAnglesRadians90);
perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians90[0],
surfaceAnglesRadians90[1], solarZenithRadians, planeOfArrayIrradianceRear, diffuseIrradianceRear);
double horizonDiffuse = diffuseIrradianceRear[2];
Expand Down Expand Up @@ -2957,7 +2971,7 @@ void irrad::getBackSurfaceIrradiances(double pvBackShadeFraction, double rowToRo
// Calculate and add direct and circumsolar irradiance components
incidence(0, 180.0 - tiltRadians * RTOD, (surfaceAzimuthRadians * RTOD - 180.0), 45.0, solarZenithRadians,
solarAzimuthRadians, this->enableBacktrack,
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, surfaceAnglesRadians);
this->groundCoverageRatio, this->slopeTilt, this->slopeAzm, this->forceToStow, this->stowAngleDegrees, this->useCustomRotAngles, this->customRotAngle, surfaceAnglesRadians);
perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians[0],
surfaceAnglesRadians[1], solarZenithRadians, planeOfArrayIrradianceRear, diffuseIrradianceRear);

Expand Down
12 changes: 10 additions & 2 deletions shared/lib_irradproc.h
Original file line number Diff line number Diff line change
Expand Up @@ -724,14 +724,16 @@ void solarpos_spa(int year, int month, int day, int hour, double minute, double
* \param[in] slope_azm azimuth angle of slopted terrain relative to tracker azimuth in radians
* \param[in] force_to_stow: force the single-axis tracking array to the stow angle specified in the next input
* \param[in] stow_angle_deg: the angle to force the single-axis tracking array to stow to, in degrees
* \param[in] useCustomAngle: use custom rotation angles for single axis tracking 0/1
* \param[in] customAngle: custom rotation angle to use, in degrees
* \param[out] angle array of elements to return angles to calling function
* \param[out] angle[0] incident angle in radians
* \param[out] angle[1] tilt angle of surface from horizontal in radians
* \param[out] angle[2] surface azimuth in radians, measured east from north
* \param[out] angle[3] tracking axis rotation angle in radians, measured from surface normal of unrotating axis (only for 1 axis trackers)
* \param[out] angle[4] backtracking difference (rot - ideal_rot) will be zero except in case of backtracking for 1 axis tracking
*/
void incidence(int mode, double tilt, double sazm, double rlim, double zen, double azm, bool en_backtrack, double gcr, double slope_tilt, double slope_azm, bool force_to_stow, double stow_angle_deg, double angle[5]);
void incidence(int mode, double tilt, double sazm, double rlim, double zen, double azm, bool en_backtrack, double gcr, double slope_tilt, double slope_azm, bool force_to_stow, double stow_angle_deg, bool useCustomAngle, double customAngle, double angle[5]);


/**
Expand Down Expand Up @@ -991,6 +993,10 @@ class irrad
//Enable subhourly clipping correction
bool enableSubhourlyClipping;

//Custom rotation angles for single-axis trackers
bool useCustomRotAngles;
double customRotAngle; // custom tracker rotation angle in degrees

// Subarray properties
double tiltDegrees; ///< Surface tilt of subarray in degrees
double surfaceAzimuthDegrees; ///< Surface azimuth of subarray in degrees
Expand Down Expand Up @@ -1055,7 +1061,7 @@ class irrad
double dtHour, double tiltDegrees, double azimuthDegrees, double trackerRotationLimitDegrees, double stowAngleDegreesIn,
double groundCoverageRatio, double slopeTilt, double slopeAzm, std::vector<double> monthlyTiltDegrees, std::vector<double> userSpecifiedAlbedo,
poaDecompReq* poaAllIn,
bool useSpatialAlbedos = false, const util::matrix_t<double>* userSpecifiedSpatialAlbedos = nullptr, bool enableSubhourlyClipping = false);
bool useSpatialAlbedos = false, const util::matrix_t<double>* userSpecifiedSpatialAlbedos = nullptr, bool enableSubhourlyClipping = false, bool useCustomRotAngles = false, double customRotAngle = 0);

/// Construct the irrad class with an Irradiance_IO() object and Subarray_IO() object
irrad();
Expand All @@ -1078,6 +1084,8 @@ class irrad
//Set whether to use subhourly clipping model
void set_subhourly_clipping(bool enable = false);

void set_custom_rot_angles(bool enable = false, double angle = 0);

/// Set the sky model for the irradiance processor, using \link Irradiance_IO::SKYMODEL
void set_sky_model(int skymodel, double albedo, const std::vector<double> &albedoSpatial = std::vector<double>());

Expand Down
33 changes: 32 additions & 1 deletion shared/lib_pv_io_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ Subarray_IO::Subarray_IO(compute_module* cm, const std::string& cmName, size_t s
nModulesPerString = cm->as_integer(prefix + "modules_per_string");
mpptInput = cm->as_integer(prefix + "mppt_input");
trackMode = cm->as_integer(prefix + "track_mode");
useCustomRotAngles = cm->as_boolean(prefix + "use_custom_rot_angles");
useCustomCellTemp = cm->as_boolean(prefix + "use_custom_cell_temp");
tiltEqualLatitude = 0;
if (cm->is_assigned(prefix + "tilt_eq_lat")) tiltEqualLatitude = cm->as_boolean(prefix + "tilt_eq_lat");

Expand All @@ -376,6 +378,35 @@ Subarray_IO::Subarray_IO(compute_module* cm, const std::string& cmName, size_t s
if (monthlyTiltDegrees[i] < 0.0) throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + " monthly tilt angles cannot be negative.");
}
}

/* Insert checks for custom tracker rotation angles here*/
if (useCustomRotAngles == 1) {
if (cm->is_assigned(prefix + "custom_rot_angles_array")) {
customRotAngles = cm->as_vector_double(prefix + "custom_rot_angles_array");
for (int i = 0; i < customRotAngles.size(); i++) {
if (customRotAngles[i] > 90.0 || customRotAngles[i] < -90.0) throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + " custom tracker rotation angles must be between -90 and 90 degrees.");
}
}
else {
throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + " custom tracker rotation angles required but not assigned.");
}
}



/* Insert checks for using custom cell temperature array*/
if (useCustomCellTemp == 1) {
if (cm->is_assigned(prefix + "custom_cell_temp_array")) {
customCellTempArray = cm->as_vector_double(prefix + "custom_cell_temp_array");
for (int i = 0; i < customCellTempArray.size(); i++) {
if (customCellTempArray[i] > 100.0) throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + " custom cell temperature cannot be greater than 100 degrees Celsius.");
}
}
else {
throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + " custom cell temperatures required but not assigned.");
}
}

//azimuth required for fixed tilt, single axis, and seasonal tilt- can't check for this in variable table so check here
azimuthDegrees = std::numeric_limits<double>::quiet_NaN();
if (trackMode == irrad::FIXED_TILT || trackMode == irrad::SINGLE_AXIS || trackMode == irrad::SEASONAL_TILT)
Expand Down Expand Up @@ -642,7 +673,7 @@ void PVSystem_IO::SetupPOAInput()


if (tms[2] > 0) {
incidence(Subarrays[nn]->trackMode, Subarrays[nn]->tiltDegrees, Subarrays[nn]->azimuthDegrees, Subarrays[nn]->trackerRotationLimitDegrees, sun[1], sun[0], Subarrays[nn]->backtrackingEnabled, Subarrays[nn]->groundCoverageRatio, Subarrays[nn]->slopeTilt, Subarrays[nn]->slopeAzm, false, 0.0, angle);
incidence(Subarrays[nn]->trackMode, Subarrays[nn]->tiltDegrees, Subarrays[nn]->azimuthDegrees, Subarrays[nn]->trackerRotationLimitDegrees, sun[1], sun[0], Subarrays[nn]->backtrackingEnabled, Subarrays[nn]->groundCoverageRatio, Subarrays[nn]->slopeTilt, Subarrays[nn]->slopeAzm, false, 0.0, false, 0.0, angle);
}
else {
angle[0] = -999;
Expand Down
4 changes: 4 additions & 0 deletions shared/lib_pv_io_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ struct Subarray_IO
double slopeTilt; // Angle of sloped terrain [degrees]
double slopeAzm; // azimuth of sloped terrain relative to tracker azimuth [degrees]
double tiltDegrees; // The surface tilt [degrees]
flag useCustomRotAngles; // Use custom timeseries rotation angles
std::vector<double> customRotAngles; //Custom timeseries rotation angles [degrees]
flag useCustomCellTemp;
std::vector<double> customCellTempArray;
double azimuthDegrees; // The surface azimuth [degrees]
int trackMode; // The tracking mode [0 = fixed, 1 = single-axis tracking, 2 = two-axis tracking, 3 = azimuth-axis tracking, 4 = seasonal-tilt
double trackerRotationLimitDegrees; // The rotational limit of the tracker [degrees]
Expand Down
2 changes: 1 addition & 1 deletion ssc/cmod_irradproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ static var_info _cm_vtab_irradproc[] = {
{ SSC_OUTPUT, SSC_ARRAY, "incidence", "Incidence angle to surface", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
{ SSC_OUTPUT, SSC_ARRAY, "surf_tilt", "Surface tilt angle", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
{ SSC_OUTPUT, SSC_ARRAY, "surf_azm", "Surface azimuth angle", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
{ SSC_OUTPUT, SSC_ARRAY, "axis_rotation", "Tracking axis rotation angle", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
{ SSC_OUTPUT, SSC_ARRAY, "axis_rotation", "Tracker rotation angle", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
{ SSC_OUTPUT, SSC_ARRAY, "bt_diff", "Backtracking difference from ideal rotation", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },

{ SSC_OUTPUT, SSC_ARRAY, "sun_azm", "Solar azimuth", "deg", "", "Irradiance Processor", "*", "LENGTH_EQUAL=beam", "" },
Expand Down
Loading

0 comments on commit 6c9fda0

Please sign in to comment.