From 3210afc0882b6c9943a0253ab9f68ce6bdaac40c Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:49:42 +0200 Subject: [PATCH 01/40] Initial commit --- src/ECModel.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ECModel.jl b/src/ECModel.jl index 9448f7b..8c3cf4f 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -852,6 +852,15 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end +function business_plan_dataframe(ECModel::AbstractEC,users,profit_distribution::Dict) + business_plan = split_financial_terms(ECModel) + return business_plan +end + +function business_plan_plot(ECModel:AbstractEC,general::DataFrame) + print +end + function EnergyCommunity.split_financial_terms(ECModel::AbstractEC, profit_distribution::Dict) return split_financial_terms( ECModel, From 66cebd28cc7c6d680c10a8f1849cc7cdaba2e636 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 12 Jul 2023 18:08:34 +0200 Subject: [PATCH 02/40] First dataframe function --- src/ECModel.jl | 59 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 8c3cf4f..6a96617 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -842,6 +842,7 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) NPV=NPV, CAPEX=CAPEX, OPEX=OPEX, + OEM = Ann_Maintenance, REP=Ann_Replacement, RV=Ann_Recovery, REWARD=Ann_reward, @@ -852,15 +853,61 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end -function business_plan_dataframe(ECModel::AbstractEC,users,profit_distribution::Dict) - business_plan = split_financial_terms(ECModel) - return business_plan -end +function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing,user_plot=nothing) + if isnothing(profit_distribution) + user_set = get_user_set(ECModel) + profit_distribution = JuMP.Containers.DenseAxisArray( + fill(0.0, length(user_set)), + user_set, + ) + end -function business_plan_plot(ECModel:AbstractEC,general::DataFrame) - print + # Create a vector of years from 2023 to (2023 + project_lifetime) + gen_data = ECModel.gen_data + project_lifetime = field(gen_data, "project_lifetime") + years = collect(2023:(2023 + project_lifetime)) + + business_plan = split_financial_terms(ECModel,profit_distribution) + + # Create an empty DataFrame + df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], RV = Float64[], PEAK = Float64[]) + for i in 1:axes(years) + if isnothing(user_plot) + if i == 1 + year_y = years[i] + CAPEX = sum(business_plan.CAPEX) + push!(df_business, (year_y, CAPEX)) + elseif i == axes(years) + year_y = years[i] + OEM = sum(business_plan.OEM) + EN_SELL = sum(business_plant.EN_SELL) + PEAK = sum(business_plant.PEAK) + RV = sum(business_plan.RV) + #revaward not sure if correct + #energy consumed not sure if correct + push!(df_business, (year_y, OEM, PEAK, EN_SELL, RV)) + + elseif i == "maintenance" + #add when i == year of maintenance + else + year_y = years[i] + OEM = sum(business_plan.OEM) + PEAK = sum(business_plant.PEAK) + EN_SELL = sum(business_plant.EN_SELL) + #revaward not sure if correct + #energy consumed not sure if correct + push!(df_business, (year_y, CAPEX, OEM, PEAK, EN_SELL)) + end + end + end + + return df_business end +#function business_plan_plot(ECModel:AbstractEC,general::DataFrame) + # print +#end + function EnergyCommunity.split_financial_terms(ECModel::AbstractEC, profit_distribution::Dict) return split_financial_terms( ECModel, From 8722f556b1670343e3127b8262848cba70c74da0 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:10:11 +0200 Subject: [PATCH 03/40] New yearly function split financial --- src/ECModel.jl | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/ECModel.jl b/src/ECModel.jl index 6a96617..aa9ec9d 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -853,6 +853,125 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end +function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) + if isnothing(profit_distribution) + user_set = get_user_set(ECModel) + profit_distribution = JuMP.Containers.DenseAxisArray( + fill(0.0, length(user_set)), + user_set, + ) + end + @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED + + gen_data = ECModel.gen_data + + project_lifetime = field(gen_data, "project_lifetime") + + get_value = ((dense_axis, element) -> (if (element in axes(dense_axis)[1]) dense_axis[element] else 0.0 end)) + + user_set = axes(profit_distribution)[1] + year_set = 1:project_lifetime + + ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] + + # Investment costs + CAPEX = JuMP.Containers.DenseAxisArray( + [get_value(ECModel.results[:CAPEX_tot_us], u) for u in user_set] + , user_set + ) + # Maintenance costs + Ann_Maintenance = JuMP.Containers.DenseAxisArray( + [ + [get_value(ECModel.results[:C_OEM_tot_us], u) * ann_factor[y] for y in year_set] + + for u in user_set] + , user_set + ) + # Replacement costs + Ann_Replacement = JuMP.Containers.DenseAxisArray( + [ + [get_value(ECModel.results[:C_REP_tot_us][y, :], u) / ((1 + field(gen_data, "d_rate"))^y) + for y in year_set + ] + for u in user_set + ] + , user_set + ) + # Recovery value + Ann_Recovery = JuMP.Containers.DenseAxisArray( + [ + [get_value(ECModel.results[:R_RV_tot_us][y, :], u) / ((1 + field(gen_data, "d_rate"))^y) + for y in year_set + ] + for u in user_set + ] + , user_set + ) + + # Peak energy charges + Ann_peak_charges = JuMP.Containers.DenseAxisArray( + [ + [get_value(ECModel.results[:C_Peak_tot_us], u) * ann_factor[y] for y in year_set] + for u in user_set] + , user_set + ) + + # Get revenes by selling energy and costs by buying or consuming energy + zero_if_negative = x->((x>=0) ? x : 0.0) + Ann_energy_revenues = JuMP.Containers.DenseAxisArray( + [ + if (u in axes(ECModel.results[:R_Energy_us])[1]) + sum(zero_if_negative.(ECModel.results[:R_Energy_us][u, :])) * ann_factor + else + 0.0 + end + for u in user_set + ], + user_set + ) + Ann_energy_costs = JuMP.Containers.DenseAxisArray( + [ + if (u in axes(ECModel.results[:R_Energy_us])[1]) + sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) * ann_factor + else + 0.0 + end + for u in user_set + ], + user_set + ) + Ann_net_energy_costs = Ann_energy_costs .- Ann_energy_revenues + + # Total OPEX costs + OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_net_energy_costs + + # get NPV given the reward allocation + NPV = profit_distribution + + # Total reward + Ann_reward = JuMP.Containers.DenseAxisArray( + [ + NPV[u] + (CAPEX[u] + OPEX[u] + Ann_Replacement[u] - Ann_Recovery[u]) + for u in user_set + ], + user_set + ) + + return ( + NPV=NPV, + CAPEX=CAPEX, + OPEX=OPEX, + OEM = Ann_Maintenance, + REP=Ann_Replacement, + RV=Ann_Recovery, + REWARD=Ann_reward, + PEAK=Ann_peak_charges, + EN_SELL=Ann_energy_revenues, + EN_CONS=Ann_energy_costs, + EN_NET=Ann_net_energy_costs, + ) +end + function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing,user_plot=nothing) if isnothing(profit_distribution) user_set = get_user_set(ECModel) From eb9d12f5ea209df638b2249408764abb090fd7e6 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:26:53 +0200 Subject: [PATCH 04/40] Fixing yearly financial terms --- src/ECModel.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index aa9ec9d..2f3e5ce 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -920,23 +920,23 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n zero_if_negative = x->((x>=0) ? x : 0.0) Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [ - if (u in axes(ECModel.results[:R_Energy_us])[1]) - sum(zero_if_negative.(ECModel.results[:R_Energy_us][u, :])) * ann_factor + [if (u in axes(ECModel.results[:R_Energy_us])[1]) + zero_if_negative.(ECModel.results[:R_Energy_us][u,y]) * ann_factor[y] for y in year_set else 0.0 end - for u in user_set + ] for u in user_set ], user_set ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( [ - if (u in axes(ECModel.results[:R_Energy_us])[1]) - sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) * ann_factor + [if (u in axes(ECModel.results[:R_Energy_us])[1]) + zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :])) * ann_factor[y] for y in year_set else 0.0 end - for u in user_set + ] for u in user_set ], user_set ) From d89b4b2a354a1c6a071cb7fb5fd7bc56776194eb Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:35:25 +0200 Subject: [PATCH 05/40] Fixing function but problem with ann_energy-costs --- src/ECModel.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 2f3e5ce..8ba4ec3 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -921,7 +921,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [ [if (u in axes(ECModel.results[:R_Energy_us])[1]) - zero_if_negative.(ECModel.results[:R_Energy_us][u,y]) * ann_factor[y] for y in year_set + zero_if_negative.(ECModel.results[:R_Energy_us][u,:]) * ann_factor[y] for y in year_set else 0.0 end @@ -931,12 +931,12 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( [ - [if (u in axes(ECModel.results[:R_Energy_us])[1]) - zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :])) * ann_factor[y] for y in year_set + if (u in axes(ECModel.results[:R_Energy_us])[1]) + [zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :])) * ann_factor[y] for y in year_set] else 0.0 end - ] for u in user_set + for u in user_set ], user_set ) From 795d6c83123831df3de96ab3e39fc6fe23aa80c1 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 26 Jul 2023 15:51:44 +0200 Subject: [PATCH 06/40] Minor fix --- Project.toml | 9 +++++---- src/ECModel.jl | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Project.toml b/Project.toml index 09c5815..b5b1416 100644 --- a/Project.toml +++ b/Project.toml @@ -9,28 +9,29 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" -TheoryOfGames = "eb50afb4-6f20-4b37-9b66-473e668300bf" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LayeredLayouts = "f4a74d36-062a-4d48-97cd-1356bad1de4e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" SankeyPlots = "8fd88ec8-d95c-41fc-b299-05f2225f2cc5" +TheoryOfGames = "eb50afb4-6f20-4b37-9b66-473e668300bf" XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" [compat] -julia = "1" CSV = "0.10" DataFrames = "1" ExportAll = "0.1" FileIO = "1" Formatting = "0.4" -TheoryOfGames = "0.1" JuMP = "1" LayeredLayouts = "0.2" MathOptInterface = "1.0" Plots = "1" SankeyPlots = "0.2.2" +TheoryOfGames = "0.1" XLSX = "0.9" -YAML = "0.4" \ No newline at end of file +YAML = "0.4" +julia = "1" diff --git a/src/ECModel.jl b/src/ECModel.jl index 8ba4ec3..9bf8027 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -876,13 +876,20 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( - [get_value(ECModel.results[:CAPEX_tot_us], u) for u in user_set] - , user_set + for y in year_set + if y == 1 + [get_value(ECModel.results[:CAPEX_tot_us], u) for u in user_set] + else + 0.0 + end + end + , user_set ) # Maintenance costs Ann_Maintenance = JuMP.Containers.DenseAxisArray( [ - [get_value(ECModel.results[:C_OEM_tot_us], u) * ann_factor[y] for y in year_set] + [get_value(ECModel.results[:C_OEM_tot_us], u) * ann_factor[y] + for y in year_set] for u in user_set] , user_set @@ -920,19 +927,19 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n zero_if_negative = x->((x>=0) ? x : 0.0) Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [ - [if (u in axes(ECModel.results[:R_Energy_us])[1]) - zero_if_negative.(ECModel.results[:R_Energy_us][u,:]) * ann_factor[y] for y in year_set + if (u in axes(ECModel.results[:R_Energy_us])[1]) + [sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) * ann_factor[y] for y in year_set] else 0.0 end - ] for u in user_set + for u in user_set ], user_set ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( [ if (u in axes(ECModel.results[:R_Energy_us])[1]) - [zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :])) * ann_factor[y] for y in year_set] + [sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) * ann_factor[y] for y in year_set] else 0.0 end @@ -986,7 +993,7 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing project_lifetime = field(gen_data, "project_lifetime") years = collect(2023:(2023 + project_lifetime)) - business_plan = split_financial_terms(ECModel,profit_distribution) + business_plan = split_yearly_financial_terms(ECModel,profit_distribution) # Create an empty DataFrame df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], RV = Float64[], PEAK = Float64[]) From f65375c5a3084729d44cd0376b960e8fb6a6c14b Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:06:23 +0200 Subject: [PATCH 07/40] Minor fix and DenseAxisArray 2-D --- src/ECModel.jl | 90 ++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 9bf8027..2f7cffc 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -853,7 +853,10 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end -function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) +function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=nothing, profit_distribution=nothing) + if isnothing(user_set_financial) + user_set_financial = [EC_CODE; get_user_set(ECModel)] + end if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( @@ -870,82 +873,60 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n get_value = ((dense_axis, element) -> (if (element in axes(dense_axis)[1]) dense_axis[element] else 0.0 end)) user_set = axes(profit_distribution)[1] - year_set = 1:project_lifetime + year_set = 0:project_lifetime ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( - for y in year_set - if y == 1 - [get_value(ECModel.results[:CAPEX_tot_us], u) for u in user_set] - else - 0.0 - end - end - , user_set + [(y==0) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u) for u in setdiff(user_set_financial, [EC_CODE])]) : 0.0 + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) # Maintenance costs Ann_Maintenance = JuMP.Containers.DenseAxisArray( - [ - [get_value(ECModel.results[:C_OEM_tot_us], u) * ann_factor[y] - for y in year_set] - - for u in user_set] - , user_set + [get_value(ECModel.results[:C_OEM_tot_us], u) + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) # Replacement costs Ann_Replacement = JuMP.Containers.DenseAxisArray( - [ - [get_value(ECModel.results[:C_REP_tot_us][y, :], u) / ((1 + field(gen_data, "d_rate"))^y) - for y in year_set + [get_value(ECModel.results[:C_REP_tot_us][y, :], u) + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) ] - for u in user_set - ] - , user_set - ) + , year_set, user_set + ) # Recovery value Ann_Recovery = JuMP.Containers.DenseAxisArray( - [ - [get_value(ECModel.results[:R_RV_tot_us][y, :], u) / ((1 + field(gen_data, "d_rate"))^y) - for y in year_set + [get_value(ECModel.results[:R_RV_tot_us][y, :], u) + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) ] - for u in user_set - ] - , user_set + , year_set, user_set ) # Peak energy charges Ann_peak_charges = JuMP.Containers.DenseAxisArray( - [ - [get_value(ECModel.results[:C_Peak_tot_us], u) * ann_factor[y] for y in year_set] - for u in user_set] - , user_set + [get_value(ECModel.results[:C_Peak_tot_us], u) + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) # Get revenes by selling energy and costs by buying or consuming energy zero_if_negative = x->((x>=0) ? x : 0.0) Ann_energy_revenues = JuMP.Containers.DenseAxisArray( - [ - if (u in axes(ECModel.results[:R_Energy_us])[1]) - [sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) * ann_factor[y] for y in year_set] - else - 0.0 - end - for u in user_set - ], - user_set + [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( - [ - if (u in axes(ECModel.results[:R_Energy_us])[1]) - [sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) * ann_factor[y] for y in year_set] - else - 0.0 - end - for u in user_set - ], - user_set + [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) : 0.0 + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) Ann_net_energy_costs = Ann_energy_costs .- Ann_energy_revenues @@ -953,15 +934,16 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_net_energy_costs # get NPV given the reward allocation + #Check hot to proceed NPV = profit_distribution # Total reward Ann_reward = JuMP.Containers.DenseAxisArray( [ NPV[u] + (CAPEX[u] + OPEX[u] + Ann_Replacement[u] - Ann_Recovery[u]) - for u in user_set - ], - user_set + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set ) return ( From c20b7cee3f63b17a54f00bb1773668244fe602de Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 26 Jul 2023 18:10:58 +0200 Subject: [PATCH 08/40] Final fix of split_yearly_financial function --- src/ECModel.jl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 2f7cffc..c9896b4 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -860,8 +860,10 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( - fill(0.0, length(user_set)), - user_set, + [ 0.0 + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set, ) end @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED @@ -938,13 +940,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no NPV = profit_distribution # Total reward - Ann_reward = JuMP.Containers.DenseAxisArray( - [ - NPV[u] + (CAPEX[u] + OPEX[u] + Ann_Replacement[u] - Ann_Recovery[u]) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] - , year_set, user_set - ) + Ann_reward = NPV .+ CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery return ( NPV=NPV, From 5d14cd3815edfea5ebea0467e0f8cb0bd253b74d Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:08:02 +0200 Subject: [PATCH 09/40] Fix but issues with get_value function --- src/ECModel.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index c9896b4..286afa3 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -854,6 +854,14 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) end function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=nothing, profit_distribution=nothing) + gen_data = ECModel.gen_data + + project_lifetime = field(gen_data, "project_lifetime") + + get_value = ((dense_axis, element) -> (if (element in axes(dense_axis)[1]) dense_axis[element] else 0.0 end)) + + year_set = 1:project_lifetime + if isnothing(user_set_financial) user_set_financial = [EC_CODE; get_user_set(ECModel)] end @@ -867,16 +875,8 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no ) end @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED - - gen_data = ECModel.gen_data - - project_lifetime = field(gen_data, "project_lifetime") - - get_value = ((dense_axis, element) -> (if (element in axes(dense_axis)[1]) dense_axis[element] else 0.0 end)) user_set = axes(profit_distribution)[1] - year_set = 0:project_lifetime - ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] # Investment costs From 755cd6b2f9d8a7706bbab4b80d374107741504a6 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:57:51 +0200 Subject: [PATCH 10/40] Final split_yearly function --- src/ECModel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 286afa3..113262d 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -876,12 +876,12 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no end @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED - user_set = axes(profit_distribution)[1] + user_set = axes(profit_distribution)[2] ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( - [(y==0) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u) for u in setdiff(user_set_financial, [EC_CODE])]) : 0.0 + [(y==1) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) ] , year_set, user_set From 1bccd4c24a6b1bbc3c19ce3e4da349c3734d57f5 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:21:16 +0200 Subject: [PATCH 11/40] business_plan_plot function --- src/ECModel.jl | 88 +++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 113262d..f2b52fe 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -957,60 +957,80 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no ) end -function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing,user_plot=nothing) +function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) + if isnothing(user_set_financial) + user_set_financial = [EC_CODE; get_user_set(ECModel)] + end if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( - fill(0.0, length(user_set)), - user_set, + [ 0.0 + for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + ] + , year_set, user_set, ) end # Create a vector of years from 2023 to (2023 + project_lifetime) gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") - years = collect(2023:(2023 + project_lifetime)) + years = 1:project_lifetime - business_plan = split_yearly_financial_terms(ECModel,profit_distribution) + business_plan = split_yearly_financial_terms(ECModel) # Create an empty DataFrame - df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], RV = Float64[], PEAK = Float64[]) + df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], REP = Float64[], + REWARD = Float64[], RV = Float64[], PEAK = Float64[]) for i in 1:axes(years) if isnothing(user_plot) - if i == 1 - year_y = years[i] - CAPEX = sum(business_plan.CAPEX) - push!(df_business, (year_y, CAPEX)) - elseif i == axes(years) - year_y = years[i] - OEM = sum(business_plan.OEM) - EN_SELL = sum(business_plant.EN_SELL) - PEAK = sum(business_plant.PEAK) - RV = sum(business_plan.RV) - #revaward not sure if correct - #energy consumed not sure if correct - push!(df_business, (year_y, OEM, PEAK, EN_SELL, RV)) - - elseif i == "maintenance" - #add when i == year of maintenance - else - year_y = years[i] - OEM = sum(business_plan.OEM) - PEAK = sum(business_plant.PEAK) - EN_SELL = sum(business_plant.EN_SELL) - #revaward not sure if correct - #energy consumed not sure if correct - push!(df_business, (year_y, CAPEX, OEM, PEAK, EN_SELL)) - end + CAPEX = sum(business_plan.CAPEX[i, :]) + year_y = 2022 + years[i] + OEM = sum(business_plan.OEM[i, :]) + EN_SELL = sum(business_plant.EN_SELL[i, :]) + PEAK = sum(business_plant.PEAK[i, :]) + REP = sum(business_plan.REP[i, :]) + RV = sum(business_plan.RV[i, :]) + REWARD = sum(business_plant.REWARD[i, :]) + EN_CONS = sum(business_plant.EN_CONS[i, :]) + push!(df_business, (year_y, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) end end return df_business end -#function business_plan_plot(ECModel:AbstractEC,general::DataFrame) - # print -#end +function business_plan_plot(ECModel::AbstactEC, df_business=nothing) + if df_business === nothing + df_business = business_plan_dataframe(ECModel) + end + + # Extract the required columns from the DataFrame + years = df_business.Year + capex = df_business.CAPEX + oem = df_business.OEM + en_sell = df_business.EN_SELL + en_cons = df_business.EN_CONS + rep = df_business.REP + reward = df_business.REWARD + rv = df_business.RV + peak = df_business.PEAK + + # Create a bar plot + p = bar(years, [capex, oem, en_sell, en_cons, rep, reward, rv, peak], + label=["CAPEX" "OEM" "EN_SELL" "EN_CONS" "REP" "REWARD" "RV" "PEAK"], + xlabel="Year", ylabel="Amount", + title="Financial Data Over 20 Years", + ylims=(0, maximum([capex; oem; en_sell; en_cons; rep; reward; rv; peak])*1.2), + legend=:topright, + color=:auto, + xrotation=45, + bar_width=0.6, + bar_position=:dodge, + grid=false, + framestyle=:box, + ) + return p +end function EnergyCommunity.split_financial_terms(ECModel::AbstractEC, profit_distribution::Dict) return split_financial_terms( From 200e877cb866bebef7ae2a9891f000a1497b84aa Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:31:20 +0200 Subject: [PATCH 12/40] Fix of business_plan_plot_function --- src/ECModel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index f2b52fe..5a4168b 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -999,7 +999,7 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing return df_business end -function business_plan_plot(ECModel::AbstactEC, df_business=nothing) +function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing df_business = business_plan_dataframe(ECModel) end From 6a143e6ddac7c67784ccf38bf13c18a0607809f2 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:32:15 +0200 Subject: [PATCH 13/40] Fix --- src/ECModel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 5a4168b..b94d75a 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -984,7 +984,7 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing for i in 1:axes(years) if isnothing(user_plot) CAPEX = sum(business_plan.CAPEX[i, :]) - year_y = 2022 + years[i] + Year = 2022 + years[i] OEM = sum(business_plan.OEM[i, :]) EN_SELL = sum(business_plant.EN_SELL[i, :]) PEAK = sum(business_plant.PEAK[i, :]) @@ -992,7 +992,7 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing RV = sum(business_plan.RV[i, :]) REWARD = sum(business_plant.REWARD[i, :]) EN_CONS = sum(business_plant.EN_CONS[i, :]) - push!(df_business, (year_y, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) + push!(df_business, (Year, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) end end From c405b01b8d7260b51c12e928d9bf9d734d6d9ed6 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:36:57 +0200 Subject: [PATCH 14/40] First bar plot with errors --- examples/RunSingleModel.jl | 2 ++ src/ECModel.jl | 48 ++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index baa18d7..7dbede3 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -45,6 +45,8 @@ save_summary(ECModel, output_file_combined) # Plot sankey plot of CO model plot_sankey(ECModel) +business_plan_plot(ECModel) + ## Model NC # create NonCooperative model diff --git a/src/ECModel.jl b/src/ECModel.jl index b94d75a..3b18372 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -936,7 +936,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_net_energy_costs # get NPV given the reward allocation - #Check hot to proceed + #Check how to proceed NPV = profit_distribution # Total reward @@ -958,6 +958,11 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no end function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) + gen_data = ECModel.gen_data + + project_lifetime = field(gen_data, "project_lifetime") + year_set = 1:project_lifetime + if isnothing(user_set_financial) user_set_financial = [EC_CODE; get_user_set(ECModel)] end @@ -981,19 +986,17 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing # Create an empty DataFrame df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], REP = Float64[], REWARD = Float64[], RV = Float64[], PEAK = Float64[]) - for i in 1:axes(years) - if isnothing(user_plot) - CAPEX = sum(business_plan.CAPEX[i, :]) - Year = 2022 + years[i] - OEM = sum(business_plan.OEM[i, :]) - EN_SELL = sum(business_plant.EN_SELL[i, :]) - PEAK = sum(business_plant.PEAK[i, :]) - REP = sum(business_plan.REP[i, :]) - RV = sum(business_plan.RV[i, :]) - REWARD = sum(business_plant.REWARD[i, :]) - EN_CONS = sum(business_plant.EN_CONS[i, :]) - push!(df_business, (Year, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) - end + for i in years + CAPEX = sum(business_plan.CAPEX[i, :]) + Year = 2022 + years[i] + OEM = sum(business_plan.OEM[i, :]) + EN_SELL = sum(business_plan.EN_SELL[i, :]) + PEAK = sum(business_plan.PEAK[i, :]) + REP = sum(business_plan.REP[i, :]) + RV = sum(business_plan.RV[i, :]) + REWARD = sum(business_plan.REWARD[i, :]) + EN_CONS = sum(business_plan.EN_CONS[i, :]) + push!(df_business, (Year, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) end return df_business @@ -1006,26 +1009,25 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) # Extract the required columns from the DataFrame years = df_business.Year - capex = df_business.CAPEX - oem = df_business.OEM + capex = -df_business.CAPEX + oem = -df_business.OEM en_sell = df_business.EN_SELL - en_cons = df_business.EN_CONS - rep = df_business.REP + en_cons = -df_business.EN_CONS + rep = -df_business.REP reward = df_business.REWARD rv = df_business.RV - peak = df_business.PEAK + peak = -df_business.PEAK # Create a bar plot p = bar(years, [capex, oem, en_sell, en_cons, rep, reward, rv, peak], - label=["CAPEX" "OEM" "EN_SELL" "EN_CONS" "REP" "REWARD" "RV" "PEAK"], - xlabel="Year", ylabel="Amount", - title="Financial Data Over 20 Years", + label=["CAPEX" "OEM" "Energy sell" "Energy consumption" "Replacement" "Reward" "Recovery" "Peak charges"], + xlabel="Year", ylabel="Amount [€]", + title="Business Over 20 Years", ylims=(0, maximum([capex; oem; en_sell; en_cons; rep; reward; rv; peak])*1.2), legend=:topright, color=:auto, xrotation=45, bar_width=0.6, - bar_position=:dodge, grid=false, framestyle=:box, ) From a69e0dbf8a26b45814ccd8d496c37c86f15a1a36 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:17:26 +0200 Subject: [PATCH 15/40] Description of functions --- src/ECModel.jl | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/ECModel.jl b/src/ECModel.jl index 3b18372..6427796 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -853,6 +853,36 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end +""" +split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution) + +Function to describe the cost term distributions by all users for all years. + +Parameters +---------- +- ECModel : AbstractEC + EnergyCommunity model +- profit_distribution + Final objective function +- user_set_financial + User set to be considered for the financial analysis + +Returns +------- + The output value is a NamedTuple with the following elements + - NPV: the NPV of each user given the final profit_distribution adjustment + by game theory techniques + - CAPEX: the annualized CAPEX + - OPEX: the annualized operating costs (yearly maintenance and yearly peak and energy grid charges) + - REP: the annualized replacement costs + - RV: the annualized recovery charges + - REWARD: the annualized reward distribution by user + - PEAK: the annualized peak costs + - EN_SELL: the annualized revenues from energy sales + - EN_BUY: the annualized costs from energy consumption and buying + - EN_NET: the annualized net energy costs +""" + function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=nothing, profit_distribution=nothing) gen_data = ECModel.gen_data @@ -957,6 +987,27 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no ) end +""" + business_plan_dataframe(ECModel::AbstractEC, profit_distribution) + +Function to describe the cost term distributions by all users for all years. + +Parameters +---------- +- ECModel : AbstractEC + EnergyCommunity model +- profit_distribution + Final objective function +- user_set_financial + User set to be considered for the financial analysis + +Returns +------- + The output value is a NamedTuple with the following elements + - df_business + Dataframe with the business plan information +""" + function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) gen_data = ECModel.gen_data @@ -1002,6 +1053,23 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing return df_business end +""" + business_plan_plot(ECModel::AbstractEC, profit_distribution) + +Function to describe the cost term distributions by all users for all years. + +Parameters +---------- +- ECModel : AbstractEC + EnergyCommunity model +- df_business + Dataframe with the business plan information + +Returns +------- + The output value is a plot with the business plan information +""" + function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing df_business = business_plan_dataframe(ECModel) From 1c20249280a9803a3c334bb4041d25c31de872bd Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:22:26 +0200 Subject: [PATCH 16/40] Fixing of tab intended --- src/ECModel.jl | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 6427796..04b66ef 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -912,37 +912,32 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( [(y==1) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) # Maintenance costs Ann_Maintenance = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_OEM_tot_us], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) # Replacement costs Ann_Replacement = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_REP_tot_us][y, :], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) # Recovery value Ann_Recovery = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:R_RV_tot_us][y, :], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) # Peak energy charges Ann_peak_charges = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_Peak_tot_us], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) @@ -950,14 +945,12 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no zero_if_negative = x->((x>=0) ? x : 0.0) Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) Ann_net_energy_costs = Ann_energy_costs .- Ann_energy_revenues @@ -1020,9 +1013,7 @@ function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( - [ 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) - ] + [0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set, ) end From 63151b198fe865862e47122e733084d8657d7195 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:24:26 +0200 Subject: [PATCH 17/40] business_plant rename function --- src/ECModel.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 04b66ef..e52bd4f 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -981,7 +981,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no end """ - business_plan_dataframe(ECModel::AbstractEC, profit_distribution) + business_plan(ECModel::AbstractEC, profit_distribution) Function to describe the cost term distributions by all users for all years. @@ -1001,7 +1001,7 @@ Returns Dataframe with the business plan information """ -function business_plan_dataframe(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) +function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") @@ -1063,7 +1063,7 @@ Returns function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing - df_business = business_plan_dataframe(ECModel) + df_business = business_plan(ECModel) end # Extract the required columns from the DataFrame From c078743bfc246b5da925c0f8506c01ad33f7317d Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:28:06 +0200 Subject: [PATCH 18/40] Year 0 as star year --- src/ECModel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index e52bd4f..388e133 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1030,7 +1030,7 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set REWARD = Float64[], RV = Float64[], PEAK = Float64[]) for i in years CAPEX = sum(business_plan.CAPEX[i, :]) - Year = 2022 + years[i] + Year = 0 + years[i] OEM = sum(business_plan.OEM[i, :]) EN_SELL = sum(business_plan.EN_SELL[i, :]) PEAK = sum(business_plan.PEAK[i, :]) From 769f4133aac48653281ba9f0a8c0b665b6cddfec Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:09:21 +0200 Subject: [PATCH 19/40] Fix --- src/ECModel.jl | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 388e133..6a9e6d8 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -899,14 +899,14 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( [ 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE]) + for u in setdiff(user_set_financial, [EC_CODE]) ] - , year_set, user_set, + , user_set, ) end @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED - user_set = axes(profit_distribution)[2] + user_set = axes(profit_distribution)[1] ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] # Investment costs @@ -953,17 +953,20 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) - Ann_net_energy_costs = Ann_energy_costs .- Ann_energy_revenues # Total OPEX costs - OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_net_energy_costs + OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_energy_costs # get NPV given the reward allocation #Check how to proceed - NPV = profit_distribution + NPV = JuMP.Containers.DenseAxisArray( + [get_value(profit_distribution, u)*ann_factor[y] + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + , year_set, user_set + ) # Total reward - Ann_reward = NPV .+ CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery + Ann_reward = NPV .- CAPEX .- OPEX .- Ann_Replacement .+ Ann_Recovery .+ Ann_energy_revenues .- Ann_energy_costs return ( NPV=NPV, @@ -975,8 +978,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no REWARD=Ann_reward, PEAK=Ann_peak_charges, EN_SELL=Ann_energy_revenues, - EN_CONS=Ann_energy_costs, - EN_NET=Ann_net_energy_costs, + EN_CONS=-Ann_energy_costs, ) end @@ -1065,6 +1067,7 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing df_business = business_plan(ECModel) end + #p = @df_business df_business bar(:Year, [:CAPEX, :OEM, :EN_SELL, :EN_CONS, :REP, :REWARD, :RV, :PEAK],title="Business Over 20 Years") # Extract the required columns from the DataFrame years = df_business.Year From 381c9d16f42a9b0710efdb5b9833309c083f63fd Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:27:23 +0200 Subject: [PATCH 20/40] Fixing on ECModel --- src/ECModel.jl | 63 +++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 6a9e6d8..e968b19 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -883,18 +883,18 @@ Returns - EN_NET: the annualized net energy costs """ -function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=nothing, profit_distribution=nothing) +function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") - get_value = ((dense_axis, element) -> (if (element in axes(dense_axis)[1]) dense_axis[element] else 0.0 end)) + get_value = (dense_axis, element) -> (element in axes(dense_axis)[1] ? dense_axis[element] : 0.0) + zero_if_negative = x->((x>=0) ? x : 0.0) - year_set = 1:project_lifetime + year_set = 0:project_lifetime - if isnothing(user_set_financial) - user_set_financial = [EC_CODE; get_user_set(ECModel)] - end + user_set_financial = [EC_CODE; get_user_set(ECModel)] + if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( @@ -904,6 +904,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no , user_set, ) end + @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED user_set = axes(profit_distribution)[1] @@ -911,7 +912,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( - [(y==1) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 + [(y == 0) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) @@ -929,7 +930,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no ) # Recovery value Ann_Recovery = JuMP.Containers.DenseAxisArray( - [get_value(ECModel.results[:R_RV_tot_us][y, :], u) + [(y == project_lifetime) ? (get_value(ECModel.results[:R_RV_tot_us][y, :], u)) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) @@ -942,7 +943,6 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no ) # Get revenes by selling energy and costs by buying or consuming energy - zero_if_negative = x->((x>=0) ? x : 0.0) Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] @@ -954,19 +954,35 @@ function split_yearly_financial_terms(ECModel::AbstractEC, user_set_financial=no , year_set, user_set ) + # Revenues for shared energy within the community + #Need to change this part + Ann_shared_revenues = JuMP.Containers.DenseAxisArray( + [sum(ECModel.results[:P_shared_agg])/length(user_set_financial) + for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + , year_set, user_set + ) + # Total OPEX costs OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_energy_costs # get NPV given the reward allocation - #Check how to proceed + #= + Basically, what we may need to do is to create a proxy total discounted cost of all terms but NPV so to reproduce the old (CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery). + For example, something like: + (CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery).data * ann_factor (note that since they are matrix operation, there may be the need for some transpositions + + Then, the resulting vector shall be a 1 column or 1 vector and we can create the equivalent 1D cost vector, so that we can do (total_discounted_reward = NPV .- new_vector). + Then, we can do total_discounted_reward ./(sum(act_factor) - 1) and this should be a 1D vector of the yearly reward allocation by user, that can be exploded into 2D by simply duplicating the entries. + + =# NPV = JuMP.Containers.DenseAxisArray( - [get_value(profit_distribution, u)*ann_factor[y] + [get_value(profit_distribution, u) .* ann_factor[y] for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) # Total reward - Ann_reward = NPV .- CAPEX .- OPEX .- Ann_Replacement .+ Ann_Recovery .+ Ann_energy_revenues .- Ann_energy_costs + Ann_reward = .- CAPEX .- OPEX .- Ann_Replacement .+ Ann_Recovery .+ Ann_energy_revenues .- Ann_energy_costs .+ Ann_shared_revenues return ( NPV=NPV, @@ -1007,7 +1023,7 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") - year_set = 1:project_lifetime + year_set = 0:project_lifetime if isnothing(user_set_financial) user_set_financial = [EC_CODE; get_user_set(ECModel)] @@ -1023,23 +1039,22 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set # Create a vector of years from 2023 to (2023 + project_lifetime) gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") - years = 1:project_lifetime business_plan = split_yearly_financial_terms(ECModel) # Create an empty DataFrame df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], REP = Float64[], REWARD = Float64[], RV = Float64[], PEAK = Float64[]) - for i in years - CAPEX = sum(business_plan.CAPEX[i, :]) - Year = 0 + years[i] - OEM = sum(business_plan.OEM[i, :]) - EN_SELL = sum(business_plan.EN_SELL[i, :]) - PEAK = sum(business_plan.PEAK[i, :]) - REP = sum(business_plan.REP[i, :]) - RV = sum(business_plan.RV[i, :]) - REWARD = sum(business_plan.REWARD[i, :]) - EN_CONS = sum(business_plan.EN_CONS[i, :]) + for i in year_set + CAPEX = sum(business_plan.CAPEX[i, user_set_financial]) + Year = 0 + year_set[i] + OEM = sum(business_plan.OEM[i, user_set_financial]) + EN_SELL = sum(business_plan.EN_SELL[i, user_set_financial]) + PEAK = sum(business_plan.PEAK[i, user_set_financial]) + REP = sum(business_plan.REP[i, user_set_financial]) + RV = sum(business_plan.RV[i, user_set_financial]) + REWARD = sum(business_plan.REWARD[i, user_set_financial]) + EN_CONS = sum(business_plan.EN_CONS[i, user_set_financial]) push!(df_business, (Year, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) end From 4a4a66a14e579b0eb5a841bc531e3232a3f06cd8 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:05:46 +0200 Subject: [PATCH 21/40] Fixgin not working --- src/ECModel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index e968b19..2f027dc 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1082,7 +1082,7 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing df_business = business_plan(ECModel) end - #p = @df_business df_business bar(:Year, [:CAPEX, :OEM, :EN_SELL, :EN_CONS, :REP, :REWARD, :RV, :PEAK],title="Business Over 20 Years") + p = @df_business df_business bar(:Year, [:CAPEX, :OEM] ,title="Business Over 20 Years") # Extract the required columns from the DataFrame years = df_business.Year From 8b86766048ec1f96c88d85ef3279a54e2e9783e3 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:31:03 +0200 Subject: [PATCH 22/40] Next try --- src/ECModel.jl | 72 +++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 2f027dc..88655c9 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -853,7 +853,7 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end -""" +""" TO BE IMPROVED split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution) Function to describe the cost term distributions by all users for all years. @@ -898,10 +898,8 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n if isnothing(profit_distribution) user_set = get_user_set(ECModel) profit_distribution = JuMP.Containers.DenseAxisArray( - [ 0.0 - for u in setdiff(user_set_financial, [EC_CODE]) - ] - , user_set, + fill(0.0, length(user_set)), + user_set, ) end @@ -923,8 +921,9 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n , year_set, user_set ) # Replacement costs + #The index is 1:20 so in the result should be proper changed. I'll open an issue Ann_Replacement = JuMP.Containers.DenseAxisArray( - [get_value(ECModel.results[:C_REP_tot_us][y, :], u) + [(y == 0) ? 0.0 : get_value(ECModel.results[:C_REP_tot_us][y, :], u) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) @@ -934,14 +933,12 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) - # Peak energy charges Ann_peak_charges = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_Peak_tot_us], u) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) - # Get revenes by selling energy and costs by buying or consuming energy Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 @@ -953,17 +950,12 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) - - # Revenues for shared energy within the community - #Need to change this part - Ann_shared_revenues = JuMP.Containers.DenseAxisArray( - [sum(ECModel.results[:P_shared_agg])/length(user_set_financial) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set - ) # Total OPEX costs - OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_energy_costs + # I think that I miss the reward here + Ann_ene_net_costs = Ann_energy_costs .- Ann_energy_revenues + + OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_ene_net_costs # get NPV given the reward allocation #= @@ -976,25 +968,32 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n =# NPV = JuMP.Containers.DenseAxisArray( - [get_value(profit_distribution, u) .* ann_factor[y] - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + [get_value(profit_distribution, u) + for u in setdiff(user_set_financial, [EC_CODE])] + , user_set ) # Total reward - Ann_reward = .- CAPEX .- OPEX .- Ann_Replacement .+ Ann_Recovery .+ Ann_energy_revenues .- Ann_energy_costs .+ Ann_shared_revenues - + # This is the total discounted cost of all terms but NPV. I think that this must be improved + total_discounted_cost= CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery + + Ann_reward = JuMP.Containers.DenseAxisArray( + [(NPV[u] .- sum(total_discounted_cost[:,u]))/(sum(ann_factor) - 1) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + , year_set, user_set + ) + return ( NPV=NPV, CAPEX=CAPEX, OPEX=OPEX, OEM = Ann_Maintenance, - REP=Ann_Replacement, - RV=Ann_Recovery, - REWARD=Ann_reward, - PEAK=Ann_peak_charges, - EN_SELL=Ann_energy_revenues, - EN_CONS=-Ann_energy_costs, + REP = Ann_Replacement, + RV = Ann_Recovery, + REWARD = Ann_reward, + PEAK = Ann_peak_charges, + EN_SELL = Ann_energy_revenues, + EN_CONS = Ann_energy_costs, + year_set = year_set ) end @@ -1023,17 +1022,9 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") - year_set = 0:project_lifetime if isnothing(user_set_financial) - user_set_financial = [EC_CODE; get_user_set(ECModel)] - end - if isnothing(profit_distribution) - user_set = get_user_set(ECModel) - profit_distribution = JuMP.Containers.DenseAxisArray( - [0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set, - ) + user_set_financial = get_user_set(ECModel) end # Create a vector of years from 2023 to (2023 + project_lifetime) @@ -1041,13 +1032,14 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set project_lifetime = field(gen_data, "project_lifetime") business_plan = split_yearly_financial_terms(ECModel) + year_set = business_plan.year_set # Create an empty DataFrame df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], REP = Float64[], REWARD = Float64[], RV = Float64[], PEAK = Float64[]) for i in year_set CAPEX = sum(business_plan.CAPEX[i, user_set_financial]) - Year = 0 + year_set[i] + Year = 0 + year_set[i+1] OEM = sum(business_plan.OEM[i, user_set_financial]) EN_SELL = sum(business_plan.EN_SELL[i, user_set_financial]) PEAK = sum(business_plan.PEAK[i, user_set_financial]) @@ -1082,7 +1074,6 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) if df_business === nothing df_business = business_plan(ECModel) end - p = @df_business df_business bar(:Year, [:CAPEX, :OEM] ,title="Business Over 20 Years") # Extract the required columns from the DataFrame years = df_business.Year @@ -1107,7 +1098,10 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) bar_width=0.6, grid=false, framestyle=:box, + bar_position=:stack, ) + + save_fig(p, "business_plan.png") return p end From e1e6ec7253b9357a04bff89586914e6ab8c67578 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:37:05 +0200 Subject: [PATCH 23/40] Fix business plot --- src/ECModel.jl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 88655c9..a3b7e3e 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -923,7 +923,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n # Replacement costs #The index is 1:20 so in the result should be proper changed. I'll open an issue Ann_Replacement = JuMP.Containers.DenseAxisArray( - [(y == 0) ? 0.0 : get_value(ECModel.results[:C_REP_tot_us][y, :], u) + [(y == 10) ? get_value(ECModel.results[:C_REP_tot_us][y, :], u) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] , year_set, user_set ) @@ -1035,19 +1035,19 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set year_set = business_plan.year_set # Create an empty DataFrame - df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], REP = Float64[], - REWARD = Float64[], RV = Float64[], PEAK = Float64[]) + df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], PEAK = Float64[], REP = Float64[], + REWARD = Float64[], RV = Float64[]) for i in year_set - CAPEX = sum(business_plan.CAPEX[i, user_set_financial]) Year = 0 + year_set[i+1] + CAPEX = sum(business_plan.CAPEX[i, user_set_financial]) OEM = sum(business_plan.OEM[i, user_set_financial]) EN_SELL = sum(business_plan.EN_SELL[i, user_set_financial]) + EN_CONS = sum(business_plan.EN_CONS[i, user_set_financial]) PEAK = sum(business_plan.PEAK[i, user_set_financial]) REP = sum(business_plan.REP[i, user_set_financial]) - RV = sum(business_plan.RV[i, user_set_financial]) REWARD = sum(business_plan.REWARD[i, user_set_financial]) - EN_CONS = sum(business_plan.EN_CONS[i, user_set_financial]) - push!(df_business, (Year, CAPEX, OEM, PEAK, REP, REWARD, EN_SELL, EN_CONS, RV)) + RV = sum(business_plan.RV[i, user_set_financial]) + push!(df_business, (Year, CAPEX, OEM, EN_SELL, EN_CONS, PEAK, REP, REWARD, RV)) end return df_business @@ -1100,8 +1100,7 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) framestyle=:box, bar_position=:stack, ) - - save_fig(p, "business_plan.png") + print(df_business) return p end From ed08d98e1b94686feb970996fa7547fc2127f41d Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:31:11 +0200 Subject: [PATCH 24/40] Fixing --- src/ECModel.jl | 8 ++++++-- src/EnergyCommunity.jl | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index a3b7e3e..a13af4a 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1091,16 +1091,20 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) label=["CAPEX" "OEM" "Energy sell" "Energy consumption" "Replacement" "Reward" "Recovery" "Peak charges"], xlabel="Year", ylabel="Amount [€]", title="Business Over 20 Years", - ylims=(0, maximum([capex; oem; en_sell; en_cons; rep; reward; rv; peak])*1.2), + #ylims=(maximum([capex; oem; en_cons; rep; peak]), maximum([oem; en_sell; reward; rv])*1.2), legend=:topright, color=:auto, xrotation=45, bar_width=0.6, grid=false, framestyle=:box, - bar_position=:stack, + barmode=:stack, ) + + #p = @df_business df_business bar(:Year, [:CAPEX, :OEM, :EN_SELL, :EN_CONS, :REP, :REWARD, :RV, :PEAK], xlabel="Year", ylabel="Value", title="Business plan information", bar_position=:stacked, bar_width=0.5, color=[:red :blue :green :orange :purple :yellow :brown :pink], legend=:topleft) + print(df_business) + savefig(p, "business_plan.png") return p end diff --git a/src/EnergyCommunity.jl b/src/EnergyCommunity.jl index 907e11d..dafe984 100644 --- a/src/EnergyCommunity.jl +++ b/src/EnergyCommunity.jl @@ -8,6 +8,7 @@ module EnergyCommunity using MathOptInterface using Base.Iterators using TheoryOfGames + using StatsPlots # import ECharts import SankeyPlots import CSV From 7677bacc170625ebd780a86edb070b29b31163aa Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:11:28 +0200 Subject: [PATCH 25/40] First update --- Project.toml | 1 + business_plan.png | Bin 0 -> 29116 bytes 2 files changed, 1 insertion(+) create mode 100644 business_plan.png diff --git a/Project.toml b/Project.toml index b5b1416..eada850 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ LayeredLayouts = "f4a74d36-062a-4d48-97cd-1356bad1de4e" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" SankeyPlots = "8fd88ec8-d95c-41fc-b299-05f2225f2cc5" +StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd" TheoryOfGames = "eb50afb4-6f20-4b37-9b66-473e668300bf" XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" diff --git a/business_plan.png b/business_plan.png new file mode 100644 index 0000000000000000000000000000000000000000..0923f5fb429b720d01d8cbb6b9480244a7f50f67 GIT binary patch literal 29116 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

Ae@BjpU0Xa6#^j;4qFf#Wbdh-x0_gtl*$jV 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

kJrbSB)re#f~S>)I4jRhoN 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 literal 0 HcmV?d00001 From 27702abaec01dce41c66661a09853583ab7a28bb Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:31:54 +0200 Subject: [PATCH 26/40] First plot fix --- business_plan.png | Bin 29116 -> 0 bytes examples/RunSingleModel.jl | 5 +++- src/ECModel.jl | 52 +++++++++++++++++++++---------------- 3 files changed, 33 insertions(+), 24 deletions(-) delete mode 100644 business_plan.png diff --git a/business_plan.png b/business_plan.png deleted file mode 100644 index 0923f5fb429b720d01d8cbb6b9480244a7f50f67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29116 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

Ae@BjpU0Xa6#^j;4qFf#Wbdh-x0_gtl*$jV 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

kJrbSB)re#f~S>)I4jRhoN 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 diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index 7dbede3..63464e7 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -68,4 +68,7 @@ print_summary(NC_Model) save_summary(NC_Model, output_file_isolated) # plot Sankey plot of NC model -plot_sankey(NC_Model) \ No newline at end of file +plot_sankey(NC_Model) + +# plot business plan of NC model +#business_plan_plot(NC_Model) \ No newline at end of file diff --git a/src/ECModel.jl b/src/ECModel.jl index a13af4a..b8783be 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -912,43 +912,49 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n CAPEX = JuMP.Containers.DenseAxisArray( [(y == 0) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) # Maintenance costs Ann_Maintenance = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_OEM_tot_us], u) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) # Replacement costs #The index is 1:20 so in the result should be proper changed. I'll open an issue Ann_Replacement = JuMP.Containers.DenseAxisArray( [(y == 10) ? get_value(ECModel.results[:C_REP_tot_us][y, :], u) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) # Recovery value Ann_Recovery = JuMP.Containers.DenseAxisArray( [(y == project_lifetime) ? (get_value(ECModel.results[:R_RV_tot_us][y, :], u)) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) # Peak energy charges Ann_peak_charges = JuMP.Containers.DenseAxisArray( [get_value(ECModel.results[:C_Peak_tot_us], u) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) # Get revenes by selling energy and costs by buying or consuming energy Ann_energy_revenues = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) : 0.0 for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set + , year_set, user_set_financial + ) + + Ann_energy_reward = JuMP.Containers.DenseAxisArray( + [ECModel == ECModel ? ECModel.results[:R_Reward_agg_tot] : 0.0 + for y in year_set, u in [EC_CODE]] + , year_set, user_set_financial ) # Total OPEX costs @@ -970,17 +976,17 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n NPV = JuMP.Containers.DenseAxisArray( [get_value(profit_distribution, u) for u in setdiff(user_set_financial, [EC_CODE])] - , user_set + , user_set_financial ) # Total reward # This is the total discounted cost of all terms but NPV. I think that this must be improved total_discounted_cost= CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery - Ann_reward = JuMP.Containers.DenseAxisArray( + #=Ann_reward = JuMP.Containers.DenseAxisArray( [(NPV[u] .- sum(total_discounted_cost[:,u]))/(sum(ann_factor) - 1) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set - ) + , year_set, user_set_financial + )=# return ( NPV=NPV, @@ -989,7 +995,7 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n OEM = Ann_Maintenance, REP = Ann_Replacement, RV = Ann_Recovery, - REWARD = Ann_reward, + REWARD = Ann_energy_reward, PEAK = Ann_peak_charges, EN_SELL = Ann_energy_revenues, EN_CONS = Ann_energy_costs, @@ -1039,14 +1045,14 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set REWARD = Float64[], RV = Float64[]) for i in year_set Year = 0 + year_set[i+1] - CAPEX = sum(business_plan.CAPEX[i, user_set_financial]) - OEM = sum(business_plan.OEM[i, user_set_financial]) - EN_SELL = sum(business_plan.EN_SELL[i, user_set_financial]) - EN_CONS = sum(business_plan.EN_CONS[i, user_set_financial]) - PEAK = sum(business_plan.PEAK[i, user_set_financial]) - REP = sum(business_plan.REP[i, user_set_financial]) - REWARD = sum(business_plan.REWARD[i, user_set_financial]) - RV = sum(business_plan.RV[i, user_set_financial]) + CAPEX = sum(business_plan.CAPEX[i, :]) + OEM = sum(business_plan.OEM[i, :]) + EN_SELL = sum(business_plan.EN_SELL[i, :]) + EN_CONS = sum(business_plan.EN_CONS[i, :]) + PEAK = sum(business_plan.PEAK[i, :]) + REP = sum(business_plan.REP[i, :]) + REWARD = sum(business_plan.REWARD[i, :]) + RV = sum(business_plan.RV[i, :]) push!(df_business, (Year, CAPEX, OEM, EN_SELL, EN_CONS, PEAK, REP, REWARD, RV)) end @@ -1090,9 +1096,9 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) p = bar(years, [capex, oem, en_sell, en_cons, rep, reward, rv, peak], label=["CAPEX" "OEM" "Energy sell" "Energy consumption" "Replacement" "Reward" "Recovery" "Peak charges"], xlabel="Year", ylabel="Amount [€]", - title="Business Over 20 Years", + title="Business Plan Over 20 Years", #ylims=(maximum([capex; oem; en_cons; rep; peak]), maximum([oem; en_sell; reward; rv])*1.2), - legend=:topright, + legend=:bottomright, color=:auto, xrotation=45, bar_width=0.6, @@ -1104,7 +1110,7 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing) #p = @df_business df_business bar(:Year, [:CAPEX, :OEM, :EN_SELL, :EN_CONS, :REP, :REWARD, :RV, :PEAK], xlabel="Year", ylabel="Value", title="Business plan information", bar_position=:stacked, bar_width=0.5, color=[:red :blue :green :orange :purple :yellow :brown :pink], legend=:topleft) print(df_business) - savefig(p, "business_plan.png") + savefig(p, joinpath("results","Img","business_plan","CO")) # Save the plot return p end From f1fd37138e9a6cbb340f363d701c01336e1e3852 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:13:10 +0100 Subject: [PATCH 27/40] Project.toml update --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index eada850..31fe93e 100644 --- a/Project.toml +++ b/Project.toml @@ -9,7 +9,6 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" -HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LayeredLayouts = "f4a74d36-062a-4d48-97cd-1356bad1de4e" @@ -28,6 +27,7 @@ ExportAll = "0.1" FileIO = "1" Formatting = "0.4" JuMP = "1" +StatsPlots = "0.15" LayeredLayouts = "0.2" MathOptInterface = "1.0" Plots = "1" From 39ae9c03e1834b88474c780a1c8a74cb3b45e787 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 31 Oct 2023 14:14:52 +0100 Subject: [PATCH 28/40] Clear RunSingleModel --- examples/RunSingleModel.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index 63464e7..34af866 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -45,6 +45,7 @@ save_summary(ECModel, output_file_combined) # Plot sankey plot of CO model plot_sankey(ECModel) +# plot 20 years business plan of CO model business_plan_plot(ECModel) ## Model NC @@ -71,4 +72,4 @@ save_summary(NC_Model, output_file_isolated) plot_sankey(NC_Model) # plot business plan of NC model -#business_plan_plot(NC_Model) \ No newline at end of file +business_plan_plot(NC_Model) From 1e28fdd66fa3e4bbf612484bc672168c0367021f Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 2 Nov 2023 15:38:51 +0100 Subject: [PATCH 29/40] Fixing of bar_label and values --- Project.toml | 3 ++- src/ECModel.jl | 64 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Project.toml b/Project.toml index 31fe93e..25324a1 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LayeredLayouts = "f4a74d36-062a-4d48-97cd-1356bad1de4e" @@ -27,11 +28,11 @@ ExportAll = "0.1" FileIO = "1" Formatting = "0.4" JuMP = "1" -StatsPlots = "0.15" LayeredLayouts = "0.2" MathOptInterface = "1.0" Plots = "1" SankeyPlots = "0.2.2" +StatsPlots = "0.15" TheoryOfGames = "0.1" XLSX = "0.9" YAML = "0.4" diff --git a/src/ECModel.jl b/src/ECModel.jl index b8783be..b7d2758 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1076,41 +1076,53 @@ Returns The output value is a plot with the business plan information """ -function business_plan_plot(ECModel::AbstractEC, df_business=nothing) +function business_plan_plot(ECModel::AbstractEC, df_business=nothing, + xlabel="Year", + ylabel="Amount [k€]", + title="Business Plan Over 20 Years", + legend=:bottomright, + color=:auto, + xrotation=45, + bar_width=0.6, + grid=false, + framestyle=:box, + barmode=:stack, + scaling_factor = 0.001, + kwargs...) + if df_business === nothing df_business = business_plan(ECModel) end - # Extract the required columns from the DataFrame + # Define the plot structure + plot_struct = Dict( + "CAPEX" => [(-1, :CAPEX)], + "OEM" => [(-1, :OEM), (-1, :PEAK)], + "Repl. and Recovery" => [(-1, :REP), (+1, :RV)], + "Energy expences" => [(-1, :EN_CONS), (+1, :EN_SELL)], + "Reward" => [(+1, :REWARD)], + ) + + # Extract the year from the DataFrame years = df_business.Year - capex = -df_business.CAPEX - oem = -df_business.OEM - en_sell = df_business.EN_SELL - en_cons = -df_business.EN_CONS - rep = -df_business.REP - reward = df_business.REWARD - rv = df_business.RV - peak = -df_business.PEAK + + bar_labels = collect(keys(plot_struct)) + bar_data = [sum(tup[1] .* df_business[!, tup[2]] .* scaling_factor for tup in plot_struct[l]) for l in bar_labels] # Create a bar plot - p = bar(years, [capex, oem, en_sell, en_cons, rep, reward, rv, peak], - label=["CAPEX" "OEM" "Energy sell" "Energy consumption" "Replacement" "Reward" "Recovery" "Peak charges"], - xlabel="Year", ylabel="Amount [€]", - title="Business Plan Over 20 Years", - #ylims=(maximum([capex; oem; en_cons; rep; peak]), maximum([oem; en_sell; reward; rv])*1.2), - legend=:bottomright, - color=:auto, - xrotation=45, - bar_width=0.6, - grid=false, - framestyle=:box, - barmode=:stack, + p = bar(years, bar_data, + label=bar_labels, + xlabel=xlabel, ylabel=ylabel, + title=title, + legend=legend, + color=color, + xrotation=xrotation, + bar_width=bar_width, + grid=grid, + framestyle=framestyle, + barmode=barmode, ) - #p = @df_business df_business bar(:Year, [:CAPEX, :OEM, :EN_SELL, :EN_CONS, :REP, :REWARD, :RV, :PEAK], xlabel="Year", ylabel="Value", title="Business plan information", bar_position=:stacked, bar_width=0.5, color=[:red :blue :green :orange :purple :yellow :brown :pink], legend=:topleft) - - print(df_business) - savefig(p, joinpath("results","Img","business_plan","CO")) # Save the plot return p end From 2ca53cceb257f6e03c9380a44f962c79f60096ef Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:20:25 +0100 Subject: [PATCH 30/40] Only CO --- examples/RunSingleModel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index 34af866..5e8bcce 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -72,4 +72,4 @@ save_summary(NC_Model, output_file_isolated) plot_sankey(NC_Model) # plot business plan of NC model -business_plan_plot(NC_Model) +#business_plan_plot(NC_Model) From 6407d2a3b040fa362f3e4103c7aa1afec6037ed1 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:22:09 +0100 Subject: [PATCH 31/40] Update Project.toml --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 25324a1..17f44d3 100644 --- a/Project.toml +++ b/Project.toml @@ -9,7 +9,6 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" -HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LayeredLayouts = "f4a74d36-062a-4d48-97cd-1356bad1de4e" From 60d09434c9fce40bcb2f8166a4b014ea5e5552f4 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:31:21 +0100 Subject: [PATCH 32/40] Almost last fix --- examples/RunSingleModel.jl | 2 +- src/ECModel.jl | 38 +++++++++-------- src/non_cooperative.jl | 85 +++++++++++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 20 deletions(-) diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index 5e8bcce..34af866 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -72,4 +72,4 @@ save_summary(NC_Model, output_file_isolated) plot_sankey(NC_Model) # plot business plan of NC model -#business_plan_plot(NC_Model) +business_plan_plot(NC_Model) diff --git a/src/ECModel.jl b/src/ECModel.jl index b7d2758..2bab2da 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -853,7 +853,7 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end -""" TO BE IMPROVED +""" split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution) Function to describe the cost term distributions by all users for all years. @@ -870,8 +870,7 @@ Parameters Returns ------- The output value is a NamedTuple with the following elements - - NPV: the NPV of each user given the final profit_distribution adjustment - by game theory techniques + - NPV: the NPV of each user given the final profit_distribution adjustment by game theory techniques - CAPEX: the annualized CAPEX - OPEX: the annualized operating costs (yearly maintenance and yearly peak and energy grid charges) - REP: the annualized replacement costs @@ -1070,13 +1069,17 @@ Parameters EnergyCommunity model - df_business Dataframe with the business plan information +- plot_struct + Plot structure of the business plan Returns ------- The output value is a plot with the business plan information """ -function business_plan_plot(ECModel::AbstractEC, df_business=nothing, +function business_plan_plot( + ECModel::AbstractEC; + plot_struct=nothing, xlabel="Year", ylabel="Amount [k€]", title="Business Plan Over 20 Years", @@ -1090,24 +1093,24 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing, scaling_factor = 0.001, kwargs...) - if df_business === nothing - df_business = business_plan(ECModel) - end + df_business = business_plan(ECModel) - # Define the plot structure - plot_struct = Dict( - "CAPEX" => [(-1, :CAPEX)], - "OEM" => [(-1, :OEM), (-1, :PEAK)], - "Repl. and Recovery" => [(-1, :REP), (+1, :RV)], - "Energy expences" => [(-1, :EN_CONS), (+1, :EN_SELL)], - "Reward" => [(+1, :REWARD)], - ) + if isnothing(plot_struct) + # Define the plot structure + plot_struct = Dict( + "CAPEX" => [(-1, :CAPEX)], + "OEM" => [(-1, :OEM), (-1, :PEAK)], + "Repl. and Recovery" => [(-1, :REP), (+1, :RV)], + "Energy expences" => [(-1, :EN_CONS), (+1, :EN_SELL)], + "Reward" => [(+1, :REWARD)], + ) + end # Extract the year from the DataFrame years = df_business.Year - bar_labels = collect(keys(plot_struct)) - bar_data = [sum(tup[1] .* df_business[!, tup[2]] .* scaling_factor for tup in plot_struct[l]) for l in bar_labels] + bar_labels = keys(plot_struct) + bar_data = [sum(tup[1] .* df_business[!, tup[2]] .* scaling_factor for tup in plot_struct[l]) for l in keys(plot_struct)] # Create a bar plot p = bar(years, bar_data, @@ -1121,6 +1124,7 @@ function business_plan_plot(ECModel::AbstractEC, df_business=nothing, grid=grid, framestyle=framestyle, barmode=barmode, + kwargs... ) return p diff --git a/src/non_cooperative.jl b/src/non_cooperative.jl index dc2e86b..8bf4a91 100644 --- a/src/non_cooperative.jl +++ b/src/non_cooperative.jl @@ -685,11 +685,92 @@ end finalize_results!(::AbstractGroupNC, ECModel::AbstractEC) Function to finalize the results of the Non Cooperative model after the execution -Nothing to do +Many of the variables are set to zero due to the absence of cooperation between users """ function finalize_results!(::AbstractGroupNC, ECModel::AbstractEC) - # Nothing to do + + # get user set + user_set = ECModel.user_set + user_set_EC = vcat(EC_CODE, user_set) + + + gen_data = ECModel.gen_data + users_data = ECModel.users_data + market_data = ECModel.market_data + + # get time set + init_step = field(gen_data, "init_step") + final_step = field(gen_data, "final_step") + n_steps = final_step - init_step + 1 + time_set = 1:n_steps + project_lifetime = field(gen_data, "project_lifetime") + + + # Set definitions + user_set = ECModel.user_set + year_set = 1:project_lifetime + year_set_0 = 0:project_lifetime + time_set = 1:n_steps + peak_categories = profile(gen_data,"peak_categories") + # Set definition when optional value is not included + user_set = ECModel.user_set + + # Power of the aggregator + ECModel.results[:P_agg] = JuMP.Containers.DenseAxisArray( + [0.0 for t in time_set], + time_set + ) + + # Shared power: the minimum between the supply and demand for each time step + ECModel.results[:P_shared_agg] = JuMP.Containers.DenseAxisArray( + [0.0 + for t in time_set], + time_set + ) + + # Total reward awarded to the community at each time step + ECModel.results[:R_Reward_agg] = JuMP.Containers.DenseAxisArray( + [0.0 + for t in time_set], + time_set + ) + + # Total reward awarded to the community in a year + ECModel.results[:R_Reward_agg_tot] = sum(ECModel.results[:R_Reward_agg]) + + + # Total reward awarded to the aggregator in NPV terms + ECModel.results[:R_Reward_agg_NPV] = 0.0 + + + # Total reward awarded to the aggregator in NPV terms + ECModel.results[:NPV_agg] = ECModel.results[:R_Reward_agg_NPV] + + + # Cash flow + ECModel.results[:Cash_flow_agg] = JuMP.Containers.DenseAxisArray( + [(y == 0) ? 0.0 : ECModel.results[:R_Reward_agg_tot] for y in year_set_0], + year_set_0 + ) + + + # Cash flow total + ECModel.results[:Cash_flow_tot] = JuMP.Containers.DenseAxisArray( + [ + ((y == 0) ? 0.0 : + sum(ECModel.results[:Cash_flow_us][y, :]) + ECModel.results[:Cash_flow_agg][y]) + for y in year_set_0 + ], + year_set_0 + ) + + # Social welfare of the users + ECModel.results[:SW_us] = sum(ECModel.results[:NPV_us]) + + # Social welfare of the entire aggregation + ECModel.results[:SW] = ECModel.results[:SW_us] + ECModel.results[:NPV_agg] + ECModel.results[:objective_value] = ECModel.results[:SW] end From 458967e8e398cab815501e2b6a5ae8df4fe0531a Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:24:33 +0100 Subject: [PATCH 33/40] Not working legend :( --- src/ECModel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 2bab2da..80dd459 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1114,7 +1114,7 @@ function business_plan_plot( # Create a bar plot p = bar(years, bar_data, - label=bar_labels, + labelS=bar_labels, xlabel=xlabel, ylabel=ylabel, title=title, legend=legend, From 2106981440ccfcad33a0789aff246fdf932bc9e0 Mon Sep 17 00:00:00 2001 From: Tommaso Ferrucci <64267107+TomFer97@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:10:38 +0100 Subject: [PATCH 34/40] Reorder plot structure in business_plan_plot function --- src/ECModel.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index 80dd459..47dd754 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1099,11 +1099,10 @@ function business_plan_plot( # Define the plot structure plot_struct = Dict( "CAPEX" => [(-1, :CAPEX)], - "OEM" => [(-1, :OEM), (-1, :PEAK)], "Repl. and Recovery" => [(-1, :REP), (+1, :RV)], + "OEM" => [(-1, :OEM), (-1, :PEAK)], "Energy expences" => [(-1, :EN_CONS), (+1, :EN_SELL)], - "Reward" => [(+1, :REWARD)], - ) + "Reward" => [(+1, :REWARD)]) end # Extract the year from the DataFrame From 1cedff998f99f47655c106f290aa0bf50f35170d Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 13:03:04 +0100 Subject: [PATCH 35/40] Finalize plot_business_plan --- examples/Project.toml | 27 +++++ examples/RunSingleModel.jl | 2 +- examples/RunSingleModel_complete.jl | 4 +- src/ECModel.jl | 150 +++++++++++++++------------- 4 files changed, 108 insertions(+), 75 deletions(-) create mode 100644 examples/Project.toml diff --git a/examples/Project.toml b/examples/Project.toml new file mode 100644 index 0000000..7f6c90d --- /dev/null +++ b/examples/Project.toml @@ -0,0 +1,27 @@ +[deps] +CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0" +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +EnergyCommunity = "2f2d8a28-e724-42c4-aa4e-51fe4e6b7a61" +ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" +FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" +GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" +TheoryOfGames = "eb50afb4-6f20-4b37-9b66-473e668300bf" +Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" +SankeyPlots = "8fd88ec8-d95c-41fc-b299-05f2225f2cc5" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TickTock = "9ff05d80-102d-5586-aa04-3a8bd1a90d20" +XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" +YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index 34af866..fe5708c 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -1,6 +1,6 @@ # Run this script from EnergyCommunity.jl root!!! using Pkg -Pkg.activate(".") # this line requires EnergyCommunity.jl to be the current directory +Pkg.activate("examples") using EnergyCommunity, JuMP using HiGHS, Plots diff --git a/examples/RunSingleModel_complete.jl b/examples/RunSingleModel_complete.jl index d9de5f1..2d00327 100644 --- a/examples/RunSingleModel_complete.jl +++ b/examples/RunSingleModel_complete.jl @@ -1,6 +1,6 @@ # Run this script from EnergyCommunity.jl root!!! -# using Pkg -# Pkg.activate(".") # this line requires EnergyCommunity.jl to be the current directory +using Pkg +Pkg.activate("examples") using EnergyCommunity, JuMP using HiGHS, Plots diff --git a/src/ECModel.jl b/src/ECModel.jl index 47dd754..c94000a 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -708,6 +708,7 @@ function plot_sankey(ECModel::AbstractEC; return plot_sankey(ECModel, sank_data; label_size=label_size) end + """ split_financial_terms(ECModel::AbstractEC, profit_distribution) @@ -842,7 +843,7 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) NPV=NPV, CAPEX=CAPEX, OPEX=OPEX, - OEM = Ann_Maintenance, + OEM=Ann_Maintenance, REP=Ann_Replacement, RV=Ann_Recovery, REWARD=Ann_reward, @@ -853,6 +854,7 @@ function split_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) ) end + """ split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution) @@ -881,7 +883,6 @@ Returns - EN_BUY: the annualized costs from energy consumption and buying - EN_NET: the annualized net energy costs """ - function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=nothing) gen_data = ECModel.gen_data @@ -896,63 +897,61 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n if isnothing(profit_distribution) user_set = get_user_set(ECModel) + profit_distribution = JuMP.Containers.DenseAxisArray( - fill(0.0, length(user_set)), - user_set, + [ + objective_value(ECModel) - sum(ECModel.results[:NPV_us]); + ECModel.results[:NPV_us][user_set].data; + ], + user_set_financial, ) end @assert termination_status(ECModel) != MOI.OPTIMIZE_NOT_CALLED - user_set = axes(profit_distribution)[1] ann_factor = [1. ./((1 + field(gen_data, "d_rate")).^y) for y in year_set] # Investment costs CAPEX = JuMP.Containers.DenseAxisArray( - [(y == 0) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set_financial + [(y == 0) && (u != EC_CODE) ? sum(Float64[get_value(ECModel.results[:CAPEX_tot_us], u)]) : 0.0 + for y in year_set, u in user_set_financial] + , year_set, user_set_financial ) # Maintenance costs Ann_Maintenance = JuMP.Containers.DenseAxisArray( - [get_value(ECModel.results[:C_OEM_tot_us], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + [(y != 0) && (u != EC_CODE) ? get_value(ECModel.results[:C_OEM_tot_us], u) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) # Replacement costs #The index is 1:20 so in the result should be proper changed. I'll open an issue Ann_Replacement = JuMP.Containers.DenseAxisArray( - [(y == 10) ? get_value(ECModel.results[:C_REP_tot_us][y, :], u) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + [(y != 0) && (u != EC_CODE) ? get_value(ECModel.results[:C_REP_tot_us][y, :], u) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) # Recovery value Ann_Recovery = JuMP.Containers.DenseAxisArray( - [(y == project_lifetime) ? (get_value(ECModel.results[:R_RV_tot_us][y, :], u)) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + [(y != 0) && (u != EC_CODE) ? (get_value(ECModel.results[:R_RV_tot_us][y, :], u)) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) # Peak energy charges Ann_peak_charges = JuMP.Containers.DenseAxisArray( - [get_value(ECModel.results[:C_Peak_tot_us], u) - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + [(y != 0) && (u != EC_CODE) ? get_value(ECModel.results[:C_Peak_tot_us], u) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) # Get revenes by selling energy and costs by buying or consuming energy Ann_energy_revenues = JuMP.Containers.DenseAxisArray( - [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] + [(y != 0) && (u in axes(ECModel.results[:R_Energy_us])[1]) && (u != EC_CODE) ? + sum(zero_if_negative.(ECModel.results[:R_Energy_us][u,:])) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) Ann_energy_costs = JuMP.Containers.DenseAxisArray( - [(u in axes(ECModel.results[:R_Energy_us])[1]) ? sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) : 0.0 - for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set_financial - ) - - Ann_energy_reward = JuMP.Containers.DenseAxisArray( - [ECModel == ECModel ? ECModel.results[:R_Reward_agg_tot] : 0.0 - for y in year_set, u in [EC_CODE]] + [(y != 0) && (u in axes(ECModel.results[:R_Energy_us])[1]) && (u != EC_CODE) ? sum(zero_if_negative.(.-(ECModel.results[:R_Energy_us][u, :]))) : 0.0 + for y in year_set, u in user_set_financial] , year_set, user_set_financial ) @@ -963,42 +962,41 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n OPEX = Ann_Maintenance .+ Ann_peak_charges .+ Ann_ene_net_costs # get NPV given the reward allocation - #= - Basically, what we may need to do is to create a proxy total discounted cost of all terms but NPV so to reproduce the old (CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery). - For example, something like: - (CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery).data * ann_factor (note that since they are matrix operation, there may be the need for some transpositions - - Then, the resulting vector shall be a 1 column or 1 vector and we can create the equivalent 1D cost vector, so that we can do (total_discounted_reward = NPV .- new_vector). - Then, we can do total_discounted_reward ./(sum(act_factor) - 1) and this should be a 1D vector of the yearly reward allocation by user, that can be exploded into 2D by simply duplicating the entries. - - =# + # Using the total NPV by user, we calculate the annual reward that enables that NPV = JuMP.Containers.DenseAxisArray( - [get_value(profit_distribution, u) - for u in setdiff(user_set_financial, [EC_CODE])] - , user_set_financial + [get_value(profit_distribution, u) for u in user_set_financial], + user_set_financial, ) # Total reward # This is the total discounted cost of all terms but NPV. I think that this must be improved - total_discounted_cost= CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery + total_discounted_cost_by_user = JuMP.Containers.DenseAxisArray( + (CAPEX .+ OPEX .+ Ann_Replacement .- Ann_Recovery).data' * ann_factor, + user_set_financial, + ) + total_discounted_reward_by_user = NPV .+ total_discounted_cost_by_user - #=Ann_reward = JuMP.Containers.DenseAxisArray( - [(NPV[u] .- sum(total_discounted_cost[:,u]))/(sum(ann_factor) - 1) for y in year_set, u in setdiff(user_set_financial, [EC_CODE])] - , year_set, user_set_financial - )=# + Ann_reward = JuMP.Containers.DenseAxisArray( + [ + (y != 0) ? total_discounted_reward_by_user[u]/(sum(ann_factor) - 1) : 0.0 + for y in year_set, u in user_set_financial + ], + year_set, user_set_financial + ) return ( NPV=NPV, CAPEX=CAPEX, OPEX=OPEX, - OEM = Ann_Maintenance, - REP = Ann_Replacement, - RV = Ann_Recovery, - REWARD = Ann_energy_reward, - PEAK = Ann_peak_charges, - EN_SELL = Ann_energy_revenues, - EN_CONS = Ann_energy_costs, - year_set = year_set + OEM=Ann_Maintenance, + REP=Ann_Replacement, + RV=Ann_Recovery, + REWARD=Ann_reward, + PEAK=Ann_peak_charges, + EN_SELL=Ann_energy_revenues, + EN_CONS=Ann_energy_costs, + EN_NET=Ann_ene_net_costs, + year_set=year_set, ) end @@ -1022,7 +1020,6 @@ Returns - df_business Dataframe with the business plan information """ - function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) gen_data = ECModel.gen_data @@ -1043,7 +1040,7 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], PEAK = Float64[], REP = Float64[], REWARD = Float64[], RV = Float64[]) for i in year_set - Year = 0 + year_set[i+1] + Year = year_set[i+1] CAPEX = sum(business_plan.CAPEX[i, :]) OEM = sum(business_plan.OEM[i, :]) EN_SELL = sum(business_plan.EN_SELL[i, :]) @@ -1076,13 +1073,12 @@ Returns ------- The output value is a plot with the business plan information """ - function business_plan_plot( ECModel::AbstractEC; plot_struct=nothing, xlabel="Year", ylabel="Amount [k€]", - title="Business Plan Over 20 Years", + title="Business Plan", legend=:bottomright, color=:auto, xrotation=45, @@ -1091,7 +1087,8 @@ function business_plan_plot( framestyle=:box, barmode=:stack, scaling_factor = 0.001, - kwargs...) + kwargs... +) df_business = business_plan(ECModel) @@ -1102,29 +1099,38 @@ function business_plan_plot( "Repl. and Recovery" => [(-1, :REP), (+1, :RV)], "OEM" => [(-1, :OEM), (-1, :PEAK)], "Energy expences" => [(-1, :EN_CONS), (+1, :EN_SELL)], - "Reward" => [(+1, :REWARD)]) + "Reward" => [(+1, :REWARD)], + ) end # Extract the year from the DataFrame years = df_business.Year - bar_labels = keys(plot_struct) - bar_data = [sum(tup[1] .* df_business[!, tup[2]] .* scaling_factor for tup in plot_struct[l]) for l in keys(plot_struct)] + bar_labels = hcat(keys(plot_struct)...) + bar_data = [ + sum( + tup[1] .* df_business[!, tup[2]] .* scaling_factor + for tup in plot_struct[l] + ) + for l in keys(plot_struct) + ] # Create a bar plot - p = bar(years, bar_data, - labelS=bar_labels, - xlabel=xlabel, ylabel=ylabel, - title=title, - legend=legend, - color=color, - xrotation=xrotation, - bar_width=bar_width, - grid=grid, - framestyle=framestyle, - barmode=barmode, - kwargs... - ) + p = bar( + years, + bar_data, + labels=bar_labels, + xlabel=xlabel, ylabel=ylabel, + title=title, + legend=legend, + color=color, + xrotation=xrotation, + bar_width=bar_width, + grid=grid, + framestyle=framestyle, + barmode=barmode, + kwargs... + ) return p end From 181b7d7b950b846b09b812d05aa8ab3a25717fc5 Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 13:09:47 +0100 Subject: [PATCH 36/40] Update examples of the documentation --- docs/src/examples/example_aggregated_non_cooperative.jl | 8 +++++++- docs/src/examples/example_cooperative.jl | 8 +++++++- docs/src/examples/example_non_cooperative.jl | 8 +++++++- examples/RunSingleModel.jl | 6 ++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/src/examples/example_aggregated_non_cooperative.jl b/docs/src/examples/example_aggregated_non_cooperative.jl index 4579a60..ffbc730 100644 --- a/docs/src/examples/example_aggregated_non_cooperative.jl +++ b/docs/src/examples/example_aggregated_non_cooperative.jl @@ -42,4 +42,10 @@ print_summary(ANC_Model) save_summary(ANC_Model, output_file_isolated) # Plot the sankey plot of resources -plot_sankey(ANC_Model) \ No newline at end of file +plot_sankey(ANC_Model) + +# DataFrame of the business plan +business_plan(ANC_Model) + +# plot business plan +business_plan_plot(ANC_Model) \ No newline at end of file diff --git a/docs/src/examples/example_cooperative.jl b/docs/src/examples/example_cooperative.jl index 623cfd8..d904e56 100644 --- a/docs/src/examples/example_cooperative.jl +++ b/docs/src/examples/example_cooperative.jl @@ -42,4 +42,10 @@ print_summary(CO_Model) save_summary(CO_Model, output_file_isolated) # Plot the sankey plot of resources -plot_sankey(CO_Model) \ No newline at end of file +plot_sankey(CO_Model) + +# DataFrame of the business plan +business_plan(CO_Model) + +# plot business plan +business_plan_plot(CO_Model) \ No newline at end of file diff --git a/docs/src/examples/example_non_cooperative.jl b/docs/src/examples/example_non_cooperative.jl index db65ac0..2bd8852 100644 --- a/docs/src/examples/example_non_cooperative.jl +++ b/docs/src/examples/example_non_cooperative.jl @@ -42,4 +42,10 @@ print_summary(NC_Model) save_summary(NC_Model, output_file_isolated) # Plot the sankey plot of resources -plot_sankey(NC_Model) \ No newline at end of file +plot_sankey(NC_Model) + +# DataFrame of the business plan +business_plan(NC_Model) + +# plot business plan +business_plan_plot(NC_Model) \ No newline at end of file diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index fe5708c..e9ce1c4 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -45,6 +45,9 @@ save_summary(ECModel, output_file_combined) # Plot sankey plot of CO model plot_sankey(ECModel) +# DataFrame of the business plan +business_plan(ECModel) + # plot 20 years business plan of CO model business_plan_plot(ECModel) @@ -71,5 +74,8 @@ save_summary(NC_Model, output_file_isolated) # plot Sankey plot of NC model plot_sankey(NC_Model) +# DataFrame of the business plan of NC model +business_plan(NC_Model) + # plot business plan of NC model business_plan_plot(NC_Model) From 542fae0a901ff75ff52e54d09ce97c155f433fd6 Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 15:23:12 +0100 Subject: [PATCH 37/40] Add test to business_plot --- .../group_Aggregating-Non-Cooperative Model.png | Bin 0 -> 22559 bytes .../group_Cooperative Model.png | Bin 0 -> 22253 bytes .../group_Non-Cooperative Model.png | Bin 0 -> 22177 bytes test/tests.jl | 1 + 4 files changed, 1 insertion(+) create mode 100644 test/refs/business_plan_plot/group_Aggregating-Non-Cooperative Model.png create mode 100644 test/refs/business_plan_plot/group_Cooperative Model.png create mode 100644 test/refs/business_plan_plot/group_Non-Cooperative Model.png diff --git a/test/refs/business_plan_plot/group_Aggregating-Non-Cooperative Model.png b/test/refs/business_plan_plot/group_Aggregating-Non-Cooperative Model.png new file mode 100644 index 0000000000000000000000000000000000000000..d43e909b040ceadc268e753b98fb4bdfd644ec20 GIT binary patch literal 22559 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 literal 0 HcmV?d00001 diff --git a/test/refs/business_plan_plot/group_Non-Cooperative Model.png b/test/refs/business_plan_plot/group_Non-Cooperative Model.png new file mode 100644 index 0000000000000000000000000000000000000000..e64bb779f6ec448878b8aab066cf61c8c12400fc GIT binary patch literal 22177 zcmcJ%2{e}P|26uUg$$)gWlCi#Q-&y02n~j0s?5rekRe1KAyYC$q7)&7h$LkyiKH?n z$rNSEtTH?M*6+Xm=d5+!bI$v&^S0KvzIfcveP7pS*n5BW^@QmjKfIoSi-ANUt=H02 z(t+WXRkYLspkAS`fSs+(huJ*R!Uuy{@eMD zUrU{@OKfm;&YwGqJIcr1<x?j3kiT&ynf?AfzTB8IsqGCyYCzhatWxTlQv zeS%oYlPB}zFDE8>Qd@=vv{c#ng7Wg5iXGZ~eve3dOrLAtiN3(zd?P_WfS7GGkrc z6a!(3fgeATmHf@}3>W9-Qf||+a0>_sJTEP^-^a!7J=yz;$A8In@Z+<)wPMq*=dRXDN=k7Cy1Fjk--KP&HPB*Q|%gbw@U1L>41Cx4TVd0H! z`!C{Yu3u-0)HqS@^P5a2X~jp?$`^xQr_w>MQs7ieN_%SRjEPA`Vd)B#6_q4RM z1RrAK@tAWVzJ7ib>FMdc``<=xm38he!{yoavJK4rez!e$?bjVx_&utkqOx7V^H+EI zB5oJ!$D4Dybo$=G%f7z8%CleTSz{Cv6BAE7JWA1sg;+YY`}kc>r7LA2A+^6ciX7V? z=d}!vcbC6(8$9ml=vda+U4FU!@fiiDuTRf@NIK}ZuuoEQda|z?PyF?{>yB_H9!1}d zLR*iK*2I1G`iOY$176~8KO26Jwo@6GT-a_CNW1=p`*8D6Q`+L6vC-b%=-AkceXoP} zoO-5w>*mdkTwDtS?`|T1%Kmm=b|_xJ+hrb4e&M$$vr>AhyE=j)}{Qe-EpvAuI-d{`{D%aB1{&9^PEuXI3!O zY%8*D)8@_Bf`XL)%nrBUF*mWZGi{TbT%4$^RdS!~RUw%pHSiTnVnTvZ#O6JdlCG|< zi+?Bl|8`yC%fYSusE;qX(3O;saL&q#vKMjo{??wEnVH4qzda{~H<8TEdmbLUBXjNINs3E5W$JX86O`Ptoi(Tb5%_QM@vgfUT*Fri{H{zt!n7f(h?R7K`5%E zRH+h3>$mtP_?1K3qaWkrSFkK`26*7cMqPFZX=$hKvU#Kjs@~YIUrZ0$50OJ{v@;~7 zr8lYT>+4r6j=yxST>cZKv?7NZqn&Z}mHeggrTM}8VyBE*J8;9~Exi)X!Ecm^@KTTIq_qt&YO>|rw^@DZ|)LU0~ z_q!6-Z--klS+>a)_|8vbabH>N-MbetpM*s|dbGh8zi+a-vV892U3({|$ibq50%fgO z0SZA;(V$=GAtV{+Zc(ib+J!|$HwD%eqDz#%d?|M3g?kQ@G7)K??(=Nh#-yC8d%o+1 zw4~%k54K>{rZVrDU`7tZ9K-cuH>0C1E?i))diz!*R-jB5#R653dAt0;@bLV~;)H^{ z{38Ql(#+teO8?a>SFR|AKl$$)pl)*9p{(|agPq;Iw<>@LG65}0T|9KGq@<*7Y)Q#q zvC5^`n>XDM=jcls>gsz;3I}l0$hR61-JCYkpRrE-cF^Dn;rPD7mzO5EDqiid>^j3|u<*;xc_=4?1tIcV1D2vX^%R^sdDneu( z3Smas5#F^+gFb+N=H~~hrcM=d=shtZp>^crnSn>sn&|mrV`Fuj<3)^8qOiCdO}xCG zzkIof;*IXo1Z>N8Da-YHo=>hNz^u~&g~ygmx_XeXy%xZC%?jU*+>I58x$ zK0tFv^?2d)`%!+rm<_7@*FsyyaPL3A5>4|oBs@Jmh2uxCP&70&s`N|d>p8_OP*s6O zy6AX(dxwTXt)_9W)6)$fKHzT#M#c{f4Z7DOA{fbc?`|V)*|G)LB~~CcR5jJb(~~y( z=FLFKA45a*y{LnNXY1pH2)yG=_pb;Zzb#Htn46n>N7i|~qiDC7SiWojTl7`cn8wCN zgyyC3Z}U0%6_0dNU*oIFl?6u@mZ-|rm96S13YUhPGf*mwatuGz)a>Nr>kHwz;)SI` z<#a|$7)^dun7Ga*?a*E3jnG0v%RBS(S$zD-)2F{>pU5=KHGX2>@(@i5@JdNgFw428 z0!0&jxp~;VHi~EU=N;!DRs=Ie0Tvdw^?RZRFRSb7I=Ewpb>}lDK!jF@V*HAFqQmFh zL;)=n{w9ZFYz2VFnP=xaaOvc`cM*dsvzu9Y`4f+4F8=9s*1LY^&M71n-a;%$IY#T| zuI1nD=7>M$_3LSPwVKb}_d9EAD{c9jnmrrE$=!Vxby@EyvhvEp@b0q0Qnx|ey6e}c z_I-VQf)8{BQ9SS7y-UinakCM8(B_bqmbSdKM3R$}OV7wScJIKJ8iO1|qINhGbJ^B^ z`sBQ_G@o`jikXEa1o3$O>r)|qe*XRYx2QQ&Q1GXnv$ge@`FSjE0ASGCI#3^&#B=Pc z2M`g`@Xnn(vW}mz?_&s$u9uhE?e;|Kc@6*? zgRSZ*^KPlHXW026N!k%1uu0fJ8MQzHn1pSI#x|uZ2lnng9xHI}+#rft>5CVt58E8j z0i3@*cb%X9k)AFPul-|q*repb8NjwWgmKcp&W*5nn4bQnqr?01WjbnVL3V}%_f=D$ zd(Z45q0JoVi4!`Z5y|yqWF(xQ$8S~-p?>_{fyBFaPnTZ&ft14^Bf!SpyAS6X9{M>t zdKpiS4i&f0@^uiZA*wfPxeTS6hEA-2j*iaK#)&%JH-^`&umD@%Pi{lI6MXya+qagM zjTv(9bi;##iP}Hq+bww@L_n+9W^VU^g9oQ1Z%D=&q^bs^&8Bb?sLR^HVPtqXO#hcH zuTL^{XlUs5+axx&7`BbP{QQzul%N6{NB=_w*x0s6P+)g<>{3=!vw8D>ezBm~m6iEH zz&{j-Eoy4Bo?dg4eIG;;D7KqvXXkdNqzY&WDj*Jvi;K@ZwPQ2WOeuSQ`7a>L1q%z_ z(9oeVmw`IAmf5(+iXXia^KUF~|LmUGcY z3PbMl!}agFS2j&a7U}d1cb4R*rR}Kyh-KWkapRQa|JNJ;pL)vweJ}PJ{cvB&AFXo# z{{1|@ldl>QrQ)@BVmInTt2)6?4z)Zy)>-LasgdSAQT`W&b|26aD;rxpTR=%CI0`NE zwjppQi>0|KsdFE}3IzV^chMjKmdUw>d8PmbEyL~%!Nl6ws`QTCN=kCDwWUccF7`l` z)zG+xdMm^sW`_Dae3gcgS4u)6EHW}rFOAkLQa}rJl9qMXF7MJ(!3?z4!#B1CSU$VZ z^)>YnUEG7XxZoG6oK@ex@k>cjWy~!un!j*gcWoY}FUKJJs=k00ikLV#GV()zZIqri z!5TJfu(*u^P4FImA%X4qsbP-aWG{ubtBZ@~v17p$>wD-FY=>n2k+?MwkdGq$|19rD~Z_nd0OuSn4SFifUO}FSM zt>@ZDe~sv&=vR8-iP%=R*+7=$k^9f*^3{r(1(yjmQs@=TeRgti3Ru6deyadMdW zyASWxh0vkvQnL2!z7iO~oX)-c) z&QAUPyE@k&1&9(Qd9ow(ZE~ZXL2dxYqMP;mfe($D;1|JR^}}O>{kHOZoE{tIq(+q{ z)=YO-%AYc-Vsv_0;CgC_cIZUW*$?&M!W$1&ynX-F)Yv?;MTKW$#_-Qqbh!qfy;9As z67GLIK(9ycnM(w7ASK`^8cGO^5Y3X3a5T*UQg#^kPqiQ+coa!bkR#a51gr^kv z1Wu`T%y&3#uAuUpo}NA$|8TL|8ac<}n>Sk@qR7pCZ%^{qO7l^wvc|OQaV)n=0u@V3 zOAUobw^#euzW04%ef_lz&%$%VtS|2es}0+}_6LurcXLFIx59-So36DlLzjRK$?k)-&f%PpNi_t-Sd0QGRG6JNtnBcLfQ74Pq@m zmQ;w+rLEN67fwDhDD|C}v_X>O>Su(jrjpp+{rdHbUAKyrAviO0cW4$n@)0PSwjSRG z@shHrBC+bZGU#prZV$F>Fc8))<(>3&?0WGV{CVp>yXb@j4iLSmTAskrp<+EjcJb@D zYCI#QbNMG5)El4m)I-0KwY)yZ+{HtWhqPON4B-6aj3wFP@TX1IREAxnm zh~S45?7DWvrbE`wj&#+x6O&YVE?>T^rC&v2sCxHq-GcF{Q`O=>H~(xroYo}BzJV!) zJTNdo?I*fN)RI%Am}@~!US2o_*I)<^45XR`O&7l&S-gH0fQ3QO0wTtFC#Te??67rA z*3Rb;7kBUKBjyh`VX4I?oGKfw1IYd?LO=!-cgPS#}@vbM6~ zKYG-HgzYvrHy`RB9gWy39+VMGGpm==_EXr6v+DKh2WtV?`I4d8l3wAI0-oR5**^Ok zMXQ4d3k%Co|M*YlYP;m8jkPs3HQG)zRK_ORXU?2S)d3E%xUIcOWG$evf}B;_PCtkx z*~hh>ssG0?jUNOH(LFjrVea zU%8tL?7{E&ab}yg9HWWGR4N+N(3xkH&rLM18&#*QL6*=B10o~Wlc90d zfjEw~=PTLNu^Jh*qoFzVRFne=XA|Y%Ddh7ftfnqyb(}R;wgk?r4@Vh=+Ts@ z$jHdFG;5$E8&!G*1qIxGd34oQ_tDRLIIC)FYd?Sf0|Eoy=IP;aOjDCA%EzaY)5gG; z5(S7=S-Fa<6Ch1IpyUwlPCG+ike$TLqj>k;z488jsuZw~tup85hSH)0v`m3xAz`Sf zsTp`ucQZo}-G0!=U0GTA?Af2d`p_3g5vLYaWQwMyrah)l3yO*s2};O)km28YdD>wg zz#7pFPYSo`C@l|s|4s?=w$dM^#LRu*_MJN~FaFSmWJc`4_JEQ=0WE1;!J5|Ar;x4Y zKE(VlsKF!UNDQziYj#JTQlQ^Ud;C{tUapZ-+awjYQH$u`AVgVBAJp` zj0fLo<`@+7qc{&+EzLiXsGgL(r2k!jc-D-I<4u~3QqpFE(#w{4w9=`#?I$HVHzK!O zOVIMZE1G1ggS!l4J ziGr3@6-@uo{STe8nd#|OBovv(OlYnUhs4Q1CFSJ@XFH1gbxcjUn0f~X>HKJ!I9XX) zNmRzBrU6n>uGwD`d@f(tI(inhTYeC90Wb}g!`)uNinXWF0YM(2gzGjr7pKbc=e3()H?rM>Or?^nz zPP1cqrp1#E(8!6Ol-&7WAYcDeoQLZfR+WZPw5N!G->O0W#D5`#eOMXAH2~b^NXu z6bLLNh1c`|D98G6pgHZ>*`~uh;4bvU<${&jl8=`FW}nP46k=x}VJ2`#!dfF)-h-Wi z#s-r6Bg4F})gdg>Tg(0;fgY1eL3nQiOV ztqKY+spy!An5zL7rzyl2An!n)M8)EuR2R^Ku5?Gy??qIU4(gt6a{v{c;__?@v37GWnt`@FCYyR`A12C=%axdR|s3o8CIc|>Ru_Z1^0V`F0UjE(JVZG)|vo0}h-S3=Sr9~cO-f&dr`(F)T& zOzzg|f6Axkj3Ww&gEof{79Qr5loVc3(O+MlSYxVDEwYHG+riI&0b(}vFK8><)McE$ z?n9r16b7Z*5-6GH${ZgV(gi3K=3dobzsk$XUP441KYsjQZ(krrAQ2p5+!*`hGoo=YER`%V?|CyI*l>vKp`XGv1{jC+7tz8$%^RO*6?d%;O?6a_mjHjjoA! zdgj>9tc>{U>gu}m>q}G7xlfqr02p*)C|6Y#K!r_`bo?wztUp`8;2n7n2VdVM&~RC& zudNT%8Ox3X)L}8PBa%=L@s^a3LLP^N(AjWvbFW*s4nn7P#)b?VTicr2+KJ8*7D>p| zKrq0vxaPt8$}vPHsi~!;FD?1Zgzog+7O`0DJAW}Q-9Q*g7CBhg(6CQhTAj_! zZKk2&C<&83Btmqy!6OKCzkZ!Zgv7;0N3(9-x;R$Sqxw)+P#El{*x}@V-h*M2Fj44i zUv+lA#Kb}nLi>UHMMZ874#MFWMK*SHYyo3G2B!WCAmQ`p&zRIT*5bu(gCC*I(LqCc zMIF8`E>+a){iIH7_KCrX%GE0%^BQUI-o4w8sbopd6~+_@zrY@&oh4#qtZp0jlGu*J zy^#@7Q9aNWf$46>#@^a{W@LQ48wz-RyvQY#6Z6U|kQ=eT^!UTXL@n++H;E9Sp{ADG zzuylECPs%u=qOOCgM)A6;Aqb^kt%D*uUz^1!o#uHG;;N3Lqh|eYrld5rXR})B0%fH zp`jtP5n!CU+S+p%#YN9AEIiWogM z$TQuvx*LsD>TH9u5Z2>F=5ckQ_5Y!9;aW$JBHsDTG%{NPPy2LMH-E-VnDcFO^9C%!svo6#5Gzzt{Qq0E|2qlpX_jlze|Z7^OO7Ke0!3qN;qaw^ zFMPl2ceUzk>pkSRuT@%#Kuq>P^oYpZL3xds!Ff%lKq>!MWvnpzSsc#1ulhMlZ%3Tu zg>Q2IURcu#5ucx*vWi7%sSy)JLN&y+X7=x2=e9@xsZds2?@5>vGi{Y=K)=Nu|41S$ zLXMy$q(oM)8yHSFcRm|~JPvU{@GK_N#O{bu&n9A03%39UE5)XH+1WzGtq29^yUTY$ zES2~C#kpyd?YVPwC^XYYQsTfNckA5q{nK#{f)#Ovc53Y>B_*&JcrVPZ{jNfQKH#(B z>e5VIT^;RuuBBv@1PD0{`~o%j?lBW z%RdD*nW$JK+!Q}T$%;fR(L!@TmYI}!x%O1}{j0#7xj5qQvd zj#c@>g}wha&hO)WrTdtw^UEiX0}z?}FBb;}9zwkb#wUt+3o-+;blgB+-xJRCAvl2LK#vk#nId1eg3P9Jt*hIE@@E19HFJ9qchQ(y-|DQTj>mDJDeOGnN4<+ zKC`3kI`mB`DuJj~onOCFKF3Ud_kCYV6AO#pj3Wue7I`8}pFJ}aj)w%B13fjIP(~@f zbawXJe*gX*29wZOAjZyTd+v#0T*%AID0AkC!x)Ndytb{jb~s>>4G;~WIeOEH zoHoH9pt4|OuvA=ac=N^v0VVCw3e?p!vo7v|VGddzK!WwDQ=PZ=oKp2f(Og+xY|qlG z#(+S~ZR&dskO$jNq4;_@XTHyC+}O6x1mk&Wt`9%nCpfldSvoo{{_2pbYi?;lkq2c? z)dAGU3`y4dEff2bq9Q816XUv}BCo0Mz3?tXv(nMgVPX;&hb*<(y*m?VvYIqzJ6EZn z(}vE2K^zGR$Kx|EX*;mF7{~y)%>v2eXaAB}@-`WAA~!V9RZOv?wkzDad-vDiU^`Y8 zV7mj0760SM4@~z((Fx&=*|X;Z0Dhh!lophT#f62RKYpldXgq2g!BmHmo{@2J;HHko zbxunsr-S}0OBH^LM11_BKM-P?$G!KE2_TEEot>Z!1}9kPGk7}KB+!mm=BBJkSjsPD z-bI+Cl3@+vKIqfh*?B5e7xshe+y{x0O!O$mb?dZ_9aGl=89Az{Ni^ITV$OIQ_Xr@! z$iQGAyjCCM$cmVKB*R>Utw2AznV1-618@ zZ_mfa_vgm^Wm94e`KVkY4Gm*cj{_u zYu96=pox8b=ER7ackx&Xd$Fk#?&v=S`FILDBVz>i29A|q$(Oo8CG<*nr9|cDPssN8 zwiH-aqeLd&xs!Gq()oW1QbUroh_v+YASOZ#A<97+YvUj&DXc3*&6%K{#po_T5eTMu zEyVP^`7Fp-7y>`STGS1(2Bm1NDh3Qf7X{+HapOie;1S{o1+il8J0tN{J17@G4aE|G z(~e{tFz~HFT}SW|;My z$g+Td0L+`uU=Rhdjtsv8@#7PeH8wUj5I5Ry?U$2Vdz6iWXOWwZpL0zreCMHq5M~b4 zKEJuX5aQt~sl&5Tgm6(bu`;YWY9JsE^rV)Wnhd~ZY3a7^Zf;^@B9w)+GYXS`^;L%; zzr(}BU-++9pn{{b`v7VJCj48^PpCkOF_;PuV{ul{yunl-v<&}i3!GILufDy$1u$e8 z7?1eo4SLR_8Tx*cuju}@0ipx^ugv?r`neT-pX1&;OPrxj{RM}H-9-`<&G!8*gzdt@ zg6Bl{nQQ~qTeok|y%(#rK_wsi+6Kb?-?=mO=g(ukw71Bwhes2q@c=Jgyr3j3U}$lG z_zLiOoIx`%uh^yx!waY$@IY3R-AH>r2++!ZLY^%^b{wWF0MKaZ3*WpT@55Sf1Q>kf8sP zV5G`2eqx1?BH8Ur0;xf3^6~K*@2RW=V2mbAFlZ2K6!u0{45yGtFLU&V6<;p>S$L{u3lt*b_r>xV50vg*3pVAR)n~4r`u>K^7@RHJE{L)^%0- z_n@n^wz4l^W=~B^3#LI}APdU;fat#cAdc;+heEK+<)CDO&2ZKs1!19k2L6G_RJi=- zJR+9#)V}2`@bV6g>!bp(c2GVdLw?bhxgPYHZ5(!Iv>|;haaM9@g-(kt&AzFT)&Z;$v*R!k!q6z6qjgE_-c6@23lv7~*k+*)s@3gBIF(^o^Y(rFks2Ze+L zUJumIz7DSqtU>ijBxoWnP!lSb;3un?qqLz$)-4Y;?I@%De;F*-NBpye_WmwfGgVSz zl!JU$x9efC7BiQ9Qt)~-T|`vSi~at+KlBEBQ5NGlI=tt%bXA6!)0^ewWF^m|PNnr2Bs0ov_LzeP{T2Uw}~5PxYQ zgNNWMpU{jE=t4q5hy~-)iy3&|c$&5MMVKKckZ8Ku1|(?V+?zIKv<&k`gokrM838n_ z09glEPYo?9D$2}-Fpa8!j!6Zj!Ozc6@4A2%fpCz`L@2?62QrB+&H%UuOF{e?JOPjh zkZ?5+b4YkAFnUZL%3;QWN9k}RSFk>W4Q_~<9l$(*s_POnGc%*2SV%lP_Ysr0R{ghc zOTe`$g)n!i-+-&kVDs8$L6<;*J)S6;0W1da0x1e8Di`e%7z7~_ zY6T?c3SkUJEJT#)sZ(hJ&dw8@TWpXGXE5&h+vBf<8-hMS6)j*7D;6gD+vQwRG}0)D zFn=)p`kO(PtS`S+?xg%M10zGQvJlYpjE&$~R4qY!c-*7MzC5AI5IyybDFgXJSpy3< zQa){MRSnE?SNF>+Dst!F#|6Qxh@6Iq-5m-KgRVE9GH4jnNax^0#9*$$S+8UU29Lk@ z&OSWo9WEJ9SeT=GCnll_0ch_gCJv8}GdE-Fv&!l|yx54Q5#W9XkMI7dkBpU*bfppM3pfhYxu!gERc49J+T6Pk&qc46ix!N!>+nRTDlk) zL~KwO;9oel0r05knVJ7gP3^}K5O@hm*SAPvPCr801&)F=2JY>*rKF-dp|6i6N=QmF zg~X4?mUiym1{={a8=G_i0>*L4UO4URTUJyw0}e6KQ<>l9aOU*s7oKBD@OkPZKA{B1 zCnO}?y7kPpUjtki3xtTkDUNQ8s+7IF=AfM5?n^9QT@Bl~8zy*?i@Q5DapVG8Q(c`V zpBiKcL|_qGqmJUd02E`?&z>ubV-R_u6~FYFvLa#QBf`R(?Dmlm1yX24!2aw+oy86? zb^x8iGZ)FIcdVV^x=mqSi{w%dmRng7cCIZaLaaNL3N z&%QnibfrNUku1o_`=fW|rhvQvF8l-n8#+2nVg>429e$(FVkC_s@YHmV4f2$*bqk^J z63MHh!vnAhRSa1)(e=_uR~Jl^hka^#n$iVn&%?vR4(D_4&!4-3$PJyHi-}LQ7<<%x>*~@=2f4u3$YalNLg&DN17N3<4*_fROd9R> z-7Cac344h({My!5461nQ*?AQqRNO#OMaAx#8XDR;oDBj**}P!`r3F+TFU35=JMr=H zu%w6Z{?Ru(Ah{xmUGU|1Y*a+A5Ge~j*Rhme0&@7YN zeW(#He`eQ2S(DW^0uYL`Nl5v8^U4ZTNiSybWehp|;FUyvmqRGRLStJZ2Eh+7@@WDI z1#bcdUxhdZhxnU!?}iB=1jD(D%OsjVA_{xjg&cws*V#cxDLU|k-#fT?Q%A7_zXuuY z=coHDCdS5J(zR})EOg?C3d}B6h%o#C$^fMSn*NL(M&S?CM-myNX{--ced5$9Q>KE> zHd~KD?6&ROiRMil{Q$_s(E?x04I%0IP7l0O4MnQ9<(u;mO`-(9h6G1>!{tf8#>OBX zQJBCL0rgTl3Gy!DpF`9bXg-io1jWQy$e14iTj{1_;s zVffX}GnHoFB6apC3Jh>s1yU68_z?XbaCmxlb{h^+kOBE@ZETpf%kSgkd)3n725TWu z3xFQd69*6i?%W~dFJYs{OeRDk=bNv>aB({+Np04B1VmPuxKzp9Y%h)5iH&8)gM;NyNJ+)9Z`u@Wr5Hys`S))REbH3# zUkWU}yyU{|MFY=6N)yaL%>Z;!)|Nic?EC`Kh2sX>g~4w#?X5)Og|A(JQCZ_gpE zWgEe_ot>8v41yV;VyFST!|@pNN3U7Q*Xtg|dY(W33v6}r>^g!o>7^Qn-0hO%eP-tQgyw`%bAtkmQ4+1Y))&9HN;#Kfe`-1L{TO z_3PUttf+0kVD4i&W@9seWK&)%iRFcmM)pBY#?tKH-_hKB5m!NnY#nyTic;aY2%)M& zDyf4LoCHy!v$K;hPJ&yIF-^Ax>Hx=1k=5+&iB3-TTV3%xeE2ZowgMifL%(11;-LsX9=CRTz`+KC_+OH;f{{R%2sX*&QHin zR=}gA16P(f3(=duy}ZOyh|&sZqw*OtBn-8_X!1B>)CtAv?|;235>5qncmiFad9XuI z{P6LkVYY!FAXnw=SZ6!XY89kE7%g_}*rB2}dkc6Bm*WmE#b1QYqXP%Tdb|g>$9=~! zN!%=WqKreUW?B;&=2-tvfMl44OWHRJu-l_(czQmAo(S}b$6?{cc_2|yCP^TFTypc8 z5<YYxkb1Xnnv4k6KLrl6Qyc#ONA(ui;Ig!j||p{;hb3|puw9r zZ^$d_Df3?uH*o$7IW|uF2M!6#ZaYqt?NcRtZ4i(!6%vONWt>d0Ibc4P_V)A8 z#sQd#FvRhbqQb&f2Wjn6lvTtTUJ<|#Z86uZTpk3kvY?>g{*{Gnz!xkt8Yz$^0fs=e zuJHq}VjLO(F$Pl@I(2LlijxEAm6vB}BXZBV6gR*tG%1>#R7( zYQU($(a!EX7%mP<1+h-=UP<=PDlXpSd=Ft(*Ln3-&jpx z$Hzzkhd5*5?}jC!^z>dba#T!A%o)0clamu1cW4!Kd_hZL=;0?%p6vV3^7->UIamI= zQvt7f_5$pPiSOG7Nt$CNRD==lESVbdnrlKfTZD0Hmxt15g-iZ}c`Yftnu0H=Y|p*0 z`yV7Xnv8aSKjtv(J7P3;b?{iS{BLl)A++MD53)UECTb>nzMxqXbO}|}*JvMA-!GQ( zQc+W>((^TA%9N9p6>5em1s~+=9~TMN4~ic+=XYp7bdyll!0vezJddiV@CgXmAk0W8 z9q?8|;vfW}2M_qc|Dd3KtMKb=YSJ?|_s6MB%zZCN%#fP1P7sHRN|!Cw|FJHcdgafn=0>1#?%i!RkKm49n zPrPt0gYX66$Q0BcoWntd|0I4rJKG-BTUC{koL{k^jXmOJO-Vpm0(Q9-A3rsZYBZP+ zat1~TCG?KCo zE_$My{h{`w)qZ;S4o&)TL_|bPOiXF%JghS-pyk9NUUbjNnVBQUk9QYX9y)gH1L}QC zb2C67RO-`4Ms&QGlme(CV{k5+k(v2ML<9js0it&wOES>cZ^ntI`Dxo(DI(Z3i7Q~y zUvbHt=QKuYV0uErT%_FQT`WLzQc@DOH1pv@kYUQn-@gySf&k|{89)!7a7r|(^DvTw z{)AK~%6mM5XV921s=2rTTm_buErF*a!zRmSXJ^rpmxM$F1?|uUaX->X9jq=IC%_Vl za$8&5tEAVI1y(iTFz-Hk^aw+V&z}u1UHS_h>ZGCJXIPPaS9O@j@9B-5A|jU~rO!5SB0&H1#MMZ-!4G#r2JP+a z$T%TCi}C^_Lc9%sjHqz|=^)M&RyDV^(Tw8KEv>BsE^-zSxdLo4OmlGX8EWK1+M&@L zO_Zc5*A$3Fl%MZ>c1%TuLKL%z0uU{nI5>}SlcQqM;O5=?0s87JYySfjK{82mg4 zdGbWUST&VZR9(I1*)tguj)jVgiz|y^bPHP)JV#8`WF!|guaFQ#ZVzA!jGBQN?#ViH zFKna@VpUzyzzS^$8vs_Sudnyw4ymSiUsaWY_bV?ygL|n07Qrm|6~nr9@9OFrKYw0< zbb>19)8`_GZfRb0HWFr%dnGP?su%m2`7h#tVYaqEP!RSe zL?mp1y};gH$fs}0OEpy-oDj6U+|I#a{L7Pmh+lG7mY(P2jG}L~wzV}mi}MDt@~;QE z6%rMtK7t2@W87a2=gR>$VA_H@b?6#Xc5d###ZrXY5G=;6&CU96Ep-8vGo`eLGzp2K@d7Pe8B6+F~$ZNY;DB`Zsq1C zsLa!+PchKwMv;8+LS9^)II)ddfcN8wsL>Xk-Mv<~6U)o}P|9GdFa`z1m>ii3`!!WW zb>H0lJgkD0a|@&S0RN&J!mg|?U)s3w=H#RcaZmsoIOGb(hH8RL5jycuPCw7`>wCbgw~7)A0A(itL(_t5!Dis$fo8Z3oHeDumaBoSn-tOJA_ei00l<^}It@0d z31|zmla>J)Sy|6LMhy=*W3q;s>Hd2q=Q|{43oHYW9C3HF=@}H1$)7?ZBK_+-EBr1WKYruk!?Pz(wqZ0(%!Ns4Al(>vpp378y>1#}evFX%`SUCy1~Zoe9FCWy^tg# ze8^^MS6EG~h-|07fAdw@qrt&ut;Sy6#KC-z5d-e1koMF^j~HHCDbw>gN1XfgB2&S& zcFTL90mp=(H_yI%BVgc|AVJj_85+VK3$rs!l@RU(|CJdY?kKwHgntPIZw4f+ocw%H zszq z1xTo2wh(w!QyY7FRNP}ZaWKh^ zP=wLpVH{?}@%m%8#Z~xYa-rIQcS0W_35$qOEP-bsZh2-Ai;&WnQnr7!hTo!?v~tabwjj+S=P;r`as(;o;%!?H#RXfq+rdMw}yJF}gzXxsQF7 z#stgHZy6@H1D}A7_U|u!{P+U+FTxj_2OR?+r9+EP9H0RCdhTN;$~fcs@BGasSjjhToWUI4)7>2w;BW8WJEEP#xB+YcxrU323!#eA zj66eyBc-IJ6(OZ?q8-@E`AY$XKOFS1;eUlp({Nh?-cIzgSqcK8`N`muZBR(B*8I4L zI=BQA^*ZXSkV6TH1G~dijgx3I^S&fuVazo|#U&&tQcwV(+M+KOzzPdKD-F!CXHOoa zO5ipwQJk#~P$u1qiD`nC2Dl4P7ta59iAGn3uN5T&gJ6l_OkDUgdxWL30pgu_^HVkGF5%KBxtYc~#%gpeAsXp$O8H;Z#eNcUmzzQaZ3SGZtv;mBuV;xGBV#k5SvGJ6VTDpvJc}7 zWm>vJhYpodpkLTnFjxdcS6Jc&K}=Re1Bq6-{$Fn~;GYKv+KCf4Ejd~3?VO#7>8`Z2 zv`YE_<^U-9bTP3QG`T6;e9tnrWL0i|uTJo~?uE)`581~-27xy~#ynwj> zGUorm_VC%TT4S;5P($W?73qf)O=_W50w!-IeXv|0DQ_4DVi zot^kRj*8S&KBy1U(jEwNUcUk;)wn9=X#tvt^z?|&HNm(6j-~{t_x`IZkj(;?PJ*mr zP>fuFIgGcNb@OI?euO($T0Dk>|Bik%AM0z|uwyN`XwU&=6%QZoVh-5>;aXXc zeKQA#&K;Q#Xl;NGd7k4moFBKVsd_o7r zQ6^(ZhK0|b0n5;O`=N{CoC5l=CWgr;PEbwaL(KNe%i{yqE?{=XoBuE;=ZNTLkTVy# zyYPczjtf&V9UUFXSJ5AljcMUu6b+>+av4)dH54#cD-7$Ejf_fia&n4_P6)DJT{;Pe zT)d{h5X!wJfabIV9vd^LQqYTnr^UtKH$&gQH-Gzf_x5eU-MgVX^0PCjm>t7rBG~Ng zvS64s#%)4b!@A}Ig8&o5yBBT58>0!=Ee?|c?x5W_BSDt>c~-wXwZDnDLMK8*5uQVa zVAWX`q;T)dJh&Nqm>McNAP87z5qu&bjG8sc3-~Kg5)KY2 zo2lS`R8(F;1-O%x1QG~z3GDzLn@3>G8^SP`!Q5mL%?=t8#K-hg4PZeG(jbWDI_@Fj2yw)bQjcYj7keU@c8j#$cs3dJOm;KFVYrH&gb4UXQPckGXlXY@atgBB-SuB0Kypv<-MR8 zBO`DwuAtN77*bYtb}BlHSuN-0&^W}!w{NFGZt&RC4r=^O$R~t$ctk|ke{N~#jvf7P zDZOn`P&xLV=9D>aY+%4nf{G4GhcjCR?j9Z(YOihj`f70~UU;EJB=^AuoU9*uH8_Kz zYv7e@6nv{E0AYCZt8qL>6wa{k->opU#`u*D2nHw>OFtkzJUSW(R7g|xZ#$~0c#(1< zB1aI1Ws<0J(9LHtBm6ZoLWyYt!desNk7Xt*)jBe~$41z%pFc6}DF$*zAVgOnaVUF7 zMjmBkY}&R>6s4n?4vA#-gj3nh&JNQDWo0s2;KCN#py+^xk01Bq)2N{Ez)~O$x;0#c zzK#!)p{Aodtgap~i+Ra)W6>MeuOo8>g@pQM=7;=&4`>Tf>@yxdB+SWpw)9T|8{UD1 zY!)-yh9_x0cY1FLbPtq(^mH`{OXi>nTk60^jZ94;GaIwo_qxa-58EM2#Lz;^#-| zxwBchxld713M^DEUX%oUt0J>DM@cZv{75i2H8uuw1G>N3*44F)F&*9)77VtaHApku zg9$d2L6CpY%i)m`2ExLDTv!0C^g6&z2KNLEbt^rMJcF`e_QuK;g&cq-O2_!cB7gzvW zj~WeVfe#~_#VI(P%HXcT=|FwlU2H6LHbgFQjE$957qt!_K}N3aM=&`8Ivn7hfVjjT zV+ZLQ**BzTU|?fu35!e^SQrZ0)o2xp8JQa>c#MqJoDa*&P&N86c8BQ>#S^Cy zw;1Et0BTqR;`qoBD%K4fu5v>FLO0}($;!wGuI*1feC0|7P|!c*YiR(gB~br{d^13KP)I4O$)^zu`xz!ZYii zJW0bBxD`o4;@h#KTKqcXbK~{v*AtFHe707U88VncQ(yz;2|!N^b8~>|IgcJe6B&dR z1!j147QC8OatHvK=_pVz2{vGitR}C58L)G3Omq|t!UaSvf)g&E`d#FBZ5k1xTW;?< zmXw~7GKu|^-@m_)1`~f0)B5#I)*vXnL1o~1HWtl|jmDVpg6l#?K%q!`{CEXb4C#xx zyC2RMA+-P<(DBegAen=%gAY>j4bT8qQ-z?QN7WvR!YCSwA6!W1Y-~u;F)>7s#b}*` z97jFGU=h{@C}sFOA39KM%*$*!X^~)6{Zqa{{{bhRG7|s* literal 0 HcmV?d00001 diff --git a/test/tests.jl b/test/tests.jl index 512c4ef..4599753 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -38,6 +38,7 @@ function _base_test(input_file, group, optimizer) @test_reference "refs/sankeys/group_$(string(group)).png" plot_sankey(ECModel) + @test_reference "refs/business_plan_plot/group_$(string(group)).png" business_plan_plot(ECModel) end function _utility_callback_test(input_file, optimizer, group_type; atol=ATOL, rtol=RTOL, kwargs...) From 0ed794dab152cdcfab22d1fb621785f499deea5e Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 16:53:18 +0100 Subject: [PATCH 38/40] Revise examples: toml and environment --- examples/Project.toml | 11 ----------- examples/RunSingleModel.jl | 6 +++--- examples/RunSingleModel_complete.jl | 6 +++--- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/examples/Project.toml b/examples/Project.toml index 7f6c90d..8c79305 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -1,27 +1,16 @@ [deps] -CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" EnergyCommunity = "2f2d8a28-e724-42c4-aa4e-51fe4e6b7a61" ExportAll = "ad2082ca-a69e-11e9-38fa-e96309a31fe4" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" -GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" TheoryOfGames = "eb50afb4-6f20-4b37-9b66-473e668300bf" -Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" -JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" -LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SankeyPlots = "8fd88ec8-d95c-41fc-b299-05f2225f2cc5" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TickTock = "9ff05d80-102d-5586-aa04-3a8bd1a90d20" XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" diff --git a/examples/RunSingleModel.jl b/examples/RunSingleModel.jl index e9ce1c4..b74440f 100644 --- a/examples/RunSingleModel.jl +++ b/examples/RunSingleModel.jl @@ -1,6 +1,6 @@ -# Run this script from EnergyCommunity.jl root!!! -using Pkg -Pkg.activate("examples") +# # Run this script from EnergyCommunity.jl root!!! +# using Pkg +# Pkg.activate("examples") using EnergyCommunity, JuMP using HiGHS, Plots diff --git a/examples/RunSingleModel_complete.jl b/examples/RunSingleModel_complete.jl index 2d00327..93fd319 100644 --- a/examples/RunSingleModel_complete.jl +++ b/examples/RunSingleModel_complete.jl @@ -1,6 +1,6 @@ -# Run this script from EnergyCommunity.jl root!!! -using Pkg -Pkg.activate("examples") +# # Run this script from EnergyCommunity.jl root!!! +# using Pkg +# Pkg.activate("examples") using EnergyCommunity, JuMP using HiGHS, Plots From 9743f12a9cb17b46deb18166cbf74df328ba1851 Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 19:43:24 +0100 Subject: [PATCH 39/40] Add cumulative discounted cash flows to yearly finantial terms --- src/ECModel.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ECModel.jl b/src/ECModel.jl index c94000a..b8008af 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -984,8 +984,16 @@ function split_yearly_financial_terms(ECModel::AbstractEC, profit_distribution=n year_set, user_set_financial ) + # Cumulative Discounted Cash Flows + total_costs = Ann_reward .- CAPEX .- OPEX .- Ann_Replacement .+ Ann_Recovery + CUM_DCF = JuMP.Containers.DenseAxisArray( + cumsum(mapslices(x->x.*ann_factor, total_costs.data, dims=1), dims=1), + year_set, user_set_financial + ) + return ( NPV=NPV, + CUM_DCF=CUM_DCF, CAPEX=CAPEX, OPEX=OPEX, OEM=Ann_Maintenance, From 76cf345a313d6b42fc4d22e737ff2b83a71de0ca Mon Sep 17 00:00:00 2001 From: Davide Fioriti Date: Mon, 13 Nov 2023 19:48:57 +0100 Subject: [PATCH 40/40] Add cumulative discounted cash flows to yearly finantial terms --- src/ECModel.jl | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/ECModel.jl b/src/ECModel.jl index b8008af..cfe245e 100644 --- a/src/ECModel.jl +++ b/src/ECModel.jl @@ -1028,7 +1028,7 @@ Returns - df_business Dataframe with the business plan information """ -function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing) +function business_plan(ECModel::AbstractEC, profit_distribution=nothing, user_set_financial=nothing) gen_data = ECModel.gen_data project_lifetime = field(gen_data, "project_lifetime") @@ -1045,10 +1045,21 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set year_set = business_plan.year_set # Create an empty DataFrame - df_business = DataFrame(Year = Int[], CAPEX = Float64[], OEM = Float64[], EN_SELL = Float64[], EN_CONS = EN_SELL = Float64[], PEAK = Float64[], REP = Float64[], - REWARD = Float64[], RV = Float64[]) + df_business = DataFrame( + Year = Int[], + CUM_DCF = Float64[], + CAPEX = Float64[], + OEM = Float64[], + EN_SELL = Float64[], + EN_CONS = Float64[], + PEAK = Float64[], + REP = Float64[], + REWARD = Float64[], + RV = Float64[], + ) for i in year_set Year = year_set[i+1] + CUM_DCF = sum(business_plan.CUM_DCF[i, :]) CAPEX = sum(business_plan.CAPEX[i, :]) OEM = sum(business_plan.OEM[i, :]) EN_SELL = sum(business_plan.EN_SELL[i, :]) @@ -1057,7 +1068,7 @@ function business_plan(ECModel::AbstractEC,profit_distribution=nothing, user_set REP = sum(business_plan.REP[i, :]) REWARD = sum(business_plan.REWARD[i, :]) RV = sum(business_plan.RV[i, :]) - push!(df_business, (Year, CAPEX, OEM, EN_SELL, EN_CONS, PEAK, REP, REWARD, RV)) + push!(df_business, (Year, CUM_DCF, CAPEX, OEM, EN_SELL, EN_CONS, PEAK, REP, REWARD, RV)) end return df_business