Skip to content

Commit

Permalink
Merge pull request #390 from NREL/tou-demand-reshape-bug
Browse files Browse the repository at this point in the history
3 tiered TOU demand bugs
  • Loading branch information
zolanaj authored May 7, 2024
2 parents 3c6db71 + 8efd7d7 commit bc047ce
Show file tree
Hide file tree
Showing 8 changed files with 1,333 additions and 19 deletions.
24 changes: 14 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ Classify the change according to the following categories:
- Added new variables **dvHeatToStorage** and **dvHeatFromStorage** which are indexed on `p.heating_loads` and added reconciliation constraints so that **dvProductionToStorage** and **dvDischargeFromStorage** maintain their relationship to state of charge for Hot thermal energy storage.
- In `src/constraints/thermal_tech_constraints.jl`, added function **no_existing_boiler_production** which prevents ExistingBoiler from producing heat in optimized (non-BAU) scenarios
- for all heating techs and CHP, added fields **can_serve_space_heating**, **can_serve_dhw**, and **can_serve_process_heat** in core structs and added new results fields **thermal_to_dhw_load_series_mmbtu_per_hour**, **thermal_to_space_heating_load_series_mmbtu_per_hour**, and **thermal_to_process_heat_load_series_mmbtu_per_hour**
- in `src/core/techs.jl`, added new sets **ghp_techs**, **cooling_techs**, **techs_can_serve_space_heating**, **techs_can_serve_dhw**, and **techs_can_serve_process_heat**
- in `src/core/reopt_inputs.jl`, added new fields **heating_loads**, **heating_loads_kw**, **heating_loads_served_by_tes**, and **absorption_chillers_using_heating_load** to the REoptInputs and BAUInputs structs. in the math, new set `p.heating_loads` has index q (to represent "qualities" of heat).
- In `src/core/techs.jl`, added new sets **ghp_techs**, **cooling_techs**, **techs_can_serve_space_heating**, **techs_can_serve_dhw**, and **techs_can_serve_process_heat**
- In `src/core/reopt_inputs.jl`, added new fields **heating_loads**, **heating_loads_kw**, **heating_loads_served_by_tes**, and **absorption_chillers_using_heating_load** to the REoptInputs and BAUInputs structs. in the math, new set `p.heating_loads` has index q (to represent "qualities" of heat).
- In `src/core/heating_cooling_loads.jl`, added new struct **ProcessHeatLoad**
- In `src/core/scenario.jl`, added new field **process_heat_load**
- In `src/mpc/inputs.jl`, added new field **heating_loads**
Expand All @@ -40,16 +40,20 @@ Classify the change according to the following categories:
- In `results/heating_cooling_load.jl`, added new fields **process_heat_thermal_load_series_mmbtu_per_hour**, **process_heat_boiler_fuel_load_series_mmbtu_per_hour**, **annual_calculated_process_heat_thermal_load_mmbtu**, and **annual_calculated_process_heat_boiler_fuel_load_mmbtu** to HeatingLoad results, with sum heating loads now including process heat
### Changed
- Change the way we determine which dataset to utilize in the PVWatts API call. Previously, we utilized defined lat-long bounds to determine if "nsrdb" or "intl" data should be used in PVWatts call. Now, we call the Solar Dataset Query API (v2) (https://developer.nrel.gov/docs/solar/data-query/v2/) to determine the dataset to use, and include "tmy3" as an option, as this is currently the best-available data for many locations in Alaska.
- refactored **dvThermalProduction** to be separated in **dvCoolingProduction** and **dvHeatingProduction** with **dvHeatingProduction** now indexed on `p.heating_loads`
- refactored heating load balance constraints so that a separate flow balance is reconciled for each heating load in `p.heating_loads`
- renamed **dvThermalProductionYIntercept** to **dvHeatingProductionYIntercept**
- divided **ThermalStorage** into **HotThermalStorage** and **ColdThermalStorage** as the former now has attributes related to the compatible heat loads as input or output.
- changed technologies included **dvProductionToWaste** to all heating techs. NOTE: this variable is forced to zero to allow steam turbine tests to pass, but I believe that waste heat should be allowed for the turbine. A TODO is in place to review this commit (a406cc5df6e4a27b56c92815c35d04815904e495).
- changed test values and tolerances for CHP Sizing test.
- Refactored **dvThermalProduction** to be separated in **dvCoolingProduction** and **dvHeatingProduction** with **dvHeatingProduction** now indexed on `p.heating_loads`
- Refactored heating load balance constraints so that a separate flow balance is reconciled for each heating load in `p.heating_loads`
- Renamed **dvThermalProductionYIntercept** to **dvHeatingProductionYIntercept**
- Divided **ThermalStorage** into **HotThermalStorage** and **ColdThermalStorage** as the former now has attributes related to the compatible heat loads as input or output.
- Changed technologies included **dvProductionToWaste** to all heating techs. NOTE: this variable is forced to zero to allow steam turbine tests to pass, but I believe that waste heat should be allowed for the turbine. A TODO is in place to review this commit (a406cc5df6e4a27b56c92815c35d04815904e495).
- Changed test values and tolerances for CHP Sizing test.
- Updated test sets "Emissions and Renewable Energy Percent" and "Minimize Unserved Load" to decrease computing time.
- Test for tiered TOU demand rates in `test/runtests.jl`
### Fixed
- added a constraint in `src/constraints/steam_turbine_constraints.jl` that allows for heat loads to reconcile when thermal storage is paired with a SteamTurbine.
- fixed a bug in which net-metering system size limits could be exceeded while still obtaining the net-metering benefit due to a large "big-M".
- Added a constraint in `src/constraints/steam_turbine_constraints.jl` that allows for heat loads to reconcile when thermal storage is paired with a SteamTurbine.
- Fixed a bug in which net-metering system size limits could be exceeded while still obtaining the net-metering benefit due to a large "big-M".
- Fixed a reshape call in function `parse_urdb_tou_demand` that incorrectly assumed row major instead of column major ordering
- Fixed a loop range in function `parse_urdb_tou_demand` that incorrectly started at 0 instead of 1
- Added the missing tier index when accessing `p.s.electric_tariff.tou_demand_rates` in function `add_elec_utility_expressions`

## v0.45.0
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/electric_utility_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ function add_elec_utility_expressions(m, p; _n="")

if !isempty(p.s.electric_tariff.tou_demand_rates)
m[Symbol("DemandTOUCharges"*_n)] = @expression(m,
p.pwf_e * sum( p.s.electric_tariff.tou_demand_rates[r] * m[Symbol("dvPeakDemandTOU"*_n)][r, tier]
p.pwf_e * sum( p.s.electric_tariff.tou_demand_rates[r, tier] * m[Symbol("dvPeakDemandTOU"*_n)][r, tier]
for r in p.ratchets, tier in 1:p.s.electric_tariff.n_tou_demand_tiers)
)
else
Expand Down
4 changes: 2 additions & 2 deletions src/core/urdb.jl
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_
n_ratchets = 0 # counter

for month in range(1, stop=12)
for period in range(0, stop=n_periods)
for period in range(1, stop=n_periods)
time_steps = get_tou_demand_steps(d, year=year, month=month, period=period-1, time_steps_per_hour=time_steps_per_hour)
if length(time_steps) > 0 # can be zero! not every month contains same number of periods
n_ratchets += 1
Expand All @@ -457,7 +457,7 @@ function parse_urdb_tou_demand(d::Dict; year::Int, n_tiers::Int, time_steps_per_
end
end
end
rates = reshape(rates_vec, (:, n_tiers)) # Array{Float64,2}
rates = reshape(rates_vec, (n_tiers, :))' # Array{Float64,2}
ratchet_time_steps = convert(Array{Array{Int64,1},1}, ratchet_time_steps)
return ratchet_time_steps, rates
end
Expand Down
16 changes: 13 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ else # run HiGHS tests

@testset "Tiered Energy" begin
m = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false))
results = run_reopt(m, "./scenarios/tiered_rate.json")
results = run_reopt(m, "./scenarios/tiered_energy_rate.json")
@test results["ElectricTariff"]["year_one_energy_cost_before_tax"] 2342.88
@test results["ElectricUtility"]["annual_energy_supplied_kwh"] 24000.0 atol=0.1
@test results["ElectricLoad"]["annual_calculated_kwh"] 24000.0 atol=0.1
Expand Down Expand Up @@ -1186,16 +1186,26 @@ else # run HiGHS tests
@test results["PV"]["size_kw"] p.s.pvs[1].existing_kw
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))
results = run_reopt(model, data)
max_demand = data["ElectricLoad"]["annual_kwh"] / 8760
tier1_max = data["ElectricTariff"]["urdb_response"]["demandratestructure"][1][1]["max"]
tier1_rate = data["ElectricTariff"]["urdb_response"]["demandratestructure"][1][1]["rate"]
tier2_rate = data["ElectricTariff"]["urdb_response"]["demandratestructure"][1][2]["rate"]
expected_demand_charges = 12 * (tier1_max * tier1_rate + (max_demand - tier1_max) * tier2_rate)
@test results["ElectricTariff"]["year_one_demand_cost_before_tax"] expected_demand_charges atol=1
end

# # tiered monthly demand rate TODO: expected results?
# m = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false))
# data = JSON.parsefile("./scenarios/tiered_rate.json")
# data = JSON.parsefile("./scenarios/tiered_energy_rate.json")
# data["ElectricTariff"]["urdb_label"] = "59bc22705457a3372642da67"
# s = Scenario(data)
# inputs = REoptInputs(s)
# results = run_reopt(m, inputs)

# TODO test for tiered TOU demand rates
end

@testset "EASIUR" begin
Expand Down
File renamed without changes.
Loading

0 comments on commit bc047ce

Please sign in to comment.