From b8a93c2485f6b0999fb312691f30adb0a2e7471a Mon Sep 17 00:00:00 2001 From: adfarth Date: Tue, 26 Nov 2024 13:08:10 -0500 Subject: [PATCH] add soc_init_fraction from PR#447 --- CHANGELOG.md | 1 + src/constraints/storage_constraints.jl | 19 +++++++++++++++---- src/core/absorption_chiller.jl | 2 +- src/core/energy_storage/electric_storage.jl | 4 ++++ src/results/electric_load.jl | 2 +- src/results/financial.jl | 2 +- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc175322..b48c6bbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Classify the change according to the following categories: - ElectricUtility **annual_renewable_electricity_supplied_kwh** - Site **onsite_and_grid_renewable_electricity_fraction_of_elec_load** - Site **onsite_and_grid_renewable_energy_fraction_of_elec_and_thermal_load** +- Added input option optimize_soc_init_fraction (defaults to false) to ElectricStorage, which makes the optimization choose the inital SOC (equal to final SOC) instead of using soc_init_fraction. The initial SOC is also constrained to equal the final SOC, which eliminates the "free energy" issue. We currently do not fix SOC when soc_init_fraction is used because this has caused infeasibility. ### Changed - Changed name of the following inputs: - ElectricUtility input **cambium_metric_col** changed to **cambium_co2_metric** diff --git a/src/constraints/storage_constraints.jl b/src/constraints/storage_constraints.jl index c46d4e775..469fbcced 100644 --- a/src/constraints/storage_constraints.jl +++ b/src/constraints/storage_constraints.jl @@ -38,10 +38,21 @@ end function add_general_storage_dispatch_constraints(m, p, b; _n="") - # Constraint (4a): initial state of charge - @constraint(m, - m[Symbol("dvStoredEnergy"*_n)][b, 0] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] - ) + # Constraint (4a): initial and final state of charge + if hasproperty(p.s.storage.attr[b], :optimize_soc_init_fraction) && p.s.storage.attr[b].optimize_soc_init_fraction + print("\nOptimizing "*b*" inital SOC and constraining initial SOC = final SOC. soc_init_fraction will not apply.\n") + @constraint(m, + m[Symbol("dvStoredEnergy"*_n)][b, 0] == m[:dvStoredEnergy][b, maximum(p.time_steps)] + ) + else + @constraint(m, + m[Symbol("dvStoredEnergy"*_n)][b, 0] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] + ) + # TODO: constrain final soc to equal initial soc + # @constraint(m, + # m[Symbol("dvStoredEnergy"*_n)][b, maximum(p.time_steps)] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] + # ) + end #Constraint (4n): State of charge upper bound is storage system size @constraint(m, [ts in p.time_steps], diff --git a/src/core/absorption_chiller.jl b/src/core/absorption_chiller.jl index ae681ef61..f9a1a9827 100644 --- a/src/core/absorption_chiller.jl +++ b/src/core/absorption_chiller.jl @@ -12,7 +12,7 @@ min_ton::Float64 = 0.0, # Minimum thermal power size constraint for optimization max_ton::Float64 = BIG_NUMBER, # Maximum thermal power size constraint for optimization cop_thermal::Union{Float64, Nothing} = nothing, # Absorption chiller system coefficient of performance - conversion of hot thermal power input to usable cooling thermal energy output - cop_electric::Float64 = 14.1, # Absorption chiller electric consumption CoP from cooling tower heat rejection - conversion of electric power input to usable cooling thermal energy outpu + cop_electric::Float64 = 14.1, # Absorption chiller electric consumption CoP from cooling tower heat rejection - conversion of electric power input to usable cooling thermal energy output macrs_option_years::Float64 = 0, # MACRS schedule for financial analysis. Set to zero to disable macrs_bonus_fraction::Float64 = 0 # Percent of upfront project costs to depreciate under MACRS heating_load_input::Union{String, Nothing} = nothing # heating load that serves as input to absorption chiller diff --git a/src/core/energy_storage/electric_storage.jl b/src/core/energy_storage/electric_storage.jl index 771a84364..28bf42315 100644 --- a/src/core/energy_storage/electric_storage.jl +++ b/src/core/energy_storage/electric_storage.jl @@ -185,6 +185,7 @@ end model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 + optimize_soc_init_fraction::Bool = false # If true, soc_init_fraction will not apply. Model will optimize initial SOC and constrain initial SOC = final SOC. min_duration_hours::Real = 0.0 # Minimum amount of time storage can discharge at its rated power capacity max_duration_hours::Real = 100000.0 # Maximum amount of time storage can discharge at its rated power capacity (ratio of ElectricStorage size_kwh to size_kw) ``` @@ -220,6 +221,7 @@ Base.@kwdef struct ElectricStorageDefaults model_degradation::Bool = false degradation::Dict = Dict() minimum_avg_soc_fraction::Float64 = 0.0 + optimize_soc_init_fraction::Bool = false min_duration_hours::Real = 0.0 max_duration_hours::Real = 100000.0 end @@ -263,6 +265,7 @@ struct ElectricStorage <: AbstractElectricStorage model_degradation::Bool degradation::Degradation minimum_avg_soc_fraction::Float64 + optimize_soc_init_fraction::Bool min_duration_hours::Real max_duration_hours::Real @@ -356,6 +359,7 @@ struct ElectricStorage <: AbstractElectricStorage s.model_degradation, degr, s.minimum_avg_soc_fraction, + s.optimize_soc_init_fraction, s.min_duration_hours, s.max_duration_hours ) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index 761e20658..cac2d7246 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -1,7 +1,7 @@ # 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 +- `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 diff --git a/src/results/financial.jl b/src/results/financial.jl index 31b035adf..9f40459fc 100644 --- a/src/results/financial.jl +++ b/src/results/financial.jl @@ -356,7 +356,7 @@ function get_depreciation_schedule(p::REoptInputs, tech::Union{AbstractTech,Abst federal_itc_fraction = 0.0 try - federal_itc_fraction = tech.federal_itc_fraction # TODO: also check for total_itc_fraction as storage does not use federal_itc_fraction? + federal_itc_fraction = tech.federal_itc_fraction catch @warn "Did not find $(tech).federal_itc_fraction so using 0.0 in calculation of depreciation_schedule." end