Skip to content

Commit

Permalink
Merge pull request #1228 from NREL/custom-generation-profile-tech
Browse files Browse the repository at this point in the history
Rename Generic System to Custom Generation Profile
  • Loading branch information
mjprilliman authored Oct 31, 2024
2 parents 1750ef9 + ae13196 commit 6f8d859
Show file tree
Hide file tree
Showing 40 changed files with 352 additions and 92 deletions.
2 changes: 1 addition & 1 deletion ssc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(SSC_SRC
cmod_csp_subcomponent.cpp
cmod_csp_trough_eqns.cpp
cmod_csp_trough_eqns.h
cmod_custom_generation.cpp
cmod_etes_electric_resistance.cpp
cmod_etes_ptes.cpp
cmod_equpartflip.cpp
Expand All @@ -44,7 +45,6 @@ set(SSC_SRC
cmod_fuelcell.h
cmod_generic_system-builder.cpp
cmod_generic_system-builder.h
cmod_generic_system.cpp
cmod_geothermal.cpp
cmod_geothermal_costs.cpp
cmod_geothermal_eqns.cpp
Expand Down
260 changes: 260 additions & 0 deletions ssc/cmod_custom_generation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/*
BSD 3-Clause License
Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/ssc/blob/develop/LICENSE
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "core.h"
#include "lib_windfile.h"
#include "lib_windwatts.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", "", "" },

// 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", "", "" },
{ SSC_INPUT, SSC_ARRAY, "generic_degradation", "Annual AC degradation", "%/year", "", "Lifetime", "system_use_lifetime_output=1", "", "" },


// OUTPUTS ----------------------------------------------------------------------------
// VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS
// { SSC_OUTPUT, SSC_ARRAY, "hourly_energy", "Hourly Energy", "kWh", "", "Time Series", "*", "LENGTH=8760", "" },
{ SSC_OUTPUT, SSC_ARRAY, "monthly_energy", "Monthly Energy Gross", "kWh", "", "Monthly", "*", "LENGTH=12", "" },
{ SSC_OUTPUT, SSC_NUMBER, "annual_energy", "Annual Energy", "kWh", "", "Annual", "*", "", "" },

{ SSC_OUTPUT, SSC_NUMBER, "annual_fuel_usage", "Annual Fuel Usage", "kWht", "", "Annual", "*", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "water_usage", "Annual Water Usage", "", "", "Annual", "*", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "system_heat_rate", "Heat Rate Conversion Factor", "MMBTUs/MWhe", "", "Annual", "*", "", "" },

{ SSC_OUTPUT, SSC_NUMBER, "capacity_factor", "Capacity factor", "%", "", "Annual", "*", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "kwh_per_kw", "First year kWh/kW", "kWh/kW", "", "Annual", "*", "", "" },


var_info_invalid };

class cm_custom_generation : public compute_module
{
private:
public:

cm_custom_generation()
{
add_var_info( _cm_vtab_custom_generation );

// performance adjustment factors
add_var_info(vtab_adjustment_factors);
add_var_info(vtab_technology_outputs);
add_var_info(vtab_hybrid_tech_om_outputs);
}

void exec( )
{
int spec_mode = as_integer("spec_mode");
bool system_use_lifetime_output = (as_integer("system_use_lifetime_output") == 1);

// Warning workaround
static bool is32BitLifetime = (__ARCHBITS__ == 32 && system_use_lifetime_output);
if (is32BitLifetime)
throw exec_error( "custom_generation", "Lifetime simulation of custom generation profile systems is only available in the 64 bit version of SAM.");

// Lifetime setup
ssc_number_t *enet = nullptr;
size_t nyears = 1;
if (system_use_lifetime_output) {
nyears = as_integer("analysis_period");
}

// Load parsing
std::vector<double> load;
size_t nrec_load = 8760;

if (is_assigned("load")) {
load = as_vector_double("load");
nrec_load = load.size();
}
size_t nlifetime = nrec_load * nyears;
size_t steps_per_hour = nrec_load / 8760;
double ts_hour = 1 / (double)(steps_per_hour);

// Degradation and adjustments
std::vector<ssc_number_t> sys_degradation;
sys_degradation.reserve(nyears);
double derate = (1 - (double)as_number("derate") / 100);

adjustment_factors haf(this, "adjust");
if (!haf.setup(nrec_load, nyears))
throw exec_error("custom_generation", "failed to setup adjustment factors: " + haf.error());

if (system_use_lifetime_output)
{
// setup system degradation
size_t i, count_degrad = 0;
ssc_number_t *degrad = 0;
degrad = as_array("generic_degradation", &count_degrad);

if (count_degrad == 1)
{
for (i = 0; i < nyears; i++)
sys_degradation.push_back((ssc_number_t)pow((1.0 - (double)degrad[0] / 100.0), i));
}
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));
}
}
else {
sys_degradation.push_back(1); // single year mode - degradation handled in financial models.
}

size_t idx = 0;
double annual_output = 0;

// Constant generation profile
if (spec_mode == 0)
{
double output = (double)as_number("system_capacity")
* (double)as_number("user_capacity_factor") / 100
* derate; // kW

annual_output = 8760 * output; // kWh
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; ihourstep++)
{
enet[idx] = (ssc_number_t)(output*haf(ihour)) * sys_degradation[iyear]; // kW
idx++;
}
}
}
}
// Input generation profile
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) {
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;
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++){
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];
idx++;
}
}
}
}
double annual_ac_pre_avail = 0, annual_energy = 0;
idx = 0;

// Run generic system
for (size_t iyear = 0; iyear < nyears; iyear++){
for (size_t hour = 0; hour < 8760; hour++){
for (size_t jj = 0; jj < steps_per_hour; jj++)
{

// accumulate system generation before curtailment and availability
if (iyear == 0) {
annual_ac_pre_avail += enet[idx] * ts_hour;
}

//apply availability and curtailment
enet[idx] *= haf(hour);

if (iyear == 0) {
annual_energy += (ssc_number_t)(enet[idx] * ts_hour);
}

idx++;
}
}
}

ssc_number_t* p_annual_energy_dist_time = gen_heatmap(this, steps_per_hour);

accumulate_monthly_for_year("gen", "monthly_energy", ts_hour, steps_per_hour);
annual_output = accumulate_annual_for_year("gen", "annual_energy", ts_hour, steps_per_hour);

// if conversion efficiency is zero then set fuel usage to zero per email from Paul 5/17/12
double fuel_usage = 0.0;
if (as_double("conv_eff") != 0.0)
fuel_usage = annual_output * 100.0 / as_double("conv_eff");
assign("annual_fuel_usage", (ssc_number_t)fuel_usage);

assign("water_usage", 0.0);
assign("system_heat_rate", (ssc_number_t)(as_number("heat_rate") * as_number("conv_eff") / 100.0));

// metric outputs moved to technology
double kWhperkW = 0.0;
double nameplate = as_double("system_capacity");


if (nameplate <= 0) {
nameplate = annual_output / (8760 * (double)(as_number("user_capacity_factor") / 100) * derate);
}
assign("system_capacity", (var_data)((ssc_number_t)nameplate));

if (nameplate > 0) {
kWhperkW = annual_output / nameplate;
}
assign("capacity_factor", var_data((ssc_number_t)(kWhperkW / 87.6)));
assign("kwh_per_kw", var_data((ssc_number_t)kWhperkW));
} // exec
};

DEFINE_MODULE_ENTRY( custom_generation, "Custom Generation Profile (formerly Generic System)", 1 );

4 changes: 2 additions & 2 deletions ssc/cmod_hybrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class cm_hybrid : public compute_module

for (size_t i = 0; i < vec_cms.size(); i++) {
std::string computemodulename = vec_cms[i].str;
if ((computemodulename == "pvsamv1") || (computemodulename == "pvwattsv8") || (computemodulename == "windpower") || (computemodulename == "generic_system"))
if ((computemodulename == "pvsamv1") || (computemodulename == "pvwattsv8") || (computemodulename == "windpower") || (computemodulename == "custom_generation"))
generators.push_back(computemodulename);
else if (computemodulename == "battery")
batteries.push_back(computemodulename);
Expand Down Expand Up @@ -216,7 +216,7 @@ class cm_hybrid : public compute_module
else {
size_t count_degrad = 0;
ssc_number_t* degrad = input.as_array("degradation", &count_degrad);
if (compute_module == "generic_system")
if (compute_module == "custom_generation")
input.assign("generic_degradation", *input.lookup("degradation"));
if (count_degrad == 1) {
for (int i = 1; i <= analysisPeriod; i++)
Expand Down
8 changes: 4 additions & 4 deletions ssc/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ var_info vtab_oandm[] = {
{ SSC_INPUT, SSC_NUMBER, "om_production_escal", "Production-based O&M escalation", "%/year", "", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_ARRAY, "om_capacity", "Capacity-based O&M amount", "$/kWcap", "!battery,!fuelcell", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_NUMBER, "om_capacity_escal", "Capacity-based O&M escalation", "%/year", "", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_ARRAY, "om_fuel_cost", "Fuel cost", "$/MMBtu", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_NUMBER, "om_fuel_cost_escal", "Fuel cost escalation", "%/year", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_NUMBER, "annual_fuel_usage", "Fuel usage (yr 1)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0", "MIN=0", "" },
{ SSC_INPUT, SSC_ARRAY, "annual_fuel_usage_lifetime", "Fuel usage (lifetime)", "kWht", "generic_system,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "", "", "" },
{ SSC_INPUT, SSC_ARRAY, "om_fuel_cost", "Fuel cost", "$/MMBtu", "custom_generation,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_NUMBER, "om_fuel_cost_escal", "Fuel cost escalation", "%/year", "custom_generation,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0.0", "", "" },
{ SSC_INPUT, SSC_NUMBER, "annual_fuel_usage", "Fuel usage (yr 1)", "kWht", "custom_generation,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "?=0", "MIN=0", "" },
{ SSC_INPUT, SSC_ARRAY, "annual_fuel_usage_lifetime", "Fuel usage (lifetime)", "kWht", "custom_generation,fuelcell,tcslinearfresnel,tcstroughempirical,tcsgenericsolar,fresnelphysical", "System Costs", "", "", "" },

// replacements
{ SSC_INPUT,SSC_ARRAY , "om_batt_replacement_cost" , "Replacement cost 1" , "$/kWh" , "battery" , "System Costs" , "?=0.0" , "" , ""},
Expand Down
4 changes: 2 additions & 2 deletions ssc/sscapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ extern module_entry_info
cm_entry_geothermal_costs,
cm_entry_windpower,
cm_entry_snowmodel,
cm_entry_generic_system,
cm_entry_custom_generation,
cm_entry_wfcsvconv,
cm_entry_tcstrough_empirical,
cm_entry_tcstrough_physical,
Expand Down Expand Up @@ -203,7 +203,7 @@ static module_entry_info *module_table[] = {
&cm_entry_geothermal_costs,
&cm_entry_windpower,
&cm_entry_snowmodel,
&cm_entry_generic_system,
&cm_entry_custom_generation,
&cm_entry_wfcsvconv,
&cm_entry_tcstrough_empirical,
&cm_entry_tcstrough_physical,
Expand Down
2 changes: 1 addition & 1 deletion test/input_cases/biomass_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace biomass_test {
char file_name[256];
int n1 = sprintf(file_name, "%s/test/input_cases/swh_residential_data/fargo_nd_46.9_-96.8_mts1_60_tmy.csv", SSCDIR);
char dispatch_factors[256];
int n2 = sprintf(dispatch_factors, "%s/test/input_cases/generic_system_data/dispatch_factors_ts.csv", SSCDIR);
int n2 = sprintf(dispatch_factors, "%s/test/input_cases/custom_generation_data/dispatch_factors_ts.csv", SSCDIR);

}

Expand Down
Loading

0 comments on commit 6f8d859

Please sign in to comment.