Basic tutorial
This tutorial goes over a creation of a simple Tulipa problem, with the following:
- 1 generator, with 1 existing unit, and capacity for 500 KW;
- 1 consumer, with fake demand around 400 KW.
using TulipaBuilderThe first step is to create a TulipaData object.
tulipa = TulipaData()TulipaData{String}(Meta graph based on a Graphs.SimpleGraphs.SimpleDiGraph{Int64} with vertex labels of type String, vertex metadata of type TulipaBuilder.TulipaAsset, edge metadata of type TulipaBuilder.TulipaFlow, graph metadata given by nothing, and default weight 1.0, false, Dict{Int64, Dict{Symbol, Any}}(), Dict{Tuple{String, Int64}, Dict{Symbol, Any}}())Then, we add each asset with their respective characteristics.
add_asset!(tulipa, "generator", :producer, capacity = 500.0, initial_units = 1.0)
add_asset!(tulipa, "consumer", :consumer, peak_demand = 500.0)TulipaData{String}(Meta graph based on a Graphs.SimpleGraphs.SimpleDiGraph{Int64} with vertex labels of type String, vertex metadata of type TulipaBuilder.TulipaAsset, edge metadata of type TulipaBuilder.TulipaFlow, graph metadata given by nothing, and default weight 1.0, false, Dict{Int64, Dict{Symbol, Any}}(), Dict{Tuple{String, Int64}, Dict{Symbol, Any}}())Next, we define the flow between these assets and the operational cost:
add_flow!(tulipa, "generator", "consumer", operational_cost = 5.00)TulipaData{String}(Meta graph based on a Graphs.SimpleGraphs.SimpleDiGraph{Int64} with vertex labels of type String, vertex metadata of type TulipaBuilder.TulipaAsset, edge metadata of type TulipaBuilder.TulipaFlow, graph metadata given by nothing, and default weight 1.0, false, Dict{Int64, Dict{Symbol, Any}}(), Dict{Tuple{String, Int64}, Dict{Symbol, Any}}())Now, let's attach the profiles to the solar and demand assets. Notice that we need to pass the year in which these profiles are defined. In a single-year problem, the year doesn't matter, so any integer value could be used.
num_timesteps = 24
demand_profile = (400 .+ randn(num_timesteps) * 20) / 500
attach_profile!(tulipa, "consumer", :demand, 2030, demand_profile)TulipaData{String}(Meta graph based on a Graphs.SimpleGraphs.SimpleDiGraph{Int64} with vertex labels of type String, vertex metadata of type TulipaBuilder.TulipaAsset, edge metadata of type TulipaBuilder.TulipaFlow, graph metadata given by nothing, and default weight 1.0, false, Dict{Int64, Dict{Symbol, Any}}(2030 => Dict(:is_milestone => true, :length => 24)), Dict{Tuple{String, Int64}, Dict{Symbol, Any}}())Now we can create the connection with the data of the Tulipa problem using the create_connection function.
using TulipaEnergyModel: TulipaEnergyModel as TEM
connection = create_connection(tulipa, TEM.schema)
# Inspect all tables in DuckDB
using DuckDB, DataFrames
DuckDB.query(connection, "SELECT table_name, estimated_size, column_count FROM duckdb_tables()") |> DataFrame| Row | table_name | estimated_size | column_count |
|---|---|---|---|
| String | Int64 | Int64 | |
| 1 | asset | 2 | 3 |
| 2 | assets_profiles | 1 | 4 |
| 3 | assets_timeframe_profiles | 0 | 5 |
| 4 | asset_both | 2 | 4 |
| 5 | asset_commission | 2 | 2 |
| 6 | asset_milestone | 2 | 3 |
| 7 | flow | 1 | 2 |
| 8 | flow_both | 0 | 4 |
| 9 | flow_commission | 1 | 3 |
| 10 | flow_milestone | 1 | 4 |
| 11 | profiles | 24 | 5 |
| 12 | profiles_timeframe | 0 | 4 |
| 13 | year_data | 1 | 3 |
Optionally, you might also want to create a folder with the data in CSV format:
output_folder = mktempdir() # Define the output folder
create_case_study_csv_folder(connection, TEM.schema, output_folder)
readdir(output_folder)13-element Vector{String}:
"asset.csv"
"asset_both.csv"
"asset_commission.csv"
"asset_milestone.csv"
"assets_profiles.csv"
"assets_timeframe_profiles.csv"
"flow.csv"
"flow_both.csv"
"flow_commission.csv"
"flow_milestone.csv"
"profiles.csv"
"profiles_timeframe.csv"
"year_data.csv"For completeness, here is rest of the pipeline for clustering, populating with defaults, and solving the problem:
# Don't forget to cluster and populate with defaults before solving the problem
using TulipaClustering: TulipaClustering as TC
TC.dummy_cluster!(connection; layout = TC.ProfilesTableLayout(year = :milestone_year))
TEM.populate_with_defaults!(connection)
ep = TEM.run_scenario(connection)EnergyProblem:
- Model created!
- Number of variables: 24
- Number of constraints for variable bounds: 24
- Number of structural constraints: 48
- Model solved!
- Termination status: OPTIMAL
- Objective value: 48100.54714075506