From ef343a9c0bf84e5d7760132e92c5880e7cdc92b6 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 13:05:55 -0600 Subject: [PATCH 01/16] update NSRDB bounds --- src/core/production_factor.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/production_factor.jl b/src/core/production_factor.jl index dbb2c8f18..fae874615 100644 --- a/src/core/production_factor.jl +++ b/src/core/production_factor.jl @@ -35,16 +35,17 @@ function get_production_factor(pv::PV, latitude::Real, longitude::Real; timefram return pv.production_factor_series end - # Check if site is beyond the bounds of the NRSDB dataset. If so, use the international dataset. + # Check if site is beyond the bounds of the NRSDB TMY dataset. If so, use the international dataset. dataset = "nsrdb" if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 - if longitude < 81.5 || longitude > 179.5 || latitude < -43.8 || latitude > 60.0 - if longitude < 67.0 || longitude > 81.5 || latitude < -43.8 || latitude > 38.0 + if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 + if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 dataset = "intl" end end end + ## TODO: Update to v8 here url = string("https://developer.nrel.gov/api/pvwatts/v6.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", pv.tilt, "&system_capacity=1", "&azimuth=", pv.azimuth, "&module_type=", pv.module_type, From 0be383aea1ecf4d31cccc263c85ddf7485dcec0f Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 13:38:48 -0600 Subject: [PATCH 02/16] add descriptions to PV inputs --- src/core/pv.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/pv.jl b/src/core/pv.jl index 358077b59..df6d69085 100644 --- a/src/core/pv.jl +++ b/src/core/pv.jl @@ -33,12 +33,12 @@ array_type::Int=1, # PV Watts array type (0: Ground Mount Fixed (Open Rack); 1: Rooftop, Fixed; 2: Ground Mount 1-Axis Tracking; 3 : 1-Axis Backtracking; 4: Ground Mount, 2-Axis Tracking) tilt::Real= array_type == 1 ? 10 : abs(latitude), # tilt = 10 deg for rooftop systems, abs(lat) for ground-mount module_type::Int=0, # PV module type (0: Standard; 1: Premium; 2: Thin Film) - losses::Real=0.14, + losses::Real=0.14, # System losses azimuth::Real = latitude≥0 ? 180 : 0, # set azimuth to zero for southern hemisphere - gcr::Real=0.4, - radius::Int=0, - name::String="PV", - location::String="both", + gcr::Real=0.4, # Ground coverage ratio + radius::Int=0, # Radius, in miles, to use when searching for the closest climate data station. Use zero to use the closest station regardless of the distance + name::String="PV", # for use with multiple pvs + location::String="both", # one of ["roof", "ground", "both"] existing_kw::Real=0, min_kw::Real=0, max_kw::Real=1.0e9, From 42b8ca5b398b22f29c9ef7b8dba67615fe94455a Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 13:53:03 -0600 Subject: [PATCH 03/16] update to PVWatts v8 --- CHANGELOG.md | 5 +++++ src/core/production_factor.jl | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4b2bed9..3726695b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,11 @@ Classify the change according to the following categories: ### Deprecated ### Removed + +## Develop - 2023-06-21 +### Changed +- In `src/core/production_factor.jl`, 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/. ## v0.32.3 ### Fixed - Calculate **num_battery_bins** default in `backup_reliability.jl` based on battery duration to prevent significant discretization error (and add test) diff --git a/src/core/production_factor.jl b/src/core/production_factor.jl index fae874615..beba394ff 100644 --- a/src/core/production_factor.jl +++ b/src/core/production_factor.jl @@ -45,8 +45,7 @@ function get_production_factor(pv::PV, latitude::Real, longitude::Real; timefram end end - ## TODO: Update to v8 here - url = string("https://developer.nrel.gov/api/pvwatts/v6.json", "?api_key=", nrel_developer_key, + url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", pv.tilt, "&system_capacity=1", "&azimuth=", pv.azimuth, "&module_type=", pv.module_type, "&array_type=", pv.array_type, "&losses=", round(pv.losses*100, digits=3), "&dc_ac_ratio=", pv.dc_ac_ratio, From 38527b98ba90680c71770aed784377fb9d35a177 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 14:39:06 -0600 Subject: [PATCH 04/16] Update runtests.jl PV size changed by 2% which aligns with change in PV output with update to v8 --- test/runtests.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 43040028f..447263af5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -77,8 +77,8 @@ else # run HiGHS tests inputs = REoptInputs(s) results = run_reopt(model, inputs) - @test results["PV"]["size_kw"] ≈ 70.3084 atol=0.01 - @test results["Financial"]["lcc"] ≈ 430747.0 rtol=1e-5 # with levelization_factor hack the LCC is within 5e-5 of REopt API LCC + @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 all(x == 0.0 for x in results["PV"]["electric_to_load_series_kw"][1:744]) end From 8e65999e95834ebc756e7c265013986a0b1cc062 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 14:54:59 -0600 Subject: [PATCH 05/16] update to v8 in utils.jl --- src/core/utils.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/utils.jl b/src/core/utils.jl index 067cd9852..f511c3f79 100644 --- a/src/core/utils.jl +++ b/src/core/utils.jl @@ -378,7 +378,7 @@ end function get_ambient_temperature(latitude::Real, longitude::Real; timeframe="hourly") - url = string("https://developer.nrel.gov/api/pvwatts/v6.json", "?api_key=", nrel_developer_key, + url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", latitude, "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, "&array_type=", 0, "&losses=", 14, @@ -405,7 +405,7 @@ end function get_pvwatts_prodfactor(latitude::Real, longitude::Real; timeframe="hourly") - url = string("https://developer.nrel.gov/api/pvwatts/v6.json", "?api_key=", nrel_developer_key, + url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", latitude, "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, "&array_type=", 0, "&losses=", 14, From 9a3a421d38de051a3e246addefc4fba059daa810 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 15:57:37 -0600 Subject: [PATCH 06/16] update other pvwatts calls and some tests --- CHANGELOG.md | 3 ++- src/core/scenario.jl | 12 ++++++++++-- src/core/utils.jl | 32 +++++++++++++++++++++++++------- test/runtests.jl | 6 +++--- test/test_with_cplex.jl | 6 +++--- test/test_with_xpress.jl | 12 ++++++------ 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3726695b1..2186d986b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ Classify the change according to the following categories: ## Develop - 2023-06-21 ### Changed - In `src/core/production_factor.jl`, 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/. +- 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/. +- Updated PVwatts calls in `src/core/utils.jl` (`get_ambient_temperature` and `get_pvwatts_prodfactor`) and `scenario.jl` (used for GHP) to use v8 and to determine dataset based on lat long. ## v0.32.3 ### Fixed - Calculate **num_battery_bins** default in `backup_reliability.jl` based on battery duration to prevent significant discretization error (and add test) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 07e95a6da..0f5202053 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -456,11 +456,19 @@ function Scenario(d::Dict; flex_hvac_from_json=false) # Call PVWatts for hourly dry-bulb outdoor air temperature ambient_temperature_f = [] if !haskey(d["GHP"]["ghpghx_inputs"][1], "ambient_temperature_f") || isempty(d["GHP"]["ghpghx_inputs"][1]["ambient_temperature_f"]) - url = string("https://developer.nrel.gov/api/pvwatts/v6.json", "?api_key=", nrel_developer_key, + dataset = "nsrdb" + if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 + if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 + if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 + dataset = "intl" + end + end + end + url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", d["Site"]["latitude"] , "&lon=", d["Site"]["longitude"], "&tilt=", d["Site"]["latitude"], "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, "&array_type=", 0, "&losses=", 0.14, "&dc_ac_ratio=", 1.1, - "&gcr=", 0.4, "&inv_eff=", 99, "&timeframe=", "hourly", "&dataset=nsrdb", + "&gcr=", 0.4, "&inv_eff=", 99, "&timeframe=", "hourly", "&dataset=", dataset, "&radius=", 100) try @info "Querying PVWatts for ambient temperature" diff --git a/src/core/utils.jl b/src/core/utils.jl index f511c3f79..ac3a2077d 100644 --- a/src/core/utils.jl +++ b/src/core/utils.jl @@ -377,12 +377,21 @@ function generate_year_profile_hourly(year::Int64, consecutive_periods::Abstract end -function get_ambient_temperature(latitude::Real, longitude::Real; timeframe="hourly") +function get_ambient_temperature(latitude::Real, longitude::Real) + dataset = "nsrdb" + if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 + if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 + if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 + dataset = "intl" + end + end + end url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", latitude, "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, "&array_type=", 0, "&losses=", 14, - "&timeframe=", timeframe, "&dataset=nsrdb" + "&timeframe=hourly", # can only get tamb when timeframe=houly according to PVWatts documentation + "&dataset=", dataset ) try @@ -404,12 +413,21 @@ function get_ambient_temperature(latitude::Real, longitude::Real; timeframe="hou end -function get_pvwatts_prodfactor(latitude::Real, longitude::Real; timeframe="hourly") +function get_pvwatts_prodfactor(latitude::Real, longitude::Real; timeframe="hourly", azimuth=180, module_type=0, array_type=1, tilt=latitude) + # Check if site is beyond the bounds of the NRSDB TMY dataset. If so, use the international dataset. + dataset = "nsrdb" + if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 + if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 + if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 + dataset = "intl" + end + end + end url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, - "&lat=", latitude , "&lon=", longitude, "&tilt=", latitude, - "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, - "&array_type=", 0, "&losses=", 14, - "&timeframe=", timeframe, "&dataset=nsrdb" + "&lat=", latitude , "&lon=", longitude, "&tilt=", tilt, + "&system_capacity=1", "&azimuth=", azimuth, "&module_type=", module_type, + "&array_type=", array_type, "&losses=", 14, + "&timeframe=", timeframe, "&dataset=", dataset ) try diff --git a/test/runtests.jl b/test/runtests.jl index 447263af5..fa66a39bc 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -98,9 +98,9 @@ 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.240037e7 rtol=1e-5 - @test r["ElectricStorage"]["size_kw"] ≈ 55.9 atol=0.1 - @test r["ElectricStorage"]["size_kwh"] ≈ 78.9 atol=0.1 + @test r["Financial"]["lcc"] ≈ 1.239151e7 rtol=1e-5 + @test r["ElectricStorage"]["size_kw"] ≈ 49.0 atol=0.1 + @test r["ElectricStorage"]["size_kwh"] ≈ 83.3 atol=0.1 end @testset "Outage with Generator" begin diff --git a/test/test_with_cplex.jl b/test/test_with_cplex.jl index fd3c2f658..ca7fee696 100644 --- a/test/test_with_cplex.jl +++ b/test/test_with_cplex.jl @@ -59,9 +59,9 @@ end results = run_reopt(model, "./scenarios/pv_storage.json") @test results["PV"]["size_kw"] ≈ 217 atol=1 - @test results["Financial"]["lcc"] ≈ 1.240037e7 rtol=1e-5 - @test results["ElectricStorage"]["size_kw"] ≈ 56 atol=1 - @test results["ElectricStorage"]["size_kwh"] ≈ 79 atol=1 + @test results["Financial"]["lcc"] ≈ 1.239151e7 rtol=1e-5 + @test results["ElectricStorage"]["size_kw"] ≈ 49 atol=1 + @test results["ElectricStorage"]["size_kwh"] ≈ 83 atol=1 end diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index 3189e795c..260c30ab5 100644 --- a/test/test_with_xpress.jl +++ b/test/test_with_xpress.jl @@ -428,11 +428,11 @@ end results = run_reopt([m1,m2], d) @test results["PV"]["size_kw"] ≈ 216.6667 atol=0.01 - @test results["PV"]["lcoe_per_kwh"] ≈ 0.0483 atol = 0.001 - @test results["Financial"]["lcc"] ≈ 1.240037e7 rtol=1e-5 + @test results["PV"]["lcoe_per_kwh"] ≈ 0.0468 atol = 0.001 + @test results["Financial"]["lcc"] ≈ 1.239151e7rtol=1e-5 @test results["Financial"]["lcc_bau"] ≈ 12766397 rtol=1e-5 - @test results["ElectricStorage"]["size_kw"] ≈ 55.9 atol=0.1 - @test results["ElectricStorage"]["size_kwh"] ≈ 78.9 atol=0.1 + @test results["ElectricStorage"]["size_kw"] ≈ 49.02 atol=0.1 + @test results["ElectricStorage"]["size_kwh"] ≈ 83.3 atol=0.1 proforma_npv = REopt.npv(results["Financial"]["offtaker_annual_free_cashflows"] - results["Financial"]["offtaker_annual_free_cashflows_bau"], 0.081) @test results["Financial"]["npv"] ≈ proforma_npv rtol=0.0001 @@ -456,8 +456,8 @@ end # @test r["ElectricStorage"]["maintenance_cost"] ≈ 2972.66 atol=0.01 # the maintenance_cost comes out to 3004.39 on Actions, so we test the LCC since it should match @test r["Financial"]["lcc"] ≈ 1.240096e7 rtol=0.01 - @test last(value.(m[:SOH])) ≈ 63.129 rtol=0.01 - @test r["ElectricStorage"]["size_kwh"] ≈ 78.91 rtol=0.01 + @test last(value.(m[:SOH])) ≈ 66.633 rtol=0.01 + @test r["ElectricStorage"]["size_kwh"] ≈ 83.29 rtol=0.01 # test minimum_avg_soc_fraction d["ElectricStorage"]["minimum_avg_soc_fraction"] = 0.72 From cd90bbfc23a0b92e30747c509f9f549947618616 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 21 Jun 2023 17:09:02 -0600 Subject: [PATCH 07/16] update tests --- test/runtests.jl | 2 +- test/test_with_cplex.jl | 2 +- test/test_with_xpress.jl | 92 ++++++++++++++++++++-------------------- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index fa66a39bc..26d132d98 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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"] ≈ 8.13 atol=0.01 + @test results["Generator"]["size_kw"] ≈ 9.53 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/test_with_cplex.jl b/test/test_with_cplex.jl index ca7fee696..f48b5b7fa 100644 --- a/test/test_with_cplex.jl +++ b/test/test_with_cplex.jl @@ -74,7 +74,7 @@ end @test value(m[:binMGTechUsed]["Generator"]) == 1 @test value(m[:binMGTechUsed]["PV"]) == 1 @test value(m[:binMGStorageUsed]) == 1 - @test results["Financial"]["lcc"] ≈ 7.19753998668e7 atol=5e4 + @test results["Financial"]["lcc"] ≈ 6.82164056207e7 atol=5e4 #= Scenario with $0/kWh value_of_lost_load_per_kwh, 12x169 hour outages, 1kW load/hour, and min_resil_time_steps = 168 diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index 260c30ab5..db4f54587 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.239151e7rtol=1e-5 + @test results["Financial"]["lcc"] ≈ 1.239151e7 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"] ≈ 8.13 atol=0.01 + @test results["Generator"]["size_kw"] ≈ 9.53 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 @@ -493,7 +493,7 @@ end @test value(m[:binMGTechUsed]["CHP"]) ≈ 1 @test value(m[:binMGTechUsed]["PV"]) ≈ 1 @test value(m[:binMGStorageUsed]) ≈ 1 - @test results["Financial"]["lcc"] ≈ 7.0176719775e7 atol=5e4 + @test results["Financial"]["lcc"] ≈ 6.82164056207e7 atol=5e4 #= Scenario with $0.001/kWh value_of_lost_load_per_kwh, 12x169 hour outages, 1kW load/hour, and min_resil_time_steps = 168 @@ -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"] ≈ 4.800393567995261e6 atol=10 - @test results["Financial"]["lcc"] ≈ 8.9857671584e7 atol=100 + @test results["Outages"]["expected_outage_cost"] ≈ 3.5478948132891157e6 atol=10 + @test results["Financial"]["lcc"] ≈ 8.64478971865e7 atol=100 # Scenario with generator, PV, wind, electric storage m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) @@ -522,8 +522,8 @@ 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"] ≈ 50147.6 atol=1.0 - @test results["Financial"]["lcc"] ≈ 6.84048993e7 rtol=0.001 + @test results["Outages"]["expected_outage_cost"] ≈ 430157.43 atol=1.0 + @test results["Financial"]["lcc"] ≈ 6.71661825335e7 rtol=0.001 end @testset "Multiple Sites" begin @@ -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.240037e7 + 437169.0 rtol=1e-5 + @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830591384e7 rtol=1e-5 end @testset "MPC" begin @@ -708,10 +708,10 @@ end @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"] ≈ 8844.19 atol=0.1 - @test roof_west["annual_energy_produced_kwh_bau"] ≈ 7440.1 atol=0.1 - @test ground_pv["annual_energy_produced_kwh"] ≈ 26533.54 atol=0.1 - @test roof_west["annual_energy_produced_kwh"] ≈ 10416.52 atol=0.1 - @test roof_east["annual_energy_produced_kwh"] ≈ 6482.37 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 roof_west["annual_energy_produced_kwh"] ≈ 10719.51 atol=0.1 + @test roof_east["annual_energy_produced_kwh"] ≈ 6685.95 atol=0.1 end @testset "Thermal Energy Storage + Absorption Chiller" begin @@ -1305,67 +1305,67 @@ end end if i == 1 - @test results["PV"]["size_kw"] ≈ 61.16 atol=1e-1 + @test results["PV"]["size_kw"] ≈ 60.12 atol=1e-1 @test results["ElectricStorage"]["size_kw"] ≈ 0.0 atol=1e-1 @test results["ElectricStorage"]["size_kwh"] ≈ 0.0 atol=1e-1 @test results["Generator"]["size_kw"] ≈ 21.52 atol=1e-1 - expected_npv = -70908 + expected_npv = -70009 @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.14495 atol=1e-4 + @test results["Site"]["renewable_electricity_fraction_bau"] ≈ 0.147698 atol=1e-4 @test results["Site"]["total_renewable_energy_fraction"] ≈ 0.8 - @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.14495 atol=1e-4 - @test results["Site"]["lifecycle_emissions_reduction_CO2_fraction"] ≈ 0.61865 atol=1e-4 - @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 283.5 atol=1 - @test results["Site"]["annual_emissions_tonnes_CO2"] ≈ 11.36 atol=1e-2 - @test results["Site"]["annual_emissions_tonnes_CO2_bau"] ≈ 32.16 atol=1e-2 - @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2"] ≈ 6.96 + @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.147698 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 + @test results["Site"]["annual_emissions_tonnes_CO2_bau"] ≈ 32.06 atol=1e-2 + @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"] ≈ 7752.46 atol=1 - @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 20514.15 atol=1e-1 - @test results["Site"]["lifecycle_emissions_tonnes_CO2"] ≈ 217.19 - @test results["Site"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 569.53 - @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2"] ≈ 139.18 + @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["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_from_fuelburn_tonnes_CO2_bau"] ≈ 0.0 - @test results["ElectricUtility"]["annual_emissions_tonnes_CO2"] ≈ 4.41 + @test results["ElectricUtility"]["annual_emissions_tonnes_CO2"] ≈ 4.34 @test results["ElectricUtility"]["annual_emissions_tonnes_CO2_bau"] ≈ 32.16 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 78.01 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 569.53 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 76.88 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 567.69 elseif i == 2 #commented out values are results using same levelization factor as API - @test results["PV"]["size_kw"] ≈ 97.52 atol=1 - @test results["ElectricStorage"]["size_kw"] ≈ 20.27 atol=1 # 20.29 - @test results["ElectricStorage"]["size_kwh"] ≈ 159.05 atol=1 + @test results["PV"]["size_kw"] ≈ 106.13 atol=1 + @test results["ElectricStorage"]["size_kw"] ≈ 21.58 atol=1 # 20.29 + @test results["ElectricStorage"]["size_kwh"] ≈ 165.27 atol=1 @test !haskey(results, "Generator") # NPV @info results["Financial"]["npv"] - expected_npv = -249474.49 + expected_npv = -267404.54 @test (expected_npv - results["Financial"]["npv"])/expected_npv ≈ 0.0 atol=1e-3 # Renewable energy - @test results["Site"]["renewable_electricity_fraction"] ≈ 0.786193 atol=1e-3 - @test results["Site"]["annual_renewable_electricity_kwh"] ≈ 78619.3 atol=10 - @test results["Site"]["renewable_electricity_fraction_bau"] ≈ 0.1365 atol=1e-3 #0.1354 atol=1e-3 - @test results["Site"]["annual_renewable_electricity_kwh_bau"] ≈ 13650.39 atol=10 # 13542.62 atol=10 - @test results["Site"]["total_renewable_energy_fraction"] ≈ 0.786193 atol=1e-3 - @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.1365 atol=1e-3 # 0.1354 atol=1e-3 + @test results["Site"]["renewable_electricity_fraction"] ≈ 0.783298 atol=1e-3 + @test results["Site"]["annual_renewable_electricity_kwh"] ≈ 78329.85 atol=10 + @test results["Site"]["renewable_electricity_fraction_bau"] ≈ 0.132118 atol=1e-3 #0.1354 atol=1e-3 + @test results["Site"]["annual_renewable_electricity_kwh_bau"] ≈ 13211.78 atol=10 # 13542.62 atol=10 + @test results["Site"]["total_renewable_energy_fraction"] ≈ 0.783298 atol=1e-3 + @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"] ≈ 351.24 atol=1e-1 + @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 374.242 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"] ≈ 9056.43 atol=1 - @test results["Financial"]["lifecycle_emissions_cost_climate_bau"] ≈ 45282.17 atol=1 - @test results["Site"]["lifecycle_emissions_tonnes_CO2"] ≈ 251.43 atol=1 - @test results["Site"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 1257.16 atol=1 + @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["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 @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2_bau"] ≈ 0.0 atol=1 # 0.0 @test results["ElectricUtility"]["annual_emissions_tonnes_CO2"] ≈ 14.2 atol=1 @test results["ElectricUtility"]["annual_emissions_tonnes_CO2_bau"] ≈ 70.99 atol=1 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 251.43 atol=1 - @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 1257.16 atol=1 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2"] ≈ 252.92 atol=1 + @test results["ElectricUtility"]["lifecycle_emissions_tonnes_CO2_bau"] ≈ 1264.62 atol=1 #also test CO2 breakeven cost inputs["PV"]["min_kw"] = results["PV"]["size_kw"] - inputs["PV"]["existing_kw"] From 825b27767b6866ec07b3f01b0a3a5260175f02fb Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 22 Jun 2023 08:38:29 -0600 Subject: [PATCH 08/16] Update Tests --- test/runtests.jl | 2 +- test/test_with_cplex.jl | 2 +- test/test_with_xpress.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 26d132d98..9a3ae7983 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -464,7 +464,7 @@ else # run HiGHS tests @test reliability_results["unlimited_fuel_cumulative_survival_final_time_step"][1] ≈ 0.802997 atol=0.0001 @test reliability_results["cumulative_survival_final_time_step"][1] ≈ 0.802997 atol=0.0001 - @test reliability_results["mean_cumulative_survival_final_time_step"] ≈ 0.817088 atol=0.0001 + @test reliability_results["mean_cumulative_survival_final_time_step"] ≈ 0.817586 atol=0.0001 end # removed Wind test for two reasons diff --git a/test/test_with_cplex.jl b/test/test_with_cplex.jl index f48b5b7fa..c8d359e89 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.23887e7 + 437169.0 rtol=1e-5 + @test results[3]["Financial"]["lcc"] + results[10]["Financial"]["lcc"] ≈ 1.2830591384e7 rtol=1e-5 end diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index db4f54587..68a322101 100644 --- a/test/test_with_xpress.jl +++ b/test/test_with_xpress.jl @@ -707,7 +707,7 @@ 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"] ≈ 8844.19 atol=0.1 + @test ground_pv["annual_energy_produced_kwh_bau"] ≈ 8912.06 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 roof_west["annual_energy_produced_kwh"] ≈ 10719.51 atol=0.1 @@ -1329,7 +1329,7 @@ end @test results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_CO2"] ≈ 140.75 @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.16 + @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 elseif i == 2 From 01860edb6da7728c4cc78db6574e645a2a8a931b Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Sun, 2 Jul 2023 15:24:20 -0600 Subject: [PATCH 09/16] Update single PVWatts API function For both production factor (PV) and ambient air temperature data (GHP) --- src/core/utils.jl | 69 +++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/src/core/utils.jl b/src/core/utils.jl index ac3a2077d..705f69dac 100644 --- a/src/core/utils.jl +++ b/src/core/utils.jl @@ -377,43 +377,15 @@ function generate_year_profile_hourly(year::Int64, consecutive_periods::Abstract end -function get_ambient_temperature(latitude::Real, longitude::Real) - dataset = "nsrdb" - if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 - if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 - if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 - dataset = "intl" - end - end - end - url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, - "&lat=", latitude , "&lon=", longitude, "&tilt=", latitude, - "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, - "&array_type=", 0, "&losses=", 14, - "&timeframe=hourly", # can only get tamb when timeframe=houly according to PVWatts documentation - "&dataset=", dataset - ) - - try - @info "Querying PVWatts for ambient temperature... " - r = HTTP.get(url) - response = JSON.parse(String(r.body)) - if r.status != 200 - throw(@error("Bad response from PVWatts: $(response["errors"])")) - end - @info "PVWatts success." - tamb = collect(get(response["outputs"], "tamb", [])) # Celcius - if length(tamb) != 8760 - throw(@error("PVWatts did not return a valid temperature. Got $tamb")) - end - return tamb - catch e - throw(@error("Error occurred when calling PVWatts: $e")) - end -end - +""" + call_pvwatts_api(latitude::Real, longitude::Real; timeframe="hourly", azimuth=180, module_type=0, array_type=1, tilt=latitude, time_steps_per_hour=1) -function get_pvwatts_prodfactor(latitude::Real, longitude::Real; timeframe="hourly", azimuth=180, module_type=0, array_type=1, tilt=latitude) +This calls the PVWatts API and returns both: + - PV production factor + - Ambient outdoor air dry bulb temperature profile [Celcius] +""" +function call_pvwatts_api(latitude::Real, longitude::Real; tilt=latitude, azimuth=180, module_type=1, array_type=1, + losses=14, dc_ac_ratio=1.2, gcr=0.4, inv_eff=96, timeframe="hourly", radius=0, time_steps_per_hour=1) # Check if site is beyond the bounds of the NRSDB TMY dataset. If so, use the international dataset. dataset = "nsrdb" if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 @@ -426,23 +398,36 @@ function get_pvwatts_prodfactor(latitude::Real, longitude::Real; timeframe="hour url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, "&lat=", latitude , "&lon=", longitude, "&tilt=", tilt, "&system_capacity=1", "&azimuth=", azimuth, "&module_type=", module_type, - "&array_type=", array_type, "&losses=", 14, - "&timeframe=", timeframe, "&dataset=", dataset - ) + "&array_type=", array_type, "&losses=", losses, "&dc_ac_ratio=", dc_ac_ratio, + "&gcr=", gcr, "&inv_eff=", inv_eff, "&timeframe=", timeframe, "&dataset=", dataset, + "&radius=", radius + ) try - @info "Querying PVWatts for production factor of 1 kW system with tilt set to latitude... " - r = HTTP.get(url) + @info "Querying PVWatts for production factor and ambient air temperature... " + r = HTTP.get(url, keepalive=true, readtimeout=10) response = JSON.parse(String(r.body)) if r.status != 200 throw(@error("Bad response from PVWatts: $(response["errors"])")) end @info "PVWatts success." + # Get both possible data of interest watts = collect(get(response["outputs"], "ac", []) / 1000) # scale to 1 kW system (* 1 kW / 1000 W) + tamb_celcius = collect(get(response["outputs"], "tamb", [])) # Celcius + # Validate outputs if length(watts) != 8760 throw(@error("PVWatts did not return a valid prodfactor. Got $watts")) end - return watts + # Validate tamb_celcius + if length(tamb_celcius) != 8760 + throw(@error("PVWatts did not return a valid temperature. Got $tamb_celcius")) + end + # Upsample or downsample based on model time_steps_per_hour + if time_steps_per_hour > 1 + watts = repeat(watts, inner=time_steps_per_hour) + tamb_celcius = repeat(tamb_celcius, inner=time_steps_per_hour) + end + return watts, tamb_celcius catch e throw(@error("Error occurred when calling PVWatts: $e")) end From 3bb505c5c0f6807dafb499a293685737d33878bd Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Sun, 2 Jul 2023 15:25:03 -0600 Subject: [PATCH 10/16] Use call_pvwatts_api() for get_production_factor(PV) --- src/core/production_factor.jl | 42 +++++------------------------------ 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/src/core/production_factor.jl b/src/core/production_factor.jl index beba394ff..12786c438 100644 --- a/src/core/production_factor.jl +++ b/src/core/production_factor.jl @@ -35,44 +35,14 @@ function get_production_factor(pv::PV, latitude::Real, longitude::Real; timefram return pv.production_factor_series end - # Check if site is beyond the bounds of the NRSDB TMY dataset. If so, use the international dataset. - dataset = "nsrdb" - if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 - if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 - if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 - dataset = "intl" - end - end - end + # TODO need to add all of these parameters defined above into the call_pvwatts_api function arguments + watts, ambient_temp_celcius = call_pvwatts_api(latitude, longitude; tilt=pv.tilt, azimuth=pv.azimuth, module_type=pv.module_type, + array_type=pv.array_type, losses=round(pv.losses*100, digits=3), dc_ac_ratio=pv.dc_ac_ratio, + gcr=pv.gcr, inv_eff=pv.inv_eff*100, timeframe=timeframe, radius=pv.radius, + time_steps_per_hour=time_steps_per_hour) - url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, - "&lat=", latitude , "&lon=", longitude, "&tilt=", pv.tilt, - "&system_capacity=1", "&azimuth=", pv.azimuth, "&module_type=", pv.module_type, - "&array_type=", pv.array_type, "&losses=", round(pv.losses*100, digits=3), "&dc_ac_ratio=", pv.dc_ac_ratio, - "&gcr=", pv.gcr, "&inv_eff=", pv.inv_eff*100, "&timeframe=", timeframe, "&dataset=", dataset, - "&radius=", pv.radius - ) + return watts - try - @info "Querying PVWatts for production_factor with " pv.name - r = HTTP.get(url, keepalive=true, readtimeout=10) - @info "Response received from PVWatts" - response = JSON.parse(String(r.body)) - if r.status != 200 - throw(@error("Bad response from PVWatts: $(response["errors"])")) - end - @info "PVWatts success." - watts = collect(get(response["outputs"], "ac", []) / 1000) # scale to 1 kW system (* 1 kW / 1000 W) - if length(watts) != 8760 - throw(@error("PVWatts did not return a valid production factor. Got $watts")) - end - if time_steps_per_hour > 1 - watts = repeat(watts, inner=time_steps_per_hour) - end - return watts - catch e - throw(@error("Error occurred when calling PVWatts: $e")) - end end From a5967e360eb2010eb652c23509512e508e5e63a2 Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Sun, 2 Jul 2023 15:26:22 -0600 Subject: [PATCH 11/16] Use call_pvwatts_api for GHP ambient temp, and assign PV production_factor at the same time This avoids an extra call of PVWatts with GHP and PV --- src/core/scenario.jl | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 0f5202053..904e07855 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -454,45 +454,27 @@ function Scenario(d::Dict; flex_hvac_from_json=false) number_of_ghpghx = length(d["GHP"]["ghpghx_inputs"]) end # Call PVWatts for hourly dry-bulb outdoor air temperature - ambient_temperature_f = [] + ambient_temp_degF = [] if !haskey(d["GHP"]["ghpghx_inputs"][1], "ambient_temperature_f") || isempty(d["GHP"]["ghpghx_inputs"][1]["ambient_temperature_f"]) - dataset = "nsrdb" - if longitude < -179.5 || longitude > -21.0 || latitude < -21.5 || latitude > 60.0 - if longitude < 81.5 || longitude > 179.5 || latitude < -60.0 || latitude > 60.0 - if longitude < 67.0 || latitude < -40.0 || latitude > 38.0 - dataset = "intl" - end - end - end - url = string("https://developer.nrel.gov/api/pvwatts/v8.json", "?api_key=", nrel_developer_key, - "&lat=", d["Site"]["latitude"] , "&lon=", d["Site"]["longitude"], "&tilt=", d["Site"]["latitude"], - "&system_capacity=1", "&azimuth=", 180, "&module_type=", 0, - "&array_type=", 0, "&losses=", 0.14, "&dc_ac_ratio=", 1.1, - "&gcr=", 0.4, "&inv_eff=", 99, "&timeframe=", "hourly", "&dataset=", dataset, - "&radius=", 100) - try - @info "Querying PVWatts for ambient temperature" - r = HTTP.get(url) - response = JSON.parse(String(r.body)) - if r.status != 200 - throw(@error("Bad response from PVWatts: $(response["errors"])")) + # If PV is evaluated and we need to call PVWatts for ambient temperature, assign PV production factor here too with the same call + # By assigning pv.production_factor_series here, it will skip the PVWatts call in get_production_factor(PV) call from reopt_input.jl + if !isempty(pvs) + for pv in pvs + pv.production_factor_series, ambient_temp_celcius = call_pvwatts_api(site.latitude, site.longitude; tilt=pv.tilt, azimuth=pv.azimuth, module_type=pv.module_type, + array_type=pv.array_type, losses=round(pv.losses*100, digits=3), dc_ac_ratio=pv.dc_ac_ratio, + gcr=pv.gcr, inv_eff=pv.inv_eff*100, timeframe="hourly", radius=pv.radius, time_steps_per_hour=settings.time_steps_per_hour) end - @info "PVWatts success." - temp_c = get(response["outputs"], "tamb", []) - if length(temp_c) != 8760 || isempty(temp_c) - throw(@error("PVWatts did not return a valid temperature profile. Got $temp_c")) - end - ambient_temperature_f = temp_c * 1.8 .+ 32.0 - catch e - throw(@error("Error occurred when calling PVWatts: $e")) + else + pv_prodfactor, ambient_temp_celcius = call_pvwatts_api(site.latitude, site.longitude; time_steps_per_hour=settings.time_steps_per_hour) end + ambient_temp_degF = ambient_temp_celcius * 1.8 .+ 32.0 else - ambient_temperature_f = d["GHP"]["ghpghx_inputs"][1]["ambient_temperature_f"] + ambient_temp_degF = d["GHP"]["ghpghx_inputs"][1]["ambient_temperature_f"] end for i in 1:number_of_ghpghx ghpghx_inputs = d["GHP"]["ghpghx_inputs"][i] - d["GHP"]["ghpghx_inputs"][i]["ambient_temperature_f"] = ambient_temperature_f + d["GHP"]["ghpghx_inputs"][i]["ambient_temperature_f"] = ambient_temp_degF # Only SpaceHeating portion of Heating Load gets served by GHP, unless allowed by can_serve_dhw if get(ghpghx_inputs, "heating_thermal_load_mmbtu_per_hr", []) in [nothing, []] if haskey(d["GHP"], "can_serve_dhw") && d["GHP"]["can_serve_dhw"] From e4f35c7aca0f192603717cb0835ab1d95698a2de Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Sun, 2 Jul 2023 15:27:46 -0600 Subject: [PATCH 12/16] Make PV struct mutable This allows for assigning pv.production_factor_series when calling PVWatts for GHP, to avoid a extra PVWatts calls later --- src/core/pv.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/pv.jl b/src/core/pv.jl index df6d69085..9a071e6a5 100644 --- a/src/core/pv.jl +++ b/src/core/pv.jl @@ -82,7 +82,7 @@ If `azimuth` is not provided, then it is set to 180 if the site is in the northern hemisphere and 0 if in the southern hemisphere. """ -struct PV <: AbstractTech +mutable struct PV <: AbstractTech tilt array_type module_type @@ -151,7 +151,7 @@ struct PV <: AbstractTech acres_per_kw::Real=6e-3, inv_eff::Real=0.96, dc_ac_ratio::Real=1.2, - production_factor_series::Union{Nothing, Array{Real,1}} = nothing, + production_factor_series::Union{Nothing, Array{<:Real,1}} = nothing, federal_itc_fraction::Real = 0.3, federal_rebate_per_kw::Real = 0.0, state_ibi_fraction::Real = 0.0, From da4e224bc63d05078a990e2f5459d8a7ee8c817b Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Sun, 2 Jul 2023 17:14:41 -0600 Subject: [PATCH 13/16] Update function call to call_pvwatts_api() The get_ambient_temperature() function is now combined into that call. --- test/test_with_xpress.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index 68a322101..e40fd3e71 100644 --- a/test/test_with_xpress.jl +++ b/test/test_with_xpress.jl @@ -256,7 +256,7 @@ end =# # Austin, TX -> existing_chiller and existing_boiler added with FlexibleHVAC - tamb = REopt.get_ambient_temperature(30.2672, -97.7431); + pf, tamb = REopt.call_pvwatts_api(30.2672, -97.7431); R = 0.00025 # K/kW C = 1e5 # kJ/K # the starting scenario has flat fuel and electricty costs From 697736fc84bc6a4c02d1f68abf1c4221f0c1143d Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 5 Jul 2023 11:02:21 -0600 Subject: [PATCH 14/16] update changelog, module_type=0, remove todo --- CHANGELOG.md | 5 +++-- src/core/production_factor.jl | 1 - src/core/site.jl | 2 +- src/core/utils.jl | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2186d986b..2bdc75c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,9 +26,10 @@ Classify the change according to the following categories: ## Develop - 2023-06-21 ### Changed -- In `src/core/production_factor.jl`, updated NSRDB bounds used in PVWatts query (now includes southern New Zealand) +- 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/. -- Updated PVwatts calls in `src/core/utils.jl` (`get_ambient_temperature` and `get_pvwatts_prodfactor`) and `scenario.jl` (used for GHP) to use v8 and to determine dataset based on lat long. +- Made PV struct mutable: This allows for assigning pv.production_factor_series when calling PVWatts for GHP, to avoid a extra PVWatts calls later. ## v0.32.3 ### Fixed - Calculate **num_battery_bins** default in `backup_reliability.jl` based on battery duration to prevent significant discretization error (and add test) diff --git a/src/core/production_factor.jl b/src/core/production_factor.jl index 12786c438..d1b0eedcc 100644 --- a/src/core/production_factor.jl +++ b/src/core/production_factor.jl @@ -35,7 +35,6 @@ function get_production_factor(pv::PV, latitude::Real, longitude::Real; timefram return pv.production_factor_series end - # TODO need to add all of these parameters defined above into the call_pvwatts_api function arguments watts, ambient_temp_celcius = call_pvwatts_api(latitude, longitude; tilt=pv.tilt, azimuth=pv.azimuth, module_type=pv.module_type, array_type=pv.array_type, losses=round(pv.losses*100, digits=3), dc_ac_ratio=pv.dc_ac_ratio, gcr=pv.gcr, inv_eff=pv.inv_eff*100, timeframe=timeframe, radius=pv.radius, diff --git a/src/core/site.jl b/src/core/site.jl index 2907ae879..6b42c2608 100644 --- a/src/core/site.jl +++ b/src/core/site.jl @@ -36,7 +36,7 @@ Inputs related to the physical location: longitude::Real, land_acres::Union{Real, Nothing} = nothing, # acres of land available for PV panels and/or Wind turbines. Constraint applied separately to PV and Wind, meaning the two technologies are assumed to be able to be co-located. roof_squarefeet::Union{Real, Nothing} = nothing, - min_resil_time_steps::Int=0, + min_resil_time_steps::Int=0, # The minimum number consecutive timesteps that load must be fully met once an outage begins. Only applies to multiple outage modeling using inputs outage_start_time_steps and outage_durations. mg_tech_sizes_equal_grid_sizes::Bool = true, node::Int = 1, CO2_emissions_reduction_min_fraction::Union{Float64, Nothing} = nothing, diff --git a/src/core/utils.jl b/src/core/utils.jl index 705f69dac..650af9446 100644 --- a/src/core/utils.jl +++ b/src/core/utils.jl @@ -378,13 +378,13 @@ end """ - call_pvwatts_api(latitude::Real, longitude::Real; timeframe="hourly", azimuth=180, module_type=0, array_type=1, tilt=latitude, time_steps_per_hour=1) - + call_pvwatts_api(latitude::Real, longitude::Real; tilt=latitude, azimuth=180, module_type=0, array_type=1, + losses=14, dc_ac_ratio=1.2, gcr=0.4, inv_eff=96, timeframe="hourly", radius=0, time_steps_per_hour=1) This calls the PVWatts API and returns both: - PV production factor - Ambient outdoor air dry bulb temperature profile [Celcius] """ -function call_pvwatts_api(latitude::Real, longitude::Real; tilt=latitude, azimuth=180, module_type=1, array_type=1, +function call_pvwatts_api(latitude::Real, longitude::Real; tilt=latitude, azimuth=180, module_type=0, array_type=1, losses=14, dc_ac_ratio=1.2, gcr=0.4, inv_eff=96, timeframe="hourly", radius=0, time_steps_per_hour=1) # Check if site is beyond the bounds of the NRSDB TMY dataset. If so, use the international dataset. dataset = "nsrdb" From 53c3a13a6d7de9cd7aa3e3152ffc11fed857adca Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Wed, 5 Jul 2023 12:53:19 -0600 Subject: [PATCH 15/16] Fix leap year issue when using URDB rate The energy_rates array was 8784 instead of skipping the leap year day like the rest of the code does This was throwing errors when checking the length of the input like wholesale_rate (8760) with energy_rate from URDB (8784 in this case) --- src/core/urdb.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 41cbbefe5..80c204d8a 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -270,6 +270,9 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM for month in range(1, stop=12) n_days = daysinmonth(Date(string(year) * "-" * string(month))) + if month == 2 && isleapyear(year) + n_days -= 1 + end for day in range(1, stop=n_days) From 7105221de852d0e8cea28a93af21303d1d727732 Mon Sep 17 00:00:00 2001 From: Bill Becker <42586683+Bill-Becker@users.noreply.github.com> Date: Wed, 5 Jul 2023 12:58:32 -0600 Subject: [PATCH 16/16] Add bug fix to CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bdc75c65..ea047ce89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ Classify the change according to the following categories: - 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. +### 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 + ## v0.32.3 ### Fixed - Calculate **num_battery_bins** default in `backup_reliability.jl` based on battery duration to prevent significant discretization error (and add test)