Skip to content

Commit

Permalink
Ssc 1256 better hybrid batteries (#1258)
Browse files Browse the repository at this point in the history
* Update pGen with battery outputs

* Update hybrids to better include annual energy in metrics. Add exceptions for length issues. Add energy metrics to tests

* Correct PV scaling units for hybrid O&M

* Update hybrid tests for changing pv capacity O&M basis

---------

Co-authored-by: Matt Prilliman <[email protected]>
  • Loading branch information
brtietz and mjprilliman authored Dec 9, 2024
1 parent 154e6ed commit bfca1ea
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 42 deletions.
63 changes: 35 additions & 28 deletions ssc/cmod_hybrid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@ class cm_hybrid : public compute_module

ssc_module_exec_with_error(module, input, compute_module);

ssc_number_t system_capacity = compute_module_inputs->table.lookup("system_capacity")->num;
hybridSystemCapacity += system_capacity;
hybridTotalInstalledCost += compute_module_inputs->table.lookup("total_installed_cost")->num;

ssc_data_t compute_module_outputs = ssc_data_create();

int pidx = 0;
Expand All @@ -174,6 +170,13 @@ class cm_hybrid : public compute_module
if (compute_module_inputs->table.lookup("system_use_lifetime_output"))
system_use_lifetime_output = compute_module_inputs->table.lookup("system_use_lifetime_output")->num;

ssc_number_t system_capacity = compute_module_inputs->table.lookup("system_capacity")->num;
if ((compute_module == "pvsamv1") || (compute_module == "pvwattsv8")) {
ssc_data_get_number(compute_module_outputs, "system_capacity_ac", &system_capacity);
}
hybridSystemCapacity += system_capacity;
hybridTotalInstalledCost += compute_module_inputs->table.lookup("total_installed_cost")->num;

// get minimum timestep from gen vector
ssc_number_t* curGen = ssc_data_get_array(compute_module_outputs, "gen", &len);
currentTimeStepsPerHour = len / 8760;
Expand Down Expand Up @@ -287,26 +290,6 @@ class cm_hybrid : public compute_module
}
}

// monthly energy generated
size_t step_per_hour = maximumTimeStepsPerHour;
ssc_number_t* pGenMonthly = ((var_table*)outputs)->allocate("monthly_energy", 12);
size_t c = 0;
for (int m = 0; m < 12; m++) // each month
{
pGenMonthly[m] = 0;
for (size_t d = 0; d < util::nday[m]; d++) // for each day in each month
for (int h = 0; h < 24; h++) // for each hour in each day
for (size_t j = 0; j < step_per_hour; j++)
pGenMonthly[m] += pGen[c++];
}

ssc_number_t pGenAnnual = 0;
for (size_t i = 0; i < genLength; i++)
pGenAnnual += pGen[i];
((var_table*)outputs)->assign("annual_energy", var_data(pGenAnnual));



if (fuelcells.size() > 0) { // run single fuel cell if present

percent = 100.0f * ((float)(generators.size() + fuelcells.size()) / (float)(generators.size() + fuelcells.size() + batteries.size() + financials.size()));
Expand Down Expand Up @@ -564,8 +547,35 @@ class cm_hybrid : public compute_module
if (batteries.size() > 0) {
use_batt_output = true;
pBattGen = ((var_table*)outputs)->lookup(batteries[0])->table.as_array("gen", &battGenLen);
if (battGenLen == genLength) {
for (size_t g = 0; g < genLength; g++) {
// Batt's gen is an inout that includes other system components
pGen[g] = pBattGen[g];
}
}
else {
throw exec_error("hybrid", util::format("Battery gen length incorrect, battery timeseries contains %d entries, generators contain %d", battGenLen, genLength));
}
}

// monthly energy generated
size_t step_per_hour = maximumTimeStepsPerHour;
ssc_number_t* pGenMonthly = ((var_table*)outputs)->allocate("monthly_energy", 12);
size_t c = 0;
for (int m = 0; m < 12; m++) // each month
{
pGenMonthly[m] = 0;
for (size_t d = 0; d < util::nday[m]; d++) // for each day in each month
for (int h = 0; h < 24; h++) // for each hour in each day
for (size_t j = 0; j < step_per_hour; j++)
pGenMonthly[m] += pGen[c++];
}

ssc_number_t pGenAnnual = 0;
for (size_t i = 0; i < genLength; i++)
pGenAnnual += pGen[i];
((var_table*)outputs)->assign("annual_energy", var_data(pGenAnnual));

ssc_number_t* pHybridOMSum = ((var_table*)outputs)->allocate("cf_hybrid_om_sum", analysisPeriod + 1); // add to top level "output" - assumes analysis period the same for all generators

for (int i = 0; i <= analysisPeriod; i++)
Expand Down Expand Up @@ -636,10 +646,7 @@ class cm_hybrid : public compute_module
var_data* compute_module_inputs = input_table->table.lookup(hybridVarTable);
var_table& input = compute_module_inputs->table;

// if (use_batt_output)
// ssc_data_set_array(static_cast<ssc_data_t>(&input), "gen", pBattGen, (int)battGenLen);
// else
ssc_data_set_array(static_cast<ssc_data_t>(&input), "gen", pGen, (int)genLength);
ssc_data_set_array(static_cast<ssc_data_t>(&input), "gen", pGen, (int)genLength);

if (batteries.size() > 0)
ssc_data_set_number(static_cast<ssc_data_t>(&input), "is_hybrid", 1); // for updating battery outputs to annual length in update_battery_outputs in common_financial.cpp
Expand Down
3 changes: 3 additions & 0 deletions ssc/cmod_pvsamv1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,8 @@ static var_info _cm_vtab_pvsamv1[] = {
//miscellaneous outputs
{ SSC_OUTPUT, SSC_NUMBER, "ts_shift_hours", "Sun position time offset", "hours", "", "Miscellaneous", "", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "nameplate_dc_rating", "System nameplate DC rating", "kW", "", "Miscellaneous", "*", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "system_capacity_ac", "System nameplate AC rating", "kWac", "", "Miscellaneous", "", "", "" },



// test outputs
Expand Down Expand Up @@ -3594,6 +3596,7 @@ void cm_pvsamv1::exec()
kWhACperkWAC = annual_energy / nameplate_ac_kW;
}
assign("capacity_factor_ac", var_data((ssc_number_t)(kWhACperkWAC / 87.6)));
assign("system_capacity_ac", var_data((ssc_number_t)nameplate_ac_kW));

if (is_assigned("load"))
{
Expand Down
2 changes: 2 additions & 0 deletions ssc/cmod_pvwattsv8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ static var_info _cm_vtab_pvwattsv8[] = {

{ SSC_OUTPUT, SSC_NUMBER, "ts_shift_hours", "Time offset for interpreting time series outputs", "hours","", "Miscellaneous", "*", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "percent_complete", "Estimated percent of total completed simulation", "%", "", "Miscellaneous", "", "", "" },
{ SSC_OUTPUT, SSC_NUMBER, "system_capacity_ac", "System nameplate AC rating", "kWac", "", "Miscellaneous", "", "", "" },

var_info_invalid };

Expand Down Expand Up @@ -1442,6 +1443,7 @@ class cm_pvwattsv8 : public compute_module

// for battery model, specify a number of inverters
assign("inverter_efficiency", var_data((ssc_number_t)(as_double("inv_eff"))));
assign("system_capacity_ac", var_data((ssc_number_t)pv.ac_nameplate / 1000.0));

if (en_snowloss && snowmodel.badValues > 0)
log(util::format("The snow model has detected %d bad snow depth values (less than 0 or greater than 610 cm). These values have been set to zero.", snowmodel.badValues), SSC_WARNING);
Expand Down
67 changes: 53 additions & 14 deletions test/ssc_test/cmod_hybrid_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ TEST_F(CmodHybridTest, PVWattsv8WindBatterySingleOwner) {
{
int len;

ssc_number_t pvannualenergy, windannualenergy, battannualenergy, npv;
ssc_number_t pvannualenergy, windannualenergy, battannualenergy, npv, total_energy;
ssc_number_t* battchargeenergy;
ssc_number_t* battdischargeenergy;
auto outputs = ssc_data_get_table(dat, "output");
auto inputs = ssc_data_get_table(dat, "input");

Expand All @@ -84,21 +86,27 @@ TEST_F(CmodHybridTest, PVWattsv8WindBatterySingleOwner) {
auto batt_outputs = ssc_data_get_table(outputs, "battery");
auto batt_inputs = ssc_data_get_table(inputs, "battery");
ssc_data_get_number(batt_outputs, "annual_energy", &battannualenergy);
battchargeenergy = ssc_data_get_array(batt_outputs, "batt_annual_charge_energy", &len);
battdischargeenergy = ssc_data_get_array(batt_outputs, "batt_annual_discharge_energy", &len);
EXPECT_NEAR(battannualenergy, 570565000, 570565000 * 0.01);
EXPECT_NEAR(battchargeenergy[1], 81116970, 81116970 * 0.001);
EXPECT_NEAR(battdischargeenergy[1], 72917429, 72917429 * 0.001);

auto hybrid_outputs = ssc_data_get_table(outputs, "Hybrid");

ssc_number_t value;
ssc_data_get_number(hybrid_outputs, "annual_energy", &total_energy);

auto ebitda = ssc_data_get_array(hybrid_outputs, "cf_ebitda", &len);
auto revenue = ssc_data_get_array(hybrid_outputs, "cf_total_revenue", &len);
auto om_expenses = ssc_data_get_array(hybrid_outputs, "cf_operating_expenses", &len);
ssc_data_get_number(hybrid_outputs, "project_return_aftertax_npv", &npv);

EXPECT_NEAR(om_expenses[1], 10772001, 1);
EXPECT_NEAR(revenue[1], 31651347, 1);
EXPECT_NEAR(ebitda[1], 20879346, 1);
EXPECT_NEAR(npv, -246312045, 246312045 * 0.001);
EXPECT_NEAR(om_expenses[1], 10425847, 1);
EXPECT_NEAR(revenue[1], 33062516, 1);
EXPECT_NEAR(ebitda[1], 22636669, 1);
EXPECT_NEAR(npv, -227222606, 227222606 * 0.001);

EXPECT_NEAR(total_energy, battannualenergy, total_energy * 0.001);
EXPECT_NEAR(total_energy, pvannualenergy + windannualenergy - battchargeenergy[1] + battdischargeenergy[1], total_energy * 0.001);
}
ssc_data_free(dat);
dat = nullptr;
Expand Down Expand Up @@ -134,7 +142,10 @@ TEST_F(CmodHybridTest, PVWattsv8WindBatteryHostDeveloper) {
EXPECT_FALSE(errors);
if (!errors)
{
ssc_number_t pvannualenergy, windannualenergy, npv;
int len;
ssc_number_t pvannualenergy, windannualenergy, battannualenergy, npv, total_energy;
ssc_number_t* battchargeenergy;
ssc_number_t* battdischargeenergy;
auto outputs = ssc_data_get_table(dat, "output");

auto pv_outputs = ssc_data_get_table(outputs, "pvwattsv8");
Expand All @@ -145,9 +156,23 @@ TEST_F(CmodHybridTest, PVWattsv8WindBatteryHostDeveloper) {
ssc_data_get_number(wind_outputs, "annual_energy", &windannualenergy);
EXPECT_NEAR(windannualenergy, 187767, 187767 * 0.01);

//,
auto battery_outputs = ssc_data_get_table(outputs, "battery");
ssc_data_get_number(battery_outputs, "annual_energy", &battannualenergy);
battchargeenergy = ssc_data_get_array(battery_outputs, "batt_annual_charge_energy", &len);
battdischargeenergy = ssc_data_get_array(battery_outputs, "batt_annual_discharge_energy", &len);
EXPECT_NEAR(battannualenergy, 1118877, 1118877 * 0.01);
EXPECT_NEAR(battchargeenergy[1], 83565, 83565 * 0.001);
EXPECT_NEAR(battdischargeenergy[1], 76334, 76334 * 0.001);

auto hybrid_outputs = ssc_data_get_table(outputs, "Hybrid");
ssc_data_get_number(hybrid_outputs, "project_return_aftertax_npv", &npv);
EXPECT_NEAR(npv, -182666, 182665 * 0.001);
EXPECT_NEAR(npv, -168769, 168769 * 0.001);

ssc_data_get_number(hybrid_outputs, "annual_energy", &total_energy);

EXPECT_NEAR(total_energy, battannualenergy, total_energy * 0.001);
EXPECT_NEAR(total_energy, pvannualenergy + windannualenergy - battchargeenergy[1] + battdischargeenergy[1], total_energy * 0.001);
}
ssc_data_free(dat);
dat = nullptr;
Expand Down Expand Up @@ -179,7 +204,10 @@ TEST_F(CmodHybridTest, CustomGenerationPVWattsWindFuelCellBatteryHybrid_SingleOw
EXPECT_FALSE(errors);
if (!errors)
{
ssc_number_t genericannualenergy, pvannualenergy, windannualenergy, battannualenergy, npv;
ssc_number_t genericannualenergy, pvannualenergy, windannualenergy, battannualenergy, fuelcellannualenergy, npv, total_energy;
ssc_number_t* battchargeenergy;
ssc_number_t* battdischargeenergy;

int len;
auto outputs = ssc_data_get_table(dat, "output");

Expand All @@ -195,13 +223,19 @@ TEST_F(CmodHybridTest, CustomGenerationPVWattsWindFuelCellBatteryHybrid_SingleOw
ssc_data_get_number(wind_outputs, "annual_energy", &windannualenergy);
auto wind_om_expenses = ssc_data_get_array(wind_outputs, "cf_operating_expenses", &len);

auto fuelcell_outputs = ssc_data_get_table(outputs, "fuelcell");
ssc_data_get_number(fuelcell_outputs, "annual_energy_discharged", &fuelcellannualenergy);

auto batt_outputs = ssc_data_get_table(outputs, "battery");
ssc_data_get_number(batt_outputs, "annual_energy", &battannualenergy);
auto batt_om_expenses = ssc_data_get_array(batt_outputs, "cf_operating_expenses", &len);
battchargeenergy = ssc_data_get_array(batt_outputs, "batt_annual_charge_energy", &len);
battdischargeenergy = ssc_data_get_array(batt_outputs, "batt_annual_discharge_energy", &len);

auto hybrid_outputs = ssc_data_get_table(outputs, "Hybrid");
ssc_data_get_number(hybrid_outputs, "project_return_aftertax_npv", &npv);
auto ebitda = ssc_data_get_array(hybrid_outputs, "cf_ebitda", &len);
ssc_data_get_number(hybrid_outputs, "annual_energy", &total_energy);

auto revenue = ssc_data_get_array(hybrid_outputs, "cf_total_revenue", &len);
auto om_expenses = ssc_data_get_array(hybrid_outputs, "cf_operating_expenses", &len);
Expand All @@ -210,12 +244,17 @@ TEST_F(CmodHybridTest, CustomGenerationPVWattsWindFuelCellBatteryHybrid_SingleOw
EXPECT_NEAR(genericannualenergy, 756864000., 1e6);
EXPECT_NEAR(pvannualenergy, 211907456., 1e6);
EXPECT_NEAR(windannualenergy, 366975552., 1e6);
EXPECT_NEAR(fuelcellannualenergy, 1561993, 1e6);
EXPECT_NEAR(battannualenergy, 1331720000., 1e6);
EXPECT_NEAR(battchargeenergy[1], 55372248, 55372248 * 0.001);
EXPECT_NEAR(battdischargeenergy[1], 49818321, 49818321 * 0.001);
EXPECT_NEAR(total_energy, battannualenergy, total_energy * 0.001);
EXPECT_NEAR(total_energy, pvannualenergy + windannualenergy + genericannualenergy + fuelcellannualenergy - battchargeenergy[1] + battdischargeenergy[1], total_energy * 0.001);

EXPECT_NEAR(om_expenses[1], 90570832., 1e5);
EXPECT_NEAR(revenue[1], 66865452., 1e5);
EXPECT_NEAR(ebitda[1], -23705384., 1e5);
EXPECT_NEAR(npv, -1754023822., 1e6);
EXPECT_NEAR(om_expenses[1], 90224679., 1e5);
EXPECT_NEAR(revenue[1], 66590988., 1e5);
EXPECT_NEAR(ebitda[1], -23633690., 1e5);
EXPECT_NEAR(npv, -1750593259., 1e6);
}
ssc_data_free(dat);
dat = nullptr;
Expand Down

0 comments on commit bfca1ea

Please sign in to comment.