Skip to content

Commit

Permalink
Create function to read parameters from file (#384)
Browse files Browse the repository at this point in the history
* Create function to read parameters from file

* Add documentation on changing solvers and parameters

* Apply suggestions from code review

Co-authored-by: Lauren Clisby <lclisby@gmail.com>

---------

Co-authored-by: Lauren Clisby <lclisby@gmail.com>
  • Loading branch information
abelsiqueira and clizbe committed Jan 4, 2024
1 parent 7d9710b commit 1b5010f
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .JuliaFormatter.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ align_matrix = true
align_pair_arrow = true
align_struct_field = true
conditional_to_if = true
format_docstrings = true
format_docstrings = false
format_markdown = false
indent = 4
margin = 100
Expand Down
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
MetaGraphsNext = "fa8bd995-216d-47f1-8a91-f3b68fbeb377"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[compat]
CSV = "0.10"
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Expand Down
54 changes: 54 additions & 0 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,60 @@ solution = solve_model(model)

This `solution` structure is exactly the same as the one returned when using an `EnergyProblem`.

### Change optimizer and specify parameters

By default, the model is solved using the [HiGHS](https://github.com/jump-dev/HiGHS.jl) optimizer (or solver).
To change this, we can give the functions `run_scenario`, `solve_model`, or
`solve_model!` a different optimizer.

For instance, we run the [Cbc](https://github.com/jump-dev/Cbc.jl) optimizer below:

```@example
using TulipaEnergyModel, Cbc
input_dir = "../../test/inputs/Tiny" # hide
energy_problem = run_scenario(input_dir, optimizer = Cbc.Optimizer)
```

or

```@example manual-energy-problem
using Cbc
solution = solve_model!(energy_problem, Cbc.Optimizer)
```

or

```@example manual
using Cbc
solution = solve_model(model, Cbc.Optimizer)
```

Notice that, in any of these cases, we need to explicitly add the Cbc package
ourselves and add `using Cbc` before using `Cbc.Optimizer`.

In any of these cases, default parameters for the `Cbc` optimizer are used,
which you can query using [`default_parameters`](@ref).
If you want to change these, you can pass a dictionary via the keyword argument `parameters`.
For instance, in the example below, we change the maximum allowed runtime for
Cbc to be 0.01 seconds, which causes it to fail to converge in time.

```@example
using TulipaEnergyModel, Cbc
input_dir = "../../test/inputs/Tiny" # hide
parameters = Dict("seconds" => 0.01)
energy_problem = run_scenario(input_dir, optimizer = Cbc.Optimizer, parameters = parameters)
energy_problem.termination_status
```

For the full list of parameters, check your chosen optimizer.

These parameters can also be passed via a file. See the
[`read_parameters_from_file`](@ref) function for more details.

## [Using the graph structure](@id graph-tutorial)

Read about the graph structure in the [Graph](@ref) section first.
Expand Down
2 changes: 2 additions & 0 deletions src/TulipaEnergyModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ using HiGHS
using JuMP
using MathOptInterface
using MetaGraphsNext
using TOML

include("input-tables.jl")
include("structures.jl")
include("io.jl")
include("create-model.jl")
include("solver-parameters.jl")
include("solve-model.jl")
include("run-scenario.jl")
include("time-resolution.jl")
Expand Down
71 changes: 4 additions & 67 deletions src/solve-model.jl
Original file line number Diff line number Diff line change
@@ -1,69 +1,4 @@
export solve_model!, solve_model, default_parameters

"""
default_parameters(Val(optimizer_name_symbol))
default_parameters(optimizer)
default_parameters(optimizer_name_symbol)
default_parameters(optimizer_name_string)
Returns the default parameters for a given JuMP optimizer.
Falls back to `Dict()` for undefined solvers.
## Arguments
There are four ways to use this function:
- `Val(optimizer_name_symbol)`: This uses type dispatch with the special `Val` type.
Just give the solver name as a Symbol (e.g., `Val(:HiGHS)`).
- `optimizer`: The JuMP optimizer type (e.g., `HiGHS.Optimizer`).
- `optimizer_name_symbol` or `optimizer_name_string`: Just give the name in Symbol
or String format and we will convert to `Val`.
Using `Val` is necessary for the dispatch.
All other cases will convert the argument and call the `Val` version, which might lead to some type instability.
## Examples
```jldoctest
using HiGHS
default_parameters(HiGHS.Optimizer)
# output
Dict{String, Any} with 1 entry:
"output_flag" => false
```
This also
```jldoctest
default_parameters(Val(:Cbc))
# output
Dict{String, Any} with 1 entry:
"logLevel" => 0
```
```jldoctest
default_parameters(:Cbc) == default_parameters("Cbc") == default_parameters(Val(:Cbc))
# output
true
```
"""
default_parameters(::Any) = Dict{String,Any}()
default_parameters(::Val{:HiGHS}) = Dict{String,Any}("output_flag" => false)
default_parameters(::Val{:Cbc}) = Dict{String,Any}("logLevel" => 0)
default_parameters(::Val{:GLPK}) = Dict{String,Any}("msg_lev" => 0)

function default_parameters(::Type{T}) where {T<:MathOptInterface.AbstractOptimizer}
solver_name = split(string(T), ".")[1]
return default_parameters(Val(Symbol(solver_name)))
end

default_parameters(optimizer::Union{String,Symbol}) = default_parameters(Val(Symbol(optimizer)))
export solve_model!, solve_model

"""
solution = solve_model!(energy_problem[, optimizer; parameters])
Expand Down Expand Up @@ -124,6 +59,8 @@ list of [supported solvers](https://jump.dev/JuMP.jl/stable/installation/#Suppor
By default we use HiGHS.
The keyword argument `parameters` should be passed as a list of `key => value` pairs.
These can be created manually, obtained using [`default_parameters`](@ref), or read from a file
using [`read_parameters_from_file`](@ref).
The `solution` object is a NamedTuple with the following fields:
Expand Down Expand Up @@ -160,7 +97,7 @@ The `solution` object is a NamedTuple with the following fields:
## Examples
```julia
parameters = ["presolve" => "on", "time_limit" => 60.0, "output_flag" => true]
parameters = Dict{String,Any}("presolve" => "on", "time_limit" => 60.0, "output_flag" => true)
solution = solve_model(model, HiGHS.Optimizer; parameters = parameters)
```
"""
Expand Down
118 changes: 118 additions & 0 deletions src/solver-parameters.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
export default_parameters, read_parameters_from_file

"""
default_parameters(Val(optimizer_name_symbol))
default_parameters(optimizer)
default_parameters(optimizer_name_symbol)
default_parameters(optimizer_name_string)
Returns the default parameters for a given JuMP optimizer.
Falls back to `Dict()` for undefined solvers.
## Arguments
There are four ways to use this function:
- `Val(optimizer_name_symbol)`: This uses type dispatch with the special `Val` type.
Just give the solver name as a Symbol (e.g., `Val(:HiGHS)`).
- `optimizer`: The JuMP optimizer type (e.g., `HiGHS.Optimizer`).
- `optimizer_name_symbol` or `optimizer_name_string`: Just give the name in Symbol
or String format and we will convert to `Val`.
Using `Val` is necessary for the dispatch.
All other cases will convert the argument and call the `Val` version, which might lead to some type instability.
## Examples
```jldoctest
using HiGHS
default_parameters(HiGHS.Optimizer)
# output
Dict{String, Any} with 1 entry:
"output_flag" => false
```
Another case
```jldoctest
default_parameters(Val(:Cbc))
# output
Dict{String, Any} with 1 entry:
"logLevel" => 0
```
```jldoctest
default_parameters(:Cbc) == default_parameters("Cbc") == default_parameters(Val(:Cbc))
# output
true
```
"""
default_parameters(::Any) = Dict{String,Any}()
default_parameters(::Val{:HiGHS}) = Dict{String,Any}("output_flag" => false)
default_parameters(::Val{:Cbc}) = Dict{String,Any}("logLevel" => 0)
default_parameters(::Val{:GLPK}) = Dict{String,Any}("msg_lev" => 0)

function default_parameters(::Type{T}) where {T<:MathOptInterface.AbstractOptimizer}
solver_name = split(string(T), ".")[1]
return default_parameters(Val(Symbol(solver_name)))
end

default_parameters(optimizer::Union{String,Symbol}) = default_parameters(Val(Symbol(optimizer)))

"""
read_parameters_from_file(filepath)
Parse the parameters from a file into a dictionary.
The keys and values are NOT checked to be valid parameters for any specific solvers.
The file should contain a list of lines of the following type:
```toml
key = value
```
The file is parsed as [TOML](https://toml.io), which is very intuitive. See the example below.
## Example
```jldoctest
# Creating file
filepath, io = mktemp()
println(io,
\"\"\"
true_or_false = true
integer_number = 5
real_number1 = 3.14
big_number = 6.66E06
small_number = 1e-8
string = "something"
\"\"\"
)
close(io)
# Reading
read_parameters_from_file(filepath)
# output
Dict{String, Any} with 6 entries:
"string" => "something"
"integer_number" => 5
"small_number" => 1.0e-8
"true_or_false" => true
"real_number1" => 3.14
"big_number" => 6.66e6
```
"""
function read_parameters_from_file(filepath)
if !isfile(filepath)
throw(ArgumentError("'$filepath' is not a file."))
end

return TOML.parsefile(filepath)
end
27 changes: 27 additions & 0 deletions test/test-options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,33 @@ end
end
end

@testset "Test reading from file" begin
filepath, io = mktemp()
println(
io,
"""
true_or_false = true
integer_number = 5
real_number1 = 3.14
big_number = 6.66E06
small_number = 1e-8
string = "something"
""",
)
close(io)

@test read_parameters_from_file(filepath) == Dict{String,Any}(
"string" => "something",
"integer_number" => 5,
"small_number" => 1.0e-8,
"true_or_false" => true,
"real_number1" => 3.14,
"big_number" => 6.66e6,
)

@test_throws ArgumentError read_parameters_from_file("badfile")
end

@testset "Test that bad options throw errors" begin
dir = joinpath(INPUT_FOLDER, "Tiny")
@test_throws MathOptInterface.UnsupportedAttribute energy_problem = run_scenario(
Expand Down

0 comments on commit 1b5010f

Please sign in to comment.