Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Business plan plot #12

Merged
merged 41 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3210afc
Initial commit
TomFer97 Jul 12, 2023
66cebd2
First dataframe function
TomFer97 Jul 12, 2023
8722f55
New yearly function split financial
TomFer97 Jul 25, 2023
eb9d12f
Fixing yearly financial terms
TomFer97 Jul 25, 2023
d89b4b2
Fixing function but problem with ann_energy-costs
TomFer97 Jul 25, 2023
795d6c8
Minor fix
TomFer97 Jul 26, 2023
f65375c
Minor fix and DenseAxisArray 2-D
TomFer97 Jul 26, 2023
c20b7ce
Final fix of split_yearly_financial function
TomFer97 Jul 26, 2023
5d14cd3
Fix but issues with get_value function
TomFer97 Jul 31, 2023
755cd6b
Final split_yearly function
TomFer97 Jul 31, 2023
1bccd4c
business_plan_plot function
TomFer97 Jul 31, 2023
200e877
Fix of business_plan_plot_function
TomFer97 Jul 31, 2023
6a143e6
Fix
TomFer97 Jul 31, 2023
c405b01
First bar plot with errors
TomFer97 Jul 31, 2023
a69e0db
Description of functions
TomFer97 Aug 21, 2023
1c20249
Fixing of tab intended
TomFer97 Aug 21, 2023
63151b1
business_plant rename function
TomFer97 Aug 21, 2023
c078743
Year 0 as star year
TomFer97 Aug 21, 2023
769f413
Fix
TomFer97 Aug 30, 2023
381c9d1
Fixing on ECModel
TomFer97 Aug 31, 2023
4a4a66a
Fixgin not working
TomFer97 Sep 1, 2023
8b86766
Next try
TomFer97 Oct 9, 2023
e1e6ec7
Fix business plot
TomFer97 Oct 12, 2023
ed08d98
Fixing
TomFer97 Oct 12, 2023
7677bac
First update
TomFer97 Oct 20, 2023
27702ab
First plot fix
TomFer97 Oct 26, 2023
f1fd371
Project.toml update
TomFer97 Oct 31, 2023
39ae9c0
Clear RunSingleModel
TomFer97 Oct 31, 2023
1e28fdd
Fixing of bar_label and values
TomFer97 Nov 2, 2023
2ca53cc
Only CO
TomFer97 Nov 2, 2023
6407d2a
Update Project.toml
TomFer97 Nov 7, 2023
60d0943
Almost last fix
TomFer97 Nov 7, 2023
458967e
Not working legend :(
TomFer97 Nov 7, 2023
2106981
Reorder plot structure in business_plan_plot
TomFer97 Nov 8, 2023
1cedff9
Finalize plot_business_plan
davide-f Nov 13, 2023
519d65d
Merge remote-tracking branch 'upstream/main' into sankey_branch
davide-f Nov 13, 2023
181b7d7
Update examples of the documentation
davide-f Nov 13, 2023
542fae0
Add test to business_plot
davide-f Nov 13, 2023
0ed794d
Revise examples: toml and environment
davide-f Nov 13, 2023
9743f12
Add cumulative discounted cash flows to yearly finantial terms
davide-f Nov 13, 2023
76cf345
Add cumulative discounted cash flows to yearly finantial terms
davide-f Nov 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,31 @@ 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"
davide-f marked this conversation as resolved.
Show resolved Hide resolved
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
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"
StatsPlots = "f3b207a7-027a-5e70-b257-86293d7955fd"
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"
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
LayeredLayouts = "0.2"
MathOptInterface = "1.0"
Plots = "1"
SankeyPlots = "0.2.2"
StatsPlots = "0.15"
TheoryOfGames = "0.1"
XLSX = "0.9"
YAML = "0.4"
YAML = "0.4"
julia = "1"
8 changes: 7 additions & 1 deletion examples/RunSingleModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ 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)
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved

## Model NC

# create NonCooperative model
Expand All @@ -66,4 +69,7 @@ print_summary(NC_Model)
save_summary(NC_Model, output_file_isolated)

# plot Sankey plot of NC model
plot_sankey(NC_Model)
plot_sankey(NC_Model)

# plot business plan of NC model
#business_plan_plot(NC_Model)
274 changes: 274 additions & 0 deletions src/ECModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -852,6 +853,279 @@ 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.

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, profit_distribution=nothing)
gen_data = ECModel.gen_data

project_lifetime = field(gen_data, "project_lifetime")

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 = 0:project_lifetime

user_set_financial = [EC_CODE; get_user_set(ECModel)]

if isnothing(profit_distribution)
davide-f marked this conversation as resolved.
Show resolved Hide resolved
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

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
)
# 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_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_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_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_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_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]]
, year_set, user_set_financial
)

# Total OPEX 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
#=
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)
for u in setdiff(user_set_financial, [EC_CODE])]
, 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(
[(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
)=#

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
)
end

"""
business_plan(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(ECModel::AbstractEC,profit_distribution=nothing, user_set_financial=nothing)
gen_data = ECModel.gen_data

project_lifetime = field(gen_data, "project_lifetime")

if isnothing(user_set_financial)
user_set_financial = get_user_set(ECModel)
end

# Create a vector of years from 2023 to (2023 + project_lifetime)
gen_data = ECModel.gen_data
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[], PEAK = Float64[], REP = Float64[],
REWARD = Float64[], RV = Float64[])
for i in year_set
Year = 0 + year_set[i+1]
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

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,
xlabel="Year",
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
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
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
df_business = business_plan(ECModel)
end

TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
# Define the plot structure
plot_struct = Dict(
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
"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

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]

TomFer97 marked this conversation as resolved.
Show resolved Hide resolved
# Create a bar plot
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,
)
TomFer97 marked this conversation as resolved.
Show resolved Hide resolved

return p
end

function EnergyCommunity.split_financial_terms(ECModel::AbstractEC, profit_distribution::Dict)
return split_financial_terms(
ECModel,
Expand Down
1 change: 1 addition & 0 deletions src/EnergyCommunity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module EnergyCommunity
using MathOptInterface
using Base.Iterators
using TheoryOfGames
using StatsPlots
# import ECharts
import SankeyPlots
import CSV
Expand Down
Loading