Skip to content

Reconcile total site load #456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5df7222
incorporate electrified thermal loads in AnnualEleckWh
zolanaj Nov 19, 2024
685d64f
rm node reference from AnnualEleckWh calculation
zolanaj Nov 19, 2024
86b9fd5
Merge branch 'gridRE-dev' into reconcile-total-site-load
zolanaj Nov 19, 2024
03bc1ad
Merge branch 'gridRE-dev' into reconcile-total-site-load
zolanaj Nov 20, 2024
c0dc1bd
Merge branch 'gridRE-dev' into reconcile-total-site-load
zolanaj Nov 26, 2024
af1c35a
Merge remote-tracking branch 'origin/gridRE-dev' into reconcile-total…
zolanaj Jan 28, 2025
bc1ea5f
rm electric heater and ASHP from surplus calculation
zolanaj Jan 28, 2025
1d257d1
add new electric load result that includes converted thermal loads
zolanaj Jan 28, 2025
a901eb2
update RE fraction tests
zolanaj Jan 28, 2025
94615d0
update new result annual_kwh_with_thermal_conversion
zolanaj Jan 28, 2025
c9a68f0
rm result annual_kwh_with_thermal_conversion
zolanaj Jan 29, 2025
ba03fb1
new result Site.electric_load_converted_from_thermal_kwh
zolanaj Jan 29, 2025
756dd7a
update RE tests
zolanaj Jan 29, 2025
75cbde1
rm supplementary thermal from RE calc (double-counted)
zolanaj Feb 7, 2025
136dbc4
Let AnnualREHeatkWh and AnnualHeatkWh be expressions in all cases
zolanaj Feb 7, 2025
ce694d9
adjust site electric load for cooling
zolanaj Feb 7, 2025
ae5dfd2
update sets of RE Heat to be only fuel-burnign techs
zolanaj Feb 7, 2025
1499d3c
update comments to describe end-use consumed heat
zolanaj Feb 7, 2025
f8d1115
update RE Heat calc to include accounting of storage losses
zolanaj Feb 7, 2025
8bd8c93
include cooling loads in converted values
zolanaj Feb 7, 2025
b16e4ff
make expressions in site results model objects
zolanaj Feb 7, 2025
d89da90
fix storage loss calcs
zolanaj Feb 7, 2025
0bc9ad7
change new results field to annual_electric_load_with_thermal_convers…
zolanaj Feb 10, 2025
efca6e2
use if statements to avoid NaN values when storage loss = 0
zolanaj Feb 10, 2025
067c389
update tests to use new results measure
zolanaj Feb 10, 2025
4fc40d6
rm commented TODO on reconciliation of electrified thermal loads
zolanaj Feb 10, 2025
426c872
Update CHANGELOG.md
zolanaj Feb 10, 2025
be74d5f
Update docstrings in site.jl
zolanaj Feb 11, 2025
3ed47ce
Update docstrings in site.jl
zolanaj Feb 11, 2025
5e527d6
move total elec to ElectricLoad and describe outputs
adfarth Feb 11, 2025
9894d82
Merge branch 'gridRE-dev' into reconcile-total-site-load
adfarth Feb 11, 2025
0d31aa9
no annual_electric_load_with_thermal_conversions_kwh for multinode
adfarth Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## reconcile-total-site-load
### Added
- Added new result **ElectricLoad** **annual_electric_load_with_thermal_conversions_kwh**` which calculates end-use electrical load after including electric consumption by heaters and chillers
### Fixed
- Updated the expression `m[:AnnualEleckWh]` to include electrified thermal loads
- Updated expressions `m[AnnualREHeatkWh]` and `AnnualHeatkWh` so that only non-electrified thermal loads are included and storage losses are proportional to the contribution of fuel-burning technologies to charging storage

## gridRE-dev
### Added
- Add the following inputs to account for the clean or renewable energy fraction of grid-purchased electricity:
Expand Down
9 changes: 5 additions & 4 deletions src/constraints/renewable_energy_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ function add_re_elec_calcs(m,p)
# input electric load
sum(p.s.electric_load.loads_kw[ts] for ts in p.time_steps_with_grid)
+ sum(p.s.electric_load.critical_loads_kw[ts] for ts in p.time_steps_without_grid)
# tech electric loads #TODO: Uncomment and address any double counting with AnnualHeatkWh
# + sum(m[:dvCoolingProduction][t,ts] for t in p.ElectricChillers, ts in p.time_steps )/ p.ElectricChillerCOP # electric chiller elec load
# + sum(m[:dvCoolingProduction][t,ts] for t in p.AbsorptionChillers, ts in p.time_steps )/ p.AbsorptionChillerElecCOP # absorportion chiller elec load
# + sum(p.GHPElectricConsumed[g,ts] * m[:binGHP][g] for g in p.GHPOptions, ts in p.time_steps) # GHP elec load
- sum( p.s.cooling_load.loads_kw_thermal[ts] / p.cooling_cop["ExistingChiller"][ts] for ts in p.time_steps)
# tech electric loads from thermal techs
+ sum(m[:dvCoolingProduction][t, ts] / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling,p.techs.ghp), ts in p.time_steps)
+ sum(m[:dvHeatingProduction][t, q, ts] / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater, ts in p.time_steps)
+ sum(p.ghp_electric_consumption_kw[g,ts] * m[:binGHP][g] for g in p.ghp_options, ts in p.time_steps)
)
)
nothing
Expand Down
20 changes: 12 additions & 8 deletions src/results/electric_load.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# REopt®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE.
"""
`ElectricLoad` results keys:
- `load_series_kw` vector of site load in every time step. Does not (currently) include electric load for any new heating or cool techs.
- `critical_load_series_kw` vector of site critical load in every time step
- `annual_calculated_kwh` sum of the `load_series_kw`
- `offgrid_load_met_series_kw` vector of electric load met by generation techs, for off-grid scenarios only
- `offgrid_load_met_fraction` percentage of total electric load met on an annual basis, for off-grid scenarios only
- `offgrid_annual_oper_res_required_series_kwh` , total operating reserves required (for load and techs) on an annual basis, for off-grid scenarios only
- `offgrid_annual_oper_res_provided_series_kwh` , total operating reserves provided on an annual basis, for off-grid scenarios only
- `load_series_kw` # vector of BAU site load in every time step. Does not include electric load for any new heating or cooling techs.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zolanaj I added these descriptions. If you have a second to review that is welcome!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look good, thank you @adfarth! I'll merge the PR once tests are confirmed to pass.

- `critical_load_series_kw` # vector of site critical load in every time step
- `annual_calculated_kwh` # sum of the `load_series_kw`. Does not include electric load for any new heating or cooling techs.
- `annual_electric_load_with_thermal_conversions_kwh` # Total end-use electrical load, including electrified heating and cooling end-use load
- `offgrid_load_met_series_kw` # vector of electric load met by generation techs, for off-grid scenarios only
- `offgrid_load_met_fraction` # percentage of total electric load met on an annual basis, for off-grid scenarios only
- `offgrid_annual_oper_res_required_series_kwh` # total operating reserves required (for load and techs) on an annual basis, for off-grid scenarios only
- `offgrid_annual_oper_res_provided_series_kwh` # total operating reserves provided on an annual basis, for off-grid scenarios only

!!! note "'Series' and 'Annual' energy outputs are average annual"
REopt performs load balances using average annual production values for technologies that include degradation.
Expand All @@ -25,7 +26,10 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic
r["annual_calculated_kwh"] = round(
sum(r["load_series_kw"]) * p.hours_per_time_step, digits=2
)


# Aggregation of all end-use electrical loads (including electrified heating and cooling).
r["annual_electric_load_with_thermal_conversions_kwh"] = round(value(m[:AnnualEleckWh]), digits=2)

if p.s.settings.off_grid_flag
@expression(m, LoadMet[ts in p.time_steps_without_grid], p.s.electric_load.critical_loads_kw[ts] * m[Symbol("dvOffgridLoadServedFraction"*_n)][ts])
r["offgrid_load_met_series_kw"] = round.(value.(LoadMet).data, digits=6)
Expand Down
61 changes: 37 additions & 24 deletions src/results/site.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ Adds the Site results to the dictionary passed back from `run_reopt` using the s

Site results:
- `annual_onsite_renewable_electricity_kwh` # renewable electricity from on-site renewable electricity-generating technologies (including fuel-burning technologies)
- `onsite_renewable_electricity_fraction_of_elec_load`
- `onsite_and_grid_renewable_electricity_fraction_of_elec_load`
- `onsite_renewable_energy_fraction_of_total_load`
- `onsite_and_grid_renewable_energy_fraction_of_total_load`
- `onsite_renewable_electricity_fraction_of_elec_load` # Portion of electricity consumption (incl. electric heating/cooling loads) that is derived from on-site renewable resource generation
- `onsite_and_grid_renewable_electricity_fraction_of_elec_load` # "Calculation is the same as onsite_renewable_electricity_fraction_of_elec_load, but additionally includes the renewable energy content of grid-purchased electricity, accounting for any battery efficiency losses
- `onsite_renewable_energy_fraction_of_total_load` # Portion of total annual energy consumption (including non-electric heating/cooling loads) that is derived from on-site renewable resource generation
- `onsite_and_grid_renewable_energy_fraction_of_total_load` # Calculation is the same as onsite_renewable_energy_fraction_of_total_load, but additionally includes the renewable energy content of grid-purchased electricity, accounting for any battery efficiency losses
- `annual_emissions_tonnes_CO2` # Average annual total tons of emissions associated with the site's grid-purchased electricity and on-site fuel consumption.
- `annual_emissions_tonnes_NOx` # Average annual total tons of emissions associated with the site's grid-purchased electricity and on-site fuel consumption.
- `annual_emissions_tonnes_SO2` # Average annual total tons of emissions associated with the site's grid-purchased electricity and on-site fuel consumption.
Expand Down Expand Up @@ -38,7 +38,6 @@ calculated in combine_results function if BAU scenario is run:
By default, REopt uses marginal emissions rates for grid-purchased electricity. Marginal emissions rates are most appropriate for reporting a change in emissions (avoided or increased) rather than emissions totals.
It is therefore recommended that emissions results from REopt (using default marginal emissions rates) be reported as the difference in emissions between the optimized and BAU case.


"""
function add_site_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="")
r = Dict{String, Any}()
Expand All @@ -47,7 +46,7 @@ function add_site_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="")
r["annual_onsite_renewable_electricity_kwh"] = round(value(m[:AnnualOnsiteREEleckWh]), digits=2)
r["onsite_renewable_electricity_fraction_of_elec_load"] = round(value(m[:AnnualOnsiteREEleckWh])/value(m[:AnnualEleckWh]), digits=4)
r["onsite_and_grid_renewable_electricity_fraction_of_elec_load"] = round((value(m[:AnnualOnsiteREEleckWh]) + value(m[:AnnualGridREEleckWh])) /value(m[:AnnualEleckWh]), digits=4)

# total renewable energy
add_re_tot_calcs(m,p)
r["onsite_renewable_energy_fraction_of_total_load"] = round(value(m[:AnnualOnsiteRETotkWh])/value(m[:AnnualTotkWh]), digits=4)
Expand Down Expand Up @@ -88,9 +87,7 @@ Function to calculate annual energy (electricity plus heat) demand and annual en
#Renewable heat calculations and totalling heat/electric emissions
function add_re_tot_calcs(m::JuMP.AbstractModel, p::REoptInputs)

AnnualREHeatkWh = 0
AnnualHeatkWh = 0
if !isempty(union(p.techs.heating, p.techs.chp))
if !isempty(intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)))
# TODO: When steam turbine implemented, uncomment code below, replacing p.TechCanSupplySteamTurbine, p.STElecOutToThermInRatio, p.STThermOutToThermInRatio with new names
# # Steam turbine RE heat calculations
# if isempty(p.steam)
Expand All @@ -116,28 +113,44 @@ function add_re_tot_calcs(m::JuMP.AbstractModel, p::REoptInputs)
# )
# end

# Renewable heat (RE steam/hot water heat that is not being used to generate electricity)
AnnualREHeatkWh = @expression(m,p.hours_per_time_step*(
sum(m[:dvHeatingProduction][t,q,ts] * p.tech_renewable_energy_fraction[t] for t in setdiff(union(p.techs.heating, p.techs.chp), p.techs.ghp), q in p.heating_loads, ts in p.time_steps) #total RE heat generation (excl steam turbine, GHP)
- sum(m[:dvProductionToWaste][t,q,ts]* p.tech_renewable_energy_fraction[t] for t in p.techs.chp, q in p.heating_loads, ts in p.time_steps) #minus CHP waste heat
+ sum(m[:dvSupplementaryThermalProduction][t,ts] * p.tech_renewable_energy_fraction[t] for t in p.techs.chp, ts in p.time_steps) # plus CHP supplemental firing thermal generation
- sum(m[:dvProductionToStorage][b,t,ts]*p.tech_renewable_energy_fraction[t]*(1-p.s.storage.attr[b].charge_efficiency*p.s.storage.attr[b].discharge_efficiency) for t in setdiff(union(p.techs.heating, p.techs.chp), p.techs.ghp), b in p.s.storage.types.thermal, ts in p.time_steps) #minus thermal storage losses, note does not account for p.DecayRate
#To account for hot storage losses and the RE contributions from fuel-fired sources when calculating end-use load, these expressions are used.
m[:AnnualHeatContributionToStorage] = @expression(m, sum(m[:dvProductionToStorage][b,t,ts] for t in union(p.techs.heating, p.techs.chp), b in p.s.storage.types.hot, ts in p.time_steps))
m[:AnnualREFBToHotStoragekWh] = @expression(m, sum(m[:dvProductionToStorage][b,t,ts]*p.tech_renewable_energy_fraction[t] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), b in p.s.storage.types.hot, ts in p.time_steps))
m[:AnnualFBToHotStoragekWh] = @expression(m, sum(m[:dvProductionToStorage][b,t,ts] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), b in p.s.storage.types.hot, ts in p.time_steps))
if value(m[:AnnualFBToHotStoragekWh]) > 0.0
m[:FBStorageDeliveryREFraction] = @expression(m, m[:AnnualREFBToHotStoragekWh] / m[:AnnualFBToHotStoragekWh])
else
m[:FBStorageDeliveryREFraction] = @expression(m, 0.0)
end
m[:AnnualHotStorageLosses] = @expression(m, m[:AnnualFBToHotStoragekWh] - sum(m[:dvDischargeFromStorage][b, ts] for b in p.s.storage.types.hot, ts in p.time_steps))
if value(m[:AnnualHeatContributionToStorage]) > 0.0
m[:FBToHotStorageFraction] = @expression(m, m[:AnnualFBToHotStoragekWh] / m[:AnnualHeatContributionToStorage])
else
m[:FBToHotStorageFraction] = @expression(m, 0.0)
end
# End-use consumed heating load from renewable, fuel-fired sources (electrified heat is addressed in the renewable electricity calculation)
m[:AnnualREHeatkWh] = @expression(m,p.hours_per_time_step*(
sum(m[:dvHeatingProduction][t,q,ts] * p.tech_renewable_energy_fraction[t] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), q in p.heating_loads, ts in p.time_steps) #total RE end-use heat generation from fuel sources
- sum(m[:dvProductionToWaste][t,q,ts]* p.tech_renewable_energy_fraction[t] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), q in p.heating_loads, ts in p.time_steps) #minus waste heat
- m[:FBStorageDeliveryREFraction] * m[:FBToHotStorageFraction] * m[:AnnualHotStorageLosses] # RE weight times hot storage loss attributable to FB techs
)
# - AnnualRESteamToSteamTurbine # minus RE steam feeding steam turbine, adjusted by p.hours_per_time_step
# + AnnualSteamTurbineREThermOut #plus steam turbine RE generation, adjusted for storage losses, adjusted by p.hours_per_time_step (not included in first line because p.tech_renewable_energy_fraction for SteamTurbine is 0)
)

# Total heat (steam/hot water heat that is not being used to generate electricity)
AnnualHeatkWh = @expression(m,p.hours_per_time_step*(
sum(m[:dvHeatingProduction][t,q,ts] for t in setdiff(union(p.techs.heating, p.techs.chp), p.techs.ghp), q in p.heating_loads, ts in p.time_steps) #total heat generation (need to see how GHP fits into this)
- sum(m[:dvProductionToWaste][t,q,ts] for t in p.techs.chp, q in p.heating_loads, ts in p.time_steps) #minus CHP waste heat
+ sum(m[:dvSupplementaryThermalProduction][t,ts] for t in p.techs.chp, ts in p.time_steps) # plus CHP supplemental firing thermal generation
- sum(m[:dvProductionToStorage][b,t,ts]*(1-p.s.storage.attr[b].charge_efficiency*p.s.storage.attr[b].discharge_efficiency) for t in setdiff(union(p.techs.heating, p.techs.chp), p.techs.ghp), b in p.s.storage.types.thermal, ts in p.time_steps) #minus thermal storage losses
# End-use consumed heating load from fuel-fired sources (electrified heat is addressed in the renewable electricity calculation)
m[:AnnualHeatkWh] = @expression(m,p.hours_per_time_step*(
sum(m[:dvHeatingProduction][t,q,ts] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), q in p.heating_loads, ts in p.time_steps) #total end-use heat generation from fuel sources
- sum(m[:dvProductionToWaste][t,q,ts] for t in intersect(p.techs.fuel_burning, union(p.techs.heating, p.techs.chp)), q in p.heating_loads, ts in p.time_steps) #minus waste heat
- m[:FBToHotStorageFraction] * m[:AnnualHotStorageLosses] # hot storage loss attributable to FB techs
)
# - AnnualSteamToSteamTurbine # minus steam going to SteamTurbine; already adjusted by p.hours_per_time_step
)
else
m[:AnnualREHeatkWh] = @expression(m, 0.0)
m[:AnnualHeatkWh] = @expression(m, 0.0)
end
m[:AnnualOnsiteRETotkWh] = @expression(m, m[:AnnualOnsiteREEleckWh] + AnnualREHeatkWh)
m[:AnnualTotkWh] = @expression(m, m[:AnnualEleckWh] + AnnualHeatkWh) # TODO: ensure no double counting once AnnualEleckWh accounts for electric heating and cooling loads
m[:AnnualOnsiteRETotkWh] = @expression(m, m[:AnnualOnsiteREEleckWh] + m[:AnnualREHeatkWh])
m[:AnnualTotkWh] = @expression(m, m[:AnnualEleckWh] + m[:AnnualHeatkWh])
nothing
end
end
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2372,10 +2372,10 @@ else # run HiGHS tests
@test results["Site"]["lifecycle_emissions_tonnes_SO2"] ≈ results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_SO2"] + results["ElectricUtility"]["lifecycle_emissions_tonnes_SO2"] rtol=0.01 # rounding causes difference
@test results["Site"]["lifecycle_emissions_tonnes_PM25"] ≈ results["Site"]["lifecycle_emissions_from_fuelburn_tonnes_PM25"] + results["ElectricUtility"]["lifecycle_emissions_tonnes_PM25"] rtol=0.001
@test results["Site"]["annual_onsite_renewable_electricity_kwh"] ≈ results["PV"]["annual_energy_produced_kwh"] + inputs["CHP"]["fuel_renewable_energy_fraction"] * results["CHP"]["annual_electric_production_kwh"] atol=1
@test results["Site"]["onsite_renewable_electricity_fraction_of_elec_load"] ≈ results["Site"]["annual_onsite_renewable_electricity_kwh"] / results["ElectricLoad"]["annual_calculated_kwh"] rtol=0.001 #0.044285 atol=1e-4
@test results["Site"]["onsite_renewable_electricity_fraction_of_elec_load"] ≈ results["Site"]["annual_onsite_renewable_electricity_kwh"] / results["ElectricLoad"]["annual_electric_load_with_thermal_conversions_kwh"] rtol=0.001
annual_RE_kwh = inputs["CHP"]["fuel_renewable_energy_fraction"] * results["CHP"]["annual_thermal_production_mmbtu"] * REopt.KWH_PER_MMBTU + results["Site"]["annual_onsite_renewable_electricity_kwh"]
annual_heat_kwh = (results["CHP"]["annual_thermal_production_mmbtu"] + results["ExistingBoiler"]["annual_thermal_production_mmbtu"]) * REopt.KWH_PER_MMBTU
@test results["Site"]["onsite_renewable_energy_fraction_of_total_load"] ≈ annual_RE_kwh / (annual_heat_kwh + results["ElectricLoad"]["annual_calculated_kwh"]) rtol=0.001
@test results["Site"]["onsite_renewable_energy_fraction_of_total_load"] ≈ annual_RE_kwh / (annual_heat_kwh + results["ElectricLoad"]["annual_electric_load_with_thermal_conversions_kwh"]) rtol=0.001
end
end
end
Expand Down
Loading