Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions shared/lib_battery_dispatch_automatic_fom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ dispatch_automatic_front_of_meter_t::dispatch_automatic_front_of_meter_t(

revenueToClipCharge = revenueToDischarge = revenueToGridCharge = revenueToPVCharge = 0;

discharge_hours = (size_t) std::ceil(_Battery->energy_max(m_batteryPower->stateOfChargeMax, m_batteryPower->stateOfChargeMin) / m_batteryPower->powerBatteryDischargeMaxDC);

costToCycle();
setup_cost_forecast_vector();
}
Expand All @@ -90,7 +92,9 @@ void dispatch_automatic_front_of_meter_t::init_with_pointer(const dispatch_autom
_forecast_hours = tmp->_forecast_hours;
_inverter_paco = tmp->_inverter_paco;
_forecast_price_rt_series = tmp->_forecast_price_rt_series;
ppa_prices = tmp->ppa_prices;

discharge_hours = tmp->discharge_hours;
m_etaPVCharge = tmp->m_etaPVCharge;
m_etaGridCharge = tmp->m_etaGridCharge;
m_etaDischarge = tmp->m_etaDischarge;
Expand All @@ -115,6 +119,12 @@ void dispatch_automatic_front_of_meter_t::setup_cost_forecast_vector()
ppa_price_series.push_back(_forecast_price_rt_series[i]);
}
_forecast_price_rt_series = ppa_price_series;

ppa_prices.reserve(_forecast_hours * _steps_per_hour);
if (discharge_hours >= _forecast_hours * _steps_per_hour) {
// -1 for 0 indexed arrays, additional -1 to ensure there is always a charging price lower than the discharing price if the forecast hours is = to battery capacity in hourrs
discharge_hours = _forecast_hours * _steps_per_hour - 2;
}
}

// deep copy from dispatch to this
Expand Down Expand Up @@ -164,8 +174,14 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho

// Compute forecast variables
size_t idx_lookahead = _forecast_hours * _steps_per_hour;

ppa_prices.clear();
std::copy(_forecast_price_rt_series.begin() + lifetimeIndex, _forecast_price_rt_series.begin() + lifetimeIndex + idx_lookahead, std::back_inserter(ppa_prices));
std::sort(ppa_prices.begin(), ppa_prices.end());
auto max_ppa_cost = std::max_element(_forecast_price_rt_series.begin() + lifetimeIndex, _forecast_price_rt_series.begin() + lifetimeIndex + idx_lookahead);
auto min_ppa_cost = std::min_element(_forecast_price_rt_series.begin() + lifetimeIndex, _forecast_price_rt_series.begin() + lifetimeIndex + idx_lookahead);
auto charge_ppa_cost = ppa_prices[discharge_hours];
auto discharge_ppa_cost = ppa_prices[ppa_prices.size() - discharge_hours];
double ppa_cost = _forecast_price_rt_series[lifetimeIndex];

/*! Cost to purchase electricity from the utility */
Expand Down Expand Up @@ -229,7 +245,7 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho
}

/*! Economic benefit of charging from clipped PV in current time step to discharge sometime in the next X hours (clipped PV is free) ($/kWh) */
revenueToClipCharge = *max_ppa_cost * m_etaDischarge - m_cycleCost;
revenueToClipCharge = _P_cliploss_dc[lifetimeIndex] > 0 ? *max_ppa_cost * m_etaDischarge - m_cycleCost : 0;

/*! Economic benefit of discharging in current time step ($/kWh) */
revenueToDischarge = ppa_cost * m_etaDischarge - m_cycleCost;
Expand All @@ -238,8 +254,8 @@ void dispatch_automatic_front_of_meter_t::update_dispatch(size_t year, size_t ho
double energyNeededToFillBattery = _Battery->energy_to_fill(m_batteryPower->stateOfChargeMax);

/* Booleans to assist decisions */
bool highDischargeValuePeriod = ppa_cost == *max_ppa_cost;
bool highChargeValuePeriod = ppa_cost == *min_ppa_cost;
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;

Expand Down
3 changes: 3 additions & 0 deletions shared/lib_battery_dispatch_automatic_fom.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ class dispatch_automatic_front_of_meter_t : public dispatch_automatic_t

/*! Market real time and forecast prices */
std::vector<double> _forecast_price_rt_series;
std::vector<double> ppa_prices; // Smaller vector for copying and sorting

size_t discharge_hours; // Battery size in hours

/*! Utility rate information */
std::shared_ptr<UtilityRateCalculator> m_utilityRateCalculator;
Expand Down
102 changes: 53 additions & 49 deletions test/shared_test/lib_battery_dispatch_automatic_fom_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,18 +255,18 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_DCAuto) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = {-9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0., // 0 - 5
-41690.48, -16690.50, -2334.45, -324.19, 0., 0., // 6 - 11
std::vector<double> targetkW = { 55396.69, 6901.80, 32515.30, -9767.19, -10052.39, -9202.19, // 0 - 5
-84205.39, -78854.6, -54551.36, -29551.38, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 77000, // 12 - 17
77000, 77000, 77000, 77000, 77000, 0. }; // 18 - 23
std::vector<double> dispatchedkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0., // 0 - 5
-29200.17, -16690.50, -2334.45, -324.19, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 28116.05, // 18
27946.64, 27664.23, 27099.14, 25401.83, 9343.36, 0. }; // 24
std::vector<double> SOC = {55.72, 61.58, 66.93, 71.12, 72.21, 72.21, // 6
88.87, 98.44, 99.78, 99.97, 99.97, 99.97, // 12
99.97, 99.97, 99.97, 99.97, 99.97, 83.30, // 12 - 17
66.63, 49.97, 33.30, 16.63, 10, 10 }; // 18 - 23
std::vector<double> dispatchedkW = { 27100.68, 6901.79, 24259.38, -9767.20, -10052.40, -9202.19, // 0 - 5
-28633.67, -28948.33, -29132.38, -29255.23, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 28090.71, // 18
27906.71, 27592.06, 26931.25, 24654.69, 5135.31, 0. }; // 24
std::vector<double> SOC = { 33.33, 29.11, 12.44, 18.59, 24.74, 30.29, // 6
46.96, 63.63, 80.29, 96.96, 96.96, 96.96, // 12
96.96, 96.96, 96.96, 96.96, 96.96, 80.30, // 12 - 17
63.63, 46.96, 30.30, 13.63, 10, 10 }; // 18 - 23
for (size_t h = 0; h < 24; h++) {
batteryPower->powerGeneratedBySystem = pv[h];
batteryPower->powerSystem = pv[h];
Expand Down Expand Up @@ -297,18 +297,18 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_DCAutoWithLosses) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0., // 0 - 5
-41690.48, -16690.50, -2334.45, -324.19, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 77005, // 12 - 17
std::vector<double> targetkW = { 55401.69, 6906.80, 32520.30, -9762.19, -10047.39, -9197.19, // 0 - 5
-84205.39, -78854.6, -54568.93, -29568.95, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 77005, // 12 - 17
77005, 77005, 77005, 77005, 77005, 0. }; // 18 - 23
std::vector<double> dispatchedkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0., // 0 - 5
-29200.17, -16690.50, -2334.45, -324.19, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 28116.05, // 18
27946.64, 27664.23, 27099.14, 25401.83, 9343.36, 0. }; // 24
std::vector<double> SOC = { 55.72, 61.58, 66.93, 71.12, 72.21, 72.21, // 6
88.87, 98.44, 99.78, 99.97, 99.97, 99.97, // 12
99.97, 99.97, 99.97, 99.97, 99.97, 83.30, // 12 - 17
66.63, 49.97, 33.30, 16.63, 10, 10 }; // 18 - 23
std::vector<double> dispatchedkW = { 27100.68, 6906.79, 24258.25, -9762.20, -10047.40, -9197.19, // 0 - 5
-28633.37, -28948.17, -29132.28, -29255.12, 0., 0., // 6 - 11
0., 0., 0., 0., 0., 28090.71, // 18
27906.54, 27591.76, 26930.53, 24651.13, 5118.83, 0 }; // 24
std::vector<double> SOC = { 33.33, 29.11, 12.44, 18.59, 24.74, 30.29, // 6
46.96, 63.63, 80.29, 96.96, 96.96, 96.96, // 12
96.96, 96.96, 96.96, 96.96, 96.96, 80.30, // 12 - 17
63.63, 46.96, 30.30, 13.63, 10, 10 }; // 18 - 23
for (size_t h = 0; h < 24; h++) {
batteryPower->powerGeneratedBySystem = pv[h];
batteryPower->powerSystem = pv[h];
Expand Down Expand Up @@ -338,8 +338,9 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_DCAutoSubhourly) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = {-9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.};
std::vector<double> SOC = {52.86, 55.80, 58.49, 60.60, 61.14, 61.14};
std::vector<double> targetkW = { 55396.69, 6901.80, 32515.30, -9767.19, -10052.39, -9202.19 };
std::vector<double> dispatchkW = { 27439.22, 6901.79, 26988.49, -9767.20, -10052.39, -9202.19 };
std::vector<double> SOC = {41.66, 39.59, 31.26, 34.18, 37.17, 39.90 };
for (size_t h = 0; h < 6; h++) {
size_t hour_of_year = hour_of_year_from_index(h, dtHour);
size_t step = step_from_index(h, dtHour);
Expand All @@ -352,7 +353,7 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_DCAutoSubhourly) {
EXPECT_NEAR(batteryPower->powerBatteryTarget, targetkW[h], 0.1) << "error in expected target at hour " << h;

dispatchAuto->dispatch(0, hour_of_year, step);
EXPECT_NEAR(batteryPower->powerBatteryDC, targetkW[h], 0.1) << "error in dispatched power at hour " << h;
EXPECT_NEAR(batteryPower->powerBatteryDC, dispatchkW[h], 0.1) << "error in dispatched power at hour " << h;
EXPECT_NEAR(dispatchAuto->battery_soc(), SOC[h], 0.1) << "error in SOC at hour " << h;
}
}
Expand Down Expand Up @@ -478,18 +479,19 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAuto) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = {-9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.,
-41690.48, -16690.50, -2334.45, -324.19, 0., 0.,
// Note on hour 3: the price here is both the 2nd highest price and the 2nd lowest price. A non-zero replacement cost could prevent cycling here.
std::vector<double> targetkW = { 77000, 77000, 77000, -7205.42, 77000, 0.,
-84205.39, -78854.60, -67702.19, -31516.09, 0., 0.,
0., 0., 0., 0., 0., 77000,
77000, 77000, 77000, 77000, 77000, 0. };
std::vector<double> dispatchedkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.,
-29200.17, -16690.50, -2334.45, -324.19, 0., 0.,
0., 0., 0., 0., 0., 28116.05,
27946.64, 27664.23, 27099.14, 25401.83, 9343.36, 0. };
std::vector<double> SOC = {55.72, 61.58, 66.93, 71.12, 72.21, 72.21,
88.87, 98.44, 99.78, 99.97, 99.97, 99.97,
99.97, 99.97, 99.97, 99.97, 99.97, 83.30,
66.63, 49.97, 33.30, 16.63, 10.0, 10.0 };
77000, 77000, 77000, 0., 0., 0. };
std::vector<double> dispatchedkW = { 27100.68, 25407.98, 9385.55, -7205.42, 6616.55, 0.,
-27719.16, -28532.97, -28894.66, -29099.09, 0., 0.,
0., 0., 0., 0., 0., 27852.99,
27491.37, 26677.62, 23151.35, 0., 0., 0. };
std::vector<double> SOC = { 33.3, 16.66, 10.0, 14.68, 10.0, 10.0,
26.66, 43.33, 60.0, 76.66, 76.66, 76.66,
76.66, 76.66, 76.66, 76.66, 76.66, 60.00,
43.33, 26.66, 10.0, 10.0, 10.0, 10.0 };
for (size_t h = 0; h < 24; h++) {
batteryPower->powerGeneratedBySystem = pv[h];
batteryPower->powerSystem = pv[h];
Expand Down Expand Up @@ -520,18 +522,19 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAutoWithLosses) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.,
-41690.48, -16690.50, -2334.45, -324.19, 0., 0.,
std::vector<double> targetkW = { 77000, 77000, 77000, -7205.42, 77000, 0.,
-84205.39, -78854.60, -67702.19, -31516.09, 0., 0.,
0., 0., 0., 0., 0., 77000,
77000, 77000, 77000, 77000, 77000, 0. };
std::vector<double> dispatchedkW = { -9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.,
-29200.17, -16690.50, -2334.45, -324.19, 0., 0.,
0., 0., 0., 0., 0., 28116.05,
27946.64, 27664.23, 27099.14, 25401.83, 9343.36, 0. }; // Battery was already discharging at max power, it stays unchanged
std::vector<double> SOC = { 55.72, 61.58, 66.93, 71.12, 72.21, 72.21,
88.87, 98.44, 99.78, 99.97, 99.97, 99.97,
99.97, 99.97, 99.97, 99.97, 99.97, 83.30,
66.63, 49.97, 33.30, 16.63, 10.0, 10.0 };
77000, 77000, 77000, 0., 0., 0. };
std::vector<double> dispatchedkW = { 27100.68, 25407.98, 9385.55, -7205.42, 6616.55, 0.,
-27719.16, -28532.97, -28894.66, -29099.09, 0., 0.,
0., 0., 0., 0., 0., 27852.99,
27491.37, 26677.62, 23151.35, 0., 0., 0. };
std::vector<double> SOC = { 33.3, 16.66, 10.0, 14.68, 10.0, 10.0,
26.66, 43.33, 60.0, 76.66, 76.66, 76.66,
76.66, 76.66, 76.66, 76.66, 76.66, 60.00,
43.33, 26.66, 10.0, 10.0, 10.0, 10.0 };
// Battery was already discharging at max power, it stays unchanged
for (size_t h = 0; h < 24; h++) {
batteryPower->powerGeneratedBySystem = pv[h];
batteryPower->powerSystem = pv[h];
Expand Down Expand Up @@ -562,8 +565,9 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAutoSubhourly) {
batteryPower->voltageSystem = 600;
batteryPower->setSharedInverter(m_sharedInverter);

std::vector<double> targetkW = {-9767.18, -10052.40, -9202.19, -7205.42, -1854.60, 0.};
std::vector<double> SOC = {52.86, 55.80, 58.49, 60.60, 61.14, 61.14};
std::vector<double> targetkW = { 77000, 77000, 77000, 77000, 77000, 0. };
std::vector<double> dispatchedkW = { 27439.22, 27100.68, 26536.45, 25407.98, 18604.42, 0. };
std::vector<double> SOC = { 41.66, 33.33, 25.0, 16.66, 10.0, 10.0};
for (size_t h = 0; h < 6; h++) {
size_t hour_of_year = hour_of_year_from_index(h, dtHour);
size_t step = step_from_index(h, dtHour);
Expand All @@ -576,7 +580,7 @@ TEST_F(AutoFOM_lib_battery_dispatch, DispatchFOM_ACAutoSubhourly) {
EXPECT_NEAR(batteryPower->powerBatteryTarget, targetkW[h], 0.1) << "error in expected target at hour " << h;

dispatchAuto->dispatch(0, hour_of_year, step);
EXPECT_NEAR(batteryPower->powerBatteryDC, targetkW[h], 0.1) << "error in dispatched power at hour " << h;
EXPECT_NEAR(batteryPower->powerBatteryDC, dispatchedkW[h], 0.1) << "error in dispatched power at hour " << h;
EXPECT_NEAR(dispatchAuto->battery_soc(), SOC[h], 0.1) << "error in SOC at hour " << h;

}
Expand Down
2 changes: 1 addition & 1 deletion test/ssc_test/save_as_JSON_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ TEST(save_as_JSON_test_run, pv_batt_mechant_plant_rapidjson) {
EXPECT_TRUE(success);
ssc_number_t npv;
ssc_data_get_number(data, "project_return_aftertax_npv", &npv);
EXPECT_NEAR(npv, -82401180, fabs(-82401180) / 1e6);
EXPECT_NEAR(npv, -82495656, fabs(-82401180) / 1e6);

ssc_module_free(mod_pv);
ssc_module_free(mod_grid);
Expand Down