diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index 47949bb557..b654f098ca 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -361,11 +361,20 @@ function thermal_maintenance_capacity_reserve_margin_adjustment!(EP::Model, MAINT = resources_with_maintenance(dfGen) applicable_resources = intersect(MAINT, THERM_COMMIT) - resource_component(y) = dfGen[y, :Resource] - capresfactor(y, capres) = dfGen[y, Symbol("CapRes_$capres")] - cap_size(y) = dfGen[y, :Cap_Size] - down_var(y) = EP[Symbol(maintenance_down_name(resource_component(y)))] maint_adj = @expression(EP, [capres in 1:ncapres, t in 1:T], - -sum(capresfactor(y, capres) * down_var(y)[t] * cap_size(y) for y in applicable_resources)) + sum(thermal_maintenance_capacity_reserve_margin_adjustment(EP, inputs, y, capres, t) for y in applicable_resources)) add_similar_to_expression!(EP[:eCapResMarBalance], maint_adj) end + +function thermal_maintenance_capacity_reserve_margin_adjustment(EP::Model, + inputs::Dict, + y::Int, + capres::Int, + t) + dfGen = inputs["dfGen"] + resource_component = dfGen[y, :Resource] + capresfactor = dfGen[y, Symbol("CapRes_$capres")] + cap_size = dfGen[y, :Cap_Size] + down_var = EP[Symbol(maintenance_down_name(resource_component))] + return -capresfactor * down_var[t] * cap_size +end diff --git a/src/write_outputs/capacity_reserve_margin/effective_capacity.jl b/src/write_outputs/capacity_reserve_margin/effective_capacity.jl new file mode 100644 index 0000000000..5d6d47246c --- /dev/null +++ b/src/write_outputs/capacity_reserve_margin/effective_capacity.jl @@ -0,0 +1,54 @@ +@doc raw""" + thermal_plant_effective_capacity(EP::Model, + inputs::Dict, + resources::Vector{Int}, + capres_zone::Int, + timesteps::Vector{Int})::Matrix{Float64} + + Effective capacity in a capacity reserve margin zone for certain resources in the given timesteps. +""" +function thermal_plant_effective_capacity( + EP, + inputs, + resources::Vector{Int}, + capres_zone::Int, + timesteps::Vector{Int}, +)::Matrix{Float64} + eff_cap = + thermal_plant_effective_capacity.( + Ref(EP), + Ref(inputs), + resources, + Ref(capres_zone), + Ref(timesteps), + ) + return reduce(hcat, eff_cap) +end + +function thermal_plant_effective_capacity(EP::Model, inputs::Dict, y, capres_zone::Int) + T = inputs["T"] + timesteps = collect(1:T) + return thermal_plant_effective_capacity(EP, inputs, y, capres_zone, timesteps) +end + +function thermal_plant_effective_capacity( + EP::Model, + inputs::Dict, + r_id::Int, + capres_zone::Int, + timesteps::Vector{Int}, +)::Vector{Float64} + y = r_id + dfGen = inputs["dfGen"] + capresfactor = dfGen[y, Symbol("CapRes_$capres_zone")] + eTotalCap = value.(EP[:eTotalCap][y]) + + effective_capacity = fill(capresfactor * eTotalCap, length(timesteps)) + + if has_maintenance(inputs) && y in resources_with_maintenance(dfGen) + adjustment = thermal_maintenance_capacity_reserve_margin_adjustment(EP, inputs, y, capres_zone, timesteps) + effective_capacity = effective_capacity .+ value.(adjustment) + end + + return effective_capacity +end diff --git a/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl b/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl index 1acae4ee7f..cbefad6c83 100644 --- a/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl +++ b/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl @@ -48,7 +48,7 @@ function write_capacity_value(path::AbstractString, inputs::Dict, setup::Dict, E power(y::Vector{Int}) = value.(EP[:vP][y, riskyhour])' - capvalue[riskyhour, THERM_ALL_EX] .= crm_derate(i, THERM_ALL_EX) + capvalue[riskyhour, THERM_ALL_EX] = thermal_plant_effective_capacity(EP, inputs, THERM_ALL_EX, i, riskyhour) ./ total_cap(THERM_ALL_EX) capvalue[riskyhour, VRE_EX] = crm_derate(i, VRE_EX) .* max_power(riskyhour, VRE_EX) @@ -121,3 +121,4 @@ function capacity_reserve_margin_price(EP::Model, inputs::Dict, setup::Dict, cap scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 return dual.(EP[:cCapacityResMargin][capres_zone, :]) ./ ω * scale_factor end + diff --git a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl index e2bfc70153..f40877ac78 100644 --- a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl @@ -9,6 +9,7 @@ Function for reporting the capacity revenue earned by each generator listed in t As a reminder, GenX models the capacity reserve margin (aka capacity market) at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ function write_reserve_margin_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 dfGen = inputs["dfGen"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) @@ -31,9 +32,10 @@ function write_reserve_margin_revenue(path::AbstractString, inputs::Dict, setup: dfResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) annual_sum = zeros(G) for i in 1:inputs["NCapacityReserveMargin"] + weighted_price = capacity_reserve_margin_price(EP, inputs, setup, i) .* inputs["omega"] / scale_factor sym = Symbol("CapRes_$i") tempresrev = zeros(G) - tempresrev[THERM_ALL] = dfGen[THERM_ALL, sym] .* (value.(EP[:eTotalCap][THERM_ALL])) * sum(dual.(EP[:cCapacityResMargin][i, :])) + tempresrev[THERM_ALL] = thermal_plant_effective_capacity(EP, inputs, THERM_ALL, i)' * weighted_price tempresrev[VRE] = dfGen[VRE, sym] .* (value.(EP[:eTotalCap][VRE])) .* (inputs["pP_Max"][VRE, :] * (dual.(EP[:cCapacityResMargin][i, :]))) tempresrev[MUST_RUN] = dfGen[MUST_RUN, sym] .* (value.(EP[:eTotalCap][MUST_RUN])) .* (inputs["pP_Max"][MUST_RUN, :] * (dual.(EP[:cCapacityResMargin][i, :]))) tempresrev[HYDRO_RES] = dfGen[HYDRO_RES, sym] .* (value.(EP[:vP][HYDRO_RES, :]) * (dual.(EP[:cCapacityResMargin][i, :]))) @@ -52,9 +54,7 @@ function write_reserve_margin_revenue(path::AbstractString, inputs::Dict, setup: tempresrev[DC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_DC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_DC_CHARGE][DC_CHARGE, :]).data ./ dfVRE_STOR[(dfVRE_STOR.STOR_DC_CHARGE.!=0), :EtaInverter]) * (dual.(EP[:cCapacityResMargin][i, :]))) tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * (dual.(EP[:cCapacityResMargin][i, :]))) end - if setup["ParameterScale"] == 1 - tempresrev *= ModelScalingFactor^2 - end + tempresrev *= scale_factor^2 annual_sum .+= tempresrev dfResRevenue = hcat(dfResRevenue, DataFrame([tempresrev], [sym])) end