Skip to content

Commit

Permalink
Implement ModelParameters (#813)
Browse files Browse the repository at this point in the history
Create a new structure called ModelParameters.
Add a model_parameters field to EnergyProblem.
Create constructors for ModelParameters for reading a file,
and for obtaining some default values from the connection.
Pass the model_parameters to create_model.

Closes #802
  • Loading branch information
abelsiqueira authored Sep 20, 2024
1 parent 550a1c6 commit 1210744
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 7 deletions.
3 changes: 2 additions & 1 deletion docs/src/tutorials.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ input_dir = "../../test/inputs/Tiny" # hide
# input_dir should be the path to Tiny as a string (something like "test/inputs/Tiny")
connection = DBInterface.connect(DuckDB.DB)
read_csv_folder(connection, input_dir; schemas = TulipaEnergyModel.schema_per_table_name)
model_parameters = ModelParameters(connection)
graph, representative_periods, timeframe, groups, years = create_internal_structures(connection)
```

Expand All @@ -119,7 +120,7 @@ dataframes = construct_dataframes(graph, representative_periods, constraints_par
Now we can compute the model.

```@example manual
model = create_model(graph, representative_periods, dataframes, years, timeframe, groups)
model = create_model(graph, representative_periods, dataframes, years, timeframe, groups, model_parameters)
```

Finally, we can compute the solution.
Expand Down
1 change: 1 addition & 0 deletions src/TulipaEnergyModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ using TimerOutputs: TimerOutput, @timeit
const to = TimerOutput()

include("input-schemas.jl")
include("model-parameters.jl")
include("structures.jl")
include("io.jl")
include("create-model.jl")
Expand Down
7 changes: 5 additions & 2 deletions src/create-model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ function create_model!(energy_problem; kwargs...)
constraints_partitions = energy_problem.constraints_partitions
timeframe = energy_problem.timeframe
groups = energy_problem.groups
model_parameters = energy_problem.model_parameters
years = energy_problem.years
energy_problem.dataframes = @timeit to "construct_dataframes" construct_dataframes(
graph,
Expand All @@ -550,7 +551,8 @@ function create_model!(energy_problem; kwargs...)
energy_problem.dataframes,
years,
timeframe,
groups;
groups,
model_parameters;
kwargs...,
)
energy_problem.termination_status = JuMP.OPTIMIZE_NOT_CALLED
Expand All @@ -574,7 +576,8 @@ function create_model(
dataframes,
years,
timeframe,
groups;
groups,
model_parameters;
write_lp_file = false,
)

Expand Down
61 changes: 61 additions & 0 deletions src/model-parameters.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
export ModelParameters

"""
ModelParameters(;key = value, ...)
ModelParameters(path; ...)
ModelParameters(connection; ...)
ModelParameters(connection, path; ...)
Structure to hold the model parameters.
Some values are defined by default and some required explicit definition.
If `path` is passed, it is expected to be a string pointing to a TOML file with
a `key = value` list of parameters. Explicit keyword arguments take precedence.
If `connection` is passed, the default `discount_year` is set to the
minimum of all milestone years. In other words, we check for the table
`year_data` for the column `year` where the column `is_milestone` is true.
Explicit keyword arguments take precedence.
If both are passed, then `path` has preference. Explicit keyword arguments take precedence.
## Parameters
- `discount_rate::Float64 = 0.0`: The model discount rate.
- `discount_year::Int`: The model discount year.
"""
Base.@kwdef mutable struct ModelParameters
discount_rate::Float64 = 0.0
discount_year::Int # Explicit definition expected
end

# Using `@kwdef` defines a default constructor based on keywords

function _read_model_parameters(path)
if length(path) > 0 && !isfile(path)
throw(ArgumentError("path `$path` does not contain a file"))
end

file_data = length(path) > 0 ? TOML.parsefile(path) : Dict{String,Any}()
file_parameters = Dict(Symbol(k) => v for (k, v) in file_data)

return file_parameters
end

function ModelParameters(path::String; kwargs...)
file_parameters = _read_model_parameters(path)

return ModelParameters(; file_parameters..., kwargs...)
end

function ModelParameters(connection::DuckDB.DB, path::String = ""; kwargs...)
discount_year = minimum(
row.year for
row in DuckDB.query(connection, "SELECT year FROM year_data WHERE is_milestone = true")
)
# This can't be naively refactored to reuse the function above because of
# the order of preference of the parameters.
file_parameters = _read_model_parameters(path)

return ModelParameters(; discount_year, file_parameters..., kwargs...)
end
6 changes: 5 additions & 1 deletion src/run-scenario.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ function run_scenario(
connection;
output_folder = "",
optimizer = HiGHS.Optimizer,
model_parameters_file = "",
parameters = default_parameters(optimizer),
write_lp_file = false,
log_file = "",
show_log = true,
)
energy_problem = @timeit to "create EnergyProblem from connection" EnergyProblem(connection)
energy_problem = @timeit to "create EnergyProblem from connection" EnergyProblem(
connection;
model_parameters_file,
)

@timeit to "create_model!" create_model!(energy_problem; write_lp_file)

Expand Down
7 changes: 5 additions & 2 deletions src/structures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ It hides the complexity behind the energy problem, making the usage more friendl
- `timeframe`: The number of periods of the `representative_periods`.
- `dataframes`: The data frames used to linearize the variables and constraints. These are used internally in the model only.
- `groups`: The input data of the groups to create constraints that are common to a set of assets in the model.
- `model_parameters`: The model parameters.
- `model`: A JuMP.Model object representing the optimization model.
- `solved`: A boolean indicating whether the `model` has been solved or not.
- `objective_value`: The objective value of the solved problem.
Expand Down Expand Up @@ -319,6 +320,7 @@ mutable struct EnergyProblem
groups::Vector{Group}
years::Vector{Year}
dataframes::Dict{Symbol,DataFrame}
model_parameters::ModelParameters
model::Union{JuMP.Model,Nothing}
solution::Union{Solution,Nothing}
solved::Bool
Expand All @@ -327,12 +329,12 @@ mutable struct EnergyProblem
timings::Dict{String,Float64}

"""
EnergyProblem(connection)
EnergyProblem(connection; model_parameters_file = "")
Constructs a new EnergyProblem object using the `connection`.
This will call relevant functions to generate all input that is required for the model creation.
"""
function EnergyProblem(connection)
function EnergyProblem(connection; model_parameters_file = "")
elapsed_time_internal = @elapsed begin
graph, representative_periods, timeframe, groups, years =
create_internal_structures(connection)
Expand All @@ -351,6 +353,7 @@ mutable struct EnergyProblem
groups,
years,
Dict(),
ModelParameters(connection, model_parameters_file),
nothing,
nothing,
false,
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
MetaGraphsNext = "fa8bd995-216d-47f1-8a91-f3b68fbeb377"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TulipaIO = "7b3808b7-0819-42d4-885c-978ba173db11"
2 changes: 2 additions & 0 deletions test/inputs/model-parameters-example.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
discount_rate = 0.03
discount_year = 2020
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ using HiGHS
using JuMP
using MathOptInterface
using Test
using TOML
using TulipaEnergyModel
using TulipaIO

Expand Down
5 changes: 4 additions & 1 deletion test/test-case-studies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ end
dir = joinpath(INPUT_FOLDER, "Multi-year Investments")
connection = DBInterface.connect(DuckDB.DB)
_read_csv_folder(connection, dir)
energy_problem = run_scenario(connection)
energy_problem = run_scenario(
connection;
model_parameters_file = joinpath(@__DIR__, "inputs", "model-parameters-example.toml"),
)
# @test energy_problem.objective_value ≈ 28.45872 atol = 1e-5
end

Expand Down
50 changes: 50 additions & 0 deletions test/test-model-parameters.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@testset "Testing Model Parameters" begin
path = joinpath(@__DIR__, "inputs", "model-parameters-example.toml")

@testset "Basic usage" begin
mp = ModelParameters(; discount_rate = 0.1, discount_year = 2018)
@test mp.discount_rate == 0.1
@test mp.discount_year == 2018
end

@testset "Errors when missing required parameters" begin
@test_throws UndefKeywordError ModelParameters()
end

@testset "Read from file" begin
mp = ModelParameters(path)
data = TOML.parsefile(path)
for (key, value) in data
@test value == getfield(mp, Symbol(key))
end

@testset "explicit keywords take precedence" begin
mp = ModelParameters(path; discount_year = 2019)
@test mp.discount_year == 2019
end

@testset "Errors if path does not exist" begin
@test_throws ArgumentError ModelParameters("nonexistent.toml")
end
end

@testset "Read from DuckDB" begin
connection = DBInterface.connect(DuckDB.DB)
read_csv_folder(connection, joinpath(@__DIR__, "inputs", "Norse"))
mp = ModelParameters(connection)
@test mp.discount_year == 2030

@testset "path has precedence" begin
mp = ModelParameters(connection, path)
data = TOML.parsefile(path)
for (key, value) in data
@test value == getfield(mp, Symbol(key))
end
end

@testset "explicit keywords take precedence" begin
mp = ModelParameters(connection, path; discount_year = 2019)
@test mp.discount_year == 2019
end
end
end

0 comments on commit 1210744

Please sign in to comment.