Skip to content

Commit

Permalink
Add automated and manual dispatch tests, add current restriction to a…
Browse files Browse the repository at this point in the history
…vailability losses
  • Loading branch information
brtietz committed Nov 7, 2024
1 parent 559122e commit 282d2a6
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 18 deletions.
4 changes: 4 additions & 0 deletions shared/lib_battery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
3 changes: 3 additions & 0 deletions shared/lib_battery.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
27 changes: 15 additions & 12 deletions shared/lib_battery_dispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down
124 changes: 118 additions & 6 deletions test/shared_test/lib_battery_dispatch_automatic_btm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> 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<double> 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<double> 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,
Expand Down Expand Up @@ -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<double> 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<double> 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
Expand Down Expand Up @@ -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<double> 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<double> 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;
}

}
44 changes: 44 additions & 0 deletions test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> 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<double> 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<double> 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;
}
}
36 changes: 36 additions & 0 deletions test/shared_test/lib_battery_dispatch_automatic_fom_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> charging_losses(12, 0); // Monthly losses
std::vector<double> discharging_losses(12, 0);
std::vector<double> idle_losses(12, 0);
std::vector<double> 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();
Expand Down
25 changes: 25 additions & 0 deletions test/shared_test/lib_battery_dispatch_manual_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> testCanDischargeToGrid;
Expand Down
31 changes: 31 additions & 0 deletions test/shared_test/lib_battery_dispatch_manual_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> charging_losses(12, 0); // Monthly losses
std::vector<double> discharging_losses(12, 0);
std::vector<double> idle_losses(12, 0);
std::vector<double> 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

0 comments on commit 282d2a6

Please sign in to comment.