diff --git a/shared/lib_irradproc.cpp b/shared/lib_irradproc.cpp index 353f4945a..35b635ee7 100644 --- a/shared/lib_irradproc.cpp +++ b/shared/lib_irradproc.cpp @@ -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). @@ -1225,6 +1225,9 @@ void incidence(int mode, double tilt, double sazm, double rlim, double zen, } /*Check if custom tilt 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); @@ -1768,7 +1771,7 @@ irrad::irrad(weather_record wf, weather_header hdr, double groundCoverageRatioIn, double slopeTiltIn, double slopeAzmIn, std::vector monthlyTiltDegrees, std::vector userSpecifiedAlbedo, poaDecompReq *poaAllIn, - bool useSpatialAlbedos, const util::matrix_t* userSpecifiedSpatialAlbedos, bool enableSubhourlyClipping, bool useCustomTiltAngles) : + bool useSpatialAlbedos, const util::matrix_t* userSpecifiedSpatialAlbedos, bool enableSubhourlyClipping, bool useCustomTiltAngles, double customTiltAngles) : skyModel(skyModelIn), radiationMode(radiationModeIn), trackingMode(trackModeIn), enableBacktrack(backtrackingEnabled), forceToStow(forceToStowIn), delt(dtHour), tiltDegrees(tiltDegreesIn), surfaceAzimuthDegrees(azimuthDegreesIn), @@ -1797,6 +1800,8 @@ irrad::irrad(weather_record wf, weather_header hdr, set_subhourly_clipping(enableSubhourlyClipping); + set_custom_tilt_angles(useCustomTiltAngles, customTiltAngle); + 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); @@ -1990,6 +1995,12 @@ void irrad::set_subhourly_clipping(bool enable) if (enable) this->enableSubhourlyClipping = true; } +void irrad::set_custom_tilt_angles(bool enable, double angle) +{ + this->useCustomTiltAngles = enable; + this->customTiltAngle = angle; +} + void irrad::set_sky_model(int sm, double alb, const std::vector &albSpatial) { this->skyModel = sm; this->albedo = alb; @@ -2178,7 +2189,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, useCustomTiltAngles, customTiltAngle, surfaceAnglesRadians); if (radiationMode < irrad::POA_R) { double hextra = sunAnglesRadians[8]; double hbeam = directNormal * @@ -2578,7 +2589,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->useCustomTiltAngles, this->customTiltAngle, angleTmp); perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, angleTmp[0], angleTmp[1], solarZenithRadians, poa, diffc); double horizonDiffuse = diffc[2]; @@ -2714,7 +2725,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->useCustomTiltAngles, this->customTiltAngle, surfaceAnglesRadians); perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians[0], surfaceAnglesRadians[1], solarZenithRadians, poa, diffc); @@ -2759,7 +2770,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->useCustomTiltAngles, this->customTiltAngle, surfaceAnglesRadians90); perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians90[0], surfaceAnglesRadians90[1], solarZenithRadians, planeOfArrayIrradianceRear, diffuseIrradianceRear); double horizonDiffuse = diffuseIrradianceRear[2]; @@ -2959,7 +2970,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->useCustomTiltAngles, this->customTiltAngle, surfaceAnglesRadians); perez(0, calculatedDirectNormal, calculatedDiffuseHorizontal, albedo, surfaceAnglesRadians[0], surfaceAnglesRadians[1], solarZenithRadians, planeOfArrayIrradianceRear, diffuseIrradianceRear); diff --git a/shared/lib_irradproc.h b/shared/lib_irradproc.h index 61ec9a122..8ba76fc4c 100644 --- a/shared/lib_irradproc.h +++ b/shared/lib_irradproc.h @@ -731,7 +731,7 @@ void solarpos_spa(int year, int month, int day, int hour, double minute, double * \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]); /** @@ -991,6 +991,10 @@ class irrad //Enable subhourly clipping correction bool enableSubhourlyClipping; + //Custom rotation angles for single-axis trackers + bool useCustomTiltAngles; + double customTiltAngle; // custom tracker rotation angle in degrees + // Subarray properties double tiltDegrees; ///< Surface tilt of subarray in degrees double surfaceAzimuthDegrees; ///< Surface azimuth of subarray in degrees @@ -1055,7 +1059,7 @@ class irrad double dtHour, double tiltDegrees, double azimuthDegrees, double trackerRotationLimitDegrees, double stowAngleDegreesIn, double groundCoverageRatio, double slopeTilt, double slopeAzm, std::vector monthlyTiltDegrees, std::vector userSpecifiedAlbedo, poaDecompReq* poaAllIn, - bool useSpatialAlbedos = false, const util::matrix_t* userSpecifiedSpatialAlbedos = nullptr, bool enableSubhourlyClipping = false, bool useCustomTiltAngles = false); + bool useSpatialAlbedos = false, const util::matrix_t* userSpecifiedSpatialAlbedos = nullptr, bool enableSubhourlyClipping = false, bool useCustomTiltAngles = false, double customTiltAngle = 0); /// Construct the irrad class with an Irradiance_IO() object and Subarray_IO() object irrad(); @@ -1078,6 +1082,8 @@ class irrad //Set whether to use subhourly clipping model void set_subhourly_clipping(bool enable = false); + void set_custom_tilt_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 &albedoSpatial = std::vector()); diff --git a/shared/lib_pv_io_manager.cpp b/shared/lib_pv_io_manager.cpp index 11d7d90b6..5d64bc30f 100644 --- a/shared/lib_pv_io_manager.cpp +++ b/shared/lib_pv_io_manager.cpp @@ -358,6 +358,7 @@ 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"); + useCustomTiltAngles = cm->as_integer("use_custom_tilt_angles"); tiltEqualLatitude = 0; if (cm->is_assigned(prefix + "tilt_eq_lat")) tiltEqualLatitude = cm->as_boolean(prefix + "tilt_eq_lat"); @@ -376,8 +377,14 @@ 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 tilt angles here*/ - + if (cm->is_assigned("custom_tilt_angles_array") && useCustomTiltAngles == 1) { + customTiltAngles = cm->as_vector_double("custom_tilt_angle_array"); + for (int i = 0; i < customTiltAngles.size(); i++) { + if (customTiltAngles[i] < 0.0) throw exec_error(cmName, "Subarray " + util::to_string((int)subarrayNumber) + "custom tilt angles cannot be negative."); + } + } //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::quiet_NaN(); if (trackMode == irrad::FIXED_TILT || trackMode == irrad::SINGLE_AXIS || trackMode == irrad::SEASONAL_TILT) diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index a3ad405ef..e5c788a4f 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -1126,9 +1126,9 @@ void cm_pvsamv1::exec() std::vector user_tilt_angles; user_tilt_angles.reserve(nrec); size_t user_tilt_angles_size; - int use_user_tilt_angles = (as_integer("use_user_tilt_angles") == 1 && is_assigned("user_tilt_angles_array")); - if (as_integer("use_user_tilt_angles") == 1 && is_assigned("user_tilt_angles_array")) { - user_tilt_angles = as_vector_ssc_number_t("user_tilt_angles_array"); + int use_custom_tilt_angles = (as_integer("use_custom_tilt_angles") == 1 && is_assigned("custom_tilt_angles_array")); + if (as_integer("use_custom_tilt_angles") == 1 && is_assigned("custom_tilt_angles_array")) { + user_tilt_angles = as_vector_ssc_number_t("custom_tilt_angles_array"); user_tilt_angles_size = user_tilt_angles.size(); if (user_tilt_angles_size != nrec) throw exec_error("pvsamv1", "The measured temperature array must be the size of nrecords per year"); @@ -1138,7 +1138,7 @@ void cm_pvsamv1::exec() //also check here for tilt > 0 for tracking systems, since this is a very uncommon configuration but an easy mistake to make for (size_t nn = 0; nn < num_subarrays; nn++) { - if (as_integer("use_user_tilt_angles") == 1 && is_assigned("user_tilt_angles_array")) { + if (as_integer("use_custom_tilt_angles") == 1 && is_assigned("custom_tilt_angles_array")) { Subarrays[nn]->trackMode = irrad::SINGLE_AXIS; //Subarrays[nn]->tiltDegrees = 0; //reset to 0 to then be replaced in loop? Subarrays[nn]->backtrackingEnabled = false; //account for backtracking in user-specified angles [deg] @@ -1507,7 +1507,7 @@ void cm_pvsamv1::exec() continue; // skip disabled subarrays /* - if (as_integer("use_user_tilt_angles") == 1 && is_assigned("user_tilt_angles_array")) { + if (as_integer("use_custom_tilt_angles") == 1 && is_assigned("custom_tilt_angles_array")) { Subarrays[nn]->tiltDegrees = user_tilt_angles[inrec]; }*/ @@ -1518,7 +1518,7 @@ void cm_pvsamv1::exec() Irradiance->dtHour, Subarrays[nn]->tiltDegrees, Subarrays[nn]->azimuthDegrees, Subarrays[nn]->trackerRotationLimitDegrees, 0.0, Subarrays[nn]->groundCoverageRatio, Subarrays[nn]->slopeTilt, Subarrays[nn]->slopeAzm, Subarrays[nn]->monthlyTiltDegrees, Irradiance->userSpecifiedMonthlyAlbedo, Subarrays[nn]->poa.poaAll.get(), - Irradiance->useSpatialAlbedos, &Irradiance->userSpecifiedMonthlySpatialAlbedos, as_boolean("enable_subhourly_clipping")); + Irradiance->useSpatialAlbedos, &Irradiance->userSpecifiedMonthlySpatialAlbedos, as_boolean("enable_subhourly_clipping"), as_boolean("use_custom_tilt_angles"), user_tilt_angles[inrec]); int code = irr.calc();