Skip to content

Commit

Permalink
Sam 1869 battery adjust losses (#1241)
Browse files Browse the repository at this point in the history
* Add a few required variables. Need to make a lot of decisions to use these

* Implement adjust losses within the capacity model. Not yet fully hooked up to cmod battery

* mid-stream updates for power losses

* First cut change of definition for powerflow AC losses to match new convention (PV only applies to PV when AC connected

* Add adjust losses to batt stateful, clean up cmod batt to fix seh exception (still doesn't relay losses properly

* Test updates for new capacity and availability loss conventions

* First cut at availability losses testing. Revealed that DC battery still has a problem with a giant resistor that doesn't exist

* Add automated and manual dispatch tests, add current restriction to availability losses

* Pass data through all compute modules to losses and powerflow code

* Add fallback code if batt_adjust is not defined

* Refactor max power and current to be functions instead of calculating in each individual location

* Fix typo in manual dispatch power vs current

* Run adjustment code based on which variables are actually available

* Improved error handling for battery adjust losses

* Pass analysis period to haf setup function

* Use correct nyears to account for system lifetime mode == 0

* Add output for total performance adjustment loss at a given timestep

* Make ssc variable labels consistent with UI forms, help, and the losses page

* bring cmod battery in line with name chage
  • Loading branch information
brtietz authored Nov 14, 2024
1 parent ecb119f commit da7f153
Show file tree
Hide file tree
Showing 33 changed files with 992 additions and 526 deletions.
38 changes: 26 additions & 12 deletions shared/lib_battery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ Define Losses
*/
void losses_t::initialize() {
state = std::make_shared<losses_state>();
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<double>(12, params->monthly_charge_loss[0]);
Expand Down Expand Up @@ -210,19 +210,21 @@ void losses_t::initialize() {
}
}

losses_t::losses_t(const std::vector<double>& monthly_charge, const std::vector<double>& monthly_discharge, const std::vector<double>& monthly_idle) {
losses_t::losses_t(const std::vector<double>& monthly_charge, const std::vector<double>& monthly_discharge, const std::vector<double>& monthly_idle, const std::vector<double>& adjust_losses) {
params = std::make_shared<losses_params>();
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<double>& schedule_loss) {
losses_t::losses_t(const std::vector<double>& schedule_loss, const std::vector<double>& adjust_losses) {
params = std::make_shared<losses_params>();
params->loss_choice = losses_params::SCHEDULE;
params->schedule_loss = schedule_loss;
params->adjust_loss = adjust_losses;
initialize();
}

Expand Down Expand Up @@ -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; }

Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -772,8 +782,12 @@ double battery_t::calculate_loss(double power, size_t lifetimeIndex) {
}
}

double battery_t::getLoss() {
return losses->getLoss();
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; }
Expand Down
21 changes: 15 additions & 6 deletions shared/lib_battery.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand All @@ -166,6 +167,7 @@ struct losses_params {
std::vector<double> monthly_discharge_loss;
std::vector<double> monthly_idle_loss;
std::vector<double> schedule_loss;
std::vector<double> adjust_loss;

friend std::ostream &operator<<(std::ostream &os, const losses_params &p);
};
Expand All @@ -181,17 +183,19 @@ 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<double>& monthly_charge, const std::vector<double>& monthly_discharge, const std::vector<double>& monthly_idle);
losses_t(const std::vector<double>& monthly_charge, const std::vector<double>& monthly_discharge, const std::vector<double>& monthly_idle, const std::vector<double>& adjust_losses);

/**
* \function 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<double>& schedule_loss = std::vector<double>(1, 0));
explicit losses_t(const std::vector<double>& schedule_loss = std::vector<double>(1, 0), const std::vector<double>& adjust_losses = std::vector<double>(1,0));

explicit losses_t(std::shared_ptr<losses_params> p);

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

Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -409,7 +415,10 @@ class battery_t {
double calculate_loss(double power, size_t lifetimeIndex);

// Get the losses at the current step
double getLoss();
double getAncillaryLoss();

// Get the adjust loss at the current timestep
double getAvailabilityLoss(size_t lifetimeIndex);

battery_state get_state();

Expand Down
48 changes: 42 additions & 6 deletions shared/lib_battery_capacity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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; }
8 changes: 8 additions & 0 deletions shared/lib_battery_capacity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
};
Expand Down
Loading

0 comments on commit da7f153

Please sign in to comment.