Skip to content

Commit

Permalink
Merge branch 'develop' into particle_gen3_csp
Browse files Browse the repository at this point in the history
  • Loading branch information
qualand committed Nov 5, 2024
2 parents 0cc79ea + 78a58ba commit 8b35767
Show file tree
Hide file tree
Showing 294 changed files with 19,892 additions and 19,001 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# SSC (SAM Simulation Core)
![Build](https://github.com/NREL/ssc/actions/workflows/ci.yml/badge.svg)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FNREL%2Fssc.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FNREL%2Fssc?ref=badge_shield)

The SSC Open Source Project repository contains the source code for the technology and financial models contained within the National Renewable Energy Laboratory's System Advisor Model™ (SAM™). For more details about SAM's capabilities, see the SAM website at [https://sam.nrel.gov/](https://sam.nrel.gov).

Expand Down
5 changes: 5 additions & 0 deletions shared/lib_battery_dispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ dispatch_automatic_t::dispatch_automatic_t(
bool can_clip_charge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
std::vector<double> battCycleCost,
Expand Down Expand Up @@ -684,6 +685,7 @@ dispatch_automatic_t::dispatch_automatic_t(
_safety_factor = 0.0;

m_batteryPower->canClipCharge = can_clip_charge;
m_batteryPower->canCurtailCharge = can_curtail_charge;
m_batteryPower->canSystemCharge = can_charge;
m_batteryPower->canGridCharge = can_grid_charge;
m_batteryPower->canFuelCellCharge = can_fuelcell_charge;
Expand Down Expand Up @@ -924,6 +926,7 @@ battery_metrics_t::battery_metrics_t(double dt_hour)
_average_efficiency = 100.;
_average_roundtrip_efficiency = 100.;
_pv_charge_percent = 0.;
_grid_charge_percent = 0.;

// annual metrics
_e_charge_from_pv_annual = 0.;
Expand All @@ -938,6 +941,7 @@ battery_metrics_t::battery_metrics_t(double dt_hour)
double battery_metrics_t::average_battery_conversion_efficiency() { return _average_efficiency; }
double battery_metrics_t::average_battery_roundtrip_efficiency() { return _average_roundtrip_efficiency; }
double battery_metrics_t::pv_charge_percent() { return _pv_charge_percent; }
double battery_metrics_t::grid_charge_percent() { return _grid_charge_percent; }
double battery_metrics_t::energy_pv_charge_annual() { return _e_charge_from_pv_annual; }
double battery_metrics_t::energy_grid_charge_annual() { return _e_charge_from_grid_annual; }
double battery_metrics_t::energy_charge_annual() { return _e_charge_annual; }
Expand Down Expand Up @@ -996,6 +1000,7 @@ void battery_metrics_t::accumulate_battery_charge_components(double P_tofrom_bat
_average_efficiency = 100. * (_e_discharge_accumulated / _e_charge_accumulated);
_average_roundtrip_efficiency = 100. * (_e_discharge_accumulated / (_e_charge_accumulated + _e_loss_system));
_pv_charge_percent = 100. * (_e_charge_from_pv / _e_charge_accumulated);
_grid_charge_percent = 100. * (_e_charge_from_grid / _e_charge_accumulated);
}
void battery_metrics_t::accumulate_grid_annual(double P_tofrom_grid)
{
Expand Down
3 changes: 3 additions & 0 deletions shared/lib_battery_dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ class dispatch_automatic_t : public dispatch_t
bool can_clipcharge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
std::vector<double> battCycleCost,
Expand Down Expand Up @@ -474,6 +475,7 @@ class battery_metrics_t
double average_battery_conversion_efficiency();
double average_battery_roundtrip_efficiency();
double pv_charge_percent();
double grid_charge_percent();

protected:

Expand All @@ -492,6 +494,7 @@ class battery_metrics_t

/*! This is the percentage of energy charge from the PV system [%] */
double _pv_charge_percent;
double _grid_charge_percent;

// annual metrics
double _e_charge_from_pv_annual; // [Kwh]
Expand Down
38 changes: 32 additions & 6 deletions shared/lib_battery_dispatch_automatic_btm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ dispatch_automatic_behind_the_meter_t::dispatch_automatic_behind_the_meter_t(
bool can_clip_charge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
rate_data* util_rate,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
Expand All @@ -73,7 +74,8 @@ dispatch_automatic_behind_the_meter_t::dispatch_automatic_behind_the_meter_t(
double SOC_min_outage,
int load_forecast_mode
) : dispatch_automatic_t(Battery, dt_hour, SOC_min, SOC_max, current_choice, Ic_max, Id_max, Pc_max_kwdc, Pd_max_kwdc, Pc_max_kwac, Pd_max_kwac,
t_min, dispatch_mode, weather_forecast_mode, pv_dispatch, nyears, look_ahead_hours, dispatch_update_frequency_hours, can_charge, can_clip_charge, can_grid_charge, can_fuelcell_charge,
t_min, dispatch_mode, weather_forecast_mode, pv_dispatch, nyears, look_ahead_hours, dispatch_update_frequency_hours,
can_charge, can_clip_charge, can_grid_charge, can_fuelcell_charge, can_curtail_charge,
battReplacementCostPerkWh, battCycleCostChoice, battCycleCost, battOMCost, interconnection_limit, chargeOnlySystemExceedLoad, dischargeOnlyLoadExceedSystem,
behindTheMeterDischargeToGrid, SOC_min_outage)
{
Expand Down Expand Up @@ -401,10 +403,10 @@ double dispatch_automatic_behind_the_meter_t::compute_costs(size_t idx, size_t y
year++;
}
for (size_t step = 0; step != _steps_per_hour && idx < _P_load_ac.size(); step++)
{
{
double power = _P_load_ac[idx] - _P_pv_ac[idx];
// One at a time so we can sort grid points by no-dispatch cost
std::vector<double> forecast_power = { -power }; // Correct sign convention for cost forecast
// One at a time so we can sort grid points by no-dispatch cost; TODO: consider curtailment limits - would need to pass in a forecast of these...
std::vector<double> forecast_power = { std::fmin(-1.0*power, m_batteryPower->powerInterconnectionLimit)}; // Correct sign convention for cost forecast
double step_cost = noDispatchForecast->forecastCost(forecast_power, year, (hour_of_year + hour) % 8760, step);
no_dispatch_cost += step_cost;

Expand Down Expand Up @@ -707,6 +709,30 @@ void dispatch_automatic_behind_the_meter_t::plan_dispatch_for_cost(dispatch_plan
}
}

// Iterate over sorted grid to prioritize curtail charging
i = 0;
if (m_batteryPower->canCurtailCharge || m_batteryPower->canSystemCharge) {
while (i < _num_steps) {
// Don't plan to charge if we were already planning to discharge. 0 is no plan, negative is clipped energy
index = sorted_grid[i].Hour() * _steps_per_hour + sorted_grid[i].Step();
if (plan.plannedDispatch[index] <= 0.0)
{
double requiredPower = 0.0;
if (sorted_grid[i].Grid() < 0) {
double powerLimit = std::fmin(m_batteryPower->powerInterconnectionLimit, m_batteryPower->powerCurtailmentLimit);
requiredPower = sorted_grid[i].Grid() + powerLimit;
requiredPower = std::fmin(0.0, requiredPower);
}
// Add to existing clipped energy
requiredPower += plan.plannedDispatch[index];
// Clipped energy was already counted once, so subtract that off incase requiredPower + clipped hit a current restriction
requiredEnergy += (requiredPower - plan.plannedDispatch[index]) * _dt_hour;
plan.plannedDispatch[index] = requiredPower;
}
i++;
}
}

// Iterating over sorted grid
std::stable_sort(sorted_grid.begin(), sorted_grid.end(), byLowestMarginalCost());
// Find m hours to get required energy - hope we got today's energy yesterday (for morning peaks). Apportion between hrs of lowest marginal cost
Expand Down Expand Up @@ -765,9 +791,9 @@ void dispatch_automatic_behind_the_meter_t::plan_dispatch_for_cost(dispatch_plan

// Clipped energy was already counted once, so subtract that off incase requiredPower + clipped hit a current restriction
requiredEnergy += (requiredPower - plan.plannedDispatch[index]) * _dt_hour;
}

plan.plannedDispatch[index] = requiredPower;
plan.plannedDispatch[index] = requiredPower;
}

}
i++;
Expand Down
1 change: 1 addition & 0 deletions shared/lib_battery_dispatch_automatic_btm.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class dispatch_automatic_behind_the_meter_t : public dispatch_automatic_t
bool can_clipcharge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
rate_data* util_rate,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
Expand Down
20 changes: 18 additions & 2 deletions shared/lib_battery_dispatch_automatic_fom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ dispatch_automatic_front_of_meter_t::dispatch_automatic_front_of_meter_t(
bool can_clip_charge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
double inverter_paco,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
Expand All @@ -71,7 +72,8 @@ dispatch_automatic_front_of_meter_t::dispatch_automatic_front_of_meter_t(
double etaGridCharge,
double etaDischarge,
double interconnection_limit) : dispatch_automatic_t(Battery, dt_hour, SOC_min, SOC_max, current_choice, Ic_max, Id_max, Pc_max_kwdc, Pd_max_kwdc, Pc_max_kwac, Pd_max_kwac,
t_min, dispatch_mode, weather_forecast_mode, pv_dispatch, nyears, look_ahead_hours, dispatch_update_frequency_hours, can_charge, can_clip_charge, can_grid_charge, can_fuelcell_charge,
t_min, dispatch_mode, weather_forecast_mode, pv_dispatch, nyears, look_ahead_hours, dispatch_update_frequency_hours,
can_charge, can_clip_charge, can_grid_charge, can_fuelcell_charge, can_curtail_charge,
battReplacementCostPerkWh, battCycleCostChoice, battCycleCost, battOMCost, interconnection_limit)
{
// if look behind, only allow 24 hours
Expand Down Expand Up @@ -272,18 +274,31 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho
/*! Energy need to charge the battery (kWh) */
double energyNeededToFillBattery = _Battery->energy_to_fill(m_batteryPower->stateOfChargeMax);

// Positive: spare power to discharge. Negative: system power will be curtailed. Negative number can be fed directly into powerBattery to support charging
double interconnectionCapacity = std::fmin(m_batteryPower->powerInterconnectionLimit, m_batteryPower->powerCurtailmentLimit) - m_batteryPower->powerSystem;

/* Booleans to assist decisions */
bool highDischargeValuePeriod = ppa_cost >= discharge_ppa_cost && ppa_cost >= charge_ppa_cost;
bool highChargeValuePeriod = ppa_cost <= charge_ppa_cost && ppa_cost <= discharge_ppa_cost;
bool excessAcCapacity = _inverter_paco > m_batteryPower->powerSystemThroughSharedInverter;
bool batteryHasDischargeCapacity = _Battery->SOC() >= m_batteryPower->stateOfChargeMin + 1.0;
bool interconnectionHasCapacity = interconnectionCapacity > 0.0;
bool canChargeFromCurtailedPower = interconnectionCapacity < 0.0 && (m_batteryPower->canCurtailCharge || m_batteryPower->canSystemCharge);

revenueToCurtailCharge = canChargeFromCurtailedPower ? *max_ppa_cost * m_etaDischarge - m_cycleCost - m_omCost : 0;

// Always Charge if PV is clipping
if (m_batteryPower->canClipCharge && m_batteryPower->powerSystemClipped > 0 && revenueToClipCharge >= 0)
{
powerBattery = -m_batteryPower->powerSystemClipped;
}

// Always charge if system power is curtailed
if (canChargeFromCurtailedPower && revenueToCurtailCharge >= 0) {
// powerBattery is DC. Convert from AC to DC units to maximize utilization
powerBattery = interconnectionCapacity * m_batteryPower->singlePointEfficiencyACToDC;
}

// Increase charge from system (PV) if it is more valuable later than selling now
if (m_batteryPower->canSystemCharge &&
revenueToPVCharge > 0 &&
Expand Down Expand Up @@ -332,14 +347,15 @@ 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) {
if (highDischargeValuePeriod && revenueToDischarge > 0 && excessAcCapacity && batteryHasDischargeCapacity && interconnectionHasCapacity) {
double 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;
}
else {
powerBattery = _inverter_paco; // AC connected battery is already maxed out by AC power limit, cannot increase dispatch to ccover losses
}
powerBattery = std::fmin(powerBattery, interconnectionCapacity);
}
// save for extraction
m_batteryPower->powerBatteryTarget = powerBattery;
Expand Down
2 changes: 2 additions & 0 deletions shared/lib_battery_dispatch_automatic_fom.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class dispatch_automatic_front_of_meter_t : public dispatch_automatic_t
bool can_clipcharge,
bool can_grid_charge,
bool can_fuelcell_charge,
bool can_curtail_charge,
double inverter_paco,
std::vector<double> battReplacementCostPerkWh,
int battCycleCostChoice,
Expand Down Expand Up @@ -142,6 +143,7 @@ class dispatch_automatic_front_of_meter_t : public dispatch_automatic_t
double revenueToGridCharge;
double revenueToClipCharge;
double revenueToDischarge;
double revenueToCurtailCharge;
};

#endif // __LIB_BATTERY_DISPATCH_AUTOMATIC_FOM_H__
20 changes: 15 additions & 5 deletions shared/lib_battery_dispatch_manual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ dispatch_manual_t::dispatch_manual_t(battery_t * Battery, double dt, double SOC_
double t_min, int mode, int battMeterPosition,
util::matrix_t<size_t> dm_dynamic_sched, util::matrix_t<size_t> dm_dynamic_sched_weekend,
std::vector<bool> dm_charge, std::vector<bool> dm_discharge, std::vector<bool> dm_gridcharge, std::vector<bool> dm_fuelcellcharge, std::vector<bool> dm_btm_to_grid,
std::map<size_t, double> dm_percent_discharge, std::map<size_t, double> dm_percent_gridcharge, bool can_clip_charge, double interconnection_limit,
std::map<size_t, double> dm_percent_discharge, std::map<size_t, double> dm_percent_gridcharge, bool can_clip_charge, bool can_curtail_charge, double interconnection_limit,
bool chargeOnlySystemExceedLoad, bool dischargeOnlyLoadExceedSystem, double SOC_min_outage, bool priorityChargeBattery)
: dispatch_t(Battery, dt, SOC_min, SOC_max, current_choice, Ic_max, Id_max, Pc_max_kwdc, Pd_max_kwdc, Pc_max_kwac, Pd_max_kwac,
t_min, mode, battMeterPosition, interconnection_limit, chargeOnlySystemExceedLoad, dischargeOnlyLoadExceedSystem, SOC_min_outage)
{
init_with_vects(dm_dynamic_sched, dm_dynamic_sched_weekend, dm_charge, dm_discharge, dm_gridcharge, dm_fuelcellcharge, dm_btm_to_grid, dm_percent_discharge, dm_percent_gridcharge, can_clip_charge, priorityChargeBattery);
init_with_vects(dm_dynamic_sched, dm_dynamic_sched_weekend, dm_charge, dm_discharge, dm_gridcharge, dm_fuelcellcharge, dm_btm_to_grid, dm_percent_discharge, dm_percent_gridcharge, can_clip_charge, can_curtail_charge, priorityChargeBattery);
}

void dispatch_manual_t::init_with_vects(
Expand All @@ -63,6 +63,7 @@ void dispatch_manual_t::init_with_vects(
std::map<size_t, double> dm_percent_discharge,
std::map<size_t, double> dm_percent_gridcharge,
bool can_clip_charge,
bool can_curtail_charge,
bool priorityChargeBattery)
{
_sched = dm_dynamic_sched;
Expand All @@ -75,6 +76,7 @@ void dispatch_manual_t::init_with_vects(
_percent_discharge_array = dm_percent_discharge;
_percent_charge_array = dm_percent_gridcharge;
_can_clip_charge = can_clip_charge;
_can_curtail_charge = can_curtail_charge;
_priority_charge_battery = priorityChargeBattery;
}

Expand All @@ -85,7 +87,7 @@ dispatch_t(dispatch)
const dispatch_manual_t * tmp = dynamic_cast<const dispatch_manual_t *>(&dispatch);
init_with_vects(tmp->_sched, tmp->_sched_weekend,
tmp->_charge_array, tmp->_discharge_array, tmp->_gridcharge_array, tmp->_fuelcellcharge_array, tmp->_discharge_grid_array,
tmp->_percent_discharge_array, tmp->_percent_charge_array, tmp->_can_clip_charge, tmp->_priority_charge_battery);
tmp->_percent_discharge_array, tmp->_percent_charge_array, tmp->_can_clip_charge, tmp->_can_curtail_charge, tmp->_priority_charge_battery);
}

// shallow copy from dispatch to this
Expand All @@ -95,7 +97,7 @@ void dispatch_manual_t::copy(const dispatch_t * dispatch)
const dispatch_manual_t * tmp = dynamic_cast<const dispatch_manual_t *>(dispatch);
init_with_vects(tmp->_sched, tmp->_sched_weekend,
tmp->_charge_array, tmp->_discharge_array, tmp->_gridcharge_array, tmp->_fuelcellcharge_array, tmp->_discharge_grid_array,
tmp->_percent_discharge_array, tmp->_percent_charge_array, tmp->_can_clip_charge, tmp->_priority_charge_battery);
tmp->_percent_discharge_array, tmp->_percent_charge_array, tmp->_can_clip_charge, tmp->_can_curtail_charge, tmp->_priority_charge_battery);
}

void dispatch_manual_t::prepareDispatch(size_t hour_of_year, size_t )
Expand All @@ -115,6 +117,7 @@ void dispatch_manual_t::prepareDispatch(size_t hour_of_year, size_t )
m_batteryPower->canDischarge = _discharge_array[iprofile - 1];
m_batteryPower->canGridCharge = _gridcharge_array[iprofile - 1];
m_batteryPower->canClipCharge = _can_clip_charge;
m_batteryPower->canCurtailCharge = _can_curtail_charge;

if (iprofile <= _fuelcellcharge_array.size()) {
m_batteryPower->canFuelCellCharge = _fuelcellcharge_array[iprofile - 1];
Expand All @@ -128,7 +131,7 @@ void dispatch_manual_t::prepareDispatch(size_t hour_of_year, size_t )
_percent_charge = 0.;

if (m_batteryPower->canDischarge){ _percent_discharge = _percent_discharge_array[iprofile]; }
if (m_batteryPower->canClipCharge || m_batteryPower->canSystemCharge || m_batteryPower->canFuelCellCharge){ _percent_charge = 100.; }
if (m_batteryPower->canCurtailCharge || m_batteryPower->canClipCharge || m_batteryPower->canSystemCharge || m_batteryPower->canFuelCellCharge){ _percent_charge = 100.; }
if (m_batteryPower->canGridCharge){ _percent_charge = _percent_charge_array[iprofile]; }
}
void dispatch_manual_t::dispatch(size_t year,
Expand Down Expand Up @@ -209,6 +212,13 @@ bool dispatch_manual_t::check_constraints(double &I, size_t count)
else
I -= (m_batteryPower->powerBatteryToGrid / std::abs(m_batteryPower->powerBatteryAC)) * std::abs(I);
}
// Back off discharge if we're violating interconnection limits
else if (m_batteryPower->powerInterconnectionLoss > 0 && m_batteryPower->powerBatteryAC > 0) {
I -= m_batteryPower->powerInterconnectionLoss / std::abs(m_batteryPower->powerBatteryAC) * std::abs(I);
if (I < 0) {
I = 0.0;
}
}
else
iterate = false;

Expand Down
3 changes: 3 additions & 0 deletions shared/lib_battery_dispatch_manual.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class dispatch_manual_t : public dispatch_t
std::map<size_t, double> dm_percent_discharge,
std::map<size_t, double> dm_percent_gridcharge,
bool can_clip_charge,
bool can_curtail_charge,
double interconnection_limit,
bool chargeOnlySystemExceedLoad = true,
bool dischargeOnlyLoadExceedSystem = true,
Expand Down Expand Up @@ -100,6 +101,7 @@ class dispatch_manual_t : public dispatch_t
std::map<size_t, double> dm_percent_discharge,
std::map<size_t, double> dm_percent_gridcharge,
bool can_clip_charge,
bool can_curtail_charge,
bool priorityChargeBattery);

void SOC_controller() override;
Expand All @@ -114,6 +116,7 @@ class dispatch_manual_t : public dispatch_t
std::vector<bool> _fuelcellcharge_array;
std::vector<bool> _discharge_grid_array;
bool _can_clip_charge;
bool _can_curtail_charge;
bool _priority_charge_battery;

double _percent_discharge;
Expand Down
Loading

0 comments on commit 8b35767

Please sign in to comment.