From 26689d0d2e86923b851bfcfad16029eeff5089e9 Mon Sep 17 00:00:00 2001 From: Yifu_eve <49561157+yifueve@users.noreply.github.com> Date: Mon, 12 Feb 2024 17:06:06 -0500 Subject: [PATCH] change load generator data.jl --- ...rs_data_old.jl => load_generators_data.jl} | 239 ++++++++---------- src/write_outputs/write_capacity.jl | 1 - 2 files changed, 102 insertions(+), 138 deletions(-) rename src/load_inputs/{load_generators_data_old.jl => load_generators_data.jl} (69%) mode change 100755 => 100644 diff --git a/src/load_inputs/load_generators_data_old.jl b/src/load_inputs/load_generators_data.jl old mode 100755 new mode 100644 similarity index 69% rename from src/load_inputs/load_generators_data_old.jl rename to src/load_inputs/load_generators_data.jl index eb7eec3757..bfb4ee711a --- a/src/load_inputs/load_generators_data_old.jl +++ b/src/load_inputs/load_generators_data.jl @@ -6,21 +6,25 @@ Function for reading input parameters related to electricity generators (plus st function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Dict, fuel_costs::Dict, fuel_CO2::Dict) filename = "Generators_data.csv" - gen_in = DataFrame(CSV.File(joinpath(path, filename), header=true), copycols=true) + gen_in = load_dataframe(joinpath(path, filename)) - # Add Resource IDs after reading to prevent user errors - gen_in[!,:R_ID] = 1:length(collect(skipmissing(gen_in[!,1]))) - # Store DataFrame of generators/resources input data for use in model - inputs_gen["dfGen"] = gen_in + # Store DataFrame of generators/resources input data for use in model + inputs_gen["dfGen"] = gen_in - # Number of resources - inputs_gen["G"] = length(collect(skipmissing(gen_in[!,:R_ID]))) + # initial screen that resources are valid + resources = dataframerow_to_dict.(eachrow(gen_in)) + validate_resources(resources) + inputs_gen["resources_d"] = resources - # Set indices for internal use - G = inputs_gen["G"] # Number of resources (generators, storage, DR, and DERs) + # Number of resources (generators, storage, DR, and DERs) + G = nrow(gen_in) + inputs_gen["G"] = G - scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 + # Add Resource IDs after reading to prevent user errors + gen_in[!,:R_ID] = 1:G + + scale_factor = setup["ParameterScale"] == 1 ? ModelScalingFactor : 1 ## Defining sets of generation and storage resources # Set of storage resources with symmetric charge/discharge capacity @@ -62,6 +66,7 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di if !("RETRO" in names(gen_in)) gen_in[!, "RETRO"] = zero(gen_in[!, "R_ID"]) end + inputs_gen["RETRO"] = gen_in[gen_in.RETRO.==1,:R_ID] # Disable Retrofit while it's under development # if !(isempty(inputs_gen["RETRO"])) @@ -71,12 +76,10 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di # Set of thermal generator resources if setup["UCommit"]>=1 # Set of thermal resources eligible for unit committment - # Yifu changed to include retrofit (change later!) - inputs_gen["THERM_COMMIT"] = union(gen_in[gen_in.THERM.==1,:R_ID]) + inputs_gen["THERM_COMMIT"] = gen_in[gen_in.THERM.==1,:R_ID] # Set of thermal resources not eligible for unit committment - # Yifu changed to include retrofit (change later!) - inputs_gen["THERM_NO_COMMIT"] = union(gen_in[gen_in.THERM.==2,:R_ID]) - else # When UCommit == 0, no thermal resources are eligible for unit committment (change later!) + inputs_gen["THERM_NO_COMMIT"] = gen_in[gen_in.THERM.==2,:R_ID] + else # When UCommit == 0, no thermal resources are eligible for unit committment inputs_gen["THERM_COMMIT"] = Int64[] inputs_gen["THERM_NO_COMMIT"] = union(gen_in[gen_in.THERM.==1,:R_ID], gen_in[gen_in.THERM.==2,:R_ID]) end @@ -101,31 +104,45 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di end # Set of all resources eligible for new capacity - inputs_gen["NEW_CAP"] = intersect(gen_in[gen_in.New_Build.>=1,:R_ID], gen_in[gen_in.Max_Cap_MW.!=0,:R_ID]) + inputs_gen["NEW_CAP"] = intersect(buildable, gen_in[gen_in.Max_Cap_MW.!=0,:R_ID]) # Set of all resources eligible for capacity retirements - inputs_gen["RET_CAP"] = intersect(gen_in[gen_in.New_Build.!=-1,:R_ID], gen_in[gen_in.Existing_Cap_MW.>=0,:R_ID]) - # Set of all resources eligible for capacity retrofitting (by Yifu, same with retirement) - inputs_gen["RETRO_CAP"] = intersect(gen_in[gen_in.New_Build.>=2,:R_ID], gen_in[gen_in.Existing_Cap_MW.>=0,:R_ID]) - - # Set of all storage resources eligible for new energy capacity - inputs_gen["NEW_CAP_ENERGY"] = intersect(gen_in[gen_in.New_Build.==1,:R_ID], gen_in[gen_in.Max_Cap_MWh.!=0,:R_ID], inputs_gen["STOR_ALL"]) - # Set of all storage resources eligible for energy capacity retirements - inputs_gen["RET_CAP_ENERGY"] = intersect(gen_in[gen_in.New_Build.!=-1,:R_ID], gen_in[gen_in.Existing_Cap_MWh.>=0,:R_ID], inputs_gen["STOR_ALL"]) - # Set of all storage resources for created energy capacity retrofit (Yifu) - inputs_gen["RETRO_CAP_ENERGY"] = intersect(gen_in[gen_in.New_Build.==-1,:R_ID], inputs_gen["RETRO"], inputs_gen["STOR_ALL"]) - - # Set of asymmetric charge/discharge storage resources eligible for new charge capacity - inputs_gen["NEW_CAP_CHARGE"] = intersect(gen_in[gen_in.New_Build.==1,:R_ID], gen_in[gen_in.Max_Charge_Cap_MW.!=0,:R_ID], inputs_gen["STOR_ASYMMETRIC"]) - # Set of asymmetric charge/discharge storage resources eligible for charge capacity retirements - inputs_gen["RET_CAP_CHARGE"] = intersect(gen_in[gen_in.New_Build.!=-1,:R_ID], gen_in[gen_in.Existing_Charge_Cap_MW.>=0,:R_ID], inputs_gen["STOR_ASYMMETRIC"]) - # Set of all storage resources for created energy capacity retrofit (Yifu) - inputs_gen["RETRO_CAP_CHARGE"] = intersect(gen_in[gen_in.New_Build.==-1,:R_ID], inputs_gen["RETRO"], inputs_gen["STOR_ASYMMETRIC"]) + inputs_gen["RET_CAP"] = intersect(retirable, gen_in[gen_in.Existing_Cap_MW.>=0,:R_ID]) + # Set of all resources eligible for capacity retrofitting (by Yifu, same with retirement) + inputs_gen["RETRO_CAP"] = intersect(gen_in[gen_in.New_Build.>=2,:R_ID], gen_in[gen_in.Existing_Cap_MW.>=0,:R_ID]) + + new_cap_energy = Set{Int64}() + ret_cap_energy = Set{Int64}() + retro_cap_energy = Set{Int64}() + if !isempty(inputs_gen["STOR_ALL"]) + # Set of all storage resources eligible for new energy capacity + new_cap_energy = intersect(buildable, gen_in[gen_in.Max_Cap_MWh.!=0,:R_ID], inputs_gen["STOR_ALL"]) + # Set of all storage resources eligible for energy capacity retirements + ret_cap_energy = intersect(retirable, gen_in[gen_in.Existing_Cap_MWh.>=0,:R_ID], inputs_gen["STOR_ALL"]) + retro_cap_energy = intersect(gen_in[gen_in.New_Build.==-1,:R_ID], inputs_gen["RETRO"], inputs_gen["STOR_ALL"]) + end + inputs_gen["NEW_CAP_ENERGY"] = new_cap_energy + inputs_gen["RET_CAP_ENERGY"] = ret_cap_energy + inputs_gen["RETRO_CAP_ENERGY"] = retro_cap_energy + + new_cap_charge = Set{Int64}() + ret_cap_charge = Set{Int64}() + retro_cap_charge = Set{Int64}() + if !isempty(inputs_gen["STOR_ASYMMETRIC"]) + # Set of asymmetric charge/discharge storage resources eligible for new charge capacity + new_cap_charge = intersect(buildable, gen_in[gen_in.Max_Charge_Cap_MW.!=0,:R_ID], inputs_gen["STOR_ASYMMETRIC"]) + # Set of asymmetric charge/discharge storage resources eligible for charge capacity retirements + ret_cap_charge = intersect(buildable, gen_in[gen_in.Existing_Charge_Cap_MW.>=0,:R_ID], inputs_gen["STOR_ASYMMETRIC"]) + # Set of all storage resources (power capacity component) CREATED AFTER RETROFITTING (Yifu) + retro_cap_charge = intersect(gen_in[gen_in.New_Build.==-1,:R_ID], inputs_gen["RETRO"], inputs_gen["STOR_ASYMMETRIC"]) + end + inputs_gen["NEW_CAP_CHARGE"] = new_cap_charge + inputs_gen["RET_CAP_CHARGE"] = ret_cap_charge + inputs_gen["RETRO_CAP_CHARGE"] = retro_cap_charge # Names of resources - inputs_gen["RESOURCES"] = collect(skipmissing(gen_in[!,:Resource][1:inputs_gen["G"]])) - inputs_gen["RESOURCE_TYPE"] = collect(skipmissing(gen_in[!,:Resource_Type][1:inputs_gen["G"]])) + inputs_gen["RESOURCES"] = gen_in[!,:Resource] # Zones resources are located in - zones = collect(skipmissing(gen_in[!,:Zone][1:inputs_gen["G"]])) + zones = gen_in[!,:Zone] # Resource identifiers by zone (just zones in resource order + resource and zone concatenated) inputs_gen["R_ZONES"] = zones inputs_gen["RESOURCE_ZONES"] = inputs_gen["RESOURCES"] .* "_z" .* string.(zones) @@ -135,114 +152,62 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di # Resource identifiers by clsuter (just zones in resource order + resource and zone concatenated) inputs_gen["R_CLUSTERING"] = clusters - if setup["ParameterScale"] == 1 # Parameter scaling turned on - adjust values of subset of parameter values - - # The existing capacity of a power plant in megawatts - inputs_gen["dfGen"][!,:Existing_Charge_Cap_MW] = gen_in[!,:Existing_Charge_Cap_MW]/ModelScalingFactor # Convert to GW - # The existing capacity of storage in megawatt-hours STOR = 1 or STOR = 2 - inputs_gen["dfGen"][!,:Existing_Cap_MWh] = gen_in[!,:Existing_Cap_MWh]/ModelScalingFactor # Convert to GWh - # The existing charging capacity for resources where STOR = 2 - inputs_gen["dfGen"][!,:Existing_Cap_MW] = gen_in[!,:Existing_Cap_MW]/ModelScalingFactor # Convert to GW - - # Cap_Size scales only capacities for those technologies with capacity >1 - # Step 1: convert vector to float - inputs_gen["dfGen"][!,:Cap_Size] =convert(Array{Float64}, gen_in[!,:Cap_Size]) - inputs_gen["dfGen"][!,:Cap_Size] ./= ModelScalingFactor - - # Min capacity terms - # Limit on minimum discharge capacity of the resource. -1 if no limit on minimum capacity - inputs_gen["dfGen"][!,:Min_Cap_MW] = gen_in[!,:Min_Cap_MW]/ModelScalingFactor # Convert to GW - # Limit on minimum energy capacity of the resource. -1 if no limit on minimum capacity - inputs_gen["dfGen"][!,:Min_Cap_MWh] = gen_in[!,:Min_Cap_MWh]/ModelScalingFactor # Convert to GWh - # Limit on minimum charge capacity of the resource. -1 if no limit on minimum capacity - inputs_gen["dfGen"][!,:Min_Charge_Cap_MW] = gen_in[!,:Min_Charge_Cap_MW]/ModelScalingFactor # Convert to GWh - - ## Max capacity terms - # Limit on maximum discharge capacity of the resource. -1 if no limit on maximum capacity - inputs_gen["dfGen"][!,:Max_Cap_MW] = gen_in[!,:Max_Cap_MW]/ModelScalingFactor # Convert to GW - # Limit on maximum energy capacity of the resource. -1 if no limit on maximum capacity - inputs_gen["dfGen"][!,:Max_Cap_MWh] = gen_in[!,:Max_Cap_MWh]/ModelScalingFactor # Convert to GWh - # Limit on maximum charge capacity of the resource. -1 if no limit on maximum capacity - inputs_gen["dfGen"][!,:Max_Charge_Cap_MW] = gen_in[!,:Max_Charge_Cap_MW]/ModelScalingFactor # Convert to GW - - ## Investment cost terms - # Annualized capacity investment cost of a generation technology - inputs_gen["dfGen"][!,:Inv_Cost_per_MWyr] = gen_in[!,:Inv_Cost_per_MWyr]/ModelScalingFactor # Convert to $ million/GW/yr with objective function in millions - # Annualized investment cost of the energy capacity for a storage technology with STOR = 1 or STOR = 2 - inputs_gen["dfGen"][!,:Inv_Cost_per_MWhyr] = gen_in[!,:Inv_Cost_per_MWhyr]/ModelScalingFactor # Convert to $ million/GWh/yr with objective function in millions - # Annualized capacity investment cost for the charging portion of a storage technology with STOR = 2 - inputs_gen["dfGen"][!,:Inv_Cost_Charge_per_MWyr] = gen_in[!,:Inv_Cost_Charge_per_MWyr]/ModelScalingFactor # Convert to $ million/GWh/yr with objective function in millions - - ## Fixed O&M cost terms - # Fixed operations and maintenance cost of a generation or storage technology - inputs_gen["dfGen"][!,:Fixed_OM_Cost_per_MWyr] = gen_in[!,:Fixed_OM_Cost_per_MWyr]/ModelScalingFactor # Convert to $ million/GW/yr with objective function in millions - # Fixed operations and maintenance cost of the power aspect of a storage technology of type STOR = 1 or STOR = 2 - inputs_gen["dfGen"][!,:Fixed_OM_Cost_per_MWhyr] = gen_in[!,:Fixed_OM_Cost_per_MWhyr]/ModelScalingFactor # Convert to $ million/GW/yr with objective function in millions - # Fixed operations and maintenance cost of the charging aspect of a storage technology of type STOR = 2 - inputs_gen["dfGen"][!,:Fixed_OM_Cost_Charge_per_MWyr] = gen_in[!,:Fixed_OM_Cost_Charge_per_MWyr]/ModelScalingFactor # Convert to $ million/GW/yr with objective function in millions - - ## Variable O&M cost terms - # Variable operations and maintenance cost of a generation or storage technology - inputs_gen["dfGen"][!,:Var_OM_Cost_per_MWh] = gen_in[!,:Var_OM_Cost_per_MWh]/ModelScalingFactor # Convert to $ million/GWh with objective function in millions - # Variable operations and maintenance cost of the charging aspect of a storage technology with STOR = 2, - # or variable operations and maintenance costs associated with flexible demand with FLEX = 1 - inputs_gen["dfGen"][!,:Var_OM_Cost_per_MWh_In] = gen_in[!,:Var_OM_Cost_per_MWh_In]/ModelScalingFactor # Convert to $ million/GWh with objective function in millions - # Cost of providing regulation reserves - inputs_gen["dfGen"][!,:Reg_Cost] = gen_in[!,:Reg_Cost]/ModelScalingFactor # Convert to $ million/GW with objective function in millions - # Cost of providing spinning reserves - inputs_gen["dfGen"][!,:Rsv_Cost] = gen_in[!,:Rsv_Cost]/ModelScalingFactor # Convert to $ million/GW with objective function in millions - - if setup["MultiStage"] == 1 - inputs_gen["dfGen"][!,:Min_Retired_Cap_MW] = gen_in[!,:Min_Retired_Cap_MW]/ModelScalingFactor - inputs_gen["dfGen"][!,:Min_Retired_Charge_Cap_MW] = gen_in[!,:Min_Retired_Charge_Cap_MW]/ModelScalingFactor - inputs_gen["dfGen"][!,:Min_Retired_Energy_Cap_MW] = gen_in[!,:Min_Retired_Energy_Cap_MW]/ModelScalingFactor - end - end + # See documentation for descriptions of each column + # Generally, these scalings converts energy and power units from MW to GW + # and $/MW to $M/GW. Both are done by dividing the values by 1000. + columns_to_scale = [:Existing_Charge_Cap_MW, # to GW + :Existing_Cap_MWh, # to GWh + :Existing_Cap_MW, # to GW -# Dharik - Done, we have scaled fuel costs above so any parameters on per MMBtu do not need to be scaled - if setup["UCommit"]>=1 - if setup["ParameterScale"] ==1 # Parameter scaling turned on - adjust values of subset of parameter values - # Cost per MW of nameplate capacity to start a generator - inputs_gen["dfGen"][!,:Start_Cost_per_MW] = gen_in[!,:Start_Cost_per_MW]/ModelScalingFactor # Convert to $ million/GW with objective function in millions - end + :Cap_Size, # to GW + + :Min_Cap_MW, # to GW + :Min_Cap_MWh, # to GWh + :Min_Charge_Cap_MW, # to GWh + + :Max_Cap_MW, # to GW + :Max_Cap_MWh, # to GWh + :Max_Charge_Cap_MW, # to GW - # Fuel consumed on start-up (million BTUs per MW per start) if unit commitment is modelled - start_fuel = convert(Array{Float64}, collect(skipmissing(gen_in[!,:Start_Fuel_MMBTU_per_MW]))) - # Fixed cost per start-up ($ per MW per start) if unit commitment is modelled - start_cost = convert(Array{Float64}, collect(skipmissing(inputs_gen["dfGen"][!,:Start_Cost_per_MW]))) + :Inv_Cost_per_MWyr, # to $M/GW/yr + :Inv_Cost_per_MWhyr, # to $M/GWh/yr + :Inv_Cost_Charge_per_MWyr, # to $M/GW/yr + + :Fixed_OM_Cost_per_MWyr, # to $M/GW/yr + :Fixed_OM_Cost_per_MWhyr, # to $M/GWh/yr + :Fixed_OM_Cost_Charge_per_MWyr, # to $M/GW/yr + + :Var_OM_Cost_per_MWh, # to $M/GWh + :Var_OM_Cost_per_MWh_In, # to $M/GWh + + :Reg_Cost, # to $M/GW + :Rsv_Cost, # to $M/GW + + :Min_Retired_Cap_MW, # to GW + :Min_Retired_Charge_Cap_MW, # to GW + :Min_Retired_Energy_Cap_MW, # to GW + + :Start_Cost_per_MW, # to $M/GW + + :Hydrogen_MWh_Per_Tonne, # to GWh/t + ] + + for column in columns_to_scale + if string(column) in names(gen_in) + gen_in[!, column] /= scale_factor + end + end + + if setup["UCommit"] >= 1 + start_cost = convert(Array{Float64}, gen_in[!,:Start_Cost_per_MW]) inputs_gen["C_Start"] = zeros(Float64, G, inputs_gen["T"]) - inputs_gen["dfGen"][!,:CO2_per_Start] = zeros(Float64, G) end - # Heat rate of all resources (million BTUs/MWh) - heat_rate = convert(Array{Float64}, collect(skipmissing(gen_in[!,:Heat_Rate_MMBTU_per_MWh])) ) - # Fuel used by each resource - fuel_type = collect(skipmissing(gen_in[!,:Fuel])) - # Maximum fuel cost in $ per MWh and CO2 emissions in tons per MWh - inputs_gen["C_Fuel_per_MWh"] = zeros(Float64, G, inputs_gen["T"]) - inputs_gen["dfGen"][!,:CO2_per_MWh] = zeros(Float64, G) + # scale the start costs for g in 1:G - # NOTE: When Setup[ParameterScale] =1, fuel costs are scaled in fuels_data.csv, so no if condition needed to scale C_Fuel_per_MWh - inputs_gen["C_Fuel_per_MWh"][g,:] = fuel_costs[fuel_type[g]].*heat_rate[g] - inputs_gen["dfGen"][g,:CO2_per_MWh] = fuel_CO2[fuel_type[g]]*heat_rate[g] - if setup["ParameterScale"] ==1 - inputs_gen["dfGen"][g,:CO2_per_MWh] = inputs_gen["dfGen"][g,:CO2_per_MWh] * ModelScalingFactor - end - # kton/MMBTU * MMBTU/MWh = kton/MWh, to get kton/GWh, we need to mutiply 1000 if g in inputs_gen["COMMIT"] - # Start-up cost is sum of fixed cost per start plus cost of fuel consumed on startup. - # CO2 from fuel consumption during startup also calculated - - inputs_gen["C_Start"][g,:] = inputs_gen["dfGen"][g,:Cap_Size] * (fuel_costs[fuel_type[g]] .* start_fuel[g] .+ start_cost[g]) - # No need to re-scale C_Start since Cap_size, fuel_costs and start_cost are scaled When Setup[ParameterScale] =1 - Dharik - inputs_gen["dfGen"][g,:CO2_per_Start] = inputs_gen["dfGen"][g,:Cap_Size]*(fuel_CO2[fuel_type[g]]*start_fuel[g]) - if setup["ParameterScale"] ==1 - inputs_gen["dfGen"][g,:CO2_per_Start] = inputs_gen["dfGen"][g,:CO2_per_Start] * ModelScalingFactor - end - # Setup[ParameterScale] =1, inputs_gen["dfGen"][g,:Cap_Size] is GW, fuel_CO2[fuel_type[g]] is ktons/MMBTU, start_fuel is MMBTU/MW, - # thus the overall is MTons/GW, and thus inputs_gen["dfGen"][g,:CO2_per_Start] is Mton, to get kton, change we need to multiply 1000 - # Setup[ParameterScale] =0, inputs_gen["dfGen"][g,:Cap_Size] is MW, fuel_CO2[fuel_type[g]] is tons/MMBTU, start_fuel is MMBTU/MW, - # thus the overall is MTons/GW, and thus inputs_gen["dfGen"][g,:CO2_per_Start] is ton + # Start-up cost is sum of fixed cost per start startup. + inputs_gen["C_Start"][g,:] .= gen_in[g,:Cap_Size] * ( start_cost[g]) end end diff --git a/src/write_outputs/write_capacity.jl b/src/write_outputs/write_capacity.jl index b8a6ee52d2..4b075491fe 100755 --- a/src/write_outputs/write_capacity.jl +++ b/src/write_outputs/write_capacity.jl @@ -81,7 +81,6 @@ function write_capacity(path::AbstractString, inputs::Dict, setup::Dict, EP::Mod end dfCap = DataFrame( Resource = inputs["RESOURCES"], - Resource_type = inputs["RESOURCE_TYPE"], Zone = dfGen[!,:Zone], Cluster = dfGen[!,:cluster], StartCap = MultiStage == 1 ? value.(EP[:vEXISTINGCAP]) : dfGen[!,:Existing_Cap_MW],