diff --git a/Example_Systems/RealSystemExample/ISONE_Trizone/Capacity_reserve_margin.csv b/Example_Systems/RealSystemExample/ISONE_Trizone/Capacity_reserve_margin.csv deleted file mode 100644 index c4ce6c337d..0000000000 --- a/Example_Systems/RealSystemExample/ISONE_Trizone/Capacity_reserve_margin.csv +++ /dev/null @@ -1,4 +0,0 @@ -,Network_zones,CapRes_1 -NENGREST,z1,0.156 -NENG_CT,z2,0.156 -NENG_ME,z3,0.156 diff --git a/src/GenX.jl b/src/GenX.jl index 6546f2ddf4..4e46f17fc6 100644 --- a/src/GenX.jl +++ b/src/GenX.jl @@ -45,6 +45,11 @@ using HiGHS # To translate $/MWh to $M/GWh, multiply by ModelScalingFactor const ModelScalingFactor = 1e+3 +# abstract type for all resources +abstract type AbstractResource end +# Name of the type of resources available in the model +const resources_type = (:ELECTROLYZER, :FLEX, :HYDRO, :STOR, :THERM, :VRE, :MUST_RUN) + # thanks, ChatGPT function include_all_in_folder(folder) base_path = joinpath(@__DIR__, folder) @@ -60,8 +65,8 @@ end include_all_in_folder("case_runners") include_all_in_folder("configure_settings") include_all_in_folder("configure_solver") -include_all_in_folder("model") include_all_in_folder("load_inputs") +include_all_in_folder("model") include_all_in_folder("write_outputs") include("time_domain_reduction/time_domain_reduction.jl") @@ -72,4 +77,5 @@ include("simple_operation.jl") include_all_in_folder("multi_stage") include_all_in_folder("additional_tools") + end diff --git a/src/additional_tools/modeling_to_generate_alternatives.jl b/src/additional_tools/modeling_to_generate_alternatives.jl index 7ff0e80c68..6786dbc13b 100644 --- a/src/additional_tools/modeling_to_generate_alternatives.jl +++ b/src/additional_tools/modeling_to_generate_alternatives.jl @@ -27,13 +27,13 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict, outpath Least_System_Cost = objective_value(EP) # Read sets - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zonests zones = unique(inputs["R_ZONES"]) # Create a set of unique technology types - resources_with_mga = has_mga_on(resources) + resources_with_mga = has_mga_on(res) TechTypes = unique(resource_type.(resources_with_mga)) # Read slack parameter representing desired increase in budget from the least cost solution @@ -52,8 +52,8 @@ function mga(EP::Model, path::AbstractString, setup::Dict, inputs::Dict, outpath # Constraint to compute total generation in each zone from a given Technology Type function resource_in_zone_with_TechType(tt::Int64, z::Int64) - condition::BitVector = (resource_type.(resources) .== TechTypes[tt]) .& (zone_id.(resources) .== z) - return resource_id.(resources[condition]) + condition::BitVector = (resource_type.(res) .== TechTypes[tt]) .& (zone_id.(res) .== z) + return resource_id.(res[condition]) end @constraint(EP,cGeneration[tt = 1:length(TechTypes), z = 1:Z], vSumvP[tt,z] == sum(EP[:vP][y,t] * inputs["omega"][t] for y in resource_in_zone_with_TechType(tt,z), t in 1:T)) diff --git a/src/load_inputs/load_generators_data.jl b/src/load_inputs/load_generators_data.jl index 8a01ea984f..501091bf17 100644 --- a/src/load_inputs/load_generators_data.jl +++ b/src/load_inputs/load_generators_data.jl @@ -15,7 +15,7 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di # initial screen that resources are valid resources = dataframerow_to_dict.(eachrow(gen_in)) validate_resources(resources) - inputs_gen["resources_d"] = resources + inputs_gen["RESOURCES"] = resources # Number of resources (generators, storage, DR, and DERs) G = nrow(gen_in) @@ -520,8 +520,6 @@ end function process_piecewisefuelusage!(inputs::Dict, scale_factor) gen_in = inputs["dfGen"] - inputs["PWFU_Num_Segments"] = 0 - inputs["THERM_COMMIT_PWFU"] = Int64[] if any(occursin.(Ref("PWFU_"), names(gen_in))) heat_rate_mat = extract_matrix_from_dataframe(gen_in, "PWFU_Heat_Rate_MMBTU_per_MWh") diff --git a/src/load_inputs/load_inputs.jl b/src/load_inputs/load_inputs.jl index 506136943a..6c775f4b3c 100644 --- a/src/load_inputs/load_inputs.jl +++ b/src/load_inputs/load_inputs.jl @@ -29,7 +29,6 @@ function load_inputs(setup::Dict,path::AbstractString) load_fuels_data!(setup, path, inputs) # Read in generator/resource related inputs load_resources_data!(setup, path, inputs) - return inputs # Read in generator/resource availability profiles load_generators_variability!(setup, path, inputs) diff --git a/src/load_inputs/load_reserves.jl b/src/load_inputs/load_reserves.jl index 6c82fb3fa5..830565c1ea 100644 --- a/src/load_inputs/load_reserves.jl +++ b/src/load_inputs/load_reserves.jl @@ -7,7 +7,7 @@ function load_reserves!(setup::Dict, path::AbstractString, inputs::Dict) filename = "Reserves.csv" res_in = load_dataframe(joinpath(path, filename)) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] function load_field_with_deprecated_symbol(df::DataFrame, columns::Vector{Symbol}) best = popfirst!(columns) @@ -52,11 +52,11 @@ function load_reserves!(setup::Dict, path::AbstractString, inputs::Dict) if inputs["pDynamic_Contingency"] > 0 inputs["pContingency_BigM"] = zeros(Float64, inputs["G"]) for y in inputs["COMMIT"] - inputs["pContingency_BigM"][y] = max_capacity_mw(resources[y]) + inputs["pContingency_BigM"][y] = max_capacity_mw(res[y]) # When Max_Cap_MW == -1, there is no limit on capacity size if inputs["pContingency_BigM"][y] < 0 # NOTE: this effectively acts as a maximum cluster size when not otherwise specified, adjust accordingly - inputs["pContingency_BigM"][y] = 5000 * cap_size(resources[y]) + inputs["pContingency_BigM"][y] = 5000 * cap_size(res[y]) end end end diff --git a/src/load_inputs/load_resources_data.jl b/src/load_inputs/load_resources_data.jl index bef67a6e73..f76b42c478 100644 --- a/src/load_inputs/load_resources_data.jl +++ b/src/load_inputs/load_resources_data.jl @@ -6,6 +6,7 @@ function _get_resource_info() storage = (filename="storage.csv", type=STOR), #key="STOR_ALL"), flex_demand = (filename="flex_demand.csv", type=FLEX), #key="FLEX"), electrolyzer = (filename="electrolyzer.csv", type=ELECTROLYZER), #key="ELECTROLYZER") + must_run = (filename="must_run.csv", type=MUST_RUN) #key="MUST_RUN") ) return resources end @@ -80,9 +81,6 @@ function _get_resource_df(path::AbstractString, scale_factor::Float64=1.0) scale_resources_data!(resource_in, scale_factor) # ensure columns ensure_columns!(resource_in) - - println("co2_capture_fraction" ∈ names(resource_in)) - # return dataframe return resource_in end @@ -115,8 +113,6 @@ function _get_all_resources(resources_folder::AbstractString, resources_info::Na resource_id_offset += length(resources_same_type) # print log @info filename * " Successfully Read." - # add indices to input_data - # input_data[key] = resources_indices end return reduce(vcat, resources) end @@ -135,11 +131,13 @@ end function load_resources_data!(setup::Dict, case_path::AbstractString, input_data::Dict) if isfile(joinpath(case_path, "Generators_data.csv")) Base.depwarn( - "The `Generators_data.csv` file will be deprecated in a future release. " * + "The `Generators_data.csv` file was deprecated in release v0.4. " * "Please use the new interface for generators creation, and see the documentation for additional details.", :load_resources_data!, force=true) - load_generators_data!(setup, case_path, input_data) - translate_generators_data!(setup, input_data) + @info "Exiting GenX..." + exit(-1) + # load_generators_data!(setup, case_path, input_data) + # translate_generators_data!(setup, input_data) else # Scale factor for energy and currency units scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 @@ -206,13 +204,14 @@ function add_resources_to_input_data!(setup::Dict, input_data::Dict, resources:: ## TODO: MUST_RUN # Set of must-run plants - could be behind-the-meter PV, hydro run-of-river, must-run fossil or thermal plants - # input_data["MUST_RUN"] = must_run(resources) + input_data["MUST_RUN"] = must_run(resources) ## ELECTROLYZER # Set of hydrogen electolyzer resources: input_data["ELECTROLYZER"] = electrolyzer(resources) ## Retrofit ## TODO: ask how to add it + input_data["RETRO"] = [] ## Reserves if setup["Reserves"] >= 1 @@ -238,6 +237,8 @@ function add_resources_to_input_data!(setup::Dict, input_data::Dict, resources:: input_data["C_Start"][g,:] .= start_up_cost end # Piecewise fuel usage option + input_data["PWFU_Num_Segments"] = 0 + input_data["THERM_COMMIT_PWFU"] = Int64[] # process_piecewisefuelusage!(input_data, scale_factor) else # Set of thermal resources with unit commitment @@ -250,6 +251,7 @@ function add_resources_to_input_data!(setup::Dict, input_data::Dict, resources:: ## Co-located resources # VRE and storage + input_data["VRE_STOR"] = [] # load_vre_stor_data!(input_data, setup, path) buildable = is_buildable(resources) diff --git a/src/model/core/co2.jl b/src/model/core/co2.jl index a054d08d0b..cbca927b26 100644 --- a/src/model/core/co2.jl +++ b/src/model/core/co2.jl @@ -54,36 +54,30 @@ function co2!(EP::Model, inputs::Dict) println("CO2 Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones fuel_CO2 = inputs["fuel_CO2"] # CO2 content of fuel (t CO2/MMBTU or ktCO2/Billion BTU) - biomass(y::Int64) = biomass(resources[y]) - fuel(y::Int64) = fuel(resources[y]) - co2_capture_fraction(y::Int64) = co2_capture_fraction(resources[y]) - co2_capture_fraction_startup(y::Int64) = co2_capture_fraction_startup(resources[y]) - ccs_disposal_cost_per_metric_ton(y::Int64) = ccs_disposal_cost_per_metric_ton(resources[y]) - ### Expressions ### # CO2 emissions from power plants in "Generators_data.csv" # If all the CO2 capture fractions from Generators_data are zeros, the CO2 emissions from thermal generators are determined by fuel consumption times CO2 content per MMBTU - if all(co2_capture_fraction(resources) .==0) + if all(co2_capture_fraction.(res) .==0) @expression(EP, eEmissionsByPlant[y=1:G, t=1:T], - ((1-biomass(y)) *(EP[:vFuel][y, t] + EP[:eStartFuel][y, t]) * fuel_CO2[fuel(y)])) + ((1-biomass(res[y])) *(EP[:vFuel][y, t] + EP[:eStartFuel][y, t]) * fuel_CO2[fuel(res[y])])) else @info "Using the CO2 module to determine the CO2 emissions of CCS-equipped plants" # CO2_Capture_Fraction refers to the CO2 capture rate of CCS equiped power plants at a steady state # CO2_Capture_Fraction_Startup refers to the CO2 capture rate of CCS equiped power plants during startup events @expression(EP, eEmissionsByPlant[y=1:G, t=1:T], - (1-biomass(y) - co2_capture_fraction(y)) * EP[:vFuel][y, t] * fuel_CO2[fuel(y)]+ - (1-biomass(y) - co2_capture_fraction_startup(y)) * EP[:eStartFuel][y, t] * fuel_CO2[fuel(y)]) + (1-biomass(res[y]) - co2_capture_fraction(res[y])) * EP[:vFuel][y, t] * fuel_CO2[fuel(res[y])]+ + (1-biomass(res[y]) - co2_capture_fraction_startup(res[y])) * EP[:eStartFuel][y, t] * fuel_CO2[fuel(res[y])]) # CO2 captured from power plants in "Generators_data.csv" @expression(EP, eEmissionsCaptureByPlant[y=1:G, t=1:T], - co2_capture_fraction(y) * EP[:vFuel][y, t] * fuel_CO2[fuel(y)]+ - co2_capture_fraction_startup(y) * EP[:eStartFuel][y, t] * fuel_CO2[fuel(y)]) + co2_capture_fraction(res[y]) * EP[:vFuel][y, t] * fuel_CO2[fuel(res[y])]+ + co2_capture_fraction_startup(res[y]) * EP[:eStartFuel][y, t] * fuel_CO2[fuel(res[y])]) @expression(EP, eEmissionsCaptureByPlantYear[y=1:G], sum(inputs["omega"][t] * eEmissionsCaptureByPlant[y, t] @@ -92,10 +86,10 @@ function co2!(EP::Model, inputs::Dict) # when scale factor is on tCO2/MWh = > kt CO2/GWh @expression(EP, ePlantCCO2Sequestration[y=1:G], sum(inputs["omega"][t] * eEmissionsCaptureByPlant[y, t] * - ccs_disposal_cost_per_metric_ton(y) for t in 1:T)) + ccs_disposal_cost_per_metric_ton(res[y]) for t in 1:T)) @expression(EP, eZonalCCO2Sequestration[z=1:Z], - sum(ePlantCCO2Sequestration[y] for y in resources_in_zone_by_rid(resources,z))) + sum(ePlantCCO2Sequestration[y] for y in resources_in_zone_by_rid(res,z))) @expression(EP, eTotaleCCO2Sequestration, sum(eZonalCCO2Sequestration[z] for z in 1:Z)) @@ -105,7 +99,7 @@ function co2!(EP::Model, inputs::Dict) # emissions by zone @expression(EP, eEmissionsByZone[z = 1:Z, t = 1:T], - sum(eEmissionsByPlant[y, t] for y in resources_in_zone_by_rid(resources,z))) + sum(eEmissionsByPlant[y, t] for y in resources_in_zone_by_rid(res,z))) return EP end diff --git a/src/model/core/discharge/discharge.jl b/src/model/core/discharge/discharge.jl index 1c9b5ce494..e8600c8a6d 100644 --- a/src/model/core/discharge/discharge.jl +++ b/src/model/core/discharge/discharge.jl @@ -13,8 +13,7 @@ function discharge!(EP::Model, inputs::Dict, setup::Dict) println("Discharge Module") - resources = inputs["RESOURCES"] - var_om_cost_per_mwh(y) = var_om_cost_per_mwh(resources[y]) + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps @@ -29,7 +28,7 @@ function discharge!(EP::Model, inputs::Dict, setup::Dict) ## Objective Function Expressions ## # Variable costs of "generation" for resource "y" during hour "t" = variable O&M - @expression(EP, eCVar_out[y=1:G,t=1:T], (inputs["omega"][t]*(var_om_cost_per_mwh(y)*vP[y,t]))) + @expression(EP, eCVar_out[y=1:G,t=1:T], (inputs["omega"][t]*(var_om_cost_per_mwh(res[y])*vP[y,t]))) # Sum individual resource contributions to variable discharging costs to get total variable discharging costs @expression(EP, eTotalCVarOutT[t=1:T], sum(eCVar_out[y,t] for y in 1:G)) @expression(EP, eTotalCVarOut, sum(eTotalCVarOutT[t] for t in 1:T)) diff --git a/src/model/core/discharge/investment_discharge.jl b/src/model/core/discharge/investment_discharge.jl index 99eb51af16..b60d9fcb63 100644 --- a/src/model/core/discharge/investment_discharge.jl +++ b/src/model/core/discharge/investment_discharge.jl @@ -37,14 +37,7 @@ function investment_discharge!(EP::Model, inputs::Dict, setup::Dict) println("Investment Discharge Module") MultiStage = setup["MultiStage"] - resources = inputs["RESOURCES"] - cap_size(y) = cap_size(resources[y]) - existing_capacity_mw(y) = existing_capacity_mw(resources[y]) - max_capacity_mw(y) = max_capacity_mw(resources[y]) - min_capacity_mw(y) = min_capacity_mw(resources[y]) - inv_cost_per_mwyr(y) = inv_cost_per_mwyr(resources[y]) - fixed_om_cost_per_mwyr(y) = fixed_om_cost_per_mwyr(resources[y]) - + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) @@ -87,7 +80,7 @@ function investment_discharge!(EP::Model, inputs::Dict, setup::Dict) if MultiStage == 1 @expression(EP, eExistingCap[y in 1:G], vEXISTINGCAP[y]) else - @expression(EP, eExistingCap[y in 1:G], existing_capacity_mw(y)) + @expression(EP, eExistingCap[y in 1:G], existing_capacity_mw(res[y])) end # Cap_Size is set to 1 for all variables when unit UCommit == 0 @@ -95,19 +88,19 @@ function investment_discharge!(EP::Model, inputs::Dict, setup::Dict) @expression(EP, eTotalCap[y in 1:G], if y in intersect(NEW_CAP, RET_CAP) # Resources eligible for new capacity and retirements if y in COMMIT - eExistingCap[y] + cap_size(y)*(EP[:vCAP][y] - EP[:vRETCAP][y]) + eExistingCap[y] + cap_size(res[y])*(EP[:vCAP][y] - EP[:vRETCAP][y]) else eExistingCap[y] + EP[:vCAP][y] - EP[:vRETCAP][y] end elseif y in setdiff(NEW_CAP, RET_CAP) # Resources eligible for only new capacity if y in COMMIT - eExistingCap[y] + cap_size(y)*EP[:vCAP][y] + eExistingCap[y] + cap_size(res[y])*EP[:vCAP][y] else eExistingCap[y] + EP[:vCAP][y] end elseif y in setdiff(RET_CAP, NEW_CAP) # Resources eligible for only capacity retirements if y in COMMIT - eExistingCap[y] - cap_size(y)*EP[:vRETCAP][y] + eExistingCap[y] - cap_size(res[y])*EP[:vRETCAP][y] else eExistingCap[y] - EP[:vRETCAP][y] end @@ -123,18 +116,18 @@ function investment_discharge!(EP::Model, inputs::Dict, setup::Dict) @expression(EP, eCFix[y in 1:G], if y in setdiff(NEW_CAP, RETRO) # Resources eligible for new capacity (Non-Retrofit) if y in COMMIT - inv_cost_per_mwyr(y)*cap_size(y)*vCAP[y] + fixed_om_cost_per_mwyr(y)*eTotalCap[y] + inv_cost_per_mwyr(res[y])*cap_size(res[y])*vCAP[y] + fixed_om_cost_per_mwyr(res[y])*eTotalCap[y] else - inv_cost_per_mwyr(y)*vCAP[y] + fixed_om_cost_per_mwyr(y)*eTotalCap[y] + inv_cost_per_mwyr(res[y])*vCAP[y] + fixed_om_cost_per_mwyr(res[y])*eTotalCap[y] end elseif y in intersect(NEW_CAP, RETRO) # Resources eligible for new capacity (Retrofit yr -> y) if y in COMMIT - sum( RETRO_SOURCE_IDS[y][i] in RET_CAP ? RETRO_INV_CAP_COSTS[y][i]*cap_size(y)*vRETROFIT[RETRO_SOURCE_IDS[y][i],y]*RETRO_EFFICIENCY[y][i] : 0 for i in 1:NUM_RETRO_SOURCES[y]) + fixed_om_cost_per_mwyr(y)*eTotalCap[y] + sum( RETRO_SOURCE_IDS[y][i] in RET_CAP ? RETRO_INV_CAP_COSTS[y][i]*cap_size(res[y])*vRETROFIT[RETRO_SOURCE_IDS[y][i],y]*RETRO_EFFICIENCY[y][i] : 0 for i in 1:NUM_RETRO_SOURCES[y]) + fixed_om_cost_per_mwyr(res[y])*eTotalCap[y] else - sum( RETRO_SOURCE_IDS[y][i] in RET_CAP ? RETRO_INV_CAP_COSTS[y][i]*vRETROFIT[RETRO_SOURCE_IDS[y][i],y]*RETRO_EFFICIENCY[y][i] : 0 for i in 1:NUM_RETRO_SOURCES[y]) + fixed_om_cost_per_mwyr(y)*eTotalCap[y] + sum( RETRO_SOURCE_IDS[y][i] in RET_CAP ? RETRO_INV_CAP_COSTS[y][i]*vRETROFIT[RETRO_SOURCE_IDS[y][i],y]*RETRO_EFFICIENCY[y][i] : 0 for i in 1:NUM_RETRO_SOURCES[y]) + fixed_om_cost_per_mwyr(res[y])*eTotalCap[y] end else - fixed_om_cost_per_mwyr(y)*eTotalCap[y] + fixed_om_cost_per_mwyr(res[y])*eTotalCap[y] end ) @@ -155,24 +148,24 @@ function investment_discharge!(EP::Model, inputs::Dict, setup::Dict) if MultiStage == 1 # Existing capacity variable is equal to existing capacity specified in the input file - @constraint(EP, cExistingCap[y in 1:G], EP[:vEXISTINGCAP][y] == existing_capacity_mw(y)) + @constraint(EP, cExistingCap[y in 1:G], EP[:vEXISTINGCAP][y] == existing_capacity_mw(res[y])) end ## Constraints on retirements and capacity additions # Cannot retire more capacity than existing capacity @constraint(EP, cMaxRetNoCommit[y in setdiff(RET_CAP,COMMIT)], vRETCAP[y] <= eExistingCap[y]) - @constraint(EP, cMaxRetCommit[y in intersect(RET_CAP,COMMIT)], cap_size(y)*vRETCAP[y] <= eExistingCap[y]) + @constraint(EP, cMaxRetCommit[y in intersect(RET_CAP,COMMIT)], cap_size(res[y])*vRETCAP[y] <= eExistingCap[y]) ## Constraints on new built capacity # Constraint on maximum capacity (if applicable) [set input to -1 if no constraint on maximum capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MW is >= Max_Cap_MW and lead to infeasabilty - MAX_CAP = has_positive_max_capacity_mw(resources) - @constraint(EP, cMaxCap[y in MAX_CAP], eTotalCap[y] <= max_capacity_mw(y)) + MAX_CAP = has_positive_max_capacity_mw(res) + @constraint(EP, cMaxCap[y in MAX_CAP], eTotalCap[y] <= max_capacity_mw(res[y])) # Constraint on minimum capacity (if applicable) [set input to -1 if no constraint on minimum capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MW is <= Min_Cap_MW and lead to infeasabilty - MIN_CAP = has_positive_min_capacity_mw(resources) - @constraint(EP, cMinCap[y in MIN_CAP], eTotalCap[y] >= min_capacity_mw(y)) + MIN_CAP = has_positive_min_capacity_mw(res) + @constraint(EP, cMinCap[y in MIN_CAP], eTotalCap[y] >= min_capacity_mw(res[y])) diff --git a/src/model/core/fuel.jl b/src/model/core/fuel.jl index 360fd6cae0..6c839fa96a 100644 --- a/src/model/core/fuel.jl +++ b/src/model/core/fuel.jl @@ -54,7 +54,7 @@ PWFU_Intercept_* for at least one segment. function fuel!(EP::Model, inputs::Dict, setup::Dict) println("Fuel Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -64,11 +64,6 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) fuels = inputs["fuels"] NUM_FUEL = length(fuels) - cap_size(y) = cap_size(resources[y]) - fuel(y) = fuel(resources[y]) - start_fuel_mmbtu_per_mw(y) = start_fuel_mmbtu_per_mw(resources[y]) - heat_rate_mmbtu_per_mwh(y) = heat_rate_mmbtu_per_mwh(resources[y]) - # create variable for fuel consumption for output @variable(EP, vFuel[y in 1:G, t = 1:T] >= 0) @@ -77,8 +72,8 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) # if unit commitment is modelled @expression(EP, eStartFuel[y in 1:G, t = 1:T], if y in THERM_COMMIT - (cap_size(y) * EP[:vSTART][y, t] * - start_fuel_mmbtu_per_mw(y)) + (cap_size(res[y]) * EP[:vSTART][y, t] * + start_fuel_mmbtu_per_mw(res[y])) else 0 end) @@ -88,23 +83,23 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) # eCFuel_start or eCFuel_out is $ or Million$ # Start up fuel cost @expression(EP, eCFuelStart[y = 1:G, t = 1:T], - (inputs["fuel_costs"][fuel(y)][t] * EP[:eStartFuel][y, t])) + (inputs["fuel_costs"][fuel(res[y])][t] * EP[:eStartFuel][y, t])) # plant level start-up fuel cost for output @expression(EP, ePlantCFuelStart[y = 1:G], sum(inputs["omega"][t] * EP[:eCFuelStart][y, t] for t in 1:T)) # zonal level total fuel cost for output @expression(EP, eZonalCFuelStart[z = 1:Z], - sum(EP[:ePlantCFuelStart][y] for y in resources_in_zone_by_rid(resources,z))) + sum(EP[:ePlantCFuelStart][y] for y in resources_in_zone_by_rid(res,z))) # Fuel cost for power generation @expression(EP, eCFuelOut[y = 1:G, t = 1:T], - (inputs["fuel_costs"][fuel(y)][t] * EP[:vFuel][y, t])) + (inputs["fuel_costs"][fuel(res[y])][t] * EP[:vFuel][y, t])) # plant level start-up fuel cost for output @expression(EP, ePlantCFuelOut[y = 1:G], sum(inputs["omega"][t] * EP[:eCFuelOut][y, t] for t in 1:T)) # zonal level total fuel cost for output @expression(EP, eZonalCFuelOut[z = 1:Z], - sum(EP[:ePlantCFuelOut][y] for y in resources_in_zone_by_rid(resources,z))) + sum(EP[:ePlantCFuelOut][y] for y in resources_in_zone_by_rid(res,z))) # system level total fuel cost for output @@ -117,7 +112,7 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) #fuel consumption (MMBTU or Billion BTU) @expression(EP, eFuelConsumption[f in 1:NUM_FUEL, t in 1:T], sum(EP[:vFuel][y, t] + EP[:eStartFuel][y,t] - for y in resources_with_fuel(resources, fuels[f]))) + for y in resources_with_fuel(res, fuels[f]))) @expression(EP, eFuelConsumptionYear[f in 1:NUM_FUEL], sum(inputs["omega"][t] * EP[:eFuelConsumption][f, t] for t in 1:T)) @@ -126,7 +121,7 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) ### Constraint ### ### only apply constraint to generators with fuel type other than None @constraint(EP, FuelCalculation[y in setdiff(HAS_FUEL, THERM_COMMIT), t = 1:T], - EP[:vFuel][y, t] - EP[:vP][y, t] * heat_rate_mmbtu_per_mwh(y) == 0) + EP[:vFuel][y, t] - EP[:vP][y, t] * heat_rate_mmbtu_per_mwh(res[y]) == 0) if !isempty(THERM_COMMIT) # Only apply piecewise fuel consumption to thermal generators in THERM_COMMIT_PWFU set @@ -146,7 +141,7 @@ function fuel!(EP::Model, inputs::Dict, setup::Dict) end # constraint for fuel consumption at a constant heat rate @constraint(EP, FuelCalculationCommit[y in setdiff(THERM_COMMIT,THERM_COMMIT_PWFU), t = 1:T], - EP[:vFuel][y, t] - EP[:vP][y, t] * heat_rate_mmbtu_per_mwh(y) == 0) + EP[:vFuel][y, t] - EP[:vP][y, t] * heat_rate_mmbtu_per_mwh(res[y]) == 0) end return EP diff --git a/src/model/core/reserves.jl b/src/model/core/reserves.jl index ffdf28cd46..290e7fd398 100644 --- a/src/model/core/reserves.jl +++ b/src/model/core/reserves.jl @@ -71,8 +71,7 @@ function reserves_contingency!(EP::Model, inputs::Dict, setup::Dict) println("Reserves Contingency Module") - resources = inputs["RESOURCES"] - cap_size(y) = cap_size(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) UCommit = setup["UCommit"] @@ -117,7 +116,7 @@ function reserves_contingency!(EP::Model, inputs::Dict, setup::Dict) # Dynamic contingency related constraints # option 1: ensures vLARGEST_CONTINGENCY is greater than the capacity of the largest installed generator if UCommit == 1 && pDynamic_Contingency == 1 - @constraint(EP, cContingency[y in COMMIT], vLARGEST_CONTINGENCY >= cap_size(y)*vCONTINGENCY_AUX[y] ) + @constraint(EP, cContingency[y in COMMIT], vLARGEST_CONTINGENCY >= cap_size(res[y])*vCONTINGENCY_AUX[y] ) # Ensure vCONTINGENCY_AUX = 0 if total capacity = 0 @constraint(EP, cContAux1[y in COMMIT], vCONTINGENCY_AUX[y] <= EP[:eTotalCap][y]) # Ensure vCONTINGENCY_AUX = 1 if total capacity > 0 @@ -125,7 +124,7 @@ function reserves_contingency!(EP::Model, inputs::Dict, setup::Dict) # option 2: ensures vLARGEST_CONTINGENCY is greater than the capacity of the largest commited generator in each hour elseif UCommit == 1 && pDynamic_Contingency == 2 - @constraint(EP, cContingency[y in COMMIT, t=1:T], vLARGEST_CONTINGENCY[t] >= cap_size(y)*vCONTINGENCY_AUX[y,t] ) + @constraint(EP, cContingency[y in COMMIT, t=1:T], vLARGEST_CONTINGENCY[t] >= cap_size(res[y])*vCONTINGENCY_AUX[y,t] ) # Ensure vCONTINGENCY_AUX = 0 if vCOMMIT = 0 @constraint(EP, cContAux[y in COMMIT, t=1:T], vCONTINGENCY_AUX[y,t] <= EP[:vCOMMIT][y,t]) # Ensure vCONTINGENCY_AUX = 1 if vCOMMIT > 0 @@ -208,9 +207,7 @@ function reserves_core!(EP::Model, inputs::Dict, setup::Dict) println("Reserves Core Module") - resources = inputs["RESOURCES"] - reg_cost(y) = reg_cost(resources[y]) - rsv_cost(y) = rsv_cost(resources[y]) + res = inputs["RESOURCES"] UCommit = setup["UCommit"] @@ -265,8 +262,8 @@ function reserves_core!(EP::Model, inputs::Dict, setup::Dict) # TODO: check these expressions @expression(EP, eCRsvPen[t=1:T], inputs["omega"][t]*inputs["pC_Rsv_Penalty"]*vUNMET_RSV[t]) @expression(EP, eTotalCRsvPen, sum(eCRsvPen[t] for t=1:T) + - sum(reg_cost(y)*vRSV[y,t] for y in RSV, t=1:T) + - sum(rsv_cost(y)*vREG[y,t] for y in REG, t=1:T) ) + sum(reg_cost(res[y])*vRSV[y,t] for y in RSV, t=1:T) + + sum(rsv_cost(res[y])*vREG[y,t] for y in REG, t=1:T) ) add_to_expression!(EP[:eObj], eTotalCRsvPen) end diff --git a/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl b/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl index c423ca52a6..c3f8f1485d 100644 --- a/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl +++ b/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl @@ -18,8 +18,7 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) ## Default value of Num_VRE_Bins ==1 println("Dispatchable Resources Module") - resources = inputs["RESOURCES"] - num_vre_bins(y) = num_vre_bins(resources[y]) + res = inputs["RESOURCES"] Reserves = setup["Reserves"] CapacityReserveMargin = setup["CapacityReserveMargin"] @@ -30,7 +29,7 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) VRE = inputs["VRE"] - VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(resources)) + VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(res)) VRE_NO_POWER_OUT = setdiff(VRE, VRE_POWER_OUT) ### Expressions ### @@ -38,7 +37,7 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceDisp[t=1:T, z=1:Z], - sum(EP[:vP][y,t] for y in intersect(VRE, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(VRE, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], EP[:ePowerBalanceDisp]) @@ -58,7 +57,7 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) for y in VRE_POWER_OUT # Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone. # For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits. - VRE_BINS = intersect(resource_id.(resources[1:G .>= y]), resource_id.(resources[1:G .<= y+num_vre_bins(y)-1])) + VRE_BINS = intersect(resource_id.(res[1:G .>= y]), resource_id.(res[1:G .<= y+num_vre_bins(res[y])-1])) # Maximum power generated per hour by renewable generators must be less than # sum of product of hourly capacity factor for each bin times its the bin installed capacity @@ -73,7 +72,7 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) end ##CO2 Polcy Module VRE Generation by zone @expression(EP, eGenerationByVRE[z=1:Z, t=1:T], # the unit is GW - sum(EP[:vP][y,t] for y in intersect(inputs["VRE"], resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(inputs["VRE"], resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:eGenerationByZone], eGenerationByVRE) @@ -103,11 +102,11 @@ The amount of frequency regulation and operating reserves procured in each time ``` """ function curtailable_variable_renewable_reserves!(EP::Model, inputs::Dict) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] VRE = inputs["VRE"] - VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(resources)) + VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(res)) REG = intersect(VRE_POWER_OUT, inputs["REG"]) RSV = intersect(VRE_POWER_OUT, inputs["RSV"]) @@ -116,16 +115,13 @@ function curtailable_variable_renewable_reserves!(EP::Model, inputs::Dict) vREG = EP[:vREG] vRSV = EP[:vRSV] hourly_capacity_factor(y, t) = inputs["pP_Max"][y, t] - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) - num_vre_bins(y) = num_vre_bins(resources[y]) hourly_capacity(y, t) = hourly_capacity_factor(y, t) * eTotalCap[y] - resources_in_bin(y) = UnitRange(y, y + num_vre_bins(y) - 1) + resources_in_bin(y) = UnitRange(y, y + num_vre_bins(res[y]) - 1) hourly_bin_capacity(y, t) = sum(hourly_capacity(yy, t) for yy in resources_in_bin(y)) - @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= reg_max(y) * hourly_bin_capacity(y, t)) - @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * hourly_bin_capacity(y, t)) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= reg_max(res[y]) * hourly_bin_capacity(y, t)) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= rsv_max(res[y]) * hourly_bin_capacity(y, t)) expr = extract_time_series_to_expression(vP, VRE_POWER_OUT) add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) @@ -138,10 +134,10 @@ function curtailable_variable_renewable_reserves!(EP::Model, inputs::Dict) end function remove_reserves_for_binned_vre_resources!(EP::Model, inputs::Dict) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] VRE = inputs["VRE"] - VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(resources)) + VRE_POWER_OUT = intersect(VRE, has_positive_num_vre_bins(res)) REG = inputs["REG"] RSV = inputs["RSV"] diff --git a/src/model/resources/flexible_demand/flexible_demand.jl b/src/model/resources/flexible_demand/flexible_demand.jl index 23eaeec327..43918649d7 100644 --- a/src/model/resources/flexible_demand/flexible_demand.jl +++ b/src/model/resources/flexible_demand/flexible_demand.jl @@ -43,11 +43,7 @@ T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones FLEX = inputs["FLEX"] # Set of flexible demand resources -resources = inputs["RESOURCES"] -var_om_cost_per_mwh_in(y) = var_om_cost_per_mwh_in(resources[y]) -max_flexible_demand_delay(y) = max_flexible_demand_delay(resources[y]) -max_flexible_demand_advance(y) = max_flexible_demand_advance(resources[y]) -flexible_demand_energy_eff(y) = flexible_demand_energy_eff(resources[y]) +res = inputs["RESOURCES"] hours_per_subperiod = inputs["hours_per_subperiod"] # Total number of hours per subperiod @@ -63,7 +59,7 @@ hours_per_subperiod = inputs["hours_per_subperiod"] # Total number of hours per ## Power Balance Expressions ## @expression(EP, ePowerBalanceDemandFlex[t=1:T, z=1:Z], - sum(-EP[:vP][y,t]+EP[:vCHARGE_FLEX][y,t] for y in intersect(FLEX, resources_in_zone_by_rid(resources,z))) + sum(-EP[:vP][y,t]+EP[:vCHARGE_FLEX][y,t] for y in intersect(FLEX, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceDemandFlex) @@ -76,7 +72,7 @@ end ## Objective Function Expressions ## # Variable costs of "charging" for technologies "y" during hour "t" in zone "z" -@expression(EP, eCVarFlex_in[y in FLEX,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(y)*vCHARGE_FLEX[y,t]) +@expression(EP, eCVarFlex_in[y in FLEX,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(res[y])*vCHARGE_FLEX[y,t]) # Sum individual resource contributions to variable charging costs to get total variable charging costs @expression(EP, eTotalCVarFlexInT[t=1:T], sum(eCVarFlex_in[y,t] for y in FLEX)) @@ -88,13 +84,13 @@ add_to_expression!(EP[:eObj], eTotalCVarFlexIn) ## Flexible demand is available only during specified hours with time delay or time advance (virtual storage-shiftable demand) for z in 1:Z # NOTE: Flexible demand operates by zone since capacity is now related to zone demand - FLEX_Z = intersect(FLEX, resources_in_zone_by_rid(resources,z)) + FLEX_Z = intersect(FLEX, resources_in_zone_by_rid(res,z)) @constraints(EP, begin # State of "charge" constraint (equals previous state + charge - discharge) # NOTE: no maximum energy "stored" or deferred for later hours # NOTE: Flexible_Demand_Energy_Eff corresponds to energy loss due to time shifting - [y in FLEX_Z, t in 1:T], EP[:vS_FLEX][y,t] == EP[:vS_FLEX][y, hoursbefore(hours_per_subperiod, t, 1)] - flexible_demand_energy_eff(y) * EP[:vP][y,t] + EP[:vCHARGE_FLEX][y,t] + [y in FLEX_Z, t in 1:T], EP[:vS_FLEX][y,t] == EP[:vS_FLEX][y, hoursbefore(hours_per_subperiod, t, 1)] - flexible_demand_energy_eff(res[y]) * EP[:vP][y,t] + EP[:vCHARGE_FLEX][y,t] # Maximum charging rate # NOTE: the maximum amount that can be shifted is given by hourly availability of the resource times the maximum capacity of the resource @@ -106,18 +102,18 @@ for z in 1:Z for y in FLEX_Z # Require deferred demands to be satisfied within the specified time delay - max_flexible_demand_delay = Int(floor(max_flexible_demand_delay(y))) + max_flex_demand_delay = Int(floor(max_flexible_demand_delay(res[y]))) # Require advanced demands to be satisfied within the specified time period - max_flexible_demand_advance = Int(floor(max_flexible_demand_advance(y))) + max_flex_demand_advance = Int(floor(max_flexible_demand_advance(res[y]))) @constraint(EP, [t in 1:T], # cFlexibleDemandDelay: Constraints looks forward over next n hours, where n = max_flexible_demand_delay - sum(EP[:vP][y,e] for e=hoursafter(hours_per_subperiod, t, 1:max_flexible_demand_delay)) >= EP[:vS_FLEX][y,t]) + sum(EP[:vP][y,e] for e=hoursafter(hours_per_subperiod, t, 1:max_flex_demand_delay)) >= EP[:vS_FLEX][y,t]) @constraint(EP, [t in 1:T], # cFlexibleDemandAdvance: Constraint looks forward over next n hours, where n = max_flexible_demand_advance - sum(EP[:vCHARGE_FLEX][y,e] for e=hoursafter(hours_per_subperiod, t, 1:max_flexible_demand_advance)) >= -EP[:vS_FLEX][y,t]) + sum(EP[:vCHARGE_FLEX][y,e] for e=hoursafter(hours_per_subperiod, t, 1:max_flex_demand_advance)) >= -EP[:vS_FLEX][y,t]) end end diff --git a/src/model/resources/hydro/hydro_inter_period_linkage.jl b/src/model/resources/hydro/hydro_inter_period_linkage.jl index 1b692df469..072cf929d0 100644 --- a/src/model/resources/hydro/hydro_inter_period_linkage.jl +++ b/src/model/resources/hydro/hydro_inter_period_linkage.jl @@ -45,9 +45,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) println("Long Duration Storage Module for Hydro Reservoir") - resources = inputs["RESOURCES"] - eff_down(y) = eff_down(resources[y]) - hydro_energy_to_power_ratio(y) = hydro_energy_to_power_ratio(resources[y]) + res = inputs["RESOURCES"] REP_PERIOD = inputs["REP_PERIOD"] # Number of representative periods @@ -79,7 +77,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) # Alternative to cSoCBalStart constraint which is included when not modeling operations wrapping and long duration storage # Note: tw_min = hours_per_subperiod*(w-1)+1; tw_max = hours_per_subperiod*w @constraint(EP, cSoCBalLongDurationStorageStart_H[w=1:REP_PERIOD, y in STOR_HYDRO_LONG_DURATION], - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1] == (EP[:vS_HYDRO][y,hours_per_subperiod*w]-vdSOC_HYDRO[y,w])-(1/eff_down(y)*EP[:vP][y,hours_per_subperiod*(w-1)+1])-EP[:vSPILL][y,hours_per_subperiod*(w-1)+1]+inputs["pP_Max"][y,hours_per_subperiod*(w-1)+1]*EP[:eTotalCap][y]) + EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1] == (EP[:vS_HYDRO][y,hours_per_subperiod*w]-vdSOC_HYDRO[y,w])-(1/efficiency_down(res[y])*EP[:vP][y,hours_per_subperiod*(w-1)+1])-EP[:vSPILL][y,hours_per_subperiod*(w-1)+1]+inputs["pP_Max"][y,hours_per_subperiod*(w-1)+1]*EP[:eTotalCap][y]) # Storage at beginning of period w = storage at beginning of period w-1 + storage built up in period w (after n representative periods) ## Multiply storage build up term from prior period with corresponding weight @@ -88,7 +86,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) # Storage at beginning of each modeled period cannot exceed installed energy capacity @constraint(EP, cSoCBalLongDurationStorageUpper_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], - vSOC_HYDROw[y,r] <= hydro_energy_to_power_ratio(y)*EP[:eTotalCap][y]) + vSOC_HYDROw[y,r] <= hydro_energy_to_power_ratio(res[y])*EP[:eTotalCap][y]) # Initial storage level for representative periods must also adhere to sub-period storage inventory balance # Initial storage = Final storage - change in storage inventory across representative period diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index 931779947d..06dab1bd51 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -60,12 +60,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) println("Hydro Reservoir Core Resources Module") - resources = inputs["RESOURCES"] - eff_down(y) = eff_down(resources[y]) - min_power(y) = min_power(resources[y]) - hydro_energy_to_power_ratio(y) = hydro_energy_to_power_ratio(resources[y]) - ramp_up_percentage(y) = ramp_up_percentage(resources[y]) - ramp_down_percentage(y) = ramp_down_percentage(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -100,7 +95,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceHydroRes[t=1:T, z=1:Z], - sum(EP[:vP][y,t] for y in intersect(HYDRO_RES, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(HYDRO_RES, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceHydroRes) @@ -121,13 +116,13 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) # Constraints for reservoir hydro cHydroReservoir[y in HYDRO_RES, t in 1:T], EP[:vS_HYDRO][y,t] == (EP[:vS_HYDRO][y, hoursbefore(p,t,1)] - - (1/eff_down(y)*EP[:vP][y,t]) - vSPILL[y,t] + inputs["pP_Max"][y,t]*EP[:eTotalCap][y]) + - (1/efficiency_down(res[y])*EP[:vP][y,t]) - vSPILL[y,t] + inputs["pP_Max"][y,t]*EP[:eTotalCap][y]) # Maximum ramp up and down - cRampUp[y in HYDRO_RES, t in 1:T], EP[:vP][y,t] + regulation_term[y,t] + reserves_term[y,t] - EP[:vP][y, hoursbefore(p,t,1)] <= ramp_up_percentage(y)*EP[:eTotalCap][y] - cRampDown[y in HYDRO_RES, t in 1:T], EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] - regulation_term[y,t] + reserves_term[y, hoursbefore(p,t,1)] <= ramp_down_percentage(y)*EP[:eTotalCap][y] + cRampUp[y in HYDRO_RES, t in 1:T], EP[:vP][y,t] + regulation_term[y,t] + reserves_term[y,t] - EP[:vP][y, hoursbefore(p,t,1)] <= ramp_up_percentage(res[y])*EP[:eTotalCap][y] + cRampDown[y in HYDRO_RES, t in 1:T], EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] - regulation_term[y,t] + reserves_term[y, hoursbefore(p,t,1)] <= ramp_down_percentage(res[y])*EP[:eTotalCap][y] # Minimum streamflow running requirements (power generation and spills must be >= min value) in all hours - cHydroMinFlow[y in HYDRO_RES, t in 1:T], EP[:vP][y,t] + EP[:vSPILL][y,t] >= min_power(y)*EP[:eTotalCap][y] + cHydroMinFlow[y in HYDRO_RES, t in 1:T], EP[:vP][y,t] + EP[:vSPILL][y,t] >= min_power(res[y])*EP[:eTotalCap][y] # DEV NOTE: When creating new hydro inputs, should rename Min_Power with Min_flow or similar for clarity since this includes spilled water as well # Maximum discharging rate must be less than power rating OR available stored energy at start of hour, whichever is less @@ -139,7 +134,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) ### Constraints to limit maximum energy in storage based on known limits on reservoir energy capacity (only for HYDRO_RES_KNOWN_CAP) # Maximum energy stored in reservoir must be less than energy capacity in all hours - only applied to HYDRO_RES_KNOWN_CAP - @constraint(EP, cHydroMaxEnergy[y in HYDRO_RES_KNOWN_CAP, t in 1:T], EP[:vS_HYDRO][y,t] <= hydro_energy_to_power_ratio(y)*EP[:eTotalCap][y]) + @constraint(EP, cHydroMaxEnergy[y in HYDRO_RES_KNOWN_CAP, t in 1:T], EP[:vS_HYDRO][y,t] <= hydro_energy_to_power_ratio(res[y])*EP[:eTotalCap][y]) if setup["Reserves"] == 1 ### Reserve related constraints for reservoir hydro resources (y in HYDRO_RES), if used @@ -147,7 +142,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict) end ##CO2 Polcy Module Hydro Res Generation by zone @expression(EP, eGenerationByHydroRes[z=1:Z, t=1:T], # the unit is GW - sum(EP[:vP][y,t] for y in intersect(HYDRO_RES, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(HYDRO_RES, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:eGenerationByZone], eGenerationByHydroRes) @@ -185,7 +180,7 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) println("Hydro Reservoir Reserves Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) @@ -200,8 +195,6 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) vREG = EP[:vREG] vRSV = EP[:vRSV] eTotalCap = EP[:eTotalCap] - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) max_up_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) max_dn_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) @@ -216,6 +209,6 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) @constraint(EP, [y in HYDRO_RES, t in 1:T], max_up_reserves_lhs[y, t] <= eTotalCap[y]) @constraint(EP, [y in HYDRO_RES, t in 1:T], max_dn_reserves_lhs[y, t] >= 0) - @constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= reg_max(y) * eTotalCap[y]) - @constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= reg_max(res[y]) * eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= rsv_max(res[y]) * eTotalCap[y]) end diff --git a/src/model/resources/hydrogen/electrolyzer.jl b/src/model/resources/hydrogen/electrolyzer.jl index 79109af5c1..965454d28e 100644 --- a/src/model/resources/hydrogen/electrolyzer.jl +++ b/src/model/resources/hydrogen/electrolyzer.jl @@ -81,16 +81,7 @@ This constraint permits modeling of the 'three pillars' requirements for clean h function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) println("Electrolyzer Resources Module") - resources = inputs["RESOURCES"] - - electrolyzer_min_kt(y) = electrolyzer_min_kt(resources[y]) - hydrogen_mwh_per_tonne(y) = hydrogen_mwh_per_tonne(resources[y]) - hydrogen_price_per_tonne(y) = hydrogen_price_per_tonne(resources[y]) - qualified_hydrogen_supply(y) = qualified_hydrogen_supply(resources[y]) - - min_power(y) = min_power(resources[y]) - ramp_up_percentage(y) = ramp_up_percentage(resources[y]) - ramp_down_percentage(y) = ramp_down_percentage(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -110,7 +101,7 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceElectrolyzers[t in 1:T, z in 1:Z], - sum(EP[:vUSE][y,t] for y in intersect(ELECTROLYZERS, resources_in_zone_by_rid(resources,z)))) + sum(EP[:vUSE][y,t] for y in intersect(ELECTROLYZERS, resources_in_zone_by_rid(res,z)))) # Electrolyzers consume electricity so their vUSE is subtracted from power balance EP[:ePowerBalance] -= ePowerBalanceElectrolyzers @@ -123,10 +114,10 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) ### Maximum ramp up and down between consecutive hours (Constraints #1-2) @constraints(EP, begin ## Maximum ramp up between consecutive hours - [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y,t] - EP[:vUSE][y, hoursbefore(p,t,1)] <= ramp_up_percentage(y)*EP[:eTotalCap][y] + [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y,t] - EP[:vUSE][y, hoursbefore(p,t,1)] <= ramp_up_percentage(res[y])*EP[:eTotalCap][y] ## Maximum ramp down between consecutive hours - [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y, hoursbefore(p,t,1)] - EP[:vUSE][y,t] <= ramp_down_percentage(y)*EP[:eTotalCap][y] + [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y, hoursbefore(p,t,1)] - EP[:vUSE][y,t] <= ramp_down_percentage(res[y])*EP[:eTotalCap][y] end) ### Minimum and maximum power output constraints (Constraints #3-4) @@ -135,7 +126,7 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) # Could allow them to contribute as a curtailable demand in future. @constraints(EP, begin # Minimum stable power generated per technology "y" at hour "t" Min_Power - [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y,t] >= min_power(y)*EP[:eTotalCap][y] + [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y,t] >= min_power(res[y])*EP[:eTotalCap][y] # Maximum power generated per technology "y" at hour "t" [y in ELECTROLYZERS, t in 1:T], EP[:vUSE][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] @@ -145,7 +136,7 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) kt_to_t = 10^3 @constraint(EP, cHydrogenMin[y in ELECTROLYZERS], - sum(inputs["omega"][t] * EP[:vUSE][y,t] / hydrogen_mwh_per_tonne(y) for t=1:T) >= electrolyzer_min_kt(y) * kt_to_t + sum(inputs["omega"][t] * EP[:vUSE][y,t] / hydrogen_mwh_per_tonne(res[y]) for t=1:T) >= electrolyzer_min_kt(res[y]) * kt_to_t ) ### Remove vP (electrolyzers do not produce power so vP = 0 for all periods) @@ -158,10 +149,10 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) # from within the same zone as the electrolyzers are located to be >= hourly consumption from electrolyzers in the zone # (and any charging by qualified storage within the zone used to help increase electrolyzer utilization). if setup["HydrogenHourlyMatching"] == 1 - HYDROGEN_ZONES = unique(zone_id.(resources.ELECTROLYZER)) - QUALIFIED_SUPPLY = resources_with_qualified_hydrogen_supply(resources) + HYDROGEN_ZONES = unique(zone_id.(res.ELECTROLYZER)) + QUALIFIED_SUPPLY = resources_with_qualified_hydrogen_supply(res) @constraint(EP, cHourlyMatching[z in HYDROGEN_ZONES, t in 1:T], - sum(EP[:vP][y,t] for y=intersect(resources_in_zone_by_rid(resources,z), QUALIFIED_SUPPLY)) >= sum(EP[:vUSE][y,t] for y=intersect(resources_in_zone_by_rid(resources,z), ELECTROLYZERS)) + sum(EP[:vCHARGE][y,t] for y=intersect(resources_in_zone_by_rid(resources,z), QUALIFIED_SUPPLY, STORAGE)) + sum(EP[:vP][y,t] for y=intersect(resources_in_zone_by_rid(res,z), QUALIFIED_SUPPLY)) >= sum(EP[:vUSE][y,t] for y=intersect(resources_in_zone_by_rid(res,z), ELECTROLYZERS)) + sum(EP[:vCHARGE][y,t] for y=intersect(resources_in_zone_by_rid(res,z), QUALIFIED_SUPPLY, STORAGE)) ) end @@ -178,7 +169,7 @@ function electrolyzer!(EP::Model, inputs::Dict, setup::Dict) ### Objective Function ### # Subtract hydrogen revenue from objective function scale_factor = setup["ParameterScale"] == 1 ? 10^6 : 1 # If ParameterScale==1, costs are in millions of $ - @expression(EP, eHydrogenValue[y in ELECTROLYZERS, t in 1:T], (inputs["omega"][t] * EP[:vUSE][y,t] / hydrogen_mwh_per_tonne(y) * hydrogen_price_per_tonne(y) / scale_factor)) + @expression(EP, eHydrogenValue[y in ELECTROLYZERS, t in 1:T], (inputs["omega"][t] * EP[:vUSE][y,t] / hydrogen_mwh_per_tonne(res[y]) * hydrogen_price_per_tonne(res[y]) / scale_factor)) @expression(EP, eTotalHydrogenValueT[t in 1:T], sum(eHydrogenValue[y,t] for y in ELECTROLYZERS)) @expression(EP, eTotalHydrogenValue, sum(eTotalHydrogenValueT[t] for t in 1:T)) EP[:eObj] -= eTotalHydrogenValue diff --git a/src/model/resources/must_run/must_run.jl b/src/model/resources/must_run/must_run.jl index 7a9bd7d74e..13dd06e219 100644 --- a/src/model/resources/must_run/must_run.jl +++ b/src/model/resources/must_run/must_run.jl @@ -16,7 +16,7 @@ function must_run!(EP::Model, inputs::Dict, setup::Dict) println("Must-Run Resources Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -30,7 +30,7 @@ function must_run!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceNdisp[t=1:T, z=1:Z], - sum(EP[:vP][y,t] for y in intersect(MUST_RUN, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(MUST_RUN, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceNdisp) @@ -45,7 +45,7 @@ function must_run!(EP::Model, inputs::Dict, setup::Dict) @constraint(EP, [y in MUST_RUN, t=1:T], EP[:vP][y,t] == inputs["pP_Max"][y,t]*EP[:eTotalCap][y]) ##CO2 Polcy Module Must Run Generation by zone @expression(EP, eGenerationByMustRun[z=1:Z, t=1:T], # the unit is GW - sum(EP[:vP][y,t] for y in intersect(MUST_RUN, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(MUST_RUN, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:eGenerationByZone], eGenerationByMustRun) diff --git a/src/model/resources/resources.jl b/src/model/resources/resources.jl index ccc677ec7a..63f9808f17 100644 --- a/src/model/resources/resources.jl +++ b/src/model/resources/resources.jl @@ -1,6 +1,3 @@ -abstract type AbstractResource end - -const resources_type = (:ELECTROLYZER, :FLEX, :HYDRO, :STOR, :THERM, :VRE) for r in resources_type let nt = Symbol("nt"), r = r @eval begin @@ -37,7 +34,6 @@ end Base.pairs(r::AbstractResource) = pairs(parent(r)) function Base.show(io::IO, r::AbstractResource) - for (k,v) in pairs(r) println(io, "$k: $v") end @@ -76,6 +72,10 @@ reg_max(r::AbstractResource)::Float64 = get(r, :reg_max, default) rsv_cost(r::AbstractResource)::Float64 = get(r, :rsv_cost, default) rsv_max(r::AbstractResource)::Float64 = get(r, :rsv_max, default) +var_om_cost_per_mwh(r::AbstractResource)::Float64 = get(r, :var_om_cost_per_mwh, default) +inv_cost_per_mwyr(r::AbstractResource)::Float64 = r.inv_cost_per_mwyr +fixed_om_cost_per_mwyr(r::AbstractResource)::Float64 = r.fixed_om_cost_per_mwyr + # fuel fuel(r::AbstractResource)::String = get(r, :fuel, "None") start_fuel_mmbtu_per_mw(r::AbstractResource)::Float64 = r.start_fuel_mmbtu_per_mw @@ -86,8 +86,8 @@ ccs_disposal_cost_per_metric_ton(r::AbstractResource)::Float64 = r.ccs_disposal_ biomass(r::AbstractResource) = get(r, :biomass, default) # Reservoir hydro and storage -efficiency_up(r::T) where T <: Union{HYDRO,STOR} = get(r, :eff_up, default) -efficiency_down(r::T) where T <: Union{HYDRO,STOR} = get(r, :eff_down, default) +efficiency_up(r::T) where T <: Union{HYDRO,STOR} = get(r, :eff_up, 1.0) +efficiency_down(r::T) where T <: Union{HYDRO,STOR} = get(r, :eff_down, 1.0) # Ramp up and down const TCOMMIT = Union{ELECTROLYZER, HYDRO, THERM} @@ -134,11 +134,6 @@ function has_fuel(rs::Vector{T}) where T <: AbstractResource return findall(r -> get(r, :fuel, "None") != "None", rs) end -# VRE and STOR -const VRE_STOR = Union{VRE, STOR} -inv_cost_per_mwyr(r::VRE_STOR)::Float64 = r.inv_cost_per_mwyr -fixed_om_cost_per_mwyr(r::VRE_STOR)::Float64 = r.fixed_om_cost_per_mwyr - # STOR interface storage(rs::Vector{T}) where T <: AbstractResource = findall(r -> isa(r,STOR), rs) @@ -150,13 +145,12 @@ max_duration(r::STOR)::Float64 = r.max_duration existing_capacity_mwh(r::STOR)::Float64 = r.existing_cap_mwh max_capacity_mwh(r::STOR)::Float64 = r.max_cap_mwh min_capacity_mwh(r::STOR)::Float64 = r.min_cap_mwh -var_om_cost_per_mwh(r::STOR)::Float64 = r.var_om_cost_per_mwh fixed_om_cost_per_mwhyr(r::STOR)::Float64 = r.fixed_om_cost_per_mwhyr inv_cost_per_mwhyr(r::STOR)::Float64 = r.inv_cost_per_mwhyr -existing_charge_capacity_mw(r::STOR)::Float64 = r.existing_charge_cap_mw -fixed_om_cost_charge_per_mwyr(r::STOR)::Float64 = r.fixed_om_cost_charge_per_mwyr +existing_charge_capacity_mw(r::STOR)::Float64 = get(r, :existing_charge_cap_mw, default) +fixed_om_cost_charge_per_mwyr(r::STOR)::Float64 = get(r, :fixed_om_cost_charge_per_mwyr, default) inv_cost_charge_per_mwyr(r::STOR)::Float64 = r.inv_cost_charge_per_mwyr max_charge_capacity_mw(r::STOR)::Float64 = r.max_charge_cap_mw min_charge_capacity_mw(r::STOR)::Float64 = r.min_charge_cap_mw @@ -270,7 +264,9 @@ var_om_cost_per_mwh_in(r::FLEX)::Float64 = r.var_om_cost_per_mwh_in flexible_demand_energy_eff(r::FLEX)::Float64 = r.flexible_demand_energy_eff max_flexible_demand_delay(r::FLEX)::Float64 = r.max_flexible_demand_delay max_flexible_demand_advance(r::FLEX)::Float64 = r.max_flexible_demand_advance -flexible_demand_energy_eff(r::FLEX)::Float64 = r.flexible_demand_energy_eff + +# MUST_RUN interface +must_run(rs::Vector{T}) where T <: AbstractResource = findall(r -> isa(r,MUST_RUN), rs) # MGA resources_with_mga(rs::Vector{T}) where T <: AbstractResource = rs[findall(r -> get(r, :mga, default) == 1, rs)] diff --git a/src/model/resources/storage/investment_charge.jl b/src/model/resources/storage/investment_charge.jl index 27ed0b8d13..38d2cc4259 100644 --- a/src/model/resources/storage/investment_charge.jl +++ b/src/model/resources/storage/investment_charge.jl @@ -42,13 +42,7 @@ function investment_charge!(EP::Model, inputs::Dict, setup::Dict) println("Charge Investment Module") - resources = inputs["RESOURCES"] - - max_charge_capacity_mw(y) = max_charge_capacity_mw(resources[y]) - min_charge_capacity_mw(y) = min_charge_capacity_mw(resources[y]) - existing_charge_capacity_mw(y) = existing_charge_capacity_mw(resources[y]) - fixed_om_cost_charge_per_mwyr(y) = fixed_om_cost_charge_per_mwyr(resources[y]) - inv_cost_charge_per_mwyr(y) = inv_cost_charge_per_mwyr(resources[y]) + res = inputs["RESOURCES"] MultiStage = setup["MultiStage"] @@ -76,7 +70,7 @@ function investment_charge!(EP::Model, inputs::Dict, setup::Dict) if MultiStage == 1 @expression(EP, eExistingCapCharge[y in STOR_ASYMMETRIC], vEXISTINGCAPCHARGE[y]) else - @expression(EP, eExistingCapCharge[y in STOR_ASYMMETRIC], existing_charge_capacity_mw(y)) + @expression(EP, eExistingCapCharge[y in STOR_ASYMMETRIC], existing_charge_capacity_mw(res[y])) end @expression(EP, eTotalCapCharge[y in STOR_ASYMMETRIC], @@ -97,9 +91,9 @@ function investment_charge!(EP::Model, inputs::Dict, setup::Dict) # If resource is not eligible for new charge capacity, fixed costs are only O&M costs @expression(EP, eCFixCharge[y in STOR_ASYMMETRIC], if y in NEW_CAP_CHARGE # Resources eligible for new charge capacity - inv_cost_charge_per_mwyr(y)*vCAPCHARGE[y] + fixed_om_cost_charge_per_mwyr(y)*eTotalCapCharge[y] + inv_cost_charge_per_mwyr(res[y])*vCAPCHARGE[y] + fixed_om_cost_charge_per_mwyr(res[y])*eTotalCapCharge[y] else - fixed_om_cost_charge_per_mwyr(y)*eTotalCapCharge[y] + fixed_om_cost_charge_per_mwyr(res[y])*eTotalCapCharge[y] end ) @@ -131,11 +125,11 @@ function investment_charge!(EP::Model, inputs::Dict, setup::Dict) # Constraint on maximum charge capacity (if applicable) [set input to -1 if no constraint on maximum charge capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Charge_Cap_MW is >= Max_Charge_Cap_MWh and lead to infeasabilty - @constraint(EP, cMaxCapCharge[y in intersect(resource_id.(has_positive_max_capacity_mw(resources)), STOR_ASYMMETRIC)], eTotalCapCharge[y] <= max_charge_capacity_mw(y)) + @constraint(EP, cMaxCapCharge[y in intersect(has_positive_max_capacity_mw(res), STOR_ASYMMETRIC)], eTotalCapCharge[y] <= max_charge_capacity_mw(res[y])) # Constraint on minimum charge capacity (if applicable) [set input to -1 if no constraint on minimum charge capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Charge_Cap_MW is <= Min_Charge_Cap_MWh and lead to infeasabilty - @constraint(EP, cMinCapCharge[y in intersect(resource_id.(has_positive_min_charge_capacity_mw(resources)), STOR_ASYMMETRIC)], eTotalCapCharge[y] >= min_charge_capacity_mw(y)) + @constraint(EP, cMinCapCharge[y in intersect(has_positive_min_charge_capacity_mw(res), STOR_ASYMMETRIC)], eTotalCapCharge[y] >= min_charge_capacity_mw(res[y])) end diff --git a/src/model/resources/storage/investment_energy.jl b/src/model/resources/storage/investment_energy.jl index d7696d94c0..238c6f885d 100644 --- a/src/model/resources/storage/investment_energy.jl +++ b/src/model/resources/storage/investment_energy.jl @@ -45,15 +45,7 @@ function investment_energy!(EP::Model, inputs::Dict, setup::Dict) println("Storage Investment Module") - resources = inputs["RESOURCES"] - - min_duration(y) = min_duration(resources[y]) - max_duration(y) = max_duration(resources[y]) - max_capacity_mwh(y) = max_capacity_mwh(resources[y]) - min_capacity_mwh(y) = min_capacity_mwh(resources[y]) - existing_capacity_mwh(y) = existing_capacity_mwh(resources[y]) - fixed_om_cost_per_mwhyr(y) = fixed_om_cost_per_mwhyr(resources[y]) - inv_cost_per_mwhyr(y) = inv_cost_per_mwhyr(resources[y]) + res = inputs["RESOURCES"] MultiStage = setup["MultiStage"] @@ -80,7 +72,7 @@ function investment_energy!(EP::Model, inputs::Dict, setup::Dict) if MultiStage == 1 @expression(EP, eExistingCapEnergy[y in STOR_ALL], vEXISTINGCAPENERGY[y]) else - @expression(EP, eExistingCapEnergy[y in STOR_ALL], existing_capacity_mwh(y)) + @expression(EP, eExistingCapEnergy[y in STOR_ALL], existing_capacity_mwh(res[y])) end @expression(EP, eTotalCapEnergy[y in STOR_ALL], @@ -101,9 +93,9 @@ function investment_energy!(EP::Model, inputs::Dict, setup::Dict) # If resource is not eligible for new energy capacity, fixed costs are only O&M costs @expression(EP, eCFixEnergy[y in STOR_ALL], if y in NEW_CAP_ENERGY # Resources eligible for new capacity - inv_cost_per_mwhyr(y)*vCAPENERGY[y] + fixed_om_cost_per_mwhyr(y)*eTotalCapEnergy[y] + inv_cost_per_mwhyr(res[y])*vCAPENERGY[y] + fixed_om_cost_per_mwhyr(res[y])*eTotalCapEnergy[y] else - fixed_om_cost_per_mwhyr(y)*eTotalCapEnergy[y] + fixed_om_cost_per_mwhyr(res[y])*eTotalCapEnergy[y] end ) @@ -123,7 +115,7 @@ function investment_energy!(EP::Model, inputs::Dict, setup::Dict) ### Constraints ### if MultiStage == 1 - @constraint(EP, cExistingCapEnergy[y in STOR_ALL], EP[:vEXISTINGCAPENERGY][y] == existing_capacity_mwh(y)) + @constraint(EP, cExistingCapEnergy[y in STOR_ALL], EP[:vEXISTINGCAPENERGY][y] == existing_capacity_mwh(res[y])) end ## Constraints on retirements and capacity additions @@ -133,14 +125,14 @@ function investment_energy!(EP::Model, inputs::Dict, setup::Dict) ## Constraints on new built energy capacity # Constraint on maximum energy capacity (if applicable) [set input to -1 if no constraint on maximum energy capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MWh is >= Max_Cap_MWh and lead to infeasabilty - @constraint(EP, cMaxCapEnergy[y in intersect(has_positive_max_capacity_mwh(resources), STOR_ALL)], eTotalCapEnergy[y] <= max_capacity_mwh(y)) + @constraint(EP, cMaxCapEnergy[y in intersect(has_positive_max_capacity_mwh(res), STOR_ALL)], eTotalCapEnergy[y] <= max_capacity_mwh(res[y])) # Constraint on minimum energy capacity (if applicable) [set input to -1 if no constraint on minimum energy apacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MWh is <= Min_Cap_MWh and lead to infeasabilty - @constraint(EP, cMinCapEnergy[y in intersect(has_positive_min_capacity_mwh(resources), STOR_ALL)], eTotalCapEnergy[y] >= min_capacity_mwh(y)) + @constraint(EP, cMinCapEnergy[y in intersect(has_positive_min_capacity_mwh(res), STOR_ALL)], eTotalCapEnergy[y] >= min_capacity_mwh(res[y])) # Max and min constraints on energy storage capacity built (as proportion to discharge power capacity) - @constraint(EP, cMinCapEnergyDuration[y in STOR_ALL], EP[:eTotalCapEnergy][y] >= min_duration(y) * EP[:eTotalCap][y]) - @constraint(EP, cMaxCapEnergyDuration[y in STOR_ALL], EP[:eTotalCapEnergy][y] <= max_duration(y) * EP[:eTotalCap][y]) + @constraint(EP, cMinCapEnergyDuration[y in STOR_ALL], EP[:eTotalCapEnergy][y] >= min_duration(res[y]) * EP[:eTotalCap][y]) + @constraint(EP, cMaxCapEnergyDuration[y in STOR_ALL], EP[:eTotalCapEnergy][y] <= max_duration(res[y]) * EP[:eTotalCap][y]) end diff --git a/src/model/resources/storage/long_duration_storage.jl b/src/model/resources/storage/long_duration_storage.jl index 29ee1f8ecb..c62a19c151 100644 --- a/src/model/resources/storage/long_duration_storage.jl +++ b/src/model/resources/storage/long_duration_storage.jl @@ -66,10 +66,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) println("Long Duration Storage Module") - resources = inputs["RESOURCES"] - self_discharge(y) = self_discharge(resources[y]) - efficiency_up(y) = efficiency_up(resources[y]) - efficiency_down(y) = efficiency_down(resources[y]) + res = inputs["RESOURCES"] CapacityReserveMargin = setup["CapacityReserveMargin"] @@ -112,8 +109,8 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) # Alternative to cSoCBalStart constraint which is included when not modeling operations wrapping and long duration storage # Note: tw_min = hours_per_subperiod*(w-1)+1; tw_max = hours_per_subperiod*w @constraint(EP, cSoCBalLongDurationStorageStart[w=1:REP_PERIOD, y in STOR_LONG_DURATION], - EP[:vS][y,hours_per_subperiod*(w-1)+1] == (1-self_discharge(y))*(EP[:vS][y,hours_per_subperiod*w]-vdSOC[y,w]) - -(1/efficiency_down(y)*EP[:vP][y,hours_per_subperiod*(w-1)+1])+(efficiency_up(y)*EP[:vCHARGE][y,hours_per_subperiod*(w-1)+1])) + EP[:vS][y,hours_per_subperiod*(w-1)+1] == (1-self_discharge(res[y]))*(EP[:vS][y,hours_per_subperiod*w]-vdSOC[y,w]) + -(1/efficiency_down(res[y])*EP[:vP][y,hours_per_subperiod*(w-1)+1])+(efficiency_up(res[y])*EP[:vCHARGE][y,hours_per_subperiod*(w-1)+1])) # Storage at beginning of period w = storage at beginning of period w-1 + storage built up in period w (after n representative periods) ## Multiply storage build up term from prior period with corresponding weight @@ -138,8 +135,8 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) # Alternative to cVSoCBalStart constraint which is included when not modeling operations wrapping and long duration storage # Note: tw_min = hours_per_subperiod*(w-1)+1; tw_max = hours_per_subperiod*w @constraint(EP, cVSoCBalLongDurationStorageStart[w=1:REP_PERIOD, y in STOR_LONG_DURATION], - EP[:vCAPRES_socinreserve][y,hours_per_subperiod*(w-1)+1] == (1-self_discharge(y))*(EP[:vCAPRES_socinreserve][y,hours_per_subperiod*w]-vCAPRES_dsoc[y,w]) - +(1/efficiency_down(y)*EP[:vCAPRES_discharge][y,hours_per_subperiod*(w-1)+1])-(efficiency_up(y)*EP[:vCAPRES_charge][y,hours_per_subperiod*(w-1)+1])) + EP[:vCAPRES_socinreserve][y,hours_per_subperiod*(w-1)+1] == (1-self_discharge(res[y]))*(EP[:vCAPRES_socinreserve][y,hours_per_subperiod*w]-vCAPRES_dsoc[y,w]) + +(1/efficiency_down(res[y])*EP[:vCAPRES_discharge][y,hours_per_subperiod*(w-1)+1])-(efficiency_up(res[y])*EP[:vCAPRES_charge][y,hours_per_subperiod*(w-1)+1])) # Storage held in reserve at beginning of period w = storage at beginning of period w-1 + storage built up in period w (after n representative periods) ## Multiply storage build up term from prior period with corresponding weight diff --git a/src/model/resources/storage/storage.jl b/src/model/resources/storage/storage.jl index 8db762bd58..0046f89d9a 100644 --- a/src/model/resources/storage/storage.jl +++ b/src/model/resources/storage/storage.jl @@ -131,7 +131,7 @@ The above reserve related constraints are established by ```storage_all_reserves function storage!(EP::Model, inputs::Dict, setup::Dict) println("Storage Resources Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] STOR_ALL = inputs["STOR_ALL"] @@ -166,7 +166,7 @@ function storage!(EP::Model, inputs::Dict, setup::Dict) # ESR Lossses if EnergyShareRequirement >= 1 if IncludeLossesInESR == 1 - @expression(EP, eESRStor[ESR=1:inputs["nESR"]], sum(inputs["dfESR"][z,ESR]*sum(EP[:eELOSS][y] for y in intersect(resources_in_zone_by_rid(resources,z),STOR_ALL)) for z=findall(x->x>0,inputs["dfESR"][:,ESR]))) + @expression(EP, eESRStor[ESR=1:inputs["nESR"]], sum(inputs["dfESR"][z,ESR]*sum(EP[:eELOSS][y] for y in intersect(resources_in_zone_by_rid(res,z),STOR_ALL)) for z=findall(x->x>0,inputs["dfESR"][:,ESR]))) add_similar_to_expression!(EP[:eESR], -eESRStor) end end diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index c3e2a618b2..c42ca31ad9 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -7,15 +7,7 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) # Setup variables, constraints, and expressions common to all storage resources println("Storage Core Resources Module") - resources = inputs["RESOURCES"] - - efficiency_up(y) = efficiency_up(resources[y]) - efficiency_down(y) = efficiency_down(resources[y]) - self_discharge(y) = self_discharge(resources[y]) - - var_om_cost_per_mwh(y) = var_om_cost_per_mwh(resources[y]) - var_om_cost_per_mwh_in(y) = var_om_cost_per_mwh_in(resources[y]) - + res = inputs["RESOURCES"] Reserves = setup["Reserves"] CapacityReserveMargin = setup["CapacityReserveMargin"] @@ -58,7 +50,7 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) ## Objective Function Expressions ## #Variable costs of "charging" for technologies "y" during hour "t" in zone "z" - @expression(EP, eCVar_in[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(y)*vCHARGE[y,t]) + @expression(EP, eCVar_in[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(res[y])*vCHARGE[y,t]) # Sum individual resource contributions to variable charging costs to get total variable charging costs @expression(EP, eTotalCVarInT[t=1:T], sum(eCVar_in[y,t] for y in STOR_ALL)) @@ -68,13 +60,13 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 #Variable costs of "virtual charging" for technologies "y" during hour "t" in zone "z" - @expression(EP, eCVar_in_virtual[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(y)*vCAPRES_charge[y,t]) + @expression(EP, eCVar_in_virtual[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh_in(res[y])*vCAPRES_charge[y,t]) @expression(EP, eTotalCVarInT_virtual[t=1:T], sum(eCVar_in_virtual[y,t] for y in STOR_ALL)) @expression(EP, eTotalCVarIn_virtual, sum(eTotalCVarInT_virtual[t] for t in 1:T)) EP[:eObj] += eTotalCVarIn_virtual #Variable costs of "virtual discharging" for technologies "y" during hour "t" in zone "z" - @expression(EP, eCVar_out_virtual[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh(y)*vCAPRES_discharge[y,t]) + @expression(EP, eCVar_out_virtual[y in STOR_ALL,t=1:T], inputs["omega"][t]*var_om_cost_per_mwh(res[y])*vCAPRES_discharge[y,t]) @expression(EP, eTotalCVarOutT_virtual[t=1:T], sum(eCVar_out_virtual[y,t] for y in STOR_ALL)) @expression(EP, eTotalCVarOut_virtual, sum(eTotalCVarOutT_virtual[t] for t in 1:T)) EP[:eObj] += eTotalCVarOut_virtual @@ -84,7 +76,7 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) # Term to represent net dispatch from storage in any period @expression(EP, ePowerBalanceStor[t=1:T, z=1:Z], - sum(EP[:vP][y,t]-EP[:vCHARGE][y,t] for y in intersect(resources_in_zone_by_rid(resources,z),STOR_ALL)) + sum(EP[:vP][y,t]-EP[:vCHARGE][y,t] for y in intersect(resources_in_zone_by_rid(res,z),STOR_ALL)) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceStor) @@ -100,8 +92,8 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) CONSTRAINTSET = STOR_ALL end @constraint(EP, cSoCBalStart[t in START_SUBPERIODS, y in CONSTRAINTSET], EP[:vS][y,t] == - EP[:vS][y,t+hours_per_subperiod-1] - (1/efficiency_down(y) * EP[:vP][y,t]) - + (efficiency_up(y)*EP[:vCHARGE][y,t]) - (self_discharge(y) * EP[:vS][y,t+hours_per_subperiod-1])) + EP[:vS][y,t+hours_per_subperiod-1] - (1/efficiency_down(res[y]) * EP[:vP][y,t]) + + (efficiency_up(res[y])*EP[:vCHARGE][y,t]) - (self_discharge(res[y]) * EP[:vS][y,t+hours_per_subperiod-1])) @constraints(EP, begin @@ -110,7 +102,7 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) # energy stored for the next hour cSoCBalInterior[t in INTERIOR_SUBPERIODS, y in STOR_ALL], EP[:vS][y,t] == - EP[:vS][y,t-1]-(1/efficiency_down(y)*EP[:vP][y,t])+(efficiency_up(y)*EP[:vCHARGE][y,t])-(self_discharge(y)*EP[:vS][y,t-1]) + EP[:vS][y,t-1]-(1/efficiency_down(res[y])*EP[:vP][y,t])+(efficiency_up(res[y])*EP[:vCHARGE][y,t])-(self_discharge(res[y])*EP[:vS][y,t-1]) end) # Storage discharge and charge power (and reserve contribution) related constraints: @@ -125,18 +117,18 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) # wrapping from end of sample period to start of sample period for energy capacity constraint @constraints(EP, begin [y in STOR_ALL, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_ALL, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:vS][y, hoursbefore(hours_per_subperiod,t,1)]*efficiency_down(y) + [y in STOR_ALL, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:vS][y, hoursbefore(hours_per_subperiod,t,1)]*efficiency_down(res[y]) end) else @constraints(EP, begin [y in STOR_ALL, t=1:T], EP[:vP][y,t] <= EP[:eTotalCap][y] - [y in STOR_ALL, t=1:T], EP[:vP][y,t] <= EP[:vS][y, hoursbefore(hours_per_subperiod,t,1)]*efficiency_down(y) + [y in STOR_ALL, t=1:T], EP[:vP][y,t] <= EP[:vS][y, hoursbefore(hours_per_subperiod,t,1)]*efficiency_down(res[y]) end) end end # From CO2 Policy module - expr = @expression(EP, [z=1:Z], sum(EP[:eELOSS][y] for y in intersect(STOR_ALL, resources_in_zone_by_rid(resources,z)))) + expr = @expression(EP, [z=1:Z], sum(EP[:eELOSS][y] for y in intersect(STOR_ALL, resources_in_zone_by_rid(res,z)))) add_similar_to_expression!(EP[:eELOSSByZone], expr) # Capacity Reserve Margin policy @@ -146,12 +138,12 @@ function storage_all!(EP::Model, inputs::Dict, setup::Dict) # Links energy held in reserve in first time step with decisions in last time step of each subperiod # We use a modified formulation of this constraint (cVSoCBalLongDurationStorageStart) when operations wrapping and long duration storage are being modeled @constraint(EP, cVSoCBalStart[t in START_SUBPERIODS, y in CONSTRAINTSET], EP[:vCAPRES_socinreserve][y,t] == - EP[:vCAPRES_socinreserve][y,t+hours_per_subperiod-1] + (1/efficiency_down(y) * EP[:vCAPRES_discharge][y,t]) - - (efficiency_up(y)*EP[:vCAPRES_charge][y,t]) - (self_discharge(y) * EP[:vCAPRES_socinreserve][y,t+hours_per_subperiod-1])) + EP[:vCAPRES_socinreserve][y,t+hours_per_subperiod-1] + (1/efficiency_down(res[y]) * EP[:vCAPRES_discharge][y,t]) + - (efficiency_up(res[y])*EP[:vCAPRES_charge][y,t]) - (self_discharge(res[y]) * EP[:vCAPRES_socinreserve][y,t+hours_per_subperiod-1])) # energy held in reserve for the next hour @constraint(EP, cVSoCBalInterior[t in INTERIOR_SUBPERIODS, y in STOR_ALL], EP[:vCAPRES_socinreserve][y,t] == - EP[:vCAPRES_socinreserve][y,t-1]+(1/efficiency_down(y)*EP[:vCAPRES_discharge][y,t])-(efficiency_up(y)*EP[:vCAPRES_charge][y,t])-(self_discharge(y)*EP[:vCAPRES_socinreserve][y,t-1])) + EP[:vCAPRES_socinreserve][y,t-1]+(1/efficiency_down(res[y])*EP[:vCAPRES_discharge][y,t])-(efficiency_up(res[y])*EP[:vCAPRES_charge][y,t])-(self_discharge(res[y])*EP[:vCAPRES_socinreserve][y,t-1])) # energy held in reserve acts as a lower bound on the total energy held in storage @constraint(EP, cSOCMinCapRes[t in 1:T, y in STOR_ALL], EP[:vS][y,t] >= EP[:vCAPRES_socinreserve][y,t]) @@ -160,7 +152,7 @@ end function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] p = inputs["hours_per_subperiod"] CapacityReserveMargin = setup["CapacityReserveMargin"] > 1 @@ -183,14 +175,9 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) eTotalCap = EP[:eTotalCap] eTotalCapEnergy = EP[:eTotalCapEnergy] - eff_up(y) = efficiency_up(resources[y]) - eff_down(y) = efficiency_down(resources[y]) - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) - # Maximum storage contribution to reserves is a specified fraction of installed capacity - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= reg_max(y) * eTotalCap[y]) - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * eTotalCap[y]) + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= reg_max(res[y]) * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= rsv_max(res[y]) * eTotalCap[y]) # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) @@ -208,7 +195,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) + @constraint(EP, [y in STOR_REG, t in 1:T], efficiency_up(res[y])*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type @@ -222,5 +209,5 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Maximum discharging rate and contribution to reserves up must be less than power rating @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period - @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * efficiency_down(res[y])) end diff --git a/src/model/resources/storage/storage_asymmetric.jl b/src/model/resources/storage/storage_asymmetric.jl index d3e770f75b..4ee04e650a 100644 --- a/src/model/resources/storage/storage_asymmetric.jl +++ b/src/model/resources/storage/storage_asymmetric.jl @@ -10,7 +10,7 @@ function storage_asymmetric!(EP::Model, inputs::Dict, setup::Dict) println("Storage Resources with Asmymetric Charge/Discharge Capacity Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] Reserves = setup["Reserves"] CapacityReserveMargin = setup["CapacityReserveMargin"] diff --git a/src/model/resources/thermal/thermal.jl b/src/model/resources/thermal/thermal.jl index 6660fd38f8..fa304f38bb 100644 --- a/src/model/resources/thermal/thermal.jl +++ b/src/model/resources/thermal/thermal.jl @@ -4,7 +4,7 @@ The thermal module creates decision variables, expressions, and constraints rela This module uses the following 'helper' functions in separate files: ```thermal_commit()``` for thermal resources subject to unit commitment decisions and constraints (if any) and ```thermal_no_commit()``` for thermal resources not subject to unit commitment (if any). """ function thermal!(EP::Model, inputs::Dict, setup::Dict) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -13,7 +13,7 @@ function thermal!(EP::Model, inputs::Dict, setup::Dict) THERM_NO_COMMIT = inputs["THERM_NO_COMMIT"] THERM_ALL = inputs["THERM_ALL"] - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] if !isempty(THERM_COMMIT) thermal_commit!(EP, inputs, setup) @@ -24,7 +24,7 @@ function thermal!(EP::Model, inputs::Dict, setup::Dict) end ##CO2 Polcy Module Thermal Generation by zone @expression(EP, eGenerationByThermAll[z=1:Z, t=1:T], # the unit is GW - sum(EP[:vP][y,t] for y in intersect(inputs["THERM_ALL"], resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(inputs["THERM_ALL"], resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:eGenerationByZone], eGenerationByThermAll) @@ -36,7 +36,7 @@ function thermal!(EP::Model, inputs::Dict, setup::Dict) sum(capresfactor(y, capres) * EP[:eTotalCap][y] for y in THERM_ALL)) add_similar_to_expression!(EP[:eCapResMarBalance], eCapResMarBalanceThermal) - MAINT = resources_with_maintenance(resources) + MAINT = resources_with_maintenance(res) if !isempty(intersect(MAINT, THERM_COMMIT)) thermal_maintenance_capacity_reserve_margin_adjustment!(EP, inputs) end @@ -44,7 +44,7 @@ function thermal!(EP::Model, inputs::Dict, setup::Dict) #= ##CO2 Polcy Module Thermal Generation by zone @expression(EP, eGenerationByThermAll[z=1:Z, t=1:T], # the unit is GW - sum(EP[:vP][y,t] for y in intersect(inputs["THERM_ALL"], resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(inputs["THERM_ALL"], resources_in_zone_by_rid(res,z))) ) EP[:eGenerationByZone] += eGenerationByThermAll =# ##From main diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index 565ad94233..2a86203757 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -128,13 +128,7 @@ function thermal_commit!(EP::Model, inputs::Dict, setup::Dict) println("Thermal (Unit Commitment) Resources Module") - resources = inputs["RESOURCES"] - cap_size(y) = cap_size(resources[y]) - min_power(y) = min_power(resources[y]) - up_time(y) = up_time(resources[y]) - down_time(y) = down_time(resources[y]) - ramp_up_percentage(y) = ramp_up_percentage(resources[y]) - ramp_down_percentage(y) = ramp_down_percentage(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -161,7 +155,7 @@ function thermal_commit!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceThermCommit[t=1:T, z=1:Z], - sum(EP[:vP][y,t] for y in intersect(THERM_COMMIT, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(THERM_COMMIT, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceThermCommit) @@ -169,9 +163,9 @@ function thermal_commit!(EP::Model, inputs::Dict, setup::Dict) ### Capacitated limits on unit commitment decision variables (Constraints #1-3) @constraints(EP, begin - [y in THERM_COMMIT, t=1:T], EP[:vCOMMIT][y,t] <= EP[:eTotalCap][y]/cap_size(y) - [y in THERM_COMMIT, t=1:T], EP[:vSTART][y,t] <= EP[:eTotalCap][y]/cap_size(y) - [y in THERM_COMMIT, t=1:T], EP[:vSHUT][y,t] <= EP[:eTotalCap][y]/cap_size(y) + [y in THERM_COMMIT, t=1:T], EP[:vCOMMIT][y,t] <= EP[:eTotalCap][y]/cap_size(res[y]) + [y in THERM_COMMIT, t=1:T], EP[:vSTART][y,t] <= EP[:eTotalCap][y]/cap_size(res[y]) + [y in THERM_COMMIT, t=1:T], EP[:vSHUT][y,t] <= EP[:eTotalCap][y]/cap_size(res[y]) end) # Commitment state constraint linking startup and shutdown decisions (Constraint #4) @@ -185,15 +179,15 @@ function thermal_commit!(EP::Model, inputs::Dict, setup::Dict) # Links last time step with first time step, ensuring position in hour 1 is within eligible ramp of final hour position # rampup constraints @constraint(EP,[y in THERM_COMMIT, t in 1:T], - EP[:vP][y,t] - EP[:vP][y, hoursbefore(p, t, 1)] + regulation_term[y,t] + reserves_term[y,t] <= ramp_up_percentage(y)*cap_size(y)*(EP[:vCOMMIT][y,t]-EP[:vSTART][y,t]) - + min(inputs["pP_Max"][y,t],max(min_power(y),ramp_up_percentage(y)))*cap_size(y)*EP[:vSTART][y,t] - - min_power(y)*cap_size(y)*EP[:vSHUT][y,t]) + EP[:vP][y,t] - EP[:vP][y, hoursbefore(p, t, 1)] + regulation_term[y,t] + reserves_term[y,t] <= ramp_up_percentage(res[y])*cap_size(res[y])*(EP[:vCOMMIT][y,t]-EP[:vSTART][y,t]) + + min(inputs["pP_Max"][y,t],max(min_power(res[y]),ramp_up_percentage(res[y])))*cap_size(res[y])*EP[:vSTART][y,t] + - min_power(res[y])*cap_size(res[y])*EP[:vSHUT][y,t]) # rampdown constraints @constraint(EP,[y in THERM_COMMIT, t in 1:T], - EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] - regulation_term[y,t] + reserves_term[y, hoursbefore(p,t,1)] <= ramp_down_percentage(y)*cap_size(y)*(EP[:vCOMMIT][y,t]-EP[:vSTART][y,t]) - - min_power(y)*cap_size(y)*EP[:vSTART][y,t] - + min(inputs["pP_Max"][y,t],max(min_power(y),ramp_down_percentage(y)))*cap_size(y)*EP[:vSHUT][y,t]) + EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] - regulation_term[y,t] + reserves_term[y, hoursbefore(p,t,1)] <= ramp_down_percentage(res[y])*cap_size(res[y])*(EP[:vCOMMIT][y,t]-EP[:vSTART][y,t]) + - min_power(res[y])*cap_size(res[y])*EP[:vSTART][y,t] + + min(inputs["pP_Max"][y,t],max(min_power(res[y]),ramp_down_percentage(res[y])))*cap_size(res[y])*EP[:vSHUT][y,t]) ### Minimum and maximum power output constraints (Constraints #7-8) @@ -203,28 +197,28 @@ function thermal_commit!(EP::Model, inputs::Dict, setup::Dict) else @constraints(EP, begin # Minimum stable power generated per technology "y" at hour "t" > Min power - [y in THERM_COMMIT, t=1:T], EP[:vP][y,t] >= min_power(y)*cap_size(y)*EP[:vCOMMIT][y,t] + [y in THERM_COMMIT, t=1:T], EP[:vP][y,t] >= min_power(res[y])*cap_size(res[y])*EP[:vCOMMIT][y,t] # Maximum power generated per technology "y" at hour "t" < Max power - [y in THERM_COMMIT, t=1:T], EP[:vP][y,t] <= inputs["pP_Max"][y,t]*cap_size(y)*EP[:vCOMMIT][y,t] + [y in THERM_COMMIT, t=1:T], EP[:vP][y,t] <= inputs["pP_Max"][y,t]*cap_size(res[y])*EP[:vCOMMIT][y,t] end) end ### Minimum up and down times (Constraints #9-10) Up_Time = zeros(Int, G) - Up_Time[THERM_COMMIT] .= Int.(floor.(up_time.(resources[THERM_COMMIT]))) + Up_Time[THERM_COMMIT] .= Int.(floor.(up_time.(res[THERM_COMMIT]))) @constraint(EP, [y in THERM_COMMIT, t in 1:T], EP[:vCOMMIT][y,t] >= sum(EP[:vSTART][y, u] for u in hoursbefore(p, t, 0:(Up_Time[y] - 1))) ) Down_Time = zeros(Int, G) - Down_Time[THERM_COMMIT] .= Int.(floor.(down_time.(resources[THERM_COMMIT]))) + Down_Time[THERM_COMMIT] .= Int.(floor.(down_time.(res[THERM_COMMIT]))) @constraint(EP, [y in THERM_COMMIT, t in 1:T], - EP[:eTotalCap][y]/cap_size(y)-EP[:vCOMMIT][y,t] >= sum(EP[:vSHUT][y, u] for u in hoursbefore(p, t, 0:(Down_Time[y] - 1))) + EP[:eTotalCap][y]/cap_size(res[y])-EP[:vCOMMIT][y,t] >= sum(EP[:vSHUT][y, u] for u in hoursbefore(p, t, 0:(Down_Time[y] - 1))) ) ## END Constraints for thermal units subject to integer (discrete) unit commitment decisions - if !isempty(resources_with_maintenance(resources)) + if !isempty(resources_with_maintenance(res)) maintenance_formulation_thermal_commit!(EP, inputs, setup) end end @@ -275,7 +269,7 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) println("Thermal Commit Reserves Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) @@ -288,20 +282,17 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) vREG = EP[:vREG] vRSV = EP[:vRSV] - commit(y,t) = cap_size(resources[y]) * EP[:vCOMMIT][y,t] - min_power(y) = min_power(resources[y]) + commit(y,t) = cap_size(res[y]) * EP[:vCOMMIT][y,t] max_power(y,t) = inputs["pP_Max"][y,t] - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(y) * commit(y, t)) - @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(y) * commit(y, t)) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(res[y]) * commit(y, t)) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(res[y]) * commit(y, t)) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power expr = extract_time_series_to_expression(vP, THERM_COMMIT) add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) - @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * commit(y, t)) + @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] >= min_power(res[y]) * commit(y, t)) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power expr = extract_time_series_to_expression(vP, THERM_COMMIT) @@ -320,11 +311,11 @@ function maintenance_formulation_thermal_commit!(EP::Model, inputs::Dict, setup: @info "Maintenance Module for Thermal plants" ensure_maintenance_variable_records!(inputs) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] - by_rid(rid, sym) = by_rid_df(rid, sym, resources) + by_rid(rid, sym) = by_rid_df(rid, sym, res) - MAINT = resources_with_maintenance(resources) + MAINT = resources_with_maintenance(res) resource_component(y) = by_rid(y, :Resource) cap(y) = by_rid(y, :Cap_Size) maint_dur(y) = Int(floor(by_rid(y, :Maintenance_Duration))) @@ -361,12 +352,12 @@ end """ function thermal_maintenance_capacity_reserve_margin_adjustment!(EP::Model, inputs::Dict) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) ncapres = inputs["NCapacityReserveMargin"] THERM_COMMIT = inputs["THERM_COMMIT"] - MAINT = resources_with_maintenance(resources) + MAINT = resources_with_maintenance(res) applicable_resources = intersect(MAINT, THERM_COMMIT) maint_adj = @expression(EP, [capres in 1:ncapres, t in 1:T], diff --git a/src/model/resources/thermal/thermal_no_commit.jl b/src/model/resources/thermal/thermal_no_commit.jl index fb680436da..cb2683809f 100644 --- a/src/model/resources/thermal/thermal_no_commit.jl +++ b/src/model/resources/thermal/thermal_no_commit.jl @@ -45,10 +45,7 @@ function thermal_no_commit!(EP::Model, inputs::Dict, setup::Dict) println("Thermal (No Unit Commitment) Resources Module") - resources = inputs["RESOURCES"] - min_power(y) = min_power(resources[y]) - ramp_up_percentage(y) = ramp_up_percentage(resources[y]) - ramp_down_percentage(y) = ramp_down_percentage(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -61,7 +58,7 @@ function thermal_no_commit!(EP::Model, inputs::Dict, setup::Dict) ## Power Balance Expressions ## @expression(EP, ePowerBalanceThermNoCommit[t=1:T, z=1:Z], - sum(EP[:vP][y,t] for y in intersect(THERM_NO_COMMIT, resources_in_zone_by_rid(resources,z))) + sum(EP[:vP][y,t] for y in intersect(THERM_NO_COMMIT, resources_in_zone_by_rid(res,z))) ) add_similar_to_expression!(EP[:ePowerBalance], ePowerBalanceThermNoCommit) @@ -71,10 +68,10 @@ function thermal_no_commit!(EP::Model, inputs::Dict, setup::Dict) @constraints(EP, begin ## Maximum ramp up between consecutive hours - [y in THERM_NO_COMMIT, t in 1:T], EP[:vP][y,t] - EP[:vP][y, hoursbefore(p,t,1)] <= ramp_up_percentage(y)*EP[:eTotalCap][y] + [y in THERM_NO_COMMIT, t in 1:T], EP[:vP][y,t] - EP[:vP][y, hoursbefore(p,t,1)] <= ramp_up_percentage(res[y])*EP[:eTotalCap][y] ## Maximum ramp down between consecutive hours - [y in THERM_NO_COMMIT, t in 1:T], EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] <= ramp_down_percentage(y)*EP[:eTotalCap][y] + [y in THERM_NO_COMMIT, t in 1:T], EP[:vP][y, hoursbefore(p,t,1)] - EP[:vP][y,t] <= ramp_down_percentage(res[y])*EP[:eTotalCap][y] end) ### Minimum and maximum power output constraints (Constraints #3-4) @@ -84,7 +81,7 @@ function thermal_no_commit!(EP::Model, inputs::Dict, setup::Dict) else @constraints(EP, begin # Minimum stable power generated per technology "y" at hour "t" Min_Power - [y in THERM_NO_COMMIT, t=1:T], EP[:vP][y,t] >= min_power(y)*EP[:eTotalCap][y] + [y in THERM_NO_COMMIT, t=1:T], EP[:vP][y,t] >= min_power(res[y])*EP[:eTotalCap][y] # Maximum power generated per technology "y" at hour "t" [y in THERM_NO_COMMIT, t=1:T], EP[:vP][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] @@ -141,7 +138,7 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) println("Thermal No Commit Reserves Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) @@ -155,19 +152,16 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) vRSV = EP[:vRSV] eTotalCap = EP[:eTotalCap] - min_power(y) = min_power(resources[y]) max_power(y,t) = inputs["pP_Max"][y,t] - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(y) * eTotalCap[y]) - @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(y) * eTotalCap[y]) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(res[y]) * eTotalCap[y]) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(res[y]) * eTotalCap[y]) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power expr = extract_time_series_to_expression(vP, THERM_NO_COMMIT) add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) - @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * eTotalCap[y]) + @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] >= min_power(res[y]) * eTotalCap[y]) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power expr = extract_time_series_to_expression(vP, THERM_NO_COMMIT) diff --git a/src/model/resources/vre_stor/vre_stor.jl b/src/model/resources/vre_stor/vre_stor.jl index 18ac2f4e4b..9ef75a37b6 100644 --- a/src/model/resources/vre_stor/vre_stor.jl +++ b/src/model/resources/vre_stor/vre_stor.jl @@ -101,9 +101,7 @@ function vre_stor!(EP::Model, inputs::Dict, setup::Dict) ### LOAD DATA ### # Load generators dataframe, sets, and time periods - resources = inputs["RESOURCES"] - inv_cost_per_mwyr(y) = inv_cost_per_mwyr(resources[y]) - fixed_om_cost_per_mwyr(y) = fixed_om_cost_per_mwyr(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones @@ -136,9 +134,9 @@ function vre_stor!(EP::Model, inputs::Dict, setup::Dict) # Separate grid costs @expression(EP, eCGrid[y in VRE_STOR], if y in NEW_CAP # Resources eligible for new capacity - inv_cost_per_mwyr(y)*EP[:vCAP][y] + fixed_om_cost_per_mwyr(y)*EP[:eTotalCap][y] + inv_cost_per_mwyr(res[y])*EP[:vCAP][y] + fixed_om_cost_per_mwyr(res[y])*EP[:eTotalCap][y] else - fixed_om_cost_per_mwyr(y)*EP[:eTotalCap][y] + fixed_om_cost_per_mwyr(res[y])*EP[:eTotalCap][y] end ) @expression(EP, eTotalCGrid, sum(eCGrid[y] for y in VRE_STOR)) @@ -963,15 +961,7 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) T = inputs["T"] Z = inputs["Z"] - resources = inputs["RESOURCES"] - - existing_cap_mwh(y) = existing_cap_mwh(resources[y]) - max_capacity_mwh(y) = max_capacity_mwh(resources[y]) - min_capacity_mwh(y) = min_capacity_mwh(resources[y]) - inv_cost_per_mwhyr(y) = inv_cost_per_mwhyr(resources[y]) - fixed_om_cost_per_mwhyr(y) = fixed_om_cost_per_mwhyr(resources[y]) - self_discharge(y) = self_discharge(resources[y]) - existing_capacity_mwh(y) = existing_capacity_mwh(resources[y]) + res = inputs["RESOURCES"] STOR = inputs["VS_STOR"] dfVRE_STOR = inputs["dfVRE_STOR"] @@ -1030,7 +1020,7 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) if MultiStage == 1 @expression(EP, eExistingCapEnergy_VS[y in STOR], vEXISTINGCAPENERGY_VS[y]) else - @expression(EP, eExistingCapEnergy_VS[y in STOR], existing_cap_mwh(y)) + @expression(EP, eExistingCapEnergy_VS[y in STOR], existing_cap_mwh(res[y])) end # 1. Total storage energy capacity @@ -1051,9 +1041,9 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) # Fixed costs for storage resources (if resource is not eligible for new energy capacity, fixed costs are only O&M costs) @expression(EP, eCFixEnergy_VS[y in STOR], if y in NEW_CAP_STOR # Resources eligible for new capacity - inv_cost_per_mwhyr(y)*vCAPENERGY_VS[y] + fixed_om_cost_per_mwhyr(y)*eTotalCap_STOR[y] + inv_cost_per_mwhyr(res[y])*vCAPENERGY_VS[y] + fixed_om_cost_per_mwhyr(res[y])*eTotalCap_STOR[y] else - fixed_om_cost_per_mwhyr(y)*eTotalCap_STOR[y] + fixed_om_cost_per_mwhyr(res[y])*eTotalCap_STOR[y] end ) @expression(EP, eTotalCFixStor, sum(eCFixEnergy_VS[y] for y in STOR)) @@ -1095,9 +1085,9 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) # SoC expressions @expression(EP, eSoCBalStart_VRE_STOR[y in CONSTRAINTSET, t in START_SUBPERIODS], - vS_VRE_STOR[y,t+hours_per_subperiod-1] - self_discharge(y)*vS_VRE_STOR[y,t+hours_per_subperiod-1]) + vS_VRE_STOR[y,t+hours_per_subperiod-1] - self_discharge(res[y])*vS_VRE_STOR[y,t+hours_per_subperiod-1]) @expression(EP, eSoCBalInterior_VRE_STOR[y in STOR, t in INTERIOR_SUBPERIODS], - vS_VRE_STOR[y,t-1] - self_discharge(y)*vS_VRE_STOR[y,t-1]) + vS_VRE_STOR[y,t-1] - self_discharge(res[y])*vS_VRE_STOR[y,t-1]) # Expression for energy losses related to technologies (increase in effective demand) @expression(EP, eELOSS_VRE_STOR[y in STOR], JuMP.AffExpr()) @@ -1184,7 +1174,7 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) # Constraint 0: Existing capacity variable is equal to existing capacity specified in the input file if MultiStage == 1 - @constraint(EP, cExistingCapEnergy_VS[y in STOR], EP[:vEXISTINGCAPENERGY_VS][y] == existing_capacity_mwh(y)) + @constraint(EP, cExistingCapEnergy_VS[y in STOR], EP[:vEXISTINGCAPENERGY_VS][y] == existing_capacity_mwh(res[y])) end # Constraints 1: Retirements and capacity additions @@ -1192,12 +1182,12 @@ function stor_vre_stor!(EP::Model, inputs::Dict, setup::Dict) @constraint(EP, cMaxRet_Stor[y=RET_CAP_STOR], vRETCAPENERGY_VS[y] <= eExistingCapEnergy_VS[y]) # Constraint on maximum capacity (if applicable) [set input to -1 if no constraint on maximum capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MW is >= Max_Cap_MW and lead to infeasabilty - @constraint(EP, cMaxCap_Stor[y in intersect(has_positive_max_capacity_mwh(resources), STOR)], - eTotalCap_STOR[y] <= max_capacity_mwh(y)) + @constraint(EP, cMaxCap_Stor[y in intersect(has_positive_max_capacity_mwh(res), STOR)], + eTotalCap_STOR[y] <= max_capacity_mwh(res[y])) # Constraint on minimum capacity (if applicable) [set input to -1 if no constraint on minimum capacity] # DEV NOTE: This constraint may be violated in some cases where Existing_Cap_MW is <= Min_Cap_MW and lead to infeasabilty - @constraint(EP, cMinCap_Stor[y in intersect(has_positive_min_capacity_mwh(resources), STOR)], - eTotalCap_STOR[y] >= min_capacity_mwh(y)) + @constraint(EP, cMinCap_Stor[y in intersect(has_positive_min_capacity_mwh(res), STOR)], + eTotalCap_STOR[y] >= min_capacity_mwh(res[y])) # Constraint 2: SOC Maximum @constraint(EP, cSOCMax[y in STOR, t=1:T], vS_VRE_STOR[y,t] <= eTotalCap_STOR[y]) @@ -1262,7 +1252,7 @@ function lds_vre_stor!(EP::Model, inputs::Dict) ### LOAD DATA ### VS_LDS = inputs["VS_LDS"] - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"] REP_PERIOD = inputs["REP_PERIOD"] # Number of representative periods @@ -1288,7 +1278,7 @@ function lds_vre_stor!(EP::Model, inputs::Dict) # Note: tw_min = hours_per_subperiod*(w-1)+1; tw_max = hours_per_subperiod*w @expression(EP, eVreStorSoCBalLongDurationStorageStart[y in VS_LDS, w=1:REP_PERIOD], - (1-self_discharge(y)) * (EP[:vS_VRE_STOR][y,hours_per_subperiod*w]-EP[:vdSOC_VRE_STOR][y,w])) + (1-self_discharge(res[y])) * (EP[:vS_VRE_STOR][y,hours_per_subperiod*w]-EP[:vdSOC_VRE_STOR][y,w])) DC_DISCHARGE_CONSTRAINTSET = intersect(inputs["VS_STOR_DC_DISCHARGE"], VS_LDS) DC_CHARGE_CONSTRAINTSET = intersect(inputs["VS_STOR_DC_CHARGE"], VS_LDS) @@ -1885,7 +1875,7 @@ function vre_stor_capres!(EP::Model, inputs::Dict, setup::Dict) ### LOAD DATA ### T = inputs["T"] - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"] STOR = inputs["VS_STOR"] DC_DISCHARGE = inputs["VS_STOR_DC_DISCHARGE"] @@ -1940,10 +1930,10 @@ function vre_stor_capres!(EP::Model, inputs::Dict, setup::Dict) # Virtual State of Charge Expressions @expression(EP, eVreStorVSoCBalStart[y in CONSTRAINTSET, t in START_SUBPERIODS], EP[:vCAPRES_VS_VRE_STOR][y,t+hours_per_subperiod-1] - - self_discharge(y)*EP[:vCAPRES_VS_VRE_STOR][y,t+hours_per_subperiod-1]) + - self_discharge(res[y])*EP[:vCAPRES_VS_VRE_STOR][y,t+hours_per_subperiod-1]) @expression(EP, eVreStorVSoCBalInterior[y in STOR, t in INTERIOR_SUBPERIODS], EP[:vCAPRES_VS_VRE_STOR][y,t-1] - - self_discharge(y)*EP[:vCAPRES_VS_VRE_STOR][y,t-1]) + - self_discharge(res[y])*EP[:vCAPRES_VS_VRE_STOR][y,t-1]) DC_DISCHARGE_CONSTRAINTSET = intersect(CONSTRAINTSET, DC_DISCHARGE) DC_CHARGE_CONSTRAINTSET = intersect(CONSTRAINTSET, DC_CHARGE) @@ -2096,7 +2086,7 @@ function vre_stor_capres!(EP::Model, inputs::Dict, setup::Dict) ### EXPRESSIONS ### @expression(EP, eVreStorVSoCBalLongDurationStorageStart[y in VS_LDS, w=1:REP_PERIOD], - (1-self_discharge(y))*(EP[:vCAPRES_VS_VRE_STOR][y,hours_per_subperiod*w]-vCAPCONTRSTOR_VdSOC_VRE_STOR[y,w])) + (1-self_discharge(res[y]))*(EP[:vCAPRES_VS_VRE_STOR][y,hours_per_subperiod*w]-vCAPCONTRSTOR_VdSOC_VRE_STOR[y,w])) DC_DISCHARGE_CONSTRAINTSET = intersect(DC_DISCHARGE, VS_LDS) DC_CHARGE_CONSTRAINTSET = intersect(DC_CHARGE, VS_LDS) @@ -2228,10 +2218,7 @@ function vre_stor_reserves!(EP::Model, inputs::Dict, setup::Dict) ### LOAD DATA & CREATE SETS ### - resources = inputs["RESOURCES"] - - reg_max(y) = reg_max(resources[y]) - rsv_max(y) = rsv_max(resources[y]) + res = inputs["RESOURCES"] T = inputs["T"] VRE_STOR = inputs["VRE_STOR"] @@ -2469,8 +2456,8 @@ function vre_stor_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(VRE_STOR_REG_RSV) @constraints(EP, begin # Maximum VRE-STOR contribution to reserves is a specified fraction of installed grid connection capacity - [y in VRE_STOR_REG_RSV, t=1:T], EP[:vREG][y,t] <= reg_max(y)*EP[:eTotalCap][y] - [y in VRE_STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] <= rsv_max(y)*EP[:eTotalCap][y] + [y in VRE_STOR_REG_RSV, t=1:T], EP[:vREG][y,t] <= reg_max(res[y])*EP[:eTotalCap][y] + [y in VRE_STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] <= rsv_max(res[y])*EP[:eTotalCap][y] # Actual contribution to regulation and reserves is sum of auxilary variables [y in VRE_STOR_REG_RSV, t=1:T], EP[:vREG][y,t] == eVreStorRegOnlyBalance[y,t] @@ -2480,7 +2467,7 @@ function vre_stor_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(VRE_STOR_REG_ONLY) @constraints(EP, begin # Maximum VRE-STOR contribution to reserves is a specified fraction of installed grid connection capacity - [y in VRE_STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] <= reg_max(y)*EP[:eTotalCap][y] + [y in VRE_STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] <= reg_max(res[y])*EP[:eTotalCap][y] # Actual contribution to regulation is sum of auxilary variables [y in VRE_STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] == eVreStorRegOnlyBalance[y,t] @@ -2489,7 +2476,7 @@ function vre_stor_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(VRE_STOR_RSV_ONLY) @constraints(EP, begin # Maximum VRE-STOR contribution to reserves is a specified fraction of installed grid connection capacity - [y in VRE_STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] <= rsv_max(y)*EP[:eTotalCap][y] + [y in VRE_STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] <= rsv_max(res[y])*EP[:eTotalCap][y] # Actual contribution to reserves is sum of auxilary variables [y in VRE_STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] == eVreStorRsvOnlyBalance[y,t] diff --git a/src/multi_stage/endogenous_retirement.jl b/src/multi_stage/endogenous_retirement.jl index 4e9774eda6..f534d25c8a 100644 --- a/src/multi_stage/endogenous_retirement.jl +++ b/src/multi_stage/endogenous_retirement.jl @@ -122,10 +122,7 @@ function endogenous_retirement_discharge!(EP::Model, inputs::Dict, num_stages::I println("Endogenous Retirement (Discharge) Module") - resources = inputs["RESOURCES"] - cap_size(y) = cap_size(resources[y]) - lifetime(y) = lifetime(resources[y]) - cum_min_retired_cap_mw(y) = cum_min_retired_cap_mw(resources[y]) + res = inputs["RESOURCES"] NEW_CAP = inputs["NEW_CAP"] # Set of all resources eligible for new capacity RET_CAP = inputs["RET_CAP"] # Set of all resources eligible for capacity retirements @@ -151,12 +148,12 @@ function endogenous_retirement_discharge!(EP::Model, inputs::Dict, num_stages::I # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrack[y in RET_CAP], sum(EP[:vRETCAPTRACK][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrack[y in RET_CAP], sum(EP[:vCAPTRACK][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrack[y in RET_CAP], sum(EP[:vCAPTRACK][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrack[y in RET_CAP], if y in COMMIT - cum_min_retired_cap_mw(y)/cap_size(y) + cum_min_retired_cap_mw(res[y])/cap_size(res[y]) else - cum_min_retired_cap_mw(y) + cum_min_retired_cap_mw(res[y]) end ) @@ -180,8 +177,7 @@ function endogenous_retirement_charge!(EP::Model, inputs::Dict, num_stages::Int, println("Endogenous Retirement (Charge) Module") - resources = inputs["RESOURCES"] - cum_min_retired_charge_cap_mw(y) = cum_min_retired_charge_cap_mw(resources[y]) + res = inputs["RESOURCES"] STOR_ASYMMETRIC = inputs["STOR_ASYMMETRIC"] # Set of storage resources with asymmetric (separte) charge/discharge capacity components @@ -208,8 +204,8 @@ function endogenous_retirement_charge!(EP::Model, inputs::Dict, num_stages::Int, # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackCharge[y in RET_CAP_CHARGE], sum(EP[:vRETCAPTRACKCHARGE][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackCharge[y in RET_CAP_CHARGE], sum(EP[:vCAPTRACKCHARGE][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) - @expression(EP, eMinRetCapTrackCharge[y in RET_CAP_CHARGE], cum_min_retired_charge_cap_mw(y)) + @expression(EP, eNewCapTrackCharge[y in RET_CAP_CHARGE], sum(EP[:vCAPTRACKCHARGE][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) + @expression(EP, eMinRetCapTrackCharge[y in RET_CAP_CHARGE], cum_min_retired_charge_cap_mw(res[y])) ### Constratints ### @@ -231,9 +227,7 @@ function endogenous_retirement_energy!(EP::Model, inputs::Dict, num_stages::Int, println("Endogenous Retirement (Energy) Module") - resources = inputs["RESOURCES"] - lifetime(y) = lifetime(resources[y]) - cum_min_retired_energy_cap_mw(y) = cum_min_retired_energy_cap_mw(resources[y]) + res = inputs["RESOURCES"] NEW_CAP_ENERGY = inputs["NEW_CAP_ENERGY"] # Set of all storage resources eligible for new energy capacity RET_CAP_ENERGY = inputs["RET_CAP_ENERGY"] # Set of all storage resources eligible for energy capacity retirements @@ -258,8 +252,8 @@ function endogenous_retirement_energy!(EP::Model, inputs::Dict, num_stages::Int, # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackEnergy[y in RET_CAP_ENERGY], sum(EP[:vRETCAPTRACKENERGY][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackEnergy[y in RET_CAP_ENERGY], sum(EP[:vCAPTRACKENERGY][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) - @expression(EP, eMinRetCapTrackEnergy[y in RET_CAP_ENERGY], cum_min_retired_energy_cap_mw(y)) + @expression(EP, eNewCapTrackEnergy[y in RET_CAP_ENERGY], sum(EP[:vCAPTRACKENERGY][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) + @expression(EP, eMinRetCapTrackEnergy[y in RET_CAP_ENERGY], cum_min_retired_energy_cap_mw(res[y])) ### Constratints ### @@ -280,8 +274,7 @@ function endogenous_retirement_vre_stor_dc!(EP::Model, inputs::Dict, num_stages: println("Endogenous Retirement (VRE-Storage DC) Module") - resources = inputs["RESOURCES"] - lifetime(y) = lifetime(resources[y]) + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"]; @@ -308,7 +301,7 @@ function endogenous_retirement_vre_stor_dc!(EP::Model, inputs::Dict, num_stages: # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackDC[y in RET_CAP_DC], sum(EP[:vRETCAPTRACKDC][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackDC[y in RET_CAP_DC], sum(EP[:vCAPTRACKDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackDC[y in RET_CAP_DC], sum(EP[:vCAPTRACKDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackDC[y in RET_CAP_DC], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Inverter_MW]) ### Constraints ### @@ -330,8 +323,7 @@ function endogenous_retirement_vre_stor_solar!(EP::Model, inputs::Dict, num_stag println("Endogenous Retirement (VRE-Storage Solar) Module") - resources = inputs["RESOURCES"] - lifetime(y) = lifetime(resources[y]) + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"]; @@ -358,7 +350,7 @@ function endogenous_retirement_vre_stor_solar!(EP::Model, inputs::Dict, num_stag # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackSolar[y in RET_CAP_SOLAR], sum(EP[:vRETCAPTRACKSOLAR][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackSolar[y in RET_CAP_SOLAR], sum(EP[:vCAPTRACKSOLAR][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackSolar[y in RET_CAP_SOLAR], sum(EP[:vCAPTRACKSOLAR][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackSolar[y in RET_CAP_SOLAR], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Solar_MW]) ### Constraints ### @@ -380,8 +372,7 @@ function endogenous_retirement_vre_stor_wind!(EP::Model, inputs::Dict, num_stage println("Endogenous Retirement (VRE-Storage Wind) Module") - resources = inputs["RESOURCES"] - lifetime(y) = lifetime(resources[y]) + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"]; @@ -408,7 +399,7 @@ function endogenous_retirement_vre_stor_wind!(EP::Model, inputs::Dict, num_stage # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackWind[y in RET_CAP_WIND], sum(EP[:vRETCAPTRACKWIND][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackWind[y in RET_CAP_WIND], sum(EP[:vCAPTRACKWIND][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackWind[y in RET_CAP_WIND], sum(EP[:vCAPTRACKWIND][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackWind[y in RET_CAP_WIND], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Wind_MW]) ### Constraints ### @@ -430,9 +421,7 @@ function endogenous_retirement_vre_stor_stor!(EP::Model, inputs::Dict, num_stage println("Endogenous Retirement (VRE-Storage Storage) Module") - resources = inputs["RESOURCES"] - lifetime(y) = lifetime(resources[y]) - cum_min_retired_energy_cap_mw(y) = cum_min_retired_energy_cap_mw(resources[y]) + res = inputs["RESOURCES"] NEW_CAP_STOR = inputs["NEW_CAP_STOR"] # Set of all resources eligible for new capacity RET_CAP_STOR = inputs["RET_CAP_STOR"] # Set of all resources eligible for capacity retirements @@ -457,8 +446,8 @@ function endogenous_retirement_vre_stor_stor!(EP::Model, inputs::Dict, num_stage # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackEnergy_VS[y in RET_CAP_STOR], sum(EP[:vRETCAPTRACKENERGY_VS][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackEnergy_VS[y in RET_CAP_STOR], sum(EP[:vCAPTRACKENERGY_VS][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) - @expression(EP, eMinRetCapTrackEnergy_VS[y in RET_CAP_STOR], cum_min_retired_energy_cap_mw(y)) + @expression(EP, eNewCapTrackEnergy_VS[y in RET_CAP_STOR], sum(EP[:vCAPTRACKENERGY_VS][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) + @expression(EP, eMinRetCapTrackEnergy_VS[y in RET_CAP_STOR], cum_min_retired_energy_cap_mw(res[y])) ### Constratints ### @@ -479,7 +468,7 @@ function endogenous_retirement_vre_stor_discharge_dc!(EP::Model, inputs::Dict, n println("Endogenous Retirement (VRE-Storage Discharge DC) Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"] @@ -506,7 +495,7 @@ function endogenous_retirement_vre_stor_discharge_dc!(EP::Model, inputs::Dict, n # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackDischargeDC[y in RET_CAP_DISCHARGE_DC], sum(EP[:vRETCAPTRACKDISCHARGEDC][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackDischargeDC[y in RET_CAP_DISCHARGE_DC], sum(EP[:vCAPTRACKDISCHARGEDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackDischargeDC[y in RET_CAP_DISCHARGE_DC], sum(EP[:vCAPTRACKDISCHARGEDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackDischargeDC[y in RET_CAP_DISCHARGE_DC], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Discharge_DC_MW]) ### Constraints ### @@ -528,7 +517,7 @@ function endogenous_retirement_vre_stor_charge_dc!(EP::Model, inputs::Dict, num_ println("Endogenous Retirement (VRE-Storage Charge DC) Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"]; NEW_CAP_CHARGE_DC = inputs["NEW_CAP_CHARGE_DC"] # Set of all resources eligible for new capacity RET_CAP_CHARGE_DC = inputs["RET_CAP_CHARGE_DC"] # Set of all resources eligible for capacity retirements @@ -553,7 +542,7 @@ function endogenous_retirement_vre_stor_charge_dc!(EP::Model, inputs::Dict, num_ # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackChargeDC[y in RET_CAP_CHARGE_DC], sum(EP[:vRETCAPTRACKCHARGEDC][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackChargeDC[y in RET_CAP_CHARGE_DC], sum(EP[:vCAPTRACKCHARGEDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackChargeDC[y in RET_CAP_CHARGE_DC], sum(EP[:vCAPTRACKCHARGEDC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackChargeDC[y in RET_CAP_CHARGE_DC], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Charge_DC_MW]) ### Constraints ### @@ -575,7 +564,7 @@ function endogenous_retirement_vre_stor_discharge_ac!(EP::Model, inputs::Dict, n println("Endogenous Retirement (VRE-Storage Discharge AC) Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"]; NEW_CAP_DISCHARGE_AC = inputs["NEW_CAP_DISCHARGE_AC"] # Set of all resources eligible for new capacity RET_CAP_DISCHARGE_AC = inputs["RET_CAP_DISCHARGE_AC"] # Set of all resources eligible for capacity retirements @@ -600,7 +589,7 @@ function endogenous_retirement_vre_stor_discharge_ac!(EP::Model, inputs::Dict, n # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackDischargeAC[y in RET_CAP_DISCHARGE_AC], sum(EP[:vRETCAPTRACKDISCHARGEAC][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackDischargeAC[y in RET_CAP_DISCHARGE_AC], sum(EP[:vCAPTRACKDISCHARGEAC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackDischargeAC[y in RET_CAP_DISCHARGE_AC], sum(EP[:vCAPTRACKDISCHARGEAC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackDischargeAC[y in RET_CAP_DISCHARGE_AC], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Discharge_AC_MW]) ### Constraints ### @@ -622,7 +611,7 @@ function endogenous_retirement_vre_stor_charge_ac!(EP::Model, inputs::Dict, num_ println("Endogenous Retirement (VRE-Storage Charge AC) Module") - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"] NEW_CAP_CHARGE_AC = inputs["NEW_CAP_CHARGE_AC"] # Set of all resources eligible for new capacity RET_CAP_CHARGE_AC = inputs["RET_CAP_CHARGE_AC"] # Set of all resources eligible for capacity retirements @@ -647,7 +636,7 @@ function endogenous_retirement_vre_stor_charge_ac!(EP::Model, inputs::Dict, num_ # Construct and add the endogenous retirement constraint expressions @expression(EP, eRetCapTrackChargeAC[y in RET_CAP_CHARGE_AC], sum(EP[:vRETCAPTRACKCHARGEAC][y,p] for p=1:cur_stage)) - @expression(EP, eNewCapTrackChargeAC[y in RET_CAP_CHARGE_AC], sum(EP[:vCAPTRACKCHARGEAC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(y), stage_lens))) + @expression(EP, eNewCapTrackChargeAC[y in RET_CAP_CHARGE_AC], sum(EP[:vCAPTRACKCHARGEAC][y,p] for p=1:get_retirement_stage(cur_stage, lifetime(res[y]), stage_lens))) @expression(EP, eMinRetCapTrackChargeAC[y in RET_CAP_CHARGE_AC], dfVRE_STOR[y,:Cum_Min_Retired_Cap_Charge_AC_MW]) ### Constraints ### diff --git a/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl b/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl index cd36a728fd..3ede4d9020 100644 --- a/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl +++ b/src/write_outputs/capacity_reserve_margin/write_capacity_value.jl @@ -1,5 +1,7 @@ function write_capacity_value(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) THERM_ALL = inputs["THERM_ALL"] @@ -93,7 +95,7 @@ function write_capacity_value(path::AbstractString, inputs::Dict, setup::Dict, E capvalue[riskyhour, AC_CHARGE_EX] -= crm_derate(i, AC_CHARGE_EX) .* capres_ac_charge ./ total_cap(AC_CHARGE_EX) end capvalue = collect(transpose(capvalue)) - temp_dfCapValue = DataFrame(Resource = inputs["RESOURCES"], Zone = dfGen.Zone, Reserve = fill(Symbol("CapRes_$i"), G)) + temp_dfCapValue = DataFrame(Resource = inputs["RESOURCES"], Zone = zones, Reserve = fill(Symbol("CapRes_$i"), G)) temp_dfCapValue = hcat(temp_dfCapValue, DataFrame(capvalue, :auto)) auxNew_Names = [Symbol("Resource"); Symbol("Zone"); Symbol("Reserve"); [Symbol("t$t") for t in 1:T]] rename!(temp_dfCapValue, auxNew_Names) diff --git a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl index 9833bafbe8..4cf4f285d9 100644 --- a/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl +++ b/src/write_outputs/capacity_reserve_margin/write_reserve_margin_revenue.jl @@ -10,11 +10,12 @@ Function for reporting the capacity revenue earned by each generator listed in t """ function write_reserve_margin_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 - resources = inputs["RESOURCES"] + + res = inputs["RESOURCES"] + regions = region.(res) + clusters = cluster.(res) + zones = zone_id.(res) - regions = region.(resources) - clusters = cluster.(resources) - zones = zone_id.(resources) G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) diff --git a/src/write_outputs/capacity_reserve_margin/write_virtual_discharge.jl b/src/write_outputs/capacity_reserve_margin/write_virtual_discharge.jl index 7cdf3789ca..61cf2cdb12 100644 --- a/src/write_outputs/capacity_reserve_margin/write_virtual_discharge.jl +++ b/src/write_outputs/capacity_reserve_margin/write_virtual_discharge.jl @@ -5,12 +5,14 @@ Function for writing the "virtual" discharge of each storage technology. Virtual allow storage resources to contribute to the capacity reserve margin without actually discharging. """ function write_virtual_discharge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) STOR_ALL = inputs["STOR_ALL"] - dfVirtualDischarge = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), AnnualSum = Array{Union{Missing,Float64}}(undef, G)) + dfVirtualDischarge = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones, AnnualSum = Array{Union{Missing,Float64}}(undef, G)) virtual_discharge = zeros(G,T) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 diff --git a/src/write_outputs/energy_share_requirement/write_esr_revenue.jl b/src/write_outputs/energy_share_requirement/write_esr_revenue.jl index c438556521..754cd2a730 100644 --- a/src/write_outputs/energy_share_requirement/write_esr_revenue.jl +++ b/src/write_outputs/energy_share_requirement/write_esr_revenue.jl @@ -4,8 +4,13 @@ Function for reporting the renewable/clean credit revenue earned by each generator listed in the input file. GenX will print this file only when RPS/CES 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 earned from each RPS constraint. The revenue is calculated as the total annual generation (if elgible for the corresponding constraint) multiplied by the RPS/CES price. The last column is the total revenue received from all constraint. The unit is \$. """ function write_esr_revenue(path::AbstractString, inputs::Dict, setup::Dict, dfPower::DataFrame, dfESR::DataFrame, EP::Model) - resources = inputs["RESOURCES"] - dfESRRev = DataFrame(region = dfGen[!,:region], Resource = inputs["RESOURCE_NAMES"], zone = zone_id.(resources), Cluster = dfGen[!,:cluster], R_ID = dfGen[!,:R_ID]) + res = inputs["RESOURCES"] + regions = region.(res) + clusters = cluster.(res) + zones = zone_id.(res) + rid = resource_id.(res) + + dfESRRev = DataFrame(region = regions, Resource = inputs["RESOURCE_NAMES"], zone = zones, Cluster = clusters, R_ID = rid) G = inputs["G"] nESR = inputs["nESR"] weight = inputs["omega"] diff --git a/src/write_outputs/long_duration_storage/write_opwrap_lds_dstor.jl b/src/write_outputs/long_duration_storage/write_opwrap_lds_dstor.jl index 83f804da0b..cf8d275456 100644 --- a/src/write_outputs/long_duration_storage/write_opwrap_lds_dstor.jl +++ b/src/write_outputs/long_duration_storage/write_opwrap_lds_dstor.jl @@ -1,11 +1,13 @@ function write_opwrap_lds_dstor(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) ## Extract data frames from input dictionary - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + W = inputs["REP_PERIOD"] # Number of subperiods G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) #Excess inventory of storage period built up during representative period w - dfdStorage = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfdStorage = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) dsoc = zeros(G,W) for i in 1:G if i in inputs["STOR_LONG_DURATION"] diff --git a/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl b/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl index 17b88f09d7..2750affdd3 100644 --- a/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl +++ b/src/write_outputs/long_duration_storage/write_opwrap_lds_stor_init.jl @@ -1,11 +1,13 @@ function write_opwrap_lds_stor_init(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) ## Extract data frames from input dictionary - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Initial level of storage in each modeled period NPeriods = size(inputs["Period_Map"])[1] - dfStorageInit = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfStorageInit = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) socw = zeros(G,NPeriods) for i in 1:G if i in inputs["STOR_LONG_DURATION"] diff --git a/src/write_outputs/reserves/write_reg.jl b/src/write_outputs/reserves/write_reg.jl index ac79c615b7..05003456d3 100644 --- a/src/write_outputs/reserves/write_reg.jl +++ b/src/write_outputs/reserves/write_reg.jl @@ -1,12 +1,13 @@ function write_reg(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) REG = inputs["REG"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 # Regulation contributions for each resource in each time step - dfReg = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfReg = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) reg = zeros(G,T) reg[REG, :] = value.(EP[:vREG][REG, :]) dfReg.AnnualSum = (reg*scale_factor) * inputs["omega"] diff --git a/src/write_outputs/reserves/write_rsv.jl b/src/write_outputs/reserves/write_rsv.jl index 67013e3a82..7db7c48dc5 100644 --- a/src/write_outputs/reserves/write_rsv.jl +++ b/src/write_outputs/reserves/write_rsv.jl @@ -1,11 +1,12 @@ function write_rsv(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) RSV = inputs["RSV"] scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 - dfRsv = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfRsv = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) rsv = zeros(G,T) unmet_vec = zeros(T) rsv[RSV, :] = value.(EP[:vRSV][RSV, :]) * scale_factor diff --git a/src/write_outputs/ucommit/write_commit.jl b/src/write_outputs/ucommit/write_commit.jl index b87303a01c..6c48aaaa60 100644 --- a/src/write_outputs/ucommit/write_commit.jl +++ b/src/write_outputs/ucommit/write_commit.jl @@ -1,12 +1,14 @@ function write_commit(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) COMMIT = inputs["COMMIT"] # Commitment state for each resource in each time step commit = zeros(G,T) commit[COMMIT, :] = value.(EP[:vCOMMIT][COMMIT, :]) - dfCommit = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfCommit = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) dfCommit = hcat(dfCommit, DataFrame(commit, :auto)) auxNew_Names=[Symbol("Resource");Symbol("Zone");[Symbol("t$t") for t in 1:T]] rename!(dfCommit,auxNew_Names) diff --git a/src/write_outputs/ucommit/write_shutdown.jl b/src/write_outputs/ucommit/write_shutdown.jl index 076598ae0a..313fa13992 100644 --- a/src/write_outputs/ucommit/write_shutdown.jl +++ b/src/write_outputs/ucommit/write_shutdown.jl @@ -1,11 +1,13 @@ function write_shutdown(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) # Operational decision variable states COMMIT = inputs["COMMIT"] # Shutdown state for each resource in each time step - dfShutdown = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfShutdown = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) shut = zeros(G,T) shut[COMMIT, :] = value.(EP[:vSHUT][COMMIT, :]) dfShutdown.AnnualSum = shut * inputs["omega"] diff --git a/src/write_outputs/ucommit/write_start.jl b/src/write_outputs/ucommit/write_start.jl index 4f74e2e76b..374049f028 100644 --- a/src/write_outputs/ucommit/write_start.jl +++ b/src/write_outputs/ucommit/write_start.jl @@ -1,10 +1,12 @@ function write_start(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) COMMIT = inputs["COMMIT"] # Startup state for each resource in each time step - dfStart = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfStart = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) start = zeros(G,T) start[COMMIT, :] = value.(EP[:vSTART][COMMIT, :]) dfStart.AnnualSum = start * inputs["omega"] diff --git a/src/write_outputs/write_capacity.jl b/src/write_outputs/write_capacity.jl index 523ddc43d8..5a6aa05138 100644 --- a/src/write_outputs/write_capacity.jl +++ b/src/write_outputs/write_capacity.jl @@ -5,11 +5,7 @@ Function for writing the diferent capacities for the different generation techno """ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] - cap_size(y) = cap_size(resources[y]) - existing_capacity_mw(y) = existing_capacity_mw(resources[y]) - existing_capacity_mwh(y) = existing_capacity_mwh(resources[y]) - existing_charge_capacity_mw(y) = existing_charge_capacity_mw(resources[y]) + res = inputs["RESOURCES"] MultiStage = setup["MultiStage"] @@ -17,7 +13,7 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod capdischarge = zeros(size(inputs["RESOURCE_NAMES"])) for i in inputs["NEW_CAP"] if i in inputs["COMMIT"] - capdischarge[i] = value(EP[:vCAP][i])*cap_size(i) + capdischarge[i] = value(EP[:vCAP][i])*cap_size(res[i]) else capdischarge[i] = value(EP[:vCAP][i]) end @@ -26,14 +22,14 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod retcapdischarge = zeros(size(inputs["RESOURCE_NAMES"])) for i in inputs["RET_CAP"] if i in inputs["COMMIT"] - retcapdischarge[i] = first(value.(EP[:vRETCAP][i]))*cap_size(i) + retcapdischarge[i] = first(value.(EP[:vRETCAP][i]))*cap_size(res[i]) else retcapdischarge[i] = first(value.(EP[:vRETCAP][i])) end end capacity_constraint_dual = zeros(size(inputs["RESOURCE_NAMES"])) - for y in has_positive_max_capacity_mw(resources) + for y in has_positive_max_capacity_mw(res) capacity_constraint_dual[y] = -dual.(EP[:cMaxCap][y]) end @@ -47,7 +43,7 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod if i in inputs["RET_CAP_CHARGE"] retcapcharge[i] = value(EP[:vRETCAPCHARGE][i]) end - existingcapcharge[i] = MultiStage == 1 ? value(EP[:vEXISTINGCAPCHARGE][i]) : existing_charge_capacity_mw(i) + existingcapcharge[i] = MultiStage == 1 ? value(EP[:vEXISTINGCAPCHARGE][i]) : existing_charge_capacity_mw(res[i]) end capenergy = zeros(size(inputs["RESOURCE_NAMES"])) @@ -60,7 +56,7 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod if i in inputs["RET_CAP_ENERGY"] retcapenergy[i] = value(EP[:vRETCAPENERGY][i]) end - existingcapenergy[i] = MultiStage == 1 ? value(EP[:vEXISTINGCAPENERGY][i]) : existing_capacity_mwh(y) + existingcapenergy[i] = MultiStage == 1 ? value(EP[:vEXISTINGCAPENERGY][i]) : existing_capacity_mwh(res[y]) end if !isempty(inputs["VRE_STOR"]) for i in inputs["VS_STOR"] @@ -70,13 +66,13 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod if i in inputs["RET_CAP_STOR"] retcapenergy[i] = value(EP[:vRETCAPENERGY_VS][i]) end - existingcapenergy[i] = existing_capacity_mwh(y) # multistage functionality doesn't exist yet for VRE-storage resources + existingcapenergy[i] = existing_capacity_mwh(res[y]) # multistage functionality doesn't exist yet for VRE-storage resources end end dfCap = DataFrame( - Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), - StartCap = MultiStage == 1 ? value.(EP[:vEXISTINGCAP]) : existing_capacity_mw(y), + Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(res), + StartCap = MultiStage == 1 ? value.(EP[:vEXISTINGCAP]) : existing_capacity_mw(res[y]), RetCap = retcapdischarge[:], NewCap = capdischarge[:], EndCap = value.(EP[:eTotalCap]), diff --git a/src/write_outputs/write_capacityfactor.jl b/src/write_outputs/write_capacityfactor.jl index 7a0ebf07b3..2774a3570b 100644 --- a/src/write_outputs/write_capacityfactor.jl +++ b/src/write_outputs/write_capacityfactor.jl @@ -5,7 +5,7 @@ Function for writing the capacity factor of different resources. For co-located value is calculated if the site has either or both a solar PV or wind resource. """ function write_capacityfactor(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) THERM_ALL = inputs["THERM_ALL"] @@ -15,7 +15,7 @@ function write_capacityfactor(path::AbstractString, inputs::Dict, setup::Dict, E ELECTROLYZER = inputs["ELECTROLYZER"] VRE_STOR = inputs["VRE_STOR"] - dfCapacityfactor = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(resources), AnnualSum=zeros(G), Capacity=zeros(G), CapacityFactor=zeros(G)) + dfCapacityfactor = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(res), AnnualSum=zeros(G), Capacity=zeros(G), CapacityFactor=zeros(G)) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 dfCapacityfactor.AnnualSum .= value.(EP[:vP]) * inputs["omega"] * scale_factor dfCapacityfactor.Capacity .= value.(EP[:eTotalCap]) * scale_factor diff --git a/src/write_outputs/write_charge.jl b/src/write_outputs/write_charge.jl index fd8861ec75..9ec37603a6 100644 --- a/src/write_outputs/write_charge.jl +++ b/src/write_outputs/write_charge.jl @@ -4,7 +4,9 @@ Function for writing the charging energy values of the different storage technologies. """ function write_charge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) STOR_ALL = inputs["STOR_ALL"] @@ -14,7 +16,7 @@ function write_charge(path::AbstractString, inputs::Dict, setup::Dict, EP::Model VS_STOR = !isempty(VRE_STOR) ? inputs["VS_STOR"] : [] # Power withdrawn to charge each resource in each time step - dfCharge = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), AnnualSum = Array{Union{Missing,Float64}}(undef, G)) + dfCharge = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones, AnnualSum = Array{Union{Missing,Float64}}(undef, G)) charge = zeros(G,T) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 diff --git a/src/write_outputs/write_charging_cost.jl b/src/write_outputs/write_charging_cost.jl index 540be9c03f..aa837f6559 100644 --- a/src/write_outputs/write_charging_cost.jl +++ b/src/write_outputs/write_charging_cost.jl @@ -1,9 +1,9 @@ function write_charging_cost(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] - regions = region.(resources) - clusters = cluster.(resources) - zones = zone_id.(resources) + regions = region.(res) + clusters = cluster.(res) + zones = zone_id.(res) G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) @@ -18,16 +18,16 @@ function write_charging_cost(path::AbstractString, inputs::Dict, setup::Dict, EP dfChargingcost = DataFrame(Region = regions, Resource = inputs["RESOURCE_NAMES"], Zone = zones, Cluster = clusters, AnnualSum = Array{Float64}(undef, G),) chargecost = zeros(G, T) if !isempty(STOR_ALL) - chargecost[STOR_ALL, :] .= (value.(EP[:vCHARGE][STOR_ALL, :]).data) .* transpose(price)[zone_id.(resources.STOR), :] + chargecost[STOR_ALL, :] .= (value.(EP[:vCHARGE][STOR_ALL, :]).data) .* transpose(price)[zone_id.(res.STOR), :] end if !isempty(FLEX) - chargecost[FLEX, :] .= value.(EP[:vP][FLEX, :]) .* transpose(price)[zone_id.(resources.FLEX), :] + chargecost[FLEX, :] .= value.(EP[:vP][FLEX, :]) .* transpose(price)[zone_id.(res.FLEX), :] end if !isempty(ELECTROLYZER) - chargecost[ELECTROLYZER, :] .= (value.(EP[:vUSE][ELECTROLYZER, :]).data) .* transpose(price)[zone_id.(resources.ELECTROLYZER), :] + chargecost[ELECTROLYZER, :] .= (value.(EP[:vUSE][ELECTROLYZER, :]).data) .* transpose(price)[zone_id.(res.ELECTROLYZER), :] end if !isempty(VS_STOR) - chargecost[VS_STOR, :] .= value.(EP[:vCHARGE_VRE_STOR][VS_STOR, :].data) .* transpose(price)[zone_id.(resources[VS_STOR]), :] + chargecost[VS_STOR, :] .= value.(EP[:vCHARGE_VRE_STOR][VS_STOR, :].data) .* transpose(price)[zone_id.(res[VS_STOR]), :] end if setup["ParameterScale"] == 1 chargecost *= ModelScalingFactor diff --git a/src/write_outputs/write_co2.jl b/src/write_outputs/write_co2.jl index 1d6357b6b0..09bb7ca889 100644 --- a/src/write_outputs/write_co2.jl +++ b/src/write_outputs/write_co2.jl @@ -11,13 +11,13 @@ end function write_co2_emissions_plant(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones # CO2 emissions by plant - dfEmissions_plant = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(resources), AnnualSum=zeros(G)) + dfEmissions_plant = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(res), AnnualSum=zeros(G)) emissions_plant = value.(EP[:eEmissionsByPlant]) if setup["ParameterScale"] == 1 emissions_plant *= ModelScalingFactor @@ -35,13 +35,13 @@ function write_co2_emissions_plant(path::AbstractString, inputs::Dict, setup::Di end function write_co2_capture_plant(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones - dfCapturedEmissions_plant = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(resources), AnnualSum=zeros(G)) - if any(dfGen.CO2_Capture_Fraction .!= 0) + dfCapturedEmissions_plant = DataFrame(Resource=inputs["RESOURCE_NAMES"], Zone=zone_id.(res), AnnualSum=zeros(G)) + if any(co2_capture_fraction.(res) .!= 0) # Captured CO2 emissions by plant emissions_captured_plant = zeros(G, T) emissions_captured_plant = (value.(EP[:eEmissionsCaptureByPlant])) diff --git a/src/write_outputs/write_costs.jl b/src/write_outputs/write_costs.jl index e990db4adc..75d31083d6 100644 --- a/src/write_outputs/write_costs.jl +++ b/src/write_outputs/write_costs.jl @@ -5,7 +5,7 @@ Function for writing the costs pertaining to the objective function (fixed, vari """ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) ## Cost results - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] SEG = inputs["SEG"] # Number of lines Z = inputs["Z"] # Number of zones T = inputs["T"] # Number of time steps (hours) @@ -80,7 +80,7 @@ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) dfCost[!,2][11] = value(EP[:eTotalCGrid]) * (setup["ParameterScale"] == 1 ? ModelScalingFactor^2 : 1) end - if any(dfGen.CO2_Capture_Fraction .!= 0) + if any(co2_capture_fraction.(res) .!= 0) dfCost[10,2] += value(EP[:eTotaleCCO2Sequestration]) end @@ -102,7 +102,7 @@ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) tempHydrogenValue = 0.0 tempCCO2 = 0.0 - Y_ZONE = resources_in_zone_by_rid(resources,z) + Y_ZONE = resources_in_zone_by_rid(res,z) STOR_ALL_ZONE = intersect(inputs["STOR_ALL"], Y_ZONE) STOR_ASYMMETRIC_ZONE = intersect(inputs["STOR_ASYMMETRIC"], Y_ZONE) FLEX_ZONE = intersect(inputs["FLEX"], Y_ZONE) @@ -218,7 +218,7 @@ function write_costs(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) tempCNSE = sum(value.(EP[:eCNSE][:,:,z])) tempCTotal += tempCNSE - if any(dfGen.CO2_Capture_Fraction .!=0) + if any(co2_capture_fraction.(res) .!=0) tempCCO2 = sum(value.(EP[:ePlantCCO2Sequestration][Y_ZONE,:])) tempCTotal += tempCCO2 end diff --git a/src/write_outputs/write_curtailment.jl b/src/write_outputs/write_curtailment.jl index e6eabcc195..1b1033ab2c 100644 --- a/src/write_outputs/write_curtailment.jl +++ b/src/write_outputs/write_curtailment.jl @@ -5,11 +5,11 @@ Function for writing the curtailment values of the different variable renewable co-located). """ function write_curtailment(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) VRE = inputs["VRE"] - dfCurtailment = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), AnnualSum = zeros(G)) + dfCurtailment = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(res), AnnualSum = zeros(G)) curtailment = zeros(G, T) scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 curtailment[VRE, :] = scale_factor * (value.(EP[:eTotalCap][VRE]) .* inputs["pP_Max"][VRE, :] .- value.(EP[:vP][VRE, :])) diff --git a/src/write_outputs/write_emissions.jl b/src/write_outputs/write_emissions.jl index fd59a1f793..345205ca6a 100644 --- a/src/write_outputs/write_emissions.jl +++ b/src/write_outputs/write_emissions.jl @@ -5,8 +5,8 @@ Function for reporting time-dependent CO$_2$ emissions by zone. """ function write_emissions(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] - G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) + res = inputs["RESOURCES"] + T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones diff --git a/src/write_outputs/write_energy_revenue.jl b/src/write_outputs/write_energy_revenue.jl index 78d80825d5..efbe1b003c 100644 --- a/src/write_outputs/write_energy_revenue.jl +++ b/src/write_outputs/write_energy_revenue.jl @@ -4,17 +4,21 @@ Function for writing energy revenue from the different generation technologies. """ function write_energy_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + regions = region.(res) + clusters = cluster.(res) + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) FLEX = inputs["FLEX"] NONFLEX = setdiff(collect(1:G), FLEX) - dfEnergyRevenue = DataFrame(Region = dfGen.region, Resource = inputs["RESOURCE_NAMES"], Zone = dfGen.Zone, Cluster = dfGen.cluster, AnnualSum = Array{Float64}(undef, G),) + dfEnergyRevenue = DataFrame(Region = regions, Resource = inputs["RESOURCE_NAMES"], Zone = zones, Cluster = clusters, 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], :] + energyrevenue[NONFLEX, :] = value.(EP[:vP][NONFLEX, :]) .* transpose(price)[zone_id.(res[NONFLEX]), :] if !isempty(FLEX) - energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[dfGen[FLEX, :Zone], :] + energyrevenue[FLEX, :] = value.(EP[:vCHARGE_FLEX][FLEX, :]).data .* transpose(price)[zone_id.(res[FLEX]), :] end if setup["ParameterScale"] == 1 energyrevenue *= ModelScalingFactor diff --git a/src/write_outputs/write_fuel_consumption.jl b/src/write_outputs/write_fuel_consumption.jl index aa52604589..d916261c37 100644 --- a/src/write_outputs/write_fuel_consumption.jl +++ b/src/write_outputs/write_fuel_consumption.jl @@ -11,13 +11,13 @@ function write_fuel_consumption(path::AbstractString, inputs::Dict, setup::Dict, end function write_fuel_consumption_plant(path::AbstractString,inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] - G = inputs["G"] + res = inputs["RESOURCES"] + HAS_FUEL = inputs["HAS_FUEL"] # Fuel consumption cost by each resource, including start up fuel dfPlantFuel = DataFrame(Resource = inputs["RESOURCE_NAMES"][HAS_FUEL], - Fuel = dfGen[HAS_FUEL, :Fuel], - Zone = dfGen[HAS_FUEL,:Zone], + Fuel = fuel.(res[HAS_FUEL]), + Zone = zone_id.(res[HAS_FUEL]), AnnualSum = zeros(length(HAS_FUEL))) tempannualsum = value.(EP[:ePlantCFuelOut][HAS_FUEL]) + value.(EP[:ePlantCFuelStart][HAS_FUEL]) diff --git a/src/write_outputs/write_net_revenue.jl b/src/write_outputs/write_net_revenue.jl index 4dc5fc2b4e..93fae60ee4 100644 --- a/src/write_outputs/write_net_revenue.jl +++ b/src/write_outputs/write_net_revenue.jl @@ -4,9 +4,12 @@ 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) - resources = inputs["RESOURCES"] - T = inputs["T"] # Number of time steps (hours) - Z = inputs["Z"] # Number of zones + res = inputs["RESOURCES"] + zones = zone_id.(res) + regions = region.(res) + clusters = cluster.(res) + rid = resource_id.(res) + G = inputs["G"] # Number of generators COMMIT = inputs["COMMIT"] # Thermal units for unit commitment STOR_ALL = inputs["STOR_ALL"] @@ -25,11 +28,11 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: end # Create a NetRevenue dataframe - dfNetRevenue = DataFrame(region = dfGen[!,:region], Resource = inputs["RESOURCE_NAMES"], zone = zone_id.(resources), Cluster = dfGen[!,:cluster], R_ID = dfGen[!,:R_ID]) + dfNetRevenue = DataFrame(region = regions, Resource = inputs["RESOURCE_NAMES"], zone = zones, Cluster = clusters, R_ID = rid) # Add investment cost to the dataframe - dfNetRevenue.Inv_cost_MW = dfGen[!,:Inv_Cost_per_MWyr] .* dfCap[1:G,:NewCap] - dfNetRevenue.Inv_cost_MWh = dfGen[!,:Inv_Cost_per_MWhyr] .* dfCap[1:G,:NewEnergyCap] + dfNetRevenue.Inv_cost_MW = inv_cost_per_mwyr.(res) .* dfCap[1:G,:NewCap] + dfNetRevenue.Inv_cost_MWh = inv_cost_per_mwhyr.(res) .* dfCap[1:G,:NewEnergyCap] if !isempty(VRE_STOR) # Doesn't include charge capacities if !isempty(SOLAR) @@ -48,9 +51,9 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: end # 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.Var_OM_cost_out = (dfGen[!,:Var_OM_Cost_per_MWh]) .* dfPower[1:G,:AnnualSum] + dfNetRevenue.Fixed_OM_cost_MW = fixed_om_cost_per_mwyr.(res) .* dfCap[1:G,:EndCap] + dfNetRevenue.Fixed_OM_cost_MWh = fixed_om_cost_per_mwhyr.(res) .* dfCap[1:G,:EndEnergyCap] + dfNetRevenue.Var_OM_cost_out = var_om_cost_per_mwh.(res) .* dfPower[1:G,:AnnualSum] if !isempty(VRE_STOR) if !isempty(SOLAR) dfNetRevenue.Fixed_OM_cost_MW[VRE_STOR] += dfVRE_STOR[!,:Fixed_OM_Solar_Cost_per_MWyr] .* dfVreStor[1:VRE_STOR_LENGTH, :EndCapSolar] @@ -85,7 +88,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: # Add storage cost to the dataframe dfNetRevenue.Var_OM_cost_in = zeros(nrow(dfNetRevenue)) if !isempty(STOR_ALL) - dfNetRevenue.Var_OM_cost_in[STOR_ALL] = dfGen[STOR_ALL,:Var_OM_Cost_per_MWh_In] .* ((value.(EP[:vCHARGE][STOR_ALL,:]).data) * inputs["omega"]) + dfNetRevenue.Var_OM_cost_in[STOR_ALL] = var_om_cost_per_mwh_in.(resource.STOR) .* ((value.(EP[:vCHARGE][STOR_ALL,:]).data) * inputs["omega"]) end if !isempty(VRE_STOR) if !isempty(DC_CHARGE) @@ -141,7 +144,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: for cap in 1:inputs["NCO2Cap"] co2_cap_dual = dual(EP[:cCO2Emissions_systemwide][cap]) CO2ZONES = findall(x->x==1, inputs["dfCO2CapZones"][:,cap]) - GEN_IN_ZONE = dfGen[[y in CO2ZONES for y in dfGen[:, :Zone]], :R_ID] + GEN_IN_ZONE = resource_id.(res[y in CO2ZONES for y in zone_id.(res)]) if setup["CO2Cap"]==1 # Mass-based # Cost = sum(sum(emissions of gen y * dual(CO2 constraint[cap]) for z in Z) for cap in setup["NCO2"]) temp_vec = value.(EP[:eEmissionsByPlant][GEN_IN_ZONE, :]) * inputs["omega"] @@ -153,7 +156,7 @@ function write_net_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP:: elseif setup["CO2Cap"]==3 # Generation + Rate-based SET_WITH_MAXCO2RATE = union(inputs["THERM_ALL"],inputs["VRE"], inputs["VRE"],inputs["MUST_RUN"],inputs["HYDRO_RES"]) Y = intersect(GEN_IN_ZONE, SET_WITH_MAXCO2RATE) - temp_vec = (value.(EP[:eEmissionsByPlant][Y,:]) - (value.(EP[:vP][Y,:]) .* inputs["dfMaxCO2Rate"][dfGen[Y, :Zone], cap])) * inputs["omega"] + temp_vec = (value.(EP[:eEmissionsByPlant][Y,:]) - (value.(EP[:vP][Y,:]) .* inputs["dfMaxCO2Rate"][zone_id.(res[Y]), cap])) * inputs["omega"] dfNetRevenue.EmissionsCost[Y] += - co2_cap_dual * temp_vec end end diff --git a/src/write_outputs/write_nse.jl b/src/write_outputs/write_nse.jl index 28d94c8a7c..d318f1c20f 100644 --- a/src/write_outputs/write_nse.jl +++ b/src/write_outputs/write_nse.jl @@ -4,7 +4,7 @@ Function for reporting non-served energy for every model zone, time step and cost-segment. """ function write_nse(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones SEG = inputs["SEG"] # Number of demand curtailment segments diff --git a/src/write_outputs/write_power.jl b/src/write_outputs/write_power.jl index 30124eb1f1..5e5f82e4ab 100644 --- a/src/write_outputs/write_power.jl +++ b/src/write_outputs/write_power.jl @@ -4,12 +4,14 @@ Function for writing the different values of power generated by the different technologies in operation. """ function write_power(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) # Power injected by each resource in each time step - dfPower = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), AnnualSum = Array{Union{Missing,Float64}}(undef, G)) + dfPower = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones, AnnualSum = Array{Union{Missing,Float64}}(undef, G)) power = value.(EP[:vP]) if setup["ParameterScale"] == 1 power *= ModelScalingFactor diff --git a/src/write_outputs/write_power_balance.jl b/src/write_outputs/write_power_balance.jl index 4fb73cd6b3..a846b2d1e2 100644 --- a/src/write_outputs/write_power_balance.jl +++ b/src/write_outputs/write_power_balance.jl @@ -1,5 +1,5 @@ function write_power_balance(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] T = inputs["T"] # Number of time steps (hours) Z = inputs["Z"] # Number of zones SEG = inputs["SEG"] # Number of demand curtailment segments @@ -23,20 +23,20 @@ function write_power_balance(path::AbstractString, inputs::Dict, setup::Dict, EP dfPowerBalance = DataFrame(BalanceComponent = repeat(Com_list, outer = Z), Zone = repeat(1:Z, inner = L), AnnualSum = zeros(L * Z)) powerbalance = zeros(Z * L, T) # following the same style of power/charge/storage/nse for z in 1:Z - POWER_ZONE = intersect(resources_in_zone_by_rid(resources,z), union(THERM_ALL, VRE, MUST_RUN, HYDRO_RES)) + POWER_ZONE = intersect(resources_in_zone_by_rid(res,z), union(THERM_ALL, VRE, MUST_RUN, HYDRO_RES)) powerbalance[(z-1)*L+1, :] = sum(value.(EP[:vP][POWER_ZONE, :]), dims = 1) - if !isempty(intersect(resources_in_zone_by_rid(resources,z), STOR_ALL)) - STOR_ALL_ZONE = intersect(resources_in_zone_by_rid(resources,z), STOR_ALL) + if !isempty(intersect(resources_in_zone_by_rid(res,z), STOR_ALL)) + STOR_ALL_ZONE = intersect(resources_in_zone_by_rid(res,z), STOR_ALL) powerbalance[(z-1)*L+2, :] = sum(value.(EP[:vP][STOR_ALL_ZONE, :]), dims = 1) powerbalance[(z-1)*L+3, :] = (-1) * sum((value.(EP[:vCHARGE][STOR_ALL_ZONE, :]).data), dims = 1) end - if !isempty(intersect(resources_in_zone_by_rid(resources,z), VRE_STOR)) - VS_ALL_ZONE = intersect(resources_in_zone_by_rid(resources,z), inputs["VS_STOR"]) + if !isempty(intersect(resources_in_zone_by_rid(res,z), VRE_STOR)) + VS_ALL_ZONE = intersect(resources_in_zone_by_rid(res,z), inputs["VS_STOR"]) powerbalance[(z-1)*L+2, :] = sum(value.(EP[:vP][VS_ALL_ZONE, :]), dims = 1) powerbalance[(z-1)*L+3, :] = (-1) * sum(value.(EP[:vCHARGE_VRE_STOR][VS_ALL_ZONE, :]).data, dims=1) end - if !isempty(intersect(resources_in_zone_by_rid(resources,z), FLEX)) - FLEX_ZONE = intersect(resources_in_zone_by_rid(resources,z), FLEX) + if !isempty(intersect(resources_in_zone_by_rid(res,z), FLEX)) + FLEX_ZONE = intersect(resources_in_zone_by_rid(res,z), FLEX) powerbalance[(z-1)*L+4, :] = sum((value.(EP[:vCHARGE_FLEX][FLEX_ZONE, :]).data), dims = 1) powerbalance[(z-1)*L+5, :] = (-1) * sum(value.(EP[:vP][FLEX_ZONE, :]), dims = 1) end diff --git a/src/write_outputs/write_storage.jl b/src/write_outputs/write_storage.jl index dd79be32a6..d166f28f8a 100644 --- a/src/write_outputs/write_storage.jl +++ b/src/write_outputs/write_storage.jl @@ -4,7 +4,9 @@ Function for writing the capacities of different storage technologies, including hydro reservoir, flexible storage tech etc. """ function write_storage(path::AbstractString, inputs::Dict,setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) + T = inputs["T"] # Number of time steps (hours) G = inputs["G"] STOR_ALL = inputs["STOR_ALL"] @@ -14,7 +16,7 @@ function write_storage(path::AbstractString, inputs::Dict,setup::Dict, EP::Model VS_STOR = !isempty(VRE_STOR) ? inputs["VS_STOR"] : [] # Storage level (state of charge) of each resource in each time step - dfStorage = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfStorage = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) storagevcapvalue = zeros(G,T) if !isempty(inputs["STOR_ALL"]) diff --git a/src/write_outputs/write_storagedual.jl b/src/write_outputs/write_storagedual.jl index 7232fffb9f..1e7dc39f17 100644 --- a/src/write_outputs/write_storagedual.jl +++ b/src/write_outputs/write_storagedual.jl @@ -4,7 +4,8 @@ Function for reporting dual of storage level (state of charge) balance of each resource in each time step. """ function write_storagedual(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + zones = zone_id.(res) G = inputs["G"] # Number of resources (generators, storage, DR, and DERs) T = inputs["T"] # Number of time steps (hours) @@ -21,7 +22,7 @@ function write_storagedual(path::AbstractString, inputs::Dict, setup::Dict, EP:: end # # Dual of storage level (state of charge) balance of each resource in each time step - dfStorageDual = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources)) + dfStorageDual = DataFrame(Resource = inputs["RESOURCE_NAMES"], Zone = zones) dual_values = zeros(G, T) # Loop over W separately hours_per_subperiod diff --git a/src/write_outputs/write_subsidy_revenue.jl b/src/write_outputs/write_subsidy_revenue.jl index bd179448b9..0adecc3138 100644 --- a/src/write_outputs/write_subsidy_revenue.jl +++ b/src/write_outputs/write_subsidy_revenue.jl @@ -4,16 +4,21 @@ Function for reporting subsidy revenue earned if a generator specified `Min_Cap` is provided in the input file, or if a generator is subject to a Minimum Capacity Requirement constraint. The unit is \$. """ function write_subsidy_revenue(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) - resources = inputs["RESOURCES"] + res = inputs["RESOURCES"] + regions = region.(res) + clusters = cluster.(res) + zones = zone_id.(res) + rid = resource_id.(res) + G = inputs["G"] - dfSubRevenue = DataFrame(Region = dfGen[!, :region], Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), Cluster = dfGen[!, :cluster], R_ID=dfGen[!, :R_ID], SubsidyRevenue = zeros(G)) - MIN_CAP = dfGen[(dfGen[!, :Min_Cap_MW].>0), :R_ID] + dfSubRevenue = DataFrame(Region = regions, Resource = inputs["RESOURCE_NAMES"], Zone = zones, Cluster = clusters, R_ID=rid, SubsidyRevenue = zeros(G)) + MIN_CAP = has_positive_min_capacity_mw(res) if !isempty(inputs["VRE_STOR"]) dfVRE_STOR = inputs["dfVRE_STOR"] MIN_CAP_SOLAR = dfVRE_STOR[(dfVRE_STOR[!, :Min_Cap_Solar_MW].>0), :R_ID] MIN_CAP_WIND = dfVRE_STOR[(dfVRE_STOR[!, :Min_Cap_Wind_MW].>0), :R_ID] - MIN_CAP_STOR = dfGen[(dfGen[!, :Min_Cap_MWh].>0), :R_ID] + MIN_CAP_STOR = has_positive_min_capacity_mwh(res) if !isempty(MIN_CAP_SOLAR) dfSubRevenue.SubsidyRevenue[MIN_CAP_SOLAR] .+= (value.(EP[:eTotalCap_SOLAR])[MIN_CAP_SOLAR]) .* (dual.(EP[:cMinCap_Solar][MIN_CAP_SOLAR])).data end @@ -26,7 +31,7 @@ function write_subsidy_revenue(path::AbstractString, inputs::Dict, setup::Dict, end dfSubRevenue.SubsidyRevenue[MIN_CAP] .= (value.(EP[:eTotalCap])[MIN_CAP]) .* (dual.(EP[:cMinCap][MIN_CAP])).data ### calculating tech specific subsidy revenue - dfRegSubRevenue = DataFrame(Region = dfGen[!, :region], Resource = inputs["RESOURCE_NAMES"], Zone = zone_id.(resources), Cluster = dfGen[!, :cluster], R_ID=dfGen[!, :R_ID], SubsidyRevenue = zeros(G)) + dfRegSubRevenue = DataFrame(Region = regions, Resource = inputs["RESOURCE_NAMES"], Zone = zones, Cluster = clusters, R_ID=rid, SubsidyRevenue = zeros(G)) if (setup["MinCapReq"] >= 1) for mincap in 1:inputs["NumberOfMinCapReqs"] # This key only exists if MinCapReq >= 1, so we can't get it at the top outside of this condition. MIN_CAP_GEN = dfGen[(dfGen[!, Symbol("MinCapTag_$mincap")].==1), :R_ID] diff --git a/src/write_outputs/write_vre_stor.jl b/src/write_outputs/write_vre_stor.jl index f22b14fe72..8d917e2822 100644 --- a/src/write_outputs/write_vre_stor.jl +++ b/src/write_outputs/write_vre_stor.jl @@ -24,12 +24,13 @@ end Function for writing the vre-storage capacities. """ function write_vre_stor_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Model) + res = inputs["RESOURCES"] + VRE_STOR = inputs["VRE_STOR"] SOLAR = inputs["VS_SOLAR"] WIND = inputs["VS_WIND"] DC = inputs["VS_DC"] STOR = inputs["VS_STOR"] - resources = inputs["RESOURCES"] dfVRE_STOR = inputs["dfVRE_STOR"] MultiStage = setup["MultiStage"] size_vrestor_resources = size(inputs["RESOURCES_VRE_STOR"]) @@ -81,7 +82,7 @@ function write_vre_stor_capacity(path::AbstractString, inputs::Dict, setup::Dict j = 1 for i in VRE_STOR - existingcapgrid[j] = MultiStage == 1 ? value(EP[:vEXISTINGCAP][i]) : dfGen[i,:Existing_Cap_MW] + existingcapgrid[j] = MultiStage == 1 ? value(EP[:vEXISTINGCAP][i]) : existing_capacity_mw(res[i]) if i in inputs["NEW_CAP"] capgrid[j] = value(EP[:vCAP][i]) end @@ -120,7 +121,7 @@ function write_vre_stor_capacity(path::AbstractString, inputs::Dict, setup::Dict end if i in STOR - existingcapenergy[j] = MultiStage == 1 ? value(EP[:vEXISTINGCAPENERGY_VS][i]) : dfGen[i,:Existing_Cap_MWh] + existingcapenergy[j] = MultiStage == 1 ? value(EP[:vEXISTINGCAPENERGY_VS][i]) : existing_capacity_mwh([i]) if i in inputs["NEW_CAP_STOR"] capenergy[j] = value(EP[:vCAPENERGY_VS][i]) end diff --git a/test/Inputfiles/resources/Generators_variability.csv b/test/Inputfiles/Generators_variability.csv similarity index 100% rename from test/Inputfiles/resources/Generators_variability.csv rename to test/Inputfiles/Generators_variability.csv diff --git a/test/Inputfiles/Settings/genx_settings.yml b/test/Inputfiles/Settings/genx_settings.yml index 25289e4c53..83ee758167 100644 --- a/test/Inputfiles/Settings/genx_settings.yml +++ b/test/Inputfiles/Settings/genx_settings.yml @@ -1,4 +1,4 @@ -NetworkExpansion: 1 +NetworkExpansion: 0 TimeDomainReductionFolder: "TDR_Results" ModelingToGenerateAlternativeIterations: 3 ParameterScale: 1 @@ -6,7 +6,7 @@ EnergyShareRequirement: 0 PrintModel: 1 TimeDomainReduction: 0 Trans_Loss_Segments: 1 -CapacityReserveMargin: 1 +CapacityReserveMargin: 0 ModelingtoGenerateAlternativeSlack: 0.1 MethodofMorris: 0 Reserves: 0 @@ -14,7 +14,7 @@ StorageLosses: 1 OverwriteResults: 1 UCommit: 2 ModelingToGenerateAlternatives: 0 -MaxCapReq: 1 -MinCapReq: 1 -CO2Cap: 1 +MaxCapReq: 0 +MinCapReq: 0 +CO2Cap: 0 WriteShadowPrices: 1 diff --git a/test/Inputfiles/resources/electrolyzer.csv b/test/Inputfiles/resources/electrolyzer.csv index 9aa1b18ed3..c8f1a111c9 100644 --- a/test/Inputfiles/resources/electrolyzer.csv +++ b/test/Inputfiles/resources/electrolyzer.csv @@ -1,2 +1,2 @@ -resource,zone,must_run,hydrogen_mwh_per_tonne,electrolyzer_min_kt,hydrogen_price_per_tonne,qualified_hydrogen_supply,new_build,can_retire,existing_cap_mw,max_cap_mw,min_cap_mw,inv_cost_per_mwyr,fixed_om_cost_per_mwyr,heat_rate_mmbtu_per_mwh,fuel,region,cluster,resource_type -electrolyzer,1,0,55,1000,1000,0,1,1,0,-1,-1,125000,15000,0,None,NE,0,hydrogen_electrolyzer \ No newline at end of file +resource,zone,hydrogen_mwh_per_tonne,electrolyzer_min_kt,hydrogen_price_per_tonne,qualified_hydrogen_supply,new_build,can_retire,existing_cap_mw,max_cap_mw,min_cap_mw,inv_cost_per_mwyr,fixed_om_cost_per_mwyr,heat_rate_mmbtu_per_mwh,fuel,min_power,ramp_up_percentage,ramp_dn_percentage,region,cluster,resource_type +electrolyzer,1,55,1000,1000,0,1,1,0,-1,-1,125000,15000,0,None,0,1,1,NE,0,hydrogen_electrolyzer \ No newline at end of file diff --git a/test/Inputfiles/resources/must_run.csv b/test/Inputfiles/resources/must_run.csv new file mode 100644 index 0000000000..7a71dcdce6 --- /dev/null +++ b/test/Inputfiles/resources/must_run.csv @@ -0,0 +1,4 @@ +resource,zone,existing_cap_mw,new_build,can_retire,cap_size,min_cap_mw,max_cap_mw,min_cap_mwh,max_cap_mwh,inv_cost_per_mwyr,fixed_om_cost_per_mwyr,inv_cost_per_mwhyr,fixed_om_cost_per_mwhyr,inv_cost_charge_per_mwyr,fixed_om_cost_charge_per_mwyr,var_om_cost_per_mwh,var_om_cost_per_mwh_in,start_cost_per_mw,start_fuel_mmbtu_per_mw,heat_rate_mmbtu_per_mwh,fuel,min_power,self_disch,eff_up,eff_down,hydro_energy_to_power_ratio,min_duration,max_duration,max_flexible_demand_advance,max_flexible_demand_delay,flexible_demand_energy_eff,ramp_up_percentage,ramp_dn_percentage,up_time,down_time,reg_max,rsv_max,reg_cost,rsv_cost,resource_type,mga,region,cluster +NENGREST_small_hydroelectric_1,1,186.355,0,0,0.79,0,0,0,-1,0,46475,0,0,0,0,0,0,0,0,9.12,None,0.117,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,small_hydroelectric,1,NENGREST,1 +NENG_CT_small_hydroelectric_1,2,18.711,0,0,0.57,0,0,0,-1,0,46475,0,0,0,0,0,0,0,0,9.12,None,0.18,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,small_hydroelectric,1,NENG_CT,1 +NENG_ME_small_hydroelectric_1,3,195.266,0,0,1.1,0,0,0,-1,0,46475,0,0,0,0,0,0,0,0,9.12,None,0.192,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,small_hydroelectric,1,NENG_ME,1 \ No newline at end of file diff --git a/test/test_resource_loader.jl b/test/test_resource_loader.jl index 93ee2b8f9d..bfdb8d721d 100644 --- a/test/test_resource_loader.jl +++ b/test/test_resource_loader.jl @@ -1,5 +1,3 @@ -using Pkg -Pkg.activate(".") using GenX using HiGHS @@ -8,33 +6,33 @@ optimizer = HiGHS.Optimizer # case = "Example_Systems/Electrolyzer_Example" case = "test/Inputfiles" -# case = "/Users/lb9239/Documents/ZERO_lab/GenX/GenX/Example_Systems/RealSystemExample/ISONE_Trizone" + genx_settings = GenX.get_settings_path(case, "genx_settings.yml") -mysetup = GenX.configure_settings(genx_settings) +setup = configure_settings(genx_settings) settings_path = GenX.get_settings_path(case) -TDRpath = joinpath(case, mysetup["TimeDomainReductionFolder"]) +TDRpath = joinpath(case, setup["TimeDomainReductionFolder"]) -if mysetup["TimeDomainReduction"] == 1 - GenX.prevent_doubled_timedomainreduction(case) - if !GenX.time_domain_reduced_files_exist(TDRpath) +if setup["TimeDomainReduction"] == 1 + prevent_doubled_timedomainreduction(case) + if !time_domain_reduced_files_exist(TDRpath) println("Clustering Time Series Data (Grouped)...") - GenX.cluster_inputs(case, settings_path, mysetup) + cluster_inputs(case, settings_path, setup) else println("Time Series Data Already Clustered.") end end println("Configuring Solver") -OPTIMIZER = GenX.configure_solver(settings_path, optimizer) +OPTIMIZER = configure_solver(settings_path, optimizer) println("Loading Inputs") -input_data = GenX.load_inputs(mysetup, case) +input_data = load_inputs(setup, case) -rs = input_data["RESOURCES"] +rs = input_data["RESOURCES"]; # println("Generating the Optimization Model") -# time_elapsed = @elapsed EP = GenX.generate_model(mysetup, myinputs, OPTIMIZER) -# println("Time elapsed for model building is") -# println(time_elapsed) \ No newline at end of file +time_elapsed = @elapsed EP = generate_model(setup, input_data, OPTIMIZER) +println("Time elapsed for model building is") +println(time_elapsed) \ No newline at end of file