Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 80 additions & 22 deletions ssc/cmod_custom_generation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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", "", "" },
Expand Down Expand Up @@ -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
Expand All @@ -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<ssc_number_t> extrapolated_current_year_generation;

// Constant generation profile
if (spec_mode == 0)
Expand All @@ -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<ssc_number_t> current_year_gen;
std::vector<ssc_number_t> 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<ssc_number_t> 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++;
}
}
Expand Down
35 changes: 35 additions & 0 deletions test/ssc_test/cmod_custom_generation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> 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<double> 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
Expand Down
Loading