Skip to content

Commit

Permalink
Merge branch 'hybrids' into carbonFreeDispatch
Browse files Browse the repository at this point in the history
  • Loading branch information
janinefreeman committed Aug 25, 2023
2 parents 6d8a17d + efa710c commit 1800822
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 130 deletions.
105 changes: 55 additions & 50 deletions ssc/cmod_host_developer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,65 +1027,70 @@ class cm_host_developer : public compute_module
std::vector<double> battery_discharged(nyears,0);
std::vector<double> fuelcell_discharged(nyears+1,0);

if (add_om_num_types > 0) //PV Battery
{
escal_or_annual(CF_om_fixed1_expense, nyears, "om_batt_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01);
escal_or_annual(CF_om_production1_expense, nyears, "om_batt_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01); //$/MWh
escal_or_annual(CF_om_capacity1_expense, nyears, "om_batt_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01);
nameplate1 = as_number("om_batt_nameplate");
if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1)
battery_discharged = as_vector_double("batt_annual_discharge_energy");
}
if (battery_discharged.size() == 1) { // ssc #992
double first_val = battery_discharged[0];
battery_discharged.resize(nyears, first_val);
if (is_assigned("is_hybrid") && as_integer("is_hybrid") == 1) {
// already added in additional o and m - this is only necessary if dispatch and replacments at top level as when fuel cell and battery dispatch combined
}
if (battery_discharged.size() != nyears)
throw exec_error("hostdeveloper", util::format("battery_discharged size (%d) incorrect",(int)battery_discharged.size()));
else {

if (add_om_num_types > 1)
{
escal_or_annual(CF_om_fixed2_expense, nyears, "om_fuelcell_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal")*0.01);
escal_or_annual(CF_om_production2_expense, nyears, "om_fuelcell_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal")*0.01);
escal_or_annual(CF_om_capacity2_expense, nyears, "om_fuelcell_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal")*0.01);
nameplate2 = as_number("om_fuelcell_nameplate");
fuelcell_discharged = as_vector_double("fuelcell_annual_energy_discharged");
}
if (fuelcell_discharged.size()== 2) { // ssc #992
double first_val = fuelcell_discharged[1];
fuelcell_discharged.resize(nyears+1, first_val);
}
if (fuelcell_discharged.size() != nyears+1)
throw exec_error("hostdeveloper", util::format("fuelcell_discharged size (%d) incorrect",(int)fuelcell_discharged.size()));

// battery cost - replacement from lifetime analysis
if ((as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) && (as_integer("batt_replacement_option") > 0))
{
ssc_number_t* batt_rep = 0;
std::vector<ssc_number_t> replacement_percent;
if (add_om_num_types > 0) //PV Battery
{
escal_or_annual(CF_om_fixed1_expense, nyears, "om_batt_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01);
escal_or_annual(CF_om_production1_expense, nyears, "om_batt_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01); //$/MWh
escal_or_annual(CF_om_capacity1_expense, nyears, "om_batt_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01);
nameplate1 = as_number("om_batt_nameplate");
if (as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1)
battery_discharged = as_vector_double("batt_annual_discharge_energy");
}
if (battery_discharged.size() == 1) { // ssc #992
double first_val = battery_discharged[0];
battery_discharged.resize(nyears, first_val);
}
if (battery_discharged.size() != nyears)
throw exec_error("hostdeveloper", util::format("battery_discharged size (%d) incorrect", (int)battery_discharged.size()));

batt_rep = as_array("batt_bank_replacement", &count); // replacements per year calculated
if (add_om_num_types > 1)
{
escal_or_annual(CF_om_fixed2_expense, nyears, "om_fuelcell_fixed_cost", inflation_rate, 1.0, false, as_double("om_fixed_escal") * 0.01);
escal_or_annual(CF_om_production2_expense, nyears, "om_fuelcell_variable_cost", inflation_rate, 0.001, false, as_double("om_production_escal") * 0.01);
escal_or_annual(CF_om_capacity2_expense, nyears, "om_fuelcell_capacity_cost", inflation_rate, 1.0, false, as_double("om_capacity_escal") * 0.01);
nameplate2 = as_number("om_fuelcell_nameplate");
fuelcell_discharged = as_vector_double("fuelcell_annual_energy_discharged");
}
if (fuelcell_discharged.size() == 2) { // ssc #992
double first_val = fuelcell_discharged[1];
fuelcell_discharged.resize(nyears + 1, first_val);
}
if (fuelcell_discharged.size() != nyears + 1)
throw exec_error("hostdeveloper", util::format("fuelcell_discharged size (%d) incorrect", (int)fuelcell_discharged.size()));

// battery cost - replacement from lifetime analysis
if ((as_integer("en_batt") == 1 || as_integer("en_standalone_batt") == 1) && (as_integer("batt_replacement_option") > 0))
{
ssc_number_t* batt_rep = 0;
std::vector<ssc_number_t> replacement_percent;

// replace at capacity percent
if (as_integer("batt_replacement_option") == 1) {
batt_rep = as_array("batt_bank_replacement", &count); // replacements per year calculated

for (i = 0; i < (int)count; i++) {
replacement_percent.push_back(100);
// replace at capacity percent
if (as_integer("batt_replacement_option") == 1) {

for (i = 0; i < (int)count; i++) {
replacement_percent.push_back(100);
}
}
}
else {// user specified
replacement_percent = as_vector_ssc_number_t("batt_replacement_schedule_percent");
}
double batt_cap = as_double("batt_computed_bank_capacity");
// updated 10/17/15 per 10/14/15 meeting
escal_or_annual(CF_battery_replacement_cost_schedule, nyears, "om_batt_replacement_cost", inflation_rate, batt_cap, false, as_double("om_replacement_cost_escal") * 0.01);
else {// user specified
replacement_percent = as_vector_ssc_number_t("batt_replacement_schedule_percent");
}
double batt_cap = as_double("batt_computed_bank_capacity");
// updated 10/17/15 per 10/14/15 meeting
escal_or_annual(CF_battery_replacement_cost_schedule, nyears, "om_batt_replacement_cost", inflation_rate, batt_cap, false, as_double("om_replacement_cost_escal") * 0.01);

for (i = 0; i < nyears && i < (int)count; i++) {
cf.at(CF_battery_replacement_cost, i + 1) = batt_rep[i] * replacement_percent[i] * 0.01 *
cf.at(CF_battery_replacement_cost_schedule, i + 1);
for (i = 0; i < nyears && i < (int)count; i++) {
cf.at(CF_battery_replacement_cost, i + 1) = batt_rep[i] * replacement_percent[i] * 0.01 *
cf.at(CF_battery_replacement_cost_schedule, i + 1);
}
}
}

// initialize energy and revenue
// initialize energy
// differs from samsim - accumulate hourly energy
Expand Down
74 changes: 56 additions & 18 deletions ssc/cmod_hybrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,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 == "pvwattsv8") || (computemodulename == "windpower"))
if ((computemodulename == "pvsamv1") || (computemodulename == "pvwattsv8") || (computemodulename == "windpower"))
generators.push_back(computemodulename);
else if (computemodulename == "battery")
batteries.push_back(computemodulename);
Expand Down Expand Up @@ -121,7 +121,7 @@ class cm_hybrid : public compute_module
//ssc_data_set_number(static_cast<ssc_data_t>(&input), "is_hybrid", 1);


//ssc_module_exec_set_print(1);
ssc_module_exec_set_print(1);
ssc_module_exec(module, static_cast<ssc_data_t>(&input));

ssc_data_t compute_module_outputs = ssc_data_create();
Expand Down Expand Up @@ -165,26 +165,43 @@ class cm_hybrid : public compute_module
// production - multiply by yearly gen (initially assume single year) - use degradation - specific to each generator
// pvwattsv8 - "degradation" applied in financial model - assuming single year analysis like standalone pvwatts/single owner configuration
// wind - "degradation" applied in financial model - assumes system availability already applied to "gen" output
// pvsamv1 - "degradation" applied in performance model
ssc_number_t* pEnergyNet = ((var_table*)compute_module_outputs)->allocate("cf_energy_net", analysisPeriod + 1);
ssc_number_t* pDegradation = ((var_table*)compute_module_outputs)->allocate("cf_degradation", analysisPeriod + 1);
size_t count_degrad = 0;
ssc_number_t* degrad = 0;
degrad = input.as_array("degradation", &count_degrad);
if (count_degrad == 1) {
for (int i = 1; i <= analysisPeriod; i++)
pDegradation[i] = pow((1.0 - degrad[0] / 100.0), i - 1);

if (compute_module_inputs->table.lookup("system_use_lifetime_output")->num > 0) { // e.g. pvsamv1
size_t timestepsPerYear = len / analysisPeriod;
for (int i = 0; i < analysisPeriod; i++) {
pDegradation[i + 1] = 1.0;
pEnergyNet[i + 1] = 0;
for (size_t j = 0; j < timestepsPerYear; j++) { // steps per year
pEnergyNet[i + 1] += curGen[i * timestepsPerYear + j]*currentTimeStepsPerHour; // power to energy
}
}
}
else if (count_degrad > 0) {
for (int i = 0; i < analysisPeriod && i < (int)count_degrad; i++)
pDegradation[i + 1] = (1.0 - degrad[i] / 100.0);
else {
size_t count_degrad = 0;
ssc_number_t* degrad = 0;
degrad = input.as_array("degradation", &count_degrad);
if (count_degrad == 1) {
for (int i = 1; i <= analysisPeriod; i++)
pDegradation[i] = pow((1.0 - degrad[0] / 100.0), i - 1);
}
else if (count_degrad > 0) {
for (int i = 0; i < analysisPeriod && i < (int)count_degrad; i++)
pDegradation[i + 1] = (1.0 - degrad[i] / 100.0);
}
ssc_number_t first_year_energy = ((var_table*)compute_module_outputs)->as_double("annual_energy"); // first year energy value
for (int i = 1; i <= analysisPeriod; i++) {
pEnergyNet[i] = first_year_energy * pDegradation[i];
}
}
ssc_number_t first_year_energy = ((var_table*)compute_module_outputs)->as_double("annual_energy"); // first year energy value
for (int i = 1; i <= analysisPeriod; i++) {
pEnergyNet[i] = first_year_energy * pDegradation[i];
pOMProduction[i] *= pEnergyNet[i];
}

if (compute_module == "pvwattsv8") {
// optional land lease o and m costs if present - set to zero by default
if (compute_module_inputs->table.lookup("om_land_lease")) {
ssc_number_t* pOMLandLease = ((var_table*)compute_module_outputs)->allocate("cf_om_land_lease", analysisPeriod + 1);
ssc_number_t total_land_area = compute_module_inputs->table.lookup("land_area")->num;
escal_or_annual(input, pOMLandLease, analysisPeriod, "om_land_lease", inflation_rate, total_land_area, false, input.as_double("om_land_lease_escal") * 0.01);
Expand Down Expand Up @@ -292,7 +309,16 @@ setmodules( ['pvwattsv8', 'fuelcell', 'battery', 'grid', 'utilityrate5', 'therma
ssc_data_set_array(static_cast<ssc_data_t>(&input), "gen", pGen, (int)genLength); // check if issue with hourly PV and subhourly wind
//ssc_data_set_number(static_cast<ssc_data_t>(&input), "is_hybrid", 1);

ssc_module_exec(module, static_cast<ssc_data_t>(&input));
if (!ssc_module_exec(module, static_cast<ssc_data_t>(&input))) {
// merge in hybrid vartable for configurations where battery and fuel cell dispatch are combined and not in the technology bin
std::string hybridVarTable("Hybrid");
var_data* hybrid_inputs = input_table->table.lookup(hybridVarTable);// TODO - better naming of combined vartable?
if (compute_module_inputs->type != SSC_TABLE)
throw exec_error("hybrid", "No input input_table found for ." + hybridVarTable);
var_table& hybridinput = hybrid_inputs->table;
input.merge(hybridinput, false);
ssc_module_exec(module, static_cast<ssc_data_t>(&input));
}

ssc_data_t compute_module_outputs = ssc_data_create();

Expand Down Expand Up @@ -383,10 +409,22 @@ setmodules( ['pvwattsv8', 'fuelcell', 'battery', 'grid', 'utilityrate5', 'therma
var_table& input = compute_module_inputs->table;
ssc_data_set_array(static_cast<ssc_data_t>(&input), "gen", pGen, (int)genLength); // check if issue with lookahead dispatch with hourly PV and subhourly wind
ssc_data_set_number(static_cast<ssc_data_t>(&input), "system_use_lifetime_output", 1);
ssc_data_set_number(static_cast<ssc_data_t>(&input), "en_batt", 1); // should be done at UI level
//ssc_data_set_number(static_cast<ssc_data_t>(&input), "is_hybrid", 1);

//ssc_module_exec_with_handler(module, static_cast<ssc_data_t>(&input),default_internal_handler, m_handler); // handler not passed into compute module
ssc_module_exec(module, static_cast<ssc_data_t>(&input));
if (!ssc_module_exec(module, static_cast<ssc_data_t>(&input))) {
// merge in hybrid vartable for configurations where battery and fuel cell dispatch are combined and not in the technology bin
std::string hybridVarTable("Hybrid");
var_data* hybrid_inputs = input_table->table.lookup(hybridVarTable);// TODO - better naming of combined vartable?
if (compute_module_inputs->type != SSC_TABLE)
throw exec_error("hybrid", "No input input_table found for ." + hybridVarTable);
var_table& hybridinput = hybrid_inputs->table;
input.merge(hybridinput, false);
ssc_data_set_number(static_cast<ssc_data_t>(&input), "en_batt", 1); // should be done at UI level

ssc_module_exec(module, static_cast<ssc_data_t>(&input));
}

ssc_data_t compute_module_outputs = ssc_data_create();

Expand Down Expand Up @@ -525,11 +563,11 @@ setmodules( ['pvwattsv8', 'fuelcell', 'battery', 'grid', 'utilityrate5', 'therma
ssc_number_t* om_fixed = generator_outputs.as_array("cf_om_fixed", &count_gen);
ssc_number_t* om_capacity = generator_outputs.as_array("cf_om_capacity", &count_gen);
ssc_number_t* om_landlease = NULL;
if (generators[g] == "pvwattsv8")
if (generator_outputs.lookup("cf_om_land_lease"))
om_landlease = generator_outputs.as_array("cf_om_land_lease", &count_gen);
for (size_t y = 1; y <= analysisPeriod; y++) {
pHybridOMSum[y] += om_production[y] + om_fixed[y] + om_capacity[y];
if (generators[g] == "pvwattsv8")
if (generator_outputs.lookup("cf_om_land_lease"))
pHybridOMSum[y] += om_landlease[y];
}
}
Expand Down
Loading

0 comments on commit 1800822

Please sign in to comment.