From 7e7c049e53f073e9a77b7eaa1f64097c3342b725 Mon Sep 17 00:00:00 2001 From: Brian Mirletz Date: Wed, 11 Dec 2024 07:58:05 -0700 Subject: [PATCH] Allow for zero time series buy and sell rates (#1264) * 2024.12.12.ssc.297 release candidate - no expiration * Switch from tier charge == 0 to explicit booleans for handling time series rate length errors --------- Co-authored-by: Steven Janzou --- ssc/cmod_utilityrate5.cpp | 8 +++- ssc/sscapi.cpp | 2 +- test/ssc_test/cmod_utilityrate5_test.cpp | 58 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/ssc/cmod_utilityrate5.cpp b/ssc/cmod_utilityrate5.cpp index 3e5cf3fcc..204b28282 100644 --- a/ssc/cmod_utilityrate5.cpp +++ b/ssc/cmod_utilityrate5.cpp @@ -2377,17 +2377,19 @@ class cm_utilityrate5 : public compute_module ssc_number_t tier_credit = 0.0, sr = 0.0, tier_energy = 0.0; // time step sell rates + bool use_ec_table_sell_rates = true; if (as_boolean("ur_en_ts_sell_rate")) { if (c < rate.m_ec_ts_sell_rate.size()) { tier_energy = energy_surplus; sr = rate.m_ec_ts_sell_rate[c]; tier_credit = tier_energy * sr * rate_esc; curr_month.ec_energy_surplus.at(row, surplus_tier) += (ssc_number_t)tier_energy; + use_ec_table_sell_rates = false; } } // Fall back to TOU rates if m_ec_ts_sell_rate.size() is too small - if (tier_credit == 0) { // So AFAICT this is to make sure we don't compute both time step and TOU. Maybe better as an else? + if (use_ec_table_sell_rates) { // So AFAICT this is to make sure we don't compute both time step and TOU. Maybe better as an else? if (cumulative_energy <= e_upper) { // If we are within the max usage for the tier, then it's a simple multiply tier_energy = energy_surplus; // of our usage with that rate and the escalator sr = curr_month.ec_tou_sr.at(row, surplus_tier); @@ -2468,6 +2470,7 @@ class cm_utilityrate5 : public compute_module cumulative_deficit = daily_deficit_energy; ssc_number_t tier_charge = 0.0, br = 0.0, tier_energy = 0.0; + bool use_ec_table_buy_rates = true; // time step sell rates if (as_boolean("ur_en_ts_buy_rate")) { if (c < rate.m_ec_ts_buy_rate.size()) { @@ -2477,11 +2480,12 @@ class cm_utilityrate5 : public compute_module charge_amt = tier_energy * br * rate_esc; curr_month.ec_energy_use.at(row, deficit_tier) += (ssc_number_t)tier_energy; curr_month.ec_charge.at(row, deficit_tier) += (ssc_number_t)charge_amt; + use_ec_table_buy_rates = false; } } // Fall back to TOU rates if m_ec_ts_buy_rate.size() is too small - if (tier_charge == 0) { + if (use_ec_table_buy_rates) { if (cumulative_deficit <= e_upper) { tier_energy = energy_deficit; br = curr_month.ec_tou_br.at(row, deficit_tier); diff --git a/ssc/sscapi.cpp b/ssc/sscapi.cpp index 2844d1cf6..983ce9225 100644 --- a/ssc/sscapi.cpp +++ b/ssc/sscapi.cpp @@ -51,7 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SSCEXPORT int ssc_version() { - return 296; + return 297; } SSCEXPORT const char *ssc_build_info() diff --git a/test/ssc_test/cmod_utilityrate5_test.cpp b/test/ssc_test/cmod_utilityrate5_test.cpp index 68daa51a1..e6dd43aa7 100644 --- a/test/ssc_test/cmod_utilityrate5_test.cpp +++ b/test/ssc_test/cmod_utilityrate5_test.cpp @@ -2273,3 +2273,61 @@ TEST(cmod_utilityrate5_eqns, Test_Commercial_Demand_Charges_exception) { int status = run_module(data, "utilityrate5"); EXPECT_TRUE(status); } + +TEST(cmod_utilityrate5_eqns, Test_Residential_TOU_Rates_buyall_sellall_ts_buy_rate) { + ssc_data_t data = new var_table; + + setup_residential_rates(data); + ssc_data_set_number(data, "ur_metering_option", 4); + // Need to specify sell rates for this configuration. These are higher than real life + ssc_number_t p_ur_ec_tou_mat[24] = { 1, 1, 9.9999999999999998e+37, 0, 0.10000000000000001, 0.10000000000000001, + 2, 1, 9.9999999999999998e+37, 0, 0.050000000000000003, 0.050000000000000003, + 3, 1, 9.9999999999999998e+37, 0, 0.20000000000000001, 0.20000000000000001, + 4, 1, 9.9999999999999998e+37, 0, 0.25, 0.25 }; + ssc_data_set_matrix(data, "ur_ec_tou_mat", p_ur_ec_tou_mat, 4, 6); + + ssc_data_set_number(data, "ur_en_ts_buy_rate", 1); + ssc_number_t p_ur_ts_buy_rate[8760]; + for (size_t i = 0; i < 8760; i++) { + p_ur_ts_buy_rate[i] = 0.0; + } + ssc_data_set_array(data, "ur_ts_buy_rate", p_ur_ts_buy_rate, 8760); + + ssc_data_set_number(data, "ur_en_ts_sell_rate", 1); + ssc_number_t p_ur_ts_sell_rate[8760]; + for (size_t i = 0; i < 8760; i++) { + p_ur_ts_sell_rate[i] = 0.0; + } + ssc_data_set_array(data, "ur_ts_sell_rate", p_ur_ts_sell_rate, 8760); + + int analysis_period = 1; + ssc_data_set_number(data, "system_use_lifetime_output", 1); + ssc_data_set_number(data, "analysis_period", analysis_period); + set_array(data, "load", load_profile_path, 8760); + set_array(data, "gen", gen_path, 8760); // 15 min data + + int status = run_module(data, "utilityrate5"); + EXPECT_FALSE(status); + + ensure_outputs_line_up(data); + + ssc_number_t cost_without_system; + ssc_data_get_number(data, "elec_cost_without_system_year1", &cost_without_system); + EXPECT_NEAR(0.0, cost_without_system, 0.01); + + ssc_number_t cost_with_system; + ssc_data_get_number(data, "elec_cost_with_system_year1", &cost_with_system); + EXPECT_NEAR(0.0, cost_with_system, 0.0); + + int nrows; + int ncols; + ssc_number_t* net_billing_credits = ssc_data_get_matrix(data, "two_meter_sales_ym", &nrows, &ncols); + util::matrix_t credits_matrix(nrows, ncols); + credits_matrix.assign(net_billing_credits, nrows, ncols); + + double dec_year_1_credits = credits_matrix.at((size_t)1, (size_t)11); + EXPECT_NEAR(0.0, dec_year_1_credits, 0.1); + + ssc_data_free(data); + +}