From 5407e52eb16ff7a7d6cba6518806c9c451f191c2 Mon Sep 17 00:00:00 2001 From: biegelk Date: Wed, 17 Dec 2025 11:56:02 -0600 Subject: [PATCH 1/8] remove addition of fictitious units to system_portfolios --- src/ABCEfunctions.jl | 26 -------------------------- src/agent_choice.jl | 8 +------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 1b3ea172..68928b90 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -381,32 +381,6 @@ function get_demand_forecast(db, pd, fc_pd, settings) end -function fill_portfolios_missing_units(current_pd, system_portfolios, unit_specs) - # Ensure that at least 1 unit of every available type in unit_specs is - # represented in every year of the system portfolio, by adding 1 instance - # of each missing unit type. - # Units are only added starting at the earliest year in which construction - # of such a unit could have been completed. - - # Retrieve only the necessary unit_specs columns - brief_unit_specs = unit_specs[!, [:unit_type, :capacity, :capacity_factor]] - - for y = minimum(keys(system_portfolios)):maximum(keys(system_portfolios)) - for unit_type_specs in eachrow(unit_specs) - if !in(unit_type_specs.unit_type, system_portfolios[y][!, :unit_type]) - if y - current_pd >= unit_type_specs.construction_duration - # Target dataframe columns: - # unit_type, num_units, real, auto_expansion, capacity, capacity_factor, total_capacity - push!(system_portfolios[y], (unit_type_specs.unit_type, 1, 0, 0, unit_type_specs.capacity, unit_type_specs.capacity_factor, unit_type_specs.capacity)) - end - end - end - end - - return system_portfolios -end - - function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_portfolios, agent_params, current_pd, settings, demand_forecast) end_year = (current_pd + convert(Int64, settings["dispatch"]["num_dispatch_years"]) - 1) diff --git a/src/agent_choice.jl b/src/agent_choice.jl index de0bc2af..61d8d32e 100755 --- a/src/agent_choice.jl +++ b/src/agent_choice.jl @@ -204,12 +204,6 @@ function run_agent_choice() unit_specs, ) - adj_system_portfolios = ABCEfunctions.fill_portfolios_missing_units( - CLI_args["current_pd"], - deepcopy(system_portfolios), - unit_specs, - ) - # Retrieve the year-by-year projected portfolio for the current agent agent_portfolios = ABCEfunctions.get_portfolio_forecast( db, @@ -229,7 +223,7 @@ function run_agent_choice() adj_system_portfolios = ABCEfunctions.forecast_balance_of_market_investment( db, - adj_system_portfolios, + system_portfolios, agent_portfolios, agent_params, CLI_args["current_pd"], From e55d3c29dc74ef786915152654716a595e413365 Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 13:06:53 -0600 Subject: [PATCH 2/8] removing buggy implementation of PRM correction fade-in --- src/ABCEfunctions.jl | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 68928b90..353deab9 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -409,14 +409,7 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ if (c_y / d_y < (1 + prm)) && (y >= current_pd + delay) # Compute escalation factor - # Linearly increases to cover the difference between cy/dy and prm, - # starting at (b)% of the difference and increasing to the - # full difference over (n) years - n = 4 - b = 0.5 - j = min(n, y - delay - current_pd) - s = b + (1 - b) / n * j - esc = c_y / d_y + ((1 + prm) - c_y / d_y) * s * k + esc = 1 + prm * d_y / c_y ae_y = filter(:auto_expansion => auto -> auto == 1.0, adj_system_portfolios[y]) ae_c_y = sum(ae_y[!, :total_derated_capacity]) From 80888f712e531428b607d0c8fcd83d2aa4b6a99d Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 13:17:16 -0600 Subject: [PATCH 3/8] correcting escalation calculation --- src/ABCEfunctions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 353deab9..0a7aa3b3 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -409,7 +409,7 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ if (c_y / d_y < (1 + prm)) && (y >= current_pd + delay) # Compute escalation factor - esc = 1 + prm * d_y / c_y + esc = (1 + prm) * d_y / c_y ae_y = filter(:auto_expansion => auto -> auto == 1.0, adj_system_portfolios[y]) ae_c_y = sum(ae_y[!, :total_derated_capacity]) From 7dd719f653fa1039669bb027948a9b4cd75d844f Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 13:38:27 -0600 Subject: [PATCH 4/8] adding comments and making some transforms easier to read --- src/ABCEfunctions.jl | 47 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 0a7aa3b3..2420434f 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -399,8 +399,22 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ adj_system_portfolios[y] = coalesce.(outerjoin(adj_system_portfolios[y], apf, on = :unit_type), 0) - transform!(adj_system_portfolios[y], [:total_capacity, :capacity_factor] => ((cap, cf) -> cap .* cf) => :total_derated_capacity) - transform!(adj_system_portfolios[y], [:total_capacity, :agent_total_capacity] => ((total_cap, agent_cap) -> (total_cap) ./ total_cap) => :my) + # Calculate derated capacity for each unit type via CF + transform!( + adj_system_portfolios[y], + [:total_capacity, :capacity_factor] + => ((cap, cf) -> cap .* cf) + => :total_derated_capacity + ) + + # Determine the percentage of unit type capacity owned by the + # current agent + transform!( + adj_system_portfolios[y], + [:total_capacity, :agent_total_capacity] + => ((total_cap, agent_cap) -> (agent_cap) ./ total_cap) + => :agent_cap_frac + ) d_y = filter(:period => x -> x == y, demand_forecast)[1, :total_demand] c_y = sum(adj_system_portfolios[y][!, :total_derated_capacity]) @@ -411,17 +425,40 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ # Compute escalation factor esc = (1 + prm) * d_y / c_y + # Allow determination of % of year y's capacity attributable to + # unit types with auto-expansion enabled ae_y = filter(:auto_expansion => auto -> auto == 1.0, adj_system_portfolios[y]) ae_c_y = sum(ae_y[!, :total_derated_capacity]) - transform!(adj_system_portfolios[y], [:total_derated_capacity, :my, :capacity_factor, :auto_expansion] => ((c_iy, my, cf, auto) -> c_iy .+ auto .* (my .* (c_iy ./ ae_c_y) .* (d_y .* esc .- c_y))) => :total_esc_der_capacity) + # Escalate derated capacities owned by balance-of-market to meet PRM, + # based on the year's derated capacity mix + transform!( + adj_system_portfolios[y], + [:total_derated_capacity, :agent_cap_frac, :auto_expansion] + => ((c_iy, acap, auto) + -> c_iy .+ auto .* ((1 .- acap) .* (c_iy ./ ae_c_y) .* (d_y .* esc .- c_y))) + => :total_esc_der_capacity + ) + println(adj_system_portfolios[y]) else adj_system_portfolios[y][!, :total_esc_der_capacity] .= adj_system_portfolios[y][!, :total_derated_capacity] end - transform!(adj_system_portfolios[y], [:total_esc_der_capacity, :capacity_factor] => ((c_iy, cf) -> c_iy ./ cf) => :total_esc_capacity) + # Convert escalate derated capacity back into nameplate capacity via CF + transform!( + adj_system_portfolios[y], + [:total_esc_der_capacity, :capacity_factor] + => ((c_iy, cf) -> c_iy ./ cf) + => :total_esc_capacity + ) - transform!(adj_system_portfolios[y], [:total_esc_capacity, :capacity] => ((total_esc_cap, cap) -> ceil.(total_esc_cap ./cap)) => :esc_num_units) + # Convert the escalated total nameplate capacities to unit counts + transform!( + adj_system_portfolios[y], + [:total_esc_capacity, :capacity] + => ((total_esc_cap, cap) -> ceil.(total_esc_cap ./cap)) + => :esc_num_units + ) end From d33a806b05c75975cfe9d073c18ace31e8d3a7be Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 14:12:15 -0600 Subject: [PATCH 5/8] correcting derated capacity autoexpansion escalation calculation --- src/ABCEfunctions.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 2420434f..22b4f0a8 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -422,21 +422,23 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ k = agent_params[1, :k] if (c_y / d_y < (1 + prm)) && (y >= current_pd + delay) - # Compute escalation factor - esc = (1 + prm) * d_y / c_y - # Allow determination of % of year y's capacity attributable to # unit types with auto-expansion enabled ae_y = filter(:auto_expansion => auto -> auto == 1.0, adj_system_portfolios[y]) ae_c_y = sum(ae_y[!, :total_derated_capacity]) + # Compute escalation factor: autoexpandable unit types need to + # make up capacity deficit such that esc * ae_c_y / d_y = prm + # (ae_c_y <= c_y because ae is a subset of unit_types) + esc = prm * d_y / ae_c_y + # Escalate derated capacities owned by balance-of-market to meet PRM, # based on the year's derated capacity mix transform!( adj_system_portfolios[y], [:total_derated_capacity, :agent_cap_frac, :auto_expansion] - => ((c_iy, acap, auto) - -> c_iy .+ auto .* ((1 .- acap) .* (c_iy ./ ae_c_y) .* (d_y .* esc .- c_y))) + => ((c_iy, acap, auto) + -> c_iy .+ auto .* c_iy .* esc .* (1 .- acap)) => :total_esc_der_capacity ) println(adj_system_portfolios[y]) From 8bbaae85987ceb66ae13305239c9c04424210ed4 Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 14:16:36 -0600 Subject: [PATCH 6/8] removing magic number 3-year delay in forecast_balance_of_market_investment --- src/ABCEfunctions.jl | 4 ++-- src/agent_choice.jl | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 22b4f0a8..26a8049c 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -381,7 +381,7 @@ function get_demand_forecast(db, pd, fc_pd, settings) end -function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_portfolios, agent_params, current_pd, settings, demand_forecast) +function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_portfolios, agent_params, current_pd, settings, demand_forecast, unit_specs) end_year = (current_pd + convert(Int64, settings["dispatch"]["num_dispatch_years"]) - 1) prm = DBInterface.execute( @@ -391,7 +391,7 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ prm = prm[1, :value] # Shortest possible construction period - delay = 3 + delay = minimum(unit_specs[!, :construction_duration]) for y = current_pd:end_year apf = select(agent_portfolios[y], [:unit_type, :total_capacity]) diff --git a/src/agent_choice.jl b/src/agent_choice.jl index 61d8d32e..b0161f70 100755 --- a/src/agent_choice.jl +++ b/src/agent_choice.jl @@ -229,6 +229,7 @@ function run_agent_choice() CLI_args["current_pd"], settings, demand_forecast, + unit_specs, ) From f382c0373234d7ccf68b4bfa90c9ceccfda34cb8 Mon Sep 17 00:00:00 2001 From: biegelk Date: Thu, 18 Dec 2025 14:27:14 -0600 Subject: [PATCH 7/8] passing single values instead of entire dataframes where possible --- src/ABCEfunctions.jl | 8 +++----- src/agent_choice.jl | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 26a8049c..4774f5d2 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -381,8 +381,8 @@ function get_demand_forecast(db, pd, fc_pd, settings) end -function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_portfolios, agent_params, current_pd, settings, demand_forecast, unit_specs) - end_year = (current_pd + convert(Int64, settings["dispatch"]["num_dispatch_years"]) - 1) +function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_portfolios, k, current_pd, num_dispatch_years, demand_forecast, unit_specs) + end_year = (current_pd + convert(Int64, num_dispatch_years) - 1) prm = DBInterface.execute( db, @@ -419,8 +419,6 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ d_y = filter(:period => x -> x == y, demand_forecast)[1, :total_demand] c_y = sum(adj_system_portfolios[y][!, :total_derated_capacity]) - k = agent_params[1, :k] - if (c_y / d_y < (1 + prm)) && (y >= current_pd + delay) # Allow determination of % of year y's capacity attributable to # unit types with auto-expansion enabled @@ -438,7 +436,7 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ adj_system_portfolios[y], [:total_derated_capacity, :agent_cap_frac, :auto_expansion] => ((c_iy, acap, auto) - -> c_iy .+ auto .* c_iy .* esc .* (1 .- acap)) + -> c_iy .+ auto .* c_iy .* esc .* (1 .- acap) .* k) => :total_esc_der_capacity ) println(adj_system_portfolios[y]) diff --git a/src/agent_choice.jl b/src/agent_choice.jl index b0161f70..ea937b9c 100755 --- a/src/agent_choice.jl +++ b/src/agent_choice.jl @@ -225,9 +225,9 @@ function run_agent_choice() db, system_portfolios, agent_portfolios, - agent_params, + agent_params[1, :k], CLI_args["current_pd"], - settings, + settings["dispatch"]["num_dispatch_years"], demand_forecast, unit_specs, ) From 40898e093f583b5037df551ad218d1b95b8d5f05 Mon Sep 17 00:00:00 2001 From: biegelk Date: Fri, 19 Dec 2025 08:38:36 -0600 Subject: [PATCH 8/8] removing println --- src/ABCEfunctions.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ABCEfunctions.jl b/src/ABCEfunctions.jl index 4774f5d2..e122f9d9 100644 --- a/src/ABCEfunctions.jl +++ b/src/ABCEfunctions.jl @@ -439,7 +439,6 @@ function forecast_balance_of_market_investment(db, adj_system_portfolios, agent_ -> c_iy .+ auto .* c_iy .* esc .* (1 .- acap) .* k) => :total_esc_der_capacity ) - println(adj_system_portfolios[y]) else adj_system_portfolios[y][!, :total_esc_der_capacity] .= adj_system_portfolios[y][!, :total_derated_capacity] end