From 687e0c32a447037000bbe57a1b1b08842bd49a86 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Tue, 22 Oct 2024 15:14:09 -0600 Subject: [PATCH 01/19] Add a few required variables. Need to make a lot of decisions to use these --- shared/lib_battery_powerflow.h | 3 ++- ssc/cmod_battery.cpp | 1 + ssc/cmod_pvsamv1.cpp | 1 + ssc/common.cpp | 8 ++++++++ ssc/common.h | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/shared/lib_battery_powerflow.h b/shared/lib_battery_powerflow.h index 10d0da630..629a9e48e 100644 --- a/shared/lib_battery_powerflow.h +++ b/shared/lib_battery_powerflow.h @@ -196,7 +196,8 @@ struct BatteryPower double acXfmrLoadLoss; ///< Transformer load loss percent (%) double acXfmrNoLoadLoss; ///< Transformer no-load loss value (kWac) double acXfmrRating; ///< Transformer rating for transformer loss calculations (kWac) - double acLossPostBattery; ///< Expected system and daily losses. Applies to the final AC output including the battery (%) + double acLossPostBattery; ///< Expected system and daily losses. Applies power limiting to final AC output including the battery (%) + double adjustLosses; ///< Battery availability losses (%) bool isOutageStep; diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 4f7503480..017e51df9 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -2274,6 +2274,7 @@ class cm_battery : public compute_module add_var_info(_cm_vtab_battery); add_var_info(vtab_battery_inputs); add_var_info(vtab_forecast_price_signal); + add_var_info(vtab_batt_adjustment_factors); add_var_info(vtab_battery_outputs); add_var_info(vtab_resilience_outputs); add_var_info(vtab_utility_rate_common); diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index 7977bd195..a8f7c9e45 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -3128,6 +3128,7 @@ void cm_pvsamv1::exec() } // Update battery with final gen to compute grid power + // TODO: use this block to apply adjustment losses to PV and battery seperately if (en_batt) { if (batt->is_outage_step(idx % nrec)) { batt->update_grid_power(*this, PVSystem->p_systemACPower[idx], p_crit_load_full[idx], idx); // Updates interconnection losses and similar diff --git a/ssc/common.cpp b/ssc/common.cpp index baaf04f0d..87c648889 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -563,6 +563,14 @@ var_info vtab_sf_adjustment_factors[] = { { SSC_INPUT,SSC_MATRIX , "sf_adjust_periods" , "SF Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "sf_adjust_en_periods=1" , "COLS=3" , ""}, var_info_invalid }; +var_info vtab_batt_adjustment_factors[] = { +{ SSC_INPUT,SSC_NUMBER , "batt_adjust_constant" , "DC Constant loss adjustment", "%", "", "Adjustment Factors", "?=0" , "MAX=100" , ""}, +{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_timeindex" , "Enable lifetime adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, +{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, +{ SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "DC Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "dc_adjust_en_timeindex=1" , "" , ""}, +{ SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "DC Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "dc_adjust_en_periods=1" , "COLS=3" , ""}, +var_info_invalid }; + var_info vtab_financial_capacity_payments[] = { /* VARTYPE DATATYPE NAME LABEL UNITS META GROUP REQUIRED_IF CONSTRAINTS UI_HINTS */ diff --git a/ssc/common.h b/ssc/common.h index 750a1ca8e..8930c9c69 100644 --- a/ssc/common.h +++ b/ssc/common.h @@ -58,6 +58,7 @@ extern var_info vtab_financial_metrics[]; extern var_info vtab_adjustment_factors[]; extern var_info vtab_dc_adjustment_factors[]; +extern var_info vtab_batt_adjustment_factors[]; extern var_info vtab_sf_adjustment_factors[]; extern var_info vtab_technology_outputs[]; extern var_info vtab_grid_curtailment[]; From efd4d872f7eca021e9b259514cb9f83f441710af Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 6 Nov 2024 09:34:02 -0700 Subject: [PATCH 02/19] Implement adjust losses within the capacity model. Not yet fully hooked up to cmod battery --- shared/lib_battery.cpp | 34 +++-- shared/lib_battery.h | 18 ++- shared/lib_battery_capacity.cpp | 48 ++++++- shared/lib_battery_capacity.h | 8 ++ shared/lib_battery_dispatch.cpp | 2 +- shared/lib_battery_dispatch_automatic_btm.cpp | 12 +- shared/lib_battery_dispatch_automatic_fom.cpp | 10 +- .../lib_battery_dispatch_pvsmoothing_fom.cpp | 6 +- shared/logger.cpp | 2 +- ssc/cmod_battery.cpp | 6 +- ssc/cmod_battery_stateful.cpp | 4 +- .../lib_battery_dispatch_automatic_btm_test.h | 3 +- .../lib_battery_dispatch_automatic_fom_test.h | 3 +- .../lib_battery_dispatch_manual_test.h | 3 +- test/shared_test/lib_battery_test.cpp | 132 +++++++++++++++--- test/shared_test/lib_battery_test.h | 9 +- test/shared_test/lib_resilience_test.cpp | 2 +- 17 files changed, 232 insertions(+), 70 deletions(-) diff --git a/shared/lib_battery.cpp b/shared/lib_battery.cpp index 8caab948f..8fed84407 100644 --- a/shared/lib_battery.cpp +++ b/shared/lib_battery.cpp @@ -179,7 +179,7 @@ Define Losses */ void losses_t::initialize() { state = std::make_shared(); - state->loss_kw = 0; + state->ancillary_loss_kw = 0; if (params->loss_choice == losses_params::MONTHLY) { if (params->monthly_charge_loss.size() == 1) { params->monthly_charge_loss = std::vector(12, params->monthly_charge_loss[0]); @@ -210,19 +210,21 @@ void losses_t::initialize() { } } -losses_t::losses_t(const std::vector& monthly_charge, const std::vector& monthly_discharge, const std::vector& monthly_idle) { +losses_t::losses_t(const std::vector& monthly_charge, const std::vector& monthly_discharge, const std::vector& monthly_idle, const std::vector& adjust_losses) { params = std::make_shared(); params->loss_choice = losses_params::MONTHLY; params->monthly_charge_loss = monthly_charge; params->monthly_discharge_loss = monthly_discharge; params->monthly_idle_loss = monthly_idle; + params->adjust_loss = adjust_losses; initialize(); } -losses_t::losses_t(const std::vector& schedule_loss) { +losses_t::losses_t(const std::vector& schedule_loss, const std::vector& adjust_losses) { params = std::make_shared(); params->loss_choice = losses_params::SCHEDULE; params->schedule_loss = schedule_loss; + params->adjust_loss = adjust_losses; initialize(); } @@ -252,18 +254,24 @@ void losses_t::run_losses(size_t lifetimeIndex, double dtHour, double charge_ope // update system losses depending on user input if (params->loss_choice == losses_params::MONTHLY) { if (charge_operation == capacity_state::CHARGE) - state->loss_kw = params->monthly_charge_loss[monthIndex]; + state->ancillary_loss_kw = params->monthly_charge_loss[monthIndex]; if (charge_operation == capacity_state::DISCHARGE) - state->loss_kw = params->monthly_discharge_loss[monthIndex]; + state->ancillary_loss_kw = params->monthly_discharge_loss[monthIndex]; if (charge_operation == capacity_state::NO_CHARGE) - state->loss_kw = params->monthly_idle_loss[monthIndex]; + state->ancillary_loss_kw = params->monthly_idle_loss[monthIndex]; } else if (params->loss_choice == losses_params::SCHEDULE) { - state->loss_kw = params->schedule_loss[lifetimeIndex % params->schedule_loss.size()]; + state->ancillary_loss_kw = params->schedule_loss[lifetimeIndex % params->schedule_loss.size()]; } + + state->adjust_loss_percent = getAvailabilityLoss(lifetimeIndex); } -double losses_t::getLoss() { return state->loss_kw; } +double losses_t::getAncillaryLoss() { return state->ancillary_loss_kw; } + +double losses_t::getAvailabilityLoss(size_t lifetimeIndex) { + return params->adjust_loss[lifetimeIndex % params->adjust_loss.size()]; +} losses_state losses_t::get_state() { return *state; } @@ -584,7 +592,7 @@ double battery_t::run(size_t lifetimeIndex, double &I) { while (iterate_count < 5) { runThermalModel(I, lifetimeIndex); - runCapacityModel(I); + runCapacityModel(I, lifetimeIndex); double numerator = std::abs(I - I_initial); if ((numerator > 0.0) && (numerator / std::abs(I_initial) > tolerance)) { @@ -622,12 +630,14 @@ double battery_t::estimateCycleDamage() { return lifetime->estimateCycleDamage(); } -void battery_t::runCapacityModel(double &I) { +void battery_t::runCapacityModel(double &I, size_t lifetimeIndex) { // Don't update max capacity if the battery is idle if (std::abs(I) > tolerance) { // Need to first update capacity model to ensure temperature accounted for capacity->updateCapacityForThermal(thermal->capacity_percent()); } + double availability_loss = losses->getAvailabilityLoss(lifetimeIndex); + capacity->updateCapacityForAvailability(availability_loss); capacity->updateCapacity(I, params->dt_hr); } @@ -772,8 +782,8 @@ double battery_t::calculate_loss(double power, size_t lifetimeIndex) { } } -double battery_t::getLoss() { - return losses->getLoss(); +double battery_t::getAncillaryLoss() { + return losses->getAncillaryLoss(); } battery_state battery_t::get_state() { return *state; } diff --git a/shared/lib_battery.h b/shared/lib_battery.h index eeb80b45e..2ed411702 100644 --- a/shared/lib_battery.h +++ b/shared/lib_battery.h @@ -151,7 +151,8 @@ class thermal_t { */ struct losses_state { - double loss_kw; + double ancillary_loss_kw; + double adjust_loss_percent; friend std::ostream &operator<<(std::ostream &os, const losses_state &p); }; @@ -166,6 +167,7 @@ struct losses_params { std::vector monthly_discharge_loss; std::vector monthly_idle_loss; std::vector schedule_loss; + std::vector adjust_loss; friend std::ostream &operator<<(std::ostream &os, const losses_params &p); }; @@ -181,8 +183,9 @@ class losses_t { * \param[in] monthly_charge vector (size 1 for annual or 12 for monthly) containing battery system losses when charging (kW) (applied to PV or grid) * \param[in] monthly_discharge vector (size 1 for annual or 12 for monthly) containing battery system losses when discharge (kW) (applied to battery power) * \param[in] monthly_idle vector (size 1 for annual or 12 for monthly) containing battery system losses when idle (kW) (applied to PV or grid) + * \param[in] adjust_losses vector (size 0 for constant or per timestep) containing battery system availability losses (%) (applies to both power and energy capacity - if a system has 4 packs, a 25% loss means one pack is offline) */ - losses_t(const std::vector& monthly_charge, const std::vector& monthly_discharge, const std::vector& monthly_idle); + losses_t(const std::vector& monthly_charge, const std::vector& monthly_discharge, const std::vector& monthly_idle, const std::vector& adjust_losses); /** * \function losses_t @@ -190,8 +193,9 @@ class losses_t { * Construct the losses object for schedule of timeseries losses * * \param[in] schedule_loss vector (size 0 for constant or per timestep) containing battery system losses + * \param[in] adjust_losses vector (size 0 for constant or per timestep) containing battery system availability losses (%) (applies to both power and energy capacity - if a system has 4 packs, a 25% loss means one pack is offline) */ - explicit losses_t(const std::vector& schedule_loss = std::vector(1, 0)); + explicit losses_t(const std::vector& schedule_loss = std::vector(1, 0), const std::vector& adjust_losses = std::vector(1,0)); explicit losses_t(std::shared_ptr p); @@ -203,7 +207,9 @@ class losses_t { void run_losses(size_t lifetimeIndex, double dt_hour, double charge_operation); /// Get the loss at the specified simulation index (year 1) - double getLoss(); + double getAncillaryLoss(); + + double getAvailabilityLoss(size_t lifetimeIndex); losses_state get_state(); @@ -362,7 +368,7 @@ class battery_t { double estimateCycleDamage(); // Run a component level model - void runCapacityModel(double &I); + void runCapacityModel(double &I, size_t lifetimeIndex); void runVoltageModel(); @@ -409,7 +415,7 @@ class battery_t { double calculate_loss(double power, size_t lifetimeIndex); // Get the losses at the current step - double getLoss(); + double getAncillaryLoss(); battery_state get_state(); diff --git a/shared/lib_battery_capacity.cpp b/shared/lib_battery_capacity.cpp index 1be184caf..0fc94bbe8 100644 --- a/shared/lib_battery_capacity.cpp +++ b/shared/lib_battery_capacity.cpp @@ -140,16 +140,19 @@ capacity_params capacity_t::get_params() { return *params; } capacity_state capacity_t::get_state() { return *state; } void capacity_t::check_SOC() { - double q_upper = state->qmax_lifetime * params->maximum_SOC * 0.01; - double q_lower = state->qmax_lifetime * params->minimum_SOC * 0.01; + double max_SOC_available = params->maximum_SOC * (1 - state->percent_unavailable); + double min_SOC_available = params->minimum_SOC * (1 - state->percent_unavailable); + + double q_upper = state->qmax_lifetime * max_SOC_available * 0.01; + double q_lower = state->qmax_lifetime * min_SOC_available * 0.01; // set capacity to upper thermal limit - if (q_upper > state->qmax_thermal * params->maximum_SOC * 0.01) { - q_upper = state->qmax_thermal * params->maximum_SOC * 0.01; + if (q_upper > state->qmax_thermal * max_SOC_available * 0.01) { + q_upper = state->qmax_thermal * max_SOC_available * 0.01; } // do this so battery can cycle full depth and we calculate correct SOC min - if (q_lower > state->qmax_thermal * params->minimum_SOC * 0.01) { - q_lower = state->qmax_thermal * params->minimum_SOC * 0.01; + if (q_lower > state->qmax_thermal * min_SOC_available * 0.01) { + q_lower = state->qmax_thermal * min_SOC_available * 0.01; } if (state->q0 > q_upper + tolerance) { @@ -175,6 +178,7 @@ void capacity_t::check_SOC() { } void capacity_t::update_SOC() { + // Want to continue to define SOC as nameplate minus degradation (availability losses lower SOC, not nameplate) double max = fmin(state->qmax_lifetime, state->qmax_thermal); if (max == 0) { state->q0 = 0; @@ -282,6 +286,8 @@ void capacity_kibam_t::replace_battery(double replacement_percent) { state->leadacid.q2_0 = state->q0 - state->leadacid.q1_0; state->SOC = params->initial_SOC; state->SOC_prev = 50; + state->percent_unavailable = 0.0; + state->percent_unavailable_prev = 0.0; update_SOC(); } @@ -438,6 +444,22 @@ void capacity_kibam_t::updateCapacityForLifetime(double capacity_percent) { update_SOC(); } +void capacity_kibam_t::updateCapacityForAvailability(double availability_percent) { + state->percent_unavailable_prev = state->percent_unavailable; + state->percent_unavailable = availability_percent; + + double timestep_loss = state->percent_unavailable_prev - state->percent_unavailable; + if (timestep_loss > 1e-7) { + double q0_orig = state->q0; + state->q0 *= (1 - timestep_loss); + state->leadacid.q1 *= (1 - timestep_loss); + state->leadacid.q2 *= (1 - timestep_loss); + state->I_loss += (q0_orig - state->q0) / params->dt_hr; + } + + update_SOC(); +} + double capacity_kibam_t::q1() { return state->leadacid.q1_0; } double capacity_kibam_t::q2() { return state->leadacid.q2_0; } @@ -533,6 +555,20 @@ void capacity_lithium_ion_t::updateCapacityForLifetime(double capacity_percent) update_SOC(); } +void capacity_lithium_ion_t::updateCapacityForAvailability(double availability_percent) { + state->percent_unavailable_prev = state->percent_unavailable; + state->percent_unavailable = availability_percent; + + double timestep_loss = state->percent_unavailable - state->percent_unavailable_prev; + if (timestep_loss > 1e-7) { + double q0_orig = state->q0; + state->q0 *= (1 - timestep_loss); + state->I_loss += (q0_orig - state->q0) / params->dt_hr; + } + + update_SOC(); +} + double capacity_lithium_ion_t::q1() { return state->q0; } double capacity_lithium_ion_t::q10() { return state->qmax_lifetime; } diff --git a/shared/lib_battery_capacity.h b/shared/lib_battery_capacity.h index 41e8d7a58..3c67cf0df 100644 --- a/shared/lib_battery_capacity.h +++ b/shared/lib_battery_capacity.h @@ -47,6 +47,8 @@ struct capacity_state { double I_loss; // [A] - Lifetime and thermal losses double SOC; // [%] - State of Charge double SOC_prev; // [%] - previous step + double percent_unavailable; // [%] - Percent of system that is down + double percent_unavailable_prev; // [%] - Percent of system that was down last step enum { CHARGE, NO_CHARGE, DISCHARGE @@ -120,6 +122,8 @@ class capacity_t { virtual void replace_battery(double replacement_percent) = 0; + virtual void updateCapacityForAvailability(double availability_percent) = 0; + void change_SOC_limits(double min, double max) { params->minimum_SOC = min; params->maximum_SOC = max; @@ -199,6 +203,8 @@ class capacity_kibam_t : public capacity_t { void replace_battery(double replacement_percent) override; + void updateCapacityForAvailability(double availability_percent) override; + double q1() override; // Available charge double q2(); // Bound charge double q10() override; // Capacity at 10 hour discharge rate @@ -254,6 +260,8 @@ class capacity_lithium_ion_t : public capacity_t { void replace_battery(double replacement_percent) override; + void updateCapacityForAvailability(double availability_percent) override; + double q1() override; // Available charge double q10() override; // Capacity at 10 hour discharge rate }; diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index a98e1c428..552d1607d 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -436,7 +436,7 @@ void dispatch_t::runDispatch(size_t lifetimeIndex) // Run Battery Model to update charge based on charge/discharge m_batteryPower->powerBatteryDC = _Battery->run(lifetimeIndex, I); - m_batteryPower->powerSystemLoss = _Battery->getLoss(); + m_batteryPower->powerSystemLoss = _Battery->getAncillaryLoss(); // Update power flow calculations, calculate AC power, and check the constraints m_batteryPowerFlow->calculate(); diff --git a/shared/lib_battery_dispatch_automatic_btm.cpp b/shared/lib_battery_dispatch_automatic_btm.cpp index 9ef100e67..c9053fd29 100644 --- a/shared/lib_battery_dispatch_automatic_btm.cpp +++ b/shared/lib_battery_dispatch_automatic_btm.cpp @@ -244,13 +244,13 @@ void dispatch_automatic_behind_the_meter_t::update_dispatch(size_t year, size_t { // extract input power by modifying lifetime index to year 1 m_batteryPower->powerBatteryTarget = _P_battery_use[idx % (8760 * _steps_per_hour)]; - double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, idx); // Battery is responsible for covering discharge losses + double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, idx); // Battery is responsible for covering discharge losses if (m_batteryPower->connectionMode == AC_CONNECTED) { - m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw); + m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw); } else if (m_batteryPower->powerBatteryTarget > 0) { // Adjust for DC discharge losses - m_batteryPower->powerBatteryTarget += loss_kw; + m_batteryPower->powerBatteryTarget += ancillary_loss_kw; } } @@ -882,14 +882,14 @@ void dispatch_automatic_behind_the_meter_t::check_power_restrictions(double& pow void dispatch_automatic_behind_the_meter_t::set_battery_power(size_t idx, size_t day_index, FILE *p, const bool debug) { - double loss_kw = _Battery->calculate_loss(_P_battery_use[day_index], idx); // Units are kWac for AC connected batteries, and kWdc for DC connected + double ancillary_loss_kw = _Battery->calculate_loss(_P_battery_use[day_index], idx); // Units are kWac for AC connected batteries, and kWdc for DC connected // At this point the target power is expressed in AC, must convert to DC for battery if (m_batteryPower->connectionMode == m_batteryPower->AC_CONNECTED) { - _P_battery_use[day_index] = m_batteryPower->adjustForACEfficiencies(_P_battery_use[day_index], loss_kw); + _P_battery_use[day_index] = m_batteryPower->adjustForACEfficiencies(_P_battery_use[day_index], ancillary_loss_kw); } else { - _P_battery_use[day_index] = m_batteryPower->adjustForDCEfficiencies(_P_battery_use[day_index], loss_kw); + _P_battery_use[day_index] = m_batteryPower->adjustForDCEfficiencies(_P_battery_use[day_index], ancillary_loss_kw); } if (debug) diff --git a/shared/lib_battery_dispatch_automatic_fom.cpp b/shared/lib_battery_dispatch_automatic_fom.cpp index 9e828030b..9e021193e 100644 --- a/shared/lib_battery_dispatch_automatic_fom.cpp +++ b/shared/lib_battery_dispatch_automatic_fom.cpp @@ -348,9 +348,9 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho // Discharge if we are in a high-price period and have battery and inverter capacity if (highDischargeValuePeriod && revenueToDischarge > 0 && excessAcCapacity && batteryHasDischargeCapacity && interconnectionHasCapacity) { - double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses + double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses if (m_batteryPower->connectionMode == BatteryPower::DC_CONNECTED) { - powerBattery = _inverter_paco + loss_kw - m_batteryPower->powerSystem; + powerBattery = _inverter_paco + ancillary_loss_kw - m_batteryPower->powerSystem; } else { powerBattery = _inverter_paco; // AC connected battery is already maxed out by AC power limit, cannot increase dispatch to ccover losses @@ -365,13 +365,13 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho { // extract input power by modifying lifetime index to year 1 m_batteryPower->powerBatteryTarget = _P_battery_use[lifetimeIndex % (8760 * _steps_per_hour)]; - double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses + double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses if (m_batteryPower->connectionMode == AC_CONNECTED){ - m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw); + m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw); } else if (m_batteryPower->powerBatteryTarget > 0) { // Adjust for DC discharge losses - m_batteryPower->powerBatteryTarget += loss_kw; + m_batteryPower->powerBatteryTarget += ancillary_loss_kw; } } diff --git a/shared/lib_battery_dispatch_pvsmoothing_fom.cpp b/shared/lib_battery_dispatch_pvsmoothing_fom.cpp index ca1d22ef2..85ab764c4 100644 --- a/shared/lib_battery_dispatch_pvsmoothing_fom.cpp +++ b/shared/lib_battery_dispatch_pvsmoothing_fom.cpp @@ -361,13 +361,13 @@ void dispatch_pvsmoothing_front_of_meter_t::update_dispatch(size_t year, size_t // adjust for loss and efficiencies m_batteryPower->powerBatteryTarget = (m_batt_dispatch_pvs_nameplate_ac > 0 ? m_batt_dispatch_pvs_nameplate_ac * m_batt_dispatch_pvs_battpower : m_batt_dispatch_pvs_battpower); - double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses + double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses if (m_batteryPower->connectionMode == AC_CONNECTED) { - m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw); + m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw); } else if (m_batteryPower->powerBatteryTarget > 0) { // Adjust for DC discharge losses - m_batteryPower->powerBatteryTarget += loss_kw; + m_batteryPower->powerBatteryTarget += ancillary_loss_kw; } m_batteryPower->powerBatteryDC = m_batteryPower->powerBatteryTarget; diff --git a/shared/logger.cpp b/shared/logger.cpp index a4f6ddc9e..b2196641b 100644 --- a/shared/logger.cpp +++ b/shared/logger.cpp @@ -243,7 +243,7 @@ std::ostream &operator<<(std::ostream &os, const thermal_params &p) { std::ostream &operator<<(std::ostream& os, const losses_state &p) { char buf[256]; - sprintf(buf, R"("losses_state": { "loss_percent": %.3f })", p.loss_kw); + sprintf(buf, R"("losses_state": { "loss_percent": %.3f })", p.ancillary_loss_kw); os << buf; return os; } diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 5a9c4634a..a8b808015 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1172,13 +1172,15 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c batt_vars->batt_Qfull_flow, batt_vars->batt_initial_SOC, batt_vars->batt_maximum_SOC, batt_vars->batt_minimum_SOC, dt_hr); } + // TODO: fix this! + std::vector adj_losses; if (batt_vars->batt_loss_choice == losses_params::MONTHLY) { if (*std::min_element(batt_vars->batt_losses_charging.begin(), batt_vars->batt_losses_charging.end()) < 0 || *std::min_element(batt_vars->batt_losses_discharging.begin(), batt_vars->batt_losses_discharging.end()) < 0 || *std::min_element(batt_vars->batt_losses_idle.begin(), batt_vars->batt_losses_idle.end()) < 0) { throw exec_error("battery", "Battery loss inputs batt_losses_charging, batt_losses_discharging, and batt_losses_idle cannot include negative numbers."); } - losses_model = new losses_t(batt_vars->batt_losses_charging, batt_vars->batt_losses_discharging, batt_vars->batt_losses_idle); + losses_model = new losses_t(batt_vars->batt_losses_charging, batt_vars->batt_losses_discharging, batt_vars->batt_losses_idle, adj_losses); } else if (batt_vars->batt_loss_choice == losses_params::SCHEDULE) { if (!(batt_vars->batt_losses.size() == 1 || batt_vars->batt_losses.size() == nrec)) { @@ -1187,7 +1189,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c if (*std::min_element(batt_vars->batt_losses.begin(), batt_vars->batt_losses.end()) < 0) { throw exec_error("battery", "Battery loss input batt_losses cannot include negative numbers."); } - losses_model = new losses_t(batt_vars->batt_losses); + losses_model = new losses_t(batt_vars->batt_losses, adj_losses); } else { losses_model = new losses_t(); diff --git a/ssc/cmod_battery_stateful.cpp b/ssc/cmod_battery_stateful.cpp index 03d80973c..917d3f1c6 100644 --- a/ssc/cmod_battery_stateful.cpp +++ b/ssc/cmod_battery_stateful.cpp @@ -296,7 +296,7 @@ void write_battery_state(const battery_state& state, var_table* vt) { } } - vt->assign_match_case("loss_kw", state.losses->loss_kw); + vt->assign_match_case("loss_kw", state.losses->ancillary_loss_kw); vt->assign_match_case("n_replacements", state.replacement->n_replacements); vt->assign_match_case("indices_replaced", state.replacement->indices_replaced); @@ -410,7 +410,7 @@ void read_battery_state(battery_state& state, var_table* vt) { } } - vt_get_number(vt, "loss_kw", &state.losses->loss_kw); + vt_get_number(vt, "loss_kw", &state.losses->ancillary_loss_kw); vt_get_int(vt, "n_replacements", &state.replacement->n_replacements); vt_get_array_vec(vt, "indices_replaced", state.replacement->indices_replaced); diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.h b/test/shared_test/lib_battery_dispatch_automatic_btm_test.h index df3d61c70..2e30078a3 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.h +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.h @@ -128,7 +128,8 @@ class AutoBTMTest_lib_battery_dispatch : public BatteryProperties , public Dispa std::vector charging_losses(12, 1); // Monthly losses std::vector discharging_losses(12, 2); std::vector idle_losses(12, 0.5); - lossModel = new losses_t(charging_losses, discharging_losses, idle_losses); + std::vector adjust_losses(8760, 0); + lossModel = new losses_t(charging_losses, discharging_losses, idle_losses, adjust_losses); batteryModel = new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel); int numberOfInverters = 40; diff --git a/test/shared_test/lib_battery_dispatch_automatic_fom_test.h b/test/shared_test/lib_battery_dispatch_automatic_fom_test.h index 96cf981e8..e75902c64 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_fom_test.h +++ b/test/shared_test/lib_battery_dispatch_automatic_fom_test.h @@ -104,7 +104,8 @@ class AutoFOM_lib_battery_dispatch : public BatteryProperties , public DispatchP std::vector charging_losses(12, 10); // Monthly losses std::vector discharging_losses(12, 20); std::vector idle_losses(12, 5); - lossModel = new losses_t(charging_losses, discharging_losses, idle_losses); + std::vector adjust_losses(8760, 0); + lossModel = new losses_t(charging_losses, discharging_losses, idle_losses, adjust_losses); batteryModel = new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel); int numberOfInverters = 40; diff --git a/test/shared_test/lib_battery_dispatch_manual_test.h b/test/shared_test/lib_battery_dispatch_manual_test.h index b5b0dfdee..50e8fd2ce 100644 --- a/test/shared_test/lib_battery_dispatch_manual_test.h +++ b/test/shared_test/lib_battery_dispatch_manual_test.h @@ -124,8 +124,9 @@ class ManualTest_lib_battery_dispatch_losses : public ManualTest_lib_battery_dis std::vector charging_losses(12, 1); // Monthly losses std::vector discharging_losses(12, 2); std::vector idle_losses(12, 0.5); + std::vector adjust_losses(8760, 0); - lossModel = new losses_t(charging_losses, discharging_losses, idle_losses); + lossModel = new losses_t(charging_losses, discharging_losses, idle_losses, adjust_losses); batteryModel = new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel); int numberOfInverters = 1; diff --git a/test/shared_test/lib_battery_test.cpp b/test/shared_test/lib_battery_test.cpp index 7af0f9a6c..23049eaa0 100644 --- a/test/shared_test/lib_battery_test.cpp +++ b/test/shared_test/lib_battery_test.cpp @@ -179,7 +179,7 @@ TEST_F(lib_battery_thermal_test, updateTemperatureTestSubMinute) { } TEST_F(lib_battery_losses_test, MonthlyLossesTest){ - model = std::unique_ptr(new losses_t(chargingLosses, dischargingLosses, chargingLosses)); + model = std::unique_ptr(new losses_t(chargingLosses, dischargingLosses, chargingLosses, adjustLosses)); // losses for charging and idling are the same int charge_mode = capacity_state::CHARGE; @@ -187,30 +187,30 @@ TEST_F(lib_battery_losses_test, MonthlyLossesTest){ size_t idx = 0; double dt_hr = 1; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 0, tol) << "MonthlyLossesTest: 1"; + EXPECT_NEAR(model->getAncillaryLoss(), 0, tol) << "MonthlyLossesTest: 1"; idx = 40 * 24; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 1, tol) << "MonthlyLossesTest: 2"; + EXPECT_NEAR(model->getAncillaryLoss(), 1, tol) << "MonthlyLossesTest: 2"; idx = 70 * 24; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 2, tol) << "MonthlyLossesTest: 3"; + EXPECT_NEAR(model->getAncillaryLoss(), 2, tol) << "MonthlyLossesTest: 3"; // discharging charge_mode = capacity_state::DISCHARGE; idx = 0; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 1, tol) << "MonthlyLossesTest: 4"; + EXPECT_NEAR(model->getAncillaryLoss(), 1, tol) << "MonthlyLossesTest: 4"; idx = 40 * 24; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 2, tol) << "MonthlyLossesTest: 5"; + EXPECT_NEAR(model->getAncillaryLoss(), 2, tol) << "MonthlyLossesTest: 5"; idx = 70 * 24; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 3, tol) << "MonthlyLossesTest: 6"; + EXPECT_NEAR(model->getAncillaryLoss(), 3, tol) << "MonthlyLossesTest: 6"; } @@ -222,15 +222,15 @@ TEST_F(lib_battery_losses_test, TimeSeriesLossesTest){ size_t idx = 0; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 0, tol) << "TimeSeriesLossesTest: 1"; + EXPECT_NEAR(model->getAncillaryLoss(), 0, tol) << "TimeSeriesLossesTest: 1"; idx = 40; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 40./8760, tol) << "TimeSeriesLossesTest: 2"; + EXPECT_NEAR(model->getAncillaryLoss(), 40./8760, tol) << "TimeSeriesLossesTest: 2"; idx = 70; model->run_losses(idx, dt_hour, charge_mode); - EXPECT_NEAR(model->getLoss(), 70./8760, tol) << "TimeSeriesLossesTest: 3"; + EXPECT_NEAR(model->getAncillaryLoss(), 70./8760, tol) << "TimeSeriesLossesTest: 3"; } @@ -247,7 +247,7 @@ TEST_F(lib_battery_test, runTestCycleAt1C){ // std::cerr << "\n" << idx << ": " << capacity_passed << "\n"; auto s = battery_state_test(lifetime_params::CALCYC); - s.capacity = {479.75, 1000, 960.01, 20.25, 0, 49.97, 52.09, 2}; + s.capacity = {479.75, 1000, 960.01, 20.25, 0, 49.97, 52.09, 0.0, 0.0, 2}; s.batt_voltage = 552.03; s.lifetime.calendar->q_relative_calendar = 102; s.lifetime.cycle->q_relative_cycle = 100; @@ -262,7 +262,7 @@ TEST_F(lib_battery_test, runTestCycleAt1C){ } // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; // the SOC isn't at 5 so it means the controller is not able to calculate a current/voltage at which to discharge to 5 - s.capacity = {54.5, 1000, 960.01, 20.25, 0, 5.67, 7.79, 2}; + s.capacity = {54.5, 1000, 960.01, 20.25, 0, 5.67, 7.79, 0.0, 0.0, 2}; s.batt_voltage = 470.17; s.lifetime.day_age_of_battery = 0.875; s.lifetime.q_relative = 100; @@ -288,7 +288,7 @@ TEST_F(lib_battery_test, runTestCycleAt1C){ } // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; // the SOC isn't at 5 so it means the controller is not able to calculate a current/voltage at which to discharge to 5 - s.capacity = { 45.62, 920.29, 883.48, 8.995, 0, 5.164, 6.182, 2}; + s.capacity = { 45.62, 920.29, 883.48, 8.995, 0, 5.164, 6.182, 0.0, 0.0, 2}; s.batt_voltage = 465.54; s.lifetime.q_relative = 93.08; s.lifetime.cycle->q_relative_cycle = 92.03; @@ -320,7 +320,7 @@ TEST_F(lib_battery_test, runTestCycleAt3C){ // std::cerr << "\n" << idx << ": " << capacity_passed << "\n"; auto s = battery_state_test(lifetime_params::CALCYC); - s.capacity = {439.25, 1000, 960.02, 60.75, 0, 45.75, 52.08, 2}; + s.capacity = {439.25, 1000, 960.02, 60.75, 0, 45.75, 52.08, 0.0, 0.0, 2}; s.batt_voltage = 550.10; s.lifetime.q_relative = 100; s.lifetime.cycle->q_relative_cycle = 100; @@ -336,7 +336,7 @@ TEST_F(lib_battery_test, runTestCycleAt3C){ } // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; // the SOC isn't at 5 so it means the controller is not able to calculate a current/voltage at which to discharge to 5 - s.capacity = {48.01, 1000, 960.11, 26.74, 0, 5.00, 7.78, 2}; + s.capacity = {48.01, 1000, 960.11, 26.74, 0, 5.00, 7.78, 0.0, 0.0, 2}; s.batt_voltage = 463.93; s.lifetime.day_age_of_battery = 0.29; s.lifetime.q_relative = 101.98; @@ -360,7 +360,7 @@ TEST_F(lib_battery_test, runTestCycleAt3C){ } // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; // the SOC isn't at 5 so it means the controller is not able to calculate a current/voltage at which to discharge to 5 - s.capacity = { 47.106, 920.30, 883.49, 9.08, 0, 5.33, 6.36, 2}; + s.capacity = { 47.106, 920.30, 883.49, 9.08, 0, 5.33, 6.36, 0.0, 0.0, 2}; s.batt_voltage = 467.105; s.lifetime.q_relative = 93.08; s.lifetime.day_age_of_battery = 2591.17; @@ -800,7 +800,7 @@ TEST_F(lib_battery_test, NMCLifeModel) { auto capacityModelNMC = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); auto voltageModelNMC = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, Vcut, C_rate, resistance, dtHour); - auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); auto batteryNMC = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModelNMC, voltageModelNMC, lifetimeModelNMC, thermalModelNMC, lossModelNMC)); double I = Qfull * n_strings * 2; @@ -836,7 +836,7 @@ TEST_F(lib_battery_test, AdaptiveTimestepNMC) { auto capacityModelNMC = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); auto voltageModelNMC = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, Vcut, C_rate, resistance, dtHour); - auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); batteryModel = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModelNMC, voltageModelNMC, lifetimeModelNMC, thermalModelNMC, lossModelNMC)); @@ -937,7 +937,7 @@ TEST_F(lib_battery_test, AdaptiveTimestepNonIntegerStep) { auto capacityModelNMC = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); auto voltageModelNMC = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, 0, C_rate, resistance, dtHour); - auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); auto batt_adaptive = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModelNMC, voltageModelNMC, lifetimeModelNMC, thermalModelNMC, lossModelNMC)); @@ -956,7 +956,7 @@ TEST_F(lib_battery_test, LMOLTOLifeModel) { auto capacityModelNMC = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); auto voltageModelNMC = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, Vcut, C_rate, resistance, dtHour); - auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + auto lossModelNMC = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); auto batteryNMC = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModelNMC, voltageModelNMC, lifetimeModelNMC, thermalModelNMC, lossModelNMC)); double I = Qfull * n_strings * 2; @@ -986,7 +986,7 @@ TEST_F(lib_battery_test, AdaptiveTimestepLMOLTO) { auto capacityModel = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); auto voltageModel = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, Vcut, C_rate, resistance, dtHour); - auto lossModel = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + auto lossModel = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); batteryModel = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel)); @@ -1078,3 +1078,93 @@ TEST_F(lib_battery_test, AdaptiveTimestepLMOLTO) { delete batt_adaptive; delete batt_subhourly; } + +TEST_F(lib_battery_test, testCyclesWithAvailabilityLosses) { + auto lifetimeModel = new lifetime_calendar_cycle_t(cycleLifeMatrix, dtHour, 1.02, 2.66e-3, -7280, 930); + auto thermalModel = new thermal_t(dtHour, mass, surface_area, resistance, Cp, h, capacityVsTemperature, T_room); + auto capacityModel = new capacity_lithium_ion_t(q, SOC_init, SOC_max, SOC_min, dtHour); + auto voltageModel = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, + Vcut, C_rate, resistance, dtHour); + + adjustLosses.clear(); + for (size_t i = 0; i < 8760; i++) { + adjustLosses.push_back(0.5); + } + + auto lossModel = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); + + batteryModel = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel)); + + + size_t idx = 0; + double capacity_passed = 0.; + double I = Qfull * n_strings; + batteryModel->run(idx++, I); + capacity_passed += batteryModel->I() * batteryModel->V() / 1000.; + // std::cerr << "\n" << idx << ": " << capacity_passed << "\n"; + + auto s = battery_state_test(lifetime_params::CALCYC); + // 50% availabilty loss means half of SOC lost out of the gate + s.capacity = { 229.75, 1000, 960.0, 20.25, 0, 23.93, 26.04, 0.5, 0.0, 2 }; + s.batt_voltage = 532.94; + s.lifetime.calendar->q_relative_calendar = 102; + s.lifetime.cycle->q_relative_cycle = 100; + s.lifetime.cycle->rainflow_jlt = 1; + s.lifetime.q_relative = 100; + s.thermal = { 96.0, 20.00, 20 }; + compareState(batteryModel, s, "testCyclesWithAvailabilityLosses: 1"); + + while (batteryModel->SOC() > SOC_min + 1) { + batteryModel->run(idx++, I); + capacity_passed += batteryModel->I() * batteryModel->V() / 1000.; + } + // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; + // + s.capacity = { 47.5, 1000, 960.015, 20.25, 0, 4.94, 7.05, 0.5, 0.5, 2 }; + s.batt_voltage = 463.43; + s.lifetime.day_age_of_battery = 0.375; + s.lifetime.q_relative = 100; + s.lifetime.cycle->q_relative_cycle = 100; + s.lifetime.calendar->q_relative_calendar = 101.99; + s.lifetime.calendar->dq_relative_calendar_old = 0.0002; + s.thermal = { 96.01, 20.01, 20 }; + compareState(batteryModel, s, "testCyclesWithAvailabilityLosses: 2"); + + size_t n_cycles = 400; + + while (n_cycles-- > 0) { + I *= -1; + while (batteryModel->SOC() < SOC_max * 0.5 - 1) { + batteryModel->run(idx++, I); + capacity_passed += -batteryModel->I() * batteryModel->V() / 1000.; + } + I *= -1; + while (batteryModel->SOC() > SOC_min + 1) { + batteryModel->run(idx++, I); + capacity_passed += batteryModel->I() * batteryModel->V() / 1000.; + } + } + // std::cerr << idx << ": soc " << batteryModel->SOC() << ", cap " << capacity_passed << "\n"; + // Lower DOD cycles relative to runTestCycleAt1C means lower degradation after 400 cycles + s.capacity = { 50.44, 960.60, 922.18, 9.31, 0, 5.47, 6.48, 0.5, 0.5, 2 }; + s.batt_voltage = 468.37; + s.lifetime.q_relative = 93.08; + s.lifetime.cycle->q_relative_cycle = 96.06; + s.lifetime.n_cycles = 399; + s.lifetime.cycle_range = 41.39; + s.lifetime.average_range = 42.02; + s.lifetime.cycle->rainflow_Xlt = 41.40; + s.lifetime.cycle->rainflow_Ylt = 42.03; + s.lifetime.cycle->rainflow_jlt = 3; + s.lifetime.day_age_of_battery = 972.20; + s.lifetime.calendar->q_relative_calendar = 101.25; + s.lifetime.calendar->dq_relative_calendar_old = 0.007; + s.thermal = { 96.0, 20.00, 20 }; + s.last_idx = 32991; + + compareState(batteryModel, s, "testCyclesWithAvailabilityLosses: 3"); + + EXPECT_NEAR(capacity_passed, 167601, 1000) << "Current passing through cell"; + double qmax = fmax(s.capacity.qmax_lifetime, s.capacity.qmax_thermal); + EXPECT_NEAR(qmax / q, 0.9606, 0.01) << "capacity relative to max capacity"; +} diff --git a/test/shared_test/lib_battery_test.h b/test/shared_test/lib_battery_test.h index 2ed8e2325..2b9d389ad 100644 --- a/test/shared_test/lib_battery_test.h +++ b/test/shared_test/lib_battery_test.h @@ -100,6 +100,7 @@ class lib_battery_losses_test : public ::testing::Test std::vector dischargingLosses; std::vector fullLosses; + std::vector adjustLosses; double dt_hour = 1; int nyears = 1; @@ -114,6 +115,7 @@ class lib_battery_losses_test : public ::testing::Test } for (size_t i = 0; i < 8760; i++) { fullLosses.push_back((double)i/8760); + adjustLosses.push_back(0.0); } } }; @@ -207,6 +209,7 @@ class lib_battery_test : public ::testing::Test std::vector fullLosses; std::vector fullLossesMinute; int lossChoice; + std::vector adjustLosses; // battery int chemistry; @@ -273,6 +276,10 @@ class lib_battery_test : public ::testing::Test fullLossesMinute.push_back(0); } lossChoice = 0; + for (size_t i = 0; i < 8760; i++) { + adjustLosses.push_back(0); + } + // battery chemistry = 1; @@ -283,7 +290,7 @@ class lib_battery_test : public ::testing::Test C_rate, resistance, dtHour ); lifetimeModel = new lifetime_calendar_cycle_t(cycleLifeMatrix, dtHour, 1.02, 2.66e-3, -7280, 930); thermalModel = new thermal_t(dtHour, mass, surface_area, resistance, Cp, h, capacityVsTemperature, T_room); - lossModel = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses); + lossModel = new losses_t(monthlyLosses, monthlyLosses, monthlyLosses, adjustLosses); batteryModel = std::unique_ptr(new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel)); } diff --git a/test/shared_test/lib_resilience_test.cpp b/test/shared_test/lib_resilience_test.cpp index 8531e2220..70cb33ccf 100644 --- a/test/shared_test/lib_resilience_test.cpp +++ b/test/shared_test/lib_resilience_test.cpp @@ -294,7 +294,7 @@ TEST_F(ResilienceTest_lib_resilience, PVWattsSetUp) batt->advance(vartab, ac[count], 500); // printf("%f\t current, %f\t voltage, %f\t losses, %f\t power\n", -// cap->I(), vol->V(), batt->battery_model->losses_model()->getLoss(count), power_model->powerBatteryDC); +// cap->I(), vol->V(), batt->battery_model->losses_model()->getAncillaryLoss(count), power_model->powerBatteryDC); count ++; } From e1b08dfc95fe89bf1b63802a2b842b3be12b22db Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 6 Nov 2024 16:07:47 -0700 Subject: [PATCH 03/19] mid-stream updates for power losses --- shared/lib_battery_dispatch.cpp | 44 +++++++++++-------- shared/lib_battery_dispatch_automatic_fom.cpp | 2 +- shared/lib_battery_powerflow.cpp | 2 +- ssc/cmod_pvsamv1.cpp | 8 +--- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index 552d1607d..8bf484898 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -364,18 +364,20 @@ bool dispatch_t::restrict_power(double& I) // charging if (powerBattery < 0) { - if (std::abs(powerBattery) > m_batteryPower->powerBatteryChargeMaxDC * (1 + low_tolerance)) + double max_charge_dc = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryChargeMaxDC; + double max_charge_ac = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryChargeMaxAC; + if (std::abs(powerBattery) > max_charge_dc * (1 + low_tolerance)) { - dP = std::abs(m_batteryPower->powerBatteryChargeMaxDC - std::abs(powerBattery)); + dP = std::abs(max_charge_dc - std::abs(powerBattery)); // increase (reduce) charging magnitude by percentage I -= (dP / std::abs(powerBattery)) * I; iterate = true; } else if (m_batteryPower->connectionMode == m_batteryPower->AC_CONNECTED && - std::fabs(powerBatteryAC) > m_batteryPower->powerBatteryChargeMaxAC * (1 + low_tolerance)) + std::fabs(powerBatteryAC) > max_charge_ac * (1 + low_tolerance)) { - dP = std::abs(m_batteryPower->powerBatteryChargeMaxAC - std::abs(powerBatteryAC)); + dP = std::abs(max_charge_ac - std::abs(powerBatteryAC)); // increase (reduce) charging magnitude by percentage I -= (dP / std::abs(powerBattery)) * I; @@ -394,17 +396,19 @@ bool dispatch_t::restrict_power(double& I) } else { - if (std::abs(powerBattery) > m_batteryPower->powerBatteryDischargeMaxDC * (1 + low_tolerance)) + double max_discharge_dc = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxDC; + double max_discharge_ac = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxAC; + if (std::abs(powerBattery) > max_discharge_dc * (1 + low_tolerance)) { - dP = std::abs(m_batteryPower->powerBatteryDischargeMaxDC - powerBattery); + dP = std::abs(max_discharge_dc - powerBattery); // decrease discharging magnitude I -= (dP / std::abs(powerBattery)) * I; iterate = true; } - else if (std::abs(powerBatteryAC) > m_batteryPower->powerBatteryDischargeMaxAC * (1 + low_tolerance)) + else if (std::abs(powerBatteryAC) > max_discharge_ac * (1 + low_tolerance)) { - dP = std::abs(m_batteryPower->powerBatteryDischargeMaxAC - powerBatteryAC); + dP = std::abs(max_discharge_ac - powerBatteryAC); // decrease discharging magnitude I -= (dP / std::abs(powerBattery)) * I; @@ -482,9 +486,9 @@ void dispatch_t::dispatch_dc_outage_step(size_t lifetimeIndex) { double pv_kwac = m_batteryPower->sharedInverter->powerAC_kW; double max_discharge_kwdc = _Battery->calculate_max_discharge_kw(); - max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC); + max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses)); double max_charge_kwdc = _Battery->calculate_max_charge_kw(); - max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->powerBatteryChargeMaxDC); // Max, since charging numbers are negative + max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->powerBatteryChargeMaxDC * (1 - m_batteryPower->adjustLosses)); // Max, since charging numbers are negative double batt_losses = _Battery->calculate_loss(max_charge_kwdc, lifetimeIndex); // Setup battery iteration @@ -768,6 +772,10 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) double I_initial = I; double P_battery = I * _Battery->V() * util::watt_to_kilowatt; double P_target = m_batteryPower->powerBatteryTarget; + double charge_max_dc = m_batteryPower->powerBatteryChargeMaxDC * (1 - m_batteryPower->adjustLosses); + double charge_max_ac = m_batteryPower->powerBatteryChargeMaxAC * (1 - m_batteryPower->adjustLosses); + double discharge_max_dc = m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses); + double discharge_max_ac = m_batteryPower->powerBatteryDischargeMaxAC * (1 - m_batteryPower->adjustLosses); // Common to automated behind the meter and front of meter iterate = true; @@ -801,14 +809,14 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) } // Don't charge more if would violate current or power charge limits if (I > m_batteryPower->currentChargeMax - tolerance || - std::abs(P_battery) > m_batteryPower->powerBatteryChargeMaxDC - powerflow_tolerance || - std::abs(m_batteryPower->powerBatteryAC) > m_batteryPower->powerBatteryChargeMaxAC - powerflow_tolerance) { + std::abs(P_battery) > charge_max_dc - powerflow_tolerance || + std::abs(m_batteryPower->powerBatteryAC) > charge_max_ac - powerflow_tolerance) { iterate = false; } // restrict based on power limits else { - double dP_max = fmin(fmin(dP, m_batteryPower->powerBatteryChargeMaxDC - std::abs(P_battery)), - m_batteryPower->powerBatteryChargeMaxAC - std::abs(m_batteryPower->powerBatteryAC)); + double dP_max = fmin(fmin(dP, charge_max_dc - std::abs(P_battery)), + charge_max_ac - std::abs(m_batteryPower->powerBatteryAC)); dP = fmax(dP_max, 0); } } @@ -820,14 +828,14 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) } // Don't discharge more if would violate current or power discharge limits if (I > m_batteryPower->currentDischargeMax - tolerance || - P_battery > m_batteryPower->powerBatteryDischargeMaxDC - powerflow_tolerance || - m_batteryPower->powerBatteryAC > m_batteryPower->powerBatteryDischargeMaxAC - powerflow_tolerance) { + P_battery > discharge_max_dc - powerflow_tolerance || + m_batteryPower->powerBatteryAC > discharge_max_ac - powerflow_tolerance) { iterate = false; } // restrict based on power limits else { - double dP_max = fmax(fmax(dP, P_battery - m_batteryPower->powerBatteryDischargeMaxDC), - m_batteryPower->powerBatteryAC - m_batteryPower->powerBatteryChargeMaxAC); + double dP_max = fmax(fmax(dP, P_battery - discharge_max_dc), + m_batteryPower->powerBatteryAC - discharge_max_ac); dP = fmin(dP_max, 0); } } diff --git a/shared/lib_battery_dispatch_automatic_fom.cpp b/shared/lib_battery_dispatch_automatic_fom.cpp index 9e021193e..5235eb751 100644 --- a/shared/lib_battery_dispatch_automatic_fom.cpp +++ b/shared/lib_battery_dispatch_automatic_fom.cpp @@ -95,7 +95,7 @@ dispatch_automatic_front_of_meter_t::dispatch_automatic_front_of_meter_t( revenueToClipCharge = revenueToDischarge = revenueToGridCharge = revenueToPVCharge = 0; - discharge_hours = (size_t) std::ceil(_Battery->energy_max(m_batteryPower->stateOfChargeMax, m_batteryPower->stateOfChargeMin) / m_batteryPower->powerBatteryDischargeMaxDC) - 1; + discharge_hours = (size_t) std::ceil(_Battery->energy_max(m_batteryPower->stateOfChargeMax, m_batteryPower->stateOfChargeMin) / (m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses))) - 1; costToCycle(); omCost(); diff --git a/shared/lib_battery_powerflow.cpp b/shared/lib_battery_powerflow.cpp index 883538bc7..2d4a75543 100644 --- a/shared/lib_battery_powerflow.cpp +++ b/shared/lib_battery_powerflow.cpp @@ -279,7 +279,7 @@ void BatteryPowerFlow::initialize(double stateOfCharge, bool systemPriorityCharg (m_BatteryPower->powerSystem < m_BatteryPower->powerLoad || !m_BatteryPower->dischargeOnlyLoadExceedSystem || m_BatteryPower->meterPosition == dispatch_t::FRONT)) { // try to discharge full amount. Will only use what battery can provide - m_BatteryPower->powerBatteryDC = m_BatteryPower->powerBatteryDischargeMaxDC; + m_BatteryPower->powerBatteryDC = m_BatteryPower->powerBatteryDischargeMaxDC * (1 - m_BatteryPower->adjustLosses); } // Is there extra power from system else if ((((m_BatteryPower->powerSystem > m_BatteryPower->powerLoad) || !m_BatteryPower->chargeOnlySystemExceedLoad) && m_BatteryPower->canSystemCharge) || m_BatteryPower->canGridCharge || m_BatteryPower->canClipCharge || m_BatteryPower->canCurtailCharge) diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index 1f47cdb5d..28de22dec 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -3055,14 +3055,8 @@ void cm_pvsamv1::exec() if (en_batt && batt_topology == ChargeController::AC_CONNECTED) { - double delivered_percent = adj_factor; // Delivered percent is effectively 1 before this line, so just set it to adj_factor - if (system_use_lifetime_output && PVSystem->enableACLifetimeLosses) - { - int ac_loss_index = (int)iyear * 365 + (int)floor(hour_of_year / 24); //in units of days - delivered_percent *= (1 - PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac - } double ac_loss_post_inverter = 0; // Already accounted for in pv AC power (including transformer losses) - double ac_loss_post_batt = 1 - delivered_percent; + double ac_loss_post_batt = 0.0; // With AC connected battery, losses only apply to PV // calculate timestep in hour for battery models // jj represents which timestep within the hour you're on, 0-indexed From 638998f4c152501c990ae40146a34bf6655f8707 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 6 Nov 2024 16:59:17 -0700 Subject: [PATCH 04/19] First cut change of definition for powerflow AC losses to match new convention (PV only applies to PV when AC connected --- shared/lib_battery_dispatch.cpp | 4 +- shared/lib_battery_powerflow.cpp | 60 +++++++------------ shared/lib_battery_powerflow.h | 2 +- ssc/cmod_battery.cpp | 2 +- ssc/cmod_pvsamv1.cpp | 14 ++++- ...ib_battery_dispatch_automatic_btm_test.cpp | 8 +-- .../lib_battery_powerflow_test.cpp | 44 +++++++------- 7 files changed, 64 insertions(+), 70 deletions(-) diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index 8bf484898..340fb7b91 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -479,7 +479,7 @@ void dispatch_t::dispatch_dc_outage_step(size_t lifetimeIndex) { double V_pv = m_batteryPower->voltageSystem; double pv_clipped = m_batteryPower->powerSystemClipped; double crit_load_kwac = m_batteryPower->powerCritLoad; - double ac_loss_percent = 1 - (1 - m_batteryPower->acLossWiring) * (1 - m_batteryPower->acLossPostBattery); + double ac_loss_percent = 1 - (1 - m_batteryPower->acLossWiring) * (1 - m_batteryPower->acLossSystemAvailability); m_batteryPower->sharedInverter->calculateACPower(pv_kwdc, V_pv, m_batteryPower->sharedInverter->Tdry_C); double dc_ac_eff = m_batteryPower->sharedInverter->efficiencyAC * 0.01; @@ -555,7 +555,7 @@ void dispatch_t::dispatch_ac_outage_step(size_t lifetimeIndex) { double crit_load_kwac = m_batteryPower->powerCritLoad; double pv_kwac = m_batteryPower->powerSystem; double fuel_cell_kwac = m_batteryPower->powerFuelCell; - double ac_loss_percent = m_batteryPower->acLossPostBattery; + double ac_loss_percent = m_batteryPower->acLossSystemAvailability; double max_discharge_kwdc = _Battery->calculate_max_discharge_kw(); max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC); diff --git a/shared/lib_battery_powerflow.cpp b/shared/lib_battery_powerflow.cpp index 2d4a75543..595c5136c 100644 --- a/shared/lib_battery_powerflow.cpp +++ b/shared/lib_battery_powerflow.cpp @@ -80,7 +80,7 @@ BatteryPower::BatteryPower(double dtHour) : powerCurtailmentLimit(1e+38), voltageSystem(0), acLossWiring(0.0), - acLossPostBattery(0.0), + acLossSystemAvailability(0.0), acXfmrLoadLoss(0.0), acXfmrNoLoadLoss(0.0), acXfmrRating(0.0), @@ -150,7 +150,7 @@ BatteryPower::BatteryPower(const BatteryPower& orig) { powerCurtailmentLimit = orig.powerCurtailmentLimit; voltageSystem = orig.voltageSystem; acLossWiring = orig.acLossWiring; - acLossPostBattery = orig.acLossPostBattery; + acLossSystemAvailability = orig.acLossSystemAvailability; acXfmrLoadLoss = orig.acXfmrLoadLoss; acXfmrNoLoadLoss = orig.acXfmrNoLoadLoss; acXfmrRating = orig.acXfmrRating; @@ -217,7 +217,7 @@ void BatteryPower::reset() powerCurtailmentLimit = 1e+38; voltageSystem = 0; acLossWiring = 0.0; - acLossPostBattery = 0.0; + acLossSystemAvailability = 0.0; acXfmrLoadLoss = 0.0; acXfmrNoLoadLoss = 0.0; isOutageStep = false; @@ -332,7 +332,7 @@ void BatteryPowerFlow::calculateACConnected() double P_load_ac = m_BatteryPower->powerLoad; double P_crit_load_ac = m_BatteryPower->powerCritLoad; double P_system_loss_ac = m_BatteryPower->powerSystemLoss; - double ac_loss_percent_post_battery = m_BatteryPower->acLossPostBattery; // Losses due to system availability. Inverter losses are accounted for in the PV numbers already + double system_availability_loss = m_BatteryPower->acLossSystemAvailability; // Losses due to system availability. Inverter losses are accounted for in the PV numbers already double P_pv_to_batt_ac, P_grid_to_batt_ac, P_fuelcell_to_batt_ac, P_batt_to_load_ac, P_grid_to_load_ac, P_pv_to_load_ac, P_fuelcell_to_load_ac, P_available_pv, P_pv_to_grid_ac, P_batt_to_grid_ac, P_fuelcell_to_grid_ac, P_gen_ac, P_grid_ac, @@ -364,11 +364,8 @@ 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); - } + P_pv_ac *= (1 - system_availability_loss); // charging and idle if (P_battery_ac <= 0) @@ -383,11 +380,11 @@ void BatteryPowerFlow::calculateACConnected() if (m_BatteryPower->chargeOnlySystemExceedLoad) { P_pv_to_load_ac = P_pv_ac; - if (P_pv_to_load_ac > P_required_for_load) { - P_pv_to_load_ac = P_required_for_load; + if (P_pv_to_load_ac > calc_load_ac) { + P_pv_to_load_ac = calc_load_ac; } // Fuel cell goes to load next - P_fuelcell_to_load_ac = std::fmin(P_required_for_load - P_pv_to_load_ac, P_fuelcell_ac); + P_fuelcell_to_load_ac = std::fmin(calc_load_ac - P_pv_to_load_ac, P_fuelcell_ac); } // Excess PV can go to battery, if PV can cover charging losses @@ -416,11 +413,11 @@ void BatteryPowerFlow::calculateACConnected() P_pv_to_load_ac = P_pv_ac - P_pv_to_batt_ac; } - if (P_pv_to_load_ac > P_required_for_load) { - P_pv_to_load_ac = P_required_for_load; + if (P_pv_to_load_ac > calc_load_ac) { + P_pv_to_load_ac = calc_load_ac; } // Fuel cell goes to load next - P_fuelcell_to_load_ac = std::fmin(P_required_for_load - P_pv_to_load_ac, P_fuelcell_ac); + P_fuelcell_to_load_ac = std::fmin(calc_load_ac - P_pv_to_load_ac, P_fuelcell_ac); } // Fuelcell can also charge battery @@ -467,9 +464,9 @@ void BatteryPowerFlow::calculateACConnected() P_pv_to_load_ac = P_pv_ac; // Excess PV production, no other component meets load - if (P_pv_ac >= P_required_for_load) + if (P_pv_ac >= calc_load_ac) { - P_pv_to_load_ac = P_required_for_load; + P_pv_to_load_ac = calc_load_ac; P_fuelcell_to_load_ac = 0; P_batt_to_load_ac = 0; @@ -478,14 +475,14 @@ void BatteryPowerFlow::calculateACConnected() P_fuelcell_to_grid_ac = P_fuelcell_ac; } else { - 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); + 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); } } else { - 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_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_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); } @@ -522,25 +519,14 @@ void BatteryPowerFlow::calculateACConnected() // 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; + 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(); } } - // 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_batt_to_load_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); - - P_ac_losses = P_gen_ac * ac_loss_percent_post_battery; + P_ac_losses = 0.0; // TODO? double P_loss_coverage = 0; if (m_BatteryPower->isOutageStep) { @@ -658,7 +644,7 @@ void BatteryPowerFlow::calculateDCConnected() P_interconnection_loss_ac = P_crit_load_unmet_ac = P_unmet_losses = P_batt_to_inverter_dc = 0; - double ac_loss_percent = 1 - (1 - m_BatteryPower->acLossWiring) * (1 - m_BatteryPower->acLossPostBattery); // Combine the loss types into one number - they're all on the AC side of the inverter + double ac_loss_percent = 1 - (1 - m_BatteryPower->acLossWiring) * (1 - m_BatteryPower->acLossSystemAvailability); // Combine the loss types into one number - they're all on the AC side of the inverter if (m_BatteryPower->isOutageStep) { m_BatteryPower->acXfmrNoLoadLoss = 0; @@ -816,7 +802,7 @@ void BatteryPowerFlow::calculateDCConnected() if (!m_BatteryPower->isOutageStep && P_gen_ac > 0) { P_transformer_loss = Transformer::transformerLoss(P_gen_ac * (1 - m_BatteryPower->acLossWiring), m_BatteryPower->acXfmrLoadLoss, m_BatteryPower->acXfmrRating, P_xfmr_ll, m_BatteryPower->acXfmrNoLoadLoss); } - P_ac_losses = P_gen_ac - (P_gen_ac * (1 - m_BatteryPower->acLossWiring) - P_transformer_loss) * (1 - m_BatteryPower->acLossPostBattery); + P_ac_losses = P_gen_ac - (P_gen_ac * (1 - m_BatteryPower->acLossWiring) - P_transformer_loss) * (1 - m_BatteryPower->acLossSystemAvailability); if (P_gen_ac > tolerance) { ac_loss_percent = P_ac_losses / P_gen_ac; } @@ -896,7 +882,7 @@ void BatteryPowerFlow::calculateDCConnected() if (!m_BatteryPower->isOutageStep && P_gen_ac > 0) { P_transformer_loss = Transformer::transformerLoss(P_gen_ac * (1 - m_BatteryPower->acLossWiring), m_BatteryPower->acXfmrLoadLoss, m_BatteryPower->acXfmrRating, P_xfmr_ll, m_BatteryPower->acXfmrNoLoadLoss); } - P_ac_losses = P_gen_ac - (P_gen_ac * (1 - m_BatteryPower->acLossWiring) - P_transformer_loss) * (1 - m_BatteryPower->acLossPostBattery); + P_ac_losses = P_gen_ac - (P_gen_ac * (1 - m_BatteryPower->acLossWiring) - P_transformer_loss) * (1 - m_BatteryPower->acLossSystemAvailability); if (P_gen_ac > tolerance) { ac_loss_percent = P_ac_losses / P_gen_ac; } diff --git a/shared/lib_battery_powerflow.h b/shared/lib_battery_powerflow.h index 80a118312..382f132fa 100644 --- a/shared/lib_battery_powerflow.h +++ b/shared/lib_battery_powerflow.h @@ -196,7 +196,7 @@ struct BatteryPower double acXfmrLoadLoss; ///< Transformer load loss percent (%) double acXfmrNoLoadLoss; ///< Transformer no-load loss value (kWac) double acXfmrRating; ///< Transformer rating for transformer loss calculations (kWac) - double acLossPostBattery; ///< Expected system and daily losses. Applies power limiting to final AC output including the battery (%) + double acLossSystemAvailability; ///< Expected system and daily losses. Applies power limiting to final AC output of the system (%) double adjustLosses; ///< Battery availability losses (%) bool isOutageStep; diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index eb8dc9968..d6578bb51 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1841,7 +1841,7 @@ void battstor::advance(var_table*, double P_gen, double V_gen, double P_load, do powerflow->powerCritLoad = P_crit_load; powerflow->voltageSystem = V_gen; powerflow->acLossWiring = ac_wiring_loss; - powerflow->acLossPostBattery = ac_loss_post_battery; + powerflow->acLossSystemAvailability = ac_loss_post_battery; powerflow->acXfmrLoadLoss = xfmr_ll; powerflow->acXfmrNoLoadLoss = xfmr_nll; powerflow->powerSystemClipped = P_gen_clipped; diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index 86b9ab490..9c4b18936 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -3055,8 +3055,16 @@ void cm_pvsamv1::exec() if (en_batt && batt_topology == ChargeController::AC_CONNECTED) { + double delivered_percent = adj_factor; // Delivered percent is effectively 1 before this line, so just set it to adj_factor + if (system_use_lifetime_output && PVSystem->enableACLifetimeLosses) + { + int ac_loss_index = (int)iyear * 365 + (int)floor(hour_of_year / 24); //in units of days + delivered_percent *= (1 - PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac + } + + double ac_loss_post_inverter = 0; // Already accounted for in pv AC power (including transformer losses) - double ac_loss_post_batt = 0.0; // With AC connected battery, losses only apply to PV + double ac_pv_availability_loss_for_batt = 1 - delivered_percent; // calculate timestep in hour for battery models // jj represents which timestep within the hour you're on, 0-indexed @@ -3071,7 +3079,7 @@ void cm_pvsamv1::exec() resilience->run_surviving_batteries(p_crit_load_full[idx], PVSystem->p_systemACPower[idx], 0, 0, 0, 0); } - batt->advance(m_vartab, PVSystem->p_systemACPower[idx], 0, p_load_full[idx], p_crit_load_full[idx], ac_loss_post_inverter, ac_loss_post_batt); + batt->advance(m_vartab, PVSystem->p_systemACPower[idx], 0, p_load_full[idx], p_crit_load_full[idx], ac_loss_post_inverter, ac_pv_availability_loss_for_batt); batt->outGenWithoutBattery[idx] = PVSystem->p_systemACPower[idx]; PVSystem->p_systemACPower[idx] = batt->outGenPower[idx]; @@ -3116,7 +3124,7 @@ void cm_pvsamv1::exec() if (iyear == 0 || save_full_lifetime_variables == 1) { PVSystem->p_acPerfAdjLoss[idx] = PVSystem->p_systemACPower[idx] * (1 - adj_factor); } - //apply availability and curtailment + //apply availability PVSystem->p_systemACPower[idx] *= adj_factor; if (en_batt) { batt->outGenWithoutBattery[idx] *= adj_factor; diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index 07fdde8e2..f27f81dc8 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -951,10 +951,10 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili if (h < 6) { batteryPower->isOutageStep = true; batteryPower->powerCritLoad = 50; - batteryPower->acLossPostBattery = 1; + batteryPower->acLossSystemAvailability = 1; } else { - batteryPower->acLossPostBattery = 0; + batteryPower->acLossSystemAvailability = 0; batteryPower->isOutageStep = false; } @@ -1023,10 +1023,10 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili if (h < 6) { batteryPower->isOutageStep = true; batteryPower->powerCritLoad = 50; - batteryPower->acLossPostBattery = 1; + batteryPower->acLossSystemAvailability = 1; } else { - batteryPower->acLossPostBattery = 0; + batteryPower->acLossSystemAvailability = 0; batteryPower->isOutageStep = false; } diff --git a/test/shared_test/lib_battery_powerflow_test.cpp b/test/shared_test/lib_battery_powerflow_test.cpp index be8c1cd03..dfa31a533 100644 --- a/test/shared_test/lib_battery_powerflow_test.cpp +++ b/test/shared_test/lib_battery_powerflow_test.cpp @@ -2881,7 +2881,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { m_batteryPower->powerLoad = 50; // Try to charge - but 100% system loss - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->powerBatteryDC = -50 * m_batteryPower->singlePointEfficiencyACToDC; m_batteryPowerFlow->calculate(); @@ -2902,7 +2902,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { // Try to charge when inverter loss is 100%, disallowed m_batteryPower->powerSystem = 0; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; // Try to charge - 100% inverter loss means no available power @@ -2926,7 +2926,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { // Allow grid charging m_batteryPower->powerSystem = 0; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->canGridCharge = true; @@ -2951,7 +2951,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { // Cannot grid charge with post batt loss m_batteryPower->powerSystem = 0; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->canGridCharge = true; @@ -2974,7 +2974,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); // Try to discharge to load w/ inverter loss - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -2994,7 +2994,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Cannot discharge to load w/ post batt loss - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -3014,7 +3014,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Try to discharge to grid w/ inverter loss - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->powerLoad = 0; m_batteryPower->powerBatteryDC = 50; @@ -3036,7 +3036,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); // Cannot discharge w/ post batt loss - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -3057,7 +3057,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); // Post batt loss affects meeting critical load - m_batteryPower->acLossPostBattery = 0.05; + m_batteryPower->acLossSystemAvailability = 0.05; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPower->isOutageStep = true; @@ -3077,7 +3077,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Increasing batt power allows meeting critical load - m_batteryPower->acLossPostBattery = 0.05; + m_batteryPower->acLossSystemAvailability = 0.05; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 60; m_batteryPower->isOutageStep = true; @@ -3108,7 +3108,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { m_batteryPower->powerLoad = 50; // Try to charge - allowed to charge from PV since loss is on AC side - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->powerBatteryDC = -50; m_batteryPowerFlow->calculate(); @@ -3127,7 +3127,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { // Try to charge when inverter loss is 100%, allowed for DC connected m_batteryPower->powerSystem = 100; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->powerBatteryDC = -50; @@ -3148,7 +3148,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { m_batteryPower->powerSystem = 0; m_batteryPower->powerSystem = 0; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->canGridCharge = true; @@ -3170,7 +3170,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { // Cannot grid charge with post batt loss m_batteryPower->powerSystem = 0; m_batteryPower->powerLoad = 50; - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->canGridCharge = true; @@ -3190,7 +3190,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); // Try to discharge to load w/ inverter loss - not allowed - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -3207,7 +3207,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Cannot discharge to load w/ post batt loss - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -3224,7 +3224,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Try to discharge to grid w/ inverter loss - not allowed - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acLossWiring = 1; m_batteryPower->powerLoad = 0; m_batteryPower->powerBatteryDC = 50; @@ -3243,7 +3243,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); // Cannot discharge w/ post batt loss - m_batteryPower->acLossPostBattery = 1; + m_batteryPower->acLossSystemAvailability = 1; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPowerFlow->calculate(); @@ -3261,7 +3261,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); // Transformer losses applied to battery - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acXfmrLoadLoss = 0.5; m_batteryPower->acXfmrRating = 50; m_batteryPower->acLossWiring = 0; @@ -3281,7 +3281,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); // Transformer no load loss is in kw - m_batteryPower->acLossPostBattery = 0; + m_batteryPower->acLossSystemAvailability = 0; m_batteryPower->acXfmrLoadLoss = 0.01; m_batteryPower->acXfmrNoLoadLoss = 10.0; m_batteryPower->acXfmrRating = 50; @@ -3305,7 +3305,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { m_batteryPower->acXfmrNoLoadLoss = 0.0; // Post batt loss affects meeting critical load - m_batteryPower->acLossPostBattery = 0.05; + m_batteryPower->acLossSystemAvailability = 0.05; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 50; m_batteryPower->isOutageStep = true; @@ -3325,7 +3325,7 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); // Increasing batt power allows meeting critical load - m_batteryPower->acLossPostBattery = 0.05; + m_batteryPower->acLossSystemAvailability = 0.05; m_batteryPower->acLossWiring = 0; m_batteryPower->powerBatteryDC = 60; m_batteryPower->isOutageStep = true; From 7bb945e0dff7b154e752dce6e2c5c9228abd165f Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 6 Nov 2024 17:58:52 -0700 Subject: [PATCH 05/19] Add adjust losses to batt stateful, clean up cmod batt to fix seh exception (still doesn't relay losses properly --- shared/lib_battery_powerflow.cpp | 6 +- ssc/cmod_battery.cpp | 2 +- ssc/cmod_battery_stateful.cpp | 10 + ssc/cmod_pvsamv1.cpp | 73 +++--- ssc/common.cpp | 4 +- ...ib_battery_dispatch_automatic_btm_test.cpp | 2 +- .../lib_battery_powerflow_test.cpp | 227 ------------------ test/ssc_test/cmod_battery_stateful_test.cpp | 2 +- 8 files changed, 63 insertions(+), 263 deletions(-) diff --git a/shared/lib_battery_powerflow.cpp b/shared/lib_battery_powerflow.cpp index 595c5136c..58f682949 100644 --- a/shared/lib_battery_powerflow.cpp +++ b/shared/lib_battery_powerflow.cpp @@ -81,6 +81,7 @@ BatteryPower::BatteryPower(double dtHour) : voltageSystem(0), acLossWiring(0.0), acLossSystemAvailability(0.0), + adjustLosses(0.0), acXfmrLoadLoss(0.0), acXfmrNoLoadLoss(0.0), acXfmrRating(0.0), @@ -151,6 +152,7 @@ BatteryPower::BatteryPower(const BatteryPower& orig) { voltageSystem = orig.voltageSystem; acLossWiring = orig.acLossWiring; acLossSystemAvailability = orig.acLossSystemAvailability; + adjustLosses = orig.adjustLosses; acXfmrLoadLoss = orig.acXfmrLoadLoss; acXfmrNoLoadLoss = orig.acXfmrNoLoadLoss; acXfmrRating = orig.acXfmrRating; @@ -218,6 +220,7 @@ void BatteryPower::reset() voltageSystem = 0; acLossWiring = 0.0; acLossSystemAvailability = 0.0; + adjustLosses = 0.0; acXfmrLoadLoss = 0.0; acXfmrNoLoadLoss = 0.0; isOutageStep = false; @@ -332,7 +335,6 @@ void BatteryPowerFlow::calculateACConnected() double P_load_ac = m_BatteryPower->powerLoad; double P_crit_load_ac = m_BatteryPower->powerCritLoad; double P_system_loss_ac = m_BatteryPower->powerSystemLoss; - double system_availability_loss = m_BatteryPower->acLossSystemAvailability; // Losses due to system availability. Inverter losses are accounted for in the PV numbers already double P_pv_to_batt_ac, P_grid_to_batt_ac, P_fuelcell_to_batt_ac, P_batt_to_load_ac, P_grid_to_load_ac, P_pv_to_load_ac, P_fuelcell_to_load_ac, P_available_pv, P_pv_to_grid_ac, P_batt_to_grid_ac, P_fuelcell_to_grid_ac, P_gen_ac, P_grid_ac, @@ -365,8 +367,6 @@ 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); - P_pv_ac *= (1 - system_availability_loss); - // charging and idle if (P_battery_ac <= 0) { diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index d6578bb51..59bdcd7a7 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1175,7 +1175,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c } // TODO: fix this! - std::vector adj_losses; + std::vector adj_losses(8760, 0.0); if (batt_vars->batt_loss_choice == losses_params::MONTHLY) { if (*std::min_element(batt_vars->batt_losses_charging.begin(), batt_vars->batt_losses_charging.end()) < 0 || *std::min_element(batt_vars->batt_losses_discharging.begin(), batt_vars->batt_losses_discharging.end()) < 0 diff --git a/ssc/cmod_battery_stateful.cpp b/ssc/cmod_battery_stateful.cpp index 917d3f1c6..83e23debd 100644 --- a/ssc/cmod_battery_stateful.cpp +++ b/ssc/cmod_battery_stateful.cpp @@ -97,6 +97,8 @@ var_info vtab_battery_stateful_inputs[] = { { SSC_INPUT, SSC_ARRAY, "monthly_discharge_loss", "Battery system losses when discharging", "[kW]", "", "ParamsPack", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "monthly_idle_loss", "Battery system losses when idle", "[kW]", "", "ParamsPack", "?=0", "", "" }, { SSC_INPUT, SSC_ARRAY, "schedule_loss", "Battery system losses at each timestep", "[kW]", "", "ParamsPack", "?=0", "", "" }, + { SSC_INPUT, SSC_ARRAY, "availabilty_loss", "Battery availability losses at each timestep", "[%]", "", "ParamsPack", "?=0", "", "" }, + // replacement inputs { SSC_INPUT, SSC_NUMBER, "replacement_option", "Replacements: none (0), by capacity (1), or schedule (2)", "0=none,1=capacity limit,2=yearly schedule", "", "ParamsPack", "?=0", "INTEGER,MIN=0,MAX=2", "" }, @@ -128,6 +130,8 @@ var_info vtab_battery_state[] = { { SSC_INOUT, SSC_NUMBER, "charge_mode", "Charge (0), Idle (1), Discharge (2)", "0/1/2", "", "StateCell", "", "", "" }, { SSC_INOUT, SSC_NUMBER, "SOC_prev", "State of Charge of last time step", "%", "", "StateCell", "", "", "" }, { SSC_INOUT, SSC_NUMBER, "prev_charge", "Charge mode of last time step", "0/1/2", "", "StateCell", "", "", "" }, + { SSC_INOUT, SSC_NUMBER, "percent_unavailable", "Percent of system that is down", "%", "", "StateCell", "", "", "" }, + { SSC_INOUT, SSC_NUMBER, "percent_unavailable_prev", "Percent of system that was down last step", "%", "", "StateCell", "", "", "" }, { SSC_INOUT, SSC_NUMBER, "chargeChange", "Whether Charge mode changed since last step", "0/1", "", "StateCell", "", "", "" }, { SSC_INOUT, SSC_NUMBER, "q1_0", "Lead acid - Cell charge available", "Ah", "", "StateCell", "", "", "" }, { SSC_INOUT, SSC_NUMBER, "q2_0", "Lead acid - Cell charge bound", "Ah", "", "StateCell", "", "", "" }, @@ -216,6 +220,8 @@ void write_battery_state(const battery_state& state, var_table* vt) { vt->assign_match_case("charge_mode", cap->charge_mode); vt->assign_match_case("prev_charge", cap->prev_charge); vt->assign_match_case("chargeChange", cap->chargeChange); + vt->assign_match_case("percent_unavailable", cap->percent_unavailable); + vt->assign_match_case("percent_unavailable_prev", cap->percent_unavailable_prev); int choice; vt_get_int(vt, "chem", &choice); @@ -325,6 +331,8 @@ void read_battery_state(battery_state& state, var_table* vt) { vt_get_int(vt, "charge_mode", &cap->charge_mode); vt_get_int(vt, "prev_charge", &cap->prev_charge); vt_get_bool(vt, "chargeChange", &cap->chargeChange); + vt_get_number(vt, "percent_unavailable", &cap->percent_unavailable); + vt_get_number(vt, "percent_unavailable_prev", &cap->percent_unavailable_prev); int choice; vt_get_int(vt, "chem", &choice); @@ -539,6 +547,8 @@ std::shared_ptr create_battery_params(var_table* vt, double dt_h vt_get_array_vec(vt, "schedule_loss", losses->schedule_loss); } + vt_get_array_vec(vt, "availabilty_loss", losses->adjust_loss); + // replacements auto replacements = params->replacement; vt_get_int(vt, "replacement_option", &choice); diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index 9c4b18936..aa289c26b 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -3055,16 +3055,27 @@ void cm_pvsamv1::exec() if (en_batt && batt_topology == ChargeController::AC_CONNECTED) { - double delivered_percent = adj_factor; // Delivered percent is effectively 1 before this line, so just set it to adj_factor + //apply lifetime daily AC losses only if they are enabled if (system_use_lifetime_output && PVSystem->enableACLifetimeLosses) { + //current index of the lifetime daily AC losses is the number of years that have passed (iyear, because it is 0-indexed) * days in a year + the number of complete days that have passed int ac_loss_index = (int)iyear * 365 + (int)floor(hour_of_year / 24); //in units of days - delivered_percent *= (1 - PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac + ssc_number_t ac_lifetime_loss = PVSystem->p_systemACPower[idx] * (PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac + if (iyear == 0 || save_full_lifetime_variables == 1) { + PVSystem->p_acLifetimeLoss[idx] = ac_lifetime_loss; + } + if (iyear == 0) annual_ac_lifetime_loss += ac_lifetime_loss * ts_hour; // convert to kWh for yr 1 annual sum + PVSystem->p_systemACPower[idx] *= (100 - PVSystem->acLifetimeLosses[ac_loss_index]) / 100; } - + + if (iyear == 0 || save_full_lifetime_variables == 1) { + PVSystem->p_acPerfAdjLoss[idx] = PVSystem->p_systemACPower[idx] * (1 - adj_factor); + } + //apply availability + PVSystem->p_systemACPower[idx] *= adj_factor; double ac_loss_post_inverter = 0; // Already accounted for in pv AC power (including transformer losses) - double ac_pv_availability_loss_for_batt = 1 - delivered_percent; + double ac_pv_availability_loss_for_batt = 0; // Already accounted for as well // calculate timestep in hour for battery models // jj represents which timestep within the hour you're on, 0-indexed @@ -3082,6 +3093,10 @@ void cm_pvsamv1::exec() batt->advance(m_vartab, PVSystem->p_systemACPower[idx], 0, p_load_full[idx], p_crit_load_full[idx], ac_loss_post_inverter, ac_pv_availability_loss_for_batt); batt->outGenWithoutBattery[idx] = PVSystem->p_systemACPower[idx]; PVSystem->p_systemACPower[idx] = batt->outGenPower[idx]; + // accumulate system generation availability loss + if (iyear == 0) { + annual_ac_pre_avail += (PVSystem->p_systemACPower[idx] + PVSystem->p_acPerfAdjLoss[idx]) * ts_hour; + } bool offline = false; if (batt->is_outage_step(idx % 8760)) { @@ -3099,37 +3114,39 @@ void cm_pvsamv1::exec() PVSystem->p_transmissionLoss[idx] = 0.0; } } + else { + + //apply lifetime daily AC losses only if they are enabled + if (system_use_lifetime_output && PVSystem->enableACLifetimeLosses) + { + //current index of the lifetime daily AC losses is the number of years that have passed (iyear, because it is 0-indexed) * days in a year + the number of complete days that have passed + int ac_loss_index = (int)iyear * 365 + (int)floor(hour_of_year / 24); //in units of days + ssc_number_t ac_lifetime_loss = PVSystem->p_systemACPower[idx] * (PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac + if (iyear == 0 || save_full_lifetime_variables == 1) { + PVSystem->p_acLifetimeLoss[idx] = ac_lifetime_loss; + } + if (iyear == 0) annual_ac_lifetime_loss += ac_lifetime_loss * ts_hour; // convert to kWh for yr 1 annual sum + PVSystem->p_systemACPower[idx] *= (100 - PVSystem->acLifetimeLosses[ac_loss_index]) / 100; + if (en_batt) { + batt->outGenWithoutBattery[idx] *= (100 - PVSystem->acLifetimeLosses[ac_loss_index]) / 100; + } + } + + // accumulate system generation before curtailment and availability + if (iyear == 0) { + annual_ac_pre_avail += PVSystem->p_systemACPower[idx] * ts_hour; + } - //apply lifetime daily AC losses only if they are enabled - if (system_use_lifetime_output && PVSystem->enableACLifetimeLosses) - { - //current index of the lifetime daily AC losses is the number of years that have passed (iyear, because it is 0-indexed) * days in a year + the number of complete days that have passed - int ac_loss_index = (int)iyear * 365 + (int)floor(hour_of_year / 24); //in units of days - ssc_number_t ac_lifetime_loss = PVSystem->p_systemACPower[idx] * (PVSystem->acLifetimeLosses[ac_loss_index] / 100); // loss in kWac if (iyear == 0 || save_full_lifetime_variables == 1) { - PVSystem->p_acLifetimeLoss[idx] = ac_lifetime_loss; + PVSystem->p_acPerfAdjLoss[idx] = PVSystem->p_systemACPower[idx] * (1 - adj_factor); } - if (iyear == 0) annual_ac_lifetime_loss += ac_lifetime_loss * ts_hour; // convert to kWh for yr 1 annual sum - PVSystem->p_systemACPower[idx] *= (100 - PVSystem->acLifetimeLosses[ac_loss_index]) / 100; + //apply availability + PVSystem->p_systemACPower[idx] *= adj_factor; if (en_batt) { - batt->outGenWithoutBattery[idx] *= (100 - PVSystem->acLifetimeLosses[ac_loss_index]) / 100; + batt->outGenWithoutBattery[idx] *= adj_factor; } } - // accumulate system generation before curtailment and availability - if (iyear == 0) { - annual_ac_pre_avail += PVSystem->p_systemACPower[idx] * ts_hour; - } - - if (iyear == 0 || save_full_lifetime_variables == 1) { - PVSystem->p_acPerfAdjLoss[idx] = PVSystem->p_systemACPower[idx] * (1 - adj_factor); - } - //apply availability - PVSystem->p_systemACPower[idx] *= adj_factor; - if (en_batt) { - batt->outGenWithoutBattery[idx] *= adj_factor; - } - // Update battery with final gen to compute grid power // TODO: use this block to apply adjustment losses to PV and battery seperately if (en_batt) { diff --git a/ssc/common.cpp b/ssc/common.cpp index bc1df631c..f956419c3 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -567,8 +567,8 @@ var_info vtab_batt_adjustment_factors[] = { { SSC_INPUT,SSC_NUMBER , "batt_adjust_constant" , "DC Constant loss adjustment", "%", "", "Adjustment Factors", "?=0" , "MAX=100" , ""}, { SSC_INPUT, SSC_NUMBER, "batt_adjust_en_timeindex" , "Enable lifetime adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, { SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, -{ SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "DC Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "dc_adjust_en_timeindex=1" , "" , ""}, -{ SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "DC Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "dc_adjust_en_periods=1" , "COLS=3" , ""}, +{ SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "DC Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "batt_adjust_en_timeindex=1" , "" , ""}, +{ SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "DC Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "batt_adjust_en_periods=1" , "COLS=3" , ""}, var_info_invalid }; var_info vtab_financial_capacity_payments[] = { diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index f27f81dc8..c96aa9d0f 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -951,7 +951,7 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili if (h < 6) { batteryPower->isOutageStep = true; batteryPower->powerCritLoad = 50; - batteryPower->acLossSystemAvailability = 1; + batteryPower->acLossSystemAvailability = 1; // TODO: redo this! This number only applies to PV now } else { batteryPower->acLossSystemAvailability = 0; diff --git a/test/shared_test/lib_battery_powerflow_test.cpp b/test/shared_test/lib_battery_powerflow_test.cpp index dfa31a533..e3f23de60 100644 --- a/test/shared_test/lib_battery_powerflow_test.cpp +++ b/test/shared_test/lib_battery_powerflow_test.cpp @@ -2871,233 +2871,6 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_PVCharging_ExcessLoad_Flex check_net_flows(std::string()); } -TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, AC_system_w_ac_losses) { - m_batteryPower->connectionMode = ChargeController::AC_CONNECTED; - - m_batteryPower->canSystemCharge = true; - m_batteryPower->canDischarge = true; - m_batteryPower->canGridCharge = false; - m_batteryPower->powerSystem = 100; - m_batteryPower->powerLoad = 50; - - // Try to charge - but 100% system loss - m_batteryPower->acLossSystemAvailability = 1; - m_batteryPower->powerBatteryDC = -50 * m_batteryPower->singlePointEfficiencyACToDC; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, -50, error); // Dispatch would be responsible for reducing this to zero in the full code - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - double gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - - // Try to charge when inverter loss is 100%, disallowed - m_batteryPower->powerSystem = 0; - m_batteryPower->powerLoad = 50; - m_batteryPower->acLossSystemAvailability = 0; - m_batteryPower->acLossWiring = 1; - - // Try to charge - 100% inverter loss means no available power - m_batteryPower->powerBatteryDC = -50 * m_batteryPower->singlePointEfficiencyACToDC; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 0, error); // Grid charge error handling reduces this to zero - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - // Allow grid charging - m_batteryPower->powerSystem = 0; - m_batteryPower->powerLoad = 50; - m_batteryPower->acLossSystemAvailability = 0; - m_batteryPower->acLossWiring = 1; - m_batteryPower->canGridCharge = true; - - // Try to charge - grid charging provides power even with inverter off - m_batteryPower->powerBatteryDC = -50 * m_batteryPower->singlePointEfficiencyACToDC; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, -50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 50, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - // Cannot grid charge with post batt loss - m_batteryPower->powerSystem = 0; - m_batteryPower->powerLoad = 50; - m_batteryPower->acLossSystemAvailability = 1; - m_batteryPower->acLossWiring = 0; - m_batteryPower->canGridCharge = true; - - // Try to charge - but 100% system loss - m_batteryPower->powerBatteryDC = -50 * m_batteryPower->singlePointEfficiencyACToDC; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, -50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - // Try to discharge to load w/ inverter loss - m_batteryPower->acLossSystemAvailability = 0; - m_batteryPower->acLossWiring = 1; - m_batteryPower->powerBatteryDC = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 48, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 2, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 48, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2.0, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - - // Cannot discharge to load w/ post batt loss - m_batteryPower->acLossSystemAvailability = 1; - m_batteryPower->acLossWiring = 0; - m_batteryPower->powerBatteryDC = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 48, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 50, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - - // Try to discharge to grid w/ inverter loss - m_batteryPower->acLossSystemAvailability = 0; - m_batteryPower->acLossWiring = 1; - m_batteryPower->powerLoad = 0; - m_batteryPower->powerBatteryDC = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 48, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToGrid, 48, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2.0, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); - - // Cannot discharge w/ post batt loss - m_batteryPower->acLossSystemAvailability = 1; - m_batteryPower->acLossWiring = 0; - m_batteryPower->powerBatteryDC = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 48, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - - gen = m_batteryPower->powerSystem + m_batteryPower->powerBatteryAC; - EXPECT_NEAR(m_batteryPower->powerGeneratedBySystem, gen, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 0, error); - - // Post batt loss affects meeting critical load - m_batteryPower->acLossSystemAvailability = 0.05; - m_batteryPower->acLossWiring = 0; - m_batteryPower->powerBatteryDC = 50; - m_batteryPower->isOutageStep = true; - m_batteryPower->powerCritLoad = 50; - m_batteryPower->powerLoad = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 48, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 45.6, error); - EXPECT_NEAR(m_batteryPower->powerCritLoadUnmet, 4.4, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); - - // Increasing batt power allows meeting critical load - m_batteryPower->acLossSystemAvailability = 0.05; - m_batteryPower->acLossWiring = 0; - m_batteryPower->powerBatteryDC = 60; - m_batteryPower->isOutageStep = true; - m_batteryPower->powerLoad = 50; - m_batteryPower->powerCritLoad = 50; - m_batteryPowerFlow->calculate(); - - EXPECT_NEAR(m_batteryPower->powerBatteryAC, 55.4, error); - EXPECT_NEAR(m_batteryPower->powerBatteryDC, 57.7, error); - EXPECT_NEAR(m_batteryPower->powerSystemToLoad, 0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToBatteryAC, 0, error); - EXPECT_NEAR(m_batteryPower->powerGridToBattery, 0, error); - EXPECT_NEAR(m_batteryPower->powerBatteryToLoad, 50.0, error); - EXPECT_NEAR(m_batteryPower->powerCritLoadUnmet, 0.0, error); - EXPECT_NEAR(m_batteryPower->powerSystemToGrid, 0, error); - EXPECT_NEAR(m_batteryPower->powerConversionLoss, 2.308, error); - EXPECT_NEAR(m_batteryPower->powerSystemLoss, 0.0, error); - EXPECT_NEAR(m_batteryPower->powerLoad, 50, error); -} - TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, DC_system_w_ac_losses) { m_batteryPower->connectionMode = ChargeController::DC_CONNECTED; diff --git a/test/ssc_test/cmod_battery_stateful_test.cpp b/test/ssc_test/cmod_battery_stateful_test.cpp index 9048a44cb..161eea11b 100644 --- a/test/ssc_test/cmod_battery_stateful_test.cpp +++ b/test/ssc_test/cmod_battery_stateful_test.cpp @@ -325,7 +325,7 @@ TEST_F(CMBatteryStatefulIntegration_cmod_battery_stateful, AdaptiveTimestep) { } TEST_F(CMBatteryStatefulIntegration_cmod_battery_stateful, TestCycleCount) { - std::string js = "{\"control_mode\": 0.0, \"dt_hr\": 0.002777777777777778, \"input_current\": 0.0, \"C_rate\": 0.2, \"Qexp\": 60.75, \"Qfull\": 75.56, \"Qnom\": 73.58, \"Vcut\": 3.0, \"Vexp\": 3.529, \"Vfull\": 4.2, \"Vnom\": 3.35, \"Vnom_default\": 3.6, \"chem\": 1.0, \"initial_SOC\": 66.0, \"life_model\": 1.0, \"maximum_SOC\": 100.0, \"minimum_SOC\": 0.0, \"resistance\": 0.001155, \"voltage_choice\": 0.0, \"Cp\": 980.0, \"T_room_init\": 29.69998526573181, \"h\": 8.066, \"loss_choice\": 0.0, \"mass\": 1.55417, \"monthly_charge_loss\": [0.0], \"monthly_discharge_loss\": [0.0], \"monthly_idle_loss\": [0.0], \"nominal_energy\": 0.272, \"nominal_voltage\": 3.6, \"replacement_option\": 0.0, \"schedule_loss\": [0.0], \"surface_area\": 0.1548, \"I\": 0.0, \"I_chargeable\": -8045.99999999998, \"I_dischargeable\": 643.841000000002, \"P\": 0.0, \"P_chargeable\": -108.70616176055955, \"P_dischargeable\": 1.9341550347606316, \"Q\": 53.21000000000006, \"Q_max\": 75.56, \"SOC\": 70.42085759661204, \"T_batt\": 30.07627155118435, \"T_room\": 29.69998526573181, \"V\": 3.7667917861703755, \"heat_dissipated\": 0.0004698373776256373, \"indices_replaced\": [0.0], \"last_idx\": 8639.0, \"loss_kw\": 0.0, \"n_replacements\": 0.0, \"DOD_max\": 1.0, \"DOD_min\": 0.0, \"I_loss\": 0.0, \"SOC_prev\": 70.42085759661204, \"T_batt_prev\": 30.0747312729467, \"average_range\": 33.594321796748574, \"b1_dt\": 0.008196217640552396, \"b2_dt\": 1.1539245050321905e-05, \"b3_dt\": 0.0421665242517969, \"c0_dt\": 76.8158487840016, \"c2_dt\": 3.772090139902601e-05, \"cell_current\": 0.0, \"cell_voltage\": 3.7667917861703755, \"chargeChange\": 0.0, \"charge_mode\": 1.0, \"cum_dt\": 0.9998842592591438, \"temp_dt\": 291.947118, \"cycle_DOD\": 100.0, \"cycle_DOD_max\": [0.0, 100.0, 100.0, 100.0], \"cycle_counts\": [[0.0, 1], [0.16094315625949207, 1], [100.0, 1], [0.6220222339862289, 1]], \"cycle_range\": 0.6220222339862289, \"day_age_of_battery\": 0.9998842592591438, \"dq_relative_li1\": 0.0, \"dq_relative_li2\": 0.0, \"dq_relative_li3\": 0.0, \"dq_relative_neg\": 0.0, \"n_cycles\": 3.0, \"prev_charge\": 2.0, \"q0\": 53.21000000000006, \"q_relative\": 100.0, \"q_relative_cycle\": 0.0, \"q_relative_li\": 100.0, \"q_relative_neg\": 100.0, \"q_relative_thermal\": 100.0, \"qmax_lifetime\": 75.56, \"qmax_thermal\": 75.56, \"rainflow_Xlt\": 0.6220222339862431, \"rainflow_Ylt\": 20.103229221810423, \"rainflow_jlt\": 4.0, \"rainflow_peaks\": [100.0, 0.0, 20.103229221810423, 19.48120698782418]}"; + std::string js = "{\"control_mode\": 0.0, \"dt_hr\": 0.002777777777777778, \"input_current\": 0.0, \"C_rate\": 0.2, \"Qexp\": 60.75, \"Qfull\": 75.56, \"Qnom\": 73.58, \"Vcut\": 3.0, \"Vexp\": 3.529, \"Vfull\": 4.2, \"Vnom\": 3.35, \"Vnom_default\": 3.6, \"chem\": 1.0, \"initial_SOC\": 66.0, \"life_model\": 1.0, \"maximum_SOC\": 100.0, \"minimum_SOC\": 0.0, \"resistance\": 0.001155, \"voltage_choice\": 0.0, \"Cp\": 980.0, \"T_room_init\": 29.69998526573181, \"h\": 8.066, \"loss_choice\": 0.0, \"mass\": 1.55417, \"monthly_charge_loss\": [0.0], \"monthly_discharge_loss\": [0.0], \"monthly_idle_loss\": [0.0], \"nominal_energy\": 0.272, \"nominal_voltage\": 3.6, \"replacement_option\": 0.0, \"schedule_loss\": [0.0], \"surface_area\": 0.1548, \"I\": 0.0, \"I_chargeable\": -8045.99999999998, \"I_dischargeable\": 643.841000000002, \"P\": 0.0, \"P_chargeable\": -108.70616176055955, \"P_dischargeable\": 1.9341550347606316, \"Q\": 53.21000000000006, \"Q_max\": 75.56, \"SOC\": 70.42085759661204, \"T_batt\": 30.07627155118435, \"T_room\": 29.69998526573181, \"V\": 3.7667917861703755, \"heat_dissipated\": 0.0004698373776256373, \"indices_replaced\": [0.0], \"last_idx\": 8639.0, \"loss_kw\": 0.0, \"n_replacements\": 0.0, \"DOD_max\": 1.0, \"DOD_min\": 0.0, \"I_loss\": 0.0, \"SOC_prev\": 70.42085759661204, \"T_batt_prev\": 30.0747312729467, \"average_range\": 33.594321796748574, \"b1_dt\": 0.008196217640552396, \"b2_dt\": 1.1539245050321905e-05, \"b3_dt\": 0.0421665242517969, \"c0_dt\": 76.8158487840016, \"c2_dt\": 3.772090139902601e-05, \"cell_current\": 0.0, \"cell_voltage\": 3.7667917861703755, \"chargeChange\": 0.0, \"charge_mode\": 1.0, \"cum_dt\": 0.9998842592591438, \"temp_dt\": 291.947118, \"cycle_DOD\": 100.0, \"cycle_DOD_max\": [0.0, 100.0, 100.0, 100.0], \"cycle_counts\": [[0.0, 1], [0.16094315625949207, 1], [100.0, 1], [0.6220222339862289, 1]], \"cycle_range\": 0.6220222339862289, \"day_age_of_battery\": 0.9998842592591438, \"dq_relative_li1\": 0.0, \"dq_relative_li2\": 0.0, \"dq_relative_li3\": 0.0, \"dq_relative_neg\": 0.0, \"n_cycles\": 3.0, \"prev_charge\": 2.0, \"q0\": 53.21000000000006, \"q_relative\": 100.0, \"q_relative_cycle\": 0.0, \"q_relative_li\": 100.0, \"q_relative_neg\": 100.0, \"q_relative_thermal\": 100.0, \"qmax_lifetime\": 75.56, \"qmax_thermal\": 75.56, \"rainflow_Xlt\": 0.6220222339862431, \"rainflow_Ylt\": 20.103229221810423, \"rainflow_jlt\": 4.0, \"rainflow_peaks\": [100.0, 0.0, 20.103229221810423, 19.48120698782418], \"percent_unavailable\": 0.0, \"percent_unavailable_prev\": 0.0}"; data = json_to_ssc_data(js.c_str()); mod = ssc_module_create("battery_stateful"); From f8a604b763d029445ac46252c2c67845c416b8e0 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Thu, 7 Nov 2024 08:21:47 -0700 Subject: [PATCH 06/19] Test updates for new capacity and availability loss conventions --- .../shared_test/lib_battery_capacity_test.cpp | 112 +++++++++--------- ...ib_battery_dispatch_automatic_btm_test.cpp | 20 ++-- test/ssc_test/cmod_battery_pvsamv1_test.cpp | 2 +- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/test/shared_test/lib_battery_capacity_test.cpp b/test/shared_test/lib_battery_capacity_test.cpp index d5071a1e0..cd249cc6c 100644 --- a/test/shared_test/lib_battery_capacity_test.cpp +++ b/test/shared_test/lib_battery_capacity_test.cpp @@ -48,26 +48,26 @@ TEST_F(LiIon_lib_battery_capacity_test, updateCapacityTest){ double I = 1.5; old_cap->updateCapacity(I, dt_hour); auto s1 = capacity_state({498.5, 1000, 1000, 1.5, 0, - 49.85, 50, 2}); + 49.85, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityTest: 1"); I = 3; old_cap->updateCapacity(I, dt_hour); s1 = {495.5, 1000, 1000, 3, 0, - 49.55, 49.85, 2}; + 49.55, 49.85, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 2"); I = 490; old_cap->updateCapacity(I, dt_hour); s1 = {150, 1000, 1000, 345.5, 0, - 15, 49.55, 2}; + 15, 49.55, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 3"); I = 490; old_cap->updateCapacity(I, dt_hour); s1 = {150, 1000, 1000, 0, 0, - 15, 15, 1}; + 15, 15, 0.0, 0.0, 1}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 4"); } @@ -75,27 +75,27 @@ TEST_F(LiIon_lib_battery_capacity_test, updateCapacityThermalTest){ double percent = 80; old_cap->updateCapacityForThermal(percent); auto s1 = capacity_state({500, 1000, 800, 0, 0, - 62.5, 50, 2}); + 62.5, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 1"); percent = 50; old_cap->updateCapacityForThermal(percent); - s1 = {500, 1000, 500, 0, 0, 100, 50, 2}; + s1 = {500, 1000, 500, 0, 0, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 2"); percent = 10; old_cap->updateCapacityForThermal(percent); - s1 = {100, 1000, 100, 0, 400, 100, 50, 2}; + s1 = {100, 1000, 100, 0, 400, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 3"); percent = 110; old_cap->updateCapacityForThermal(percent); - s1 = {100, 1000, 1100, 0, 400, 10, 50, 2}; + s1 = {100, 1000, 1100, 0, 400, 10, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 4"); percent = -110; old_cap->updateCapacityForThermal(percent); - s1 = {0, 1000, 0, 0, 500, 0, 50, 2}; + s1 = {0, 1000, 0, 0, 500, 0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 4"); } @@ -103,57 +103,57 @@ TEST_F(LiIon_lib_battery_capacity_test, updateCapacityThermalTest){ TEST_F(LiIon_lib_battery_capacity_test, updateCapacityLifetimeTest){ double percent = 80; old_cap->updateCapacityForLifetime(percent); - auto s1 = capacity_state({500, 800, 1000, 0, 0,62.5, 50, 2}); + auto s1 = capacity_state({500, 800, 1000, 0, 0,62.5, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 1"); percent = 50; old_cap->updateCapacityForLifetime(percent); - s1 = {500, 500, 1000, 0, 0, 100, 50, 2}; + s1 = {500, 500, 1000, 0, 0, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 2"); percent = 10; old_cap->updateCapacityForLifetime(percent); - s1 = {100, 100, 1000, 0, 400, 100, 50, 2}; + s1 = {100, 100, 1000, 0, 400, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 3"); percent = 110; old_cap->updateCapacityForLifetime(percent); - s1 = {100, 1100, 1000, 0, 400, 10, 50, 2}; + s1 = {100, 1100, 1000, 0, 400, 10, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 4"); percent = -110; old_cap->updateCapacityForLifetime(percent); - s1 = {0, 0, 1000, 0, 500, 0, 50, 2}; + s1 = {0, 0, 1000, 0, 500, 0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 5"); } TEST_F(LiIon_lib_battery_capacity_test, replaceBatteryTest){ - auto s1 = capacity_state{500, 1000, 1000, 0, 0, 50, 50, 2}; + auto s1 = capacity_state{500, 1000, 1000, 0, 0, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: init"); // degrade 100% of battery old_cap->updateCapacityForLifetime(0); - s1 = {0, 0, 1000, 0, 500,0, 50, 2}; + s1 = {0, 0, 1000, 0, 500,0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: init degradation"); double percent = 50; old_cap->replace_battery(percent); - s1 = {250, 500, 500, 0, 500,50, 50, 2}; + s1 = {250, 500, 500, 0, 500,50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 1"); percent = 20; old_cap->replace_battery(percent); - s1 = {350, 700, 700, 0, 500, 50, 50, 2}; + s1 = {350, 700, 700, 0, 500, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 2"); percent = 110; old_cap->replace_battery(percent); - s1 = {500, 1000, 1000, 0, 500, 50, 50, 2}; + s1 = {500, 1000, 1000, 0, 500, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 4"); percent = -110; old_cap->replace_battery(percent); - s1 = {500, 1000, 1000, 0, 500, 50, 50, 2}; + s1 = {500, 1000, 1000, 0, 500, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 5"); } @@ -161,47 +161,47 @@ TEST_F(LiIon_lib_battery_capacity_test, runSequenceTest) { double I = 400; old_cap->updateCapacity(I, dt_hour); auto s1 = capacity_state({150, 1000, 1000, 350, 0, - 15, 50, 2}); + 15, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "runSequenceTest: 1"); I = -400; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({550, 1000, 1000, -400, 0, - 55, 15, 0}); + 55, 15, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 2"); double percent = 80; old_cap->updateCapacityForThermal(percent); s1 = capacity_state({550, 1000, 800, -400, 0, - 68.75, 15, 0}); + 68.75, 15, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 3"); I = 400; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({150, 1000, 800, 400, 0, - 18.75, 68.75, 2}); + 18.75, 68.75, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "runSequenceTest: 4"); I = -400; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({550, 1000, 800, -400, 0, - 68.75, 18.75, 0}); + 68.75, 18.75, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 5"); percent = 70; old_cap->updateCapacityForLifetime(percent); s1 = capacity_state({550, 700, 800, -400, 0, - 78.57, 18.75, 0}); + 78.57, 18.75, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 6"); percent = 20; old_cap->replace_battery(percent); - s1 = {650, 900, 900, -400, 0, 72.22, 50, 0}; + s1 = {650, 900, 900, -400, 0, 72.22, 50, 0.0, 0.0, 0}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 7"); I = 400; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({250, 900, 900, 400, 0, - 27.77, 72.22, 2}); + 27.77, 72.22, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "replaceBatteryTest: 8"); } @@ -269,26 +269,26 @@ TEST_F(KiBam_lib_battery_capacity_test, updateCapacityTest){ double I = 1.5; old_cap->updateCapacity(I, dt_hour); auto s1 = capacity_state({52.58, 108.16, 108.16, 1.5, 0, - 48.613, 50, 2}); + 48.613, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityTest: 1"); I = 3; old_cap->updateCapacity(I, dt_hour); s1 = {49.58, 108.16, 108.16, 3, 0, - 45.839, 48.613, 2}; + 45.839, 48.613, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 2"); I = 490; old_cap->updateCapacity(I, dt_hour); s1 = {22.927, 108.16, 108.16, 26.65, 0, - 21.19, 45.839, 2}; + 21.19, 45.839, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 3"); I = 490; old_cap->updateCapacity(I, dt_hour); s1 = {16.67, 108.16, 108.16, 6.25, 0, - 15.413, 21.19, 2}; + 15.413, 21.19, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityTest: 4"); } @@ -297,27 +297,27 @@ TEST_F(KiBam_lib_battery_capacity_test, updateCapacityThermalTest){ double percent = 80; old_cap->updateCapacityForThermal(percent); auto s1 = capacity_state({54.07, 108.15, 86.53, 0, 0, - 62.5, 50, 2}); + 62.5, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 1"); percent = 50; old_cap->updateCapacityForThermal(percent); - s1 = {54.07, 108.15, 54.07, 0, 0, 100, 50, 2}; + s1 = {54.07, 108.15, 54.07, 0, 0, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 2"); percent = 10; old_cap->updateCapacityForThermal(percent); - s1 = {10.816, 108.15, 10.816, 0, 43.26, 100, 50, 2}; + s1 = {10.816, 108.15, 10.816, 0, 43.26, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 3"); percent = 110; old_cap->updateCapacityForThermal(percent); - s1 = {10.816, 108.15, 118.97, 0, 43.26, 10, 50, 2}; + s1 = {10.816, 108.15, 118.97, 0, 43.26, 10, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 4"); percent = -110; old_cap->updateCapacityForThermal(percent); - s1 = {0, 108.15, 0, 0, 54.07, 0, 50, 2}; + s1 = {0, 108.15, 0, 0, 54.07, 0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityThermalTest: 4"); } @@ -325,57 +325,57 @@ TEST_F(KiBam_lib_battery_capacity_test, updateCapacityThermalTest){ TEST_F(KiBam_lib_battery_capacity_test, updateCapacityLifetimeTest){ double percent = 80; old_cap->updateCapacityForLifetime(percent); - auto s1 = capacity_state({54.07, 86.53, 108.15, 0, 0,62.5, 50, 2}); + auto s1 = capacity_state({54.07, 86.53, 108.15, 0, 0,62.5, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 1"); percent = 50; old_cap->updateCapacityForLifetime(percent); - s1 = {54.07, 54.07, 108.15, 0, 0, 100, 50, 2}; + s1 = {54.07, 54.07, 108.15, 0, 0, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 2"); percent = 10; old_cap->updateCapacityForLifetime(percent); - s1 = {10.816, 10.816, 108.15, 0, 43.26, 100, 50, 2}; + s1 = {10.816, 10.816, 108.15, 0, 43.26, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 3"); percent = 110; old_cap->updateCapacityForLifetime(percent); - s1 = {10.816, 10.816, 108.15, 0, 43.26, 100, 50, 2}; + s1 = {10.816, 10.816, 108.15, 0, 43.26, 100, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 4"); percent = -110; old_cap->updateCapacityForLifetime(percent); - s1 = {0, 0, 108.15, 0, 54.07, 0, 50, 2}; + s1 = {0, 0, 108.15, 0, 54.07, 0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "updateCapacityLifetimeTest: 5"); } TEST_F(KiBam_lib_battery_capacity_test, replaceBatteryTest){ - auto s1 = capacity_state{54.07, 108.15, 108.15, 0, 0, 50, 50, 2}; + auto s1 = capacity_state{54.07, 108.15, 108.15, 0, 0, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: init"); // degrade 100% of battery old_cap->updateCapacityForLifetime(0); - s1 = {0, 0, 108.15, 0, 54.07,0, 50, 2}; + s1 = {0, 0, 108.15, 0, 54.07,0, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: init degradation"); double percent = 50; old_cap->replace_battery(percent); - s1 = {27.04, 54.07, 54.07, 0, 54.07,50, 50, 2}; + s1 = {27.04, 54.07, 54.07, 0, 54.07,50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 1"); percent = 20; old_cap->replace_battery(percent); - s1 = {37.85, 75.71, 75.71, 0, 54.07, 50, 50, 2}; + s1 = {37.85, 75.71, 75.71, 0, 54.07, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 2"); percent = 110; old_cap->replace_battery(percent); - s1 = {54.07, 108.15, 108.15, 0, 54.07, 50, 50, 2}; + s1 = {54.07, 108.15, 108.15, 0, 54.07, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 4"); percent = -110; old_cap->replace_battery(percent); - s1 = {54.07, 108.15, 108.15, 0, 54.07, 50, 50, 2}; + s1 = {54.07, 108.15, 108.15, 0, 54.07, 50, 50, 0.0, 0.0, 2}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 5"); } @@ -383,46 +383,46 @@ TEST_F(KiBam_lib_battery_capacity_test, runSequenceTest) { double I = 30; old_cap->updateCapacity(I, dt_hour); auto s1 = capacity_state({24.07, 108.16, 108.16, 30, 0, - 22.26, 50, 2}); + 22.26, 50, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "runSequenceTest: 1"); I = -30; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({54.07, 108.16, 108.16, -30, 0, - 50, 22.26, 0}); + 50, 22.26, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 2"); double percent = 80; old_cap->updateCapacityForThermal(percent); s1 = capacity_state({54.07, 108.16, 86.53, -30, 0, - 62.5, 22.26, 0}); + 62.5, 22.26, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 3"); I = 40; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({20.74, 108.16, 86.53, 33.34, 0, - 23.97, 62.5, 2}); + 23.97, 62.5, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "runSequenceTest: 4"); I = -40; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({60.74, 108.16, 86.53, -40, 0, - 70.19, 23.97, 0}); + 70.19, 23.97, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 5"); percent = 70; old_cap->updateCapacityForLifetime(percent); s1 = capacity_state({60.74, 75.71, 86.53, -40, 0, - 80.22, 23.97, 0}); + 80.22, 23.97, 0.0, 0.0, 0}); compareState(old_cap->get_state(), s1, "runSequenceTest: 6"); percent = 20; old_cap->replace_battery(percent); - s1 = {71.55, 97.34, 97.34, -40, 0, 73.5, 50, 0}; + s1 = {71.55, 97.34, 97.34, -40, 0, 73.5, 50, 0.0, 0.0, 0}; compareState(old_cap->get_state(), s1, "replaceBatteryTest: 7"); I = 40; old_cap->updateCapacity(I, dt_hour); s1 = capacity_state({31.86, 97.34, 97.34, 39.7, 0, - 32.73, 73.5, 2}); + 32.73, 73.5, 0.0, 0.0, 2}); compareState(old_cap->get_state(), s1, "replaceBatteryTest: 8"); } diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index c96aa9d0f..332811c3d 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -936,12 +936,12 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili // Battery will discharge as much as possible for the outage, charge when PV is available, then discharge when load increases at 7 pm std::vector expectedPower = { 52.1, 52.1, 52.1, 52.1, 39.4, 3.7, - 0, -48, -48, -48, -48, -48, - -48, -48, -48, -48.0, -11, 0, - 0, 52.1, 52.1, 52.1, 52.1, 52.1 }; + 0, 0, 0, 0, 0, 0, + 0, -48, -48, -48.0, -48.0, -48.0, + 0, 52.1, 52.1, 52.28, 52.48, 27.6 }; - std::vector expectedCritLoadUnmet = { 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in first hours - 0, 0, 0, 0, 0, 0, + std::vector expectedCritLoadUnmet = { 0, 0, 0, 0, 12.19, 46.46, + 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in hrs 6 - 12 while battery is discharged 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -951,15 +951,19 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili if (h < 6) { batteryPower->isOutageStep = true; batteryPower->powerCritLoad = 50; - batteryPower->acLossSystemAvailability = 1; // TODO: redo this! This number only applies to PV now + batteryPower->acLossSystemAvailability = 0; + } + else if (h < 12) { + batteryPower->acLossSystemAvailability = 1; + batteryPower->powerCritLoad = 50; } else { batteryPower->acLossSystemAvailability = 0; batteryPower->isOutageStep = false; } - if (h > 6 && h < 18) { - batteryPower->powerSystem = 700; // Match the predicted PV + if (h > 12 && h < 18) { + batteryPower->powerSystem = 700; // PV is 0 hrs 6 - 11 due to availability loss } else if (h > 18) { batteryPower->powerLoad = 600; // Match the predicted load diff --git a/test/ssc_test/cmod_battery_pvsamv1_test.cpp b/test/ssc_test/cmod_battery_pvsamv1_test.cpp index 6108ebc4c..4845b8098 100644 --- a/test/ssc_test/cmod_battery_pvsamv1_test.cpp +++ b/test/ssc_test/cmod_battery_pvsamv1_test.cpp @@ -1065,7 +1065,7 @@ TEST_F(CMPvsamv1BatteryIntegration_cmod_pvsamv1, ResidentialACBatteryModelInterc ssc_number_t expectedBatteryChargeEnergy = 1442; ssc_number_t expectedBatteryDischargeEnergy = 1321; - ssc_number_t peakKwCharge = -2.91; + ssc_number_t peakKwCharge = -2.78; ssc_number_t peakKwDischarge = 1.39; ssc_number_t peakCycles = 1; ssc_number_t avgCycles = 1; From 559122e55484c1a36de97760cbb3193bb24ff01b Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Thu, 7 Nov 2024 10:52:40 -0700 Subject: [PATCH 07/19] First cut at availability losses testing. Revealed that DC battery still has a problem with a giant resistor that doesn't exist --- shared/lib_battery_dispatch.cpp | 7 +- ...ib_battery_dispatch_automatic_btm_test.cpp | 161 ++++++++++++++++++ 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index 340fb7b91..96e8e1157 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -398,6 +398,9 @@ bool dispatch_t::restrict_power(double& I) { double max_discharge_dc = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxDC; double max_discharge_ac = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxAC; + if (m_batteryPower->connectionMode == m_batteryPower->DC_CONNECTED) { + max_discharge_ac *= (1 - m_batteryPower->acLossSystemAvailability); + } if (std::abs(powerBattery) > max_discharge_dc * (1 + low_tolerance)) { dP = std::abs(max_discharge_dc - powerBattery); @@ -558,9 +561,9 @@ void dispatch_t::dispatch_ac_outage_step(size_t lifetimeIndex) { double ac_loss_percent = m_batteryPower->acLossSystemAvailability; double max_discharge_kwdc = _Battery->calculate_max_discharge_kw(); - max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC); + max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses)); double max_discharge_kwac = max_discharge_kwdc * m_batteryPower->singlePointEfficiencyDCToDC; - max_discharge_kwac = std::fmin(max_discharge_kwac, m_batteryPower->powerBatteryDischargeMaxAC); + max_discharge_kwac = std::fmin(max_discharge_kwac, m_batteryPower->powerBatteryDischargeMaxAC * (1 - m_batteryPower->adjustLosses)); double max_charge_kwdc = _Battery->calculate_max_charge_kw(); max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->powerBatteryChargeMaxDC); // Max, since charging numbers are negative diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index 332811c3d..485f68117 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -1674,3 +1674,164 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMSetupRateForecastMultiYe delete util_rate; } + +TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithBatteryAvailabilityLossesAC) { + double dtHour = 1; + // Setting up the battery with a loss model that doesn't know about the battery availability loss + // This is intentional to isolate the effects on dispatch + CreateBattery(dtHour); + double defaultEff = 0.96; + + dispatchAutoBTM = new dispatch_automatic_behind_the_meter_t(batteryModel, dtHour, SOC_min, SOC_max, currentChoice, + max_current, + max_current, max_power * defaultEff, max_power / defaultEff, max_power, max_power, + 0, dispatch_t::BTM_MODES::PEAK_SHAVING, dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD, 0, 1, 24, 1, true, + true, false, false, false, util_rate, replacementCost, cyclingChoice, cyclingCost, omCost, interconnection_limit, chargeOnlySystemExceedLoad, + dischargeOnlyLoadExceedSystem, dischargeToGrid, min_outage_soc, dispatch_t::LOAD_FORECAST_CHOICE::LOAD_LOOK_AHEAD); + + // Setup pv and load signal for peak shaving algorithm + for (size_t h = 0; h < 24; h++) { + if (h > 6 && h < 18) { + pv_prediction.push_back(700); + } + else { + pv_prediction.push_back(0); + } + + if (h > 18) { + load_prediction.push_back(600); + } + else { + load_prediction.push_back(500); + } + } + + dispatchAutoBTM->update_load_data(load_prediction); + dispatchAutoBTM->update_pv_data(pv_prediction); + + batteryPower = dispatchAutoBTM->getBatteryPower(); + batteryPower->connectionMode = ChargeController::AC_CONNECTED; + + // Battery is cannot discharge hrs 0-5 due to availabilty loss, then needs to wait until hour 13 to charge when PV is available, then discharge when load increases at 7 pm + std::vector expectedPower = { 0, 0, 0, 0, 0, 0, + 52.1, 52.1, 52.1, 52.1, 39.4, 3.7, + 0, -48, -48, -48.0, -48.0, -48.0, + 0, 52.1, 52.1, 52.28, 52.48, 27.6 }; + + std::vector expectedCritLoadUnmet = { 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in hrs 0 - 5 while battery is unavailable + 0, 0, 0, 0, 12.2, 46.5, // Battery meets losses until it runs out of SOC + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + + for (size_t h = 0; h < 24; h++) { + batteryPower->powerLoad = 500; + batteryPower->powerSystem = 0; + if (h < 6) { + batteryPower->isOutageStep = true; + batteryPower->powerCritLoad = 50; + batteryPower->acLossSystemAvailability = 0; + batteryPower->adjustLosses = 1; + } + else if (h < 12) { + batteryPower->acLossSystemAvailability = 1; + batteryPower->powerCritLoad = 50; + batteryPower->adjustLosses = 0; + } + else { + batteryPower->acLossSystemAvailability = 0; + batteryPower->isOutageStep = false; + batteryPower->adjustLosses = 0; + } + + if (h > 12 && h < 18) { + batteryPower->powerSystem = 700; // PV is 0 hrs 6 - 11 due to availability loss + } + else if (h > 18) { + batteryPower->powerLoad = 600; // Match the predicted load + } + dispatchAutoBTM->dispatch(0, h, 0); + EXPECT_NEAR(batteryPower->powerBatteryDC, expectedPower[h], 0.1) << " error in power at hour " << h; + EXPECT_NEAR(batteryPower->powerCritLoadUnmet, expectedCritLoadUnmet[h], 0.1) << " error in crit load at hour " << h; + } +} + +TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithBatteryAvailabilityLossesDC) { + double dtHour = 1; + // Setting up the battery with a loss model that doesn't know about the battery availability loss + // This is intentional to isolate the effects on dispatch + CreateBattery(dtHour); + double defaultEff = 0.96; + + dispatchAutoBTM = new dispatch_automatic_behind_the_meter_t(batteryModel, dtHour, SOC_min, SOC_max, currentChoice, + max_current, + max_current, max_power * defaultEff, max_power / defaultEff, max_power, max_power, + 0, dispatch_t::BTM_MODES::PEAK_SHAVING, dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD, 0, 1, 24, 1, true, + true, false, false, false, util_rate, replacementCost, cyclingChoice, cyclingCost, omCost, interconnection_limit, chargeOnlySystemExceedLoad, + dischargeOnlyLoadExceedSystem, dischargeToGrid, min_outage_soc, dispatch_t::LOAD_FORECAST_CHOICE::LOAD_LOOK_AHEAD); + + // Setup pv and load signal for peak shaving algorithm + for (size_t h = 0; h < 24; h++) { + if (h > 6 && h < 18) { + pv_prediction.push_back(700); + } + else { + pv_prediction.push_back(0); + } + + if (h > 18) { + load_prediction.push_back(600); + } + else { + load_prediction.push_back(500); + } + } + + dispatchAutoBTM->update_load_data(load_prediction); + dispatchAutoBTM->update_pv_data(pv_prediction); + + batteryPower = dispatchAutoBTM->getBatteryPower(); + batteryPower->connectionMode = ChargeController::DC_CONNECTED; + batteryPower->setSharedInverter(m_sharedInverter); + + // Battery is cannot discharge hrs 0-5 due to availabilty loss, then needs to wait until hour 13 to charge when PV is available, then discharge when load increases at 7 pm + std::vector expectedPower = { 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, // Discharging in these steps is also blocked by PV availability loss + 0, 0, 0, 0, 0, 0, // Battery has not discharged yet, so doesn't need to charge + 0, 52.1, 52.1, 52.28, 52.48, 27.6 }; + + std::vector expectedCritLoadUnmet = { 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in hrs 0 - 5 while battery is unavailable + 50, 50, 50, 50, 50, 50, // Battery meets losses until it runs out of SOC + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + + for (size_t h = 0; h < 24; h++) { + batteryPower->powerLoad = 500; + batteryPower->powerSystem = 0; + if (h < 6) { + batteryPower->isOutageStep = true; + batteryPower->powerCritLoad = 50; + batteryPower->acLossSystemAvailability = 0; + batteryPower->adjustLosses = 1; + } + else if (h < 12) { + batteryPower->acLossSystemAvailability = 1; + batteryPower->powerCritLoad = 50; + batteryPower->adjustLosses = 0; + } + else { + batteryPower->acLossSystemAvailability = 0; + batteryPower->isOutageStep = false; + batteryPower->adjustLosses = 0; + } + + if (h > 12 && h < 18) { + batteryPower->powerSystem = 700; // PV is 0 hrs 6 - 11 due to availability loss + } + else if (h > 18) { + batteryPower->powerLoad = 600; // Match the predicted load + } + dispatchAutoBTM->dispatch(0, h, 0); + EXPECT_NEAR(batteryPower->powerBatteryDC, expectedPower[h], 0.1) << " error in power at hour " << h; + EXPECT_NEAR(batteryPower->powerCritLoadUnmet, expectedCritLoadUnmet[h], 0.1) << " error in crit load at hour " << h; + } +} From 282d2a6d5fb432c6e2fe26cfddd8b2cfaeaae08c Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Thu, 7 Nov 2024 16:14:12 -0700 Subject: [PATCH 08/19] Add automated and manual dispatch tests, add current restriction to availability losses --- shared/lib_battery.cpp | 4 + shared/lib_battery.h | 3 + shared/lib_battery_dispatch.cpp | 27 ++-- ...ib_battery_dispatch_automatic_btm_test.cpp | 124 +++++++++++++++++- ...ib_battery_dispatch_automatic_fom_test.cpp | 44 +++++++ .../lib_battery_dispatch_automatic_fom_test.h | 36 +++++ .../lib_battery_dispatch_manual_test.cpp | 25 ++++ .../lib_battery_dispatch_manual_test.h | 31 +++++ 8 files changed, 276 insertions(+), 18 deletions(-) diff --git a/shared/lib_battery.cpp b/shared/lib_battery.cpp index 8fed84407..34ce99418 100644 --- a/shared/lib_battery.cpp +++ b/shared/lib_battery.cpp @@ -786,6 +786,10 @@ double battery_t::getAncillaryLoss() { return losses->getAncillaryLoss(); } +double battery_t::getAvailabilityLoss(size_t lifetimeIndex) { + return losses->getAvailabilityLoss(lifetimeIndex); +} + battery_state battery_t::get_state() { return *state; } battery_params battery_t::get_params() { return *params; } diff --git a/shared/lib_battery.h b/shared/lib_battery.h index 2ed411702..9b2331ce7 100644 --- a/shared/lib_battery.h +++ b/shared/lib_battery.h @@ -417,6 +417,9 @@ class battery_t { // Get the losses at the current step double getAncillaryLoss(); + // Get the adjust loss at the current timestep + double getAvailabilityLoss(size_t lifetimeIndex); + battery_state get_state(); battery_params get_params(); diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index 96e8e1157..3248b21b5 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -217,20 +217,21 @@ bool dispatch_t::check_constraints(double& I, size_t count) m_batteryPower->powerBatteryDC = m_batteryPower->powerBatteryTarget; I = _Battery_initial->calculate_current_for_power_kw(m_batteryPower->powerBatteryTarget); } + // Don't allow battery to discharge if it gets wasted due to inverter efficiency limitations - // Typically, this would be due to low power flow, so just cut off battery. +// Typically, this would be due to low power flow, so just cut off battery. if (m_batteryPower->connectionMode == dispatch_t::DC_CONNECTED && m_batteryPower->sharedInverter->efficiencyAC < m_batteryPower->inverterEfficiencyCutoff) { // The requested DC power double powerBatterykWdc = _Battery->I() * _Battery->V() * util::watt_to_kilowatt; - // if battery discharging, see if can back off to get higher efficiency - if (m_batteryPower->powerBatteryDC > 0) { + // if battery discharging, see if can back off to get higher efficiency + if (m_batteryPower->powerBatteryDC > 0) { double max_dc = m_batteryPower->powerSystem + powerBatterykWdc; // Only used by "inverter::NONE" - double inverter_max_dc = m_batteryPower->sharedInverter->getInverterDCMaxPower(max_dc) * util::watt_to_kilowatt; - if (powerBatterykWdc + m_batteryPower->powerSystem > inverter_max_dc) { - powerBatterykWdc = inverter_max_dc - m_batteryPower->powerSystem; - powerBatterykWdc = fmax(powerBatterykWdc, 0); + double inverter_max_dc = m_batteryPower->sharedInverter->getInverterDCMaxPower(max_dc) * util::watt_to_kilowatt * (1 - m_batteryPower->acLossSystemAvailability); + if (powerBatterykWdc + m_batteryPower->powerSystem > inverter_max_dc) { + powerBatterykWdc = inverter_max_dc - m_batteryPower->powerSystem; + powerBatterykWdc = fmax(powerBatterykWdc, 0); m_batteryPower->powerBatteryTarget = powerBatterykWdc; I = _Battery->calculate_current_for_power_kw(m_batteryPower->powerBatteryTarget); } @@ -239,7 +240,7 @@ bool dispatch_t::check_constraints(double& I, size_t count) else if (m_batteryPower->powerBatteryDC < 0 && m_batteryPower->powerGridToBattery > 0) { I *= fmax(1.0 - std::abs(m_batteryPower->powerGridToBattery * m_batteryPower->sharedInverter->efficiencyAC * 0.01 / m_batteryPower->powerBatteryDC), 0); m_batteryPower->powerBatteryTarget = _Battery->calculate_voltage_for_current(I) * I * util::watt_to_kilowatt; - } + } } iterate = std::abs(I_initial - I) > tolerance; @@ -330,17 +331,19 @@ bool dispatch_t::restrict_current(double& I) { if (I < 0) { - if (std::abs(I) > m_batteryPower->currentChargeMax) + double max_current_charge = (1 - m_batteryPower->adjustLosses) * m_batteryPower->currentChargeMax; + if (std::abs(I) > max_current_charge) { - I = -m_batteryPower->currentChargeMax; + I = -max_current_charge; iterate = true; } } else { - if (I > m_batteryPower->currentDischargeMax) + double max_current_discharge = (1 - m_batteryPower->adjustLosses) * m_batteryPower->currentDischargeMax; + if (I > max_current_discharge) { - I = m_batteryPower->currentDischargeMax; + I = max_current_discharge; iterate = true; } } diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index 485f68117..5d8436111 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -1010,11 +1010,11 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithAvailabili batteryPower->connectionMode = ChargeController::DC_CONNECTED; batteryPower->setSharedInverter(m_sharedInverter); - // Battery will discharge as much as possible for the outage, charge when PV is available, then discharge when load increases at 7 pm - std::vector expectedPower = { 52.1, 52.1, 52.1, 52.1, 39.4, 3.7, + // Battery cannot discharge for the outage due to DC connected losses, charge when PV is available, then discharge when load increases at 7 pm + std::vector expectedPower = { 0, 0, 0, 0, 0, 0, 0, -48, -48, -48, -48, -48, - -48, -48, -48, -48.0, -11, 0, - 0, 52.2, 52.2, 52.2, 52.2, 52.2 }; + -12, 0, 0, 0, 0, 0, + 0, 52.2, 52.2, 52.2, 52.3, 52.4 }; std::vector expectedCritLoadUnmet = { 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in first hours 0, 0, 0, 0, 0, 0, @@ -1796,8 +1796,8 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithBatteryAva // Battery is cannot discharge hrs 0-5 due to availabilty loss, then needs to wait until hour 13 to charge when PV is available, then discharge when load increases at 7 pm std::vector expectedPower = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Discharging in these steps is also blocked by PV availability loss - 0, 0, 0, 0, 0, 0, // Battery has not discharged yet, so doesn't need to charge - 0, 52.1, 52.1, 52.28, 52.48, 27.6 }; + 0, -48, -48, -48, -48, -48, + 0, 52.1, 52.22, 52.28, 52.3, 52.5 }; std::vector expectedCritLoadUnmet = { 50, 50, 50, 50, 50, 50, // Losses below prevent any crit load from being met in hrs 0 - 5 while battery is unavailable 50, 50, 50, 50, 50, 50, // Battery meets losses until it runs out of SOC @@ -1835,3 +1835,115 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridOutageWithBatteryAva EXPECT_NEAR(batteryPower->powerCritLoadUnmet, expectedCritLoadUnmet[h], 0.1) << " error in crit load at hour " << h; } } + +TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMPVChargeAndDischargeAvailabilityLossAC) { + double dtHour = 1; + CreateBattery(dtHour); + + dispatchAutoBTM = new dispatch_automatic_behind_the_meter_t(batteryModel, dtHour, SOC_min, SOC_max, currentChoice, + max_current, + max_current, max_power, max_power, max_power, max_power, + 0, dispatch_t::BTM_MODES::PEAK_SHAVING, dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD, 0, 1, 24, 1, true, + true, false, false, false, util_rate, replacementCost, cyclingChoice, cyclingCost, omCost, interconnection_limit, chargeOnlySystemExceedLoad, + dischargeOnlyLoadExceedSystem, dischargeToGrid, min_outage_soc, dispatch_t::LOAD_FORECAST_CHOICE::LOAD_LOOK_AHEAD); + // Setup pv and load signal for peak shaving algorithm + for (size_t h = 0; h < 24; h++) { + if (h > 6 && h < 18) { + pv_prediction.push_back(700); + } + else { + pv_prediction.push_back(0); + } + + if (h > 18) { + load_prediction.push_back(600); + } + else { + load_prediction.push_back(500); + } + } + + dispatchAutoBTM->update_load_data(load_prediction); + dispatchAutoBTM->update_pv_data(pv_prediction); + + batteryPower = dispatchAutoBTM->getBatteryPower(); + batteryPower->connectionMode = ChargeController::AC_CONNECTED; + + // Battery will charge when PV is available, then discharge when load increases at 7 pm + std::vector expectedPower = { 0, 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -1.9, + 0, 25, 25, 25, 25, 25, 25, 25, 25 }; + + for (size_t h = 0; h < 24; h++) { + batteryPower->powerLoad = 500; + batteryPower->powerSystem = 0; + batteryPower->adjustLosses = 0.5; + if (h > 6 && h < 18) { + batteryPower->powerSystem = 700; // Match the predicted PV + } + else if (h > 18) { + batteryPower->powerLoad = 600; // Match the predicted load + } + dispatchAutoBTM->dispatch(0, h, 0); + EXPECT_NEAR(batteryPower->powerBatteryDC, expectedPower[h], 0.5) << " error in expected at hour " << h; + } + +} + +TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMPVChargeAndDischargeAvailabilityLossDC) { + double dtHour = 1; + CreateBattery(dtHour); + + dispatchAutoBTM = new dispatch_automatic_behind_the_meter_t(batteryModel, dtHour, SOC_min, SOC_max, currentChoice, + max_current, + max_current, max_power, max_power, max_power, max_power, + 0, dispatch_t::BTM_MODES::PEAK_SHAVING, dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD, 0, 1, 24, 1, true, + true, false, false, false, util_rate, replacementCost, cyclingChoice, cyclingCost, omCost, interconnection_limit, chargeOnlySystemExceedLoad, + dischargeOnlyLoadExceedSystem, dischargeToGrid, min_outage_soc, dispatch_t::LOAD_FORECAST_CHOICE::LOAD_LOOK_AHEAD); + // Setup pv and load signal for peak shaving algorithm + for (size_t h = 0; h < 24; h++) { + if (h > 6 && h < 18) { + pv_prediction.push_back(700); + } + else { + pv_prediction.push_back(0); + } + + if (h > 18) { + load_prediction.push_back(600); + } + else { + load_prediction.push_back(500); + } + } + + dispatchAutoBTM->update_load_data(load_prediction); + dispatchAutoBTM->update_pv_data(pv_prediction); + + batteryPower = dispatchAutoBTM->getBatteryPower(); + batteryPower->connectionMode = ChargeController::DC_CONNECTED; + batteryPower->setSharedInverter(m_sharedInverter); + + // DC PV charging + std::vector expectedPower = { 0, 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -1.9, + 0, 25, 25, 25, 25, 25, 25, 25, 25 }; + + for (size_t h = 0; h < 24; h++) { + batteryPower->powerLoad = 500; + batteryPower->powerSystem = 0; + batteryPower->adjustLosses = 0.5; + if (h > 6 && h < 18) { + batteryPower->powerSystem = 700; // Match the predicted PV + + } + else if (h > 18) { + batteryPower->powerLoad = 600; // Match the predicted load + } + dispatchAutoBTM->dispatch(0, h, 0); + EXPECT_NEAR(batteryPower->powerBatteryDC, expectedPower[h], 0.5) << " error in expected at hour " << h; + } + +} diff --git a/test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp index c8671dd12..b6ddcf9e2 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp @@ -849,3 +849,47 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAutoWithClearedCapacityAndPri EXPECT_NEAR(dispatchAuto->battery_soc(), SOC[h], 0.1) << "error in SOC at hour " << h; } } + +TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAutoWithAvailabilityLosses) { + double dtHour = 1; + CreateBatteryWithAvailabilityLosses(dtHour); + dispatchAuto = new dispatch_automatic_front_of_meter_t(batteryModel, dtHour, 10, 100, 1, 49960, 49960, max_power, + max_power, max_power, max_power, 1, dispatch_t::FOM_AUTOMATED_ECONOMIC, dispatch_t::WEATHER_FORECAST_CHOICE::WF_LOOK_AHEAD, dispatch_t::FRONT, 1, 18, 1, true, true, false, + false, false, 77000, replacementCost, 1, cyclingCost, omCost, ppaRate, ur, 98, 98, 98, interconnection_limit, cleared_capacity, capacity_forecast_type, cleared_cap_percent); + + // battery setup + dispatchAuto->update_pv_data(pv); // PV Resource is available for the 1st 10 hrs + dispatchAuto->update_cliploss_data(clip); // Clip charging is available for the 1st 5 hrs + batteryPower = dispatchAuto->getBatteryPower(); + batteryPower->connectionMode = ChargeController::AC_CONNECTED; + batteryPower->voltageSystem = 600; + batteryPower->setSharedInverter(m_sharedInverter); + + std::vector targetkW = { 77000, 77000, 77000, -7205.42, 77000, 0., + -84205.39, -78854.60, -67702.19, -31516.09, 0., 0., + 0., 0., 0., 0., 0., 77000, + 77000, 77000, 77000, 0., 0., 0. }; + std::vector dispatchedkW = { 27100.68, 25407.98, 9385.55, -7205.42, 6616.55, 0., + -17308.3, -20907.06, -21302.5, -21520.97, 0., 0., + 0., 0., 0., 0., 0., 20716.59, + 20321.20, 19387.11, 13348.65, 0., 0., 0. }; + std::vector SOC = { 33.3, 16.66, 10.0, 14.68, 10.0, 10.0, + 18.3, 30.85, 43.35, 55.85, 55.85, 55.85, + 55.85, 55.85, 55.85, 55.85, 55.85, 43.35, + 30.85, 18.35, 8.35, 8.35, 8.35, 8.35 }; + // Battery was already discharging at max power, it stays unchanged + for (size_t h = 0; h < 24; h++) { + batteryPower->powerGeneratedBySystem = pv[h]; + batteryPower->powerSystem = pv[h]; + batteryPower->powerSystemClipped = clip[h]; + batteryPower->adjustLosses = batteryModel->getAvailabilityLoss(h); + + dispatchAuto->update_dispatch(0, h, 0, h); + EXPECT_NEAR(batteryPower->powerBatteryTarget, targetkW[h], 0.1) << "error in expected target at hour " << h; + + dispatchAuto->dispatch(0, h, 0); + + EXPECT_NEAR(batteryPower->powerBatteryDC, dispatchedkW[h], 0.1) << "error in dispatched power at hour " << h; + EXPECT_NEAR(dispatchAuto->battery_soc(), SOC[h], 0.1) << "error in SOC at hour " << h; + } +} diff --git a/test/shared_test/lib_battery_dispatch_automatic_fom_test.h b/test/shared_test/lib_battery_dispatch_automatic_fom_test.h index e25adc87d..8dab599b5 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_fom_test.h +++ b/test/shared_test/lib_battery_dispatch_automatic_fom_test.h @@ -116,6 +116,42 @@ class AutoFOM_lib_battery_dispatch : public BatteryProperties , public DispatchP m_sharedInverter = new SharedInverter(SharedInverter::SANDIA_INVERTER, numberOfInverters, sandia, partload, ond); cleared_capacity.clear(); } + + void CreateBatteryWithAvailabilityLosses(double dtHour) + { + // For testing Automated Front-of-meter DC-coupled + BatteryProperties::SetUp(); + + capacityModel = new capacity_lithium_ion_t(2.25 * 133227, 50, 100, 10, dtHour); + voltageModel = new voltage_dynamic_t(139, 133227, 3.6, 4.10, 4.05, 3.4, + 2.25, 0.04, 2.00, 0, 0.2, 0.2, dtHour); + lifetimeModel = new lifetime_calendar_cycle_t(cycleLifeMatrix, dtHour, calendar_q0, calendar_a, calendar_b, calendar_c); + thermalModel = new thermal_t(1.0, mass, surface_area, resistance, Cp, h, capacityVsTemperature, T_room); + + std::vector charging_losses(12, 0); // Monthly losses + std::vector discharging_losses(12, 0); + std::vector idle_losses(12, 0); + std::vector adjust_losses; + for (size_t i = 0; i < 8760; i++) { + if (i < 6) { + adjust_losses.push_back(0.0); + } + else if (i < 48) { + adjust_losses.push_back(0.25); + } + else { + adjust_losses.push_back(0.0); + } + } + + lossModel = new losses_t(charging_losses, discharging_losses, idle_losses, adjust_losses); + batteryModel = new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel); + + int numberOfInverters = 40; + m_sharedInverter = new SharedInverter(SharedInverter::SANDIA_INVERTER, numberOfInverters, sandia, partload, ond); + cleared_capacity.clear(); + } + void TearDown() { BatteryProperties::TearDown(); diff --git a/test/shared_test/lib_battery_dispatch_manual_test.cpp b/test/shared_test/lib_battery_dispatch_manual_test.cpp index ce8b1f33a..0c6b524ff 100644 --- a/test/shared_test/lib_battery_dispatch_manual_test.cpp +++ b/test/shared_test/lib_battery_dispatch_manual_test.cpp @@ -563,6 +563,31 @@ TEST_F(ManualTest_lib_battery_dispatch_losses, TestLossesWithDispatch) EXPECT_NEAR(batteryPower->powerBatteryToLoad, batteryPower->powerLoad, 0.5); } +TEST_F(ManualTest_lib_battery_dispatch_availability_losses, TestAvailabilityLossesWithDispatch) +{ + dispatchManual = new dispatch_manual_t(batteryModel, dtHour, SOC_min, SOC_max, currentChoice, currentChargeMax, + currentDischargeMax, powerChargeMax, powerDischargeMax, powerChargeMax, + powerDischargeMax, minimumModeTime, + dispatchChoice, meterPosition, scheduleWeekday, scheduleWeekend, canCharge, + canDischarge, canGridcharge, canDischargeToGrid, canGridcharge, percentDischarge, + percentGridcharge, canClipCharge, canCurtailCharge, interconnection_limit); + + batteryPower = dispatchManual->getBatteryPower(); + batteryPower->connectionMode = ChargeController::DC_CONNECTED; + batteryPower->setSharedInverter(m_sharedInverter); + + // Test max charge power constraint + batteryPower->powerSystem = 40; batteryPower->voltageSystem = 600; batteryPower->adjustLosses = 0.5; + dispatchManual->dispatch(year, hour_of_year, step_of_hour); + EXPECT_NEAR(batteryPower->powerSystemToBatteryAC, powerChargeMax * batteryPower->adjustLosses - batteryPower->powerSystemLoss, 1); + + // Test max discharge power constraint + batteryPower->powerSystem = 0; batteryPower->voltageSystem = 600; batteryPower->powerLoad = 40; batteryPower->adjustLosses = 0.5; + dispatchManual->dispatch(year, hour_of_year, step_of_hour); + EXPECT_NEAR(batteryPower->powerGeneratedBySystem, powerDischargeMax * batteryPower->adjustLosses * batteryPower->singlePointEfficiencyDCToAC, 0.5); // Constraints drive efficiency lower, meaning some grid power is used to meet load (<0.5 kW) + EXPECT_NEAR(batteryPower->powerBatteryToLoad, powerDischargeMax * batteryPower->adjustLosses * batteryPower->singlePointEfficiencyDCToAC, 0.5); +} + TEST_F(ManualTest_lib_battery_dispatch, TestDischargeToGrid) { std::vector testCanDischargeToGrid; diff --git a/test/shared_test/lib_battery_dispatch_manual_test.h b/test/shared_test/lib_battery_dispatch_manual_test.h index 50e8fd2ce..da2065c8f 100644 --- a/test/shared_test/lib_battery_dispatch_manual_test.h +++ b/test/shared_test/lib_battery_dispatch_manual_test.h @@ -135,4 +135,35 @@ class ManualTest_lib_battery_dispatch_losses : public ManualTest_lib_battery_dis }; +class ManualTest_lib_battery_dispatch_availability_losses : public ManualTest_lib_battery_dispatch +{ + +public: + + void SetUp() + { + // For Manual Dispatch Test + BatteryProperties::SetUp(); + q = 1000. / 89.; + + capacityModel = new capacity_lithium_ion_t(q * n_strings, SOC_init, SOC_max, SOC_min, 1.0); + voltageModel = new voltage_dynamic_t(n_series, n_strings, Vnom_default, Vfull, Vexp, Vnom, Qfull, Qexp, Qnom, Vcut, + C_rate, resistance, dtHour); + lifetimeModel = new lifetime_calendar_cycle_t(cycleLifeMatrix, dtHour, calendar_q0, calendar_a, calendar_b, calendar_c); + thermalModel = new thermal_t(1.0, mass, surface_area, resistance, Cp, h, capacityVsTemperature, T_room); + + std::vector charging_losses(12, 0); // Monthly losses + std::vector discharging_losses(12, 0); + std::vector idle_losses(12, 0); + std::vector adjust_losses(8760, 0.5); + + lossModel = new losses_t(charging_losses, discharging_losses, idle_losses, adjust_losses); + batteryModel = new battery_t(dtHour, chemistry, capacityModel, voltageModel, lifetimeModel, thermalModel, lossModel); + + int numberOfInverters = 1; + m_sharedInverter = new SharedInverter(SharedInverter::SANDIA_INVERTER, numberOfInverters, sandia, partload, ond); + } + +}; + #endif //SAM_SIMULATION_CORE_LIB_BATTERY_DISPATCH_MANUAL_TEST_H From ebc31f7d083cf25ad3a2336ccd0d5d5c7e796160 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Thu, 7 Nov 2024 17:57:57 -0700 Subject: [PATCH 09/19] Pass data through all compute modules to losses and powerflow code --- shared/lib_battery_dispatch_automatic_fom.cpp | 1 - ssc/cmod_battery.cpp | 12 ++++++++++-- ssc/cmod_battwatts.cpp | 1 + ssc/cmod_pvsamv1.cpp | 1 + ssc/common.cpp | 10 +++++----- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/shared/lib_battery_dispatch_automatic_fom.cpp b/shared/lib_battery_dispatch_automatic_fom.cpp index 95687d8cc..67a7bba10 100644 --- a/shared/lib_battery_dispatch_automatic_fom.cpp +++ b/shared/lib_battery_dispatch_automatic_fom.cpp @@ -364,7 +364,6 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho } // Also charge from grid if it is valuable to do so, still leaving EnergyToStoreClipped capacity in battery - // TODO: Grid charging needs to check cleared capacity! if (m_batteryPower->canGridCharge && ((revenueToGridCharge >= revenueToPVChargeMax && revenueToGridCharge > 0 && diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 59bdcd7a7..6a853e087 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1174,8 +1174,13 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c batt_vars->batt_Qfull_flow, batt_vars->batt_initial_SOC, batt_vars->batt_maximum_SOC, batt_vars->batt_minimum_SOC, dt_hr); } - // TODO: fix this! - std::vector adj_losses(8760, 0.0); + std::vector adj_losses; + adjustment_factors haf(&vt, "batt_adjust"); + haf.setup(nrec); + for (size_t i = 0; i < nrec; i++) { + adj_losses.push_back(1.0 - haf(i)); // Convert to convention within powerflow and capacity code + } + if (batt_vars->batt_loss_choice == losses_params::MONTHLY) { if (*std::min_element(batt_vars->batt_losses_charging.begin(), batt_vars->batt_losses_charging.end()) < 0 || *std::min_element(batt_vars->batt_losses_discharging.begin(), batt_vars->batt_losses_discharging.end()) < 0 @@ -1846,6 +1851,9 @@ void battstor::advance(var_table*, double P_gen, double V_gen, double P_load, do powerflow->acXfmrNoLoadLoss = xfmr_nll; powerflow->powerSystemClipped = P_gen_clipped; + size_t lifetime_index = util::lifetimeIndex(year, hour, step, _dt_hour); + powerflow->adjustLosses = battery_model->getAvailabilityLoss(lifetime_index); + charge_control->run(year, hour, step, year_index); outputs_fixed(); outputs_topology_dependent(); diff --git a/ssc/cmod_battwatts.cpp b/ssc/cmod_battwatts.cpp index 6d9ef87ab..8ce5bf6e8 100644 --- a/ssc/cmod_battwatts.cpp +++ b/ssc/cmod_battwatts.cpp @@ -292,6 +292,7 @@ battwatts_create(size_t n_recs, size_t n_years, int chem, int meter_pos, double cm_battwatts::cm_battwatts() { add_var_info(vtab_battwatts); + add_var_info(vtab_batt_adjustment_factors); add_var_info(vtab_battery_outputs); add_var_info(vtab_technology_outputs); add_var_info(vtab_resilience_outputs); diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index aa289c26b..11f754e5e 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -1094,6 +1094,7 @@ cm_pvsamv1::cm_pvsamv1() add_var_info(vtab_dc_adjustment_factors); add_var_info(vtab_technology_outputs); add_var_info(vtab_battery_inputs); + add_var_info(vtab_batt_adjustment_factors); add_var_info(vtab_forecast_price_signal); add_var_info(vtab_battery_outputs); add_var_info(vtab_resilience_outputs); diff --git a/ssc/common.cpp b/ssc/common.cpp index f956419c3..89e95185b 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -564,11 +564,11 @@ var_info vtab_sf_adjustment_factors[] = { var_info_invalid }; var_info vtab_batt_adjustment_factors[] = { -{ SSC_INPUT,SSC_NUMBER , "batt_adjust_constant" , "DC Constant loss adjustment", "%", "", "Adjustment Factors", "?=0" , "MAX=100" , ""}, -{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_timeindex" , "Enable lifetime adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, -{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, -{ SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "DC Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "batt_adjust_en_timeindex=1" , "" , ""}, -{ SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "DC Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "batt_adjust_en_periods=1" , "COLS=3" , ""}, +{ SSC_INPUT,SSC_NUMBER , "batt_adjust_constant" , "Battery Constant loss adjustment", "%", "", "Adjustment Factors", "?=0" , "MAX=100" , ""}, +{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_timeindex" , "Enable battery lifetime adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, +{ SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable battery period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, +{ SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "Battery Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "batt_adjust_en_timeindex=1" , "" , ""}, +{ SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "Battery Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "batt_adjust_en_periods=1" , "COLS=3" , ""}, var_info_invalid }; var_info vtab_financial_capacity_payments[] = { From bd44be4f4035d9c8bfd5106742333a7a0505c6e5 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Fri, 8 Nov 2024 09:09:36 -0700 Subject: [PATCH 10/19] Add fallback code if batt_adjust is not defined --- shared/lib_resilience.cpp | 2 ++ ssc/cmod_battery.cpp | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/shared/lib_resilience.cpp b/shared/lib_resilience.cpp index 941083a07..52f59e789 100644 --- a/shared/lib_resilience.cpp +++ b/shared/lib_resilience.cpp @@ -73,6 +73,7 @@ bool dispatch_resilience::run_outage_step_ac(double crit_load_kwac, double pv_kw } m_batteryPower->powerCritLoad = crit_load_kwac; m_batteryPower->isOutageStep = true; + m_batteryPower->adjustLosses = _Battery->getAvailabilityLoss(current_outage_index); dispatch_ac_outage_step(current_outage_index); @@ -95,6 +96,7 @@ bool dispatch_resilience::run_outage_step_dc(double crit_load_kwac, double pv_kw m_batteryPower->powerSystemClipped = pv_clipped; m_batteryPower->sharedInverter->Tdry_C = tdry; m_batteryPower->isOutageStep = true; + m_batteryPower->adjustLosses = _Battery->getAvailabilityLoss(current_outage_index); dispatch_dc_outage_step(current_outage_index); diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 6a853e087..601bd85dc 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1174,11 +1174,14 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c batt_vars->batt_Qfull_flow, batt_vars->batt_initial_SOC, batt_vars->batt_maximum_SOC, batt_vars->batt_minimum_SOC, dt_hr); } - std::vector adj_losses; - adjustment_factors haf(&vt, "batt_adjust"); - haf.setup(nrec); - for (size_t i = 0; i < nrec; i++) { - adj_losses.push_back(1.0 - haf(i)); // Convert to convention within powerflow and capacity code + std::vector adj_losses(1, 0.0); + if (vt.is_assigned("batt_adjust")) { + adj_losses.clear(); + adjustment_factors haf(&vt, "batt_adjust"); + haf.setup(nrec); + for (size_t i = 0; i < nrec; i++) { + adj_losses.push_back(1.0 - haf(i)); // Convert to convention within powerflow and capacity code + } } if (batt_vars->batt_loss_choice == losses_params::MONTHLY) { From e8fd9eda05d35185109a3468531cf902026e7e43 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Fri, 8 Nov 2024 16:34:11 -0700 Subject: [PATCH 11/19] Refactor max power and current to be functions instead of calculating in each individual location --- shared/lib_battery_dispatch.cpp | 56 +++++++++---------- shared/lib_battery_dispatch_automatic_btm.cpp | 2 +- shared/lib_battery_dispatch_automatic_fom.cpp | 6 +- shared/lib_battery_dispatch_manual.cpp | 4 +- .../lib_battery_dispatch_pvsmoothing_fom.cpp | 2 +- shared/lib_battery_powerflow.cpp | 27 ++++++++- shared/lib_battery_powerflow.h | 43 +++++++++++--- ...ib_battery_dispatch_automatic_btm_test.cpp | 2 +- .../lib_battery_powerflow_test.cpp | 8 +-- 9 files changed, 99 insertions(+), 51 deletions(-) diff --git a/shared/lib_battery_dispatch.cpp b/shared/lib_battery_dispatch.cpp index 3248b21b5..8601a2517 100644 --- a/shared/lib_battery_dispatch.cpp +++ b/shared/lib_battery_dispatch.cpp @@ -50,15 +50,15 @@ dispatch_t::dispatch_t(battery_t* Battery, double dt_hour, double SOC_min, doubl std::unique_ptr tmp(new BatteryPowerFlow(dt_hour)); m_batteryPowerFlow = std::move(tmp); m_batteryPower = m_batteryPowerFlow->getBatteryPower(); - m_batteryPower->currentChargeMax = Ic_max; - m_batteryPower->currentDischargeMax = Id_max; + m_batteryPower->setMaxChargeCurrent(Ic_max); + m_batteryPower->setMaxDischargeCurrent(Id_max); m_batteryPower->stateOfChargeMax = SOC_max; m_batteryPower->stateOfChargeMin = SOC_min; m_batteryPower->depthOfDischargeMax = SOC_max - SOC_min; - m_batteryPower->powerBatteryChargeMaxDC = Pc_max_kwdc; - m_batteryPower->powerBatteryDischargeMaxDC = Pd_max_kwdc; - m_batteryPower->powerBatteryChargeMaxAC = Pc_max_kwac; - m_batteryPower->powerBatteryDischargeMaxAC = Pd_max_kwac; + m_batteryPower->setMaxDCChargePower(Pc_max_kwdc); + m_batteryPower->setMaxDCDischargePower(Pd_max_kwdc); + m_batteryPower->setMaxACChargePower(Pc_max_kwac); + m_batteryPower->setMaxACDischargePower(Pd_max_kwac); m_batteryPower->meterPosition = battMeterPosition; m_batteryPower->powerInterconnectionLimit = interconnection_limit; m_batteryPower->chargeOnlySystemExceedLoad = chargeOnlySystemExceedLoad; @@ -331,7 +331,7 @@ bool dispatch_t::restrict_current(double& I) { if (I < 0) { - double max_current_charge = (1 - m_batteryPower->adjustLosses) * m_batteryPower->currentChargeMax; + double max_current_charge = m_batteryPower->getMaxChargeCurrent(); if (std::abs(I) > max_current_charge) { I = -max_current_charge; @@ -340,7 +340,7 @@ bool dispatch_t::restrict_current(double& I) } else { - double max_current_discharge = (1 - m_batteryPower->adjustLosses) * m_batteryPower->currentDischargeMax; + double max_current_discharge = m_batteryPower->getMaxDischargeCurrent(); if (I > max_current_discharge) { I = max_current_discharge; @@ -367,8 +367,8 @@ bool dispatch_t::restrict_power(double& I) // charging if (powerBattery < 0) { - double max_charge_dc = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryChargeMaxDC; - double max_charge_ac = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryChargeMaxAC; + double max_charge_dc = m_batteryPower->getMaxDCChargePower(); + double max_charge_ac = m_batteryPower->getMaxACChargePower(); if (std::abs(powerBattery) > max_charge_dc * (1 + low_tolerance)) { dP = std::abs(max_charge_dc - std::abs(powerBattery)); @@ -388,9 +388,9 @@ bool dispatch_t::restrict_power(double& I) } // This could just be grid power since that's technically the only AC component. But, limit all to this else if (m_batteryPower->connectionMode == m_batteryPower->DC_CONNECTED && - std::abs(powerBatteryAC) > m_batteryPower->powerBatteryChargeMaxAC * (1 + low_tolerance)) + std::abs(powerBatteryAC) > max_charge_ac * (1 + low_tolerance)) { - dP = std::abs(m_batteryPower->powerBatteryChargeMaxAC - std::abs(powerBatteryAC)); + dP = std::abs(max_charge_ac - std::abs(powerBatteryAC)); // increase (reduce) charging magnitude by percentage I -= (dP / std::abs(powerBattery)) * I; @@ -399,11 +399,9 @@ bool dispatch_t::restrict_power(double& I) } else { - double max_discharge_dc = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxDC; - double max_discharge_ac = (1 - m_batteryPower->adjustLosses) * m_batteryPower->powerBatteryDischargeMaxAC; - if (m_batteryPower->connectionMode == m_batteryPower->DC_CONNECTED) { - max_discharge_ac *= (1 - m_batteryPower->acLossSystemAvailability); - } + double max_discharge_dc = m_batteryPower->getMaxDCDischargePower(); + double max_discharge_ac = m_batteryPower->getMaxACDischargePower(); + if (std::abs(powerBattery) > max_discharge_dc * (1 + low_tolerance)) { dP = std::abs(max_discharge_dc - powerBattery); @@ -492,9 +490,9 @@ void dispatch_t::dispatch_dc_outage_step(size_t lifetimeIndex) { double pv_kwac = m_batteryPower->sharedInverter->powerAC_kW; double max_discharge_kwdc = _Battery->calculate_max_discharge_kw(); - max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses)); + max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->getMaxDCDischargePower()); double max_charge_kwdc = _Battery->calculate_max_charge_kw(); - max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->powerBatteryChargeMaxDC * (1 - m_batteryPower->adjustLosses)); // Max, since charging numbers are negative + max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->getMaxDCChargePower()); // Max, since charging numbers are negative double batt_losses = _Battery->calculate_loss(max_charge_kwdc, lifetimeIndex); // Setup battery iteration @@ -564,11 +562,11 @@ void dispatch_t::dispatch_ac_outage_step(size_t lifetimeIndex) { double ac_loss_percent = m_batteryPower->acLossSystemAvailability; double max_discharge_kwdc = _Battery->calculate_max_discharge_kw(); - max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses)); + max_discharge_kwdc = std::fmin(max_discharge_kwdc, m_batteryPower->getMaxDCDischargePower()); double max_discharge_kwac = max_discharge_kwdc * m_batteryPower->singlePointEfficiencyDCToDC; - max_discharge_kwac = std::fmin(max_discharge_kwac, m_batteryPower->powerBatteryDischargeMaxAC * (1 - m_batteryPower->adjustLosses)); + max_discharge_kwac = std::fmin(max_discharge_kwac, m_batteryPower->getMaxACDischargePower()); double max_charge_kwdc = _Battery->calculate_max_charge_kw(); - max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->powerBatteryChargeMaxDC); // Max, since charging numbers are negative + max_charge_kwdc = std::fmax(max_charge_kwdc, -1.0 * m_batteryPower->getMaxDCChargePower()); // Max, since charging numbers are negative if ((pv_kwac + fuel_cell_kwac) * (1 - ac_loss_percent) > crit_load_kwac) { double remaining_kwdc = -((pv_kwac + fuel_cell_kwac) * (1 - ac_loss_percent) - crit_load_kwac) * m_batteryPower->singlePointEfficiencyACToDC; @@ -778,10 +776,10 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) double I_initial = I; double P_battery = I * _Battery->V() * util::watt_to_kilowatt; double P_target = m_batteryPower->powerBatteryTarget; - double charge_max_dc = m_batteryPower->powerBatteryChargeMaxDC * (1 - m_batteryPower->adjustLosses); - double charge_max_ac = m_batteryPower->powerBatteryChargeMaxAC * (1 - m_batteryPower->adjustLosses); - double discharge_max_dc = m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses); - double discharge_max_ac = m_batteryPower->powerBatteryDischargeMaxAC * (1 - m_batteryPower->adjustLosses); + double charge_max_dc = m_batteryPower->getMaxDCChargePower(); + double charge_max_ac = m_batteryPower->getMaxACChargePower(); + double discharge_max_dc = m_batteryPower->getMaxDCDischargePower(); + double discharge_max_ac = m_batteryPower->getMaxACDischargePower(); // Common to automated behind the meter and front of meter iterate = true; @@ -814,7 +812,7 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) iterate = false; } // Don't charge more if would violate current or power charge limits - if (I > m_batteryPower->currentChargeMax - tolerance || + if (I > m_batteryPower->getMaxChargeCurrent() - tolerance || std::abs(P_battery) > charge_max_dc - powerflow_tolerance || std::abs(m_batteryPower->powerBatteryAC) > charge_max_ac - powerflow_tolerance) { iterate = false; @@ -833,7 +831,7 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) iterate = false; } // Don't discharge more if would violate current or power discharge limits - if (I > m_batteryPower->currentDischargeMax - tolerance || + if (I > m_batteryPower->getMaxDischargeCurrent() - tolerance || P_battery > discharge_max_dc - powerflow_tolerance || m_batteryPower->powerBatteryAC > discharge_max_ac - powerflow_tolerance) { iterate = false; @@ -871,7 +869,7 @@ bool dispatch_automatic_t::check_constraints(double& I, size_t count) { // Don't let PV export to grid if can still charge battery (increase charging) (unless following custom dispatch) if (_mode != dispatch_t::CUSTOM_DISPATCH && m_batteryPower->powerSystemToGrid > powerflow_tolerance && m_batteryPower->canSystemCharge && - _Battery->SOC() < m_batteryPower->stateOfChargeMax - tolerance && std::abs(I) < std::abs(m_batteryPower->currentChargeMax)) + _Battery->SOC() < m_batteryPower->stateOfChargeMax - tolerance && std::abs(I) < std::abs(m_batteryPower->getMaxChargeCurrent())) { if (std::abs(m_batteryPower->powerBatteryAC) < powerflow_tolerance) I -= (m_batteryPower->powerSystemToGrid / m_batteryPower->singlePointEfficiencyDCToAC * util::kilowatt_to_watt / _Battery->V()); diff --git a/shared/lib_battery_dispatch_automatic_btm.cpp b/shared/lib_battery_dispatch_automatic_btm.cpp index c9053fd29..0cd824aee 100644 --- a/shared/lib_battery_dispatch_automatic_btm.cpp +++ b/shared/lib_battery_dispatch_automatic_btm.cpp @@ -179,7 +179,7 @@ void dispatch_automatic_behind_the_meter_t::setup_rate_forecast() { forecast_setup rate_setup(_steps_per_hour, _nyears); - rate_setup.setup(rate.get(), _P_pv_ac, _P_load_ac, m_batteryPower->powerBatteryDischargeMaxAC); + rate_setup.setup(rate.get(), _P_pv_ac, _P_load_ac, m_batteryPower->getMaxACChargePower()); rate_forecast = std::shared_ptr(new UtilityRateForecast(rate.get(), _steps_per_hour, rate_setup.monthly_net_load, rate_setup.monthly_gen, rate_setup.monthly_gross_load, _nyears, rate_setup.monthly_peaks)); rate_forecast->initializeMonth(0, 0); diff --git a/shared/lib_battery_dispatch_automatic_fom.cpp b/shared/lib_battery_dispatch_automatic_fom.cpp index 67a7bba10..f51c7be6c 100644 --- a/shared/lib_battery_dispatch_automatic_fom.cpp +++ b/shared/lib_battery_dispatch_automatic_fom.cpp @@ -102,7 +102,7 @@ dispatch_automatic_front_of_meter_t::dispatch_automatic_front_of_meter_t( revenueToClipCharge = revenueToDischarge = revenueToGridCharge = revenueToPVCharge = 0; - discharge_hours = (size_t) std::ceil(_Battery->energy_max(m_batteryPower->stateOfChargeMax, m_batteryPower->stateOfChargeMin) / (m_batteryPower->powerBatteryDischargeMaxDC * (1 - m_batteryPower->adjustLosses))) - 1; + discharge_hours = (size_t) std::ceil(_Battery->energy_max(m_batteryPower->stateOfChargeMax, m_batteryPower->stateOfChargeMin) / (m_batteryPower->getMaxDCDischargePower())) - 1; costToCycle(); omCost(); @@ -268,14 +268,14 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho /*! Computed revenue to charge from PV in each of next X hours ($/kWh)*/ size_t t_duration = static_cast(ceilf( (float) - _Battery->energy_nominal() / (float) m_batteryPower->powerBatteryChargeMaxDC)); + _Battery->energy_nominal() / (float) m_batteryPower->getMaxDCChargePower())); size_t pv_hours_on; double revenueToPVChargeMax = 0; if (m_batteryPower->canSystemCharge) { std::vector revenueToPVChargeForecast; for (size_t i = lifetimeIndex; i < lifetimeIndex + idx_lookahead && i < _P_pv_ac.size(); i++) { // when considering grid charging, require PV output to exceed battery input capacity before accepting as a better option - bool system_on = _P_pv_ac[i] >= m_batteryPower->powerBatteryChargeMaxDC ? 1 : 0; + bool system_on = _P_pv_ac[i] >= m_batteryPower->getMaxDCChargePower() ? 1 : 0; if (system_on) { revenueToPVChargeForecast.push_back(system_on * (*max_ppa_cost * m_etaDischarge - _forecast_price_rt_series[i] / m_etaPVCharge - m_cycleCost - m_omCost)); } diff --git a/shared/lib_battery_dispatch_manual.cpp b/shared/lib_battery_dispatch_manual.cpp index d9c038389..62c691343 100644 --- a/shared/lib_battery_dispatch_manual.cpp +++ b/shared/lib_battery_dispatch_manual.cpp @@ -171,8 +171,8 @@ bool dispatch_manual_t::check_constraints(double &I, size_t count) if (m_batteryPower->powerSystemToGrid > low_tolerance && m_batteryPower->canSystemCharge && // only do if battery is allowed to charge _Battery->SOC() < m_batteryPower->stateOfChargeMax - 1.0 && // and battery SOC is less than max - std::abs(I) < std::abs(m_batteryPower->currentChargeMax) && // and battery current is less than max charge current - std::abs(m_batteryPower->powerBatteryDC) < (m_batteryPower->powerBatteryChargeMaxDC - 1.0) &&// and battery power is less than max charge power + std::abs(I) < std::abs(m_batteryPower->getMaxChargeCurrent()) && // and battery current is less than max charge current + std::abs(m_batteryPower->powerBatteryDC) < (m_batteryPower->getMaxDischargeCurrent() - 1.0) &&// and battery power is less than max charge power I <= 0) // and battery was not discharging { double dI = 0; diff --git a/shared/lib_battery_dispatch_pvsmoothing_fom.cpp b/shared/lib_battery_dispatch_pvsmoothing_fom.cpp index 85ab764c4..36a5e92eb 100644 --- a/shared/lib_battery_dispatch_pvsmoothing_fom.cpp +++ b/shared/lib_battery_dispatch_pvsmoothing_fom.cpp @@ -249,7 +249,7 @@ void dispatch_pvsmoothing_front_of_meter_t::update_dispatch(size_t year, size_t ssc_number_t battery_soc = _Battery->SOC()/100.0; ssc_number_t battery_energy = _Battery->energy_nominal(); ssc_number_t batt_half_round_trip_eff = sqrt(m_etaDischarge * m_etaPVCharge); - ssc_number_t battery_power = m_batteryPower->powerBatteryChargeMaxAC; + ssc_number_t battery_power = m_batteryPower->getMaxACChargePower(); ssc_number_t soc_min = _Battery->get_params().capacity->minimum_SOC * 0.01; ssc_number_t soc_max = _Battery->get_params().capacity->maximum_SOC * 0.01; // scale by nameplate per ERPI code diff --git a/shared/lib_battery_powerflow.cpp b/shared/lib_battery_powerflow.cpp index 58f682949..7bfbc48bf 100644 --- a/shared/lib_battery_powerflow.cpp +++ b/shared/lib_battery_powerflow.cpp @@ -252,6 +252,29 @@ double BatteryPower::adjustForDCEfficiencies(double power, double loss) { } } +double BatteryPower::getMaxACChargePower() { + return (1 - adjustLosses) * powerBatteryChargeMaxAC; +} +double BatteryPower::getMaxACDischargePower() { + double max_discharge_ac = (1 - adjustLosses) * powerBatteryDischargeMaxAC; + if (connectionMode == DC_CONNECTED) { + max_discharge_ac *= (1 - acLossSystemAvailability); + } + return max_discharge_ac; +} +double BatteryPower::getMaxDCChargePower() { + return (1 - adjustLosses) * powerBatteryChargeMaxDC; +} +double BatteryPower::getMaxDCDischargePower() { + return (1 - adjustLosses) * powerBatteryDischargeMaxDC; +} +double BatteryPower::getMaxChargeCurrent() { + return (1 - adjustLosses) * currentChargeMax; +} +double BatteryPower::getMaxDischargeCurrent() { + return (1 - adjustLosses) * currentDischargeMax; +} + BatteryPowerFlow::BatteryPowerFlow(double dtHour) { std::unique_ptr tmp(new BatteryPower(dtHour)); @@ -282,7 +305,7 @@ void BatteryPowerFlow::initialize(double stateOfCharge, bool systemPriorityCharg (m_BatteryPower->powerSystem < m_BatteryPower->powerLoad || !m_BatteryPower->dischargeOnlyLoadExceedSystem || m_BatteryPower->meterPosition == dispatch_t::FRONT)) { // try to discharge full amount. Will only use what battery can provide - m_BatteryPower->powerBatteryDC = m_BatteryPower->powerBatteryDischargeMaxDC * (1 - m_BatteryPower->adjustLosses); + m_BatteryPower->powerBatteryDC = m_BatteryPower->getMaxDCDischargePower(); } // Is there extra power from system else if ((((m_BatteryPower->powerSystem > m_BatteryPower->powerLoad) || !m_BatteryPower->chargeOnlySystemExceedLoad) && m_BatteryPower->canSystemCharge) || m_BatteryPower->canGridCharge || m_BatteryPower->canClipCharge || m_BatteryPower->canCurtailCharge) @@ -311,7 +334,7 @@ void BatteryPowerFlow::initialize(double stateOfCharge, bool systemPriorityCharg } // if we want to charge from grid in addition to, or without array, we can always charge at max power if (m_BatteryPower->canGridCharge) { - m_BatteryPower->powerBatteryDC = -m_BatteryPower->powerBatteryChargeMaxDC; + m_BatteryPower->powerBatteryDC = -m_BatteryPower->getMaxDCChargePower(); } } m_BatteryPower->powerBatteryTarget = m_BatteryPower->powerBatteryDC; diff --git a/shared/lib_battery_powerflow.h b/shared/lib_battery_powerflow.h index 382f132fa..20bccbb61 100644 --- a/shared/lib_battery_powerflow.h +++ b/shared/lib_battery_powerflow.h @@ -148,6 +148,32 @@ struct BatteryPower */ double adjustForDCEfficiencies(double power, double loss); + double getMaxACChargePower(); + double getMaxACDischargePower(); + double getMaxDCChargePower(); + double getMaxDCDischargePower(); + double getMaxChargeCurrent(); + double getMaxDischargeCurrent(); + + void setMaxACChargePower(double power) { + powerBatteryChargeMaxAC = power; + } + void setMaxACDischargePower(double power) { + powerBatteryDischargeMaxAC = power; + } + void setMaxDCChargePower(double power) { + powerBatteryChargeMaxDC = power; + } + void setMaxDCDischargePower(double power) { + powerBatteryDischargeMaxDC = power; + } + void setMaxChargeCurrent(double current) { + currentChargeMax = current; + } + void setMaxDischargeCurrent(double current) { + currentDischargeMax = current; + } + /// Copy the enumeration for AC/DC connected systems from ChargeController enum CONNECTION { DC_CONNECTED, AC_CONNECTED }; @@ -182,10 +208,6 @@ struct BatteryPower double powerFuelCellToLoad; ///< The power from the fuelcell to the load (kW) double powerFuelCellToBattery; ///< The power from the fuelcell to the battery (kW) double powerPVInverterDraw; ///< The power draw from the PV inverter (kW) - double powerBatteryChargeMaxDC; ///< The maximum sustained power the battery can charge (kWdc) - double powerBatteryDischargeMaxDC; ///< The maximum sustained power the battery can discharge (kWdc) - double powerBatteryChargeMaxAC; ///< The maximum sustained power the battery can charge (kWac) - double powerBatteryDischargeMaxAC; ///< The maximum sustained power the battery can discharge (kWac) double powerSystemLoss; ///< The auxiliary power loss in the system (kW) double powerConversionLoss; ///< The power loss due to conversions in the battery power electronics (kW) double powerInterconnectionLimit; ///< The size of the grid interconnection (kW). @@ -228,11 +250,16 @@ struct BatteryPower double stateOfChargeMin; ///< The minimum state of charge (0-100) double depthOfDischargeMax; ///< The maximum depth of discharge (0-100) - double currentChargeMax; ///< The maximum sustained current for charging [A] - double currentDischargeMax; ///< The maximum sustained current for discharging [A] - - double tolerance; ///< A numerical tolerance. Below this value, zero out the power flow + +private: + // These variables have values that require computation. Access through functions + double powerBatteryChargeMaxDC; ///< The maximum sustained power the battery can charge (kWdc) + double powerBatteryDischargeMaxDC; ///< The maximum sustained power the battery can discharge (kWdc) + double powerBatteryChargeMaxAC; ///< The maximum sustained power the battery can charge (kWac) + double powerBatteryDischargeMaxAC; ///< The maximum sustained power the battery can discharge (kWac) + double currentChargeMax; ///< The maximum sustained current for charging [A] + double currentDischargeMax; ///< The maximum sustained current for discharging [A] }; diff --git a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp index 5d8436111..8f284a23e 100644 --- a/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp +++ b/test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp @@ -70,7 +70,7 @@ TEST_F(AutoBTMTest_lib_battery_dispatch, DispatchAutoBTMGridCharging) { batteryPower = dispatchAutoBTM->getBatteryPower(); batteryPower->connectionMode = ChargeController::AC_CONNECTED; - EXPECT_EQ(batteryPower->powerBatteryChargeMaxAC, 50); + EXPECT_EQ(batteryPower->getMaxACChargePower(), 50); // TEST 1: Verify no grid charging since disallowed (_P_battery_use target is ~ -50) dispatchAutoBTM->dispatch(0, 0, 0); // original target for battery power is diff --git a/test/shared_test/lib_battery_powerflow_test.cpp b/test/shared_test/lib_battery_powerflow_test.cpp index e3f23de60..40bc0272b 100644 --- a/test/shared_test/lib_battery_powerflow_test.cpp +++ b/test/shared_test/lib_battery_powerflow_test.cpp @@ -53,8 +53,8 @@ void BatteryPowerFlowTest_lib_battery_powerflow::SetUp() m_batteryPower->singlePointEfficiencyACToDC = 0.96; m_batteryPower->singlePointEfficiencyDCToAC = 0.96; m_batteryPower->singlePointEfficiencyDCToDC = 0.98; - m_batteryPower->powerBatteryChargeMaxDC = 100; - m_batteryPower->powerBatteryDischargeMaxDC = 50; + m_batteryPower->setMaxDCChargePower(100); + m_batteryPower->setMaxDCDischargePower(50); m_batteryPower->connectionMode = ChargeController::AC_CONNECTED; // setup Sandia inverter using SMA America: SB3800TL-US-22 (240V) [CEC 2013] @@ -87,14 +87,14 @@ TEST_F(BatteryPowerFlowTest_lib_battery_powerflow, TestInitialize) // Grid charging Scenario m_batteryPower->canGridCharge = true; m_batteryPowerFlow->initialize(50, false); - EXPECT_EQ(m_batteryPower->powerBatteryDC, -m_batteryPower->powerBatteryChargeMaxDC); + EXPECT_EQ(m_batteryPower->powerBatteryDC, -m_batteryPower->getMaxDCChargePower()); // Discharging Scenario m_batteryPower->canDischarge = true; m_batteryPower->powerSystem = 50; m_batteryPower->powerLoad = 100; m_batteryPowerFlow->initialize(50, false); - EXPECT_EQ(m_batteryPower->powerBatteryDC, m_batteryPower->powerBatteryDischargeMaxDC); + EXPECT_EQ(m_batteryPower->powerBatteryDC, m_batteryPower->getMaxDCDischargePower()); } // Excess PV production From 2ac26d86c0df22608e9f6ba5425d318ca1c1ba12 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Mon, 11 Nov 2024 09:45:34 -0700 Subject: [PATCH 12/19] Fix typo in manual dispatch power vs current --- shared/lib_battery_dispatch_manual.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/lib_battery_dispatch_manual.cpp b/shared/lib_battery_dispatch_manual.cpp index 62c691343..e4a8dce2d 100644 --- a/shared/lib_battery_dispatch_manual.cpp +++ b/shared/lib_battery_dispatch_manual.cpp @@ -172,7 +172,7 @@ bool dispatch_manual_t::check_constraints(double &I, size_t count) m_batteryPower->canSystemCharge && // only do if battery is allowed to charge _Battery->SOC() < m_batteryPower->stateOfChargeMax - 1.0 && // and battery SOC is less than max std::abs(I) < std::abs(m_batteryPower->getMaxChargeCurrent()) && // and battery current is less than max charge current - std::abs(m_batteryPower->powerBatteryDC) < (m_batteryPower->getMaxDischargeCurrent() - 1.0) &&// and battery power is less than max charge power + std::abs(m_batteryPower->powerBatteryDC) < (m_batteryPower->getMaxDCDischargePower() - 1.0) &&// and battery power is less than max charge power I <= 0) // and battery was not discharging { double dI = 0; From efb774335d290cece4b35a8672107cff36037a83 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Mon, 11 Nov 2024 16:23:45 -0700 Subject: [PATCH 13/19] Run adjustment code based on which variables are actually available --- ssc/cmod_battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 601bd85dc..1f1425405 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1175,7 +1175,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c } std::vector adj_losses(1, 0.0); - if (vt.is_assigned("batt_adjust")) { + if (vt.is_assigned("batt_adjust_costant") || vt.is_assigned("batt_adjust_periods") || vt.is_assigned("batt_adjust_timeindex")) { adj_losses.clear(); adjustment_factors haf(&vt, "batt_adjust"); haf.setup(nrec); From d05b4f754465551fedcb37178bf40e4a124f8907 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Mon, 11 Nov 2024 16:53:33 -0700 Subject: [PATCH 14/19] Improved error handling for battery adjust losses --- ssc/cmod_battery.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 1f1425405..aae74f83e 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1178,7 +1178,9 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c if (vt.is_assigned("batt_adjust_costant") || vt.is_assigned("batt_adjust_periods") || vt.is_assigned("batt_adjust_timeindex")) { adj_losses.clear(); adjustment_factors haf(&vt, "batt_adjust"); - haf.setup(nrec); + if (!haf.setup(nrec)) { + throw exec_error("battery", "failed to setup battery adjustment factors: " + haf.error()); + } for (size_t i = 0; i < nrec; i++) { adj_losses.push_back(1.0 - haf(i)); // Convert to convention within powerflow and capacity code } From 3cb0df8a8d2ae564575ec174a53c080572a6ea6c Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Tue, 12 Nov 2024 16:47:36 -0700 Subject: [PATCH 15/19] Pass analysis period to haf setup function --- ssc/cmod_battery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index aae74f83e..88f8f3db4 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1178,10 +1178,10 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c if (vt.is_assigned("batt_adjust_costant") || vt.is_assigned("batt_adjust_periods") || vt.is_assigned("batt_adjust_timeindex")) { adj_losses.clear(); adjustment_factors haf(&vt, "batt_adjust"); - if (!haf.setup(nrec)) { + if (!haf.setup(nrec, batt_vars->analysis_period)) { throw exec_error("battery", "failed to setup battery adjustment factors: " + haf.error()); } - for (size_t i = 0; i < nrec; i++) { + for (size_t i = 0; i < haf.size(); i++) { adj_losses.push_back(1.0 - haf(i)); // Convert to convention within powerflow and capacity code } } From e06f1837ddeb9b66fc360395d2342b0ceb0b9eca Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 13 Nov 2024 09:57:06 -0700 Subject: [PATCH 16/19] Use correct nyears to account for system lifetime mode == 0 --- ssc/cmod_battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index 88f8f3db4..d2243f887 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1178,7 +1178,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c if (vt.is_assigned("batt_adjust_costant") || vt.is_assigned("batt_adjust_periods") || vt.is_assigned("batt_adjust_timeindex")) { adj_losses.clear(); adjustment_factors haf(&vt, "batt_adjust"); - if (!haf.setup(nrec, batt_vars->analysis_period)) { + if (!haf.setup(nrec, nyears)) { throw exec_error("battery", "failed to setup battery adjustment factors: " + haf.error()); } for (size_t i = 0; i < haf.size(); i++) { From 2bb5351885a9b2d74eab3e7f7d2b05d0379c33de Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 13 Nov 2024 10:21:37 -0700 Subject: [PATCH 17/19] Add output for total performance adjustment loss at a given timestep --- ssc/cmod_battery.cpp | 4 ++++ ssc/cmod_battery.h | 8 ++++---- ssc/common.cpp | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index d2243f887..ca832beb8 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1003,6 +1003,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c outBatteryToSystemLoad = vt.allocate("batt_to_system_load", nrec * nyears); outBatteryToGrid = vt.allocate("batt_to_grid", nrec * nyears); outBatteryToInverterDC = vt.allocate("batt_to_inverter_dc", nrec * nyears); + outAdjustLosses = vt.allocate("batt_perf_adj_loss", nrec * nyears); if (batt_vars->batt_meter_position == dispatch_t::BEHIND) { @@ -1758,6 +1759,8 @@ battstor::battstor(const battstor& orig) { outSystemChargePercent = orig.outSystemChargePercent; outGridChargePercent = orig.outGridChargePercent; + outAdjustLosses = orig.outAdjustLosses; + // copy models if (orig.batt_vars) batt_vars = orig.batt_vars; battery_metrics = new battery_metrics_t(orig._dt_hour); @@ -1902,6 +1905,7 @@ void battstor::outputs_fixed() outDOD[index] = (ssc_number_t)(state.lifetime->cycle_range); outDODCycleAverage[index] = (ssc_number_t)(state.lifetime->average_range); outCapacityPercent[index] = (ssc_number_t)(state.lifetime->q_relative); + outAdjustLosses[index] = (ssc_number_t)(state.losses->adjust_loss_percent * 100.0); if (batt_vars->batt_life_model == lifetime_params::CALCYC) { outCapacityPercentCycle[index] = (ssc_number_t)(state.lifetime->cycle->q_relative_cycle); outCapacityPercentCalendar[index] = (ssc_number_t)(state.lifetime->calendar->q_relative_calendar); diff --git a/ssc/cmod_battery.h b/ssc/cmod_battery.h index 9419c1436..095bd6ac3 100644 --- a/ssc/cmod_battery.h +++ b/ssc/cmod_battery.h @@ -430,8 +430,8 @@ struct battstor * outBatteryConversionPowerLoss, * outBatterySystemLoss, * outBatteryToInverterDC, - * outInterconnectionLoss, - * outCritLoadUnmet, + * outInterconnectionLoss, + * outCritLoadUnmet, * outCritLoad, * outUnmetLosses, * outAnnualSystemChargeEnergy, @@ -455,8 +455,8 @@ struct battstor * outPVS_violation_list, * outPVS_P_pv_ac, // testing with input pv output * outPVS_PV_ramp_interval, // testing with sampled input pv output - * outPVS_forecast_pv_energy; // testing with forecast based on input pv output - + * outPVS_forecast_pv_energy, // testing with forecast based on input pv output + * outAdjustLosses; double outAverageCycleEfficiency; double outAverageRoundtripEfficiency; diff --git a/ssc/common.cpp b/ssc/common.cpp index 89e95185b..908f0a032 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -569,6 +569,8 @@ var_info vtab_batt_adjustment_factors[] = { { SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable battery period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, { SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "Battery Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "batt_adjust_en_timeindex=1" , "" , ""}, { SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "Battery Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "batt_adjust_en_periods=1" , "COLS=3" , ""}, +{ SSC_OUTPUT, SSC_ARRAY, "batt_perf_adj_loss", "Battery performance adjustment loss", "%", "", "Time Series", "", "", "" }, + var_info_invalid }; var_info vtab_financial_capacity_payments[] = { From a87f41d07db4e67bb3cc89c20efa7699a04485ef Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 13 Nov 2024 16:19:47 -0700 Subject: [PATCH 18/19] Make ssc variable labels consistent with UI forms, help, and the losses page --- ssc/cmod_pvsamv1.cpp | 6 +++--- ssc/common.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ssc/cmod_pvsamv1.cpp b/ssc/cmod_pvsamv1.cpp index 11f754e5e..40fc2a3f3 100644 --- a/ssc/cmod_pvsamv1.cpp +++ b/ssc/cmod_pvsamv1.cpp @@ -833,7 +833,7 @@ static var_info _cm_vtab_pvsamv1[] = { { SSC_OUTPUT, SSC_ARRAY, "ac_transmission_loss", "Transmission loss", "kW", "", "Time Series (Transmission)", "", "", "" }, // Post batt AC losses - record so the powerflows from PV and batt to grid add up properly - { SSC_OUTPUT, SSC_ARRAY, "ac_perf_adj_loss", "AC performance adjustment loss", "kW", "", "Time Series (AC Loss)", "", "", "" }, + { SSC_OUTPUT, SSC_ARRAY, "ac_perf_adj_loss", "AC availability loss", "kW", "", "Time Series (AC Loss)", "", "", "" }, { SSC_OUTPUT, SSC_ARRAY, "ac_lifetime_loss", "AC lifetime daily loss", "kW", "", "Time Series (AC Loss)", "", "", "" }, // monthly and annual outputs @@ -966,7 +966,7 @@ static var_info _cm_vtab_pvsamv1[] = { { SSC_OUTPUT, SSC_NUMBER, "annual_dc_tracking_loss_percent", "DC tracking loss", "%", "", "Loss", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_dc_nameplate_loss_percent", "DC nameplate loss", "%", "", "Loss", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_dc_optimizer_loss_percent", "DC power optimizer loss", "%", "", "Loss", "", "", "" }, - { SSC_OUTPUT, SSC_NUMBER, "annual_dc_perf_adj_loss_percent", "DC performance adjustment loss", "%", "", "Loss", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_dc_perf_adj_loss_percent", "DC availability loss", "%", "", "Loss", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_dc_lifetime_loss_percent", "Lifetime daily DC loss- year 1", "%", "", "Loss", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_dc_battery_loss_percent", "DC connected battery loss- year 1", "%", "", "Loss", "", "", "" }, { SSC_OUTPUT, SSC_NUMBER, "annual_dc_inv_tdc_loss_percent", "DC inverter thermal derate loss", "%", "", "Loss", "", "", "" }, @@ -990,7 +990,7 @@ static var_info _cm_vtab_pvsamv1[] = { // annual_ac_net - { SSC_OUTPUT, SSC_NUMBER, "annual_ac_perf_adj_loss_percent", "AC performance adjustment loss", "%", "", "Loss", "", "", "" }, + { SSC_OUTPUT, SSC_NUMBER, "annual_ac_perf_adj_loss_percent", "AC availability loss", "%", "", "Loss", "", "", "" }, // annual_energy /* diff --git a/ssc/common.cpp b/ssc/common.cpp index 908f0a032..d931a83e1 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -569,7 +569,7 @@ var_info vtab_batt_adjustment_factors[] = { { SSC_INPUT, SSC_NUMBER, "batt_adjust_en_periods" , "Enable battery period-based adjustment factors", "0/1", "", "Adjustment Factors", "?=0", "BOOLEAN", "" }, { SSC_INPUT,SSC_ARRAY , "batt_adjust_timeindex" , "Battery Lifetime Adjustment Factors" , "%" , "" , "Adjustment Factors" , "batt_adjust_en_timeindex=1" , "" , ""}, { SSC_INPUT,SSC_MATRIX , "batt_adjust_periods" , "Battery Period-based Adjustment Factors" , "%" , "n x 3 matrix [ start, end, loss ]" , "Adjustment Factors" , "batt_adjust_en_periods=1" , "COLS=3" , ""}, -{ SSC_OUTPUT, SSC_ARRAY, "batt_perf_adj_loss", "Battery performance adjustment loss", "%", "", "Time Series", "", "", "" }, +{ SSC_OUTPUT, SSC_ARRAY, "batt_availability_loss", "Battery availability loss", "%", "", "Time Series", "", "", "" }, var_info_invalid }; From 3ed4abedf39ae3deffa96c5cc934080d5b62abe4 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 13 Nov 2024 16:23:18 -0700 Subject: [PATCH 19/19] bring cmod battery in line with name chage --- ssc/cmod_battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index ca832beb8..a85774cc6 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -1003,7 +1003,7 @@ battstor::battstor(var_table& vt, bool setup_model, size_t nrec, double dt_hr, c outBatteryToSystemLoad = vt.allocate("batt_to_system_load", nrec * nyears); outBatteryToGrid = vt.allocate("batt_to_grid", nrec * nyears); outBatteryToInverterDC = vt.allocate("batt_to_inverter_dc", nrec * nyears); - outAdjustLosses = vt.allocate("batt_perf_adj_loss", nrec * nyears); + outAdjustLosses = vt.allocate("batt_availability_loss", nrec * nyears); if (batt_vars->batt_meter_position == dispatch_t::BEHIND) {