From 25572ccbccfdfe967936839eb5b2a2f295bfdde1 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Tue, 10 Oct 2023 10:40:32 -0600 Subject: [PATCH] Add internal resistance to voltage table calculations (#1016) * Address loss diagram issue for POA reference cell in SAM issue 1366 * Working without pressure column Need to remove some modifications in cmod_pvsamv1 - set to SAM_1366 branch * Reset POA_P mode - uses decomposition with and without pressure column * Add wfpoa to reported outputs to facilitate troubleshooting * Size the collector loop mass flow based on n_collectors (#1004) * Update to GitHub Actions to latest Ubuntu Ubuntu-18.04 deprecated as of 4/2023 https://github.blog/changelog/2022-08-09-github-actions-the-ubuntu-18-04-actions-runner-image-is-being-deprecated-and-will-be-removed-by-12-1-22/ * Add internal resistance to voltage table calculations * Update voltage equations and tests to account for zero current at full battery, new resistance in table tests * update resilience tests and fix voltage at 100 percent dod --------- Co-authored-by: Steven Janzou Co-authored-by: Paul Gilman Co-authored-by: Matthew Boyd <30417543+Matthew-Boyd@users.noreply.github.com> --- shared/lib_battery_voltage.cpp | 23 ++++++---- shared/lib_battery_voltage.h | 2 +- test/shared_test/lib_battery_voltage_test.cpp | 46 +++++++++---------- test/shared_test/lib_battery_voltage_test.h | 4 ++ test/shared_test/lib_resilience_test.cpp | 8 ++-- 5 files changed, 46 insertions(+), 37 deletions(-) diff --git a/shared/lib_battery_voltage.cpp b/shared/lib_battery_voltage.cpp index fd42afed4..8c53f3db7 100644 --- a/shared/lib_battery_voltage.cpp +++ b/shared/lib_battery_voltage.cpp @@ -181,7 +181,7 @@ voltage_t *voltage_table_t::clone() { return new voltage_table_t(*this); } -double voltage_table_t::calculate_voltage(double DOD) { +double voltage_table_t::calculate_voltage(double DOD, double I) { DOD = fmax(0., DOD); DOD = fmin(DOD, 100.); @@ -189,23 +189,27 @@ double voltage_table_t::calculate_voltage(double DOD) { while (row < params->voltage_table.size() && DOD > params->voltage_table[row][0]) { row++; } + // + if (DOD < tolerance || DOD > 100. - tolerance) { + I = 0.0; // At full or empty, current must go to zero + } - return fmax(slopes[row] * DOD + intercepts[row], 0); + return fmax(slopes[row] * DOD + intercepts[row], 0) - I * params->resistance; } void voltage_table_t::set_initial_SOC(double init_soc) { - state->cell_voltage = calculate_voltage(100. - init_soc); + state->cell_voltage = calculate_voltage(100. - init_soc, 0.0); } double voltage_table_t::calculate_voltage_for_current(double I, double q, double qmax, double) { double DOD = (q - I * params->dt_hr) / qmax * 100.; - return calculate_voltage(DOD) * params->num_cells_series; + return calculate_voltage(DOD, I / params->num_strings) * params->num_cells_series; } -void voltage_table_t::updateVoltage(double q, double qmax, double, const double, double) { +void voltage_table_t::updateVoltage(double q, double qmax, double I, const double, double) { double DOD = 100. * (1 - q / qmax); - state->cell_voltage = calculate_voltage(DOD); + state->cell_voltage = calculate_voltage(DOD, I / params->num_strings); } // helper fx to calculate depth of discharge from current and max capacities @@ -215,7 +219,7 @@ double voltage_table_t::calculate_max_charge_w(double q, double qmax, double, do double current = (q - qmax) / params->dt_hr; if (max_current) *max_current = current; - return calculate_voltage(0.) * current * params->num_cells_series; + return calculate_voltage(0., current / params->num_strings) * current * params->num_cells_series; } double voltage_table_t::calculate_max_discharge_w(double q, double qmax, double, double *max_current) { @@ -230,7 +234,7 @@ double voltage_table_t::calculate_max_discharge_w(double q, double qmax, double, dod = fmin(100, dod); dod = fmax(0, dod); double current = qmax * ((1. - DOD0 / 100.) - (1. - dod / 100.)) / params->dt_hr; - double p = calculate_voltage(dod) * current; + double p = calculate_voltage(dod, current / params->num_strings) * current; if (p > max_P) { max_P = p; max_I = current; @@ -291,7 +295,8 @@ double voltage_table_t::calculate_current_for_target_w(double P_watts, double q, auto DOD_upper = params->voltage_table[upper][0]; auto DOD_lower = params->voltage_table[lower][0]; if (DOD_new <= DOD_upper && DOD_new >= DOD_lower) { - double P = (q - (100. - DOD_new) * qmax/100) * (a * DOD_new + b); + current = qmax * ((1. - DOD / 100.) - (1. - DOD_new / 100.)) / params->dt_hr; + double P = current * (a * DOD_new + b - current / params->num_strings * params->resistance); if (std::abs(P) > std::abs(P_best)) { P_best = P; DOD_best = DOD_new; diff --git a/shared/lib_battery_voltage.h b/shared/lib_battery_voltage.h index 550ba98d0..9b069261f 100644 --- a/shared/lib_battery_voltage.h +++ b/shared/lib_battery_voltage.h @@ -165,7 +165,7 @@ class voltage_table_t : public voltage_t { std::vector slopes; std::vector intercepts; - double calculate_voltage(double DOD); + double calculate_voltage(double DOD, double I); private: void initialize(); diff --git a/test/shared_test/lib_battery_voltage_test.cpp b/test/shared_test/lib_battery_voltage_test.cpp index f50169b82..a3498ebf0 100644 --- a/test/shared_test/lib_battery_voltage_test.cpp +++ b/test/shared_test/lib_battery_voltage_test.cpp @@ -608,7 +608,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, updateCapacityTest){ I = 5; cap->updateCapacity(I, dt_hour); // qmx = 10, I = 4.5, q0 = 0.5 model->updateVoltage(cap->q0(), cap->qmax(), cap->I(), 0, dt_hour); - EXPECT_NEAR(model->cell_voltage(), 1.35, tol); + EXPECT_NEAR(model->cell_voltage(), 1.336, tol); EXPECT_NEAR(cap->q0(), 0.5, tol); } @@ -631,7 +631,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, updateCapacitySubHourly){ I = 5; cap->updateCapacity(I, dt_hour); // qmx = 10, I = 4.5, q0 = 0.5 model->updateVoltage(cap->q0(), cap->qmax(), cap->I(), 0, dt_hour); - EXPECT_NEAR(model->cell_voltage(), 3.504, tol); + EXPECT_NEAR(model->cell_voltage(), 3.492, tol); EXPECT_NEAR(cap->q0(), 2.5, tol); } @@ -654,7 +654,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, updateCapacitySubMinute){ I = 5; cap->updateCapacity(I, dt_hour); // qmx = 10, I = 4.5, q0 = 0.5 model->updateVoltage(cap->q0(), cap->qmax(), cap->I(), 0, dt_hour); - EXPECT_NEAR(model->cell_voltage(), 3.689, tol); + EXPECT_NEAR(model->cell_voltage(), 3.677, tol); EXPECT_NEAR(cap->q0(), 4.975, 1e-3); } @@ -888,15 +888,15 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeHourly){ // start at half SOC double max_current; double power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 1194, 1); // current ~4 + EXPECT_NEAR(power, 1192.6, 1); // current ~4 double max_current_calc = model->calculate_current_for_target_w(power - 1, cap->q0(), cap->qmax(), 0); - EXPECT_NEAR(max_current_calc, 2.45, 1e-2); + EXPECT_NEAR(max_current_calc, 2.44, 1e-2); // Does not empty battery for highest power cap->updateCapacity(max_current, dt_hour); EXPECT_NEAR(cap->SOC(), 25.5, 1e-3); // Check power - model->updateVoltage(cap->q0(), cap->qmax(), max_current_calc, 0, dt_hour); - EXPECT_NEAR(max_current_calc * model->battery_voltage(), power, 2); + model->updateVoltage(cap->q0(), cap->qmax(), max_current, 0, dt_hour); + EXPECT_NEAR(max_current * model->battery_voltage(), power, 2); // start at empty SOC power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); @@ -915,15 +915,15 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeHourly){ while (cap->SOC() < 95) cap->updateCapacity(I, dt_hour); power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 3569, 1); + EXPECT_NEAR(power, 3553, 1); max_current_calc = model->calculate_current_for_target_w(power - 1, cap->q0(), cap->qmax(), 0); - EXPECT_NEAR(max_current_calc, max_current, 1e-2); + EXPECT_NEAR(max_current_calc, max_current, 1e-1); // Does not empty battery for highest power cap->updateCapacity(max_current, dt_hour); EXPECT_NEAR(cap->SOC(), 27.02, 1e-2); // Check power - model->updateVoltage(cap->q0(), cap->qmax(), max_current_calc, 0, dt_hour); - EXPECT_NEAR(max_current_calc * model->battery_voltage(), power, 2); + model->updateVoltage(cap->q0(), cap->qmax(), max_current, 0, dt_hour); + EXPECT_NEAR(max_current * model->battery_voltage(), power, 2); } TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeHourlyInputErrors) { @@ -962,17 +962,17 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeSubHourly){ // start at half SOC double max_current; double power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 2388, 1); // current ~4 + EXPECT_NEAR(power, 2381, 1); // current ~4 double max_current_calc = model->calculate_current_for_target_w(power - 1, cap->q0(), cap->qmax(), 0); - EXPECT_NEAR(max_current, 4.9, 1e-2); - EXPECT_NEAR(max_current_calc, 4.9, 1e-2); + EXPECT_NEAR(max_current, 4.90, 1e-2); + EXPECT_NEAR(max_current_calc, 4.90, 1e-1); //4.88 as of 10/9/2023 // Does not empty battery for highest power cap->updateCapacity(max_current, dt_hour); EXPECT_NEAR(cap->SOC(), 25.5, 1e-3); // start at empty SOC power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 1163, 1); + EXPECT_NEAR(power, 1161, 1); max_current_calc = model->calculate_current_for_target_w(power - 1, cap->q0(), cap->qmax(), 0); EXPECT_NEAR(max_current, 2.44, 1e-1); EXPECT_NEAR(max_current_calc, 2.44, 1e-1); @@ -985,9 +985,9 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeSubHourly){ while (cap->SOC() < 95) cap->updateCapacity(I, dt_hour); power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 7139, 1); + EXPECT_NEAR(power, 7073, 1); max_current_calc = model->calculate_current_for_target_w(power - 1, cap->q0(), cap->qmax(), 0); - EXPECT_NEAR(max_current_calc, max_current, 1e-2); + EXPECT_NEAR(max_current_calc, max_current, 2e-1); // Does not empty battery for highest power cap->updateCapacity(max_current, dt_hour); EXPECT_NEAR(cap->SOC(), 27.02, 1e-2); @@ -1000,7 +1000,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeHourly_table // start at half SOC double max_current; double power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 293, &max_current); - EXPECT_NEAR(power, 719.25, 1); // current ~4 + EXPECT_NEAR(power, 714.44, 1); // current ~4 double max_current_calc = model->calculate_current_for_target_w(power, cap->q0(), cap->qmax(), 293); EXPECT_NEAR(max_current, 3.94, 1e-2); EXPECT_NEAR(max_current_calc, 3.94, 1e-2); @@ -1028,7 +1028,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeHourly_table while (cap->SOC() < 95) cap->updateCapacity(I, dt_hour); power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 293, &max_current); - EXPECT_NEAR(power, 1480.35, 1); + EXPECT_NEAR(power, 1462.98, 1); max_current_calc = model->calculate_current_for_target_w(power, cap->q0(), cap->qmax(), 293); EXPECT_NEAR(max_current, 7.5, 1e-2); EXPECT_NEAR(max_current_calc, 7.5, 1e-2); @@ -1047,7 +1047,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeSubMinute){ // start at half SOC double max_current; double power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 238891, 1); // current ~8 + EXPECT_NEAR(power, 164727, 1); // current ~8 double max_current_calc = model->calculate_current_for_target_w(power, cap->q0(), cap->qmax(), 0); EXPECT_NEAR(max_current_calc, max_current, 0.2); // Does not empty battery for highest power @@ -1059,7 +1059,7 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeSubMinute){ // start at empty SOC power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 116333, 1); + EXPECT_NEAR(power, 97792, 1); max_current_calc = model->calculate_current_for_target_w(power, cap->q0(), cap->qmax(), 0); EXPECT_NEAR(max_current_calc, max_current, 1e-1); // Does not empty battery for highest power @@ -1074,12 +1074,12 @@ TEST_F(voltage_table_lib_battery_voltage_test, calculateMaxDischargeSubMinute){ while (cap->SOC() < 95) cap->updateCapacity(I, dt_hour); power = model->calculate_max_discharge_w(cap->q0(), cap->qmax(), 0, &max_current); - EXPECT_NEAR(power, 685795, 1); + EXPECT_NEAR(power, 207281, 1); max_current_calc = model->calculate_current_for_target_w(power, cap->q0(), cap->qmax(), 0); EXPECT_NEAR(max_current_calc, max_current, 0.3); // Does not empty battery for highest power cap->updateCapacity(max_current, dt_hour); - EXPECT_NEAR(cap->SOC(), 24.52, 1e-3); + EXPECT_NEAR(cap->SOC(), 48.0, 1e-3); // Check power model->updateVoltage(cap->q0(), cap->qmax(), max_current_calc, 293, dt_hour); EXPECT_NEAR(max_current_calc * model->battery_voltage(), power, 1e-2); diff --git a/test/shared_test/lib_battery_voltage_test.h b/test/shared_test/lib_battery_voltage_test.h index aa856ff3b..af506e69e 100644 --- a/test/shared_test/lib_battery_voltage_test.h +++ b/test/shared_test/lib_battery_voltage_test.h @@ -127,6 +127,8 @@ class voltage_table_lib_battery_voltage_test : public lib_battery_voltage_test 88.9, Vnom, 99, 0}); table = util::matrix_t(4, 2, &vals); + R = 0.02; + cap = std::unique_ptr(new capacity_lithium_ion_t(10, 50, 95, 5, dt_hr)); model = std::unique_ptr(new voltage_table_t(n_cells_series, n_strings, voltage_nom, table, R, dt_hr)); model->set_initial_SOC(50); @@ -137,6 +139,7 @@ class voltage_table_lib_battery_voltage_test : public lib_battery_voltage_test std::vector voltage_vals = { 0, 1.7, 4, 1.69, 5, 1.58, 60, 1.5, 85, 1.4, 90, 1.3, 93, 1.2, 95, 1, 96, 0.9 }; util::matrix_t voltage_table(9, 2, &voltage_vals); + R = 0.02; voltage_nom = 1.5; cap = std::unique_ptr(new capacity_lithium_ion_t(10, 50, 95, 5, dt_hr)); model = std::unique_ptr(new voltage_table_t(n_cells_series, n_strings, voltage_nom, voltage_table, R, @@ -174,6 +177,7 @@ class voltage_table_lib_battery_voltage_test : public lib_battery_voltage_test n_cells_series = 6; n_strings = 28; voltage_nom = 12; + R = 0.02; cap = std::unique_ptr(new capacity_lithium_ion_t(10, 50, 95, 5, dt_hr)); model = std::unique_ptr(new voltage_table_t(n_cells_series, n_strings, voltage_nom, voltage_table, R, diff --git a/test/shared_test/lib_resilience_test.cpp b/test/shared_test/lib_resilience_test.cpp index 9d72619cb..8531e2220 100644 --- a/test/shared_test/lib_resilience_test.cpp +++ b/test/shared_test/lib_resilience_test.cpp @@ -329,13 +329,13 @@ TEST_F(ResilienceTest_lib_resilience, VoltageTable) cap.updateCapacity(current, 1); volt.updateVoltage(cap.q0(), cap.qmax(), cap.I(), 0, 0.); EXPECT_NEAR(cap.SOC(), 44.445, 1e-3); - EXPECT_NEAR(volt.cell_voltage(), 1.773, 1e-3); + EXPECT_NEAR(volt.cell_voltage(), 1.873, 1e-3); current = -1; cap.updateCapacity(current, 1); volt.updateVoltage(cap.q0(), cap.qmax(), cap.I(), 0, 0.); EXPECT_NEAR(cap.SOC(), 88.889, 1e-3); - EXPECT_NEAR(volt.cell_voltage(), 2.777, 1e-3); + EXPECT_NEAR(volt.cell_voltage(), 2.877, 1e-3); } TEST_F(ResilienceTest_lib_resilience, DischargeVoltageTable){ @@ -359,7 +359,7 @@ TEST_F(ResilienceTest_lib_resilience, DischargeVoltageTable){ cap.updateCapacity(req_cur, 1); volt.updateVoltage(cap.q0(), cap.qmax(), cap.I(), 0, 1); double v = volt.cell_voltage(); - EXPECT_NEAR(req_cur * v, 0.5, 1e-2); + EXPECT_NEAR(req_cur * v, 0.48, 1e-2); // test max discharge cap = capacity_lithium_ion_t(2.25, 50, 100, 0, 1); @@ -397,7 +397,7 @@ TEST_F(ResilienceTest_lib_resilience, ChargeVoltageTable){ cap.updateCapacity(req_cur, 1); volt.updateVoltage(cap.q0(), cap.qmax(), cap.I(), 0, 1); double v = volt.cell_voltage(); - EXPECT_NEAR(req_cur * v, -1.5, 1e-2); + EXPECT_NEAR(req_cur * v, -1.58, 1e-2); // test max charge double max_p = volt.calculate_max_charge_w(cap.q0(), cap.qmax(), 0, ¤t);