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 TulipaBuilder

The 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
13×3 DataFrame
Rowtable_nameestimated_sizecolumn_count
StringInt64Int64
1asset23
2assets_profiles14
3assets_timeframe_profiles05
4asset_both24
5asset_commission22
6asset_milestone23
7flow12
8flow_both04
9flow_commission13
10flow_milestone14
11profiles245
12profiles_timeframe04
13year_data13

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