Skip to content
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

Add Grid Renewable Energy Fraction #426

Draft
wants to merge 33 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0d4a787
Add API call for renewable energy fraction
pypapus Jul 30, 2024
71acc5e
Fixed cambium API call
pypapus Jul 31, 2024
eca3f12
Clean energy fraction added to scenarios
pypapus Aug 1, 2024
80d1d3e
Include profile of grid clean energy contribution in kW
pypapus Aug 1, 2024
c095d48
Updated help text and added checks for user-provided values
pypapus Aug 2, 2024
b67ff48
Corrected off-grid flag in scenario.jl
pypapus Aug 2, 2024
9a8f09f
Edited to remove cef calculation using BAU electric load
pypapus Aug 12, 2024
379eeab
Added new "cef_constraints" to calculate clean energy fraction of the…
pypapus Aug 12, 2024
26f5cec
Added cef (kWh) from the grid in accounting for renewable energy perc…
pypapus Aug 12, 2024
4cce89e
Added new function to calculate the clean_energy_fraction (kWh) grid …
pypapus Aug 12, 2024
6500b40
Added new input to include grid cef (kWh) in renewable energy percent…
pypapus Aug 12, 2024
98d22b5
Updated function adds grid clean energy kWh serving the load to results
pypapus Aug 12, 2024
c3c4d90
Merge branch 'develop' into gridRE-dev
adfarth Aug 12, 2024
e3b0300
removed "hours_per_time_step" to use model time step.
pypapus Aug 15, 2024
248481b
Added "annual_clean_grid_to_load_kwh" in results
pypapus Aug 15, 2024
1d77ec8
Merge branch 'gridRE-dev' of https://github.com/NREL/REopt.jl into gr…
pypapus Aug 15, 2024
fbf7691
Combined cambium_emissions_profile() and cambium_clean_energy_fractio…
pypapus Aug 16, 2024
29518dc
Update electric_utility.jl
adfarth Sep 10, 2024
132f982
Merge branch 'develop' into gridRE-dev
adfarth Nov 4, 2024
3a4bafd
spelling
adfarth Nov 5, 2024
8a34550
Merge branch 'develop' into gridRE-dev
adfarth Nov 12, 2024
dec6733
change cambium_metric_col to cambium_co2_metric
adfarth Nov 12, 2024
3916ba8
Change **cambium_emissions_region** to **cambium_region** and clean u…
adfarth Nov 12, 2024
37de923
Update electric_utility.jl
adfarth Nov 13, 2024
ff29a78
emissions_profile to profile_data
adfarth Nov 13, 2024
cee8176
reorganize constraints
adfarth Nov 13, 2024
8571808
minor edits
adfarth Nov 13, 2024
60cdeae
simplify constraints
adfarth Nov 14, 2024
7596768
Update renewable_energy_constraints.jl
adfarth Nov 14, 2024
434f928
Update site.jl
adfarth Nov 14, 2024
3b7a93a
update outputs
adfarth Nov 14, 2024
f2bb448
add outputs
adfarth Nov 14, 2024
a058fed
fix to align_profile_with_load_year
adfarth Nov 14, 2024
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
9 changes: 9 additions & 0 deletions src/core/bau_inputs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ function BAUInputs(p::REoptInputs)
heating_loads_served_by_tes = Dict{String,Array{String,1}}()
unavailability = get_unavailability_by_tech(p.s, techs, p.time_steps)

# Calculate clean energy contribution (kW)
calculate_clean_energy_contribution(p.s.electric_utility, p.s.electric_load)
adfarth marked this conversation as resolved.
Show resolved Hide resolved

REoptInputs(
bau_scenario,
techs,
Expand Down Expand Up @@ -392,4 +395,10 @@ function bau_outage_check(critical_loads_kw::AbstractArray, pv_kw_series::Abstra
end

return true, length(critical_loads_kw), generator_fuel_use_gal
end


function calculate_clean_energy_contribution(electric_utility::ElectricUtility, electric_load::ElectricLoad)
clean_energy_contribution = electric_utility.clean_energy_fraction_series .* electric_load.loads_kw
return clean_energy_contribution
end
140 changes: 133 additions & 7 deletions src/core/electric_utility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ struct ElectricUtility
outage_time_steps::Union{Nothing, UnitRange}
scenarios::Union{Nothing, UnitRange}
net_metering_limit_kw::Real
interconnection_limit_kw::Real

interconnection_limit_kw::Real
clean_energy_fraction_series::Array{<:Real,1} # Utilities renewable energy fraction.

function ElectricUtility(;

Expand Down Expand Up @@ -156,6 +156,11 @@ struct ElectricUtility
outage_probabilities::Array{<:Real,1} = isempty(outage_durations) ? Float64[] : [1/length(outage_durations) for p_i in 1:length(outage_durations)],
outage_time_steps::Union{Nothing, UnitRange} = isempty(outage_durations) ? nothing : 1:maximum(outage_durations),
scenarios::Union{Nothing, UnitRange} = isempty(outage_durations) ? nothing : 1:length(outage_durations),

### Grid Renewable Energy Fraction Inputs ###
# Utilities renewable energy fraction. Can be scalar or timeseries (aligned with time_steps_per_hour)
clean_energy_fraction_series::Union{Real, Array{<:Real, 1}} = Float64[],
pypapus marked this conversation as resolved.
Show resolved Hide resolved
cambium_cef_col::String = "cef_load", # Column name for clean energy fraction in Cambium database
pypapus marked this conversation as resolved.
Show resolved Hide resolved

### Grid Climate Emissions Inputs ###
# Climate Option 1 (Default): Use levelized emissions data from NREL's Cambium database by specifying the following fields:
Expand Down Expand Up @@ -195,6 +200,51 @@ struct ElectricUtility
cambium_emissions_region = "NA - Cambium data not used for climate emissions" # will be overwritten if Cambium is used

if !is_MPC
# Initialize clean energy fraction series
adfarth marked this conversation as resolved.
Show resolved Hide resolved
adfarth marked this conversation as resolved.
Show resolved Hide resolved
clean_energy_series_dict = Dict{String, Union{Nothing, Array{<:Real, 1}}}()
if typeof(clean_energy_fraction_series) <: Real # user provided scalar value
clean_energy_series_dict["cef"] = repeat([clean_energy_fraction_series], 8760*time_steps_per_hour)
elseif length(clean_energy_fraction_series) == 1 # user provided array of one value
clean_energy_series_dict["cef"] = repeat(clean_energy_fraction_series, 8760*time_steps_per_hour)
elseif length(clean_energy_fraction_series) / time_steps_per_hour ≈ 8760 # user provided array with correct length
clean_energy_series_dict["cef"] = clean_energy_fraction_series
elseif length(clean_energy_fraction_series) > 1 && !(length(clean_energy_fraction_series) / time_steps_per_hour ≈ 8760) # user provided array with incorrect length
if length(clean_energy_fraction_series) == 8760
clean_energy_series_dict["cef"] = repeat(clean_energy_fraction_series, inner=time_steps_per_hour)
@warn("Clean energy fraction series has been adjusted to align with time_steps_per_hour of $(time_steps_per_hour).")
else
throw(@error("The provided ElectricUtility clean energy fraction series does not match the time_steps_per_hour."))
end
else
# Retrieve clean energy fraction data if not user-provided
if cambium_start_year < 2023 || cambium_start_year > 2050
@warn("The cambium_start_year must be between 2023 and 2050. Setting cambium_start_year to 2024.")
cambium_start_year = 2024 # Must update annually
end
try
clean_energy_response_dict = cambium_clean_energy_fraction_profile(
scenario = cambium_scenario,
location_type = cambium_location_type,
latitude = latitude,
longitude = longitude,
start_year = cambium_start_year,
lifetime = cambium_levelization_years,
metric_col = cambium_cef_col,
grid_level = cambium_grid_level,
adfarth marked this conversation as resolved.
Show resolved Hide resolved
time_steps_per_hour = time_steps_per_hour,
load_year = load_year,
emissions_year = 2017 # Cambium data starts on a Sunday
)
clean_energy_series_dict["cef"] = clean_energy_response_dict["clean_energy_fraction_series"]
cambium_emissions_region = clean_energy_response_dict["location"]
catch
@warn("Could not look up Cambium renewable energy fraction profile from point ($(latitude), $(longitude)).
Location is likely outside contiguous US or something went wrong with the Cambium API request. Setting clean energy fraction to zero.")
clean_energy_series_dict["cef"] = zeros(Float64, 8760*time_steps_per_hour)
end
end


# Get AVERT emissions region
if avert_emissions_region == ""
region_abbr, meters_to_region = avert_region_abbreviation(latitude, longitude)
Expand Down Expand Up @@ -350,13 +400,12 @@ struct ElectricUtility
outage_time_steps,
scenarios,
net_metering_limit_kw,
interconnection_limit_kw
interconnection_limit_kw,
is_MPC ? Float64[] : clean_energy_series_dict["cef"]
)
end
end



"""
Determine the AVERT region abberviation for a given lat/lon pair.
1. Checks to see if given point is in an AVERT region
Expand Down Expand Up @@ -584,7 +633,7 @@ function cambium_emissions_profile(; scenario::String,
response = JSON.parse(String(r.body)) # contains response["status"]
output = response["message"]
co2_emissions = output["values"] ./ 1000 # [lb / MWh] --> [lb / kWh]

# Align day of week of emissions and load profiles (Cambium data starts on Sundays so assuming emissions_year=2017)
co2_emissions = align_emission_with_load_year(load_year=load_year,emissions_year=emissions_year,emissions_profile=co2_emissions)

Expand Down Expand Up @@ -624,4 +673,81 @@ function align_emission_with_load_year(; load_year::Int, emissions_year::Int, em
end

return emissions_profile_adj
end
end

"""
cambium_clean_energy_fraction_profile(; scenario::String,
location_type::String,
latitude::Real,
longitude::Real,
start_year::Int,
lifetime::Int,
grid_level::String,
time_steps_per_hour::Int=1,
load_year::Int=2017,
emissions_year::Int=2017)
This function constructs an API request to the Cambium database to retrieve the clean energy fraction data.
"""

function cambium_clean_energy_fraction_profile(; scenario::String,
location_type::String,
latitude::Real,
longitude::Real,
start_year::Int,
lifetime::Int,
metric_col::String,
grid_level::String,
time_steps_per_hour::Int=1,
load_year::Int=2017,
emissions_year::Int=2017)

url = "https://scenarioviewer.nrel.gov/api/get-levelized/" # Cambium API endpoint
project_uuid = "82460f06-548c-4954-b2d9-b84ba92d63e2" # Cambium 2022 project UUID

# Construct the payload for the API request
payload = Dict(
"project_uuid" => project_uuid,
"scenario" => scenario,
"location_type" => location_type,
"latitude" => string(round(latitude, digits=3)),
"longitude" => string(round(longitude, digits=3)),
"start_year" => string(start_year),
"lifetime" => string(lifetime),
"discount_rate" => "0.0",
"time_type" => "hourly",
"metric_col" => metric_col, # Metric for clean energy fraction
"smoothing_method" => "rolling",
"gwp" => "100yrAR6",
"grid_level" => grid_level,
"ems_mass_units" => "lb"
)

try
# Make the API request
r = HTTP.get(url; query=payload)
response = JSON.parse(String(r.body))
output = response["message"]
clean_energy_fraction = output["values"]
clean_energy_fraction = map(x -> Real(x), clean_energy_fraction) # Convert to Float64

# Align day of week of clean energy fraction profile with load year
clean_energy_fraction = align_emission_with_load_year(load_year=load_year, emissions_year=emissions_year, emissions_profile=clean_energy_fraction)
if time_steps_per_hour > 1
clean_energy_fraction = repeat(clean_energy_fraction, inner=time_steps_per_hour)
end

# Return the clean energy fraction data in a dictionary
response_dict = Dict{String, Any}(
"description" => "Hourly clean energy fraction for applicable Cambium location and location_type, adjusted to align with load year $(load_year).",
"units" => "Fraction of clean energy",
"location" => output["location"],
"metric_col" => output["metric_col"],
"clean_energy_fraction_series" => clean_energy_fraction
)
return response_dict
catch
return Dict{String, Any}(
"error" => "Could not look up Cambium clean energy fraction profile from point ($(latitude), $(longitude)). Location is likely outside contiguous US or something went wrong with the Cambium API request."
)
end
end
1 change: 1 addition & 0 deletions src/core/reopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ function run_reopt(m::JuMP.AbstractModel, p::REoptInputs; organize_pvs=true)
results = reopt_results(m, p)
time_elapsed = time() - tstart
@info "Results processing took $(round(time_elapsed, digits=3)) seconds."
@info "REopt results have been processed."
adfarth marked this conversation as resolved.
Show resolved Hide resolved
results["status"] = status
results["solver_seconds"] = opt_time

Expand Down
9 changes: 6 additions & 3 deletions src/core/scenario.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
off_grid_flag=settings.off_grid_flag,
time_steps_per_hour=settings.time_steps_per_hour,
analysis_years=financial.analysis_years,
load_year=electric_load.year
load_year=electric_load.year,
clean_energy_fraction_series = Float64[]
adfarth marked this conversation as resolved.
Show resolved Hide resolved
)
elseif !(settings.off_grid_flag)
electric_utility = ElectricUtility(; latitude=site.latitude, longitude=site.longitude,
Expand All @@ -140,7 +141,8 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
off_grid_flag=settings.off_grid_flag,
time_steps_per_hour=settings.time_steps_per_hour,
analysis_years=financial.analysis_years,
load_year=electric_load.year
load_year=electric_load.year,
clean_energy_fraction_series = Float64[]
)
elseif settings.off_grid_flag
if haskey(d, "ElectricUtility")
Expand All @@ -154,7 +156,8 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
emissions_factor_series_lb_CO2_per_kwh = 0,
emissions_factor_series_lb_NOx_per_kwh = 0,
emissions_factor_series_lb_SO2_per_kwh = 0,
emissions_factor_series_lb_PM25_per_kwh = 0
emissions_factor_series_lb_PM25_per_kwh = 0,
clean_energy_fraction_series = Float64[]
adfarth marked this conversation as resolved.
Show resolved Hide resolved
)
end

Expand Down
Loading