From d1a682f3f356e9c93f7a9835ad42152adc516589 Mon Sep 17 00:00:00 2001 From: Zolan Date: Wed, 22 May 2024 13:33:13 -0600 Subject: [PATCH 01/31] update bounds on wholesale, net metering benefits benefits --- src/constraints/electric_utility_constraints.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constraints/electric_utility_constraints.jl b/src/constraints/electric_utility_constraints.jl index a721b584e..193d7c874 100644 --- a/src/constraints/electric_utility_constraints.jl +++ b/src/constraints/electric_utility_constraints.jl @@ -52,7 +52,7 @@ function add_export_constraints(m, p; _n="") @warn "Adding binary variable for net metering choice. Some solvers are slow with binaries." # Good to bound the benefit - we use max_bene as a lower bound because the benefit is treated as a negative cost - max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:NEM])])*10 + max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:NEM])])*p.pwf_e*p.hours_per_time_step*10 NEM_benefit = @variable(m, lower_bound = max_bene) @@ -135,7 +135,7 @@ function add_export_constraints(m, p; _n="") else binWHL = @variable(m, binary = true) @warn "Adding binary variable for wholesale export choice. Some solvers are slow with binaries." - max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:WHL])])*10 + max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:WHL])])*p.pwf_e*p.hours_per_time_step*100 WHL_benefit = @variable(m, lower_bound = max_bene) @constraint(m, binNEM + binWHL == 1) # can either NEM or WHL export, not both From f8ea5e67c5f2a2002a9ba19f1a043b8c621c3764 Mon Sep 17 00:00:00 2001 From: Zolan Date: Wed, 22 May 2024 15:15:18 -0600 Subject: [PATCH 02/31] obtain tier limits before scrubbing demand rate structures --- src/core/urdb.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index c12022076..4b9620392 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -313,8 +313,8 @@ Parse monthly ("flat") and TOU demand rates """ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") - scrub_urdb_demand_tiers!(d["flatdemandstructure"]) monthly_demand_tier_limits = parse_urdb_demand_tiers(d["flatdemandstructure"]) + scrub_urdb_demand_tiers!(d["flatdemandstructure"]) n_monthly_demand_tiers = length(monthly_demand_tier_limits) monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers) else @@ -324,8 +324,8 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: end if haskey(d, "demandratestructure") - scrub_urdb_demand_tiers!(d["demandratestructure"]) tou_demand_tier_limits = parse_urdb_demand_tiers(d["demandratestructure"]) + scrub_urdb_demand_tiers!(d["demandratestructure"]) n_tou_demand_tiers = length(tou_demand_tier_limits) ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else From bd6acbc89a12a5fe78459de3147f5b518c1dffcb Mon Sep 17 00:00:00 2001 From: Zolan Date: Wed, 22 May 2024 15:32:06 -0600 Subject: [PATCH 03/31] new testset Multi-tier demand rates --- test/runtests.jl | 10 + test/scenarios/multi_tier_urdb_response.json | 1299 ++++++++++++++++++ 2 files changed, 1309 insertions(+) create mode 100644 test/scenarios/multi_tier_urdb_response.json diff --git a/test/runtests.jl b/test/runtests.jl index a62a95d46..b87517743 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1186,6 +1186,16 @@ else # run HiGHS tests @test results["PV"]["size_kw"] ≈ p.s.pvs[1].existing_kw end + @testset "Multi-tier demand rates" begin + #This test ensures that when multiple demand regimes are included that the tier limits load appropriately + d = JSON.parsefile("./scenarios/no_techs.json") + d["ElectricTariff"] = Dict() + d["ElectricTariff"]["urdb_response"] = JSON.parsefile("./scenarios/multi_tier_urdb_response.json") + s = Scenario(d) + p = REoptInputs(s) + @test p.s.electric_tariff.tou_demand_tier_limits[1] ≈ 100.0 atol=1.0e-4 + end + @testset "Tiered TOU Demand" begin data = JSON.parsefile("./scenarios/tiered_tou_demand.json") model = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) diff --git a/test/scenarios/multi_tier_urdb_response.json b/test/scenarios/multi_tier_urdb_response.json new file mode 100644 index 000000000..3535b0ab5 --- /dev/null +++ b/test/scenarios/multi_tier_urdb_response.json @@ -0,0 +1,1299 @@ +{ + "energyweekdayschedule": [ + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ] + ], + "energyweekendschedule": [ + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2 + ] + ], + "energyratestructure": [ + [ + { + "rate": 0, + "unit": "kWh" + } + ], + [ + { + "rate": 0.078891, + "unit": "kWh" + } + ], + [ + { + "rate": 0.061731, + "unit": "kWh" + } + ] + ], + "demandratestructure": [ + [ + { + "rate": 0, + "unit": "kW" + } + ], + [ + { + "rate": 24.368, + "max": 100.0, + "unit": "kW" + }, + { + "rate": 17.031, + "unit": "kW" + } + ] + ], + "demandweekdayschedule": [ + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ], + "demandweekendschedule": [ + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ], + "fixedchargefirstmeter": 3.298, + "fixedchargeunits": "$/day" +} \ No newline at end of file From 3a80c9c86ccfbcbc354965bc95adc2bc271ac5a8 Mon Sep 17 00:00:00 2001 From: Zolan Date: Wed, 22 May 2024 15:32:29 -0600 Subject: [PATCH 04/31] update NM test value --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index b87517743..626f42aed 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -590,7 +590,7 @@ else # run HiGHS tests d["PV"]["can_wholesale"] = true m = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) results = run_reopt(m, d) - @test results["PV"]["size_kw"] ≈ 84.029 atol=1e-3 #max benefit provides the upper bound + @test results["PV"]["size_kw"] ≈ 7440.0 atol=1e-3 #max benefit provides the upper bound end end From 8637d620e1580b13cccd0ac12852ba44e3228880 Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Wed, 22 May 2024 20:33:56 -0600 Subject: [PATCH 05/31] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23760985..3b07c4c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ Classify the change according to the following categories: ## Develop ### Fixed - Convert `max_electric_load_kw` to _Float64_ before passing to function `get_chp_defaults_prime_mover_size_class` +- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits +- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs ## v0.46.1 ### Changed From abc269d9073ecd29e06e80e5c4f0688a21df3747 Mon Sep 17 00:00:00 2001 From: Zolan Date: Thu, 23 May 2024 16:26:43 -0600 Subject: [PATCH 06/31] reduce very large hand-entered numbers for maxes to bigM --- src/core/urdb.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 4b9620392..347cc73ef 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -229,7 +229,7 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM energy_tier_max = get(energy_tier, "max", bigM) if "rate" in keys(energy_tier) || "adj" in keys(energy_tier) || "sell" in keys(energy_tier) - append!(energy_tier_limits_kwh, energy_tier_max) + append!(energy_tier_limits_kwh, min(energy_tier_max, bigM)) end if "unit" in keys(energy_tier) && string(energy_tier["unit"]) != "kWh" @@ -391,7 +391,8 @@ function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) for period in range(1, stop=length(A)) demand_max = Float64[] for tier in A[period] - append!(demand_max, get(tier, "max", bigM)) + tier_max = get(tier, "max", bigM) + append!(demand_max, min(bigM, tier_max)) end demand_tiers[period] = demand_max append!(demand_maxes, demand_max[end]) # TODO should this be maximum(demand_max)? From 209ba633c5601b4fdb0d417e9019e6616ebb42b8 Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Fri, 24 May 2024 14:54:15 -0600 Subject: [PATCH 07/31] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b07c4c29..ebc7efc6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Classify the change according to the following categories: - Convert `max_electric_load_kw` to _Float64_ before passing to function `get_chp_defaults_prime_mover_size_class` - Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits - Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs +- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits ## v0.46.1 ### Changed From 3d623f007ed7390c8303195f67f362499564c214 Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 08:46:55 -0600 Subject: [PATCH 08/31] use minimums for individual tier limits when more than one set of tier limits is provided --- src/core/urdb.jl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 32bc2e6af..3d621d7fe 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -300,8 +300,8 @@ Parse monthly ("flat") and TOU demand rates """ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") - monthly_demand_tier_limits = parse_urdb_demand_tiers(d["flatdemandstructure"]) scrub_urdb_demand_tiers!(d["flatdemandstructure"]) + monthly_demand_tier_limits = parse_urdb_demand_tiers(d; bigM) n_monthly_demand_tiers = length(monthly_demand_tier_limits) monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers) else @@ -311,8 +311,8 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: end if haskey(d, "demandratestructure") - tou_demand_tier_limits = parse_urdb_demand_tiers(d["demandratestructure"]) scrub_urdb_demand_tiers!(d["demandratestructure"]) + tou_demand_tier_limits = parse_urdb_demand_tiers(d; bigM) n_tou_demand_tiers = length(tou_demand_tier_limits) ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else @@ -359,12 +359,13 @@ end """ - parse_urdb_demand_tiers(A::Array; bigM=1.0e8) + parse_urdb_demand_tiers(d::Dict; bigM=1.0e8) set up and validate demand tiers returns demand_tiers::Array{Float64, n_tiers} """ -function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) +function parse_urdb_demand_tiers(d::Dict; bigM=1.0e8) + A = d["demandratestructure"] if length(A) == 0 return [] end @@ -387,9 +388,10 @@ function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) # test if the highest tier is the same across all periods if length(Set(demand_maxes)) > 1 - @warn "Highest demand tiers do not match across periods: using max tier from largest set of tiers." + @warn "Demand tiers do not match across periods: using minimum limit across periods for each tier." end - return demand_tiers[period_with_max_tiers] + tier_limits = [minimum(demand_tiers[period][tier] for period in length(demand_tiers)) for tier in 1:length(demand_tiers[1])] + return tier_limits end From 0c5207fe1c65d671056078bd6e2c8ce890df41c3 Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 08:47:32 -0600 Subject: [PATCH 09/31] increase energy tier limit --- src/core/urdb.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 3d621d7fe..575c1859c 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -196,7 +196,7 @@ end """ - parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e8) + parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e10) use URDB dict to return rates, energy_cost_vector, energy_tier_limits_kwh where: - rates is vector summary of rates within URDB (used for average rates when necessary) @@ -204,7 +204,7 @@ use URDB dict to return rates, energy_cost_vector, energy_tier_limits_kwh where: inner vectors are costs in each time step - energy_tier_limits_kwh is a vector of upper kWh limits for each energy tier """ -function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e8) +function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e10) if length(d["energyratestructure"]) == 0 throw(@error("No energyratestructure in URDB response.")) end From fff3597f8843b2f2c37a1c9300a1f207edd7572d Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 12:07:00 -0600 Subject: [PATCH 10/31] go back to general parse_urdb_demand_tiers using an array instead of Dict --- src/core/urdb.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 575c1859c..449a3378b 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -301,7 +301,7 @@ Parse monthly ("flat") and TOU demand rates function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") scrub_urdb_demand_tiers!(d["flatdemandstructure"]) - monthly_demand_tier_limits = parse_urdb_demand_tiers(d; bigM) + monthly_demand_tier_limits = parse_urdb_demand_tiers(d["flatdemandstructure"]; bigM) n_monthly_demand_tiers = length(monthly_demand_tier_limits) monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers) else @@ -312,7 +312,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "demandratestructure") scrub_urdb_demand_tiers!(d["demandratestructure"]) - tou_demand_tier_limits = parse_urdb_demand_tiers(d; bigM) + tou_demand_tier_limits = parse_urdb_demand_tiers(d["demandratestructure"]; bigM) n_tou_demand_tiers = length(tou_demand_tier_limits) ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else @@ -364,8 +364,7 @@ end set up and validate demand tiers returns demand_tiers::Array{Float64, n_tiers} """ -function parse_urdb_demand_tiers(d::Dict; bigM=1.0e8) - A = d["demandratestructure"] +function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) if length(A) == 0 return [] end From a91872ab0c280e024aa437eebf95279305e0cf9a Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 17:18:40 -0600 Subject: [PATCH 11/31] simplify parse_urdb_demand_tiers --- src/core/urdb.jl | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 449a3378b..df04d85a4 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -366,31 +366,11 @@ set up and validate demand tiers """ function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) if length(A) == 0 - return [] + return 0 end len_tiers = Int[length(r) for r in A] n_tiers = maximum(len_tiers) - period_with_max_tiers = findall(len_tiers .== maximum(len_tiers))[1] - - # set up tiers and validate that the highest tier has the same value across periods - demand_tiers = Dict() - demand_maxes = Float64[] - for period in range(1, stop=length(A)) - demand_max = Float64[] - for tier in A[period] - tier_max = get(tier, "max", bigM) - append!(demand_max, min(bigM, tier_max)) - end - demand_tiers[period] = demand_max - append!(demand_maxes, demand_max[end]) # TODO should this be maximum(demand_max)? - end - - # test if the highest tier is the same across all periods - if length(Set(demand_maxes)) > 1 - @warn "Demand tiers do not match across periods: using minimum limit across periods for each tier." - end - tier_limits = [minimum(demand_tiers[period][tier] for period in length(demand_tiers)) for tier in 1:length(demand_tiers[1])] - return tier_limits + return n_tiers end From 25c5c51b39bdfad66e20ae0eb6e44a71d0d9c15f Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 17:20:19 -0600 Subject: [PATCH 12/31] get limits directly from demand parsers --- src/core/urdb.jl | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index df04d85a4..be551907c 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -301,9 +301,8 @@ Parse monthly ("flat") and TOU demand rates function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") scrub_urdb_demand_tiers!(d["flatdemandstructure"]) - monthly_demand_tier_limits = parse_urdb_demand_tiers(d["flatdemandstructure"]; bigM) - n_monthly_demand_tiers = length(monthly_demand_tier_limits) - monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers) + n_monthly_demand_tiers = parse_urdb_demand_tiers(d["flatdemandstructure"]) + monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else monthly_demand_tier_limits = [] n_monthly_demand_tiers = 1 @@ -312,8 +311,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "demandratestructure") scrub_urdb_demand_tiers!(d["demandratestructure"]) - tou_demand_tier_limits = parse_urdb_demand_tiers(d["demandratestructure"]; bigM) - n_tou_demand_tiers = length(tou_demand_tier_limits) + n_tou_demand_tiers = parse_urdb_demand_tiers(d["demandratestructure"]) ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else tou_demand_tier_limits = [] @@ -375,11 +373,11 @@ end """ - parse_urdb_monthly_demand(d::Dict) + parse_urdb_monthly_demand(d::Dict, n_tiers; bigM=1.0e8) return monthly demand rates as array{month, tier} """ -function parse_urdb_monthly_demand(d::Dict, n_tiers) +function parse_urdb_monthly_demand(d::Dict, n_tiers; bigM=1.0e8) if !haskey(d, "flatdemandmonths") return [] end @@ -388,15 +386,17 @@ function parse_urdb_monthly_demand(d::Dict, n_tiers) end demand_rates = zeros(12, n_tiers) # array(month, tier) + demand_tier_limits = zeros(12, n_tiers) for month in range(1, stop=12) period = d["flatdemandmonths"][month] + 1 # URDB uses zero-indexing rates = d["flatdemandstructure"][period] # vector of dicts for (t, tier) in enumerate(rates) demand_rates[month, t] = round(get(tier, "rate", 0.0) + get(tier, "adj", 0.0), digits=6) + demand_tier_limits[month, t] = round(get(tier, "max", bigM), digits=6) end end - return demand_rates + return demand_rates, demand_tier_limits end @@ -405,13 +405,14 @@ end return array of arrary for ratchet time steps, tou demand rates array{ratchet, tier} """ -function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_hour::Int) +function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_hour::Int, bigM=1.0e8) if !haskey(d, "demandratestructure") return [], [] end n_periods = length(d["demandratestructure"]) ratchet_time_steps = Array[] rates_vec = Float64[] # array(ratchet_num, tier), reshape later + limits_vec = Float64[] # array(ratchet_num, tier), reshape later n_ratchets = 0 # counter for month in range(1, stop=12) @@ -422,13 +423,15 @@ function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_ append!(ratchet_time_steps, [time_steps]) for (t, tier) in enumerate(d["demandratestructure"][period]) append!(rates_vec, round(get(tier, "rate", 0.0) + get(tier, "adj", 0.0), digits=6)) + append!(limits_vec, round(get(tier, "rate", 0.0)), digits=6)) end end end end rates = reshape(rates_vec, (n_tiers, :))' # Array{Float64,2} + limits = reshape(limits_vec, (n_tiers, :))' # Array{Float64,2} ratchet_time_steps = convert(Array{Array{Int64,1},1}, ratchet_time_steps) - return ratchet_time_steps, rates + return ratchet_time_steps, rates, limits end From 32dfaaab17f31e7b483e85c29962f7883df827aa Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 17:23:28 -0600 Subject: [PATCH 13/31] ren parse_urdb_demand_tiers get_num_demand_tiers --- src/core/urdb.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index be551907c..41a46da9e 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -301,7 +301,7 @@ Parse monthly ("flat") and TOU demand rates function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") scrub_urdb_demand_tiers!(d["flatdemandstructure"]) - n_monthly_demand_tiers = parse_urdb_demand_tiers(d["flatdemandstructure"]) + n_monthly_demand_tiers = get_num_demand_tiers(d["flatdemandstructure"]) monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else monthly_demand_tier_limits = [] @@ -311,7 +311,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "demandratestructure") scrub_urdb_demand_tiers!(d["demandratestructure"]) - n_tou_demand_tiers = parse_urdb_demand_tiers(d["demandratestructure"]) + n_tou_demand_tiers = get_num_demand_tiers(d["demandratestructure"]) ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else tou_demand_tier_limits = [] @@ -357,12 +357,12 @@ end """ - parse_urdb_demand_tiers(d::Dict; bigM=1.0e8) + get_num_demand_tiers(d::Dict) -set up and validate demand tiers - returns demand_tiers::Array{Float64, n_tiers} + get maximum number of demand tiers in any period from scrubbed demand rate structure + returns n_tiers::Int """ -function parse_urdb_demand_tiers(A::Array; bigM=1.0e8) +function get_num_demand_tiers(A::Array) if length(A) == 0 return 0 end From 447f9ab1623ed55c691ca9be9a32b7e6b1af1b2d Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 17:24:32 -0600 Subject: [PATCH 14/31] make monthly_demand_tier_limits, tou_demand_tier_limits 2-dimensional arrays --- src/core/electric_tariff.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/electric_tariff.jl b/src/core/electric_tariff.jl index 83f2da0e5..237fa9c7c 100644 --- a/src/core/electric_tariff.jl +++ b/src/core/electric_tariff.jl @@ -13,12 +13,12 @@ struct ElectricTariff monthly_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers time_steps_monthly::AbstractArray{AbstractArray{Int64,1},1} # length = 0 or 12 - monthly_demand_tier_limits::AbstractArray{Float64,1} + monthly_demand_tier_limits::AbstractArray{Float64,2} n_monthly_demand_tiers::Int tou_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers tou_demand_ratchet_time_steps::AbstractArray{AbstractArray{Int64,1},1} # length = n_tou_demand_ratchets - tou_demand_tier_limits::AbstractArray{Float64,1} + tou_demand_tier_limits::AbstractArray{Float64,2} n_tou_demand_tiers::Int demand_lookback_months::AbstractArray{Int,1} From 60ed541f74d38e7445b142673371bad5604ccc3e Mon Sep 17 00:00:00 2001 From: Zolan Date: Fri, 31 May 2024 17:27:24 -0600 Subject: [PATCH 15/31] update indexing of p.s.electric_tariff.tou_demand_tier_limits, p.s.electric_tariff.monthly_demand_tier_limits --- src/constraints/electric_utility_constraints.jl | 8 ++++---- test/runtests.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/constraints/electric_utility_constraints.jl b/src/constraints/electric_utility_constraints.jl index 9e2720d23..6de5c3288 100644 --- a/src/constraints/electric_utility_constraints.jl +++ b/src/constraints/electric_utility_constraints.jl @@ -200,7 +200,7 @@ function add_monthly_peak_constraint(m, p; _n="") b = m[Symbol(dv)] # Upper bound on peak electrical power demand by month, tier; if tier is selected (0 o.w.) @constraint(m, [mth in p.months, tier in 1:ntiers], - m[Symbol("dvPeakDemandMonth"*_n)][mth, tier] <= p.s.electric_tariff.monthly_demand_tier_limits[tier] * + m[Symbol("dvPeakDemandMonth"*_n)][mth, tier] <= p.s.electric_tariff.monthly_demand_tier_limits[mth, tier] * b[mth, tier] ) @@ -209,7 +209,7 @@ function add_monthly_peak_constraint(m, p; _n="") # One monthly peak electrical power demand tier must be full before next one is active @constraint(m, [mth in p.months, tier in 2:ntiers], - b[mth, tier] * p.s.electric_tariff.monthly_demand_tier_limits[tier-1] <= + b[mth, tier] * p.s.electric_tariff.monthly_demand_tier_limits[mth, tier-1] <= m[Symbol("dvPeakDemandMonth"*_n)][mth, tier-1] ) # TODO implement NewMaxDemandMonthsInTier, which adds mth index to monthly_demand_tier_limits @@ -233,7 +233,7 @@ function add_tou_peak_constraint(m, p; _n="") # Upper bound on peak electrical power demand by tier, by ratchet, if tier is selected (0 o.w.) @constraint(m, [r in p.ratchets, tier in 1:ntiers], - m[Symbol("dvPeakDemandTOU"*_n)][r, tier] <= p.s.electric_tariff.tou_demand_tier_limits[tier] * b[r, tier] + m[Symbol("dvPeakDemandTOU"*_n)][r, tier] <= p.s.electric_tariff.tou_demand_tier_limits[r, tier] * b[r, tier] ) # Ratchet peak electrical power ratchet tier ordering @@ -243,7 +243,7 @@ function add_tou_peak_constraint(m, p; _n="") # One ratchet peak electrical power demand tier must be full before next one is active @constraint(m, [r in p.ratchets, tier in 2:ntiers], - b[r, tier] * p.s.electric_tariff.tou_demand_tier_limits[tier-1] + b[r, tier] * p.s.electric_tariff.tou_demand_tier_limits[r, tier-1] <= m[Symbol("dvPeakDemandTOU"*_n)][r, tier-1] ) end diff --git a/test/runtests.jl b/test/runtests.jl index 930a8788e..bc1d30765 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1223,7 +1223,7 @@ else # run HiGHS tests d["ElectricTariff"]["urdb_response"] = JSON.parsefile("./scenarios/multi_tier_urdb_response.json") s = Scenario(d) p = REoptInputs(s) - @test p.s.electric_tariff.tou_demand_tier_limits[1] ≈ 100.0 atol=1.0e-4 + @test p.s.electric_tariff.tou_demand_tier_limits[1, 1] ≈ 100.0 atol=1.0e-4 end @testset "Tiered TOU Demand" begin From 3091f0e6a842c213062f7d5a57ffacc869c40764 Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 09:18:48 -0600 Subject: [PATCH 16/31] index tier limits on month or TOU period --- src/core/electric_tariff.jl | 10 ++++----- src/core/urdb.jl | 44 +++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/core/electric_tariff.jl b/src/core/electric_tariff.jl index 237fa9c7c..28336ba2c 100644 --- a/src/core/electric_tariff.jl +++ b/src/core/electric_tariff.jl @@ -8,7 +8,7 @@ """ struct ElectricTariff energy_rates::AbstractArray{Float64, 2} # gets a second dim with tiers - energy_tier_limits::AbstractArray{Float64,1} + energy_tier_limits::AbstractArray{Float64,2} n_energy_tiers::Int monthly_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers @@ -120,11 +120,11 @@ function ElectricTariff(; # TODO remove_tiers for multinode models nem_rate = Float64[] - energy_tier_limits = Float64[] + energy_tier_limits = Array{Float64,2}(undef, 0, 0) n_energy_tiers = 1 - monthly_demand_tier_limits = Float64[] + monthly_demand_tier_limits = Array{Float64,2}(undef, 0, 0) n_monthly_demand_tiers = 1 - tou_demand_tier_limits = Float64[] + tou_demand_tier_limits = Array{Float64,2}(undef, 0, 0) n_tou_demand_tiers = 1 time_steps_monthly = get_monthly_time_steps(year, time_steps_per_hour=time_steps_per_hour) @@ -241,7 +241,7 @@ function ElectricTariff(; if remove_tiers energy_rates, monthly_demand_rates, tou_demand_rates = remove_tiers_from_urdb_rate(u) energy_tier_limits, monthly_demand_tier_limits, tou_demand_tier_limits = - Float64[], Float64[], Float64[] + Array{Float64,2}(undef, 0, 0), Array{Float64,2}(undef, 0, 0), Array{Float64,2}(undef, 0, 0) n_energy_tiers, n_monthly_demand_tiers, n_tou_demand_tiers = 1, 1, 1 end diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 41a46da9e..c08a474e3 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -12,15 +12,15 @@ Contains some of the data for ElectricTariff """ struct URDBrate energy_rates::Array{Float64,2} # time X tier - energy_tier_limits::Array{Real,1} + energy_tier_limits::Array{Float64,2} n_energy_tiers::Int n_monthly_demand_tiers::Int - monthly_demand_tier_limits::Array{Real,1} + monthly_demand_tier_limits::Array{Float64,2} monthly_demand_rates::Array{Float64,2} # month X tier n_tou_demand_tiers::Int - tou_demand_tier_limits::Array{Real,1} + tou_demand_tier_limits::Array{Float64,2} tou_demand_rates::Array{Float64,2} # ratchet X tier tou_demand_ratchet_time_steps::Array{Array{Int64,1},1} # length = n_tou_demand_ratchets @@ -88,7 +88,6 @@ function URDBrate(urdb_response::Dict, year::Int; time_steps_per_hour=1) fixed_monthly_charge, annual_min_charge, min_monthly_charge = parse_urdb_fixed_charges(urdb_response) - demand_lookback_months, demand_lookback_percent, demand_lookback_range = parse_urdb_lookback_charges(urdb_response) URDBrate( @@ -220,27 +219,20 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM period_with_max_tiers = findall(energy_tiers .== maximum(energy_tiers))[1] n_energy_tiers = Int(maximum(energy_tier_set)) - energy_tier_limits_kwh = Float64[] - - for energy_tier in d["energyratestructure"][period_with_max_tiers] - # energy_tier is a dictionary, eg. {'max': 1000, 'rate': 0.07531, 'adj': 0.0119, 'unit': 'kWh'} - energy_tier_max = get(energy_tier, "max", bigM) - - if "rate" in keys(energy_tier) || "adj" in keys(energy_tier) || "sell" in keys(energy_tier) - append!(energy_tier_limits_kwh, min(energy_tier_max, bigM)) - end - - if "unit" in keys(energy_tier) && string(energy_tier["unit"]) != "kWh" - throw(@error("URDB energy tiers have exotic units of " * energy_tier["unit"])) - end - end - energy_cost_vector = Float64[] sell_vector = Float64[] + energy_limit_vector = Float64[] for tier in range(1, stop=n_energy_tiers) for month in range(1, stop=12) + period = Int(d["energyweekdayschedule"][month][1] + 1) + for tier in range(1, stop=n_energy_tiers) + # tiered energy schedules are assumed to be consistent for each month (i.e., the first hour can represent all 24 hours of the schedule). + tier_limit = get(d["energyratestructure"][period][tier], "max", bigM) + append!(energy_limit_vector, round(tier_limit, digits=3)) + end + n_days = daysinmonth(Date(string(year) * "-" * string(month))) if month == 2 && isleapyear(year) n_days -= 1 @@ -265,6 +257,9 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM else tier_use = tier end + if "unit" in keys(d["energyratestructure"][period][tier_use]) && string(d["energyratestructure"][period][tier_use]["unit"]) != "kWh" + throw(@error("URDB energy tiers have exotic units of " * d["energyratestructure"][period][tier_use]["unit"])) + end total_rate = (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) @@ -279,6 +274,7 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM end energy_rates = reshape(energy_cost_vector, (:, n_energy_tiers)) sell_rates = reshape(sell_vector, (:, n_energy_tiers)) + energy_tier_limits_kwh = reshape(energy_limit_vector, (:, n_energy_tiers)) return energy_rates, energy_tier_limits_kwh, n_energy_tiers, sell_rates end @@ -302,9 +298,9 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "flatdemandstructure") scrub_urdb_demand_tiers!(d["flatdemandstructure"]) n_monthly_demand_tiers = get_num_demand_tiers(d["flatdemandstructure"]) - monthly_demand_rates = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) + monthly_demand_rates, monthly_demand_tier_limits = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else - monthly_demand_tier_limits = [] + monthly_demand_tier_limits = Array{Float64,2}(undef, 0, 0) n_monthly_demand_tiers = 1 monthly_demand_rates = Array{Float64,2}(undef, 0, 0) end @@ -312,9 +308,9 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "demandratestructure") scrub_urdb_demand_tiers!(d["demandratestructure"]) n_tou_demand_tiers = get_num_demand_tiers(d["demandratestructure"]) - ratchet_time_steps, tou_demand_rates = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) + ratchet_time_steps, tou_demand_rates, tou_demand_tier_limits = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else - tou_demand_tier_limits = [] + tou_demand_tier_limits = Array{Float64,2}(undef, 0, 0) n_tou_demand_tiers = 0 ratchet_time_steps = [] tou_demand_rates = Array{Float64,2}(undef, 0, 0) @@ -423,7 +419,7 @@ function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_ append!(ratchet_time_steps, [time_steps]) for (t, tier) in enumerate(d["demandratestructure"][period]) append!(rates_vec, round(get(tier, "rate", 0.0) + get(tier, "adj", 0.0), digits=6)) - append!(limits_vec, round(get(tier, "rate", 0.0)), digits=6)) + append!(limits_vec, round(get(tier, "max", bigM), digits=3)) end end end From 56ff6531d8f615a0a1e38ba75a87533ec6850868 Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 12:36:41 -0600 Subject: [PATCH 17/31] ren scrub_urdb_demand_tiers scrub_urdb_tiers --- CHANGELOG.md | 2 +- src/core/urdb.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 525b6cd2d..546db82c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,7 +177,7 @@ Classify the change according to the following categories: ## v0.37.5 ### Fixed - Fixed AVERT emissions profiles for NOx. Were previously the same as the SO2 profiles. AVERT emissions profiles are currently generated from AVERT v3.2 https://www.epa.gov/avert/download-avert. See REopt User Manual for more information. -- Fix setting of equal demand tiers in scrub_urdb_demand_tiers!, which was previously causing an error. +- Fix setting of equal demand tiers in `scrub_urdb_demand_tiers`, now renamed `scrub_urdb_tiers`. - When calling REopt.jl from a python environment using PyJulia and PyCall, some urdb_response fields get converted from a list-of-lists to a matrix type, when REopt.jl expects an array type. This fix adds checks on the type for two urdb_response fields and converts them to an array if needed. - Update the outages dispatch results to align with CHP availability during outages diff --git a/src/core/urdb.jl b/src/core/urdb.jl index c08a474e3..0e55569d2 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -296,7 +296,7 @@ Parse monthly ("flat") and TOU demand rates """ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") - scrub_urdb_demand_tiers!(d["flatdemandstructure"]) + scrub_urdb_tiers!(d["flatdemandstructure"]) n_monthly_demand_tiers = get_num_demand_tiers(d["flatdemandstructure"]) monthly_demand_rates, monthly_demand_tier_limits = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else @@ -306,7 +306,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: end if haskey(d, "demandratestructure") - scrub_urdb_demand_tiers!(d["demandratestructure"]) + scrub_urdb_tiers!(d["demandratestructure"]) n_tou_demand_tiers = get_num_demand_tiers(d["demandratestructure"]) ratchet_time_steps, tou_demand_rates, tou_demand_tier_limits = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else @@ -323,11 +323,11 @@ end """ - scrub_urdb_demand_tiers!(A::Array) +scrub_urdb_tiers!(A::Array) validate flatdemandstructure and demandratestructure have equal number of tiers across periods """ -function scrub_urdb_demand_tiers!(A::Array) +function scrub_urdb_tiers!(A::Array) if length(A) == 0 return end From 238b951f2042a166548fe4ea6cead26e83776212 Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 13:54:55 -0600 Subject: [PATCH 18/31] ren get_num_demand_tiers get_num_tiers --- src/core/urdb.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 0e55569d2..351ff21e7 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -353,12 +353,12 @@ end """ - get_num_demand_tiers(d::Dict) +get_num_tiers(d::Dict) get maximum number of demand tiers in any period from scrubbed demand rate structure returns n_tiers::Int """ -function get_num_demand_tiers(A::Array) +function get_num_tiers(A::Array) if length(A) == 0 return 0 end From 2398e8b4f000c5f57aa758ac9014d8dfe3e1981d Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 13:55:17 -0600 Subject: [PATCH 19/31] update warning for scrub_urdb_tiers --- src/core/urdb.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 351ff21e7..dbd8768fc 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -326,6 +326,7 @@ end scrub_urdb_tiers!(A::Array) validate flatdemandstructure and demandratestructure have equal number of tiers across periods +validate energyratestructure, flatdemandstructure and demandratestructure have equal number of tiers across periods """ function scrub_urdb_tiers!(A::Array) if length(A) == 0 @@ -336,7 +337,7 @@ function scrub_urdb_tiers!(A::Array) n_tiers = maximum(len_tiers_set) if length(len_tiers_set) > 1 - @warn "Demand rate structure has varying number of tiers in periods. Making the number of tiers the same across all periods by repeating the last tier." + @warn "Rate structure has varying number of tiers in periods. Making the number of tiers the same across all periods by repeating the last tier." for (i, rate) in enumerate(A) n_tiers_in_period = length(rate) if n_tiers_in_period != n_tiers From 8502b1c81eac3fe2f420fc1efdbfcfd78691ed85 Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 13:56:55 -0600 Subject: [PATCH 20/31] make energy limits 2-dimensional array instead of vector --- .../electric_utility_constraints.jl | 4 +-- src/core/urdb.jl | 26 +++++-------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/constraints/electric_utility_constraints.jl b/src/constraints/electric_utility_constraints.jl index 6de5c3288..201317e8f 100644 --- a/src/constraints/electric_utility_constraints.jl +++ b/src/constraints/electric_utility_constraints.jl @@ -299,7 +299,7 @@ function add_energy_tier_constraints(m, p; _n="") ##Constraint (10a): Usage limits by pricing tier, by month @constraint(m, [mth in p.months, tier in 1:p.s.electric_tariff.n_energy_tiers], p.hours_per_time_step * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier] for ts in p.s.electric_tariff.time_steps_monthly[mth] ) - <= b[mth, tier] * p.s.electric_tariff.energy_tier_limits[tier] + <= b[mth, tier] * p.s.electric_tariff.energy_tier_limits[mth, tier] ) ##Constraint (10b): Ordering of pricing tiers @constraint(m, [mth in p.months, tier in 2:p.s.electric_tariff.n_energy_tiers], @@ -307,7 +307,7 @@ function add_energy_tier_constraints(m, p; _n="") ) ## Constraint (10c): One tier must be full before any usage in next tier @constraint(m, [mth in p.months, tier in 2:p.s.electric_tariff.n_energy_tiers], - b[mth, tier] * p.s.electric_tariff.energy_tier_limits[tier-1] - + b[mth, tier] * p.s.electric_tariff.energy_tier_limits[mth, tier-1] - sum( m[Symbol("dvGridPurchase"*_n)][ts, tier-1] for ts in p.s.electric_tariff.time_steps_monthly[mth]) <= 0 ) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index dbd8768fc..528a58e6d 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -201,27 +201,17 @@ use URDB dict to return rates, energy_cost_vector, energy_tier_limits_kwh where: - rates is vector summary of rates within URDB (used for average rates when necessary) - energy_cost_vector is a vector of vectors with inner vectors for each energy rate tier, inner vectors are costs in each time step - - energy_tier_limits_kwh is a vector of upper kWh limits for each energy tier + - energy_tier_limits_kwh is a matrix of upper kWh limits for each energy tier, for each month """ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM = 1.0e10) if length(d["energyratestructure"]) == 0 throw(@error("No energyratestructure in URDB response.")) end - # TODO check bigM (in multiple functions) - energy_tiers = Float64[] - for energy_rate in d["energyratestructure"] - append!(energy_tiers, length(energy_rate)) - end - energy_tier_set = Set(energy_tiers) - if length(energy_tier_set) > 1 - @warn "Energy periods contain different numbers of tiers, using limits of period with most tiers." - end - period_with_max_tiers = findall(energy_tiers .== maximum(energy_tiers))[1] - n_energy_tiers = Int(maximum(energy_tier_set)) - + scrub_urdb_tiers!(d["energyratestructure"]) + n_energy_tiers = get_num_tiers(d["energyratestructure"]) energy_cost_vector = Float64[] sell_vector = Float64[] - energy_limit_vector = Float64[] + energy_tier_limits_kwh = Array{Float64}(undef, 12, n_energy_tiers) for tier in range(1, stop=n_energy_tiers) @@ -230,7 +220,7 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM for tier in range(1, stop=n_energy_tiers) # tiered energy schedules are assumed to be consistent for each month (i.e., the first hour can represent all 24 hours of the schedule). tier_limit = get(d["energyratestructure"][period][tier], "max", bigM) - append!(energy_limit_vector, round(tier_limit, digits=3)) + energy_tier_limits_kwh[month, tier] = round(tier_limit, digits=3) end n_days = daysinmonth(Date(string(year) * "-" * string(month))) @@ -274,7 +264,6 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM end energy_rates = reshape(energy_cost_vector, (:, n_energy_tiers)) sell_rates = reshape(sell_vector, (:, n_energy_tiers)) - energy_tier_limits_kwh = reshape(energy_limit_vector, (:, n_energy_tiers)) return energy_rates, energy_tier_limits_kwh, n_energy_tiers, sell_rates end @@ -297,7 +286,7 @@ Parse monthly ("flat") and TOU demand rates function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") scrub_urdb_tiers!(d["flatdemandstructure"]) - n_monthly_demand_tiers = get_num_demand_tiers(d["flatdemandstructure"]) + n_monthly_demand_tiers = get_num_tiers(d["flatdemandstructure"]) monthly_demand_rates, monthly_demand_tier_limits = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else monthly_demand_tier_limits = Array{Float64,2}(undef, 0, 0) @@ -307,7 +296,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: if haskey(d, "demandratestructure") scrub_urdb_tiers!(d["demandratestructure"]) - n_tou_demand_tiers = get_num_demand_tiers(d["demandratestructure"]) + n_tou_demand_tiers = get_num_tiers(d["demandratestructure"]) ratchet_time_steps, tou_demand_rates, tou_demand_tier_limits = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else tou_demand_tier_limits = Array{Float64,2}(undef, 0, 0) @@ -325,7 +314,6 @@ end """ scrub_urdb_tiers!(A::Array) -validate flatdemandstructure and demandratestructure have equal number of tiers across periods validate energyratestructure, flatdemandstructure and demandratestructure have equal number of tiers across periods """ function scrub_urdb_tiers!(A::Array) From 28d89178b1cf3751f72380a3ae3ef353e87e6c08 Mon Sep 17 00:00:00 2001 From: Zolan Date: Mon, 3 Jun 2024 13:58:46 -0600 Subject: [PATCH 21/31] expand mulitple tier testing --- test/runtests.jl | 13 +++-- test/scenarios/multi_tier_urdb_response.json | 53 +++++++++++--------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index bc1d30765..27db3eb8a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1216,14 +1216,21 @@ else # run HiGHS tests @test results["PV"]["size_kw"] ≈ p.s.pvs[1].existing_kw end - @testset "Multi-tier demand rates" begin - #This test ensures that when multiple demand regimes are included that the tier limits load appropriately + @testset "Multi-tier demand and energy rates" begin + #This test ensures that when multiple energy or demand regimes are included, that the tier limits load appropriately d = JSON.parsefile("./scenarios/no_techs.json") d["ElectricTariff"] = Dict() d["ElectricTariff"]["urdb_response"] = JSON.parsefile("./scenarios/multi_tier_urdb_response.json") s = Scenario(d) p = REoptInputs(s) - @test p.s.electric_tariff.tou_demand_tier_limits[1, 1] ≈ 100.0 atol=1.0e-4 + @test p.s.electric_tariff.tou_demand_tier_limits[1, 1] ≈ 1.0e8 atol=1.0 + @test p.s.electric_tariff.tou_demand_tier_limits[1, 2] ≈ 1.0e8 atol=1.0 + @test p.s.electric_tariff.tou_demand_tier_limits[2, 1] ≈ 100.0 atol=1.0 + @test p.s.electric_tariff.tou_demand_tier_limits[2, 2] ≈ 1.0e8 atol=1.0 + @test p.s.electric_tariff.energy_tier_limits[1, 1] ≈ 1.0e10 atol=1.0 + @test p.s.electric_tariff.energy_tier_limits[1, 2] ≈ 1.0e10 atol=1.0 + @test p.s.electric_tariff.energy_tier_limits[6, 1] ≈ 20000.0 atol=1.0 + @test p.s.electric_tariff.energy_tier_limits[6, 2] ≈ 1.0e10 atol=1.0 end @testset "Tiered TOU Demand" begin diff --git a/test/scenarios/multi_tier_urdb_response.json b/test/scenarios/multi_tier_urdb_response.json index 3535b0ab5..461c4ddf6 100644 --- a/test/scenarios/multi_tier_urdb_response.json +++ b/test/scenarios/multi_tier_urdb_response.json @@ -637,6 +637,11 @@ [ { "rate": 0.078891, + "max": 20000, + "unit": "kWh" + }, + { + "rate": 0.06, "unit": "kWh" } ], @@ -668,30 +673,30 @@ ], "demandweekdayschedule": [ [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 ], [ 1, From b53f9e40fa04a7026c97d87287ce4b1d51d9699a Mon Sep 17 00:00:00 2001 From: adfarth Date: Thu, 6 Jun 2024 10:47:34 -0600 Subject: [PATCH 22/31] changelog, add notes --- CHANGELOG.md | 13 +++++++++---- src/core/electric_tariff.jl | 2 +- src/core/urdb.jl | 11 +++++------ test/runtests.jl | 4 ++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe6286a03..bf3b40b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,14 @@ Classify the change according to the following categories: ### Deprecated ### Removed +## Develop - 2024-06-05 +### Fixed +- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits +- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs +- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits +- Index utility rate demand and energy tier limits on month in addition to tier + + ## v0.47.1 ### Fixed - Type issue with `CoolingLoad` monthly energy input @@ -44,11 +52,8 @@ Classify the change according to the following categories: ### Fixed - Updated the PV result **lifecycle_om_cost_after_tax** to account for the third-party factor for third-party ownership analyses. - Convert `max_electric_load_kw` to _Float64_ before passing to function `get_chp_defaults_prime_mover_size_class` -- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits -- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs -- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits -- Modified thermal waste heat constraints for heating technologies to avoid errors in waste heat results tracking. - Fixed a bug in which excess heat from one heating technology resulted in waste heat from another technology. +- Modified thermal waste heat constraints for heating technologies to avoid errors in waste heat results tracking. ## v0.46.1 ### Changed diff --git a/src/core/electric_tariff.jl b/src/core/electric_tariff.jl index 28336ba2c..3c75b08ee 100644 --- a/src/core/electric_tariff.jl +++ b/src/core/electric_tariff.jl @@ -42,7 +42,7 @@ end `ElectricTariff` is a required REopt input for on-grid scenarios only (it cannot be supplied when `Settings.off_grid_flag` is true) with the following keys and default values: ```julia urdb_label::String="", - urdb_response::Dict=Dict(), + urdb_response::Dict=Dict(), # Response JSON for URDB rates. Note: if creating your own urdb_response, ensure periods are zero-indexed. urdb_utility_name::String="", urdb_rate_name::String="", wholesale_rate::T1=nothing, # Price of electricity sold back to the grid in absence of net metering. Can be a scalar value, which applies for all-time, or an array with time-sensitive values. If an array is input then it must have a length of 8760, 17520, or 35040. The inputed array values are up/down-sampled using mean values to match the Settings.time_steps_per_hour. diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 528a58e6d..2ad1b5f67 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -216,10 +216,11 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM for tier in range(1, stop=n_energy_tiers) for month in range(1, stop=12) + # NOTE: periods are zero indexed in URDB period = Int(d["energyweekdayschedule"][month][1] + 1) for tier in range(1, stop=n_energy_tiers) # tiered energy schedules are assumed to be consistent for each month (i.e., the first hour can represent all 24 hours of the schedule). - tier_limit = get(d["energyratestructure"][period][tier], "max", bigM) + tier_limit = get(d["energyratestructure"][period][tier], "max", bigM) energy_tier_limits_kwh[month, tier] = round(tier_limit, digits=3) end @@ -229,10 +230,8 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM end for day in range(1, stop=n_days) - for hour in range(1, stop=24) - - # NOTE: periods are zero indexed + # NOTE: periods are zero indexed in URDB if dayofweek(Date(year, month, day)) < 6 # Monday == 1 period = Int(d["energyweekdayschedule"][month][hour] + 1) else @@ -248,7 +247,7 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM tier_use = tier end if "unit" in keys(d["energyratestructure"][period][tier_use]) && string(d["energyratestructure"][period][tier_use]["unit"]) != "kWh" - throw(@error("URDB energy tiers have exotic units of " * d["energyratestructure"][period][tier_use]["unit"])) + throw(@error("URDB energy tiers have non-standard units of " * d["energyratestructure"][period][tier_use]["unit"])) end total_rate = (get(d["energyratestructure"][period][tier_use], "rate", 0) + get(d["energyratestructure"][period][tier_use], "adj", 0)) @@ -325,7 +324,7 @@ function scrub_urdb_tiers!(A::Array) n_tiers = maximum(len_tiers_set) if length(len_tiers_set) > 1 - @warn "Rate structure has varying number of tiers in periods. Making the number of tiers the same across all periods by repeating the last tier." + @warn "Rate structure has varying number of tiers in periods. Making the number of tiers the same across all periods by repeating the last tier in each period." for (i, rate) in enumerate(A) n_tiers_in_period = length(rate) if n_tiers_in_period != n_tiers diff --git a/test/runtests.jl b/test/runtests.jl index 27db3eb8a..dd7b5905b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1253,14 +1253,14 @@ else # run HiGHS tests # inputs = REoptInputs(s) # results = run_reopt(m, inputs) - @testset "Exotic Units for Energy Rates" begin + @testset "Non-Standard Units for Energy Rates" begin d = JSON.parsefile("./scenarios/no_techs.json") d["ElectricTariff"] = Dict( "urdb_label" => "6272e4ae7eb76766c247d469" ) m = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) results = run_reopt(m, d) - @test occursin("URDB energy tiers have exotic units of", string(results["Messages"])) + @test occursin("URDB energy tiers have non-standard units of", string(results["Messages"])) end end From e8c285b3105ec60540cdd1fabda3149794c27998 Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Thu, 6 Jun 2024 21:23:49 -0600 Subject: [PATCH 23/31] Add thermal efficiency as input to chp defaults function To allow heuristic sizing with overrides to zero thermal efficiency defaults, e.g. for microturbine --- src/core/chp.jl | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/core/chp.jl b/src/core/chp.jl index 86343f64f..7b10601e0 100644 --- a/src/core/chp.jl +++ b/src/core/chp.jl @@ -221,7 +221,8 @@ function CHP(d::Dict; boiler_efficiency=eff, avg_electric_load_kw=avg_electric_load_kw, max_electric_load_kw=max_electric_load_kw, - is_electric_only=chp.is_electric_only) + is_electric_only=chp.is_electric_only, + thermal_efficiency=chp.thermal_efficiency_full_load) defaults = chp_defaults_response["default_inputs"] for (k, v) in custom_chp_inputs if k in [:installed_cost_per_kw, :tech_sizes_for_cost_curve] @@ -336,7 +337,8 @@ end boiler_efficiency::Union{Float64, Nothing}=nothing, avg_electric_load_kw::Union{Float64, Nothing}=nothing, max_electric_load_kw::Union{Float64, Nothing}=nothing, - is_electric_only::Bool=false) + is_electric_only::Bool=false, + thermal_efficiency::Float64=NaN) Depending on the set of inputs, different sets of outputs are determine in addition to all CHP cost and performance parameter defaults: 1. Inputs: hot_water_or_steam and avg_boiler_fuel_load_mmbtu_per_hour @@ -371,7 +373,8 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri boiler_efficiency::Union{Float64, Nothing}=nothing, avg_electric_load_kw::Union{Float64, Nothing}=nothing, max_electric_load_kw::Union{Float64, Nothing}=nothing, - is_electric_only::Union{Bool, Nothing}=nothing) + is_electric_only::Union{Bool, Nothing}=nothing, + thermal_efficiency::Float64=NaN) prime_mover_defaults_all = JSON.parsefile(joinpath(@__DIR__, "..", "..", "data", "chp", "chp_defaults.json")) avg_boiler_fuel_load_under_recip_over_ct = Dict([("hot_water", 27.0), ("steam", 7.0)]) # [MMBtu/hr] Based on external calcs for size versus production by prime_mover type @@ -434,7 +437,7 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri boiler_effic = boiler_efficiency end chp_elec_size_heuristic_kw = get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour, - prime_mover, size_class_calc, hot_water_or_steam, boiler_effic) + prime_mover, size_class_calc, hot_water_or_steam, boiler_effic, thermal_efficiency) chp_max_size_kw = 2 * chp_elec_size_heuristic_kw # If available, calculate heuristic CHP size based on average electric load, and max size based on peak electric load elseif !isnothing(avg_electric_load_kw) && !isnothing(max_electric_load_kw) @@ -482,7 +485,7 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri while !(size_class in size_class_last) append!(size_class_last, size_class) chp_elec_size_heuristic_kw = get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour, - prime_mover, size_class, hot_water_or_steam, boiler_effic) + prime_mover, size_class, hot_water_or_steam, boiler_effic, thermal_efficiency) chp_max_size_kw = 2 * chp_elec_size_heuristic_kw size_class = get_size_class_from_size(chp_elec_size_heuristic_kw, class_bounds, n_classes) end @@ -515,8 +518,12 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri end function get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour, - prime_mover, size_class, hot_water_or_steam, boiler_effic) - therm_effic = prime_mover_defaults_all[prime_mover]["thermal_efficiency_full_load"][hot_water_or_steam][size_class+1] + prime_mover, size_class, hot_water_or_steam, boiler_effic, thermal_efficiency=NaN) + if isnan(thermal_efficiency) + therm_effic = prime_mover_defaults_all[prime_mover]["thermal_efficiency_full_load"][hot_water_or_steam][size_class+1] + else + therm_effic = thermal_efficiency + end if therm_effic == 0.0 throw(@error("Error trying to calculate heuristic CHP size based on average thermal load because the thermal efficiency of prime mover $prime_mover for generating $hot_water_or_steam is 0.0")) From 226cd67b36b972559a16af48982849c0cbfaccd6 Mon Sep 17 00:00:00 2001 From: Zolan Date: Tue, 11 Jun 2024 09:58:58 -0600 Subject: [PATCH 24/31] rm comment on URDB Dict modification from parsing function docstrings --- src/core/urdb.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 2ad1b5f67..182522800 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -280,7 +280,6 @@ end parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) Parse monthly ("flat") and TOU demand rates - can modify URDB dict when there is inconsistent numbers of tiers in rate structures """ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") From 5fbc355766cf2dad01394c4a16c7006852a68d19 Mon Sep 17 00:00:00 2001 From: Zolan Date: Tue, 11 Jun 2024 11:51:48 -0600 Subject: [PATCH 25/31] refactoring to remove get_num_tiers() --- src/core/urdb.jl | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 182522800..92f7c7bce 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -207,8 +207,7 @@ function parse_urdb_energy_costs(d::Dict, year::Int; time_steps_per_hour=1, bigM if length(d["energyratestructure"]) == 0 throw(@error("No energyratestructure in URDB response.")) end - scrub_urdb_tiers!(d["energyratestructure"]) - n_energy_tiers = get_num_tiers(d["energyratestructure"]) + n_energy_tiers = scrub_urdb_tiers!(d["energyratestructure"]) energy_cost_vector = Float64[] sell_vector = Float64[] energy_tier_limits_kwh = Array{Float64}(undef, 12, n_energy_tiers) @@ -283,8 +282,7 @@ Parse monthly ("flat") and TOU demand rates """ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour::Int) if haskey(d, "flatdemandstructure") - scrub_urdb_tiers!(d["flatdemandstructure"]) - n_monthly_demand_tiers = get_num_tiers(d["flatdemandstructure"]) + n_monthly_demand_tiers = scrub_urdb_tiers!(d["flatdemandstructure"]) monthly_demand_rates, monthly_demand_tier_limits = parse_urdb_monthly_demand(d, n_monthly_demand_tiers; bigM) else monthly_demand_tier_limits = Array{Float64,2}(undef, 0, 0) @@ -293,8 +291,7 @@ function parse_demand_rates(d::Dict, year::Int; bigM=1.0e8, time_steps_per_hour: end if haskey(d, "demandratestructure") - scrub_urdb_tiers!(d["demandratestructure"]) - n_tou_demand_tiers = get_num_tiers(d["demandratestructure"]) + n_tou_demand_tiers = scrub_urdb_tiers!(d["demandratestructure"]) ratchet_time_steps, tou_demand_rates, tou_demand_tier_limits = parse_urdb_tou_demand(d, year=year, n_tiers=n_tou_demand_tiers, time_steps_per_hour=time_steps_per_hour) else tou_demand_tier_limits = Array{Float64,2}(undef, 0, 0) @@ -336,21 +333,6 @@ function scrub_urdb_tiers!(A::Array) end end end -end - - -""" -get_num_tiers(d::Dict) - - get maximum number of demand tiers in any period from scrubbed demand rate structure - returns n_tiers::Int -""" -function get_num_tiers(A::Array) - if length(A) == 0 - return 0 - end - len_tiers = Int[length(r) for r in A] - n_tiers = maximum(len_tiers) return n_tiers end From f1301b2537993b2230d17af0ea71ca0a02b73166 Mon Sep 17 00:00:00 2001 From: Zolan Date: Tue, 11 Jun 2024 11:53:48 -0600 Subject: [PATCH 26/31] add notes for dimension of tier limits --- src/core/electric_tariff.jl | 6 +++--- src/core/urdb.jl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/electric_tariff.jl b/src/core/electric_tariff.jl index 3c75b08ee..b18049a26 100644 --- a/src/core/electric_tariff.jl +++ b/src/core/electric_tariff.jl @@ -8,17 +8,17 @@ """ struct ElectricTariff energy_rates::AbstractArray{Float64, 2} # gets a second dim with tiers - energy_tier_limits::AbstractArray{Float64,2} + energy_tier_limits::AbstractArray{Float64,2} # month X tier n_energy_tiers::Int monthly_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers time_steps_monthly::AbstractArray{AbstractArray{Int64,1},1} # length = 0 or 12 - monthly_demand_tier_limits::AbstractArray{Float64,2} + monthly_demand_tier_limits::AbstractArray{Float64,2} # month X tier n_monthly_demand_tiers::Int tou_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers tou_demand_ratchet_time_steps::AbstractArray{AbstractArray{Int64,1},1} # length = n_tou_demand_ratchets - tou_demand_tier_limits::AbstractArray{Float64,2} + tou_demand_tier_limits::AbstractArray{Float64,2} # ratchet X tier n_tou_demand_tiers::Int demand_lookback_months::AbstractArray{Int,1} diff --git a/src/core/urdb.jl b/src/core/urdb.jl index 92f7c7bce..aea0dcc83 100644 --- a/src/core/urdb.jl +++ b/src/core/urdb.jl @@ -12,15 +12,15 @@ Contains some of the data for ElectricTariff """ struct URDBrate energy_rates::Array{Float64,2} # time X tier - energy_tier_limits::Array{Float64,2} + energy_tier_limits::Array{Float64,2} # month X tier n_energy_tiers::Int n_monthly_demand_tiers::Int - monthly_demand_tier_limits::Array{Float64,2} + monthly_demand_tier_limits::Array{Float64,2} # month X tier monthly_demand_rates::Array{Float64,2} # month X tier n_tou_demand_tiers::Int - tou_demand_tier_limits::Array{Float64,2} + tou_demand_tier_limits::Array{Float64,2} # ratchet X tier tou_demand_rates::Array{Float64,2} # ratchet X tier tou_demand_ratchet_time_steps::Array{Array{Int64,1},1} # length = n_tou_demand_ratchets From ccfcb512169f7927f48294416176dddd81229e3a Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Tue, 11 Jun 2024 12:04:47 -0600 Subject: [PATCH 27/31] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf3b40b4d..a5eb1c918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ Classify the change according to the following categories: - Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits - Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs - Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits -- Index utility rate demand and energy tier limits on month in addition to tier +- Index utility rate demand and energy tier limits on month and/or ratchet in addition to tier. This allows for the inclusion of multi-tiered energy and demand rates in which the rates may vary by month or ratchet, whereas previously only the maximum tier limit was used. ## v0.47.1 From 43b01a16b051d154fc774da9e55bbf9e02b5e35e Mon Sep 17 00:00:00 2001 From: Bill Becker Date: Wed, 26 Jun 2024 13:09:34 -0600 Subject: [PATCH 28/31] Improve error message for 0 thermal effic --- src/core/chp.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/chp.jl b/src/core/chp.jl index 7b10601e0..b4a9d08eb 100644 --- a/src/core/chp.jl +++ b/src/core/chp.jl @@ -525,8 +525,11 @@ function get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_loa therm_effic = thermal_efficiency end if therm_effic == 0.0 + if prime_mover == "micro_turbine" && isnothing(size_class) + size_class = "any" + end throw(@error("Error trying to calculate heuristic CHP size based on average thermal load because the - thermal efficiency of prime mover $prime_mover for generating $hot_water_or_steam is 0.0")) + thermal efficiency of prime_mover $prime_mover (size_class $size_class) for generating $hot_water_or_steam is 0.0")) end elec_effic = prime_mover_defaults_all[prime_mover]["electric_efficiency_full_load"][size_class+1] avg_heating_thermal_load_mmbtu_per_hr = avg_boiler_fuel_load_mmbtu_per_hour * boiler_effic From 3a0410d272a47b8b51e6f6282427dc7a64a0a6ae Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Wed, 3 Jul 2024 13:11:55 -0600 Subject: [PATCH 29/31] Update CHANGELOG.md --- CHANGELOG.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f98db506..6688dee1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,14 +23,13 @@ Classify the change according to the following categories: ### Deprecated ### Removed -## Develop - 2024-06-05 +## v0.47.2 ### Fixed -- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits -- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs -- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits +- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits. +- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs. +- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits. - Index utility rate demand and energy tier limits on month and/or ratchet in addition to tier. This allows for the inclusion of multi-tiered energy and demand rates in which the rates may vary by month or ratchet, whereas previously only the maximum tier limit was used. - ## v0.47.1 ### Fixed - Type issue with `CoolingLoad` monthly energy input From 33ec7df51eab05380b4cdf04924c106f5443358c Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Wed, 3 Jul 2024 13:12:09 -0600 Subject: [PATCH 30/31] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e3e179ff8..7e492ee02 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.47.1" +version = "0.47.2" [deps] ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" From 4ba2d47123ac730d09d461b79deab5db19a11a65 Mon Sep 17 00:00:00 2001 From: Alex Zolan Date: Fri, 5 Jul 2024 15:49:57 -0600 Subject: [PATCH 31/31] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6688dee1e..4c307765d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ Classify the change according to the following categories: - Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs. - Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits. - Index utility rate demand and energy tier limits on month and/or ratchet in addition to tier. This allows for the inclusion of multi-tiered energy and demand rates in which the rates may vary by month or ratchet, whereas previously only the maximum tier limit was used. +### Added +- Added thermal efficiency as input to chp defaults function. ## v0.47.1 ### Fixed