Skip to content

Commit

Permalink
Implement adjust losses within the capacity model. Not yet fully hook…
Browse files Browse the repository at this point in the history
…ed up to cmod battery
  • Loading branch information
brtietz committed Nov 6, 2024
1 parent d9120a4 commit efd4d87
Show file tree
Hide file tree
Showing 17 changed files with 232 additions and 70 deletions.
34 changes: 22 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,8 @@ double battery_t::calculate_loss(double power, size_t lifetimeIndex) {
}
}

double battery_t::getLoss() {
return losses->getLoss();
double battery_t::getAncillaryLoss() {
return losses->getAncillaryLoss();
}

battery_state battery_t::get_state() { return *state; }
Expand Down
18 changes: 12 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,7 @@ class battery_t {
double calculate_loss(double power, size_t lifetimeIndex);

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

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
2 changes: 1 addition & 1 deletion shared/lib_battery_dispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ void dispatch_t::runDispatch(size_t lifetimeIndex)

// Run Battery Model to update charge based on charge/discharge
m_batteryPower->powerBatteryDC = _Battery->run(lifetimeIndex, I);
m_batteryPower->powerSystemLoss = _Battery->getLoss();
m_batteryPower->powerSystemLoss = _Battery->getAncillaryLoss();

// Update power flow calculations, calculate AC power, and check the constraints
m_batteryPowerFlow->calculate();
Expand Down
12 changes: 6 additions & 6 deletions shared/lib_battery_dispatch_automatic_btm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,13 @@ void dispatch_automatic_behind_the_meter_t::update_dispatch(size_t year, size_t
{
// extract input power by modifying lifetime index to year 1
m_batteryPower->powerBatteryTarget = _P_battery_use[idx % (8760 * _steps_per_hour)];
double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, idx); // Battery is responsible for covering discharge losses
double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, idx); // Battery is responsible for covering discharge losses
if (m_batteryPower->connectionMode == AC_CONNECTED) {
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw);
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw);
}
else if (m_batteryPower->powerBatteryTarget > 0) {
// Adjust for DC discharge losses
m_batteryPower->powerBatteryTarget += loss_kw;
m_batteryPower->powerBatteryTarget += ancillary_loss_kw;
}
}

Expand Down Expand Up @@ -882,14 +882,14 @@ void dispatch_automatic_behind_the_meter_t::check_power_restrictions(double& pow

void dispatch_automatic_behind_the_meter_t::set_battery_power(size_t idx, size_t day_index, FILE *p, const bool debug)
{
double loss_kw = _Battery->calculate_loss(_P_battery_use[day_index], idx); // Units are kWac for AC connected batteries, and kWdc for DC connected
double ancillary_loss_kw = _Battery->calculate_loss(_P_battery_use[day_index], idx); // Units are kWac for AC connected batteries, and kWdc for DC connected

// At this point the target power is expressed in AC, must convert to DC for battery
if (m_batteryPower->connectionMode == m_batteryPower->AC_CONNECTED) {
_P_battery_use[day_index] = m_batteryPower->adjustForACEfficiencies(_P_battery_use[day_index], loss_kw);
_P_battery_use[day_index] = m_batteryPower->adjustForACEfficiencies(_P_battery_use[day_index], ancillary_loss_kw);
}
else {
_P_battery_use[day_index] = m_batteryPower->adjustForDCEfficiencies(_P_battery_use[day_index], loss_kw);
_P_battery_use[day_index] = m_batteryPower->adjustForDCEfficiencies(_P_battery_use[day_index], ancillary_loss_kw);
}

if (debug)
Expand Down
10 changes: 5 additions & 5 deletions shared/lib_battery_dispatch_automatic_fom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho

// Discharge if we are in a high-price period and have battery and inverter capacity
if (highDischargeValuePeriod && revenueToDischarge > 0 && excessAcCapacity && batteryHasDischargeCapacity && interconnectionHasCapacity) {
double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
if (m_batteryPower->connectionMode == BatteryPower::DC_CONNECTED) {
powerBattery = _inverter_paco + loss_kw - m_batteryPower->powerSystem;
powerBattery = _inverter_paco + ancillary_loss_kw - m_batteryPower->powerSystem;
}
else {
powerBattery = _inverter_paco; // AC connected battery is already maxed out by AC power limit, cannot increase dispatch to ccover losses
Expand All @@ -365,13 +365,13 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho
{
// extract input power by modifying lifetime index to year 1
m_batteryPower->powerBatteryTarget = _P_battery_use[lifetimeIndex % (8760 * _steps_per_hour)];
double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
if (m_batteryPower->connectionMode == AC_CONNECTED){
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw);
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw);
}
else if (m_batteryPower->powerBatteryTarget > 0) {
// Adjust for DC discharge losses
m_batteryPower->powerBatteryTarget += loss_kw;
m_batteryPower->powerBatteryTarget += ancillary_loss_kw;
}

}
Expand Down
6 changes: 3 additions & 3 deletions shared/lib_battery_dispatch_pvsmoothing_fom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,13 +361,13 @@ void dispatch_pvsmoothing_front_of_meter_t::update_dispatch(size_t year, size_t

// adjust for loss and efficiencies
m_batteryPower->powerBatteryTarget = (m_batt_dispatch_pvs_nameplate_ac > 0 ? m_batt_dispatch_pvs_nameplate_ac * m_batt_dispatch_pvs_battpower : m_batt_dispatch_pvs_battpower);
double loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
double ancillary_loss_kw = _Battery->calculate_loss(m_batteryPower->powerBatteryTarget, lifetimeIndex); // Battery is responsible for covering discharge losses
if (m_batteryPower->connectionMode == AC_CONNECTED) {
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, loss_kw);
m_batteryPower->powerBatteryTarget = m_batteryPower->adjustForACEfficiencies(m_batteryPower->powerBatteryTarget, ancillary_loss_kw);
}
else if (m_batteryPower->powerBatteryTarget > 0) {
// Adjust for DC discharge losses
m_batteryPower->powerBatteryTarget += loss_kw;
m_batteryPower->powerBatteryTarget += ancillary_loss_kw;
}

m_batteryPower->powerBatteryDC = m_batteryPower->powerBatteryTarget;
Expand Down
2 changes: 1 addition & 1 deletion shared/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ std::ostream &operator<<(std::ostream &os, const thermal_params &p) {

std::ostream &operator<<(std::ostream& os, const losses_state &p) {
char buf[256];
sprintf(buf, R"("losses_state": { "loss_percent": %.3f })", p.loss_kw);
sprintf(buf, R"("losses_state": { "loss_percent": %.3f })", p.ancillary_loss_kw);
os << buf;
return os;
}
Expand Down
Loading

0 comments on commit efd4d87

Please sign in to comment.