diff --git a/ssc/cmod_custom_generation.cpp b/ssc/cmod_custom_generation.cpp index 16a67b2a8..019facd8c 100644 --- a/ssc/cmod_custom_generation.cpp +++ b/ssc/cmod_custom_generation.cpp @@ -34,19 +34,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "lib_windfile.h" #include "lib_windwatts.h" +#include "lib_time.h" // for adjustment factors #include "common.h" static var_info _cm_vtab_custom_generation[] = { -// VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS - { SSC_INPUT, SSC_NUMBER, "spec_mode", "Spec mode: 0=constant CF,1=profile", "", "", "Plant", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "derate", "Derate", "%", "", "Plant", "*", "", "" }, - { SSC_INOUT, SSC_NUMBER, "system_capacity", "Nameplace Capcity", "kW", "", "Plant", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "user_capacity_factor", "Capacity Factor", "%", "", "Plant", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "heat_rate", "Heat Rate", "MMBTUs/MWhe", "", "Plant", "*", "", "" }, - { SSC_INPUT, SSC_NUMBER, "conv_eff", "Conversion Efficiency", "%", "", "Plant", "*", "", "" }, - { SSC_INPUT, SSC_ARRAY, "energy_output_array", "Array of Energy Output Profile", "kW", "", "Plant", "spec_mode=1", "", "" }, - +// VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS + { SSC_INPUT, SSC_NUMBER, "spec_mode", "Spec mode: 0=constant CF,1=profile,2=lifetime profile ", "", "", "Plant", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "derate", "Derate", "%", "", "Plant", "*", "", "" }, + { SSC_INOUT, SSC_NUMBER, "system_capacity", "Nameplace Capcity", "kW", "", "Plant", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "user_capacity_factor", "Capacity Factor", "%", "", "Plant", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "heat_rate", "Heat Rate", "MMBTUs/MWhe", "", "Plant", "*", "", "" }, + { SSC_INPUT, SSC_NUMBER, "conv_eff", "Conversion Efficiency", "%", "", "Plant", "*", "", "" }, + { SSC_INPUT, SSC_ARRAY, "energy_output_array", "Array of Energy Output Profile", "kW", "", "Plant", "spec_mode=1", "", "" }, + { SSC_INPUT, SSC_ARRAY, "energy_output_array_lifetime", "Array of Energy Output Profile", "kW", "", "Plant", "spec_mode=2", "", "" }, + // optional for lifetime analysis { SSC_INPUT, SSC_NUMBER, "system_use_lifetime_output", "Custom generation profile lifetime simulation", "0/1", "", "Lifetime", "?=0", "INTEGER,MIN=0,MAX=1", "" }, { SSC_INPUT, SSC_NUMBER, "analysis_period", "Lifetime analysis period", "years", "", "Lifetime", "system_use_lifetime_output=1", "", "" }, @@ -122,6 +124,7 @@ class cm_custom_generation : public compute_module if (!haf.setup(nrec_load, nyears)) throw exec_error("custom_generation", "failed to setup adjustment factors: " + haf.error()); + bool degradation_warning = false; if (system_use_lifetime_output) { // setup system degradation @@ -133,18 +136,33 @@ class cm_custom_generation : public compute_module { for (i = 0; i < nyears; i++) sys_degradation.push_back((ssc_number_t)pow((1.0 - (double)degrad[0] / 100.0), i)); + if (spec_mode == 2 && degrad[0] > 0) + { + degradation_warning = true; + } } else if (count_degrad > 0) { - for (i = 0; i < nyears && i < (int)count_degrad; i++) sys_degradation.push_back((ssc_number_t)(1.0 - (double)degrad[i] / 100.0)); + for (i = 0; i < nyears && i < (int)count_degrad; i++) { + sys_degradation.push_back((ssc_number_t)(1.0 - (double)degrad[i] / 100.0)); + if (spec_mode == 2 && degrad[i] > 0) + { + degradation_warning = true; + } + } } } else { sys_degradation.push_back(1); // single year mode - degradation handled in financial models. } + if (degradation_warning) { + log(util::format("Degradation specified as an input but will not be applied to lifetime data. Consider changing to hourly data or applying the degradation to the lifetime profile."), SSC_WARNING); + } + size_t idx = 0; double annual_output = 0; + std::vector extrapolated_current_year_generation; // Constant generation profile if (spec_mode == 0) @@ -161,38 +179,78 @@ class cm_custom_generation : public compute_module { for (size_t ihourstep = 0; ihourstep < steps_per_hour; ihourstep++) { - enet[idx] = (ssc_number_t)(output*haf(ihour)) * sys_degradation[iyear]; // kW + enet[idx] = (ssc_number_t)(output) * sys_degradation[iyear]; // kW idx++; } } } } - // Input generation profile + else if (spec_mode == 2) { + size_t nrec_gen = 0; + std::vector current_year_gen; + std::vector enet_in = as_vector_double("energy_output_array_lifetime"); // kW + nrec_gen = enet_in.size(); + size_t steps_per_hour_gen = nrec_gen / (8760 * nyears); + size_t steps_per_year = 0; + // Set this input up for yearly to daily arrays + if (steps_per_hour_gen < 1) { + steps_per_hour_gen = 1; + } + + if (enet_in.empty()) { + throw exec_error("custom_generation", util::format("energy_output_array variable had no values.")); + } + else { + steps_per_year = nrec_gen / (size_t) nyears; + nlifetime = steps_per_hour_gen * 8760 * nyears; + steps_per_hour = steps_per_hour_gen; + ts_hour = 1 / (double)(steps_per_hour); + } + + enet = allocate("gen", nlifetime); + for (size_t iyear = 0; iyear < nyears; iyear++) { + current_year_gen.clear(); + current_year_gen.reserve(steps_per_year); + current_year_gen.insert(current_year_gen.end(), enet_in.begin() + iyear * steps_per_year, enet_in.begin() + (iyear + 1) * steps_per_year); + extrapolated_current_year_generation = extrapolate_timeseries(current_year_gen, steps_per_hour_gen, derate); + for (size_t ihour = 0; ihour < 8760; ihour++) { + for (size_t ihourstep = 0; ihourstep < steps_per_hour_gen; ihourstep++) + { + enet[idx] = extrapolated_current_year_generation[ihour * steps_per_hour_gen + ihourstep]; + idx++; + } + } + } + } + // Input generation profile (annual or combine cases) else { size_t nrec_gen = 0; - ssc_number_t *enet_in = as_array("energy_output_array", &nrec_gen); // kW - size_t steps_per_hour_gen = nrec_gen / 8760; - - if (!enet_in) { + std::vector enet_in = as_vector_double("energy_output_array"); // kW + nrec_gen = enet_in.size(); + size_t steps_per_hour_gen = nrec_gen / 8760; + // Set this input up for yearly to daily arrays + if (steps_per_hour_gen < 1) { + steps_per_hour_gen = 1; + } + + if (enet_in.empty()) { throw exec_error("custom_generation", util::format("energy_output_array variable had no values.")); } - - if (nrec_gen < nrec_load) { - throw exec_error("custom_generation", util::format("energy_output_array %d must be greater than or equal to load array %d", nrec_gen, nrec_load)); - } else { - nlifetime = nrec_gen * nyears; + nlifetime = steps_per_hour_gen * 8760 * nyears; steps_per_hour = steps_per_hour_gen; ts_hour = 1 / (double)(steps_per_hour); } + extrapolated_current_year_generation = extrapolate_timeseries(enet_in, steps_per_hour_gen, derate); + enet = allocate("gen", nlifetime); for (size_t iyear = 0; iyear < nyears; iyear++){ for (size_t ihour = 0; ihour < 8760; ihour++){ for (size_t ihourstep = 0; ihourstep < steps_per_hour_gen; ihourstep++) { - enet[idx] = enet_in[ihour* steps_per_hour_gen + ihourstep] * (ssc_number_t)(derate* haf(ihour))* sys_degradation[iyear]; + enet[idx] = extrapolated_current_year_generation[ihour* steps_per_hour_gen + ihourstep] * sys_degradation[iyear]; idx++; } } diff --git a/test/ssc_test/cmod_custom_generation_test.cpp b/test/ssc_test/cmod_custom_generation_test.cpp index f18bfcd92..88daee0bb 100644 --- a/test/ssc_test/cmod_custom_generation_test.cpp +++ b/test/ssc_test/cmod_custom_generation_test.cpp @@ -121,6 +121,41 @@ TEST_F(CMCustomGeneration, CommercialWithBattery_cmod_generic) { } } +TEST_F(CMCustomGeneration, cmod_custom_generation_test_adjust_losses) { + + custom_generation_singleowner_battery_60min(data); + + // Test constant generation + ssc_data_set_number(data, "adjust_constant", 5.0); + ssc_data_set_number(data, "system_capacity", 100.0); + ssc_data_set_number(data, "user_capacity_factor", 100.0); + ssc_data_set_number(data, "spec_mode", 0); + + EXPECT_FALSE(run_module(data, "custom_generation")); + + ssc_number_t annual_energy; + ssc_data_get_number(data, "annual_energy", &annual_energy); + EXPECT_NEAR(annual_energy, 8760 * 100.0 * 0.95, 0.01); + + // Test annual custom schedule + std::vector custom_schedule(8760, 100.0); + ssc_data_set_array(data, "energy_output_array", custom_schedule.data(), (int)custom_schedule.size()); + + EXPECT_FALSE(run_module(data, "custom_generation")); + ssc_data_set_number(data, "spec_mode", 1); + ssc_data_get_number(data, "annual_energy", &annual_energy); + EXPECT_NEAR(annual_energy, 8760 * 100.0 * 0.95, 0.01); + + // Test lifetime custom schedule + std::vector custom_schedule_lifetime(8760 * 25, 100.0); + ssc_data_set_array(data, "energy_output_array_lifetime", custom_schedule_lifetime.data(), (int)custom_schedule_lifetime.size()); + + EXPECT_FALSE(run_module(data, "custom_generation")); + ssc_data_set_number(data, "spec_mode", 2); + ssc_data_get_number(data, "annual_energy", &annual_energy); + EXPECT_NEAR(annual_energy, 8760 * 100.0 * 0.95, 0.01); +} + /* Doesn't work to to outdated exeception handling methods in SSC which can not be handled robustly in a cross-platform environment