Skip to content

Commit

Permalink
simplify constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
adfarth committed Nov 14, 2024
1 parent 8571808 commit 60cdeae
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 84 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ Classify the change according to the following categories:


## gridRE-dev
### Added
- Added ElectricUtility inputs to account for the clean or renewable energy fraction of grid-purchased electricity:
- **cambium_cef_metric** to utilize clean energy data from NREL's Cambium database
- **clean_energy_fraction_series** to supply a custom grid clean energy scalar or series
- Added Site input to allow user to choose whether to include grid RE in min max constraints: **include_grid_renewable_electricity_in_min_max_constraints**
### Changed
- Changed name of ElectricUtility input **cambium_metric_col** to **cambium_co2_metric**, to distinguish between the CO2 and clean energy fraction metrics
- Changed name of ElectricUtility **cambium_emissions_region** to **cambium_region**
- Changed name of ElectricUtility **cambium_emissions_region** to **cambium_region**
- Changed name of function (also available as endpoint through REopt API) from **cambium_emissions_profile** to **cambium_profile**

## Develop
### Added
Expand Down
92 changes: 21 additions & 71 deletions src/constraints/renewable_energy_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
add_re_elec_constraints(m,p)
Function to add minimum and/or maximum renewable electricity (as percentage of load) constraints, if specified by user.
Function modified to include cef fraction from the grid in accounting for renewable energy percentage of load.
Includes renewable energy from grid if specified by user.
!!! note
When a single outage is modeled (using outage_start_time_step), renewable electricity calculations account for operations during this outage (e.g., the critical load is used during time_steps_without_grid)
Expand All @@ -12,14 +12,17 @@ Function modified to include cef fraction from the grid in accounting for renewa
#Renewable electricity constraints
function add_re_elec_constraints(m,p)
if !isnothing(p.s.site.renewable_electricity_min_fraction)
@constraint(m, MinREElecCon, m[:AnnualREEleckWh] >= p.s.site.renewable_electricity_min_fraction*m[:AnnualEleckWh])
@constraint(m, MinREElecCon, m[:AnnualOnsiteREEleckWh] +
include_grid_renewable_electricity_in_min_max_constraints * m[:AnnualGridREEleckWh]
>= p.s.site.renewable_electricity_min_fraction*m[:AnnualEleckWh])
end
if !isnothing(p.s.site.renewable_electricity_max_fraction)
@constraint(m, MaxREElecCon, m[:AnnualREEleckWh] <= p.s.site.renewable_electricity_max_fraction*m[:AnnualEleckWh])
@constraint(m, MaxREElecCon, m[:AnnualOnsiteREEleckWh] +
include_grid_renewable_electricity_in_min_max_constraints * m[:AnnualGridREEleckWh]
<= p.s.site.renewable_electricity_max_fraction*m[:AnnualEleckWh])
end
end


"""
add_re_elec_calcs(m,p)
Expand All @@ -35,30 +38,23 @@ function add_re_elec_calcs(m,p)
# TODO: When steam turbine implemented, uncomment code below, replacing p.TechCanSupplySteamTurbine, p.STElecOutToThermInRatio with new names
# # Steam turbine RE elec calculations
# if isempty(p.steam)
# SteamTurbineAnnualREEleckWh = 0
# SteamTurbineAnnualOnsiteREEleckWh = 0
# else
# # Note: SteamTurbine's input p.tech_renewable_energy_fraction = 0 because it is actually a decision variable dependent on fraction of steam generated by RE fuel
# SteamTurbinePercentREEstimate = @expression(m,
# sum(p.tech_renewable_energy_fraction[tst] for tst in p.TechCanSupplySteamTurbine) / length(p.TechCanSupplySteamTurbine)
# )
# # Note: Steam turbine battery losses, curtailment, and exported RE terms are only accurate if all techs that can supply ST
# # have equal RE%, otherwise it is an approximation because the general equation is non linear.
# SteamTurbineAnnualREEleckWh = @expression(m,p.hours_per_time_step * (
# SteamTurbineAnnualOnsiteREEleckWh = @expression(m,p.hours_per_time_step * (
# p.STElecOutToThermInRatio * sum(m[:dvThermalToSteamTurbine][tst,ts]*p.tech_renewable_energy_fraction[tst] for ts in p.time_steps, tst in p.TechCanSupplySteamTurbine) # plus steam turbine RE generation
# - sum(m[:dvProductionToStorage][b,t,ts] * SteamTurbinePercentREEstimate * (1-p.s.storage.attr[b].charge_efficiency*p.s.storage.attr[b].discharge_efficiency) for t in p.steam, b in p.s.storage.types.elec, ts in p.time_steps) # minus battery storage losses from RE from steam turbine
# - sum(m[:dvCurtail][t,ts] * SteamTurbinePercentREEstimate for t in p.steam, ts in p.time_steps) # minus curtailment.
# - (1-p.s.site.include_exported_renewable_electricity_in_total)*sum(m[:dvProductionToGrid][t,u,ts]*SteamTurbinePercentREEstimate for t in p.steam, u in p.export_bins_by_tech[t], ts in p.time_steps) # minus exported RE from steam turbine, if RE accounting method = 0.
# ))
# end

# Function modified to add clean energy fraction to in annual renewable energy calculations

calc_clean_grid_kWh(m, p)

m[:AnnualREEleckWh] = @expression(m, p.hours_per_time_step * (
(p.s.electric_utility.include_grid_clean_energy_in_total ?
sum(m[:grid_clean_energy_series_kw][ts] for ts in p.time_steps) : 0) + # Include grid clean energy fraction conditionally
+ # Clean energy fraction of grid electricity to load and battery
m[:AnnualOnsiteREEleckWh] = @expression(m, p.hours_per_time_step * (
sum(p.production_factor[t,ts] * p.levelization_factor[t] * m[:dvRatedProduction][t,ts] *
p.tech_renewable_energy_fraction[t] for t in p.techs.elec, ts in p.time_steps
) - #total RE elec generation, excl steam turbine
Expand All @@ -72,75 +68,29 @@ function add_re_elec_calcs(m,p)
for t in p.techs.elec, u in p.export_bins_by_tech[t], ts in p.time_steps
) # minus exported RE, if RE accounting method = 0.
)
# + SteamTurbineAnnualREEleckWh # SteamTurbine RE Elec, already adjusted for p.hours_per_time_step
# + SteamTurbineAnnualOnsiteREEleckWh # SteamTurbine RE Elec, already adjusted for p.hours_per_time_step
)
# Note: if battery ends up being allowed to discharge to grid, need to make sure only RE that is being consumed onsite is counted so battery doesn't become a back door for RE to grid.
# Note: calculations currently do not ascribe any renewable energy attribute to grid-purchased electricity

m[:AnnualGridREEleckWh] = @expression(m, p.hours_per_time_step * (
sum(m[:dvGridPurchase][ts, tier] - m[:dvGridToStorage][b, ts] *
(1 - p.s.storage.attr[b].charge_efficiency * p.s.storage.attr[b].discharge_efficiency) *
p.s.electric_utility.clean_energy_fraction_series[ts]
for ts in p.time_steps, tier in 1:p.s.electric_tariff.n_energy_tiers, b in p.s.storage.types.elec
) # clean energy from grid, minus battery efficiency losses
)
)

m[:AnnualEleckWh] = @expression(m,p.hours_per_time_step * (
# 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
# tech electric loads #TODO: Uncomment?
# + 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
)
)
nothing
end

"""
calc_clean_grid_kWh(m, p)
This function calculates the clean energy fraction of the electricity from the electric utility
by multiplying the electricity from the grid used to charge the batteries and the electricity from the grid directly serving the load
by the clean energy fraction series.
Returns:
- clean_energy_fraction: The clean energy fraction of the grid electricity.
"""
function calc_clean_grid_kWh(m, p)
# Calculate the grid electricity used to charge the batteries and directly serve the load
m[:CleanGridToLoad], m[:CleanGridToBatt] = calc_grid_to_load(m, p)

# Calculate the clean energy fraction from the electric utility
m[:grid_clean_energy_series_kw] = @expression(m, [
ts in p.time_steps], (m[:CleanGridToLoad][ts] + m[:CleanGridToBatt][ts]) * p.s.electric_utility.clean_energy_fraction_series[ts]
)
end


"""
calc_grid_to_load(m, p)
This function calculates, for each timestep:
1. The electricity from the grid used to charge the batteries, accounting for battery losses.
2. The electricity from the grid directly serving the load.
Returns:
- CleanGridToLoad: The electricity from the grid directly serving the load.
- CleanGridToBatt: The electricity from the grid used to charge the batteries, accounting for losses.
"""

function calc_grid_to_load(m, p)
if !isempty(p.s.storage.types.elec)
# Calculate the grid to load through the battery, accounting for the battery losses
m[:CleanGridToBatt] = @expression(m, [
ts in p.time_steps], sum(
m[:dvGridToStorage][b, ts] * p.s.storage.attr[b].charge_efficiency * p.s.storage.attr[b].discharge_efficiency
for b in p.s.storage.types.elec)
)
else
m[:CleanGridToBatt] = zeros(length(p.time_steps))
end

# Calculate the grid serving load not through the battery
m[:CleanGridToLoad] = @expression(m, [
ts in p.time_steps], (
sum(m[:dvGridPurchase][ts, tier] for tier in 1:p.s.electric_tariff.n_energy_tiers) -
sum(m[:dvGridToStorage][b, ts] for b in p.s.storage.types.elec)
))

return m[:CleanGridToLoad], m[:CleanGridToBatt]
end
12 changes: 4 additions & 8 deletions src/core/electric_utility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@
### Grid Clean Energy Fraction Inputs ###
cambium_cef_metric::String = "cef_load", # Options = ["cef_load", "cef_gen"] # cef_load is the fraction of generation that is clean, for the generation that is allocated to a region’s end-use load; cef_gen is the fraction of generation that is clean within a region
clean_energy_fraction_series::Union{Real,Array{<:Real,1}} = Float64[], # Utilities renewable energy fraction. Can be scalar or timeseries (aligned with time_steps_per_hour).
include_grid_clean_energy_in_total::Bool=true, # if true, the clean energy fraction of the grid electricity is included in the site's total renewable electricity calculations
clean_energy_fraction_series::Union{Real,Array{<:Real,1}} = Float64[], # Utilities renewable energy fraction. Can be scalar or timeseries (aligned with time_steps_per_hour)
```
!!! note "Outage modeling"
Expand Down Expand Up @@ -139,8 +138,7 @@ struct ElectricUtility
scenarios::Union{Nothing, UnitRange}
net_metering_limit_kw::Real
interconnection_limit_kw::Real
clean_energy_fraction_series::Array{<:Real,1} # Utility renewable energy fraction.
include_grid_clean_energy_in_total::Bool
clean_energy_fraction_series::Array{<:Real,1} # fraction of grid electricity that is clean or renewable

function ElectricUtility(;

Expand Down Expand Up @@ -197,8 +195,7 @@ struct ElectricUtility

### Grid Clean Energy Fraction Inputs ###
cambium_cef_metric::String = "cef_load", # Options = ["cef_load", "cef_gen"] # cef_load is the fraction of generation that is clean, for the generation that is allocated to a region’s end-use load; cef_gen is the fraction of generation that is clean within a region
clean_energy_fraction_series::Union{Real,Array{<:Real,1}} = Float64[], # Utilities renewable energy fraction. Can be scalar or timeseries (aligned with time_steps_per_hour).
include_grid_clean_energy_in_total::Bool=true # if true, the clean energy fraction of the grid electricity is included in the site's renewable electricity calculations
clean_energy_fraction_series::Union{Real,Array{<:Real,1}} = Float64[], # Utilities renewable energy fraction. Can be scalar or timeseries (aligned with time_steps_per_hour)
)

is_MPC = isnothing(latitude) || isnothing(longitude)
Expand Down Expand Up @@ -370,8 +367,7 @@ struct ElectricUtility
scenarios,
net_metering_limit_kw,
interconnection_limit_kw,
is_MPC ? Float64[] : emissions_and_cef_series_dict["clean_energy_fraction_series"],
include_grid_clean_energy_in_total
is_MPC ? Float64[] : emissions_and_cef_series_dict["clean_energy_fraction_series"]
)
end
end
Expand Down
7 changes: 6 additions & 1 deletion src/core/site.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Inputs related to the physical location:
bau_grid_emissions_lb_CO2_per_year::Union{Float64, Nothing} = nothing,
renewable_electricity_min_fraction::Real = 0.0,
renewable_electricity_max_fraction::Union{Float64, Nothing} = nothing,
include_grid_renewable_electricity_in_min_max_constraints::Bool = false,
include_exported_elec_emissions_in_total::Bool = true,
include_exported_renewable_electricity_in_total::Bool = true,
outdoor_air_temperature_degF::Union{Nothing, Array{<:Real,1}} = nothing,
Expand All @@ -37,8 +38,10 @@ mutable struct Site
bau_grid_emissions_lb_CO2_per_year
renewable_electricity_min_fraction
renewable_electricity_max_fraction
include_grid_renewable_electricity_in_min_max_constraints
include_exported_elec_emissions_in_total
include_exported_renewable_electricity_in_total
include_grid_renewable_electricity_in_total
outdoor_air_temperature_degF
node # TODO validate that multinode Sites do not share node numbers? Or just raise warning
function Site(;
Expand All @@ -54,8 +57,10 @@ mutable struct Site
bau_grid_emissions_lb_CO2_per_year::Union{Float64, Nothing} = nothing,
renewable_electricity_min_fraction::Union{Float64, Nothing} = nothing,
renewable_electricity_max_fraction::Union{Float64, Nothing} = nothing,
include_grid_renewable_electricity_in_min_max_constraints::Bool = false,
include_exported_elec_emissions_in_total::Bool = true,
include_exported_renewable_electricity_in_total::Bool = true,
include_grid_renewable_electricity_in_total::Bool = false,
outdoor_air_temperature_degF::Union{Nothing, Array{<:Real,1}} = nothing,
node::Int = 1,
)
Expand All @@ -79,7 +84,7 @@ mutable struct Site
mg_tech_sizes_equal_grid_sizes, CO2_emissions_reduction_min_fraction,
CO2_emissions_reduction_max_fraction, bau_emissions_lb_CO2_per_year,
bau_grid_emissions_lb_CO2_per_year, renewable_electricity_min_fraction,
renewable_electricity_max_fraction, include_exported_elec_emissions_in_total,
renewable_electricity_max_fraction, include_grid_renewable_electricity_in_min_max_constraints, include_exported_elec_emissions_in_total,
include_exported_renewable_electricity_in_total, outdoor_air_temperature_degF, node)
end
end
6 changes: 3 additions & 3 deletions src/results/site.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ function add_site_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="")
r = Dict{String, Any}()

# renewable elec
r["annual_renewable_electricity_kwh"] = round(value(m[:AnnualREEleckWh]), digits=2)
r["renewable_electricity_fraction"] = round(value(m[:AnnualREEleckWh])/value(m[:AnnualEleckWh]), digits=6)
r["annual_renewable_electricity_kwh"] = round(value(m[:AnnualOnsiteREEleckWh]), digits=2)
r["renewable_electricity_fraction"] = round(value(m[:AnnualOnsiteREEleckWh])/value(m[:AnnualEleckWh]), digits=6)

# total renewable energy
add_re_tot_calcs(m,p)
Expand Down Expand Up @@ -138,7 +138,7 @@ function add_re_tot_calcs(m::JuMP.AbstractModel, p::REoptInputs)
# - AnnualSteamToSteamTurbine # minus steam going to SteamTurbine; already adjusted by p.hours_per_time_step
)
end
m[:AnnualRETotkWh] = @expression(m, m[:AnnualREEleckWh] + AnnualREHeatkWh)
m[:AnnualRETotkWh] = @expression(m, m[:AnnualOnsiteREEleckWh] + AnnualREHeatkWh)
m[:AnnualTotkWh] = @expression(m, m[:AnnualEleckWh] + AnnualHeatkWh)
nothing
end

0 comments on commit 60cdeae

Please sign in to comment.