Skip to content

Commit

Permalink
Merge branch 'iph_fin_cmods' of https://github.com/nrel/ssc into iph_…
Browse files Browse the repository at this point in the history
…fin_cmods
  • Loading branch information
taylorbrown75 committed Nov 14, 2024
2 parents de25033 + 1a06aba commit dbbb015
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 54 deletions.
19 changes: 10 additions & 9 deletions shared/lib_battery_lifetime_calendar_cycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -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();
}

6 changes: 6 additions & 0 deletions shared/lib_battery_lifetime_calendar_cycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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:
Expand Down
10 changes: 7 additions & 3 deletions ssc/cmod_battery_eqns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ bool Reopt_size_standalone_battery_params(ssc_data_t data) {

// financial inputs
map_optional_input(vt, "itc_fed_percent", &reopt_batt, "total_itc_fraction", 0., true);
// TODO: what about reopt vars total_rebate_us_dollars_per_kw?
// TODO: what about reopt vars total_rebate_per_kw?

vd = vt->lookup("total_installed_cost");
if (vd) {
Expand Down Expand Up @@ -209,10 +209,12 @@ bool Reopt_size_standalone_battery_params(ssc_data_t data) {
map_optional_input(vt, "value_of_lost_load", &reopt_fin, "value_of_lost_load_per_kwh", 0);
reopt_fin.assign("microgrid_upgrade_cost_fraction", 0);



vd = vt->lookup("federal_tax_rate");
vd2 = vt->lookup("state_tax_rate");
if (vd && vd2) {
reopt_fin.assign("offtaker_tax_pct", vd->num[0] / 100. + vd2->num[0] / 100.);
reopt_fin.assign("offtaker_tax_rate_fraction", vd->num[0] / 100. + vd2->num[0] / 100.);
}

vt_get_number(vt, "inflation_rate", &val1);
Expand Down Expand Up @@ -242,6 +244,7 @@ bool Reopt_size_standalone_battery_params(ssc_data_t data) {
}
reopt_load.assign("loads_kw", var_data(&vec[0], sim_len));
reopt_load.assign("loads_kw_is_net", false);
reopt_load.assign("year", 2018); // recent common year starting on Monday

vd = vt->lookup("crit_load");
if (vd) {
Expand All @@ -259,10 +262,11 @@ bool Reopt_size_standalone_battery_params(ssc_data_t data) {
}
reopt_load.assign("critical_loads_kw", var_data(&vec[0], vec_size));
reopt_load.assign("year", 2018); // recent common year starting on Monday
reopt_load.assign("critical_loads_kw_is_net", false);
}

reopt_settings.assign_match_case("time_steps_per_hour", var_data((int)(sim_len / 8760)));
reopt_settings.assign("solver_name", var_data("SCIP")); // "HiGHS" option does not work with large numbers like 1e38 for tier max values per https://github.com/NREL/SAM/issues/1742
//reopt_settings.assign("solver_name", var_data("SCIP")); // "HiGHS" option does not work with large numbers like 1e38 for tier max values per https://github.com/NREL/SAM/issues/1742

// assign the reopt parameter table and log messages
reopt_table->assign_match_case("Settings", reopt_settings);
Expand Down
2 changes: 1 addition & 1 deletion ssc/cmod_pvsamv1_eqns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ SSCEXPORT bool Reopt_size_battery_params(ssc_data_t data) {
//map_input(vt, "system_capacity", &reopt_pv, "existing_kw");
map_input(vt, "system_capacity", &reopt_pv, "max_kw");
}
map_optional_input(vt, "degradation", &reopt_pv, "degradation_pct", 0.5, true);
map_optional_input(vt, "degradation", &reopt_pv, "degradation_fraction", 0.5, true);

map_optional_input(vt, "module_type", &reopt_pv, "module_type", 1);

Expand Down
2 changes: 1 addition & 1 deletion ssc/sscapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

SSCEXPORT int ssc_version()
{
return 292;
return 295;
}

SSCEXPORT const char *ssc_build_info()
Expand Down
3 changes: 1 addition & 2 deletions test/shared_test/lib_battery_capacity_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 23 additions & 23 deletions test/shared_test/lib_battery_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,27 +288,27 @@ 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};
s.last_idx = 32991;

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){
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 5 additions & 8 deletions test/shared_test/lib_battery_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -135,13 +134,12 @@ struct battery_state_test{
}
};

static void compareState(std::unique_ptr<battery_t>&model, const battery_state_test& expected_state, const std::string& msg){
static void compareState(std::unique_ptr<battery_t>&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;
Expand All @@ -159,8 +157,7 @@ static void compareState(std::unique_ptr<battery_t>&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
Expand Down
6 changes: 3 additions & 3 deletions test/ssc_test/cmod_battery_pvsamv1_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
}
}
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions test/ssc_test/cmod_battery_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit dbbb015

Please sign in to comment.