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

Open
wants to merge 78 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
78 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
c2353fb
fixes to calcs
adfarth Nov 15, 2024
42be276
Update financial.jl
adfarth Nov 15, 2024
b9b1509
Update runtests.jl
adfarth Nov 15, 2024
82aaba4
add a test
adfarth Nov 15, 2024
394ffc8
Update CHANGELOG.md
adfarth Nov 15, 2024
bf72ef7
avoid annual grid RE if mpc
adfarth Nov 15, 2024
b57f878
Update runtests.jl
adfarth Nov 19, 2024
f333311
address failing tests
adfarth Nov 19, 2024
f2b2871
Update runtests.jl
adfarth Nov 20, 2024
c357ddf
change variable name
adfarth Nov 21, 2024
a26e268
conditionally show warning
adfarth Nov 26, 2024
b8a93c2
add soc_init_fraction
adfarth Nov 26, 2024
f09fe6e
small updates
adfarth Nov 26, 2024
9a4dd25
add a PV inputs check
adfarth Nov 26, 2024
9491bb9
fix test
adfarth Nov 26, 2024
5f6def7
fix to prod factor check
adfarth Dec 2, 2024
deeb15f
add description to outage_durations
adfarth Dec 6, 2024
a0c0b77
change output names to stay below 63 chars
adfarth Dec 12, 2024
df4e6f0
Merge branch 'develop' into gridRE-dev
adfarth Dec 12, 2024
9785939
Update CHANGELOG.md
hdunham Dec 13, 2024
92d067b
update changelog/comment
hdunham Dec 13, 2024
599e2e4
update notes about considerations when battery can export to grid in …
hdunham Dec 13, 2024
d1e9ff7
use REopt.KWH_PER_MMBTU in tests
hdunham Dec 13, 2024
52b3c0f
minor comment updates
hdunham Dec 13, 2024
f022754
clarify test "Renewable Energy from Grid"
hdunham Dec 13, 2024
c4d7871
typo
hdunham Dec 13, 2024
8ec5069
group renewable_energy_fraction_series with similar inputs in Electri…
hdunham Dec 13, 2024
08b0ecb
add to error msg
hdunham Dec 13, 2024
9c8b6a5
util function error_if_series_vals_not_0_to_1
hdunham Dec 13, 2024
818bedd
add to dictkeys_tosymbols
adfarth Dec 16, 2024
74a3c4b
fix Degredation docstring
hdunham Dec 16, 2024
156d035
Merge branch 'gridRE-dev' of https://github.com/NREL/REopt.jl into gr…
hdunham Dec 16, 2024
5fc1160
fix make sub hr time step export rates work
hdunham Dec 17, 2024
e90f270
Update CHANGELOG.md
hdunham Dec 17, 2024
360427b
update manifest
hdunham Dec 17, 2024
55ef6fc
Merge branch 'develop' into gridRE-dev
adfarth Dec 18, 2024
c372337
Revert "Merge branch 'develop' into gridRE-dev"
adfarth Dec 18, 2024
bdb9a45
Revert "update manifest"
adfarth Dec 18, 2024
699abdd
use == to test string
hdunham Dec 18, 2024
7bde370
Merge branch 'develop' into gridRE-dev
hdunham Dec 20, 2024
8d6fcc6
resolve env with 1.8.3
hdunham Dec 20, 2024
5610a11
update manifest to julia 1.11
hdunham Dec 23, 2024
a6bd8b5
simplify compat
hdunham Dec 23, 2024
f7a2d28
update authors
hdunham Dec 23, 2024
37d32e1
typo
hdunham Dec 23, 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