From 360f8e26b066c938d2b8aa6b3fd6a8def1e90f60 Mon Sep 17 00:00:00 2001 From: Ni Wang <125902905+gnawin@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:29:24 +0200 Subject: [PATCH] Add compact method for multi-year (#784) --- src/constraints/capacity.jl | 58 +++++-- src/constraints/consumer.jl | 1 + src/constraints/energy.jl | 2 + .../ramping-and-unit-commitment.jl | 6 +- src/constraints/storage.jl | 5 + src/constraints/transport.jl | 4 + src/create-model.jl | 114 ++++++++++++- src/io.jl | 158 ++++++++++-------- src/structures.jl | 12 +- .../Multi-year Investments/assets-data.csv | 14 +- .../graph-assets-data.csv | 8 +- .../Multi-year Investments/year-data.csv | 2 + 12 files changed, 280 insertions(+), 104 deletions(-) diff --git a/src/constraints/capacity.jl b/src/constraints/capacity.jl index 9718f7cd..3a6257c7 100644 --- a/src/constraints/capacity.jl +++ b/src/constraints/capacity.jl @@ -26,12 +26,19 @@ function add_capacity_constraints!( flow, Ai, decommissionable_assets_using_simple_method, + decommissionable_assets_using_compact_method, + V_all, Asb, assets_investment, accumulate_capacity_simple_method, + accumulate_capacity_compact_method, + accumulated_set_using_compact_method, outgoing_flow_highest_out_resolution, incoming_flow_highest_in_resolution, ) + compact_set_lookup = Dict( + (a, y, v) => idx for (idx, (a, y, v)) in enumerate(accumulated_set_using_compact_method) + ) ## Expressions used by capacity constraints # - Create capacity limit for outgoing flows @@ -44,6 +51,7 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, @@ -52,6 +60,26 @@ function add_capacity_constraints!( accumulate_capacity_simple_method[row.year, row.asset] ) ) + elseif row.asset ∈ decommissionable_assets_using_compact_method + @expression( + model, + graph[row.asset].capacity[row.year] * sum( + profile_aggregation( + Statistics.mean, + graph[row.asset].rep_periods_profiles, + row.year, + v, + ("availability", row.rep_period), + row.timesteps_block, + 1.0, + ) * accumulate_capacity_compact_method[compact_set_lookup[( + row.asset, + row.year, + v, + )]] for v in V_all if + (row.asset, row.year, v) in accumulated_set_using_compact_method + ) + ) else @expression( model, @@ -59,12 +87,13 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + graph[row.asset].initial_units[row.year][row.year] ) end for row in eachrow(dataframes[:highest_out]) ] @@ -79,13 +108,14 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + + graph[row.asset].initial_units[row.year][row.year] + graph[row.asset].investment_limit[row.year] ) * (1 - row.is_charging) @@ -97,13 +127,14 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + graph[row.asset].initial_units[row.year][row.year] ) * (1 - row.is_charging) ) @@ -119,13 +150,14 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * ( - graph[row.asset].initial_units[row.year] * (1 - row.is_charging) + - assets_investment[row.year, row.asset] + graph[row.asset].initial_units[row.year][row.year] * + (1 - row.is_charging) + assets_investment[row.year, row.asset] ) ) ) @@ -142,12 +174,13 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * ( - graph[row.asset].initial_units[row.year] + + graph[row.asset].initial_units[row.year][row.year] + assets_investment[row.year, row.asset] ) ) @@ -159,12 +192,13 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + graph[row.asset].initial_units[row.year][row.year] ) end for row in eachrow(dataframes[:highest_in]) ] @@ -179,13 +213,14 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + + graph[row.asset].initial_units[row.year][row.year] + graph[row.asset].investment_limit[row.year] ) * row.is_charging @@ -197,13 +232,14 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * - graph[row.asset].initial_units[row.year] + graph[row.asset].initial_units[row.year][row.year] ) * row.is_charging ) @@ -219,13 +255,13 @@ function add_capacity_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, ) * ( graph[row.asset].capacity[row.year] * ( - graph[row.asset].initial_units[row.year] * row.is_charging + - assets_investment[row.year, row.asset] + graph[row.asset].initial_units[row.year][row.year] * row.is_charging + assets_investment[row.year, row.asset] ) ) ) diff --git a/src/constraints/consumer.jl b/src/constraints/consumer.jl index c0707509..0320463b 100644 --- a/src/constraints/consumer.jl +++ b/src/constraints/consumer.jl @@ -32,6 +32,7 @@ function add_consumer_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("demand", row.rep_period), row.timesteps_block, 1.0, diff --git a/src/constraints/energy.jl b/src/constraints/energy.jl index 6a07f09a..5ec89212 100644 --- a/src/constraints/energy.jl +++ b/src/constraints/energy.jl @@ -19,6 +19,7 @@ function add_energy_constraints!(model, graph, dataframes) sum, graph[row.asset].timeframe_profiles, row.year, + row.year, "max_energy", row.periods_block, 1.0, @@ -36,6 +37,7 @@ function add_energy_constraints!(model, graph, dataframes) sum, graph[row.asset].timeframe_profiles, row.year, + row.year, "min_energy", row.periods_block, 1.0, diff --git a/src/constraints/ramping-and-unit-commitment.jl b/src/constraints/ramping-and-unit-commitment.jl index 12316eb2..6dd69011 100644 --- a/src/constraints/ramping-and-unit-commitment.jl +++ b/src/constraints/ramping-and-unit-commitment.jl @@ -28,6 +28,7 @@ function add_ramping_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, @@ -54,7 +55,8 @@ function add_ramping_constraints!( @constraint( model, row.units_on ≤ - graph[row.asset].initial_units[row.year] + assets_investment[row.year, row.asset], + graph[row.asset].initial_units[row.year][row.year] + + assets_investment[row.year, row.asset], base_name = "limit_units_on_with_investment[$(row.asset),$(row.year),$(row.rep_period),$(row.timesteps_block)]" ) for row in eachrow(df_units_on) if row.asset in Ai[row.year] ] @@ -63,7 +65,7 @@ function add_ramping_constraints!( model[:limit_units_on_without_investment] = [ @constraint( model, - row.units_on ≤ graph[row.asset].initial_units[row.year], + row.units_on ≤ graph[row.asset].initial_units[row.year][row.year], base_name = "limit_units_on_without_investment[$(row.asset),$(row.year),$(row.rep_period),$(row.timesteps_block)]" ) for row in eachrow(df_units_on) if !(row.asset in Ai[row.year]) ] diff --git a/src/constraints/storage.jl b/src/constraints/storage.jl index 7e92325e..6e6e958c 100644 --- a/src/constraints/storage.jl +++ b/src/constraints/storage.jl @@ -63,6 +63,7 @@ function add_storage_constraints!( sum, graph[a].rep_periods_profiles, row.year, + row.year, ("inflows", rp), row.timesteps_block, 0.0, @@ -83,6 +84,7 @@ function add_storage_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("max-storage-level", row.rep_period), row.timesteps_block, 1.0, @@ -103,6 +105,7 @@ function add_storage_constraints!( Statistics.mean, graph[row.asset].rep_periods_profiles, row.year, + row.year, ("min_storage_level", row.rep_period), row.timesteps_block, 0.0, @@ -166,6 +169,7 @@ function add_storage_constraints!( Statistics.mean, graph[row.asset].timeframe_profiles, row.year, + row.year, "max_storage_level", row.periods_block, 1.0, @@ -186,6 +190,7 @@ function add_storage_constraints!( Statistics.mean, graph[row.asset].timeframe_profiles, row.year, + row.year, "min_storage_level", row.periods_block, 0.0, diff --git a/src/constraints/transport.jl b/src/constraints/transport.jl index 87ccacc5..dbdd0752 100644 --- a/src/constraints/transport.jl +++ b/src/constraints/transport.jl @@ -18,6 +18,7 @@ function add_transport_constraints!(model, graph, df_flows, flow, Ft, flows_inve Statistics.mean, graph[row.from, row.to].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, @@ -34,6 +35,7 @@ function add_transport_constraints!(model, graph, df_flows, flow, Ft, flows_inve Statistics.mean, graph[row.from, row.to].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, @@ -51,6 +53,7 @@ function add_transport_constraints!(model, graph, df_flows, flow, Ft, flows_inve Statistics.mean, graph[row.from, row.to].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, @@ -67,6 +70,7 @@ function add_transport_constraints!(model, graph, df_flows, flow, Ft, flows_inve Statistics.mean, graph[row.from, row.to].rep_periods_profiles, row.year, + row.year, ("availability", row.rep_period), row.timesteps_block, 1.0, diff --git a/src/create-model.jl b/src/create-model.jl index 27a43fe7..ccd2b9eb 100644 --- a/src/create-model.jl +++ b/src/create-model.jl @@ -90,7 +90,7 @@ Computes the data frames used to linearize the variables and constraints. These internally in the model only. """ function construct_dataframes(graph, representative_periods, constraints_partitions, years_struct) - years = getfield.(years_struct, :id) + years = [year.id for year in years_struct if year.is_milestone] A = MetaGraphsNext.labels(graph) |> collect F = MetaGraphsNext.edge_labels(graph) |> collect RP = Dict(year => 1:length(representative_periods[year]) for year in years) @@ -492,6 +492,7 @@ function add_expression_terms_inter_rp_constraints!( sum, graph[row_inter.asset].rep_periods_profiles, row_map.year, + row_map.year, ("inflows", row_map.rep_period), representative_periods[row_map.year][row_map.rep_period].timesteps, 0.0, @@ -514,9 +515,11 @@ If `profiles[key]` exists, then this function computes the aggregation of `profi over the range `block` using the aggregator `agg`, i.e., `agg(profiles[key][block])`. If `profiles[key]` does not exist, then this substitutes it with a vector of `default_value`s. """ -function profile_aggregation(agg, profiles, year, key, block, default_value) - if haskey(profiles, year) && haskey(profiles[year], key) - return agg(profiles[year][key][block]) +function profile_aggregation(agg, profiles, year, commission_year, key, block, default_value) + if haskey(profiles, year) && + haskey(profiles[year], commission_year) && + haskey(profiles[year][commission_year], key) + return agg(profiles[year][commission_year][key][block]) else return agg(Iterators.repeated(default_value, length(block))) end @@ -582,7 +585,9 @@ function create_model( return length(timesteps_block) * representative_periods[rp].resolution end - Y = [year.id for year in years] + Y = [year.id for year in years if year.is_milestone] + V_all = [year.id for year in years] + V_non_milestone = [year.id for year in years if !year.is_milestone] # Maximum timestep Tmax = maximum(last(rp.timesteps) for year in Y for rp in representative_periods[year]) @@ -620,12 +625,42 @@ function create_model( decommissionable_assets_using_compact_method = filter_graph(graph, A, "compact", :investment_method) - # Create a Dict for the start year of investments that are accumulated in year y + # Create dicts for the start year of investments that are accumulated in year y starting_year_using_simple_method = Dict( (y, a) => y - graph[a].technical_lifetime[y] + 1 for y in Y for a in decommissionable_assets_using_simple_method ) + starting_year_using_compact_method = Dict( + (y, a) => y - graph[a].technical_lifetime[y] + 1 for y in Y for + a in decommissionable_assets_using_compact_method + ) + + # Create a subset of decommissionable_assets_using_compact_method: existing assets invested in non-milestone years + existing_assets_by_year_using_compact_method = Dict( + y => + [ + a for a in decommissionable_assets_using_compact_method for + inner_dict in values(graph[a].initial_units) for + k in keys(inner_dict) if k == y && inner_dict[k] != 0 + ] |> unique for y in V_all + ) + + # Create sets of tuples for decommission variables/accumulated capacity of compact method + decommission_set_using_compact_method = [ + (a, y, v) for a in decommissionable_assets_using_compact_method for y in Y for + v in V_all if starting_year_using_compact_method[y, a] ≤ v < y && (( + (v in V_non_milestone && a in existing_assets_by_year_using_compact_method[v]) || (v in Y) + )) + ] + + accumulated_set_using_compact_method = [ + (a, y, v) for a in decommissionable_assets_using_compact_method for y in Y for + v in V_all if starting_year_using_compact_method[y, a] ≤ v ≤ y && (( + (v in V_non_milestone && a in existing_assets_by_year_using_compact_method[v]) || (v in Y) + )) + ] + # Create subsets of storage assets Ase = Dict(y => As ∩ filter_graph(graph, A, true, :storage_method_energy, y) for y in Y) Asb = Dict( @@ -676,6 +711,7 @@ function create_model( base_name = "flow[($(row.from), $(row.to)), $(row.year), $(row.rep_period), $(row.timesteps_block)]" ) for row in eachrow(df_flows) ] + @variable(model, 0 ≤ flows_investment[y in Y, (u, v) in Fi[y]]) ### Investment variables @@ -687,6 +723,12 @@ function create_model( a in decommissionable_assets_using_simple_method, ] ) #number of decommission asset units [N] + @variable( + model, + 0 <= + assets_decommission_compact_method[(a, y, v) in decommission_set_using_compact_method] + ) #number of decommission asset units [N] + @variable(model, 0 ≤ assets_investment_energy[y in Y, a in Ase[y]∩Ai[y]]) #number of installed asset units for storage energy [N] ### Unit commitment variables @@ -741,6 +783,14 @@ function create_model( end end + for (a, y, v) in decommission_set_using_compact_method + # We don't do anything with existing units (because it can be integers or non-integers) + if !(v in V_non_milestone && a in existing_assets_by_year_using_compact_method[y]) && + graph[a].investment_integer[y] + JuMP.set_integer(assets_decommission_compact_method[(a, y, v)]) + end + end + for y in Y, (u, v) in Fi[y] if graph[u, v].investment_integer[y] JuMP.set_integer(flows_investment[y, (u, v)]) @@ -938,7 +988,7 @@ function create_model( y ∈ Y, a ∈ decommissionable_assets_using_simple_method, ], - graph[a].initial_units[y] + sum( + graph[a].initial_units[y][y] + sum( assets_investment[yy, a] for yy in Y if a ∈ investable_assets_using_simple_method[yy] && starting_year_using_simple_method[(y, a)] ≤ yy ≤ y @@ -947,6 +997,38 @@ function create_model( yy in Y if starting_year_using_simple_method[(y, a)] ≤ yy ≤ y ) ) + cond1(a, y, v) = a in existing_assets_by_year_using_compact_method[v] + cond2(a, y, v) = v in Y && a in investable_assets_using_compact_method[v] + accumulate_capacity_compact_method = + model[:accumulate_capacity_compact_method] = JuMP.AffExpr[ + if cond1(a, y, v) && cond2(a, y, v) + @expression( + model, + graph[a].initial_units[y][v] + assets_investment[v, a] - sum( + assets_decommission_compact_method[(a, yy, v)] for yy in Y if + v ≤ yy ≤ y && (a, yy, v) in decommission_set_using_compact_method + ) + ) + elseif cond1(a, y, v) && !cond2(a, y, v) + @expression( + model, + graph[a].initial_units[y][v] - sum( + assets_decommission_compact_method[(a, yy, v)] for yy in Y if + v ≤ yy ≤ y && (a, yy, v) in decommission_set_using_compact_method + ) + ) + elseif !cond1(a, y, v) && cond2(a, y, v) + @expression( + model, + assets_investment[v, a] - sum( + assets_decommission_compact_method[(a, yy, v)] for yy in Y if + v ≤ yy ≤ y && (a, yy, v) in decommission_set_using_compact_method + ) + ) + else + @expression(model, 0.0) + end for (a, y, v) in accumulated_set_using_compact_method + ] end ## Expressions for the objective function @@ -959,13 +1041,25 @@ function create_model( ) ) + fixed_cost = Dict((a, y, v) => 0 for (a, y, v) in accumulated_set_using_compact_method) + for (a, y, v) in accumulated_set_using_compact_method + if haskey(graph[a].fixed_cost, y) && haskey(graph[a].fixed_cost[y], v) + fixed_cost[(a, y, v)] = graph[a].fixed_cost[y][v] + else + fixed_cost[(a, y, v)] = graph[a].fixed_cost[v][v] + end + end + assets_fixed_cost = @expression( model, sum( - graph[a].fixed_cost[y] * + graph[a].fixed_cost[y][y] * graph[a].capacity[y] * accumulate_capacity_simple_method[y, a] for y in Y for a in decommissionable_assets_using_simple_method + ) + sum( + fixed_cost[(a, y, v)] * graph[a].capacity[y] * accm for (accm, (a, y, v)) in + zip(accumulate_capacity_compact_method, accumulated_set_using_compact_method) ) ) @@ -1030,9 +1124,13 @@ function create_model( flow, Ai, decommissionable_assets_using_simple_method, + decommissionable_assets_using_compact_method, + V_all, Asb, assets_investment, accumulate_capacity_simple_method, + accumulate_capacity_compact_method, + accumulated_set_using_compact_method, outgoing_flow_highest_out_resolution, incoming_flow_highest_in_resolution, ) diff --git a/src/io.jl b/src/io.jl index 61306dfb..95e0a77a 100644 --- a/src/io.jl +++ b/src/io.jl @@ -70,7 +70,7 @@ function create_internal_structures(connection) groups = [Group(row...) for row in TulipaIO.get_table(Val(:raw), connection, "groups_data")] - _query(table_name, col; where_pairs...) = begin + _query_year(table_name, col; where_pairs...) = begin extra_check = table_name == "assets_data" ? "commission_year = year AND " : "" _q = "SELECT year, $col FROM $table_name" if length(where_pairs) > 0 @@ -82,56 +82,78 @@ function create_internal_structures(connection) DuckDB.query(connection, _q) end - function _get_stuff(table_name, col; where_pairs...) - result = _query(table_name, col; where_pairs...) + function _get_stuff_year(table_name, col; where_pairs...) + result = _query_year(table_name, col; where_pairs...) Dict(row.year => getproperty(row, Symbol(col)) for row in result) end + _query_commission_year(table_name, col; where_pairs...) = begin + _q = "SELECT year, commission_year, $col FROM $table_name" + if length(where_pairs) > 0 + _q *= + " WHERE " * + join(("$k=$(TulipaIO.FmtSQL.fmt_quote(v))" for (k, v) in where_pairs), " AND ") + end + DuckDB.query(connection, _q) + end + + function _get_stuff_commission_year(table_name, col; where_pairs...) + result = _query_commission_year(table_name, col; where_pairs...) + result_dict = Dict{Int,Dict{Int,Float64}}() + for row in result + if !haskey(result_dict, row.year) + result_dict[row.year] = Dict{Int,Float64}() + end + result_dict[row.year][row.commission_year] = getproperty(row, Symbol(col)) + end + return result_dict + end + unique_asset_names = Dict{String,Bool}() asset_data = [ row.name => GraphAssetData( row.type, row.group, row.investment_method, - _get_stuff("assets_data", "active"; name = row.name), - _get_stuff("assets_data", "investable"; name = row.name), - _get_stuff("assets_data", "investment_integer"; name = row.name), - _get_stuff("assets_data", "technical_lifetime"; name = row.name), - _get_stuff("assets_data", "investment_cost"; name = row.name), - _get_stuff("assets_data", "fixed_cost"; name = row.name), - _get_stuff("assets_data", "investment_limit"; name = row.name), - _get_stuff("assets_data", "capacity"; name = row.name), - _get_stuff("assets_data", "initial_units"; name = row.name), - _get_stuff("assets_data", "peak_demand"; name = row.name), + _get_stuff_year("assets_data", "active"; name = row.name), + _get_stuff_year("assets_data", "investable"; name = row.name), + _get_stuff_year("assets_data", "investment_integer"; name = row.name), + _get_stuff_year("assets_data", "technical_lifetime"; name = row.name), + _get_stuff_year("assets_data", "investment_cost"; name = row.name), + _get_stuff_commission_year("assets_data", "fixed_cost"; name = row.name), + _get_stuff_year("assets_data", "investment_limit"; name = row.name), + _get_stuff_year("assets_data", "capacity"; name = row.name), + _get_stuff_commission_year("assets_data", "initial_units"; name = row.name), + _get_stuff_year("assets_data", "peak_demand"; name = row.name), Dict( year => if ismissing(value) MathOptInterface.EqualTo(0.0) else MathOptInterface.GreaterThan(0.0) end for (year, value) in - _get_stuff("assets_data", "consumer_balance_sense"; name = row.name) + _get_stuff_year("assets_data", "consumer_balance_sense"; name = row.name) ), - _get_stuff("assets_data", "is_seasonal"; name = row.name), - _get_stuff("assets_data", "storage_inflows"; name = row.name), - _get_stuff("assets_data", "initial_storage_capacity"; name = row.name), - _get_stuff("assets_data", "initial_storage_level"; name = row.name), - _get_stuff("assets_data", "energy_to_power_ratio"; name = row.name), - _get_stuff("assets_data", "storage_method_energy"; name = row.name), - _get_stuff("assets_data", "investment_cost_storage_energy"; name = row.name), - _get_stuff("assets_data", "investment_limit_storage_energy"; name = row.name), - _get_stuff("assets_data", "capacity_storage_energy"; name = row.name), - _get_stuff("assets_data", "investment_integer_storage_energy"; name = row.name), - _get_stuff("assets_data", "use_binary_storage_method"; name = row.name), - _get_stuff("assets_data", "max_energy_timeframe_partition"; name = row.name), - _get_stuff("assets_data", "min_energy_timeframe_partition"; name = row.name), - _get_stuff("assets_data", "unit_commitment"; name = row.name), - _get_stuff("assets_data", "unit_commitment_method"; name = row.name), - _get_stuff("assets_data", "units_on_cost"; name = row.name), - _get_stuff("assets_data", "unit_commitment_integer"; name = row.name), - _get_stuff("assets_data", "min_operating_point"; name = row.name), - _get_stuff("assets_data", "ramping"; name = row.name), - _get_stuff("assets_data", "max_ramp_up"; name = row.name), - _get_stuff("assets_data", "max_ramp_down"; name = row.name), + _get_stuff_year("assets_data", "is_seasonal"; name = row.name), + _get_stuff_year("assets_data", "storage_inflows"; name = row.name), + _get_stuff_year("assets_data", "initial_storage_capacity"; name = row.name), + _get_stuff_year("assets_data", "initial_storage_level"; name = row.name), + _get_stuff_year("assets_data", "energy_to_power_ratio"; name = row.name), + _get_stuff_year("assets_data", "storage_method_energy"; name = row.name), + _get_stuff_year("assets_data", "investment_cost_storage_energy"; name = row.name), + _get_stuff_year("assets_data", "investment_limit_storage_energy"; name = row.name), + _get_stuff_year("assets_data", "capacity_storage_energy"; name = row.name), + _get_stuff_year("assets_data", "investment_integer_storage_energy"; name = row.name), + _get_stuff_year("assets_data", "use_binary_storage_method"; name = row.name), + _get_stuff_year("assets_data", "max_energy_timeframe_partition"; name = row.name), + _get_stuff_year("assets_data", "min_energy_timeframe_partition"; name = row.name), + _get_stuff_year("assets_data", "unit_commitment"; name = row.name), + _get_stuff_year("assets_data", "unit_commitment_method"; name = row.name), + _get_stuff_year("assets_data", "units_on_cost"; name = row.name), + _get_stuff_year("assets_data", "unit_commitment_integer"; name = row.name), + _get_stuff_year("assets_data", "min_operating_point"; name = row.name), + _get_stuff_year("assets_data", "ramping"; name = row.name), + _get_stuff_year("assets_data", "max_ramp_up"; name = row.name), + _get_stuff_year("assets_data", "max_ramp_down"; name = row.name), ) for row in TulipaIO.get_table(Val(:raw), connection, "graph_assets_data") ] @@ -139,67 +161,67 @@ function create_internal_structures(connection) flow_data = [ (row.from_asset, row.to_asset) => GraphFlowData( row.carrier, - _get_stuff( + _get_stuff_year( "flows_data", "active"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "is_transport"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "investable"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "investment_integer"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "variable_cost"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "investment_cost"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "investment_limit"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "capacity"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "initial_export_capacity"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "initial_import_capacity"; from_asset = row.from_asset, to_asset = row.to_asset, ), - _get_stuff( + _get_stuff_year( "flows_data", "efficiency"; from_asset = row.from_asset, @@ -280,28 +302,29 @@ function create_internal_structures(connection) end end - _df = TulipaIO.get_table(connection, "profiles_rep_periods") - for asset_profile_row in TulipaIO.get_table(Val(:raw), connection, "assets_profiles") # row = asset, profile_type, profile_name - gp = DataFrames.groupby( # 2. group by rep_period, year - filter( # 1. Filter on profile_name, year - [:profile_name, :year] => - (name, year) -> - name == asset_profile_row.profile_name && - year == asset_profile_row.commission_year, - _df; - view = true, - ), - [:rep_period, :year], - ) - for ((rep_period, year), df) in pairs(gp) # Loop over filtered DFs by rep_period, year - profiles = graph[asset_profile_row.asset].rep_periods_profiles - if !haskey(profiles, year) - profiles[year] = Dict{Tuple{Symbol,Int},Vector{Float64}}() - end - profiles[year][(asset_profile_row.profile_type, rep_period)] = df.value + _df = + DuckDB.execute( + connection, + "SELECT asset, commission_year, profile_type, year, rep_period, value + FROM assets_profiles + JOIN profiles_rep_periods + ON assets_profiles.profile_name=profiles_rep_periods.profile_name", + ) |> DataFrame + + gp = DataFrames.groupby(_df, [:asset, :commission_year, :profile_type, :year, :rep_period]) + + for ((asset, commission_year, profile_type, year, rep_period), df) in pairs(gp) + profiles = graph[asset].rep_periods_profiles + if !haskey(profiles, year) + profiles[year] = Dict{Int,Dict{Tuple{Symbol,Int},Vector{Float64}}}() + end + if !haskey(profiles[year], commission_year) + profiles[year][commission_year] = Dict{Tuple{Symbol,Int},Vector{Float64}}() end + profiles[year][commission_year][(profile_type, rep_period)] = df.value end + _df = TulipaIO.get_table(connection, "profiles_rep_periods") for flow_profile_row in TulipaIO.get_table(Val(:raw), connection, "flows_profiles") gp = DataFrames.groupby( filter(:profile_name => ==(flow_profile_row.profile_name), _df; view = true), @@ -333,9 +356,10 @@ function create_internal_structures(connection) for ((year,), df) in pairs(gp) profiles = graph[asset_profile_row.asset].timeframe_profiles if !haskey(profiles, year) - profiles[year] = Dict{String,Vector{Float64}}() + profiles[year] = Dict{Int,Dict{String,Vector{Float64}}}() + profiles[year][year] = Dict{String,Vector{Float64}}() end - profiles[year][asset_profile_row.profile_type] = df.value + profiles[year][year][asset_profile_row.profile_type] = df.value end end diff --git a/src/structures.jl b/src/structures.jl index faced770..7b189ecb 100644 --- a/src/structures.jl +++ b/src/structures.jl @@ -56,10 +56,10 @@ mutable struct GraphAssetData investment_integer::Dict{Int,Bool} technical_lifetime::Dict{Int,Float64} investment_cost::Dict{Int,Float64} - fixed_cost::Dict{Int,Float64} + fixed_cost::Dict{Int,Dict{Int,Float64}} investment_limit::Dict{Int,Union{Missing,Float64}} capacity::Dict{Int,Float64} - initial_units::Dict{Int,Float64} + initial_units::Dict{Int,Dict{Int,Float64}} peak_demand::Dict{Int,Float64} consumer_balance_sense::Dict{Int,Union{MathOptInterface.EqualTo,MathOptInterface.GreaterThan}} is_seasonal::Dict{Int,Bool} @@ -83,8 +83,8 @@ mutable struct GraphAssetData ramping::Dict{Int,Bool} max_ramp_up::Dict{Int,Union{Missing,Float64}} max_ramp_down::Dict{Int,Union{Missing,Float64}} - timeframe_profiles::Dict{Int,Dict{String,Vector{Float64}}} - rep_periods_profiles::Dict{Int,Dict{Tuple{String,Int},Vector{Float64}}} + timeframe_profiles::Dict{Int,Dict{Int,Dict{String,Vector{Float64}}}} + rep_periods_profiles::Dict{Int,Dict{Int,Dict{Tuple{String,Int},Vector{Float64}}}} timeframe_partitions::Dict{Int,Vector{PeriodsBlock}} rep_periods_partitions::Dict{Int,Dict{Int,Vector{TimestepsBlock}}} # Solution @@ -133,8 +133,8 @@ mutable struct GraphAssetData max_ramp_up, max_ramp_down, ) - timeframe_profiles = Dict{Int,Dict{String,Vector{Float64}}}() - rep_periods_profiles = Dict{Int,Dict{Tuple{String,Int},Vector{Float64}}}() + timeframe_profiles = Dict{Int,Dict{Int,Dict{String,Vector{Float64}}}}() + rep_periods_profiles = Dict{Int,Dict{Int,Dict{Tuple{String,Int},Vector{Float64}}}}() timeframe_partitions = Dict{Int,Vector{TimestepsBlock}}() rep_periods_partitions = Dict{Int,Dict{Int,Vector{TimestepsBlock}}}() return new( diff --git a/test/inputs/Multi-year Investments/assets-data.csv b/test/inputs/Multi-year Investments/assets-data.csv index 87693b43..58111616 100644 --- a/test/inputs/Multi-year Investments/assets-data.csv +++ b/test/inputs/Multi-year Investments/assets-data.csv @@ -1,16 +1,18 @@ ,{true;false},,,{true;false},{true;false},year,kEUR/MW/year,kEUR/MW/year,MW,MW,units,MW,{empty;==;>=},{true;false},MWh/year,MWh,MWh,h,{true;false},kEUR/MWh/year,MWh,MWh,{true;false},{missing;binary;relaxed_binary},MWh,MWh,{true;false},{missing;basic},kEUR/h/unit_on,{true;false},[p.u.],{true;false},[p.u.],[p.u.] name,active,year,commission_year,investable,investment_integer,technical_lifetime,investment_cost,fixed_cost,investment_limit,capacity,initial_units,peak_demand,consumer_balance_sense,is_seasonal,storage_inflows,initial_storage_capacity,initial_storage_level,energy_to_power_ratio,storage_method_energy,investment_cost_storage_energy,investment_limit_storage_energy,capacity_storage_energy,investment_integer_storage_energy,use_binary_storage_method,max_energy_timeframe_partition,min_energy_timeframe_partition,unit_commitment,unit_commitment_method,units_on_cost,unit_commitment_integer,min_operating_point,ramping,max_ramp_up,max_ramp_down -ocgt,true,2030,2030,true,true,25,25,20,,100,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +ocgt,true,2030,2030,true,true,15,25,20,,100,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +ccgt,true,2030,2028,false,false,25,40,20,10000,400,1,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, ccgt,true,2030,2030,true,true,25,40,20,10000,400,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, -wind,true,2030,2020,false,false,15,70,20,,50,0.07,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, -wind,true,2030,2030,true,true,15,70,20,,50,0.02,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +wind,true,2030,2020,false,false,30,70,20,,50,0.07,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +wind,true,2030,2030,true,true,30,70,20,,50,0.02,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, solar,true,2030,2030,true,true,15,50,20,,10,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, ens,true,2030,2030,false,false,15,0,20,,1115,1,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, demand,true,2030,2030,false,false,15,0,20,,0,0,1115,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, -ocgt,true,2050,2050,true,true,25,25,20,,100,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +ocgt,true,2050,2050,true,true,15,25,20,,100,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +ccgt,true,2050,2028,false,false,25,40,20,10000,400,1,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, ccgt,true,2050,2050,false,false,25,40,20,10000,400,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, -wind,true,2050,2030,false,false,15,70,20,,50,0.02,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, -wind,true,2050,2050,true,true,15,70,20,,50,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +wind,true,2050,2030,false,false,30,70,20,,50,0.02,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, +wind,true,2050,2050,true,true,30,70,20,,50,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, solar,true,2050,2050,true,true,15,50,20,,10,0,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, ens,true,2050,2050,false,false,15,0,20,,1115,1,0,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, demand,false,2050,2050,false,false,15,0,20,,0,0,1115,,false,0,0,0,0,false,0,,0,false,,,,false,,,false,,false,, diff --git a/test/inputs/Multi-year Investments/graph-assets-data.csv b/test/inputs/Multi-year Investments/graph-assets-data.csv index 705985fc..d43632b4 100644 --- a/test/inputs/Multi-year Investments/graph-assets-data.csv +++ b/test/inputs/Multi-year Investments/graph-assets-data.csv @@ -1,8 +1,8 @@ ,{producer;consumer},{group name},{none;simple;compact} name,type,group,investment_method -ocgt,producer,,simple -ccgt,producer,,simple -wind,producer,,simple -solar,producer,,simple +ocgt,producer,,compact +ccgt,producer,,compact +wind,producer,,compact +solar,producer,,compact ens,producer,,none demand,consumer,,none diff --git a/test/inputs/Multi-year Investments/year-data.csv b/test/inputs/Multi-year Investments/year-data.csv index 0aeaf434..46d17067 100644 --- a/test/inputs/Multi-year Investments/year-data.csv +++ b/test/inputs/Multi-year Investments/year-data.csv @@ -1,4 +1,6 @@ ,h,{true;false} year,length,is_milestone +2020,8760,false +2028,8760,false 2030,8760,true 2050,8760,true