From 495b0a8ad67d70f3fb54f2f725d6b2e907812ab1 Mon Sep 17 00:00:00 2001 From: Darice L Guittet Date: Mon, 11 Nov 2024 07:35:23 -0800 Subject: [PATCH] Battery cycle-calendar life model updated to use sum not min (#1239) * battery life cycle calendar use sum not min * increase tol in test * pass thru tol * pass through tol --- .../lib_battery_lifetime_calendar_cycle.cpp | 19 ++++---- shared/lib_battery_lifetime_calendar_cycle.h | 6 +++ test/shared_test/lib_battery_capacity_test.h | 3 +- test/shared_test/lib_battery_test.cpp | 46 +++++++++---------- test/shared_test/lib_battery_test.h | 13 ++---- test/ssc_test/cmod_battery_pvsamv1_test.cpp | 6 +-- test/ssc_test/cmod_battery_test.cpp | 8 ++-- 7 files changed, 52 insertions(+), 49 deletions(-) diff --git a/shared/lib_battery_lifetime_calendar_cycle.cpp b/shared/lib_battery_lifetime_calendar_cycle.cpp index 84cb908fb..26dd89be3 100644 --- a/shared/lib_battery_lifetime_calendar_cycle.cpp +++ b/shared/lib_battery_lifetime_calendar_cycle.cpp @@ -263,6 +263,8 @@ double lifetime_cycle_t::average_range() { return state->average_range; } double lifetime_cycle_t::capacity_percent() { return state->cycle->q_relative_cycle; } +double lifetime_cycle_t::capacity_loss_percent() { return fmax(0, 100. - state->cycle->q_relative_cycle); } + void lifetime_cycle_t::resetDailyCycles() { state->cycle->DOD_min = -1; state->cycle->DOD_max = -1; @@ -525,6 +527,8 @@ lifetime_calendar_t *lifetime_calendar_t::clone() { double lifetime_calendar_t::capacity_percent() { return state->calendar->q_relative_calendar; } +double lifetime_calendar_t::capacity_loss_percent() { return fmax(0, 100. - state->calendar->q_relative_calendar); } + lifetime_state lifetime_calendar_t::get_state() { return *state; } double lifetime_calendar_t::runLifetimeCalendarModel(size_t lifetimeIndex, double T, double SOC) { @@ -680,18 +684,15 @@ void lifetime_calendar_cycle_t::runLifetimeModels(size_t lifetimeIndex, bool cha double q_last = state->q_relative; if (q_last > 0) { - double q_cycle = cycle_model->capacity_percent(); - double q_calendar; - if (charge_changed) - q_cycle = cycle_model->runCycleLifetime(prev_DOD); + cycle_model->runCycleLifetime(prev_DOD); else if (lifetimeIndex == 0) - q_cycle = cycle_model->runCycleLifetime(DOD); + cycle_model->runCycleLifetime(DOD); - q_calendar = calendar_model->runLifetimeCalendarModel(lifetimeIndex, T_battery, 100. - DOD); + calendar_model->runLifetimeCalendarModel(lifetimeIndex, T_battery, 100. - DOD); - // total capacity is min of cycle (Q_neg) and calendar (Q_li) capacity - state->q_relative = fmin(q_cycle, q_calendar); + // total capacity is sum of cycle (Q_neg) and calendar (Q_li) capacity loss + state->q_relative = 100. - cycle_model->capacity_loss_percent() - calendar_model->capacity_loss_percent(); } state->q_relative = fmax(state->q_relative, 0); @@ -706,6 +707,6 @@ double lifetime_calendar_cycle_t::estimateCycleDamage() { void lifetime_calendar_cycle_t::replaceBattery(double percent_to_replace) { cycle_model->replaceBattery(percent_to_replace); calendar_model->replaceBattery(percent_to_replace); - state->q_relative = fmin(cycle_model->capacity_percent(), calendar_model->capacity_percent()); + state->q_relative = 100. - cycle_model->capacity_loss_percent() - calendar_model->capacity_loss_percent(); } diff --git a/shared/lib_battery_lifetime_calendar_cycle.h b/shared/lib_battery_lifetime_calendar_cycle.h index 626565c7d..83107ad40 100644 --- a/shared/lib_battery_lifetime_calendar_cycle.h +++ b/shared/lib_battery_lifetime_calendar_cycle.h @@ -125,6 +125,9 @@ class lifetime_cycle_t { /// Return the relative capacity percentage of nominal (%) double capacity_percent(); + /// Return the relative capacity loss percentage of nominal (%) + double capacity_loss_percent(); + /// Run the rainflow counting algorithm at the current depth-of-discharge to determine cycle void rainflow(double DOD); @@ -218,6 +221,9 @@ class lifetime_calendar_t { /// Return the relative capacity percentage of nominal (%) double capacity_percent(); + /// Return the relative capacity loss percentage of nominal (%) + double capacity_loss_percent(); + lifetime_state get_state(); protected: diff --git a/test/shared_test/lib_battery_capacity_test.h b/test/shared_test/lib_battery_capacity_test.h index 5ae555983..44996b29b 100644 --- a/test/shared_test/lib_battery_capacity_test.h +++ b/test/shared_test/lib_battery_capacity_test.h @@ -40,8 +40,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //#include "lib_battery_capacity.h" #include "lib_battery.h" -static void compareState(capacity_state tested_state, capacity_state expected_state, const std::string& msg){ - double tol = 0.01; +static void compareState(capacity_state tested_state, capacity_state expected_state, const std::string& msg, double tol=0.01){ EXPECT_NEAR(tested_state.q0, expected_state.q0, tol) << msg; EXPECT_NEAR(tested_state.qmax_thermal, expected_state.qmax_thermal, tol) << msg; EXPECT_NEAR(tested_state.qmax_lifetime, expected_state.qmax_lifetime, tol) << msg; diff --git a/test/shared_test/lib_battery_test.cpp b/test/shared_test/lib_battery_test.cpp index 7af0f9a6c..4100ac79a 100644 --- a/test/shared_test/lib_battery_test.cpp +++ b/test/shared_test/lib_battery_test.cpp @@ -288,17 +288,17 @@ 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.batt_voltage = 465.54; + s.capacity = { 45.00, 900.219, 864.213, 8.753, 0, 5.207, 6.220, 2}; + s.batt_voltage = 465.943; s.lifetime.q_relative = 93.08; s.lifetime.cycle->q_relative_cycle = 92.03; s.lifetime.n_cycles = 399; - s.lifetime.cycle_range = 89.58; - s.lifetime.average_range = 88.80; - s.lifetime.cycle->rainflow_Xlt = 89.60; - s.lifetime.cycle->rainflow_Ylt = 89.84; + s.lifetime.cycle_range = 89.10; + s.lifetime.average_range = 88.91; + s.lifetime.cycle->rainflow_Xlt = 89.13; + s.lifetime.cycle->rainflow_Ylt = 89.79; s.lifetime.cycle->rainflow_jlt = 3; - s.lifetime.day_age_of_battery = 2757.54; + s.lifetime.day_age_of_battery = 2765.96; s.lifetime.calendar->q_relative_calendar = 98.0; s.lifetime.calendar->dq_relative_calendar_old = 0.039; s.thermal = {96.0, 20.00, 20}; @@ -306,9 +306,9 @@ TEST_F(lib_battery_test, runTestCycleAt1C){ compareState(batteryModel, s, "runTestCycleAt1C: 3"); - EXPECT_NEAR(capacity_passed, 357300, 1000) << "Current passing through cell"; + EXPECT_NEAR(capacity_passed, 354541, 1000) << "Current passing through cell"; double qmax = fmax(s.capacity.qmax_lifetime, s.capacity.qmax_thermal); - EXPECT_NEAR(qmax/q, .93, 0.01) << "capacity relative to max capacity"; + EXPECT_NEAR(qmax/q, .90, 0.01) << "capacity relative to max capacity"; } TEST_F(lib_battery_test, runTestCycleAt3C){ @@ -360,27 +360,27 @@ 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.batt_voltage = 467.105; - s.lifetime.q_relative = 93.08; - s.lifetime.day_age_of_battery = 2591.17; + s.capacity = { 47.12, 901.56, 865.505, 8.87, 0, 5.444, 6.47, 2}; + s.batt_voltage = 468.126; + s.lifetime.q_relative = 90.16; + s.lifetime.day_age_of_battery = 2587.83; s.lifetime.cycle->q_relative_cycle = 92.08; s.lifetime.n_cycles = 399; - s.lifetime.cycle_range = 89.38; - s.lifetime.average_range = 88.95; - s.lifetime.cycle->rainflow_Xlt = 89.41; - s.lifetime.cycle->rainflow_Ylt = 89.67; + s.lifetime.cycle_range = 89.12; + s.lifetime.average_range = 89.18; + s.lifetime.cycle->rainflow_Xlt = 89.15; + s.lifetime.cycle->rainflow_Ylt = 89.56; s.lifetime.cycle->rainflow_jlt = 3; s.lifetime.cycle->q_relative_cycle = 92.04; - s.lifetime.calendar->q_relative_calendar = 98.11; + s.lifetime.calendar->q_relative_calendar = 98.13; s.lifetime.calendar->dq_relative_calendar_old = 0.0393; s.thermal = {96.01, 20, 20}; s.last_idx = 32991; - compareState(batteryModel, s, "runTest: 3"); + compareState(batteryModel, s, "runTest: 3", 0.1); - EXPECT_NEAR(capacity_passed, 357702, 100) << "Current passing through cell"; + EXPECT_NEAR(capacity_passed, 355949, 100) << "Current passing through cell"; double qmax = fmax(s.capacity.qmax_lifetime, s.capacity.qmax_thermal); - EXPECT_NEAR(qmax/q, 0.9209, 0.01) << "capacity relative to max capacity"; + EXPECT_NEAR(qmax/q, 0.9016, 0.01) << "capacity relative to max capacity"; } TEST_F(lib_battery_test, runDuplicates) { @@ -725,8 +725,8 @@ TEST_F(lib_battery_test, AdaptiveTimestep) { } - EXPECT_NEAR(batteryModel->charge_maximum(), 577.31, 1e-2); - EXPECT_NEAR(batt_subhourly->charge_maximum(), 577.22, 1e-2); + EXPECT_NEAR(batteryModel->charge_maximum(), 576.95, 1e-2); + EXPECT_NEAR(batt_subhourly->charge_maximum(), 557.69, 1e-2); EXPECT_NEAR(batt_adaptive->charge_maximum(), 577.26, 1e-2); EXPECT_NEAR(batteryModel->SOC(), 94.98, 1e-2); diff --git a/test/shared_test/lib_battery_test.h b/test/shared_test/lib_battery_test.h index 2ed8e2325..4a0cb7a46 100644 --- a/test/shared_test/lib_battery_test.h +++ b/test/shared_test/lib_battery_test.h @@ -42,8 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "lib_battery_lifetime_test.h" #include "lib_battery_lifetime_nmc.h" -static void compareState(thermal_state tested_state, thermal_state expected_state, const std::string& msg){ - double tol = 0.02; +static void compareState(thermal_state tested_state, thermal_state expected_state, const std::string& msg, double tol=0.02){ EXPECT_NEAR(tested_state.T_batt, expected_state.T_batt, tol) << msg; EXPECT_NEAR(tested_state.T_room, expected_state.T_room, tol) << msg; EXPECT_NEAR(tested_state.q_relative_thermal, expected_state.q_relative_thermal, tol) << msg; @@ -135,13 +134,12 @@ struct battery_state_test{ } }; -static void compareState(std::unique_ptr&model, const battery_state_test& expected_state, const std::string& msg){ +static void compareState(std::unique_ptr&model, const battery_state_test& expected_state, const std::string& msg, double tol=0.01){ auto tested_state = model->get_state(); - compareState(*tested_state.capacity, expected_state.capacity, msg); + compareState(*tested_state.capacity, expected_state.capacity, msg, tol); - EXPECT_NEAR(tested_state.V, expected_state.batt_voltage, 0.01) << msg; + EXPECT_NEAR(tested_state.V, expected_state.batt_voltage, tol) << msg; - double tol = 0.01; auto lifetime_tested = tested_state.lifetime; auto lifetime_expected = expected_state.lifetime; EXPECT_NEAR(lifetime_tested->day_age_of_battery, lifetime_expected.day_age_of_battery, tol) << msg; @@ -159,8 +157,7 @@ static void compareState(std::unique_ptr&model, const battery_state_t EXPECT_NEAR(lifetime_tested->cycle->rainflow_Ylt, cyc_expected.rainflow_Ylt, tol) << msg; EXPECT_NEAR(lifetime_tested->cycle->rainflow_jlt, cyc_expected.rainflow_jlt, tol) << msg; - compareState(*tested_state.thermal, expected_state.thermal, msg); - + compareState(*tested_state.thermal, expected_state.thermal, msg, tol); } class lib_battery_test : public ::testing::Test diff --git a/test/ssc_test/cmod_battery_pvsamv1_test.cpp b/test/ssc_test/cmod_battery_pvsamv1_test.cpp index 6108ebc4c..1dc6003f9 100644 --- a/test/ssc_test/cmod_battery_pvsamv1_test.cpp +++ b/test/ssc_test/cmod_battery_pvsamv1_test.cpp @@ -364,7 +364,7 @@ TEST_F(CMPvsamv1BatteryIntegration_cmod_pvsamv1, ResidentialDCBatteryModelIntegr ssc_number_t peakCycles[4] = { 1, 1, 1, 3 }; ssc_number_t avgCycles[4] = { 1.0, 1.0, 0.4794, 1.0110 }; - ssc_number_t q_rel[4] = { 97.098, 97.054, 97.239, 93.334 }; + ssc_number_t q_rel[4] = { 95.461, 95.426, 96.777, 92.489 }; ssc_number_t cyc_avg[4] = { 35.022, 35.218, 12.381, 72.29 }; // Test peak shaving look ahead, peak shaving look behind, and automated grid power target. Others require additional input data @@ -958,7 +958,7 @@ TEST_F(CMPvsamv1BatteryIntegration_cmod_pvsamv1, ResidentialDCBatteryModelPriceS auto batt_q_rel = data_vtab->as_vector_ssc_number_t("batt_capacity_percent"); auto batt_cyc_avg = data_vtab->as_vector_ssc_number_t("batt_DOD_cycle_average"); - EXPECT_NEAR(batt_q_rel.back(), 98.000, 5e-2); + EXPECT_NEAR(batt_q_rel.back(), 97.241, 5e-2); EXPECT_NEAR(batt_cyc_avg.back(), 27.49, 1.0); // High tolerance due to ~ 1% dispatch difference between linux and windows. Tighten in the future by improving the algorithm. } } @@ -1144,7 +1144,7 @@ TEST_F(CMPvsamv1BatteryIntegration_cmod_pvsamv1, ResidentialACBatteryModelGridOu ssc_number_t expectedEnergy = 8521.00; ssc_number_t expectedBatteryChargeEnergy = 3290.77; ssc_number_t expectedBatteryDischargeEnergy = 2974.91; - ssc_number_t expectedCritLoadUnmet = 494.31; + ssc_number_t expectedCritLoadUnmet = 502.003; ssc_number_t peakKwCharge = -3.4; ssc_number_t peakKwDischarge = 1.964; diff --git a/test/ssc_test/cmod_battery_test.cpp b/test/ssc_test/cmod_battery_test.cpp index ddbc8025a..4a2e42c3a 100644 --- a/test/ssc_test/cmod_battery_test.cpp +++ b/test/ssc_test/cmod_battery_test.cpp @@ -99,7 +99,7 @@ TEST_F(CMBattery_cmod_battery, ResilienceMetricsFullLoad){ EXPECT_EQ(resilience_hours[0], 0); // Max current restrictions prevent this battery from meeting the outage until day 2 (hr 46) EXPECT_EQ(resilience_hours[46], 5); - EXPECT_NEAR(avg_critical_load, 686.02, 0.1); + EXPECT_NEAR(avg_critical_load, 683.90, 0.1); EXPECT_NEAR(resilience_hrs_avg, 1.11, 0.01); EXPECT_EQ(resilience_hrs_min, 0); EXPECT_EQ(outage_durations[0], 0); @@ -117,7 +117,7 @@ TEST_F(CMBattery_cmod_battery, ResilienceMetricsFullLoad){ if (power_max - batt_power[i] < 0.1) max_indices.push_back(i); } - EXPECT_EQ(max_indices.size(), 26); + EXPECT_EQ(max_indices.size(), 25); EXPECT_EQ(max_indices[0], 2743); auto batt_q0 = data_vtab->as_vector_ssc_number_t("batt_q0"); @@ -155,8 +155,8 @@ TEST_F(CMBattery_cmod_battery, ResilienceMetricsFullLoadLifetime){ EXPECT_EQ(resilience_hours[0], 0); // Max current restrictions prevent this battery from meeting the outage until day 2 (hr 46) EXPECT_EQ(resilience_hours[46], 5); - EXPECT_NEAR(avg_critical_load, 683.06, 0.1); - EXPECT_NEAR(resilience_hrs_avg, 1.103, 0.01); + EXPECT_NEAR(avg_critical_load, 674.536, 0.1); + EXPECT_NEAR(resilience_hrs_avg, 1.076, 0.01); EXPECT_EQ(resilience_hrs_min, 0); EXPECT_EQ(outage_durations[0], 0); EXPECT_EQ(resilience_hrs_max, 17);