Skip to content
Merged
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
165 changes: 133 additions & 32 deletions ssc/cmod_battery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ var_info vtab_battery_inputs[] = {

// Dispatch forecast - optional parameters used in cmod_pvsamv1
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_wf_forecast_choice", "Weather forecast choice for automatic dispatch", "0/1/2", "0=LookAhead,1=LookBehind,2=InputForecast", "BatteryDispatch", "?=0", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_pv_clipping_forecast", "PV clipping forecast", "kW", "Length either 8760 * steps per hour (values repeat each year) or 8760 * steps per hour * analysis period", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_pv_ac_forecast", "PV ac power forecast", "kW", "Length either 8760 * steps per hour (values repeat each year) or 8760 * steps per hour * analysis period", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_load_forecast_choice", "Load forecast choice for automatic dispatch", "0/1/2", "0=LookAhead,1=LookBehind,2=InputForecast", "BatteryDispatch", "?=0", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_pv_clipping_forecast", "PV clipping forecast", "kW", "Length either 8760 * steps per hour (values repeat each year) or 8760 * steps per hour * analysis period", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_pv_ac_forecast", "PV ac power forecast", "kW", "Length either 8760 * steps per hour (values repeat each year) or 8760 * steps per hour * analysis period", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_load_ac_forecast", "Load ac power forecast", "kW", "Length either 8760 or 8760 * steps per hour", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_load_ac_forecast_escalation", "Annual load escalation for ac power forecast", "kW", "length <= analysis_period", "BatteryDispatch", "", "", "" },

// cycle cost inputs
{ SSC_INPUT, SSC_NUMBER, "batt_cycle_cost_choice", "Use SAM cost model for degradaton penalty or input custom via batt_cycle_cost", "0/1", "0=UseCostModel,1=InputCost", "BatteryDispatch", "?=0", "", "" },
Expand Down Expand Up @@ -419,6 +422,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c
// Storage dispatch controllers
batt_vars->batt_dispatch = vt.as_integer("batt_dispatch_choice");
batt_vars->batt_dispatch_wf_forecast = vt.as_integer("batt_dispatch_wf_forecast_choice");
batt_vars->batt_dispatch_load_forecast = vt.as_integer("batt_dispatch_load_forecast_choice");
batt_vars->batt_meter_position = vt.as_integer("batt_meter_position");

// Cycle cost calculations
Expand Down Expand Up @@ -1224,6 +1228,7 @@ void battstor::parse_configuration()
{
int batt_dispatch = batt_vars->batt_dispatch;
int batt_weather_forecast = batt_vars->batt_dispatch_wf_forecast;
int batt_load_forecast = batt_vars->batt_dispatch_load_forecast;
int batt_meter_position = batt_vars->batt_meter_position;

// parse configuration
Expand All @@ -1236,15 +1241,28 @@ void battstor::parse_configuration()
{
switch (batt_weather_forecast) {
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD:
look_ahead = true;
wf_look_ahead = true;
break;
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_BEHIND:
look_behind = true;
wf_look_behind = true;
break;
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_CUSTOM:
input_forecast = true;
wf_input_forecast = true;
break;
}

switch (batt_load_forecast) {
case dispatch_t::LOAD_LOOK_AHEAD:
load_look_ahead = true;
break;
case dispatch_t::LOAD_LOOK_BEHIND:
load_look_behind = true;
break;
case dispatch_t::LOAD_CUSTOM:
load_input_forecast = true;
break;
}

if (batt_dispatch == dispatch_t::MAINTAIN_TARGET)
input_target = true;
}
Expand All @@ -1258,13 +1276,13 @@ void battstor::parse_configuration()
if (batt_dispatch == dispatch_t::FOM_AUTOMATED_ECONOMIC || batt_dispatch == dispatch_t::FOM_PV_SMOOTHING) {
switch (batt_weather_forecast) {
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD:
look_ahead = true;
wf_look_ahead = true;
break;
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_BEHIND:
look_behind = true;
wf_look_behind = true;
break;
case dispatch_t::WEATHER_FORECAST_CHOICE::WF_CUSTOM:
input_forecast = true;
wf_input_forecast = true;
break;
}
}
Expand All @@ -1286,7 +1304,7 @@ void battstor::initialize_automated_dispatch(std::vector<ssc_number_t> pv, std::
if (!input_custom_dispatch)
{
// look ahead
if (look_ahead)
if (wf_look_ahead)
{
if (pv.size() != 0)
{
Expand All @@ -1295,27 +1313,26 @@ void battstor::initialize_automated_dispatch(std::vector<ssc_number_t> pv, std::
}

}
if (load.size() != 0)
{
for (size_t idx = 0; idx != nrec; idx++) {
load_prediction.push_back(load[idx]);
}
}
if (cliploss.size() != 0)
{
for (size_t idx = 0; idx != nrec; idx++) {
cliploss_prediction.push_back(cliploss[idx]);
}
}
}
else if (look_behind)
else if (wf_look_behind)
{
// day one is zeros
for (size_t idx = 0; idx != 24 * step_per_hour; idx++)
// day one Dec 31st
for (size_t idx = ((8760 * step_per_hour) - (24 * step_per_hour)); idx != 8760 * step_per_hour; idx++)
{
pv_prediction.push_back(0);
load_prediction.push_back(0);
cliploss_prediction.push_back(0);
if (pv.size() > idx)
{
pv_prediction.push_back(pv[idx]);
}
if (cliploss.size() > idx)
{
cliploss_prediction.push_back(cliploss[idx]);
}
}

if (pv.size() != 0)
Expand All @@ -1324,20 +1341,14 @@ void battstor::initialize_automated_dispatch(std::vector<ssc_number_t> pv, std::
pv_prediction.push_back(pv[idx]);
}
}
if (load.size() != 0)
{
for (size_t idx = 0; idx != nrec - 24 * step_per_hour; idx++) {
load_prediction.push_back(load[idx]);
}
}
if (cliploss.size() != 0)
{
for (size_t idx = 0; idx != nrec - 24 * step_per_hour; idx++) {
cliploss_prediction.push_back(cliploss[idx]);
}
}
}
else if (input_forecast)
else if (wf_input_forecast)
{
if (pv.size() != 0)
{
Expand All @@ -1352,6 +1363,42 @@ void battstor::initialize_automated_dispatch(std::vector<ssc_number_t> pv, std::
}
}
}

// All of these will be false for FOM, so load will not be populated
if (load_look_ahead) {
if (load.size() != 0)
{
for (size_t idx = 0; idx != nrec; idx++) {
load_prediction.push_back(load[idx]);
}
}
}
else if (load_look_behind) {
// day one uses Dec 31st
for (size_t idx = ((8760 * step_per_hour) - (24 * step_per_hour)); idx != 8760 * step_per_hour; idx++)
{
if (load.size() > idx)
{
load_prediction.push_back(load[idx]);
}
}
if (load.size() != 0)
{
for (size_t idx = 0; idx != nrec - 24 * step_per_hour; idx++) {
load_prediction.push_back(load[idx]);
}
}

}
else if (load_input_forecast) {
if (load.size() != 0)
{
for (size_t idx = 0; idx != nrec; idx++) {
load_prediction.push_back(load[idx]);
}
}
}

// Input checking
if (pv.size() == 0)
{
Expand Down Expand Up @@ -1411,9 +1458,12 @@ battstor::~battstor()
battstor::battstor(const battstor& orig) {
// copy values
manual_dispatch = orig.manual_dispatch;
look_ahead = orig.look_ahead;
look_behind = orig.look_behind;
input_forecast = orig.input_forecast;
wf_look_ahead = orig.wf_look_ahead;
wf_look_behind = orig.wf_look_behind;
wf_input_forecast = orig.wf_input_forecast;
load_look_ahead = orig.load_look_ahead;
load_look_behind = orig.load_look_behind;
load_input_forecast = orig.load_input_forecast;
input_target = orig.input_target;
input_custom_dispatch = orig.input_custom_dispatch;
step_per_hour = orig.step_per_hour;
Expand Down Expand Up @@ -1925,8 +1975,13 @@ class cm_battery : public compute_module

size_t n_rec_single_year;
double dt_hour_gen;
size_t nload;
if (is_assigned("load")) {
load_year_one = as_vector_ssc_number_t("load");
nload = load_year_one.size();
// Array length for non-lifetime mode, lifetime mode, and hourly load
if (nload != n_rec_lifetime && nload != n_rec_lifetime / analysis_period && nload != 8760)
throw exec_error("battery", "The electric load profile must have either the same time step as the weather file, or 8760 time steps.");
}
scalefactors scale_calculator(m_vartab);
// compute load (electric demand) annual escalation multipliers
Expand All @@ -1944,15 +1999,61 @@ class cm_battery : public compute_module
n_rec_single_year,
dt_hour_gen);

// Setup custom forecasts
std::vector<ssc_number_t> p_pv_ac_forecast;
int batt_forecast_choice = as_integer("batt_dispatch_wf_forecast_choice");
if (is_assigned("batt_pv_ac_forecast")) {
p_pv_ac_forecast = as_vector_ssc_number_t("batt_pv_ac_forecast");
// Annual simulation is enforced above
if (p_pv_ac_forecast.size() < dt_hour_gen * 8760 && batt_forecast_choice == dispatch_t::WEATHER_FORECAST_CHOICE::WF_CUSTOM) {
throw exec_error("battery", "batt_pv_clipping_forecast forecast length is " + std::to_string(p_pv_ac_forecast.size()) + " when custom weather file forecast is selected. Change batt_dispatch_wf_forecast_choice or provide a forecast of at least length " + std::to_string(dt_hour_gen * 8760));
}
}
else {
p_pv_ac_forecast = power_input_lifetime;
}

std::vector<ssc_number_t> p_load_forecast_in;
std::vector<ssc_number_t> p_load_forecast_full;
p_load_forecast_full.reserve(n_rec_lifetime);
if (is_assigned("batt_load_ac_forecast"))
{
p_load_forecast_in = as_vector_ssc_number_t("batt_load_ac_forecast");
size_t nload = p_load_forecast_in.size();
if (nload == 1) {
// Length 1 is "empty" to UI lk
p_load_forecast_in.clear();
}
// Array length for non-lifetime mode, lifetime mode, and hourly load
else if (nload != n_rec_lifetime && nload != n_rec_lifetime / analysis_period && nload != 8760) {
throw exec_error("battery", "The electric load forecast must have either the same time step as the weather file, or 8760 time steps.");
}
}
if (p_load_forecast_in.size() > 0) {
std::vector<ssc_number_t> load_forecast_scale = scale_calculator.get_factors("batt_load_ac_forecast_escalation");
interpolation_factor = 1.0;
single_year_to_lifetime_interpolated<ssc_number_t>(
use_lifetime,
analysis_period,
n_rec_lifetime,
p_load_forecast_in,
load_forecast_scale,
interpolation_factor,
p_load_forecast_full,
n_rec_single_year,
dt_hour_gen);
}
else {
p_load_forecast_full = load_lifetime;
}

auto batt = std::make_shared<battstor>(*m_vartab, true, n_rec_single_year, dt_hour_gen);

// Create battery structure and initialize

if (is_assigned("fuelcell_power"))
add_var_info(vtab_fuelcell_output);
batt->initialize_automated_dispatch(power_input_lifetime, load_lifetime);
batt->initialize_automated_dispatch(p_pv_ac_forecast, p_load_forecast_full);

if (load_lifetime.size() != n_rec_lifetime) {
throw exec_error("battery", "Load length does not match system generation length.");
Expand Down
22 changes: 16 additions & 6 deletions ssc/cmod_battery.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct batt_variables
int batt_chem;
int batt_dispatch;
int batt_dispatch_wf_forecast;
int batt_dispatch_load_forecast;
int batt_voltage_choice;
int batt_current_choice;
int batt_meter_position;
Expand Down Expand Up @@ -260,14 +261,23 @@ struct battstor
/*! Manual dispatch*/
bool manual_dispatch = false;

/*! Automated dispatch look ahead*/
bool look_ahead = false;
/*! Automated dispatch weather file look ahead*/
bool wf_look_ahead = false;

/*! Automated dispatch look behind*/
bool look_behind = false;
/*! Automated dispatch weather file look behind*/
bool wf_look_behind = false;

/*! Automated dispatch use custom input forecast (look ahead)*/
bool input_forecast = false;
/*! Automated dispatch use custom weather file input forecast (look ahead)*/
bool wf_input_forecast = false;

/*! Automated dispatch look ahead for load*/
bool load_look_ahead = false;

/*! Automated dispatch look behind for load*/
bool load_look_behind = false;

/*! Automated dispatch use custom input forecast (look ahead) for load*/
bool load_input_forecast = false;

/*! Automated dispatch override algorithm grid target calculation*/
bool input_target = false;
Expand Down
2 changes: 2 additions & 0 deletions ssc/cmod_battwatts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,12 @@ battwatts_create(size_t n_recs, size_t n_years, int chem, int meter_pos, double
case 0:
batt_vars->batt_dispatch = dispatch_t::PEAK_SHAVING;
batt_vars->batt_dispatch_wf_forecast = dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD;
batt_vars->batt_dispatch_load_forecast = dispatch_t::LOAD_LOOK_AHEAD;
break;
case 1:
batt_vars->batt_dispatch = dispatch_t::PEAK_SHAVING;
batt_vars->batt_dispatch_wf_forecast = dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_BEHIND;
batt_vars->batt_dispatch_load_forecast = dispatch_t::LOAD_LOOK_BEHIND;
break;
case 2: batt_vars->batt_dispatch = dispatch_t::CUSTOM_DISPATCH;
batt_vars->batt_custom_dispatch = std::move(dispatch_custom);
Expand Down
Loading