From 37ac83166181d6e488ab345db9ecb26ecd631fa1 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:16:13 -0600 Subject: [PATCH 01/10] removing unneeded C2N functionality doing fancy capital scheduling stuff which was never fully implemented --- src/C2N_projects.jl | 574 -------------------------------------------- 1 file changed, 574 deletions(-) delete mode 100644 src/C2N_projects.jl diff --git a/src/C2N_projects.jl b/src/C2N_projects.jl deleted file mode 100644 index b6e93ee0..00000000 --- a/src/C2N_projects.jl +++ /dev/null @@ -1,574 +0,0 @@ -########################################################################## -# Copyright 2023 Argonne National Laboratory -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -########################################################################## - - -module C2N - -using Logging, CSV, DataFrames - - -function create_C2N_capex_timeline( - db, - conv_type, - rxtr_type, - base_pd, - lag, - fc_pd, - C2N_specs, - unit_type_data, -) - check_init_inputs(conv_type, rxtr_type, lag, fc_pd) - capex_tl, activity_schedule = project_capex_profile( - base_pd, - lag, - db, - fc_pd, - rxtr_type, - conv_type, - C2N_specs, - unit_type_data; - status = "new", - asset_id = nothing, - ) - - continue_checking = true - while continue_checking - if capex_tl[size(capex_tl)[1], :total_capex] == 0 - deleteat!(capex_tl, size(capex_tl)[1]) - else - continue_checking = false - end - end - - return capex_tl, activity_schedule -end - - -function check_init_inputs(conv_type, rxtr_type, lag, fc_pd) - # Valid type declarations - valid_conv_types = ["greenfield", "electrical", "steam_noTES", "steam_TES"] - valid_rxtr_types = ["PWR", "SFR", "HTGR"] - - # Check project categorical descriptors - if !in(conv_type, valid_conv_types) - err_msg = string( - "Conversion type $conv_type is not valid, or is not currently ", - "supported.\n Currently supported conversion types:\n", - valid_conv_types, - ) - throw(ArgumentError(conv_type, err_msg)) - end - - if !in(rxtr_type, valid_rxtr_types) - err_msg = string( - "Reactor type $rxtr_type is not valid, or is not currently ", - "supported.\n Currently supported reactor types:\n", - valid_rxtr_types, - ) - throw(ArgumentError(rxtr_type, err_msg)) - end - - # Validate lag - if lag < 0 - throw(DomainError(lag, "Lag must be a nonnegative integer.")) - end - - # Validate fc_pd - if fc_pd < 1 - throw( - DomainError( - fc_pd, - "fc_pd (forecast period) must be an integer greater than 0.", - ), - ) - end - -end - - -function project_capex_profile( - base_pd, - lag, - db, - fc_pd, - rxtr_type, - conv_type, - C2N_specs, - unit_type_data; - status = "new", - asset_id = "nothing", -) - # Determine the project's current status - if status == "new" - project_current_status = deepcopy(C2N_specs) - elseif status == "ongoing" - # Retrieve current project status from database - pd_of_interest = base_pd - 1 - project_WIP_update = - DBInterface.execute( - db, - string( - "SELECT * FROM WIP_C2N ", - "WHERE asset_id = $asset_id ", - "AND period = $pd_of_interest", - ), - ) |> DataFrame - project_WIP_update = project_WIP_update[1, :] - project_current_status = Dict( - :npp_ns_xtr => Dict( - "cost_rem" => project_WIP_update[:npp_ns_xtr_cost_rem], - "time_rem" => project_WIP_update[:npp_ns_xtr_time_rem], - ), - :npp_safety_xtr => Dict( - "cost_rem" => project_WIP_update[:npp_safety_xtr_cost_rem], - "time_rem" => project_WIP_update[:npp_safety_xtr_time_rem], - ), - :cpp_dnd => Dict( - "cost_rem" => project_WIP_update[:cpp_dnd_cost_rem], - "time_rem" => project_WIP_update[:cpp_dnd_time_rem], - ), - :cpp_wr => Dict( - "cost_rem" => project_WIP_update[:cpp_wr_cost_rem], - "time_rem" => project_WIP_update[:cpp_wr_time_rem], - ), - :cpp_nrc => Dict( - "cost_rem" => project_WIP_update[:cpp_nrc_cost_rem], - "time_rem" => project_WIP_update[:cpp_nrc_time_rem], - ), - ) - end - - # Set up the timeline of expenditures - max_horizon = fc_pd - capex_tl = DataFrame( - year = base_pd:(base_pd + fc_pd - 1), - cpp_dnd = zeros(fc_pd), - cpp_nrc = zeros(fc_pd), - cpp_wr = zeros(fc_pd), - npp_ns_xtr = zeros(fc_pd), - npp_safety_xtr = zeros(fc_pd), - ) - - # Generate the table of activities ongoing during project intervals - activity_schedule = get_C2N_available_activities( - conv_type, - project_current_status, - base_pd, - lag, - ) - - # Convert the binary project schedule into a capex schedule - capex_activity_schedule = allocate_funds_to_activities( - activity_schedule, - project_current_status, - C2N_specs, - unit_type_data, - ) - - # Convert the interval-based schedule into a yearly schedule - capex_tl, capex_activity_schedule = - convert_to_annual_capex_schedule(capex_activity_schedule) - - return capex_tl, activity_schedule - -end - - -function get_C2N_available_activities( - conv_type, - project_current_status, - base_pd, - lag, -) - all_activities = - ["cpp_dnd", "cpp_nrc", "cpp_wr", "npp_ns_xtr", "npp_safety_xtr"] - - activity_schedule = DataFrame( - state_start = Float64[], - state_end = Float64[], - cpp_dnd = Int[], - cpp_nrc = Int[], - cpp_wr = Int[], - npp_ns_xtr = Int[], - npp_safety_xtr = Int[], - ) - - project_ongoing = true - - test_status = deepcopy(project_current_status) - - while project_ongoing - available_activities = get_available_activities(test_status, conv_type) - if size(available_activities)[1] == 0 - project_ongoing = false - break - end - - # Determine the expected end date of all activities which are currently - # available for work - end_dates = DataFrame(activity = String[], time_rem = Float64[]) - for activity in available_activities - time_rem = test_status[activity]["time_rem"] - push!(end_dates, [activity, time_rem]) - end - - # Find the activity with the shortest time remaining - sort!(end_dates, :time_rem) - next_finished = end_dates[1, :activity] - next_interval_duration = end_dates[1, :time_rem] - - # Any activities eligible for work during this interval receive a 1 - # in the activity_schedule; otherwise, they get a 0 - activity_bools = [] - for activity in all_activities - if activity in available_activities - push!(activity_bools, 1) - else - push!(activity_bools, 0) - end - end - - # Add the row to the activity_schedule dataframe - if size(activity_schedule)[1] == 0 - next_start = base_pd + lag - else - next_start = last(activity_schedule[!, :state_end]) - end - next_end = next_start + next_interval_duration - push!( - activity_schedule, - hcat(next_start, next_end, transpose(activity_bools)), - ) - - # Subtract this interval's duration from the time remaining for any - # available activities - for activity in available_activities - test_status[activity]["time_rem"] -= end_dates[1, :time_rem] - end - - end - - - return activity_schedule -end - - -function get_available_activities(project_current_status, conv_type) - if conv_type == "greenfield" - available_activities = - get_available_activities_greenfield(project_current_status) - elseif conv_type == "electrical" - available_activities = - get_available_activities_electrical(project_current_status) - elseif conv_type == "steam_noTES" - available_activities = - get_available_activities_steam_noTES(project_current_status) - elseif conv_type == "steam_TES" - available_activities = - get_available_activities_steam_TES(project_current_status) - else - println( - string( - "I don't recognize that conversion project type. ", - "Check your inputs and try again.", - ), - ) - exit() - end - - return available_activities - -end - - -function get_available_activities_greenfield(project_current_status) - all_activities = - ["npp_ns_xtr", "npp_safety_xtr", "cpp_dnd", "cpp_wr", "cpp_nrc"] - completed_activities = [] - for activity in all_activities - act_time_rem = project_current_status[activity]["time_rem"] - if (round(act_time_rem, digits = 3) == 0) - push!(completed_activities, activity) - end - end - - incomplete_activities = [ - all_activities[i] for i = 1:size(all_activities)[1] if - !(all_activities[i] in completed_activities) - ] - - # The first remaining activity in all_activities is the sole available - # activity - # If no activities remain, the project is done; return empty list - if size(incomplete_activities)[1] != 0 - available_activities = [incomplete_activities[1]] - else - available_activities = [] - end - - return available_activities -end - - -function get_available_activities_electrical(project_current_status) - all_activities = [key for key in keys(project_current_status)] - completed_activities = [] - for activity in all_activities - act_time_rem = project_current_status[activity]["time_rem"] - if (round(act_time_rem, digits = 3) == 0) - push!(completed_activities, activity) - end - end - - incomplete_activities = [ - all_activities[i] for i = 1:size(all_activities)[1] if - !(all_activities[i] in completed_activities) - ] - - # Add activities which are available to be worked on to the - # available_activities list - # This section will not make much sense without the documentary flowcharts; - # see docs for explanation - # TODO: replace with digraph traversal - available_activities = [] - - # Activities with no prerequisite: cpp_wr, npp_ns_xtr - if "cpp_wr" in incomplete_activities - push!(available_activities, "cpp_wr") - end - - if "npp_ns_xtr" in incomplete_activities - push!(available_activities, "npp_ns_xtr") - end - - # CPP NRC license approval prerequisite: cpp_wr - if ( - ("cpp_nrc" in incomplete_activities) && - !("cpp_wr" in incomplete_activities) - ) - push!(available_activities, "cpp_nrc") - end - - # NPP safety xtr prerequisite: CPP NRC license - if ( - ("npp_safety_xtr" in incomplete_activities) && - !("cpp_nrc" in incomplete_activities) && - ("npp_ns_xtr" in completed_activities) - ) - push!(available_activities, "npp_safety_xtr") - end - - # CPP D&D only after all other activities are done - if ( - !("npp_safety_xtr" in incomplete_activities) && - !("cpp_dnd" in completed_activities) - ) - push!(available_activities, "cpp_dnd") - end - - return available_activities -end - -function get_available_activities_steam_noTES(project_current_status) - all_activities = [key for key in keys(project_current_status)] - completed_activities = [] - for activity in all_activities - act_time_rem = project_current_status[activity]["time_rem"] - if (round(act_time_rem, digits = 3) == 0) - push!(completed_activities, activity) - end - end - - incomplete_activities = [ - all_activities[i] for i = 1:size(all_activities)[1] if - !(all_activities[i] in completed_activities) - ] - - # Add activities which are available to be worked on to the - # available_activities list - # This section will not make much sense without the documentary flowcharts; - # see docs for explanation - # TODO: replace with digraph traversal - available_activities = [] - - # Activities with no prerequisites: cpp_wr, cpp_nrc, npp_ns_xtr - if "cpp_wr" in incomplete_activities - push!(available_activities, "cpp_wr") - end - - if "cpp_nrc" in incomplete_activities - push!(available_activities, "cpp_nrc") - end - - if "npp_ns_xtr" in incomplete_activities - push!(available_activities, "npp_ns_xtr") - end - - # Only start NPP safety xtr after all three of the above are done - if ( - ("cpp_wr" in completed_activities) && - ("cpp_nrc" in completed_activities) && - ("npp_ns_xtr" in completed_activities) && - ("npp_safety_xtr" in incomplete_activities) - ) - push!(available_activities, "npp_safety_xtr") - end - - # Only start cpp_dnd after all other activities are complete - if ( - !("npp_safety_xtr" in incomplete_activities) && - !("cpp_dnd" in completed_activities) - ) - push!(available_activities, "cpp_dnd") - end - - return available_activities -end - - -function get_available_activities_steam_TES(project_current_status) - all_activities = [key for key in keys(project_current_status)] - completed_activities = [] - for activity in all_activities - act_time_rem = project_current_status[activity]["time_rem"] - if (round(act_time_rem, digits = 3) == 0) - push!(completed_activities, activity) - end - end - - incomplete_activities = [ - all_activities[i] for i = 1:size(all_activities)[1] if - !(all_activities[i] in completed_activities) - ] - - # Add activities which are available to be worked on to the - # available_activities list - # This section will not make much sense without the documentary flowcharts; - # see docs for explanation - # TODO: replace with digraph traversal - available_activities = [] - - # Activities with no prerequisites: cpp_nrc, npp_ns_xtr - if "cpp_nrc" in incomplete_activities - push!(available_activities, "cpp_nrc") - end - - if "npp_ns_xtr" in incomplete_activities - push!(available_activities, "npp_ns_xtr") - end - - # NPP safety xtr prerequisite: NPP NS xtr - if ( - ("npp_ns_xtr" in completed_activities) && - ("npp_safety_xtr" in incomplete_activities) - ) - push!(available_activities, "npp_safety_xtr") - end - - # After NPP safety xtr is done, then do cpp_dnd and cpp_wr - if ( - ("npp_safety_xtr" in completed_activities) && - !("cpp_dnd" in completed_activities) - ) - push!(available_activities, "cpp_dnd") - end - - if ( - ("npp_safety_xtr" in completed_activities) && - !("cpp_wr" in completed_activities) - ) - push!(available_activities, "cpp_wr") - end - - return available_activities -end - - - -function allocate_funds_to_activities( - activity_schedule, - C2N_specs, - project_current_status, - unit_type_data, -) - all_activities = [key for key in keys(C2N_specs)] - - capex_activity_schedule = deepcopy(activity_schedule) - - for activity in all_activities - if C2N_specs[activity]["time_rem"] != 0 - linear_cost = ( - C2N_specs[activity]["cost_rem"] / - C2N_specs[activity]["time_rem"] - ) - else - linear_cost = 0 - end - capacity = unit_type_data[:capacity] - transform!( - capex_activity_schedule, - [Symbol(activity), :state_start, :state_end] => - ( - (activity, s_start, s_end) -> - activity .* linear_cost * capacity * 1000 - ) => Symbol(activity), - ) - end - - return capex_activity_schedule - -end - - -function convert_to_annual_capex_schedule(capex_activity_schedule) - start_pd = floor(Int64, minimum(capex_activity_schedule[!, :state_start])) - end_pd = ceil(Int64, maximum(capex_activity_schedule[!, :state_end])) - - # Get unscaled capex totals - transform!( - capex_activity_schedule, - [:cpp_dnd, :cpp_nrc, :cpp_wr, :npp_ns_xtr, :npp_safety_xtr] => - ((a, b, c, d, e) -> a .+ b .+ c .+ d .+ e) => :unscaled_capex, - ) - - capex_tl = DataFrame( - period = start_pd:end_pd, - total_capex = zeros(end_pd - start_pd + 1), - ) - - for i = start_pd:end_pd - subset = filter( - [:state_start, :state_end] => - ((s_start, s_end) -> !(s_end <= i) && !(s_start >= i + 1)), - capex_activity_schedule, - ) - for j = 1:size(subset)[1] - duration_in_i = ( - min(i + 1, subset[j, :state_end]) - - max(i, subset[j, :state_start]) - ) - capex_tl[i - start_pd + 1, :total_capex] += - (duration_in_i * subset[j, :unscaled_capex]) - end - end - - return capex_tl, capex_activity_schedule - -end - - - -end From 63994c63b5efefa86b77a268bf6019b45ebfa399 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:38:49 -0600 Subject: [PATCH 02/10] removing unused C2N specification file --- README.md | 2 - inputs/C2N_project_definitions.yml | 155 ----------------------------- src/agent_choice.jl | 16 +-- 3 files changed, 3 insertions(+), 170 deletions(-) delete mode 100644 inputs/C2N_project_definitions.yml diff --git a/README.md b/README.md index 52cfd43a..ac701310 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,6 @@ The input files required to run ABCE are as follows: * `agent_specifications.yml`: definitions for the agents: financial parameters, starting portfolios by unit type, and mandatory retirement dates for owned units - * `C2N_project_definitions.yml`: contains project activity cost and schedule information for coal-to-nuclear projects - * `demand_data.csv`: normalized peak demand levels per simulated year (used to scale the `peak_demand` parameter) * `unit_specs.yml`: construction and operations cost and parameter data for all possible unit types in the model diff --git a/inputs/C2N_project_definitions.yml b/inputs/C2N_project_definitions.yml deleted file mode 100644 index d8c4961e..00000000 --- a/inputs/C2N_project_definitions.yml +++ /dev/null @@ -1,155 +0,0 @@ -greenfield: - PWR: - npp_ns_xtr: - cost_rem: 0.404 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.548 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.007 - time_rem: 1.5 - cpp_nrc: - cost_rem: 0.000 - time_rem: 0.0 - cpp_dnd: - cost_rem: 0.040 - time_rem: 1.0 - HTGR: - npp_ns_xtr: - cost_rem: 0.328 - time_rem: 1 - npp_safety_xtr: - cost_rem: 0.644 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.004 - time_rem: 1.5 - cpp_nrc: - cost_rem: 0.0 - time_rem: 0.0 - cpp_dnd: - cost_rem: 0.024 - time_rem: 1.0 - SFR: - npp_ns_xtr: - cost_rem: 0.4716 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.4876 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.0059 - time_rem: 1.5 - cpp_nrc: - cost_rem: 0.0 - time_rem: 0.0 - cpp_dnd: - cost_rem: 0.0350 - time_rem: 1.0 - -electrical: - baseline: - npp_ns_xtr: - cost_rem: 0.397 - time_rem: 1 - npp_safety_xtr: - cost_rem: 0.539 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.009 - time_rem: 1.5 - cpp_nrc: - cost_rem: 0.0 - time_rem: 0.0 - cpp_dnd: - cost_rem: 0.054 - time_rem: 1.0 - conservative: - npp_ns_xtr: - cost_rem: 0.397 - time_rem: 1 - npp_safety_xtr: - cost_rem: 0.539 - time_rem: 4.0 - cpp_wr: - cost_rem: 0.016 - time_rem: 2.0 - cpp_nrc: - cost_rem: 0.0 - time_rem: 0.0 - cpp_dnd: - cost_rem: 0.048 - time_rem: 1.0 - - -steam_noTES: - baseline: - npp_ns_xtr: - cost_rem: 0.309 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.607 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.031 - time_rem: 2.0 - cpp_nrc: - cost_rem: 0.015 - time_rem: 2.0 - cpp_dnd: - cost_rem: 0.037 - time_rem: 1.0 - conservative: - npp_ns_xtr: - cost_rem: 0.305 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.599 - time_rem: 4.0 - cpp_wr: - cost_rem: 0.092 - time_rem: 2.5 - cpp_nrc: - cost_rem: 0.013 - time_rem: 2.5 - cpp_dnd: - cost_rem: 0.031 - time_rem: 1.0 - - -steam_TES: - baseline: - npp_ns_xtr: - cost_rem: 0.458 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.474 - time_rem: 3.0 - cpp_wr: - cost_rem: 0.009 - time_rem: 1.5 - cpp_nrc: - cost_rem: 0.006 - time_rem: 1.5 - cpp_dnd: - cost_rem: 0.053 - time_rem: 1.0 - conservative: - npp_ns_xtr: - cost_rem: 0.456 - time_rem: 1.0 - npp_safety_xtr: - cost_rem: 0.472 - time_rem: 4.0 - cpp_wr: - cost_rem: 0.014 - time_rem: 2.0 - cpp_nrc: - cost_rem: 0.015 - time_rem: 2.0 - cpp_dnd: - cost_rem: 0.042 - time_rem: 1.0 - - diff --git a/src/agent_choice.jl b/src/agent_choice.jl index 2542201d..de0bc2af 100755 --- a/src/agent_choice.jl +++ b/src/agent_choice.jl @@ -21,9 +21,7 @@ using Logging, JuMP, LinearAlgebra, DataFrames, CSV, YAML, SQLite, ArgParse # Include local ABCE functions modules include("ABCEfunctions.jl") include("dispatch.jl") -include("C2N_projects.jl") -using .ABCEfunctions, .Dispatch, .C2N - +using .ABCEfunctions, .Dispatch function set_up_run(CLI_args) @@ -42,18 +40,11 @@ function set_up_run(CLI_args) settings["simulation"]["scenario_name"], settings["file_paths"]["db_file"], ) - C2N_specs_file = joinpath( - CLI_args["inputs_path"], - "C2N_project_definitions.yml", - ) # Load the database db = ABCEfunctions.load_db(db_file) - # Load C2N specs data - C2N_specs = YAML.load_file(C2N_specs_file) - - return settings, db, C2N_specs + return settings, db end @@ -179,7 +170,7 @@ function run_agent_choice() CLI_args = ABCEfunctions.get_CL_args() # Read in data and the database from file - settings, db, C2N_specs = set_up_run(CLI_args) + settings, db = set_up_run(CLI_args) # Read in some raw data from the database agent_params, unit_specs = get_raw_db_data(db, CLI_args) @@ -278,7 +269,6 @@ function run_agent_choice() agent_params, db, CLI_args["current_pd"], - C2N_specs, dispatch_results, ) From 83c84d53a9e1b00a1d3beb0fa60ee290fa0dd4e2 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:39:29 -0600 Subject: [PATCH 03/10] removing unused functions for C2N data handling and references to unused C2N_specs dataframe --- src/ABCEfunctions.jl | 105 ------------------------------------------- 1 file changed, 105 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index c5ae8efc..9c4b2b1a 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -19,15 +19,8 @@ module ABCEfunctions using ArgParse, CPLEX, Requires, SQLite, DataFrames, CSV, JuMP, GLPK, Cbc, Logging, Tables, HiGHS, Statistics -# Use CPLEX if available -#function __init__() -# @require CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0" @eval using CPLEX -#end - include("./dispatch.jl") using .Dispatch -include("./C2N_projects.jl") -using .C2N include("./income_statement.jl") using .ISpf @@ -104,39 +97,6 @@ function set_up_local_paths(settings, abce_abs_path) end -function validate_project_data(db, settings, unit_specs, C2N_specs) - for unit_type in unit_specs[!, :unit_type] - if occursin("C2N", unit_type) - unit_type_data = - filter(:unit_type => x -> x == unit_type, unit_specs)[1, :] - - # Dummy data for schedule retrieval - lag = 0 - fc_pd = 70 - current_pd = 0 - - capex_tl, activity_schedule = project_C2N_capex( - db, - settings, - unit_type_data, - lag, - fc_pd, - current_pd, - C2N_specs, - ) - - unit_specs[ - (unit_specs.unit_type .== unit_type), - :construction_duration, - ] .= size(capex_tl)[1] - end - end - - return unit_specs - -end - - function set_forecast_period(unit_specs, num_lags) # Compute forecast period as the maximum possible project horizon, based # on the sum of maximum lead time, maximum construction duration, @@ -561,7 +521,6 @@ function set_up_project_alternatives( agent_params, db, current_pd, - C2N_specs, dispatch_results, ) PA_summaries = create_PA_summaries(settings, unit_specs, asset_counts) @@ -579,7 +538,6 @@ function set_up_project_alternatives( PA, fc_pd, current_pd, - C2N_specs, agent_id, agent_params, dispatch_results, @@ -704,7 +662,6 @@ function create_PA_subprojects( PA, fc_pd, current_pd, - C2N_specs, agent_id, agent_params, dispatch_results, @@ -727,7 +684,6 @@ function create_PA_subprojects( subproject, fc_pd, current_pd, - C2N_specs, agent_id, agent_params, dispatch_results, @@ -795,7 +751,6 @@ function forecast_subproject_financials( subproject, fc_pd, current_pd, - C2N_specs, agent_id, agent_params, dispatch_results, @@ -836,7 +791,6 @@ function forecast_subproject_financials( unit_type_data, subproject, current_pd, - C2N_specs, deepcopy(subproject_fs), ) @@ -896,7 +850,6 @@ function forecast_capex( unit_type_data, subproject, current_pd, - C2N_specs, fs_copy, ) capex_per_pd = ( @@ -918,64 +871,6 @@ function forecast_capex( end -function project_C2N_capex( - db, - settings, - unit_type_data, - lag, - fc_pd, - current_pd, - C2N_specs, -) - assumption = settings["simulation"]["C2N_assumption"] - # Set the project parameters - if occursin("C2N0", unit_type_data[:unit_type]) - conversion_type = "greenfield" - if occursin("PWR", unit_type_data[:unit_type]) - rxtr_type = "PWR" - elseif occursin("HTGR", unit_type_data[:unit_type]) - rxtr_type = "HTGR" - else - rxtr_type = "SFR" - end - - data = deepcopy(C2N_specs[conversion_type][rxtr_type]) - - else - if occursin("C2N1", unit_type_data[:unit_type]) - conversion_type = "electrical" - rxtr_type = "PWR" - elseif occursin("C2N2", unit_type_data[:unit_type]) - conversion_type = "steam_noTES" - rxtr_type = "HTGR" - else - conversion_type = "steam_TES" - rxtr_type = "SFR" - end - data = deepcopy(C2N_specs[conversion_type][assumption]) - end - - # Scale cost component data to match unit type specification data - for key in keys(data) - data[key]["cost_rem"] = data[key]["cost_rem"] * unit_type_data[:overnight_capital_cost] - end - - # Develop the C2N capex profile - capex_tl, activity_schedule = C2N.create_C2N_capex_timeline( - db, - conversion_type, - rxtr_type, - current_pd, - lag, - fc_pd, - data, - unit_type_data, - ) - - return capex_tl, activity_schedule -end - - function get_capex_end(fs_copy) # Find the end of the capex accumulation period capex_end = size(fs_copy)[1] From af4e42ebef09e7c18b9226b54238396de4d1fd11 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:51:07 -0600 Subject: [PATCH 04/10] removing deprecated conditional inclusion of CPLEX library --- src/dispatch.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/dispatch.jl b/src/dispatch.jl index 60fee74b..7d49f47f 100644 --- a/src/dispatch.jl +++ b/src/dispatch.jl @@ -20,12 +20,6 @@ module Dispatch using Requires, Logging, CSV, DataFrames, JuMP, GLPK, Cbc, XLSX, SQLite, HiGHS, CPLEX -# Initialize this module, with CPLEX as an optional library if available -#function __init__() -# @require CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0" @eval using CPLEX -#end - - function execute_dispatch_economic_projection( CLI_args, db, From bfc41148b972240a49cf5bede783a83ee9bec165 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:58:30 -0600 Subject: [PATCH 05/10] removing unused settings --- settings.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/settings.yml b/settings.yml index b0be5c7c..8dc4955d 100644 --- a/settings.yml +++ b/settings.yml @@ -71,7 +71,6 @@ constants: time_before_start: -1 distant_time: 9999 big_number: 999999 - hours_per_year: 8760 MW2kW: 1000 # converts MW to kW # File paths and filenames @@ -99,7 +98,6 @@ system: # Settings for demand projections demand: - total_forecast_horizon: 10 # Number of periods in the complete forecast horizon demand_visibility_horizon: 5 demand_projection_mode: exp_termrate # flat, exp_fitted, or exp_termrate demand_projection_window: 5 # Total number of periods used to project demand @@ -133,7 +131,6 @@ agent_opt: profit_lamda: 1.0 # Note: only the ratio between the lamdas matters credit_rating_lamda: 1.0 fin_metric_horizon: 4 - rcdr_target_delta: 0.005 icr_floor: 3.5 # weighted sum limit fcf_debt_floor: 0.16 # weighted sum limit re_debt_floor: 0.115 # weighted sum limit From e2d7c715a2b3073d2630f5b5ebdefff5529eb719 Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 15:58:47 -0600 Subject: [PATCH 06/10] removing unused logic --- src/ABCEfunctions.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 9c4b2b1a..1b3ea172 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -2659,11 +2659,8 @@ end function compute_credit_indicator_scores(settings, agent_fs) # Credit ratings indicators # Compute retained earnings-to-debt metric column - rcdr_target = settings["agent_opt"]["re_debt_floor"] + settings["agent_opt"]["rcdr_target_delta"] transform!(agent_fs, [:remaining_debt_principal, :retained_earnings] => ((debt, re) -> re ./ debt) => :RE_debt_ratio) - # Use excess RE over the amount needed to reach the target ratio to subsidize FCF - # Compute effective FCF: if FCF is zero but RE is greater than zero, use RE in place of FCF # Compute ICR metric column From 408c3fa230606025351a2cc944f3d714fd4c4def Mon Sep 17 00:00:00 2001 From: biegelk Date: Sun, 23 Nov 2025 16:02:55 -0600 Subject: [PATCH 07/10] removing one more setting --- settings.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.yml b/settings.yml index 8dc4955d..30fb382d 100644 --- a/settings.yml +++ b/settings.yml @@ -131,7 +131,6 @@ agent_opt: profit_lamda: 1.0 # Note: only the ratio between the lamdas matters credit_rating_lamda: 1.0 fin_metric_horizon: 4 - icr_floor: 3.5 # weighted sum limit fcf_debt_floor: 0.16 # weighted sum limit re_debt_floor: 0.115 # weighted sum limit icr_solo_floor: 0.0 # individual From 05cb7dc969304ef1f62ce700dd4b08188ddb5cf0 Mon Sep 17 00:00:00 2001 From: biegelk Date: Tue, 16 Dec 2025 18:06:17 -0600 Subject: [PATCH 08/10] removing overly pessimistic comment --- src/annual_dispatch.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/annual_dispatch.jl b/src/annual_dispatch.jl index d037d347..1fdd56ea 100644 --- a/src/annual_dispatch.jl +++ b/src/annual_dispatch.jl @@ -142,7 +142,7 @@ function run_true_annual_dispatch() total_demand = ABCEfunctions.get_demand_forecast(db, CL_args["current_pd"], 1, settings) system_portfolio_dict = get_year_portfolio(db, CL_args["current_pd"], unit_specs) - @info "Running the year-long dispatch simulation (this may take a little while)..." + @info "Running the year-long dispatch simulation..." # Run the year's UC/ED problem long_econ_results, dispatch_results = Dispatch.execute_dispatch_economic_projection( CL_args, From bba819371b2b88c3e8c85ac1c3d7c72d97e31f5d Mon Sep 17 00:00:00 2001 From: biegelk Date: Tue, 16 Dec 2025 18:07:17 -0600 Subject: [PATCH 09/10] bifurcating naming scheme for 365/366-repday scenarios and less-than-365 repday scenarios --- src/scenario_reduction.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/scenario_reduction.py b/src/scenario_reduction.py index ec99fe02..309292b7 100644 --- a/src/scenario_reduction.py +++ b/src/scenario_reduction.py @@ -188,10 +188,14 @@ def run_scenario_reduction(**kwargs): rep_day_input_data, columns=["index", "Day", "Probability"] ) + if num_scenarios in [365, 366]: + fname = f"repDays_{str(num_scenarios)}.csv" + else: + fname = f"bp_{setting['current_pd']}_fp_{setting['fc_pd']}_repDays{str(num_scenarios)}.csv" rep_day_input_df.to_csv( os.path.join( setting["data_output_path"], - f"bp_{setting['current_pd']}_fp_{setting['fc_pd']}_repDays_{str(num_scenarios)}.csv" + fname, ), index=False, ) From 454117d5e3e9d94691c600f510a5805e88da7cf3 Mon Sep 17 00:00:00 2001 From: biegelk Date: Tue, 16 Dec 2025 18:24:47 -0600 Subject: [PATCH 10/10] repdays file access cleanup --- src/dispatch.jl | 41 ++++++++++++++++++++++++++++------------- src/model.py | 32 ++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/dispatch.jl b/src/dispatch.jl index 7d49f47f..d47a89ad 100644 --- a/src/dispatch.jl +++ b/src/dispatch.jl @@ -128,7 +128,14 @@ function set_up_ts_data(settings, ts_data_dir, current_pd, fc_pd, downselection_ num_repdays=num_days ) - return ts_data + repdays_data = set_up_repdays_data( + ts_data_dir, + current_pd, + fc_pd, + num_repdays=num_days + ) + + return ts_data, repdays_data end @@ -165,7 +172,7 @@ function handle_annual_dispatch( @info "\n\nDISPATCH SIMULATION: YEAR $y" # Set up the timeseries data for this year - ts_data = set_up_ts_data( + ts_data, repdays_data = set_up_ts_data( settings, ts_data_dir, CLI_args["current_pd"], @@ -173,7 +180,7 @@ function handle_annual_dispatch( downselection_mode ) - y_repdays = deepcopy(ts_data[:repdays_data]) + y_repdays = deepcopy(repdays_data) y_repdays[!, :y] .= y if all_repdays == nothing @@ -239,6 +246,21 @@ function load_ts_data(ts_file_dir, base_pd, fc_pd; num_repdays=nothing) nspin_data = CSV.read(joinpath(ts_file_dir, "timeseries_nspin_hourly.csv"), DataFrame) + ts_data = Dict( + :load_data => load_data, + :wind_data => wind_data, + :solar_data => solar_data, + :reg_data => reg_data, + :spin_data => spin_data, + :nspin_data => nspin_data, +# :repdays_data => repdays_data, + ) + + return ts_data +end + + +function set_up_repdays_data(ts_file_dir, base_pd, fc_pd; num_repdays=nothing) if num_repdays == nothing repdays_data = nothing elseif num_repdays == 365 @@ -265,19 +287,12 @@ function load_ts_data(ts_file_dir, base_pd, fc_pd; num_repdays=nothing) ) end - ts_data = Dict( - :load_data => load_data, - :wind_data => wind_data, - :solar_data => solar_data, - :reg_data => reg_data, - :spin_data => spin_data, - :nspin_data => nspin_data, - :repdays_data => repdays_data, - ) - return ts_data + + return repdays_data end + function set_up_grc_results_df() all_grc_results = DataFrame( y = Int[], diff --git a/src/model.py b/src/model.py index a90d2ae7..3260cbd5 100644 --- a/src/model.py +++ b/src/model.py @@ -554,18 +554,26 @@ def step(self, demo=False): logging.info(f"Removing agent {agent_id} from the simulation due to a size-zero portfolio.") self.schedule.remove(self.agents[agent_id]) - # Compute the scenario reduction results for the dispatch forecast - # horizon, starting with this year - fc_horiz = self.settings["dispatch"]["num_dispatch_years"] - for fc_y in range(self.current_pd, self.current_pd + fc_horiz): - ABCE.execute_scenario_reduction( - self.args, - self.db, - self.current_pd, - fc_y, - self.settings, - self.unit_specs - ) + # If using 365 or 366 representative days, produce only a single + # reference file + # If using less than 365 representative days, compute the scenario + # reduction results for the dispatch forecast horizon, starting + # with the current year + if self.settings["dispatch"]["num_repdays"] < 365: + fc_horiz = self.settings["dispatch"]["num_dispatch_years"] + else: + fc_horiz = 1 + + if (self.current_pd == 0) or (self.settings["dispatch"]["num_repdays"] < 365): + for fc_y in range(self.current_pd, self.current_pd + fc_horiz): + ABCE.execute_scenario_reduction( + self.args, + self.db, + self.current_pd, + fc_y, + self.settings, + self.unit_specs + ) # Close the database to avoid access problems in the Julia scope self.db.commit()