Skip to content

Commit

Permalink
Merge branch 'me_cost_array_scaling' into tidal-timeseries-model
Browse files Browse the repository at this point in the history
  • Loading branch information
mjprilliman committed Oct 17, 2023
2 parents 6333420 + 95ecc35 commit 9a8f784
Show file tree
Hide file tree
Showing 17 changed files with 319 additions and 134 deletions.
2 changes: 1 addition & 1 deletion shared/lib_battery_dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class dispatch_t
public:

enum FOM_MODES { FOM_AUTOMATED_ECONOMIC, FOM_PV_SMOOTHING, FOM_CUSTOM_DISPATCH, FOM_MANUAL };
enum BTM_MODES { PEAK_SHAVING, MAINTAIN_TARGET, CUSTOM_DISPATCH, MANUAL, FORECAST, SELF_CONSUMPTION };
enum BTM_MODES { PEAK_SHAVING, MAINTAIN_TARGET, CUSTOM_DISPATCH, MANUAL, RETAIL_RATE, SELF_CONSUMPTION };
enum METERING { BEHIND, FRONT };
enum WEATHER_FORECAST_CHOICE { WF_LOOK_AHEAD, WF_LOOK_BEHIND, WF_CUSTOM };
enum LOAD_FORECAST_CHOICE { LOAD_LOOK_AHEAD, LOAD_LOOK_BEHIND, LOAD_CUSTOM };
Expand Down
6 changes: 3 additions & 3 deletions shared/lib_battery_dispatch_automatic_btm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ double dispatch_automatic_behind_the_meter_t::power_grid_target() { return _P_ta

void dispatch_automatic_behind_the_meter_t::setup_rate_forecast()
{
if (_mode == dispatch_t::FORECAST)
if (_mode == dispatch_t::RETAIL_RATE)
{

forecast_setup rate_setup(_steps_per_hour, _nyears);
Expand All @@ -196,7 +196,7 @@ void dispatch_automatic_behind_the_meter_t::update_dispatch(size_t year, size_t
// [kWh] - the maximum energy that can be cycled
double E_max = 0;

if (_mode == dispatch_t::FORECAST)
if (_mode == dispatch_t::RETAIL_RATE)
{
// Hourly rolling forecast horizon
if ((hour_of_year != _hour_last_updated) || m_outage_manager->recover_from_outage)
Expand Down Expand Up @@ -883,7 +883,7 @@ void dispatch_automatic_behind_the_meter_t::costToCycle()
m_cycleCost = 0.01 * capacityPercentDamagePerCycle * m_battReplacementCostPerKWH[curr_year] * _Battery->get_params().nominal_energy;
}
else {
// Should only apply to BattWatts. BattWatts doesn't have price signal dispatch, so this is fine.
// Should only apply to BattWatts. BattWatts doesn't have retal rate dispatch, so this is fine.
m_cycleCost = 0.0;
}
}
Expand Down
4 changes: 2 additions & 2 deletions shared/lib_battery_dispatch_automatic_btm.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "lib_utility_rate.h"

/*
* Data for price signal dispatch (FORECAST) to compare dispatch plans in the cost_based_target_power function
* Data for retail rate dispatch to compare dispatch plans in the cost_based_target_power function
*/
struct dispatch_plan
{
Expand Down Expand Up @@ -154,7 +154,7 @@ class dispatch_automatic_behind_the_meter_t : public dispatch_automatic_t
void target_power(double E_max, size_t idx, FILE* p = NULL, bool debug = false);
void apply_target_power(size_t day_index);

/*! Functions used by price signal dispatch */
/*! Functions used by retail rate dispatch */
double compute_costs(size_t idx, size_t year, size_t hour_of_year, FILE* p = NULL, bool debug = false); // Initial computation of no-dispatch costs, assigned hourly to grid points
void cost_based_target_power(size_t idx, size_t year, size_t hour_of_year, double no_dispatch_cost, double E_max, FILE* p = NULL, const bool debug = false); // Optimizing loop, runs twelve possible dispatch scenarios
void plan_dispatch_for_cost(dispatch_plan& plan, size_t idx, double E_max, double startingEnergy); // Generates each dispatch plan (input argument)
Expand Down
49 changes: 30 additions & 19 deletions shared/lib_battery_powerflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ void BatteryPowerFlow::calculateACConnected()

// Code simplification to remove redundancy for code that should use either critical load or actual load
double calc_load_ac = (m_BatteryPower->isOutageStep ? P_crit_load_ac : P_load_ac);
double P_required_for_load = calc_load_ac;

if (ac_loss_percent_post_battery < 1) { // Account for possible divide by zero
P_required_for_load /= (1 - ac_loss_percent_post_battery);
}

// charging and idle
if (P_battery_ac <= 0)
Expand All @@ -365,11 +370,11 @@ void BatteryPowerFlow::calculateACConnected()
if (m_BatteryPower->chargeOnlySystemExceedLoad) {
P_pv_to_load_ac = P_pv_ac;

if (P_pv_to_load_ac > calc_load_ac) {
P_pv_to_load_ac = calc_load_ac;
if (P_pv_to_load_ac > P_required_for_load) {
P_pv_to_load_ac = P_required_for_load;
}
// Fuel cell goes to load next
P_fuelcell_to_load_ac = std::fmin(calc_load_ac - P_pv_to_load_ac, P_fuelcell_ac);
P_fuelcell_to_load_ac = std::fmin(P_required_for_load - P_pv_to_load_ac, P_fuelcell_ac);
}

// Excess PV can go to battery, if PV can cover charging losses
Expand Down Expand Up @@ -398,11 +403,11 @@ void BatteryPowerFlow::calculateACConnected()
P_pv_to_load_ac = P_pv_ac - P_pv_to_batt_ac;
}

if (P_pv_to_load_ac > calc_load_ac) {
P_pv_to_load_ac = calc_load_ac;
if (P_pv_to_load_ac > P_required_for_load) {
P_pv_to_load_ac = P_required_for_load;
}
// Fuel cell goes to load next
P_fuelcell_to_load_ac = std::fmin(calc_load_ac - P_pv_to_load_ac, P_fuelcell_ac);
P_fuelcell_to_load_ac = std::fmin(P_required_for_load - P_pv_to_load_ac, P_fuelcell_ac);
}

// Fuelcell can also charge battery
Expand Down Expand Up @@ -439,6 +444,7 @@ void BatteryPowerFlow::calculateACConnected()
// discharging, not idle
else
{

// Test if battery is discharging erroneously
if (!m_BatteryPower->canDischarge && P_battery_ac > 0) {
P_batt_to_grid_ac = P_batt_to_load_ac = 0;
Expand All @@ -448,9 +454,9 @@ void BatteryPowerFlow::calculateACConnected()
P_pv_to_load_ac = P_pv_ac;

// Excess PV production, no other component meets load
if (P_pv_ac >= calc_load_ac)
if (P_pv_ac >= P_required_for_load)
{
P_pv_to_load_ac = calc_load_ac;
P_pv_to_load_ac = P_required_for_load;
P_fuelcell_to_load_ac = 0;
P_batt_to_load_ac = 0;

Expand All @@ -459,14 +465,14 @@ void BatteryPowerFlow::calculateACConnected()
P_fuelcell_to_grid_ac = P_fuelcell_ac;
}
else {
P_fuelcell_to_load_ac = std::fmin(P_fuelcell_ac, calc_load_ac - P_pv_to_load_ac);
P_batt_to_load_ac = std::fmin(P_battery_ac - P_system_loss_ac, calc_load_ac - P_pv_to_load_ac - P_fuelcell_to_load_ac);
P_fuelcell_to_load_ac = std::fmin(P_fuelcell_ac, P_required_for_load - P_pv_to_load_ac);
P_batt_to_load_ac = std::fmin(P_battery_ac - P_system_loss_ac, P_required_for_load - P_pv_to_load_ac - P_fuelcell_to_load_ac);
}
}
else {
P_batt_to_load_ac = std::fmin(P_battery_ac, calc_load_ac);
P_fuelcell_to_load_ac = std::fmin(P_fuelcell_ac, calc_load_ac - P_batt_to_load_ac);
P_pv_to_load_ac = std::fmin(std::fmax(0, calc_load_ac - P_fuelcell_to_load_ac - P_batt_to_load_ac), P_pv_ac);
P_batt_to_load_ac = std::fmin(P_battery_ac, P_required_for_load);
P_fuelcell_to_load_ac = std::fmin(P_fuelcell_ac, P_required_for_load - P_batt_to_load_ac);
P_pv_to_load_ac = std::fmin(std::fmax(0, P_required_for_load - P_fuelcell_to_load_ac - P_batt_to_load_ac), P_pv_ac);
P_pv_to_grid_ac = std::fmax(0, P_pv_ac - P_pv_to_load_ac);
P_fuelcell_to_grid_ac = std::fmax(0, P_fuelcell_ac - P_fuelcell_to_load_ac);
}
Expand All @@ -483,11 +489,8 @@ void BatteryPowerFlow::calculateACConnected()
P_fuelcell_to_grid_ac = 0;
}

// Preliminary batt to grid for DC losses
P_batt_to_grid_ac = P_battery_ac - P_system_loss_ac - P_batt_to_load_ac - P_batt_to_pv_inverter;
if (m_BatteryPower->isOutageStep && P_batt_to_grid_ac > tolerance) {
m_BatteryPower->powerBatteryDC = (P_battery_ac - P_batt_to_grid_ac) / m_BatteryPower->singlePointEfficiencyDCToAC;
return calculateACConnected();
}

P_fuelcell_to_grid_ac = P_fuelcell_ac - P_fuelcell_to_load_ac;
P_batt_to_system_loss = P_system_loss_ac;
Expand All @@ -503,15 +506,23 @@ void BatteryPowerFlow::calculateACConnected()
// Compute total system output and grid power flow
P_gen_ac = P_pv_ac + P_fuelcell_ac + P_inverter_draw_ac + P_battery_ac - P_system_loss_ac;

// Final batt to grid for outage accounting
if (P_battery_ac > 0)
{
P_batt_to_grid_ac = P_battery_ac * (1 - ac_loss_percent_post_battery) - P_system_loss_ac - P_batt_to_load_ac - P_batt_to_pv_inverter;
if (m_BatteryPower->isOutageStep && P_batt_to_grid_ac > tolerance) {
m_BatteryPower->powerBatteryDC = (P_battery_ac - P_batt_to_grid_ac) / m_BatteryPower->singlePointEfficiencyDCToAC;
return calculateACConnected();
}
}

// Apply AC losses to powerflow - note that these are applied to gen later
P_pv_to_batt_ac *= (1 - ac_loss_percent_post_battery);
P_pv_to_load_ac *= (1 - ac_loss_percent_post_battery);
P_pv_to_batt_ac *= (1 - ac_loss_percent_post_battery);
P_pv_to_grid_ac *= (1 - ac_loss_percent_post_battery);
P_grid_to_batt_ac *= (1 - ac_loss_percent_post_battery);
P_grid_to_load_ac *= (1 - ac_loss_percent_post_battery);
P_batt_to_load_ac *= (1 - ac_loss_percent_post_battery);
P_batt_to_grid_ac *= (1 - ac_loss_percent_post_battery);
P_fuelcell_to_batt_ac *= (1 - ac_loss_percent_post_battery);
P_fuelcell_to_load_ac *= (1 - ac_loss_percent_post_battery);
P_fuelcell_to_grid_ac *= (1 - ac_loss_percent_post_battery);
Expand Down
23 changes: 14 additions & 9 deletions shared/lib_battery_voltage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,31 +181,35 @@ voltage_t *voltage_table_t::clone() {
return new voltage_table_t(*this);
}

double voltage_table_t::calculate_voltage(double DOD) {
double voltage_table_t::calculate_voltage(double DOD, double I) {
DOD = fmax(0., DOD);
DOD = fmin(DOD, 100.);

size_t row = 0;
while (row < params->voltage_table.size() && DOD > params->voltage_table[row][0]) {
row++;
}
//
if (DOD < tolerance || DOD > 100. - tolerance) {
I = 0.0; // At full or empty, current must go to zero
}

return fmax(slopes[row] * DOD + intercepts[row], 0);
return fmax(slopes[row] * DOD + intercepts[row], 0) - I * params->resistance;
}

void voltage_table_t::set_initial_SOC(double init_soc) {
state->cell_voltage = calculate_voltage(100. - init_soc);
state->cell_voltage = calculate_voltage(100. - init_soc, 0.0);
}

double voltage_table_t::calculate_voltage_for_current(double I, double q, double qmax, double) {
double DOD = (q - I * params->dt_hr) / qmax * 100.;
return calculate_voltage(DOD) * params->num_cells_series;
return calculate_voltage(DOD, I / params->num_strings) * params->num_cells_series;
}


void voltage_table_t::updateVoltage(double q, double qmax, double, const double, double) {
void voltage_table_t::updateVoltage(double q, double qmax, double I, const double, double) {
double DOD = 100. * (1 - q / qmax);
state->cell_voltage = calculate_voltage(DOD);
state->cell_voltage = calculate_voltage(DOD, I / params->num_strings);
}

// helper fx to calculate depth of discharge from current and max capacities
Expand All @@ -215,7 +219,7 @@ double voltage_table_t::calculate_max_charge_w(double q, double qmax, double, do
double current = (q - qmax) / params->dt_hr;
if (max_current)
*max_current = current;
return calculate_voltage(0.) * current * params->num_cells_series;
return calculate_voltage(0., current / params->num_strings) * current * params->num_cells_series;
}

double voltage_table_t::calculate_max_discharge_w(double q, double qmax, double, double *max_current) {
Expand All @@ -230,7 +234,7 @@ double voltage_table_t::calculate_max_discharge_w(double q, double qmax, double,
dod = fmin(100, dod);
dod = fmax(0, dod);
double current = qmax * ((1. - DOD0 / 100.) - (1. - dod / 100.)) / params->dt_hr;
double p = calculate_voltage(dod) * current;
double p = calculate_voltage(dod, current / params->num_strings) * current;
if (p > max_P) {
max_P = p;
max_I = current;
Expand Down Expand Up @@ -291,7 +295,8 @@ double voltage_table_t::calculate_current_for_target_w(double P_watts, double q,
auto DOD_upper = params->voltage_table[upper][0];
auto DOD_lower = params->voltage_table[lower][0];
if (DOD_new <= DOD_upper && DOD_new >= DOD_lower) {
double P = (q - (100. - DOD_new) * qmax/100) * (a * DOD_new + b);
current = qmax * ((1. - DOD / 100.) - (1. - DOD_new / 100.)) / params->dt_hr;
double P = current * (a * DOD_new + b - current / params->num_strings * params->resistance);
if (std::abs(P) > std::abs(P_best)) {
P_best = P;
DOD_best = DOD_new;
Expand Down
2 changes: 1 addition & 1 deletion shared/lib_battery_voltage.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class voltage_table_t : public voltage_t {
std::vector<double> slopes;
std::vector<double> intercepts;

double calculate_voltage(double DOD);
double calculate_voltage(double DOD, double I);

private:
void initialize();
Expand Down
2 changes: 1 addition & 1 deletion shared/lib_utility_rate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ void UtilityRateForecast::initializeMonth(int month, size_t year)

}
else { // Standard demand charges
// Ignore any peak charges lower than the average gross load - this prevents the price signal from showing demand charges on the first hour of each month when the load is not really a peak
// Ignore any peak charges lower than the average gross load - this prevents the retail rate forcast from showing demand charges on the first hour of each month when the load is not really a peak
double avg_load = m_monthly_avg_load_forecast[year * 12 + month];
curr_month.dc_flat_peak = avg_load;
for (int period = 0; period < (int)curr_month.dc_periods.size(); period++)
Expand Down
10 changes: 5 additions & 5 deletions ssc/cmod_battery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ var_info vtab_battery_inputs[] = {
{ SSC_INPUT, SSC_ARRAY, "batt_target_power_monthly", "Grid target power on monthly basis", "kW", "", "BatteryDispatch", "en_batt=1&batt_meter_position=0&batt_dispatch_choice=1", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_target_choice", "Target power input option", "0/1", "0=InputMonthlyTarget,1=InputFullTimeSeries", "BatteryDispatch", "en_batt=1&en_standalone_batt=0&batt_meter_position=0&batt_dispatch_choice=1", "", "" },
{ SSC_INPUT, SSC_ARRAY, "batt_custom_dispatch", "Custom battery power for every time step", "kW", "kWAC if AC-connected, else kWDC", "BatteryDispatch", "en_batt=1&en_standalone_batt=0&batt_dispatch_choice=2","", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_choice", "Battery dispatch algorithm", "0/1/2/3/4/5", "If behind the meter: 0=PeakShaving,1=InputGridTarget,2=InputBatteryPower,3=ManualDispatch,4=PriceSignalForecast,5=SelfConsumption if front of meter: 0=AutomatedEconomic,1=PV_Smoothing,2=InputBatteryPower,3=ManualDispatch", "BatteryDispatch", "en_batt=1", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_choice", "Battery dispatch algorithm", "0/1/2/3/4/5", "If behind the meter: 0=PeakShaving,1=InputGridTarget,2=InputBatteryPower,3=ManualDispatch,4=RetailRateDispatch,5=SelfConsumption if front of meter: 0=AutomatedEconomic,1=PV_Smoothing,2=InputBatteryPower,3=ManualDispatch", "BatteryDispatch", "en_batt=1", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_auto_can_fuelcellcharge", "Charging from fuel cell allowed for automated dispatch?", "0/1", "", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_auto_can_gridcharge", "Grid charging allowed for automated dispatch?", "0/1", "", "BatteryDispatch", "", "", "" },
{ SSC_INPUT, SSC_NUMBER, "batt_dispatch_auto_can_charge", "System charging allowed for automated dispatch?", "0/1", "", "BatteryDispatch", "", "", "" },
Expand Down Expand Up @@ -1036,7 +1036,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c

}

bool cycleCostRelevant = (batt_vars->batt_meter_position == dispatch_t::BEHIND && batt_vars->batt_dispatch == dispatch_t::FORECAST) ||
bool cycleCostRelevant = (batt_vars->batt_meter_position == dispatch_t::BEHIND && batt_vars->batt_dispatch == dispatch_t::RETAIL_RATE) ||
(batt_vars->batt_meter_position == dispatch_t::FRONT && (batt_vars->batt_dispatch != dispatch_t::FOM_MANUAL && batt_vars->batt_dispatch != dispatch_t::FOM_CUSTOM_DISPATCH));
if (cycleCostRelevant && batt_vars->batt_cycle_cost_choice == dispatch_t::MODEL_CYCLE_COST) {
outCostToCycle = vt.allocate("batt_cost_to_cycle", nrec * nyears);
Expand Down Expand Up @@ -1383,7 +1383,7 @@ void battstor::parse_configuration()
prediction_index = 0;
if (batt_meter_position == dispatch_t::BEHIND)
{
if (batt_dispatch == dispatch_t::PEAK_SHAVING || batt_dispatch == dispatch_t::MAINTAIN_TARGET || batt_dispatch == dispatch_t::FORECAST ||
if (batt_dispatch == dispatch_t::PEAK_SHAVING || batt_dispatch == dispatch_t::MAINTAIN_TARGET || batt_dispatch == dispatch_t::RETAIL_RATE ||
batt_dispatch == dispatch_t::SELF_CONSUMPTION)
{
switch (batt_weather_forecast) {
Expand Down Expand Up @@ -1781,7 +1781,7 @@ bool battstor::uses_forecast() {
return batt_vars->batt_dispatch == dispatch_t::FOM_AUTOMATED_ECONOMIC || batt_vars->batt_dispatch == dispatch_t::FOM_PV_SMOOTHING;
}
else {
return batt_vars->batt_dispatch == dispatch_t::FORECAST || dispatch_t::PEAK_SHAVING;
return batt_vars->batt_dispatch == dispatch_t::RETAIL_RATE || dispatch_t::PEAK_SHAVING;
}
}

Expand Down Expand Up @@ -1946,7 +1946,7 @@ void battstor::outputs_topology_dependent()
}
}

bool cycleCostRelevant = (batt_vars->batt_meter_position == dispatch_t::BEHIND && batt_vars->batt_dispatch == dispatch_t::FORECAST) ||
bool cycleCostRelevant = (batt_vars->batt_meter_position == dispatch_t::BEHIND && batt_vars->batt_dispatch == dispatch_t::RETAIL_RATE) ||
(batt_vars->batt_meter_position == dispatch_t::FRONT && (batt_vars->batt_dispatch != dispatch_t::FOM_MANUAL && batt_vars->batt_dispatch != dispatch_t::FOM_CUSTOM_DISPATCH));
if (cycleCostRelevant && batt_vars->batt_cycle_cost_choice == dispatch_t::MODEL_CYCLE_COST) {
outCostToCycle[index] = (ssc_number_t)(dispatch_model->cost_to_cycle_per_kwh());
Expand Down
Loading

0 comments on commit 9a8f784

Please sign in to comment.