From b01c606d5026b28fa3465a10ebcb93d8232e3f4c Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Thu, 11 Jan 2024 15:49:05 -0500 Subject: [PATCH 01/30] Create write_operating_reserve_price_revenue.jl For printing the operating reserve prices and revenues --- .../write_operating_reserve_price_revenue.jl | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl new file mode 100644 index 0000000000..5647d9bffb --- /dev/null +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -0,0 +1,64 @@ +@doc raw""" + write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) + +Function for reporting the operating reserve prices and revenue earned by each generator listed in the input file. + GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. + Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. + The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. + The last column is the total revenue received from all operating reserve constraints. + As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. +""" +function write_operating_reserve_price_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) + THERM_ALL = inputs["THERM_ALL"] + VRE = inputs["VRE"] + HYDRO_RES = inputs["HYDRO_RES"] + STOR_ALL = inputs["STOR_ALL"] + FLEX = inputs["FLEX"] + MUST_RUN = inputs["MUST_RUN"] + VRE_STOR = inputs["VRE_STOR"] + dfVRE_STOR = inputs["dfVRE_STOR"] + if !isempty(VRE_STOR) + VRE_STOR_STOR = inputs["VS_STOR"] + DC_DISCHARGE = inputs["VS_STOR_DC_DISCHARGE"] + AC_DISCHARGE = inputs["VS_STOR_AC_DISCHARGE"] + DC_CHARGE = inputs["VS_STOR_DC_CHARGE"] + AC_CHARGE = inputs["VS_STOR_AC_CHARGE"] + dfVRE_STOR = inputs["dfVRE_STOR"] + end + dfOpResRevenue = 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"] + sym = Symbol("CapRes_$i") + tempresrev = zeros(G) + 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, :] * weighted_price) + tempresrev[MUST_RUN] = dfGen[MUST_RUN, sym] .* (value.(EP[:eTotalCap][MUST_RUN])) .* (inputs["pP_Max"][MUST_RUN, :] * weighted_price) + tempresrev[HYDRO_RES] = dfGen[HYDRO_RES, sym] .* (value.(EP[:vP][HYDRO_RES, :]) * weighted_price) + if !isempty(STOR_ALL) + tempresrev[STOR_ALL] = dfGen[STOR_ALL, sym] .* ((value.(EP[:vP][STOR_ALL, :]) - value.(EP[:vCHARGE][STOR_ALL, :]).data + value.(EP[:vCAPRES_discharge][STOR_ALL, :]).data - value.(EP[:vCAPRES_charge][STOR_ALL, :]).data) * weighted_price) + end + if !isempty(FLEX) + tempresrev[FLEX] = dfGen[FLEX, sym] .* ((value.(EP[:vCHARGE_FLEX][FLEX, :]).data - value.(EP[:vP][FLEX, :])) * weighted_price) + end + if !isempty(VRE_STOR) + sym_vs = Symbol("CapResVreStor_$i") + tempresrev[VRE_STOR] = dfVRE_STOR[!, sym_vs] .* ((value.(EP[:vP][VRE_STOR, :])) * weighted_price) + tempresrev[VRE_STOR_STOR] .-= dfVRE_STOR[((dfVRE_STOR.STOR_DC_DISCHARGE.!=0) .| (dfVRE_STOR.STOR_DC_CHARGE.!=0) .| (dfVRE_STOR.STOR_AC_DISCHARGE.!=0) .|(dfVRE_STOR.STOR_AC_CHARGE.!=0)), sym_vs] .* (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, :]).data * weighted_price) + tempresrev[DC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, :]).data .* dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), :EtaInverter]) * weighted_price) + tempresrev[AC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_AC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, :]).data) * weighted_price) + 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]) * weighted_price) + tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) + end + tempresrev *= scale_factor + annual_sum .+= tempresrev + dfResRevenue = hcat(dfResRevenue, DataFrame([tempresrev], [sym])) + end + dfResRevenue.AnnualSum = annual_sum + CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) + return dfResRevenue +end From 012959f52bfc9d60d9cd1194bf5f9ca4a19cb168 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Thu, 11 Jan 2024 16:55:45 -0500 Subject: [PATCH 02/30] Update write_operating_reserve_price_revenue.jl --- .../write_operating_reserve_price_revenue.jl | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl index 5647d9bffb..392bec89d8 100644 --- a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -8,7 +8,7 @@ Function for reporting the operating reserve prices and revenue earned by each g The last column is the total revenue received from all operating reserve constraints. As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ -function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) +function write_operating_reserve_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) @@ -31,8 +31,9 @@ function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dic end dfOpResRevenue = 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"] + for t in 1:T + weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] sym = Symbol("CapRes_$i") tempresrev = zeros(G) tempresrev[THERM_ALL] = thermal_plant_effective_capacity(EP, inputs, THERM_ALL, i)' * weighted_price @@ -62,3 +63,15 @@ function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dic CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) return dfResRevenue end + +function operating_regulation_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} + ω = inputs["omega"] + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + return dual.(EP[:cReg]) ./ ω * scale_factor +end + +function operating_reserve_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} + ω = inputs["omega"] + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + return dual.(EP[:cRsvReq]) ./ ω * scale_factor +end From 87fe00cfec4260a12a1d9595b5ee4f03621f0db3 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Wed, 24 Jan 2024 15:57:21 -0500 Subject: [PATCH 03/30] Update write_operating_reserve_price_revenue.jl Partial updates to reserve and regulation price documentation and formulation --- .../write_operating_reserve_price_revenue.jl | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl index 392bec89d8..758336b09c 100644 --- a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -1,7 +1,7 @@ @doc raw""" write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) -Function for reporting the operating reserve prices and revenue earned by each generator listed in the input file. +Function for reporting the operating reserve prices and revenue earned by generators listed in the input file. GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. @@ -55,21 +55,48 @@ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, set 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]) * weighted_price) tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) end + tempregrev *= scale_factor tempresrev *= scale_factor - annual_sum .+= tempresrev - dfResRevenue = hcat(dfResRevenue, DataFrame([tempresrev], [sym])) + annual_reg_sum .+= tempregrev + annual_res_sum .+= tempresrev + dfOpRegRevenue = hcat(dfOpRegRevenue, DataFrame([tempregrev], [sym])) + dfOpResRevenue = hcat(dfOpResRevenue, DataFrame([tempresrev], [sym])) end - dfResRevenue.AnnualSum = annual_sum - CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) - return dfResRevenue + dfOpRegRevenue.AnnualSum = annual_reg_sum + dfOpResRevenue.AnnualSum = annual_res_sum + CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) + CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + return dfOpRegRevenue, dfOpResRevenue end +@doc raw""" + operating_regulation_price(EP::Model, + inputs::Dict, + setup::Dict)::Vector{Float64} + +Operating regulation price for each time step. +This is equal to the dual variable of the regulatin requirement constraint. + + Returns a vector, with units of $/MW +""" + function operating_regulation_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} ω = inputs["omega"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 return dual.(EP[:cReg]) ./ ω * scale_factor end +@doc raw""" + operating_reserve_price(EP::Model, + inputs::Dict, + setup::Dict)::Vector{Float64} + +Operating reserve price for each time step. +This is equal to the dual variable of the reserve requirement constraint. + + Returns a vector, with units of $/MW +""" + function operating_reserve_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} ω = inputs["omega"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 From 962f18f0294fe42c9c2ffadb6e1d3e674aa39e77 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Wed, 24 Jan 2024 16:04:53 -0500 Subject: [PATCH 04/30] Moved wrtiting of operating reserve revenue from capacity reserve margin folder to reserve folder --- .../write_operating_reserve_price_revenue.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/write_outputs/{capacity_reserve_margin => reserves}/write_operating_reserve_price_revenue.jl (100%) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl similarity index 100% rename from src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl rename to src/write_outputs/reserves/write_operating_reserve_price_revenue.jl From e89b0bf91e8f3b1175566acdd0d52c181cea2192 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 29 Jan 2024 09:08:56 -0500 Subject: [PATCH 05/30] Modified write__operating_reserve_price_revenue --- julenv.jl | 54 ++++++++++++++ package_activate.jl | 20 ++++++ src/load_inputs/load_cap_reserve_margin.jl | 1 + .../write_operating_reserve_price_revenue.jl | 72 ++++++------------- src/write_outputs/write_outputs.jl | 8 +++ 5 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 julenv.jl create mode 100644 package_activate.jl diff --git a/julenv.jl b/julenv.jl new file mode 100644 index 0000000000..c3a805b2f1 --- /dev/null +++ b/julenv.jl @@ -0,0 +1,54 @@ +""" +GenX: An Configurable Capacity Expansion Model +Copyright (C) 2021, Massachusetts Institute of Technology +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +A complete copy of the GNU General Public License v2 (GPLv2) is available +in LICENSE.txt. Users uncompressing this from an archive may not have +received this license file. If not, see . +""" + +import Pkg +using Pkg +Pkg.activate("GenXJulEnv") + +Pkg.add(Pkg.PackageSpec(name="Cbc")) +Pkg.add(Pkg.PackageSpec(name="Clp")) +Pkg.add(Pkg.PackageSpec(name="Documenter")) +Pkg.add(Pkg.PackageSpec(name="DataStructures")) +Pkg.add(Pkg.PackageSpec(name="Dates")) +Pkg.add(Pkg.PackageSpec(name="JuMP")) +Pkg.add("MathOptInterface") +Pkg.add(Pkg.PackageSpec(name="SCIP")) +#Pkg.build("SCIP") +Pkg.add(Pkg.PackageSpec(name="HiGHS")) +############################################################################################ +#Uncomment either of the following two lines for the particular version of CPLEX.jl desired +############################################################################################ +#Pkg.add(Pkg.PackageSpec(name="CPLEX", version="0.6.1")) +#Pkg.add(Pkg.PackageSpec(name="CPLEX")) +############################################################################################ +#Uncomment either of the following two lines for the particular version of Gurobi.jl desired +############################################################################################ +#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.10.3")) +#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.11.3")) +Pkg.add(Pkg.PackageSpec(name="CSV")) +Pkg.add(Pkg.PackageSpec(name="Clustering")) +Pkg.add(Pkg.PackageSpec(name="Combinatorics")) +Pkg.add(Pkg.PackageSpec(name="Distances")) +Pkg.add(Pkg.PackageSpec(name="DataFrames")) +Pkg.add(Pkg.PackageSpec(name="Statistics")) +Pkg.add(Pkg.PackageSpec(name="OrdinaryDiffEq")) +Pkg.add(Pkg.PackageSpec(name="BenchmarkTools")) +Pkg.add(Pkg.PackageSpec(name="StatsBase")) +Pkg.add(Pkg.PackageSpec(name="YAML")) +Pkg.add(Pkg.PackageSpec(name="LinearAlgebra")) +Pkg.add(Pkg.PackageSpec(name="Random")) +Pkg.add(Pkg.PackageSpec(name="RecursiveArrayTools")) + diff --git a/package_activate.jl b/package_activate.jl new file mode 100644 index 0000000000..75c76f6107 --- /dev/null +++ b/package_activate.jl @@ -0,0 +1,20 @@ +""" +GenX: An Configurable Capacity Expansion Model +Copyright (C) 2021, Massachusetts Institute of Technology +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +A complete copy of the GNU General Public License v2 (GPLv2) is available +in LICENSE.txt. Users uncompressing this from an archive may not have +received this license file. If not, see . +""" + +using Pkg +include(joinpath(dirname(@__FILE__), "julenv.jl")) #Run this line only for the first time; comment it out for all subsequent use +println("Activating the Julia virtual environment") +Pkg.status() # Store the path of the current working directory diff --git a/src/load_inputs/load_cap_reserve_margin.jl b/src/load_inputs/load_cap_reserve_margin.jl index 646385d078..c3a4ecd104 100644 --- a/src/load_inputs/load_cap_reserve_margin.jl +++ b/src/load_inputs/load_cap_reserve_margin.jl @@ -19,6 +19,7 @@ function load_cap_reserve_margin!(setup::Dict, path::AbstractString, inputs::Dic mat = extract_matrix_from_dataframe(df, "CapRes") inputs["dfCapRes"] = mat inputs["NCapacityReserveMargin"] = size(mat, 2) + print("Number of CRM types is ",inputs["NCapacityReserveMargin"]) println(filename * " Successfully Read!") end diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 758336b09c..20d2d65423 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -3,67 +3,35 @@ Function for reporting the operating reserve prices and revenue earned by generators listed in the input file. GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. - Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. The last column is the total revenue received from all operating reserve constraints. As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + 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) - THERM_ALL = inputs["THERM_ALL"] - VRE = inputs["VRE"] - HYDRO_RES = inputs["HYDRO_RES"] - STOR_ALL = inputs["STOR_ALL"] - FLEX = inputs["FLEX"] - MUST_RUN = inputs["MUST_RUN"] - VRE_STOR = inputs["VRE_STOR"] - dfVRE_STOR = inputs["dfVRE_STOR"] - if !isempty(VRE_STOR) - VRE_STOR_STOR = inputs["VS_STOR"] - DC_DISCHARGE = inputs["VS_STOR_DC_DISCHARGE"] - AC_DISCHARGE = inputs["VS_STOR_AC_DISCHARGE"] - DC_CHARGE = inputs["VS_STOR_DC_CHARGE"] - AC_CHARGE = inputs["VS_STOR_AC_CHARGE"] - dfVRE_STOR = inputs["dfVRE_STOR"] - end dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - annual_sum = zeros(G) - for t in 1:T - weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] - weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] - sym = Symbol("CapRes_$i") - tempresrev = zeros(G) - 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, :] * weighted_price) - tempresrev[MUST_RUN] = dfGen[MUST_RUN, sym] .* (value.(EP[:eTotalCap][MUST_RUN])) .* (inputs["pP_Max"][MUST_RUN, :] * weighted_price) - tempresrev[HYDRO_RES] = dfGen[HYDRO_RES, sym] .* (value.(EP[:vP][HYDRO_RES, :]) * weighted_price) - if !isempty(STOR_ALL) - tempresrev[STOR_ALL] = dfGen[STOR_ALL, sym] .* ((value.(EP[:vP][STOR_ALL, :]) - value.(EP[:vCHARGE][STOR_ALL, :]).data + value.(EP[:vCAPRES_discharge][STOR_ALL, :]).data - value.(EP[:vCAPRES_charge][STOR_ALL, :]).data) * weighted_price) - end - if !isempty(FLEX) - tempresrev[FLEX] = dfGen[FLEX, sym] .* ((value.(EP[:vCHARGE_FLEX][FLEX, :]).data - value.(EP[:vP][FLEX, :])) * weighted_price) - end - if !isempty(VRE_STOR) - sym_vs = Symbol("CapResVreStor_$i") - tempresrev[VRE_STOR] = dfVRE_STOR[!, sym_vs] .* ((value.(EP[:vP][VRE_STOR, :])) * weighted_price) - tempresrev[VRE_STOR_STOR] .-= dfVRE_STOR[((dfVRE_STOR.STOR_DC_DISCHARGE.!=0) .| (dfVRE_STOR.STOR_DC_CHARGE.!=0) .| (dfVRE_STOR.STOR_AC_DISCHARGE.!=0) .|(dfVRE_STOR.STOR_AC_CHARGE.!=0)), sym_vs] .* (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, :]).data * weighted_price) - tempresrev[DC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, :]).data .* dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), :EtaInverter]) * weighted_price) - tempresrev[AC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_AC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, :]).data) * weighted_price) - 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]) * weighted_price) - tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) - end - tempregrev *= scale_factor - tempresrev *= scale_factor - annual_reg_sum .+= tempregrev - annual_res_sum .+= tempresrev - dfOpRegRevenue = hcat(dfOpRegRevenue, DataFrame([tempregrev], [sym])) - dfOpResRevenue = hcat(dfOpResRevenue, DataFrame([tempresrev], [sym])) - end - dfOpRegRevenue.AnnualSum = annual_reg_sum - dfOpResRevenue.AnnualSum = annual_res_sum + dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) + #annual_sum = zeros(G) + weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] + #regsym = Symbol("Reg_Max") + #rsvsym = Symbol("Rsv_Max") + tempregrev = weighted_reg_price#zeros(G) + tempresrev = weighted_rsv_price#zeros(G) + tempregrev *= scale_factor + tempresrev *= scale_factor + #annual_reg_sum .+= tempregrev + #annual_res_sum .+= tempresrev + #print(DataFrame([tempregrev], :auto)) + #print(DataFrame([tempresrev], :auto)) + dfOpRegRevenue = DataFrame([tempregrev], :auto)#, [sym])) + dfOpResRevenue = DataFrame([tempresrev], :auto)#, [sym])) + + dfOpRegRevenue.AnnualSum = tempregrev#annual_reg_sum + dfOpResRevenue.AnnualSum = tempresrev#annual_res_sum CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) return dfOpRegRevenue, dfOpResRevenue diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 32abd1b6f0..2e649def7d 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -179,6 +179,14 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic dfResMar_slack = write_reserve_margin_slack(path, inputs, setup, EP) end end + + if setup["Reserves"]==1 && has_duals(EP) == 1 + dfOpRegRevenue, dfOpResRevenue = write_operating_reserve_revenue(path, inputs, setup, EP) + elapsed_time_op_res_rev = @elapsed write_operating_reserve_revenue(path, inputs, setup, EP) + println("Time elapsed for writing oerating reserve price is") + println(elapsed_time_op_res_rev) + end + if setup["CO2Cap"]>0 && has_duals(EP) == 1 dfCO2Cap = write_co2_cap(path, inputs, setup, EP) end From 6c9dfdd0c8071e40ab2bd23a025cd0786c18ee0b Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:06:20 -0500 Subject: [PATCH 06/30] Updated operating reserve and regulation revenue categorized according to resources --- .../write_operating_reserve_price_revenue.jl | 45 +++++++++---------- src/write_outputs/write_energy_revenue.jl | 4 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 20d2d65423..877c91273a 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -10,30 +10,29 @@ Function for reporting the operating reserve prices and revenue earned by genera function write_operating_reserve_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) - dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - #annual_sum = zeros(G) - weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] - weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] - #regsym = Symbol("Reg_Max") - #rsvsym = Symbol("Rsv_Max") - tempregrev = weighted_reg_price#zeros(G) - tempresrev = weighted_rsv_price#zeros(G) - tempregrev *= scale_factor - tempresrev *= scale_factor - #annual_reg_sum .+= tempregrev - #annual_res_sum .+= tempresrev - #print(DataFrame([tempregrev], :auto)) - #print(DataFrame([tempresrev], :auto)) - dfOpRegRevenue = DataFrame([tempregrev], :auto)#, [sym])) - dfOpResRevenue = DataFrame([tempresrev], :auto)#, [sym])) + G = inputs["G"] + T = inputs["T"] + RSV = inputs["RSV"] + REG = inputs["REG"] + dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) + dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) + resrevenue = zeros(G, T) + regrevenue = zeros(G, T) + weighted_reg_price = operating_regulation_price(EP, inputs, setup) + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) + resrevenue[RSV, :] = value.(EP[:vRSV][RSV, :]).* transpose(weighted_rsv_price) - dfOpRegRevenue.AnnualSum = tempregrev#annual_reg_sum - dfOpResRevenue.AnnualSum = tempresrev#annual_res_sum - CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) - CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + regrevenue[REG, :] = value.(EP[:vREG][REG, :]) .* transpose(weighted_reg_price) + + if setup["ParameterScale"] == 1 + resrevenue *= scale_factor + regrevenue *= scale_factor + end + + dfOpResRevenue.AnnualSum .= resrevenue * inputs["omega"] + dfOpRegRevenue.AnnualSum .= regrevenue * inputs["omega"] + write_simple_csv(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + write_simple_csv(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) return dfOpRegRevenue, dfOpResRevenue end diff --git a/src/write_outputs/write_energy_revenue.jl b/src/write_outputs/write_energy_revenue.jl index f53abbaa5b..c94be4caa9 100644 --- a/src/write_outputs/write_energy_revenue.jl +++ b/src/write_outputs/write_energy_revenue.jl @@ -11,8 +11,8 @@ function write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, E NONFLEX = setdiff(collect(1:G), FLEX) dfEnergyRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) energyrevenue = zeros(G, T) - price = locational_marginal_price(EP, inputs, setup) - energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] + price = locational_marginal_price(EP, inputs, setup) + energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] if !isempty(FLEX) energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[dfGen[FLEX, :Zone], :] end From 6ec3a460f5b80743c58310d1e6fcf4744f6d698b Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:39:54 -0500 Subject: [PATCH 07/30] Updated operating reserve and regulation revenue doc pages --- docs/src/write_outputs.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/write_outputs.md b/docs/src/write_outputs.md index b87cd18082..0a421d6c2f 100644 --- a/docs/src/write_outputs.md +++ b/docs/src/write_outputs.md @@ -92,6 +92,11 @@ Pages = ["write_energy_revenue.jl"] Modules = [GenX] Pages = ["write_subsidy_revenue.jl"] ``` +## Write Operating Reserve and Regulation Revenue +```@autodocs +Modules = [GenX] +Pages = ["write_operating_reserve_price_revenue.jl"] +``` ## Write Capacity Revenue ```@autodocs From 435bbe0cfb398a8792befa830c0ebfc8935e7132 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:44:25 -0500 Subject: [PATCH 08/30] Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abde2ecb3..fa54017d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Maintenance formulation for thermal-commit plants (#556). - Add new tests for GenX: three-zone, multi-stage, electrolyzer, VRE+storage, piecewise_fuel+CO2, and TDR (#563 and #578). +- Added write_operating_reserve_price_revenue.jl to compute annual operating reserve and regulation revenue (PR # 611) ### Fixed @@ -38,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix access of eELOSSByZone expr before initialization (#541) - Correctly write unmet reserves (in reg_dn.csv) (#575) - Correctly scale total reserves column (in reg_dn.csv) (#594) +- Fixes issue #46 ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). From df459bcd286a83ee993dfe4a2c0008f51519f4b4 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 5 Feb 2024 12:06:59 -0500 Subject: [PATCH 09/30] Modified the write_net_revenue.jl to take into account the addition of operating reserve and regulation revenue --- CHANGELOG.md | 1 + src/write_outputs/write_net_revenue.jl | 12 ++++++++++-- src/write_outputs/write_outputs.jl | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa54017d34..43c7b267b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new tests for GenX: three-zone, multi-stage, electrolyzer, VRE+storage, piecewise_fuel+CO2, and TDR (#563 and #578). - Added write_operating_reserve_price_revenue.jl to compute annual operating reserve and regulation revenue (PR # 611) +- Added the operating reserve and regulation revenue to net revenue ### Fixed diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index 5babd67e2d..f5ae27a957 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -3,7 +3,7 @@ Function for writing net revenue of different generation technologies. """ -function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame) +function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpResRevenue::DataFrame) dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -129,6 +129,14 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.SubsidyRevenue = dfSubRevenue[1:G,:SubsidyRevenue] # Unit is confirmed to be US$ end + # Add energy and subsidy revenue to the dataframe + dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) + dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) + if setuo["Reserves"] > 0 && has_duals(EP) == 1 + dfNetRevenue.OperatingReserveRevenue = dfOpResRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + dfNetRevenue.OperatingRegulationRevenue = dfOpRegRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + end + # Add capacity revenue to the dataframe dfNetRevenue.ReserveMarginRevenue = zeros(nrow(dfNetRevenue)) if setup["CapacityReserveMargin"] > 0 && has_duals(EP) == 1 # The unit is confirmed to be $ @@ -174,7 +182,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.RegSubsidyRevenue = dfRegSubRevenue[1:G,:SubsidyRevenue] end - dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue .+ dfNetRevenue.SubsidyRevenue .+ dfNetRevenue.ReserveMarginRevenue .+ dfNetRevenue.ESRRevenue .+ dfNetRevenue.RegSubsidyRevenue + dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue .+ dfNetRevenue.SubsidyRevenue .+ dfNetRevenue.ReserveMarginRevenue .+ dfNetRevenue.ESRRevenue .+ dfNetRevenue.RegSubsidyRevenue .+ dfNetRevenue.OperatingReserveRevenue .+ dfNetRevenue.OperatingRegulationRevenue dfNetRevenue.Cost = (dfNetRevenue.Inv_cost_MW .+ dfNetRevenue.Inv_cost_MWh .+ dfNetRevenue.Inv_cost_charge_MW diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 2e649def7d..9d2fbebe0a 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -206,7 +206,7 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end - elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor) + elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor, dfOpRegRevenue, dfOpResRevenue) println("Time elapsed for writing net revenue is") println(elapsed_time_net_rev) end From 7c12325577959c2022c23bea66ead8256f3fd88b Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 5 Feb 2024 12:29:48 -0500 Subject: [PATCH 10/30] Update write_net_revenue.jl typo fix for setup --- src/write_outputs/write_net_revenue.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index f5ae27a957..152ed8cf48 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -132,7 +132,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add energy and subsidy revenue to the dataframe dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) - if setuo["Reserves"] > 0 && has_duals(EP) == 1 + if setup["Reserves"] > 0 && has_duals(EP) == 1 dfNetRevenue.OperatingReserveRevenue = dfOpResRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ dfNetRevenue.OperatingRegulationRevenue = dfOpRegRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ end From 848b1920d08b5b7848ec4c913cc9c6be05595963 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 5 Feb 2024 15:36:05 -0500 Subject: [PATCH 11/30] Code refactoring for only selecting RSV and REG rows --- julenv.jl | 54 ------------------- package_activate.jl | 20 ------- .../write_operating_reserve_price_revenue.jl | 12 ++--- src/write_outputs/write_net_revenue.jl | 10 ++-- 4 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 julenv.jl delete mode 100644 package_activate.jl diff --git a/julenv.jl b/julenv.jl deleted file mode 100644 index c3a805b2f1..0000000000 --- a/julenv.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" -GenX: An Configurable Capacity Expansion Model -Copyright (C) 2021, Massachusetts Institute of Technology -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import Pkg -using Pkg -Pkg.activate("GenXJulEnv") - -Pkg.add(Pkg.PackageSpec(name="Cbc")) -Pkg.add(Pkg.PackageSpec(name="Clp")) -Pkg.add(Pkg.PackageSpec(name="Documenter")) -Pkg.add(Pkg.PackageSpec(name="DataStructures")) -Pkg.add(Pkg.PackageSpec(name="Dates")) -Pkg.add(Pkg.PackageSpec(name="JuMP")) -Pkg.add("MathOptInterface") -Pkg.add(Pkg.PackageSpec(name="SCIP")) -#Pkg.build("SCIP") -Pkg.add(Pkg.PackageSpec(name="HiGHS")) -############################################################################################ -#Uncomment either of the following two lines for the particular version of CPLEX.jl desired -############################################################################################ -#Pkg.add(Pkg.PackageSpec(name="CPLEX", version="0.6.1")) -#Pkg.add(Pkg.PackageSpec(name="CPLEX")) -############################################################################################ -#Uncomment either of the following two lines for the particular version of Gurobi.jl desired -############################################################################################ -#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.10.3")) -#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.11.3")) -Pkg.add(Pkg.PackageSpec(name="CSV")) -Pkg.add(Pkg.PackageSpec(name="Clustering")) -Pkg.add(Pkg.PackageSpec(name="Combinatorics")) -Pkg.add(Pkg.PackageSpec(name="Distances")) -Pkg.add(Pkg.PackageSpec(name="DataFrames")) -Pkg.add(Pkg.PackageSpec(name="Statistics")) -Pkg.add(Pkg.PackageSpec(name="OrdinaryDiffEq")) -Pkg.add(Pkg.PackageSpec(name="BenchmarkTools")) -Pkg.add(Pkg.PackageSpec(name="StatsBase")) -Pkg.add(Pkg.PackageSpec(name="YAML")) -Pkg.add(Pkg.PackageSpec(name="LinearAlgebra")) -Pkg.add(Pkg.PackageSpec(name="Random")) -Pkg.add(Pkg.PackageSpec(name="RecursiveArrayTools")) - diff --git a/package_activate.jl b/package_activate.jl deleted file mode 100644 index 75c76f6107..0000000000 --- a/package_activate.jl +++ /dev/null @@ -1,20 +0,0 @@ -""" -GenX: An Configurable Capacity Expansion Model -Copyright (C) 2021, Massachusetts Institute of Technology -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -using Pkg -include(joinpath(dirname(@__FILE__), "julenv.jl")) #Run this line only for the first time; comment it out for all subsequent use -println("Activating the Julia virtual environment") -Pkg.status() # Store the path of the current working directory diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 877c91273a..d2ad02f4c3 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -14,15 +14,15 @@ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, set T = inputs["T"] RSV = inputs["RSV"] REG = inputs["REG"] - dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) - dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) - resrevenue = zeros(G, T) - regrevenue = zeros(G, T) + dfOpResRevenue = DataFrame(Region = dfGen[RSV, :region], Resource = dfGen[RSV, :Resource], Zone = dfGen[RSV, :Zone], Cluster = dfGen[RSV, :cluster], AnnualSum = Array{Float64}(undef, length(RSV)),) + dfOpRegRevenue = DataFrame(Region = dfGen[REG, :region], Resource = dfGen[REG, :Resource], Zone = dfGen[REG, :Zone], Cluster = dfGen[REG, :cluster], AnnualSum = Array{Float64}(undef, length(REG)),) + #resrevenue = zeros(G, T) + #regrevenue = zeros(G, T) weighted_reg_price = operating_regulation_price(EP, inputs, setup) weighted_rsv_price = operating_reserve_price(EP, inputs, setup) - resrevenue[RSV, :] = value.(EP[:vRSV][RSV, :]).* transpose(weighted_rsv_price) + resrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) - regrevenue[REG, :] = value.(EP[:vREG][REG, :]) .* transpose(weighted_reg_price) + regrevenue = value.(EP[:vREG][REG, :].data) .* transpose(weighted_reg_price) if setup["ParameterScale"] == 1 resrevenue *= scale_factor diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index f5ae27a957..82f7b2b6d0 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -7,7 +7,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones - G = inputs["G"] # Number of generators + G = inputs["G"] + RSV = inputs["RSV"] + REG = inputs["REG"] # Number of generators COMMIT = inputs["COMMIT"] # Thermal units for unit commitment STOR_ALL = inputs["STOR_ALL"] VRE_STOR = inputs["VRE_STOR"] @@ -132,9 +134,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add energy and subsidy revenue to the dataframe dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) - if setuo["Reserves"] > 0 && has_duals(EP) == 1 - dfNetRevenue.OperatingReserveRevenue = dfOpResRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ - dfNetRevenue.OperatingRegulationRevenue = dfOpRegRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + if setup["Reserves"] > 0 && has_duals(EP) == 1 + dfNetRevenue.OperatingReserveRevenue[RSV] = dfOpResRevenue.AnnualSum # Unit is confirmed to be US$ + dfNetRevenue.OperatingRegulationRevenue[REG] = dfOpRegRevenue.AnnualSum # Unit is confirmed to be US$ end # Add capacity revenue to the dataframe From ee7626142316b44b3ab4d3b3eb8cd27024f9b373 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Thu, 11 Jan 2024 15:49:05 -0500 Subject: [PATCH 12/30] Create write_operating_reserve_price_revenue.jl For printing the operating reserve prices and revenues --- .../write_operating_reserve_price_revenue.jl | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl new file mode 100644 index 0000000000..5647d9bffb --- /dev/null +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -0,0 +1,64 @@ +@doc raw""" + write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) + +Function for reporting the operating reserve prices and revenue earned by each generator listed in the input file. + GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. + Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. + The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. + The last column is the total revenue received from all operating reserve constraints. + As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. +""" +function write_operating_reserve_price_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) + THERM_ALL = inputs["THERM_ALL"] + VRE = inputs["VRE"] + HYDRO_RES = inputs["HYDRO_RES"] + STOR_ALL = inputs["STOR_ALL"] + FLEX = inputs["FLEX"] + MUST_RUN = inputs["MUST_RUN"] + VRE_STOR = inputs["VRE_STOR"] + dfVRE_STOR = inputs["dfVRE_STOR"] + if !isempty(VRE_STOR) + VRE_STOR_STOR = inputs["VS_STOR"] + DC_DISCHARGE = inputs["VS_STOR_DC_DISCHARGE"] + AC_DISCHARGE = inputs["VS_STOR_AC_DISCHARGE"] + DC_CHARGE = inputs["VS_STOR_DC_CHARGE"] + AC_CHARGE = inputs["VS_STOR_AC_CHARGE"] + dfVRE_STOR = inputs["dfVRE_STOR"] + end + dfOpResRevenue = 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"] + sym = Symbol("CapRes_$i") + tempresrev = zeros(G) + 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, :] * weighted_price) + tempresrev[MUST_RUN] = dfGen[MUST_RUN, sym] .* (value.(EP[:eTotalCap][MUST_RUN])) .* (inputs["pP_Max"][MUST_RUN, :] * weighted_price) + tempresrev[HYDRO_RES] = dfGen[HYDRO_RES, sym] .* (value.(EP[:vP][HYDRO_RES, :]) * weighted_price) + if !isempty(STOR_ALL) + tempresrev[STOR_ALL] = dfGen[STOR_ALL, sym] .* ((value.(EP[:vP][STOR_ALL, :]) - value.(EP[:vCHARGE][STOR_ALL, :]).data + value.(EP[:vCAPRES_discharge][STOR_ALL, :]).data - value.(EP[:vCAPRES_charge][STOR_ALL, :]).data) * weighted_price) + end + if !isempty(FLEX) + tempresrev[FLEX] = dfGen[FLEX, sym] .* ((value.(EP[:vCHARGE_FLEX][FLEX, :]).data - value.(EP[:vP][FLEX, :])) * weighted_price) + end + if !isempty(VRE_STOR) + sym_vs = Symbol("CapResVreStor_$i") + tempresrev[VRE_STOR] = dfVRE_STOR[!, sym_vs] .* ((value.(EP[:vP][VRE_STOR, :])) * weighted_price) + tempresrev[VRE_STOR_STOR] .-= dfVRE_STOR[((dfVRE_STOR.STOR_DC_DISCHARGE.!=0) .| (dfVRE_STOR.STOR_DC_CHARGE.!=0) .| (dfVRE_STOR.STOR_AC_DISCHARGE.!=0) .|(dfVRE_STOR.STOR_AC_CHARGE.!=0)), sym_vs] .* (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, :]).data * weighted_price) + tempresrev[DC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, :]).data .* dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), :EtaInverter]) * weighted_price) + tempresrev[AC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_AC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, :]).data) * weighted_price) + 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]) * weighted_price) + tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) + end + tempresrev *= scale_factor + annual_sum .+= tempresrev + dfResRevenue = hcat(dfResRevenue, DataFrame([tempresrev], [sym])) + end + dfResRevenue.AnnualSum = annual_sum + CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) + return dfResRevenue +end From f522df700a4933520b92296c3e446cdc8bf85f55 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Thu, 11 Jan 2024 16:55:45 -0500 Subject: [PATCH 13/30] Update write_operating_reserve_price_revenue.jl --- .../write_operating_reserve_price_revenue.jl | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl index 5647d9bffb..392bec89d8 100644 --- a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -8,7 +8,7 @@ Function for reporting the operating reserve prices and revenue earned by each g The last column is the total revenue received from all operating reserve constraints. As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ -function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) +function write_operating_reserve_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) @@ -31,8 +31,9 @@ function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dic end dfOpResRevenue = 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"] + for t in 1:T + weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] sym = Symbol("CapRes_$i") tempresrev = zeros(G) tempresrev[THERM_ALL] = thermal_plant_effective_capacity(EP, inputs, THERM_ALL, i)' * weighted_price @@ -62,3 +63,15 @@ function write_operating_reserve_price_revenue(path::AbstractString, inputs::Dic CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) return dfResRevenue end + +function operating_regulation_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} + ω = inputs["omega"] + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + return dual.(EP[:cReg]) ./ ω * scale_factor +end + +function operating_reserve_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} + ω = inputs["omega"] + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + return dual.(EP[:cRsvReq]) ./ ω * scale_factor +end From bae5fee62abdec300b9f6e7de6ec4ef3b3f7984e Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Wed, 24 Jan 2024 15:57:21 -0500 Subject: [PATCH 14/30] Update write_operating_reserve_price_revenue.jl Partial updates to reserve and regulation price documentation and formulation --- .../write_operating_reserve_price_revenue.jl | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl index 392bec89d8..758336b09c 100644 --- a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl @@ -1,7 +1,7 @@ @doc raw""" write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) -Function for reporting the operating reserve prices and revenue earned by each generator listed in the input file. +Function for reporting the operating reserve prices and revenue earned by generators listed in the input file. GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. @@ -55,21 +55,48 @@ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, set 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]) * weighted_price) tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) end + tempregrev *= scale_factor tempresrev *= scale_factor - annual_sum .+= tempresrev - dfResRevenue = hcat(dfResRevenue, DataFrame([tempresrev], [sym])) + annual_reg_sum .+= tempregrev + annual_res_sum .+= tempresrev + dfOpRegRevenue = hcat(dfOpRegRevenue, DataFrame([tempregrev], [sym])) + dfOpResRevenue = hcat(dfOpResRevenue, DataFrame([tempresrev], [sym])) end - dfResRevenue.AnnualSum = annual_sum - CSV.write(joinpath(path, "ReserveMarginRevenue.csv"), dfResRevenue) - return dfResRevenue + dfOpRegRevenue.AnnualSum = annual_reg_sum + dfOpResRevenue.AnnualSum = annual_res_sum + CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) + CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + return dfOpRegRevenue, dfOpResRevenue end +@doc raw""" + operating_regulation_price(EP::Model, + inputs::Dict, + setup::Dict)::Vector{Float64} + +Operating regulation price for each time step. +This is equal to the dual variable of the regulatin requirement constraint. + + Returns a vector, with units of $/MW +""" + function operating_regulation_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} ω = inputs["omega"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 return dual.(EP[:cReg]) ./ ω * scale_factor end +@doc raw""" + operating_reserve_price(EP::Model, + inputs::Dict, + setup::Dict)::Vector{Float64} + +Operating reserve price for each time step. +This is equal to the dual variable of the reserve requirement constraint. + + Returns a vector, with units of $/MW +""" + function operating_reserve_price(EP::Model, inputs::Dict, setup::Dict)::Vector{Float64} ω = inputs["omega"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 From 3ac2cce011ad9e9ed04355140203aaf708001c53 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Wed, 24 Jan 2024 16:04:53 -0500 Subject: [PATCH 15/30] Moved wrtiting of operating reserve revenue from capacity reserve margin folder to reserve folder --- .../write_operating_reserve_price_revenue.jl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/write_outputs/{capacity_reserve_margin => reserves}/write_operating_reserve_price_revenue.jl (100%) diff --git a/src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl similarity index 100% rename from src/write_outputs/capacity_reserve_margin/write_operating_reserve_price_revenue.jl rename to src/write_outputs/reserves/write_operating_reserve_price_revenue.jl From 63e4ed8ba8021fcac9785b8fdc6db68f5d00d713 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 29 Jan 2024 09:08:56 -0500 Subject: [PATCH 16/30] Modified write__operating_reserve_price_revenue --- julenv.jl | 54 ++++++++++++++ package_activate.jl | 20 ++++++ src/load_inputs/load_cap_reserve_margin.jl | 1 + .../write_operating_reserve_price_revenue.jl | 72 ++++++------------- src/write_outputs/write_outputs.jl | 8 +++ 5 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 julenv.jl create mode 100644 package_activate.jl diff --git a/julenv.jl b/julenv.jl new file mode 100644 index 0000000000..c3a805b2f1 --- /dev/null +++ b/julenv.jl @@ -0,0 +1,54 @@ +""" +GenX: An Configurable Capacity Expansion Model +Copyright (C) 2021, Massachusetts Institute of Technology +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +A complete copy of the GNU General Public License v2 (GPLv2) is available +in LICENSE.txt. Users uncompressing this from an archive may not have +received this license file. If not, see . +""" + +import Pkg +using Pkg +Pkg.activate("GenXJulEnv") + +Pkg.add(Pkg.PackageSpec(name="Cbc")) +Pkg.add(Pkg.PackageSpec(name="Clp")) +Pkg.add(Pkg.PackageSpec(name="Documenter")) +Pkg.add(Pkg.PackageSpec(name="DataStructures")) +Pkg.add(Pkg.PackageSpec(name="Dates")) +Pkg.add(Pkg.PackageSpec(name="JuMP")) +Pkg.add("MathOptInterface") +Pkg.add(Pkg.PackageSpec(name="SCIP")) +#Pkg.build("SCIP") +Pkg.add(Pkg.PackageSpec(name="HiGHS")) +############################################################################################ +#Uncomment either of the following two lines for the particular version of CPLEX.jl desired +############################################################################################ +#Pkg.add(Pkg.PackageSpec(name="CPLEX", version="0.6.1")) +#Pkg.add(Pkg.PackageSpec(name="CPLEX")) +############################################################################################ +#Uncomment either of the following two lines for the particular version of Gurobi.jl desired +############################################################################################ +#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.10.3")) +#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.11.3")) +Pkg.add(Pkg.PackageSpec(name="CSV")) +Pkg.add(Pkg.PackageSpec(name="Clustering")) +Pkg.add(Pkg.PackageSpec(name="Combinatorics")) +Pkg.add(Pkg.PackageSpec(name="Distances")) +Pkg.add(Pkg.PackageSpec(name="DataFrames")) +Pkg.add(Pkg.PackageSpec(name="Statistics")) +Pkg.add(Pkg.PackageSpec(name="OrdinaryDiffEq")) +Pkg.add(Pkg.PackageSpec(name="BenchmarkTools")) +Pkg.add(Pkg.PackageSpec(name="StatsBase")) +Pkg.add(Pkg.PackageSpec(name="YAML")) +Pkg.add(Pkg.PackageSpec(name="LinearAlgebra")) +Pkg.add(Pkg.PackageSpec(name="Random")) +Pkg.add(Pkg.PackageSpec(name="RecursiveArrayTools")) + diff --git a/package_activate.jl b/package_activate.jl new file mode 100644 index 0000000000..75c76f6107 --- /dev/null +++ b/package_activate.jl @@ -0,0 +1,20 @@ +""" +GenX: An Configurable Capacity Expansion Model +Copyright (C) 2021, Massachusetts Institute of Technology +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +A complete copy of the GNU General Public License v2 (GPLv2) is available +in LICENSE.txt. Users uncompressing this from an archive may not have +received this license file. If not, see . +""" + +using Pkg +include(joinpath(dirname(@__FILE__), "julenv.jl")) #Run this line only for the first time; comment it out for all subsequent use +println("Activating the Julia virtual environment") +Pkg.status() # Store the path of the current working directory diff --git a/src/load_inputs/load_cap_reserve_margin.jl b/src/load_inputs/load_cap_reserve_margin.jl index 646385d078..c3a4ecd104 100644 --- a/src/load_inputs/load_cap_reserve_margin.jl +++ b/src/load_inputs/load_cap_reserve_margin.jl @@ -19,6 +19,7 @@ function load_cap_reserve_margin!(setup::Dict, path::AbstractString, inputs::Dic mat = extract_matrix_from_dataframe(df, "CapRes") inputs["dfCapRes"] = mat inputs["NCapacityReserveMargin"] = size(mat, 2) + print("Number of CRM types is ",inputs["NCapacityReserveMargin"]) println(filename * " Successfully Read!") end diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 758336b09c..20d2d65423 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -3,67 +3,35 @@ Function for reporting the operating reserve prices and revenue earned by generators listed in the input file. GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. - Each row corresponds to a generator, and each column starting from the 6th to the second last is the total revenue from each operating reserve constraint. The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. The last column is the total revenue received from all operating reserve constraints. As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + 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) - THERM_ALL = inputs["THERM_ALL"] - VRE = inputs["VRE"] - HYDRO_RES = inputs["HYDRO_RES"] - STOR_ALL = inputs["STOR_ALL"] - FLEX = inputs["FLEX"] - MUST_RUN = inputs["MUST_RUN"] - VRE_STOR = inputs["VRE_STOR"] - dfVRE_STOR = inputs["dfVRE_STOR"] - if !isempty(VRE_STOR) - VRE_STOR_STOR = inputs["VS_STOR"] - DC_DISCHARGE = inputs["VS_STOR_DC_DISCHARGE"] - AC_DISCHARGE = inputs["VS_STOR_AC_DISCHARGE"] - DC_CHARGE = inputs["VS_STOR_DC_CHARGE"] - AC_CHARGE = inputs["VS_STOR_AC_CHARGE"] - dfVRE_STOR = inputs["dfVRE_STOR"] - end dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - annual_sum = zeros(G) - for t in 1:T - weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] - weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] - sym = Symbol("CapRes_$i") - tempresrev = zeros(G) - 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, :] * weighted_price) - tempresrev[MUST_RUN] = dfGen[MUST_RUN, sym] .* (value.(EP[:eTotalCap][MUST_RUN])) .* (inputs["pP_Max"][MUST_RUN, :] * weighted_price) - tempresrev[HYDRO_RES] = dfGen[HYDRO_RES, sym] .* (value.(EP[:vP][HYDRO_RES, :]) * weighted_price) - if !isempty(STOR_ALL) - tempresrev[STOR_ALL] = dfGen[STOR_ALL, sym] .* ((value.(EP[:vP][STOR_ALL, :]) - value.(EP[:vCHARGE][STOR_ALL, :]).data + value.(EP[:vCAPRES_discharge][STOR_ALL, :]).data - value.(EP[:vCAPRES_charge][STOR_ALL, :]).data) * weighted_price) - end - if !isempty(FLEX) - tempresrev[FLEX] = dfGen[FLEX, sym] .* ((value.(EP[:vCHARGE_FLEX][FLEX, :]).data - value.(EP[:vP][FLEX, :])) * weighted_price) - end - if !isempty(VRE_STOR) - sym_vs = Symbol("CapResVreStor_$i") - tempresrev[VRE_STOR] = dfVRE_STOR[!, sym_vs] .* ((value.(EP[:vP][VRE_STOR, :])) * weighted_price) - tempresrev[VRE_STOR_STOR] .-= dfVRE_STOR[((dfVRE_STOR.STOR_DC_DISCHARGE.!=0) .| (dfVRE_STOR.STOR_DC_CHARGE.!=0) .| (dfVRE_STOR.STOR_AC_DISCHARGE.!=0) .|(dfVRE_STOR.STOR_AC_CHARGE.!=0)), sym_vs] .* (value.(EP[:vCHARGE_VRE_STOR][VRE_STOR_STOR, :]).data * weighted_price) - tempresrev[DC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_DC_DISCHARGE][DC_DISCHARGE, :]).data .* dfVRE_STOR[(dfVRE_STOR.STOR_DC_DISCHARGE.!=0), :EtaInverter]) * weighted_price) - tempresrev[AC_DISCHARGE] .+= dfVRE_STOR[(dfVRE_STOR.STOR_AC_DISCHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_DISCHARGE][AC_DISCHARGE, :]).data) * weighted_price) - 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]) * weighted_price) - tempresrev[AC_CHARGE] .-= dfVRE_STOR[(dfVRE_STOR.STOR_AC_CHARGE.!=0), sym_vs] .* ((value.(EP[:vCAPRES_AC_CHARGE][AC_CHARGE, :]).data) * weighted_price) - end - tempregrev *= scale_factor - tempresrev *= scale_factor - annual_reg_sum .+= tempregrev - annual_res_sum .+= tempresrev - dfOpRegRevenue = hcat(dfOpRegRevenue, DataFrame([tempregrev], [sym])) - dfOpResRevenue = hcat(dfOpResRevenue, DataFrame([tempresrev], [sym])) - end - dfOpRegRevenue.AnnualSum = annual_reg_sum - dfOpResRevenue.AnnualSum = annual_res_sum + dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) + #annual_sum = zeros(G) + weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] + #regsym = Symbol("Reg_Max") + #rsvsym = Symbol("Rsv_Max") + tempregrev = weighted_reg_price#zeros(G) + tempresrev = weighted_rsv_price#zeros(G) + tempregrev *= scale_factor + tempresrev *= scale_factor + #annual_reg_sum .+= tempregrev + #annual_res_sum .+= tempresrev + #print(DataFrame([tempregrev], :auto)) + #print(DataFrame([tempresrev], :auto)) + dfOpRegRevenue = DataFrame([tempregrev], :auto)#, [sym])) + dfOpResRevenue = DataFrame([tempresrev], :auto)#, [sym])) + + dfOpRegRevenue.AnnualSum = tempregrev#annual_reg_sum + dfOpResRevenue.AnnualSum = tempresrev#annual_res_sum CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) return dfOpRegRevenue, dfOpResRevenue diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 32abd1b6f0..2e649def7d 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -179,6 +179,14 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic dfResMar_slack = write_reserve_margin_slack(path, inputs, setup, EP) end end + + if setup["Reserves"]==1 && has_duals(EP) == 1 + dfOpRegRevenue, dfOpResRevenue = write_operating_reserve_revenue(path, inputs, setup, EP) + elapsed_time_op_res_rev = @elapsed write_operating_reserve_revenue(path, inputs, setup, EP) + println("Time elapsed for writing oerating reserve price is") + println(elapsed_time_op_res_rev) + end + if setup["CO2Cap"]>0 && has_duals(EP) == 1 dfCO2Cap = write_co2_cap(path, inputs, setup, EP) end From 36c439bfaacc06e05bf1d2c54d3ef9dc31c9320f Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:06:20 -0500 Subject: [PATCH 17/30] Updated operating reserve and regulation revenue categorized according to resources --- .../write_operating_reserve_price_revenue.jl | 45 +++++++++---------- src/write_outputs/write_energy_revenue.jl | 4 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 20d2d65423..877c91273a 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -10,30 +10,29 @@ Function for reporting the operating reserve prices and revenue earned by genera function write_operating_reserve_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) - dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster) - #annual_sum = zeros(G) - weighted_reg_price = operating_regulation_price(EP, inputs, setup) .* inputs["omega"] - weighted_rsv_price = operating_reserve_price(EP, inputs, setup) .* inputs["omega"] - #regsym = Symbol("Reg_Max") - #rsvsym = Symbol("Rsv_Max") - tempregrev = weighted_reg_price#zeros(G) - tempresrev = weighted_rsv_price#zeros(G) - tempregrev *= scale_factor - tempresrev *= scale_factor - #annual_reg_sum .+= tempregrev - #annual_res_sum .+= tempresrev - #print(DataFrame([tempregrev], :auto)) - #print(DataFrame([tempresrev], :auto)) - dfOpRegRevenue = DataFrame([tempregrev], :auto)#, [sym])) - dfOpResRevenue = DataFrame([tempresrev], :auto)#, [sym])) + G = inputs["G"] + T = inputs["T"] + RSV = inputs["RSV"] + REG = inputs["REG"] + dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) + dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) + resrevenue = zeros(G, T) + regrevenue = zeros(G, T) + weighted_reg_price = operating_regulation_price(EP, inputs, setup) + weighted_rsv_price = operating_reserve_price(EP, inputs, setup) + resrevenue[RSV, :] = value.(EP[:vRSV][RSV, :]).* transpose(weighted_rsv_price) - dfOpRegRevenue.AnnualSum = tempregrev#annual_reg_sum - dfOpResRevenue.AnnualSum = tempresrev#annual_res_sum - CSV.write(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) - CSV.write(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + regrevenue[REG, :] = value.(EP[:vREG][REG, :]) .* transpose(weighted_reg_price) + + if setup["ParameterScale"] == 1 + resrevenue *= scale_factor + regrevenue *= scale_factor + end + + dfOpResRevenue.AnnualSum .= resrevenue * inputs["omega"] + dfOpRegRevenue.AnnualSum .= regrevenue * inputs["omega"] + write_simple_csv(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + write_simple_csv(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) return dfOpRegRevenue, dfOpResRevenue end diff --git a/src/write_outputs/write_energy_revenue.jl b/src/write_outputs/write_energy_revenue.jl index f53abbaa5b..c94be4caa9 100644 --- a/src/write_outputs/write_energy_revenue.jl +++ b/src/write_outputs/write_energy_revenue.jl @@ -11,8 +11,8 @@ function write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, E NONFLEX = setdiff(collect(1:G), FLEX) dfEnergyRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) energyrevenue = zeros(G, T) - price = locational_marginal_price(EP, inputs, setup) - energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] + price = locational_marginal_price(EP, inputs, setup) + energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] if !isempty(FLEX) energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[dfGen[FLEX, :Zone], :] end From c0644e5b618adf35876594a882d881b5142a0cea Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:39:54 -0500 Subject: [PATCH 18/30] Updated operating reserve and regulation revenue doc pages --- docs/src/write_outputs.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/write_outputs.md b/docs/src/write_outputs.md index b87cd18082..0a421d6c2f 100644 --- a/docs/src/write_outputs.md +++ b/docs/src/write_outputs.md @@ -92,6 +92,11 @@ Pages = ["write_energy_revenue.jl"] Modules = [GenX] Pages = ["write_subsidy_revenue.jl"] ``` +## Write Operating Reserve and Regulation Revenue +```@autodocs +Modules = [GenX] +Pages = ["write_operating_reserve_price_revenue.jl"] +``` ## Write Capacity Revenue ```@autodocs From c5d03717b09a0be589739610eadc675bca66f515 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Sun, 4 Feb 2024 21:44:25 -0500 Subject: [PATCH 19/30] Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c410ec5cb..bcab544c6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Maintenance formulation for thermal-commit plants (#556). - Add new tests for GenX: three-zone, multi-stage, electrolyzer, VRE+storage, piecewise_fuel+CO2, and TDR (#563 and #578). +- Added write_operating_reserve_price_revenue.jl to compute annual operating reserve and regulation revenue (PR # 611) ### Fixed @@ -40,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Correctly scale total reserves column (in reg_dn.csv) (#594) - Add validation for `Reg_Max` and `Rsv_Max` columns in `Generators_data.csv` when `MUST_RUN` is set to 1 (#576) - Fix scaling of transmission losses in write_transmission_losses.jl (#621) +- Fixes issue #46 (#611) ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). From 34adab348a134b165260213c89642fe3cee20294 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 5 Feb 2024 12:06:59 -0500 Subject: [PATCH 20/30] Modified the write_net_revenue.jl to take into account the addition of operating reserve and regulation revenue --- CHANGELOG.md | 1 + src/write_outputs/write_net_revenue.jl | 12 ++++++++++-- src/write_outputs/write_outputs.jl | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcab544c6e..5ebed223ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add new tests for GenX: three-zone, multi-stage, electrolyzer, VRE+storage, piecewise_fuel+CO2, and TDR (#563 and #578). - Added write_operating_reserve_price_revenue.jl to compute annual operating reserve and regulation revenue (PR # 611) +- Added the operating reserve and regulation revenue to net revenue ### Fixed diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index eb17cd001b..6ed2650a90 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -3,7 +3,7 @@ Function for writing net revenue of different generation technologies. """ -function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame) +function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpResRevenue::DataFrame) dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -129,6 +129,14 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.SubsidyRevenue = dfSubRevenue[1:G,:SubsidyRevenue] # Unit is confirmed to be US$ end + # Add energy and subsidy revenue to the dataframe + dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) + dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) + if setuo["Reserves"] > 0 && has_duals(EP) == 1 + dfNetRevenue.OperatingReserveRevenue = dfOpResRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + dfNetRevenue.OperatingRegulationRevenue = dfOpRegRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + end + # Add capacity revenue to the dataframe dfNetRevenue.ReserveMarginRevenue = zeros(nrow(dfNetRevenue)) if setup["CapacityReserveMargin"] > 0 && has_duals(EP) == 1 # The unit is confirmed to be $ @@ -170,7 +178,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.RegSubsidyRevenue = dfRegSubRevenue[1:G,:SubsidyRevenue] end - dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue .+ dfNetRevenue.SubsidyRevenue .+ dfNetRevenue.ReserveMarginRevenue .+ dfNetRevenue.ESRRevenue .+ dfNetRevenue.RegSubsidyRevenue + dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue .+ dfNetRevenue.SubsidyRevenue .+ dfNetRevenue.ReserveMarginRevenue .+ dfNetRevenue.ESRRevenue .+ dfNetRevenue.RegSubsidyRevenue .+ dfNetRevenue.OperatingReserveRevenue .+ dfNetRevenue.OperatingRegulationRevenue dfNetRevenue.Cost = (dfNetRevenue.Inv_cost_MW .+ dfNetRevenue.Inv_cost_MWh .+ dfNetRevenue.Inv_cost_charge_MW diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 2e649def7d..9d2fbebe0a 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -206,7 +206,7 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end - elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor) + elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor, dfOpRegRevenue, dfOpResRevenue) println("Time elapsed for writing net revenue is") println(elapsed_time_net_rev) end From d777c1bde034b0323a298b5f1e09ccda8beea3ed Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Mon, 5 Feb 2024 15:36:05 -0500 Subject: [PATCH 21/30] Code refactoring for only selecting RSV and REG rows --- julenv.jl | 54 ------------------- package_activate.jl | 20 ------- .../write_operating_reserve_price_revenue.jl | 12 ++--- src/write_outputs/write_net_revenue.jl | 10 ++-- 4 files changed, 12 insertions(+), 84 deletions(-) delete mode 100644 julenv.jl delete mode 100644 package_activate.jl diff --git a/julenv.jl b/julenv.jl deleted file mode 100644 index c3a805b2f1..0000000000 --- a/julenv.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" -GenX: An Configurable Capacity Expansion Model -Copyright (C) 2021, Massachusetts Institute of Technology -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import Pkg -using Pkg -Pkg.activate("GenXJulEnv") - -Pkg.add(Pkg.PackageSpec(name="Cbc")) -Pkg.add(Pkg.PackageSpec(name="Clp")) -Pkg.add(Pkg.PackageSpec(name="Documenter")) -Pkg.add(Pkg.PackageSpec(name="DataStructures")) -Pkg.add(Pkg.PackageSpec(name="Dates")) -Pkg.add(Pkg.PackageSpec(name="JuMP")) -Pkg.add("MathOptInterface") -Pkg.add(Pkg.PackageSpec(name="SCIP")) -#Pkg.build("SCIP") -Pkg.add(Pkg.PackageSpec(name="HiGHS")) -############################################################################################ -#Uncomment either of the following two lines for the particular version of CPLEX.jl desired -############################################################################################ -#Pkg.add(Pkg.PackageSpec(name="CPLEX", version="0.6.1")) -#Pkg.add(Pkg.PackageSpec(name="CPLEX")) -############################################################################################ -#Uncomment either of the following two lines for the particular version of Gurobi.jl desired -############################################################################################ -#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.10.3")) -#Pkg.add(Pkg.PackageSpec(name="Gurobi", version="0.11.3")) -Pkg.add(Pkg.PackageSpec(name="CSV")) -Pkg.add(Pkg.PackageSpec(name="Clustering")) -Pkg.add(Pkg.PackageSpec(name="Combinatorics")) -Pkg.add(Pkg.PackageSpec(name="Distances")) -Pkg.add(Pkg.PackageSpec(name="DataFrames")) -Pkg.add(Pkg.PackageSpec(name="Statistics")) -Pkg.add(Pkg.PackageSpec(name="OrdinaryDiffEq")) -Pkg.add(Pkg.PackageSpec(name="BenchmarkTools")) -Pkg.add(Pkg.PackageSpec(name="StatsBase")) -Pkg.add(Pkg.PackageSpec(name="YAML")) -Pkg.add(Pkg.PackageSpec(name="LinearAlgebra")) -Pkg.add(Pkg.PackageSpec(name="Random")) -Pkg.add(Pkg.PackageSpec(name="RecursiveArrayTools")) - diff --git a/package_activate.jl b/package_activate.jl deleted file mode 100644 index 75c76f6107..0000000000 --- a/package_activate.jl +++ /dev/null @@ -1,20 +0,0 @@ -""" -GenX: An Configurable Capacity Expansion Model -Copyright (C) 2021, Massachusetts Institute of Technology -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -using Pkg -include(joinpath(dirname(@__FILE__), "julenv.jl")) #Run this line only for the first time; comment it out for all subsequent use -println("Activating the Julia virtual environment") -Pkg.status() # Store the path of the current working directory diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 877c91273a..d2ad02f4c3 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -14,15 +14,15 @@ function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, set T = inputs["T"] RSV = inputs["RSV"] REG = inputs["REG"] - dfOpResRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) - dfOpRegRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) - resrevenue = zeros(G, T) - regrevenue = zeros(G, T) + dfOpResRevenue = DataFrame(Region = dfGen[RSV, :region], Resource = dfGen[RSV, :Resource], Zone = dfGen[RSV, :Zone], Cluster = dfGen[RSV, :cluster], AnnualSum = Array{Float64}(undef, length(RSV)),) + dfOpRegRevenue = DataFrame(Region = dfGen[REG, :region], Resource = dfGen[REG, :Resource], Zone = dfGen[REG, :Zone], Cluster = dfGen[REG, :cluster], AnnualSum = Array{Float64}(undef, length(REG)),) + #resrevenue = zeros(G, T) + #regrevenue = zeros(G, T) weighted_reg_price = operating_regulation_price(EP, inputs, setup) weighted_rsv_price = operating_reserve_price(EP, inputs, setup) - resrevenue[RSV, :] = value.(EP[:vRSV][RSV, :]).* transpose(weighted_rsv_price) + resrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) - regrevenue[REG, :] = value.(EP[:vREG][REG, :]) .* transpose(weighted_reg_price) + regrevenue = value.(EP[:vREG][REG, :].data) .* transpose(weighted_reg_price) if setup["ParameterScale"] == 1 resrevenue *= scale_factor diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index 6ed2650a90..918f1e040d 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -7,7 +7,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones - G = inputs["G"] # Number of generators + G = inputs["G"] + RSV = inputs["RSV"] + REG = inputs["REG"] # Number of generators COMMIT = inputs["COMMIT"] # Thermal units for unit commitment STOR_ALL = inputs["STOR_ALL"] VRE_STOR = inputs["VRE_STOR"] @@ -132,9 +134,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add energy and subsidy revenue to the dataframe dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) - if setuo["Reserves"] > 0 && has_duals(EP) == 1 - dfNetRevenue.OperatingReserveRevenue = dfOpResRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ - dfNetRevenue.OperatingRegulationRevenue = dfOpRegRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ + if setup["Reserves"] > 0 && has_duals(EP) == 1 + dfNetRevenue.OperatingReserveRevenue[RSV] = dfOpResRevenue.AnnualSum # Unit is confirmed to be US$ + dfNetRevenue.OperatingRegulationRevenue[REG] = dfOpRegRevenue.AnnualSum # Unit is confirmed to be US$ end # Add capacity revenue to the dataframe From 2fbd18f0f34da59c1babb0eb6a52afcd25cb8078 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Mon, 5 Feb 2024 16:54:12 -0500 Subject: [PATCH 22/30] Clean and fix notation --- src/load_inputs/load_cap_reserve_margin.jl | 1 - .../write_operating_reserve_price_revenue.jl | 18 ++++++++---------- src/write_outputs/write_net_revenue.jl | 6 +++--- src/write_outputs/write_outputs.jl | 4 ++-- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/load_inputs/load_cap_reserve_margin.jl b/src/load_inputs/load_cap_reserve_margin.jl index c3a4ecd104..646385d078 100644 --- a/src/load_inputs/load_cap_reserve_margin.jl +++ b/src/load_inputs/load_cap_reserve_margin.jl @@ -19,7 +19,6 @@ function load_cap_reserve_margin!(setup::Dict, path::AbstractString, inputs::Dic mat = extract_matrix_from_dataframe(df, "CapRes") inputs["dfCapRes"] = mat inputs["NCapacityReserveMargin"] = size(mat, 2) - print("Number of CRM types is ",inputs["NCapacityReserveMargin"]) println(filename * " Successfully Read!") end diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index d2ad02f4c3..552b6616dc 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -10,30 +10,28 @@ Function for reporting the operating reserve prices and revenue earned by genera function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 dfGen = inputs["dfGen"] - G = inputs["G"] - T = inputs["T"] RSV = inputs["RSV"] REG = inputs["REG"] - dfOpResRevenue = DataFrame(Region = dfGen[RSV, :region], Resource = dfGen[RSV, :Resource], Zone = dfGen[RSV, :Zone], Cluster = dfGen[RSV, :cluster], AnnualSum = Array{Float64}(undef, length(RSV)),) + + dfOpRsvRevenue = DataFrame(Region = dfGen[RSV, :region], Resource = dfGen[RSV, :Resource], Zone = dfGen[RSV, :Zone], Cluster = dfGen[RSV, :cluster], AnnualSum = Array{Float64}(undef, length(RSV)),) dfOpRegRevenue = DataFrame(Region = dfGen[REG, :region], Resource = dfGen[REG, :Resource], Zone = dfGen[REG, :Zone], Cluster = dfGen[REG, :cluster], AnnualSum = Array{Float64}(undef, length(REG)),) - #resrevenue = zeros(G, T) - #regrevenue = zeros(G, T) + weighted_reg_price = operating_regulation_price(EP, inputs, setup) weighted_rsv_price = operating_reserve_price(EP, inputs, setup) - resrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) + rsvrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) regrevenue = value.(EP[:vREG][REG, :].data) .* transpose(weighted_reg_price) if setup["ParameterScale"] == 1 - resrevenue *= scale_factor + rsvrevenue *= scale_factor regrevenue *= scale_factor end - dfOpResRevenue.AnnualSum .= resrevenue * inputs["omega"] + dfOpRsvRevenue.AnnualSum .= rsvrevenue * inputs["omega"] dfOpRegRevenue.AnnualSum .= regrevenue * inputs["omega"] - write_simple_csv(joinpath(path, "OperatingReserveRevenue.csv"), dfOpResRevenue) + write_simple_csv(joinpath(path, "OperatingReserveRevenue.csv"), dfOpRsvRevenue) write_simple_csv(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) - return dfOpRegRevenue, dfOpResRevenue + return dfOpRegRevenue, dfOpRsvRevenue end @doc raw""" diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index 918f1e040d..d4f0d9e908 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -1,9 +1,9 @@ @doc raw""" - write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame) + write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpRsvRevenue::DataFrame) Function for writing net revenue of different generation technologies. """ -function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpResRevenue::DataFrame) +function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model, dfCap::DataFrame, dfESRRev::DataFrame, dfResRevenue::DataFrame, dfChargingcost::DataFrame, dfPower::DataFrame, dfEnergyRevenue::DataFrame, dfSubRevenue::DataFrame, dfRegSubRevenue::DataFrame, dfVreStor::DataFrame, dfOpRegRevenue::DataFrame, dfOpRsvRevenue::DataFrame) dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -135,7 +135,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) if setup["Reserves"] > 0 && has_duals(EP) == 1 - dfNetRevenue.OperatingReserveRevenue[RSV] = dfOpResRevenue.AnnualSum # Unit is confirmed to be US$ + dfNetRevenue.OperatingReserveRevenue[RSV] = dfOpRsvRevenue.AnnualSum # Unit is confirmed to be US$ dfNetRevenue.OperatingRegulationRevenue[REG] = dfOpRegRevenue.AnnualSum # Unit is confirmed to be US$ end diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 9d2fbebe0a..93c3ce41e2 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -181,7 +181,7 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end if setup["Reserves"]==1 && has_duals(EP) == 1 - dfOpRegRevenue, dfOpResRevenue = write_operating_reserve_revenue(path, inputs, setup, EP) + dfOpRegRevenue, dfOpRsvRevenue = write_operating_reserve_revenue(path, inputs, setup, EP) elapsed_time_op_res_rev = @elapsed write_operating_reserve_revenue(path, inputs, setup, EP) println("Time elapsed for writing oerating reserve price is") println(elapsed_time_op_res_rev) @@ -206,7 +206,7 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end - elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor, dfOpRegRevenue, dfOpResRevenue) + elapsed_time_net_rev = @elapsed write_net_revenue(path, inputs, setup, EP, dfCap, dfESRRev, dfResRevenue, dfChargingcost, dfPower, dfEnergyRevenue, dfSubRevenue, dfRegSubRevenue, dfVreStor, dfOpRegRevenue, dfOpRsvRevenue) println("Time elapsed for writing net revenue is") println(elapsed_time_net_rev) end From 5c3cca5edf0d9809f05022d78f762fe093db6b39 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Mon, 5 Feb 2024 16:56:37 -0500 Subject: [PATCH 23/30] Clean write_energy_revenue --- src/write_outputs/write_energy_revenue.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/write_outputs/write_energy_revenue.jl b/src/write_outputs/write_energy_revenue.jl index c94be4caa9..7fe49d13a5 100644 --- a/src/write_outputs/write_energy_revenue.jl +++ b/src/write_outputs/write_energy_revenue.jl @@ -11,8 +11,8 @@ function write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, E NONFLEX = setdiff(collect(1:G), FLEX) dfEnergyRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) energyrevenue = zeros(G, T) - price = locational_marginal_price(EP, inputs, setup) - energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] + price = locational_marginal_price(EP, inputs, setup) + energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] if !isempty(FLEX) energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[dfGen[FLEX, :Zone], :] end From a3342503e9f9937812b68a6c9c2d788b36fb2b5c Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 13:34:35 -0500 Subject: [PATCH 24/30] Update write_operating_reserve_price_revenue.jl Typo fix in documentation. Name of function changed from write_operating_reserve_revenue to write_operating_reserve_regulation_revenue, since the function outputs both reserve and regulation revenue --- .../write_operating_reserve_price_revenue.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 552b6616dc..425c0c73bb 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -1,13 +1,13 @@ @doc raw""" write_operating_reserve_price_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) -Function for reporting the operating reserve prices and revenue earned by generators listed in the input file. - GenX will print this file only when operating reserve is modeled and the shadow price can be obtained form the solver. - The revenue is calculated as the operating reserve contribution of each time steps multiplied by the shadow price, and then the sum is taken over all modeled time steps. - The last column is the total revenue received from all operating reserve constraints. - As a reminder, GenX models the operating reserve at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. +Function for reporting the operating reserve and regulation revenue earned by generators listed in the input file. + GenX will print this file only when operating reserve and regulation are modeled and the shadow price can be obtained from the solver. + The revenues are calculated as the operating reserve and regulation contributions in each time step multiplied by the corresponding shadow price, and then the sum is taken over all modeled time steps. + The last column is the total revenue received from all operating reserve and regulation constraints. + As a reminder, GenX models the operating reserve and regulation at the time-dependent level, and each constraint either stands for an overall market or a locality constraint. """ -function write_operating_reserve_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) +function write_operating_reserve_regulation_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 dfGen = inputs["dfGen"] RSV = inputs["RSV"] @@ -40,7 +40,7 @@ end setup::Dict)::Vector{Float64} Operating regulation price for each time step. -This is equal to the dual variable of the regulatin requirement constraint. +This is equal to the dual variable of the regulation requirement constraint. Returns a vector, with units of $/MW """ From 475aa47f3ef1a41f09c374738a9c862ff9b2db89 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 13:39:52 -0500 Subject: [PATCH 25/30] Update write_net_revenue.jl Replaced all the has_duals(EP)==1 by has_duals(EP), since has_duals(EP) itself returns Boolean --- src/write_outputs/write_net_revenue.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index d4f0d9e908..179b4586da 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -55,7 +55,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add operations and maintenance cost to the dataframe dfNetRevenue.Fixed_OM_cost_MW = dfGen[!,:Fixed_OM_Cost_per_MWyr] .* dfCap[1:G,:EndCap] dfNetRevenue.Fixed_OM_cost_MWh = dfGen[!,:Fixed_OM_Cost_per_MWhyr] .* dfCap[1:G,:EndEnergyCap] - dfNetRevenue.Fixed_OM_cost_charge_MW = dfGen[!, :Fixed_OM_Cost_Charge_per_MWyr] .* dfCap[1:G, :EndChargeCap] + dfNetRevenue.Fixed_OM_cost_charge_MW = dfGen[!, :Fixed_OM_Cost_Charge_per_MWyr] .* dfCap[1:G, :EndChargeCap] dfNetRevenue.Var_OM_cost_out = (dfGen[!,:Var_OM_Cost_per_MWh]) .* dfPower[1:G,:AnnualSum] if !isempty(VRE_STOR) @@ -119,14 +119,14 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: end # Add charge cost to the dataframe dfNetRevenue.Charge_cost = zeros(nrow(dfNetRevenue)) - if has_duals(EP) == 1 + if has_duals(EP) dfNetRevenue.Charge_cost = dfChargingcost[1:G,:AnnualSum] # Unit is confirmed to be US$ end # Add energy and subsidy revenue to the dataframe dfNetRevenue.EnergyRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.SubsidyRevenue = zeros(nrow(dfNetRevenue)) - if has_duals(EP) == 1 + if has_duals(EP) dfNetRevenue.EnergyRevenue = dfEnergyRevenue[1:G,:AnnualSum] # Unit is confirmed to be US$ dfNetRevenue.SubsidyRevenue = dfSubRevenue[1:G,:SubsidyRevenue] # Unit is confirmed to be US$ end @@ -134,26 +134,26 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add energy and subsidy revenue to the dataframe dfNetRevenue.OperatingReserveRevenue = zeros(nrow(dfNetRevenue)) dfNetRevenue.OperatingRegulationRevenue = zeros(nrow(dfNetRevenue)) - if setup["Reserves"] > 0 && has_duals(EP) == 1 + if setup["Reserves"] > 0 && has_duals(EP) dfNetRevenue.OperatingReserveRevenue[RSV] = dfOpRsvRevenue.AnnualSum # Unit is confirmed to be US$ dfNetRevenue.OperatingRegulationRevenue[REG] = dfOpRegRevenue.AnnualSum # Unit is confirmed to be US$ end # Add capacity revenue to the dataframe dfNetRevenue.ReserveMarginRevenue = zeros(nrow(dfNetRevenue)) - if setup["CapacityReserveMargin"] > 0 && has_duals(EP) == 1 # The unit is confirmed to be $ + if setup["CapacityReserveMargin"] > 0 && has_duals(EP) # The unit is confirmed to be $ dfNetRevenue.ReserveMarginRevenue = dfResRevenue[1:G,:AnnualSum] end # Add RPS/CES revenue to the dataframe dfNetRevenue.ESRRevenue = zeros(nrow(dfNetRevenue)) - if setup["EnergyShareRequirement"] > 0 && has_duals(EP) == 1 # The unit is confirmed to be $ + if setup["EnergyShareRequirement"] > 0 && has_duals(EP) # The unit is confirmed to be $ dfNetRevenue.ESRRevenue = dfESRRev[1:G,:Total] end # Calculate emissions cost dfNetRevenue.EmissionsCost = zeros(nrow(dfNetRevenue)) - if setup["CO2Cap"] >=1 && has_duals(EP) == 1 + if setup["CO2Cap"] >=1 && has_duals(EP) for cap in 1:inputs["NCO2Cap"] co2_cap_dual = dual(EP[:cCO2Emissions_systemwide][cap]) CO2ZONES = findall(x->x==1, inputs["dfCO2CapZones"][:,cap]) @@ -176,7 +176,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add regional technology subsidy revenue to the dataframe dfNetRevenue.RegSubsidyRevenue = zeros(nrow(dfNetRevenue)) - if setup["MinCapReq"] >= 1 && has_duals(EP) == 1 # The unit is confirmed to be US$ + if setup["MinCapReq"] >= 1 && has_duals(EP)# The unit is confirmed to be US$ dfNetRevenue.RegSubsidyRevenue = dfRegSubRevenue[1:G,:SubsidyRevenue] end From 3b3524356bd06ae7f4109c2634baa9e2fadee855 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 13:43:28 -0500 Subject: [PATCH 26/30] Update write_outputs.jl Changed the name of function write_operating_reserve_revenue to write_operating_reserve_regulation_revenue. Also replaced has_duals(EP)==1 by has_duals(EP) --- src/write_outputs/write_outputs.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 93c3ce41e2..21ffbd9cf8 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -155,17 +155,17 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end elapsed_time_time_weights = @elapsed write_time_weights(path, inputs) - println("Time elapsed for writing time weights is") - println(elapsed_time_time_weights) + println("Time elapsed for writing time weights is") + println(elapsed_time_time_weights) dfESR = DataFrame() dfESRRev = DataFrame() - if setup["EnergyShareRequirement"]==1 && has_duals(EP) == 1 + if setup["EnergyShareRequirement"]==1 && has_duals(EP) dfESR = write_esr_prices(path, inputs, setup, EP) dfESRRev = write_esr_revenue(path, inputs, setup, dfPower, dfESR, EP) end dfResMar = DataFrame() dfResRevenue = DataFrame() - if setup["CapacityReserveMargin"]==1 && has_duals(EP) == 1 + if setup["CapacityReserveMargin"]==1 && has_duals(EP) dfResMar = write_reserve_margin(path, setup, EP) elapsed_time_rsv_margin = @elapsed write_reserve_margin_w(path, inputs, setup, EP) dfVirtualDischarge = write_virtual_discharge(path, inputs, setup, EP) @@ -180,25 +180,25 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end end - if setup["Reserves"]==1 && has_duals(EP) == 1 - dfOpRegRevenue, dfOpRsvRevenue = write_operating_reserve_revenue(path, inputs, setup, EP) - elapsed_time_op_res_rev = @elapsed write_operating_reserve_revenue(path, inputs, setup, EP) - println("Time elapsed for writing oerating reserve price is") + if setup["Reserves"]==1 && has_duals(EP) + dfOpRegRevenue, dfOpRsvRevenue = write_operating_reserve_regulation_revenue(path, inputs, setup, EP) + elapsed_time_op_res_rev = @elapsed write_operating_reserve_regulation_revenue(path, inputs, setup, EP) + println("Time elapsed for writing oerating reserve and regulation revenue is") println(elapsed_time_op_res_rev) end - if setup["CO2Cap"]>0 && has_duals(EP) == 1 + if setup["CO2Cap"]>0 && has_duals(EP) dfCO2Cap = write_co2_cap(path, inputs, setup, EP) end - if setup["MinCapReq"] == 1 && has_duals(EP) == 1 + if setup["MinCapReq"] == 1 && has_duals(EP) dfMinCapReq = write_minimum_capacity_requirement(path, inputs, setup, EP) end - if setup["MaxCapReq"] == 1 && has_duals(EP) == 1 + if setup["MaxCapReq"] == 1 && has_duals(EP) dfMaxCapReq = write_maximum_capacity_requirement(path, inputs, setup, EP) end - if !isempty(inputs["ELECTROLYZER"]) && has_duals(EP) == 1 + if !isempty(inputs["ELECTROLYZER"]) && has_duals(EP) dfHydrogenPrice = write_hydrogen_prices(path, inputs, setup, EP) if setup["HydrogenHourlyMatching"] == 1 dfHourlyMatchingPrices = write_hourly_matching_prices(path, inputs, setup, EP) From 1f4a7f6c5e702e9edbeb48b570419366e5ddd2b3 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 13:45:17 -0500 Subject: [PATCH 27/30] Update write_operating_reserve_price_revenue.jl Got rid of the redundant if statement in scale factor --- .../reserves/write_operating_reserve_price_revenue.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 425c0c73bb..178d08c5c6 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -22,10 +22,9 @@ function write_operating_reserve_regulation_revenue(path::AbstractString, inputs rsvrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) regrevenue = value.(EP[:vREG][REG, :].data) .* transpose(weighted_reg_price) - if setup["ParameterScale"] == 1 - rsvrevenue *= scale_factor - regrevenue *= scale_factor - end + + rsvrevenue *= scale_factor + regrevenue *= scale_factor dfOpRsvRevenue.AnnualSum .= rsvrevenue * inputs["omega"] dfOpRegRevenue.AnnualSum .= regrevenue * inputs["omega"] From 99af8ba5ff0e38b0fa4cd46243e0ae4805711462 Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 16:34:40 -0500 Subject: [PATCH 28/30] Updated CHANGELOG.md --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 137048cfb8..d97bd8d10f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,13 +40,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix access of eELOSSByZone expr before initialization (#541) - Correctly write unmet reserves (in reg_dn.csv) (#575) - Correctly scale total reserves column (in reg_dn.csv) (#594) -<<<<<<< HEAD - Fixes issue #46 -======= - Add validation for `Reg_Max` and `Rsv_Max` columns in `Generators_data.csv` when `MUST_RUN` is set to 1 (#576) - Fix scaling of transmission losses in write_transmission_losses.jl (#621) - Fixes issue #46 (#611) ->>>>>>> 1f4a7f6c5e702e9edbeb48b570419366e5ddd2b3 ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). From 3cee2c0e7e8facd9467e30655ad3ca74e5977c7b Mon Sep 17 00:00:00 2001 From: "Chakrabarti, Sambuddha (Sam)" Date: Tue, 6 Feb 2024 16:57:03 -0500 Subject: [PATCH 29/30] Updated CHANGELOG.md to eliminate unnecessary entries in Fix, since this PR is a feature addition --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d97bd8d10f..a745fe12e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,10 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix access of eELOSSByZone expr before initialization (#541) - Correctly write unmet reserves (in reg_dn.csv) (#575) - Correctly scale total reserves column (in reg_dn.csv) (#594) -- Fixes issue #46 - Add validation for `Reg_Max` and `Rsv_Max` columns in `Generators_data.csv` when `MUST_RUN` is set to 1 (#576) - Fix scaling of transmission losses in write_transmission_losses.jl (#621) -- Fixes issue #46 (#611) ### Changed - Use add_to_expression! instead of the += and -= operators for memory performance improvements (#498). From 6503a17af400899ced267c739a4ec884187511f1 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Thu, 8 Feb 2024 17:56:53 -0500 Subject: [PATCH 30/30] Cleanup --- docs/src/write_outputs.md | 1 + src/load_inputs/load_cap_reserve_margin.jl | 1 - .../write_operating_reserve_price_revenue.jl | 4 ++-- src/write_outputs/write_energy_revenue.jl | 4 ++-- src/write_outputs/write_net_revenue.jl | 17 ++++++++++++----- src/write_outputs/write_outputs.jl | 7 +++---- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/src/write_outputs.md b/docs/src/write_outputs.md index 0a421d6c2f..6940ea5e53 100644 --- a/docs/src/write_outputs.md +++ b/docs/src/write_outputs.md @@ -92,6 +92,7 @@ Pages = ["write_energy_revenue.jl"] Modules = [GenX] Pages = ["write_subsidy_revenue.jl"] ``` + ## Write Operating Reserve and Regulation Revenue ```@autodocs Modules = [GenX] diff --git a/src/load_inputs/load_cap_reserve_margin.jl b/src/load_inputs/load_cap_reserve_margin.jl index c3a4ecd104..646385d078 100644 --- a/src/load_inputs/load_cap_reserve_margin.jl +++ b/src/load_inputs/load_cap_reserve_margin.jl @@ -19,7 +19,6 @@ function load_cap_reserve_margin!(setup::Dict, path::AbstractString, inputs::Dic mat = extract_matrix_from_dataframe(df, "CapRes") inputs["dfCapRes"] = mat inputs["NCapacityReserveMargin"] = size(mat, 2) - print("Number of CRM types is ",inputs["NCapacityReserveMargin"]) println(filename * " Successfully Read!") end diff --git a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl index 178d08c5c6..769c8b9646 100644 --- a/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl +++ b/src/write_outputs/reserves/write_operating_reserve_price_revenue.jl @@ -19,15 +19,15 @@ function write_operating_reserve_regulation_revenue(path::AbstractString, inputs weighted_reg_price = operating_regulation_price(EP, inputs, setup) weighted_rsv_price = operating_reserve_price(EP, inputs, setup) - rsvrevenue = value.(EP[:vRSV][RSV, :].data).* transpose(weighted_rsv_price) + rsvrevenue = value.(EP[:vRSV][RSV, :].data) .* transpose(weighted_rsv_price) regrevenue = value.(EP[:vREG][REG, :].data) .* transpose(weighted_reg_price) - rsvrevenue *= scale_factor regrevenue *= scale_factor dfOpRsvRevenue.AnnualSum .= rsvrevenue * inputs["omega"] dfOpRegRevenue.AnnualSum .= regrevenue * inputs["omega"] + write_simple_csv(joinpath(path, "OperatingReserveRevenue.csv"), dfOpRsvRevenue) write_simple_csv(joinpath(path, "OperatingRegulationRevenue.csv"), dfOpRegRevenue) return dfOpRegRevenue, dfOpRsvRevenue diff --git a/src/write_outputs/write_energy_revenue.jl b/src/write_outputs/write_energy_revenue.jl index 7fe49d13a5..f53abbaa5b 100644 --- a/src/write_outputs/write_energy_revenue.jl +++ b/src/write_outputs/write_energy_revenue.jl @@ -11,8 +11,8 @@ function write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, E NONFLEX = setdiff(collect(1:G), FLEX) dfEnergyRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) energyrevenue = zeros(G, T) - price = locational_marginal_price(EP, inputs, setup) - energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] + price = locational_marginal_price(EP, inputs, setup) + energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[dfGen[NONFLEX, :Zone], :] if !isempty(FLEX) energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[dfGen[FLEX, :Zone], :] end diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index 179b4586da..073bfe25dd 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -7,9 +7,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfGen = inputs["dfGen"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones - G = inputs["G"] - RSV = inputs["RSV"] - REG = inputs["REG"] # Number of generators + G = inputs["G"] # Number of generators + RSV = inputs["RSV"] # Generators contributing to operating reserves + REG = inputs["REG"] # Generators contributing to regulation COMMIT = inputs["COMMIT"] # Thermal units for unit commitment STOR_ALL = inputs["STOR_ALL"] VRE_STOR = inputs["VRE_STOR"] @@ -55,7 +55,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add operations and maintenance cost to the dataframe dfNetRevenue.Fixed_OM_cost_MW = dfGen[!,:Fixed_OM_Cost_per_MWyr] .* dfCap[1:G,:EndCap] dfNetRevenue.Fixed_OM_cost_MWh = dfGen[!,:Fixed_OM_Cost_per_MWhyr] .* dfCap[1:G,:EndEnergyCap] - dfNetRevenue.Fixed_OM_cost_charge_MW = dfGen[!, :Fixed_OM_Cost_Charge_per_MWyr] .* dfCap[1:G, :EndChargeCap] + dfNetRevenue.Fixed_OM_cost_charge_MW = dfGen[!, :Fixed_OM_Cost_Charge_per_MWyr] .* dfCap[1:G, :EndChargeCap] dfNetRevenue.Var_OM_cost_out = (dfGen[!,:Var_OM_Cost_per_MWh]) .* dfPower[1:G,:AnnualSum] if !isempty(VRE_STOR) @@ -180,7 +180,14 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: dfNetRevenue.RegSubsidyRevenue = dfRegSubRevenue[1:G,:SubsidyRevenue] end - dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue .+ dfNetRevenue.SubsidyRevenue .+ dfNetRevenue.ReserveMarginRevenue .+ dfNetRevenue.ESRRevenue .+ dfNetRevenue.RegSubsidyRevenue .+ dfNetRevenue.OperatingReserveRevenue .+ dfNetRevenue.OperatingRegulationRevenue + dfNetRevenue.Revenue = dfNetRevenue.EnergyRevenue + .+ dfNetRevenue.SubsidyRevenue + .+ dfNetRevenue.ReserveMarginRevenue + .+ dfNetRevenue.ESRRevenue + .+ dfNetRevenue.RegSubsidyRevenue + .+ dfNetRevenue.OperatingReserveRevenue + .+ dfNetRevenue.OperatingRegulationRevenue + dfNetRevenue.Cost = (dfNetRevenue.Inv_cost_MW .+ dfNetRevenue.Inv_cost_MWh .+ dfNetRevenue.Inv_cost_charge_MW diff --git a/src/write_outputs/write_outputs.jl b/src/write_outputs/write_outputs.jl index 21ffbd9cf8..8b7bedcf83 100644 --- a/src/write_outputs/write_outputs.jl +++ b/src/write_outputs/write_outputs.jl @@ -155,8 +155,8 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end elapsed_time_time_weights = @elapsed write_time_weights(path, inputs) - println("Time elapsed for writing time weights is") - println(elapsed_time_time_weights) + println("Time elapsed for writing time weights is") + println(elapsed_time_time_weights) dfESR = DataFrame() dfESRRev = DataFrame() if setup["EnergyShareRequirement"]==1 && has_duals(EP) @@ -181,8 +181,7 @@ function write_outputs(EP::Model, path::AbstractString, setup::Dict, inputs::Dic end if setup["Reserves"]==1 && has_duals(EP) - dfOpRegRevenue, dfOpRsvRevenue = write_operating_reserve_regulation_revenue(path, inputs, setup, EP) - elapsed_time_op_res_rev = @elapsed write_operating_reserve_regulation_revenue(path, inputs, setup, EP) + elapsed_time_op_res_rev = @elapsed dfOpRegRevenue, dfOpRsvRevenue = write_operating_reserve_regulation_revenue(path, inputs, setup, EP) println("Time elapsed for writing oerating reserve and regulation revenue is") println(elapsed_time_op_res_rev) end