diff --git a/shared/lib_battery_dispatch_automatic_fom.cpp b/shared/lib_battery_dispatch_automatic_fom.cpp index 907428eca..66a042470 100644 --- a/shared/lib_battery_dispatch_automatic_fom.cpp +++ b/shared/lib_battery_dispatch_automatic_fom.cpp @@ -135,8 +135,12 @@ void dispatch_automatic_front_of_meter_t::setup_cost_forecast_vector() 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; + // -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 hours + // exception caused if look_ahead_hours <= 1 and _steps_per_hour =1 ). specifically, size_t extremely large if set to negative integer - see SAM issue 1547 + if ((int)_forecast_hours * (int)_steps_per_hour - 2 < 0) // handles 1 hour look ahead but 0 hour look ahead still fails (should not be allowed) + discharge_hours = 0; + else + discharge_hours = _forecast_hours * _steps_per_hour - 2; } } diff --git a/ssc/cmod_battery.cpp b/ssc/cmod_battery.cpp index e2e334d3e..cae392db3 100644 --- a/ssc/cmod_battery.cpp +++ b/ssc/cmod_battery.cpp @@ -170,7 +170,7 @@ var_info vtab_battery_inputs[] = { { SSC_INPUT, SSC_NUMBER, "batt_dispatch_auto_btm_can_discharge_to_grid", "Behind the meter battery can discharge to grid?", "0/1", "", "BatteryDispatch", "", "", "" }, { SSC_INPUT, SSC_NUMBER, "batt_dispatch_charge_only_system_exceeds_load", "Battery can charge from system only when system output exceeds load", "0/1", "", "BatteryDispatch", "en_batt=1&en_standalone_batt=0&batt_meter_position=0", "", "" }, { SSC_INPUT, SSC_NUMBER, "batt_dispatch_discharge_only_load_exceeds_system","Battery can discharge battery only when load exceeds system output", "0/1", "", "BatteryDispatch", "en_batt=1&en_standalone_batt=0&batt_meter_position=0", "", "" }, - { SSC_INPUT, SSC_NUMBER, "batt_look_ahead_hours", "Hours to look ahead in automated dispatch", "hours", "", "BatteryDispatch", "", "", "" }, + { SSC_INPUT, SSC_NUMBER, "batt_look_ahead_hours", "Hours to look ahead in automated dispatch", "hours", "", "BatteryDispatch", "", "MIN=1", "" }, { SSC_INPUT, SSC_NUMBER, "batt_dispatch_update_frequency_hours", "Frequency to update the look-ahead dispatch", "hours", "", "BatteryDispatch", "", "", "" }, // PV smoothing specific inputs diff --git a/ssc/cmod_hcpv.cpp b/ssc/cmod_hcpv.cpp index 075e38123..6908cc943 100644 --- a/ssc/cmod_hcpv.cpp +++ b/ssc/cmod_hcpv.cpp @@ -403,7 +403,7 @@ class cm_hcpv : public compute_module adjustment_factors haf(this, "adjust"); if (!haf.setup()) - throw exec_error("pvwattsv5", "failed to setup adjustment factors: " + haf.error()); + throw exec_error("hcpv", "failed to setup adjustment factors: " + haf.error()); diff --git a/ssc/cmod_windpower.cpp b/ssc/cmod_windpower.cpp index edd3dffa7..3ca4b5038 100644 --- a/ssc/cmod_windpower.cpp +++ b/ssc/cmod_windpower.cpp @@ -313,7 +313,7 @@ void cm_windpower::exec() if (wpc.nTurbines > wpc.GetMaxTurbines()) throw exec_error("windpower", util::format("the wind model is only configured to handle up to %d turbines.", wpc.GetMaxTurbines())); - // create adjustment factors and losses + // create adjustment factors and losses - set them up initially here for the Weibull distribution method, rewrite them later with nrec for the time series method adjustment_factors haf(this, "adjust"); if (!haf.setup()) throw exec_error("windpower", "failed to setup adjustment factors: " + haf.error()); @@ -502,6 +502,10 @@ void cm_windpower::exec() if (steps_per_hour * 8760 != nstep && !contains_leap_day) throw exec_error("windpower", util::format("invalid number of data records (%d): must be an integer multiple of 8760", (int)nstep)); + // overwrite adjustment factors setup using the correct value for nrec, which we don't have until this part of the code + if (!haf.setup(nstep)) + throw exec_error("windpower", "failed to setup adjustment factors: " + haf.error()); + // allocate output data ssc_number_t *farmpwr = allocate("gen", nstep); ssc_number_t *wspd = allocate("wind_speed", nstep); diff --git a/ssc/common.cpp b/ssc/common.cpp index 42b78204f..87c1afb89 100644 --- a/ssc/common.cpp +++ b/ssc/common.cpp @@ -1093,13 +1093,17 @@ bool adjustment_factors::setup(int nsteps, int analysis_period) //nsteps is set m_factors[nsteps * a + i] *= (1.0 - p[0]/100.0); //input as factors not percentage } } - else if (n == (size_t)(nsteps * analysis_period)) { //Hourly or subhourly + else if (n == (size_t)(nsteps * analysis_period)) { //Hourly or subhourly- must match weather file resolution for (int a = 0; a < analysis_period; a++) { for (int i = 0; i < nsteps; i++) m_factors[nsteps * a + i] *= (1.0 - p[a*nsteps + i]/100.0); //convert from percentages to factors } } - else if (n % 12 == 0) { //Monthly + else if ((n % 8760 == 0) && n != (size_t)(nsteps * analysis_period)) // give a helpful error for timestep mismatch + { + m_error = util::format("Availability losses must be the same timestep as the weather file, if they are not daily/weekly/monthly."); + } + else if (n == (size_t)( 12 * analysis_period)) { //Monthly for (int a = 0; a < analysis_period; a++) { for (int i = 0; i < nsteps; i++) { month = util::month_of(int(i / steps_per_hour))-1; @@ -1108,7 +1112,7 @@ bool adjustment_factors::setup(int nsteps, int analysis_period) //nsteps is set } } - else if (n % 365 == 0) { //Daily + else if (n == (size_t)( 365 * analysis_period)) { //Daily for (int a = 0; a < analysis_period; a++) { for (int i = 0; i < nsteps; i++) { day = util::day_of_year(int(i / steps_per_hour)); @@ -1117,7 +1121,7 @@ bool adjustment_factors::setup(int nsteps, int analysis_period) //nsteps is set } } - else if (n % 52 == 0) { //Weekly + else if (n == (size_t)( 52 * analysis_period)) { //Weekly for (int a = 0; a < analysis_period; a++) { for (int i = 0; i < nsteps; i++) { week = util::week_of(int(i / steps_per_hour)); @@ -1133,7 +1137,7 @@ bool adjustment_factors::setup(int nsteps, int analysis_period) //nsteps is set } } else { - m_error = util::format("Error with lifetime loss data inputs"); + m_error = util::format("Error in length of lifetime availability losses."); } } } diff --git a/test/input_cases/pvsamv1_battery_common_data.h b/test/input_cases/pvsamv1_battery_common_data.h index c81e47f6d..5122988c8 100644 --- a/test/input_cases/pvsamv1_battery_common_data.h +++ b/test/input_cases/pvsamv1_battery_common_data.h @@ -415,7 +415,7 @@ void pvsamv1_battery_defaults(ssc_data_t& data) { ssc_data_set_number(data, "dc_adjust_constant", 0.0); ssc_data_set_number(data, "dc_adjust_en_periods", 1); ssc_data_set_matrix(data, "dc_adjust_periods", p_dc_adjust_periods, 1, 3); - ssc_data_set_number(data, "dc_adjust_en_timeindex", 1); + ssc_data_set_number(data, "dc_adjust_en_timeindex", 0); ssc_data_set_array(data, "dc_adjust_timeindex", p_dc_adjust_hourly, 8760); ssc_data_set_number(data, "batt_chem", 1); @@ -1167,7 +1167,7 @@ void commercial_multiarray_default(ssc_data_t& data) { ssc_data_set_number(data, "dc_adjust_constant", 0.0); ssc_data_set_number(data, "dc_adjust_en_periods", 1); ssc_data_set_matrix(data, "dc_adjust_periods", p_dc_adjust_periods, 1, 3); - ssc_data_set_number(data, "dc_adjust_en_timeindex", 1); + ssc_data_set_number(data, "dc_adjust_en_timeindex", 0); ssc_data_set_array(data, "dc_adjust_timeindex", p_dc_adjust_hourly, 8760); diff --git a/test/input_json/hybrids/PVWatts Wind Battery Hybrid_Single Owner.json b/test/input_json/hybrids/PVWatts Wind Battery Hybrid_Single Owner.json index f8b570a72..78f369a23 100644 --- a/test/input_json/hybrids/PVWatts Wind Battery Hybrid_Single Owner.json +++ b/test/input_json/hybrids/PVWatts Wind Battery Hybrid_Single Owner.json @@ -645,7 +645,7 @@ "batt_dispatch_pvs_timestep_multiplier": 3.0, "batt_dispatch_pvs_wf_forecast_choice": 0.0, "batt_dispatch_pvs_wf_timestep": 60.0, - "batt_dispatch_update_frequency_hours": 0.0, + "batt_dispatch_update_frequency_hours": 1.0, "batt_dispatch_wf_forecast_choice": 0.0, "batt_duration_choice": 0.0, "batt_gridcharge_percent_1": 100.0, @@ -706,7 +706,7 @@ 50.0 ] ], - "batt_look_ahead_hours": 0.0, + "batt_look_ahead_hours": 18.0, "batt_loss_choice": 0.0, "batt_losses": [ 0.0 diff --git a/test/ssc_test/cmod_pvsamv1_test.cpp b/test/ssc_test/cmod_pvsamv1_test.cpp index 6c92fa33d..2212f3832 100644 --- a/test/ssc_test/cmod_pvsamv1_test.cpp +++ b/test/ssc_test/cmod_pvsamv1_test.cpp @@ -422,10 +422,7 @@ TEST_F(CMPvsamv1PowerIntegration_cmod_pvsamv1, LossAdjustmentNonLifetime) { ssc_data_set_array(data, "adjust_timeindex", timeindex_subhourly, 17520); pvsam_errors = run_module(data, "pvsamv1"); - ssc_data_get_number(data, "annual_energy", &annual_energy); - EXPECT_NEAR(annual_energy, 8833.8, m_error_tolerance_hi); - ssc_data_get_number(data, "kwh_per_kw", &kwh_per_kw); - EXPECT_NEAR(kwh_per_kw, 1883, m_error_tolerance_hi) << "Energy yield"; // Same as 1 year because year 2 has 0 production + EXPECT_TRUE(pvsam_errors); //this should throw an error because we are not allowing losses at a more granular timestep than weather }