diff --git a/CHANGELOG.md b/CHANGELOG.md index ea047ce89..ebcda882c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,15 +23,18 @@ Classify the change according to the following categories: ### Deprecated ### Removed - -## Develop - 2023-06-21 +## v0.32.4 ### Changed - Consolidated PVWatts API calls to 1 call (previously 3 separate calls existed). API call occurs in `src/core/utils.jl/call_pvwatts_api()`. This function is called for PV in `src/core/production_factor.jl/get_production_factor(PV)` and for GHP in `src/core/scenario.jl`. If GHP and PV are evaluated together, the GHP PVWatts call for ambient temperature is also used to assign the pv.production_factor_series in Scenario.jl so that the PVWatts API does not get called again downstream in `get_production_factor(PV)`. - In `src/core/utils.jl/call_pvwatts_api()`, updated NSRDB bounds used in PVWatts query (now includes southern New Zealand) - Updated PV Watts version from v6 to v8. PVWatts V8 updates the weather data to 2020 TMY data from the NREL NSRDB for locations covered by the database. (The NSRDB weather data used in PVWatts V6 is from around 2015.) See other differences at https://developer.nrel.gov/docs/solar/pvwatts/. - Made PV struct mutable: This allows for assigning pv.production_factor_series when calling PVWatts for GHP, to avoid a extra PVWatts calls later. +- Changed unit test expected values due to update to PVWatts v8, which slightly changed expected PV production factors. +- Changed **fuel_avail_gal** default to 1e9 for on-grid scenarios (same as off-grid) ### Fixed - Issue with using a leap year with a URDB rate - the URDB rate was creating energy_rate of length 8784 instead of intended 8760 +- Don't double add adjustments to urdb rates with non-standard units +- Corrected `Generator` **installed_cost_per_kw** from 500 to 650 if **only_runs_during_grid_outage** is _true_ or 800 if _false_ ## v0.32.3 ### Fixed diff --git a/Project.toml b/Project.toml index 72047da41..128da3e0a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "REopt" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" authors = ["Nick Laws", "Hallie Dunham ", "Bill Becker ", "Bhavesh Rathod ", "Alex Zolan ", "Amanda Farthing "] -version = "0.32.3" +version = "0.32.4" [deps] ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" diff --git a/src/core/generator.jl b/src/core/generator.jl index 31a9b7969..228febf06 100644 --- a/src/core/generator.jl +++ b/src/core/generator.jl @@ -30,19 +30,19 @@ """ `Generator` is an optional REopt input with the following keys and default values: ```julia + only_runs_during_grid_outage::Bool = true, existing_kw::Real = 0, min_kw::Real = 0, max_kw::Real = 1.0e6, - installed_cost_per_kw::Real = 500.0, + installed_cost_per_kw::Real = only_runs_during_grid_outage ? 650.0 : 800.0, om_cost_per_kw::Real = off_grid_flag ? 20.0 : 10.0, om_cost_per_kwh::Real = 0.0, fuel_cost_per_gallon::Real = 3.0, electric_efficiency_full_load::Real = 0.3233, electric_efficiency_half_load::Real = electric_efficiency_full_load, - fuel_avail_gal::Real = off_grid_flag ? 1.0e9 : 660.0, + fuel_avail_gal::Real = 1.0e9, fuel_higher_heating_value_kwh_per_gal::Real = 40.7, min_turn_down_fraction::Real = off_grid_flag ? 0.15 : 0.0, - only_runs_during_grid_outage::Bool = true, sells_energy_back_to_grid::Bool = false, can_net_meter::Bool = false, can_wholesale::Bool = false, @@ -125,19 +125,19 @@ struct Generator <: AbstractGenerator function Generator(; off_grid_flag::Bool = false, analysis_years::Int = 25, + only_runs_during_grid_outage::Bool = true, existing_kw::Real = 0, min_kw::Real = 0, max_kw::Real = 1.0e6, - installed_cost_per_kw::Real = 500.0, + installed_cost_per_kw::Real = only_runs_during_grid_outage ? 650.0 : 800.0, om_cost_per_kw::Real= off_grid_flag ? 20.0 : 10.0, om_cost_per_kwh::Real = 0.0, fuel_cost_per_gallon::Real = 3.0, electric_efficiency_full_load::Real = 0.3233, electric_efficiency_half_load::Real = electric_efficiency_full_load, - fuel_avail_gal::Real = off_grid_flag ? 1.0e9 : 660.0, + fuel_avail_gal::Real = 1.0e9, fuel_higher_heating_value_kwh_per_gal::Real = KWH_PER_GAL_DIESEL, min_turn_down_fraction::Real = off_grid_flag ? 0.15 : 0.0, - only_runs_during_grid_outage::Bool = true, sells_energy_back_to_grid::Bool = false, can_net_meter::Bool = false, can_wholesale::Bool = false, diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 80c204d8a..4c7c2a724 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -99,7 +99,7 @@ function URDBrate(urdb_response::Dict, year::Int; time_steps_per_hour=1) n_monthly_demand_tiers, monthly_demand_tier_limits, monthly_demand_rates, n_tou_demand_tiers, tou_demand_tier_limits, tou_demand_rates, tou_demand_ratchet_time_steps = parse_demand_rates(urdb_response, year, time_steps_per_hour=time_steps_per_hour) - + energy_rates, energy_tier_limits, n_energy_tiers, sell_rates = parse_urdb_energy_costs(urdb_response, year; time_steps_per_hour=time_steps_per_hour) @@ -293,12 +293,10 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM else tier_use = tier end - if non_kwh_units - rate = rate_average - else - rate = get(d["energyratestructure"][period][tier_use], "rate", 0) - end - total_rate = rate + get(d["energyratestructure"][period][tier_use], "adj", 0) + total_rate = non_kwh_units ? + rate_average : + (get(d["energyratestructure"][period][tier_use], "rate", 0) + + get(d["energyratestructure"][period][tier_use], "adj", 0)) sell = get(d["energyratestructure"][period][tier_use], "sell", 0) for step in range(1, stop=time_steps_per_hour) # repeat hourly rates intrahour diff --git a/src/mpc/structs.jl b/src/mpc/structs.jl index c2a66e087..86450478e 100644 --- a/src/mpc/structs.jl +++ b/src/mpc/structs.jl @@ -283,7 +283,7 @@ function MPCGenerator(; fuel_cost_per_gallon::Real = 3.0, electric_efficiency_full_load::Real = 0.3233, electric_efficiency_half_load::Real = electric_efficiency_full_load, - fuel_avail_gal::Real = 660.0, + fuel_avail_gal::Real = 1.0e9, fuel_higher_heating_value_kwh_per_gal::Real = KWH_PER_GAL_DIESEL, min_turn_down_fraction::Real = 0.0, # TODO change this to non-zero value only_runs_during_grid_outage::Bool = true, @@ -310,7 +310,7 @@ struct MPCGenerator <: AbstractGenerator fuel_cost_per_gallon::Real = 3.0, electric_efficiency_full_load::Real = 0.3233, electric_efficiency_half_load::Real = electric_efficiency_full_load, - fuel_avail_gal::Real = 660.0, + fuel_avail_gal::Real = 1.0e9, fuel_higher_heating_value_kwh_per_gal::Real = KWH_PER_GAL_DIESEL, min_turn_down_fraction::Real = 0.0, # TODO change this to non-zero value only_runs_during_grid_outage::Bool = true, diff --git a/test/runtests.jl b/test/runtests.jl index 9a3ae7983..ff2829d2c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,7 +78,7 @@ else # run HiGHS tests results = run_reopt(model, inputs) @test results["PV"]["size_kw"] ≈ 68.9323 atol=0.01 - @test results["Financial"]["lcc"] ≈ 432672.0 rtol=1e-5 # with levelization_factor hack the LCC is within 5e-5 of REopt API LCC + @test results["Financial"]["lcc"] ≈ 432681.26 rtol=1e-5 # with levelization_factor hack the LCC is within 5e-5 of REopt API LCC @test all(x == 0.0 for x in results["PV"]["electric_to_load_series_kw"][1:744]) end @@ -98,7 +98,7 @@ else # run HiGHS tests r = run_reopt(model, "./scenarios/pv_storage.json") @test r["PV"]["size_kw"] ≈ 216.6667 atol=0.01 - @test r["Financial"]["lcc"] ≈ 1.239151e7 rtol=1e-5 + @test r["Financial"]["lcc"] ≈ 1.2391786e7 rtol=1e-5 @test r["ElectricStorage"]["size_kw"] ≈ 49.0 atol=0.1 @test r["ElectricStorage"]["size_kwh"] ≈ 83.3 atol=0.1 end @@ -108,7 +108,7 @@ else # run HiGHS tests "output_flag" => false, "log_to_console" => false) ) results = run_reopt(model, "./scenarios/generator.json") - @test results["Generator"]["size_kw"] ≈ 9.53 atol=0.01 + @test results["Generator"]["size_kw"] ≈ 9.55 atol=0.01 @test (sum(results["Generator"]["electric_to_load_series_kw"][i] for i in 1:9) + sum(results["Generator"]["electric_to_load_series_kw"][i] for i in 13:8760)) == 0 p = REoptInputs("./scenarios/generator.json") diff --git a/test/scenarios/backup_reliability_reopt_inputs.json b/test/scenarios/backup_reliability_reopt_inputs.json index 8184026b6..2b1739d40 100644 --- a/test/scenarios/backup_reliability_reopt_inputs.json +++ b/test/scenarios/backup_reliability_reopt_inputs.json @@ -21,6 +21,8 @@ "federal_rebate_per_kw": 350.0 }, "Generator": { + "installed_cost_per_kw": 500.0, + "fuel_avail_gal": 660, "min_kw": 200, "max_kw": 200 }, diff --git a/test/scenarios/emissions.json b/test/scenarios/emissions.json index ff85084ce..8534728ca 100644 --- a/test/scenarios/emissions.json +++ b/test/scenarios/emissions.json @@ -41,6 +41,7 @@ "outage_durations": [10] }, "Generator": { + "installed_cost_per_kw": 500.0, "max_kw": 500.0, "fuel_avail_gal": 125.0, "min_turn_down_fraction": 0.0, diff --git a/test/scenarios/generator.json b/test/scenarios/generator.json index fe658e749..b6de9eddf 100644 --- a/test/scenarios/generator.json +++ b/test/scenarios/generator.json @@ -10,6 +10,7 @@ "outage_end_time_step": 12 }, "Generator": { + "installed_cost_per_kw": 500.0, "max_kw": 500.0, "fuel_avail_gal": 125.0, "min_turn_down_fraction": 0.0, diff --git a/test/scenarios/mpc.json b/test/scenarios/mpc.json index 2a69e5988..988a3d9b8 100644 --- a/test/scenarios/mpc.json +++ b/test/scenarios/mpc.json @@ -101,6 +101,7 @@ 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05] }, "Generator": { + "fuel_avail_gal": 660, "size_kw": 30, "only_runs_during_grid_outage": false }, diff --git a/test/scenarios/nogridcost_minresilhours.json b/test/scenarios/nogridcost_minresilhours.json index 34b2feb50..ba787ebaf 100644 --- a/test/scenarios/nogridcost_minresilhours.json +++ b/test/scenarios/nogridcost_minresilhours.json @@ -60,6 +60,8 @@ "critical_load_fraction": 1 }, "Generator": { + "fuel_avail_gal": 660, + "installed_cost_per_kw": 500.0 }, "Financial": { "value_of_lost_load_per_kwh": 0.001, diff --git a/test/scenarios/nogridcost_multiscenario.json b/test/scenarios/nogridcost_multiscenario.json index 90d312b05..01ec7ccad 100644 --- a/test/scenarios/nogridcost_multiscenario.json +++ b/test/scenarios/nogridcost_multiscenario.json @@ -53,6 +53,8 @@ "critical_load_fraction": 0.1 }, "Generator": { + "fuel_avail_gal": 660, + "installed_cost_per_kw": 500.0, "max_kw": 0.0 }, "Financial": { diff --git a/test/scenarios/outage.json b/test/scenarios/outage.json index 64adb56de..9e655c1bf 100644 --- a/test/scenarios/outage.json +++ b/test/scenarios/outage.json @@ -8,6 +8,8 @@ "outage_durations": [10] }, "Generator": { + "fuel_avail_gal": 660, + "installed_cost_per_kw": 500.0, "existing_kw": 0.0, "min_turn_down_fraction": 0.0, "only_runs_during_grid_outage": true, diff --git a/test/test_with_cplex.jl b/test/test_with_cplex.jl index c8d359e89..50ffbf813 100644 --- a/test/test_with_cplex.jl +++ b/test/test_with_cplex.jl @@ -98,7 +98,7 @@ end REoptInputs("./scenarios/monthly_rate.json"), ]; results = run_reopt(m, ps) - @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830591384e7 rtol=1e-5 + @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830872235e7 rtol=1e-5 end diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index e40fd3e71..7aaf7b9ad 100644 --- a/test/test_with_xpress.jl +++ b/test/test_with_xpress.jl @@ -429,7 +429,7 @@ end @test results["PV"]["size_kw"] ≈ 216.6667 atol=0.01 @test results["PV"]["lcoe_per_kwh"] ≈ 0.0468 atol = 0.001 - @test results["Financial"]["lcc"] ≈ 1.239151e7 rtol=1e-5 + @test results["Financial"]["lcc"] ≈ 1.239179e7 rtol=1e-5 @test results["Financial"]["lcc_bau"] ≈ 12766397 rtol=1e-5 @test results["ElectricStorage"]["size_kw"] ≈ 49.02 atol=0.1 @test results["ElectricStorage"]["size_kwh"] ≈ 83.3 atol=0.1 @@ -472,7 +472,7 @@ end m2 = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) p = REoptInputs("./scenarios/generator.json") results = run_reopt([m1,m2], p) - @test results["Generator"]["size_kw"] ≈ 9.53 atol=0.01 + @test results["Generator"]["size_kw"] ≈ 9.55 atol=0.01 @test (sum(results["Generator"]["electric_to_load_series_kw"][i] for i in 1:9) + sum(results["Generator"]["electric_to_load_series_kw"][i] for i in 13:8760)) == 0 @test results["ElectricLoad"]["bau_critical_load_met"] == false @@ -513,8 +513,8 @@ end # Scenario with generator, PV, electric storage m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) results = run_reopt(m, "./scenarios/outages_gen_pv_stor.json") - @test results["Outages"]["expected_outage_cost"] ≈ 3.5478948132891157e6 atol=10 - @test results["Financial"]["lcc"] ≈ 8.64478971865e7 atol=100 + @test results["Outages"]["expected_outage_cost"] ≈ 3.54476923e6 atol=10 + @test results["Financial"]["lcc"] ≈ 8.6413594727e7 atol=100 # Scenario with generator, PV, wind, electric storage m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) @@ -522,7 +522,7 @@ end @test value(m[:binMGTechUsed]["Generator"]) ≈ 1 @test value(m[:binMGTechUsed]["PV"]) ≈ 1 @test value(m[:binMGTechUsed]["Wind"]) ≈ 1 - @test results["Outages"]["expected_outage_cost"] ≈ 430157.43 atol=1.0 + @test results["Outages"]["expected_outage_cost"] ≈ 446899.75 atol=1.0 @test results["Financial"]["lcc"] ≈ 6.71661825335e7 rtol=0.001 end @@ -533,7 +533,7 @@ end REoptInputs("./scenarios/monthly_rate.json"), ]; results = run_reopt(m, ps) - @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830591384e7 rtol=1e-5 + @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830872235e7 rtol=1e-5 end @testset "MPC" begin @@ -707,9 +707,9 @@ end @test roof_east["size_kw"] ≈ 4 atol=0.1 @test ground_pv["lifecycle_om_cost_after_tax_bau"] ≈ 782.0 atol=0.1 @test roof_west["lifecycle_om_cost_after_tax_bau"] ≈ 782.0 atol=0.1 - @test ground_pv["annual_energy_produced_kwh_bau"] ≈ 8912.06 atol=0.1 + @test ground_pv["annual_energy_produced_kwh_bau"] ≈ 8933.09 atol=0.1 @test roof_west["annual_energy_produced_kwh_bau"] ≈ 7656.11 atol=0.1 - @test ground_pv["annual_energy_produced_kwh"] ≈ 26735.22 atol=0.1 + @test ground_pv["annual_energy_produced_kwh"] ≈ 26799.26 atol=0.1 @test roof_west["annual_energy_produced_kwh"] ≈ 10719.51 atol=0.1 @test roof_east["annual_energy_produced_kwh"] ≈ 6685.95 atol=0.1 end @@ -1313,9 +1313,9 @@ end @test (expected_npv - results["Financial"]["npv"])/expected_npv ≈ 0.0 atol=1e-2 @test results["Site"]["annual_renewable_electricity_kwh"] ≈ 76412.02 @test results["Site"]["renewable_electricity_fraction"] ≈ 0.8 - @test results["Site"]["renewable_electricity_fraction_bau"] ≈ 0.147698 atol=1e-4 + @test results["Site"]["renewable_electricity_fraction_bau"] ≈ 0.147576 atol=1e-4 @test results["Site"]["total_renewable_energy_fraction"] ≈ 0.8 - @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.147698 atol=1e-4 + @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.147576 atol=1e-4 @test results["Site"]["lifecycle_emissions_reduction_CO2_fraction"] ≈ 0.616639 atol=1e-4 @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 281.6 atol=1 @test results["Site"]["annual_emissions_tonnes_CO2"] ≈ 11.38 atol=1e-2 @@ -1323,15 +1323,15 @@ end @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2"] ≈ 7.04 @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2_bau"] ≈ 0.0 @test results["Financial"]["lifecycle_emissions_cost_climate"] ≈ 7767.6 atol=1 - @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 20447.72 atol=1e-1 + @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 20450.62 atol=1e-1 @test results["Site"]["lifecycle_emissions_tonnes_CO2"] ≈ 217.63 - @test results["Site"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 567.69 - @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2"] ≈ 140.75 + @test results["Site"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 567.77 + @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2"] ≈ 140.78 @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2_bau"] ≈ 0.0 @test results["ElectricUtility"]["annual_emissions_tonnes_CO2"] ≈ 4.34 @test results["ElectricUtility"]["annual_emissions_tonnes_CO2_bau"] ≈ 32.06 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 76.88 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 567.69 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 76.86 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 567.77 elseif i == 2 #commented out values are results using same levelization factor as API @test results["PV"]["size_kw"] ≈ 106.13 atol=1 @@ -1351,13 +1351,13 @@ end @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.132118 atol=1e-3 # 0.1354 atol=1e-3 # CO2 emissions - totals ≈ from grid, from fuelburn, ER, $/tCO2 breakeven @test results["Site"]["lifecycle_emissions_reduction_CO2_fraction"] ≈ 0.8 atol=1e-3 # 0.8 - @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 374.242 atol=1e-1 + @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 374.02125 atol=1e-1 @test results["Site"]["annual_emissions_tonnes_CO2"] ≈ 14.2 atol=1 @test results["Site"]["annual_emissions_tonnes_CO2_bau"] ≈ 70.99 atol=1 @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2"] ≈ 0.0 atol=1 # 0.0 @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2_bau"] ≈ 0.0 atol=1 # 0.0 @test results["Financial"]["lifecycle_emissions_cost_climate"] ≈ 9110.21 atol=1 - @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 45551.06 atol=1 + @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 45546.55 atol=1 @test results["Site"]["lifecycle_emissions_tonnes_CO2"] ≈ 252.92 atol=1 @test results["Site"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 1264.62 atol=1 @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2"] ≈ 0.0 atol=1 # 0.0 @@ -1674,4 +1674,4 @@ end @test "warnings" ∈ keys(r["Messages"]) @test length(r["Messages"]["errors"]) > 0 @test length(r["Messages"]["warnings"]) > 0 -end +end \ No newline at end of file