From 141aea81b90daefabaf53de47df12ad6f1280667 Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Thu, 22 Jun 2023 17:02:22 -0300 Subject: [PATCH 1/6] add update_model --- Project.toml | 5 +++++ src/L2O.jl | 4 +++- src/datasetgen.jl | 22 ++++++++++++++++++++++ test/runtests.jl | 11 ++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/datasetgen.jl diff --git a/Project.toml b/Project.toml index 557be60..036eb08 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,11 @@ uuid = "e1d8bfa7-c465-446a-84b9-451470f6e76c" authors = ["andrewrosemberg and contributors"] version = "1.0.0-DEV" +[deps] +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +ParametricOptInterface = "0ce4ce61-57bf-432b-a095-efac525d185e" + [compat] julia = "1" diff --git a/src/L2O.jl b/src/L2O.jl index 0147403..9bf4268 100644 --- a/src/L2O.jl +++ b/src/L2O.jl @@ -1,5 +1,7 @@ module L2O -# Write your package code here. +import ParametricOptInterface as POI + +include("datasetgen.jl") end diff --git a/src/datasetgen.jl b/src/datasetgen.jl new file mode 100644 index 0000000..c8d12e4 --- /dev/null +++ b/src/datasetgen.jl @@ -0,0 +1,22 @@ +function update_model!(model::Model, p::Parameter, val::T) where {T<:Real} + MOI.set(model, ParameterValue(), p, val) +end + +function update_model!(model::Model, p::Parameter, val::AbstractArray{T}) where {T<:Real} + MOI.set(model, ParameterValue(), p, val) +end + +function update_model!(model::Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}) + for (p, val) in pairs + update_model!(model, p, val) + end +end + +function solve_and_record(model::Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Function, filterfn::Function) + update_model!(model, pairs) + optimize!(model) + if filterfn(model) + recorder(model, pairs) + end + return nothing +end diff --git a/test/runtests.jl b/test/runtests.jl index 2ad48d7..e07ec83 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,15 @@ using L2O using Test +using JuMP, HiGHS +import ParametricOptInterface as POI @testset "L2O.jl" begin - # Write your tests here. + model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) + @variable(model, x) + p = @variable(model, _p in POI.Parameter(1.0)) + @constraint(model, cons, x + _p >= 3) + @objective(model, Min, 2x) + + MOI.set(model, POI.ParameterValue(), p, 2.0) + optimize!(model) end From 5eb01f2a514a39f5322107d448c32c8806ee399d Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Mon, 26 Jun 2023 17:32:35 -0300 Subject: [PATCH 2/6] add recorder struct --- Project.toml | 5 ++-- src/L2O.jl | 3 +++ src/datasetgen.jl | 63 +++++++++++++++++++++++++++++++++++++++++------ test/runtests.jl | 10 ++++++-- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/Project.toml b/Project.toml index 036eb08..dc49acf 100644 --- a/Project.toml +++ b/Project.toml @@ -4,7 +4,6 @@ authors = ["andrewrosemberg and contributors"] version = "1.0.0-DEV" [deps] -HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" ParametricOptInterface = "0ce4ce61-57bf-432b-a095-efac525d185e" @@ -13,6 +12,8 @@ julia = "1" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" +HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" [targets] -test = ["Test"] +test = ["Test", "DelimitedFiles", "HiGHS"] diff --git a/src/L2O.jl b/src/L2O.jl index 9bf4268..dc105f0 100644 --- a/src/L2O.jl +++ b/src/L2O.jl @@ -1,7 +1,10 @@ module L2O +using JuMP import ParametricOptInterface as POI +export solve_batch, CSVRecorder + include("datasetgen.jl") end diff --git a/src/datasetgen.jl b/src/datasetgen.jl index c8d12e4..d0d697a 100644 --- a/src/datasetgen.jl +++ b/src/datasetgen.jl @@ -1,22 +1,69 @@ -function update_model!(model::Model, p::Parameter, val::T) where {T<:Real} - MOI.set(model, ParameterValue(), p, val) +abstract type Recorder end + +mutable struct CSVRecorder <: Recorder + filename::String + primal_variables::AbstractArray{Symbol} + dual_variables::AbstractArray{Symbol} + filterfn::Function +end + +function record(recorder::CSVRecorder, model::JuMP.Model, id::Int64) + if !isfile(recorder.filename) + open(recorder.filename, "w") do f + write(f, "id") + for p in recorder.primal_variables + write(f, ",$p") + end + for p in recorder.dual_variables + write(f, ",dual_$p") + end + write(f, "\n") + end + end + open(recorder.filename, "a") do f + write(f, "$id") + for p in recorder.primal_variables + val = MOI.get(model, MOI.VariablePrimal(), model[p]) + write(f, ",$val") + end + for p in recorder.dual_variables + val = MOI.get(model, MOI.ConstraintDual(), model[p]) + write(f, ",$val") + end + write(f, "\n") + end +end + +function CSVRecorder(filename::String; primal_variables=[], dual_variables=[], filterfn=(model)-> termination_status(model) == MOI.OPTIMAL) + return CSVRecorder(filename, primal_variables, dual_variables, filterfn) end -function update_model!(model::Model, p::Parameter, val::AbstractArray{T}) where {T<:Real} - MOI.set(model, ParameterValue(), p, val) +function update_model!(model::JuMP.Model, p::VariableRef, val::T) where {T<:Real} + MOI.set(model, POI.ParameterValue(), p, val) end -function update_model!(model::Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}) +function update_model!(model::JuMP.Model, p::VariableRef, val::AbstractArray{T}) where {T<:Real} + MOI.set(model, POI.ParameterValue(), p, val) +end + +function update_model!(model::JuMP.Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}) for (p, val) in pairs update_model!(model, p, val) end end -function solve_and_record(model::Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Function, filterfn::Function) +function solve_and_record(model::JuMP.Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Recorder, id::Int64) update_model!(model, pairs) optimize!(model) - if filterfn(model) - recorder(model, pairs) + if recorder.filterfn(model) + record(recorder, model, id) + end + return nothing +end + +function solve_batch(model::JuMP.Model, problem_iterator::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Recorder) + for (id, pairs) in problem_iterator + solve_and_record(model, pairs, recorder, id) end return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index e07ec83..f70bfaf 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ using L2O using Test +using DelimitedFiles using JuMP, HiGHS import ParametricOptInterface as POI @@ -10,6 +11,11 @@ import ParametricOptInterface as POI @constraint(model, cons, x + _p >= 3) @objective(model, Min, 2x) - MOI.set(model, POI.ParameterValue(), p, 2.0) - optimize!(model) + problem_iterator = zip(1:10, (zip(p, 1.0) for i in 1:10)) + recorder = CSVRecorder("test.csv", primal_variables=[:x], dual_variables=[:cons]) + solve_batch(model, problem_iterator, recorder) + @test isfile("test.csv") + @test length(readdlm("test.csv", ',')[:, 1]) == 11 + @test length(readdlm("test.csv", ',')[1, :]) == 3 + rm("test.csv") end From 238713e56cc6da8b137c85e5b1ce0b783d6f40e6 Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Wed, 28 Jun 2023 12:12:59 -0300 Subject: [PATCH 3/6] rm vscode file --- .gitignore | 1 + .vscode/settings.json | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 31d573e..4e890ab 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /docs/Manifest.toml /docs/build/ Manifest.toml +/.vscode/* diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From dcea45b20c513799f46ebcb0d2fa7fd7936e2ed2 Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Thu, 13 Jul 2023 14:45:25 -0300 Subject: [PATCH 4/6] create problem iterator --- src/L2O.jl | 2 +- src/datasetgen.jl | 27 +++++++++++++++++++-------- test/runtests.jl | 5 +++-- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/L2O.jl b/src/L2O.jl index dc105f0..13ae9e7 100644 --- a/src/L2O.jl +++ b/src/L2O.jl @@ -3,7 +3,7 @@ module L2O using JuMP import ParametricOptInterface as POI -export solve_batch, CSVRecorder +export solve_batch, CSVRecorder, ProblemIterator include("datasetgen.jl") diff --git a/src/datasetgen.jl b/src/datasetgen.jl index d0d697a..e3f6501 100644 --- a/src/datasetgen.jl +++ b/src/datasetgen.jl @@ -7,6 +7,17 @@ mutable struct CSVRecorder <: Recorder filterfn::Function end +struct ProblemIterator{T<:Real, Z<:Integer} + ids::Vector{Z} + pairs::Dict{VariableRef, Vector{T}} + function ProblemIterator(ids::Vector{Z}, pairs::Dict{VariableRef, Vector{T}}) where {T<:Real, Z<:Integer} + for (p, val) in pairs + @assert length(ids) == length(val) + end + return new{T, Z}(ids, pairs) + end +end + function record(recorder::CSVRecorder, model::JuMP.Model, id::Int64) if !isfile(recorder.filename) open(recorder.filename, "w") do f @@ -46,24 +57,24 @@ function update_model!(model::JuMP.Model, p::VariableRef, val::AbstractArray{T}) MOI.set(model, POI.ParameterValue(), p, val) end -function update_model!(model::JuMP.Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}) +function update_model!(model::JuMP.Model, pairs::Dict, idx::Int64) for (p, val) in pairs - update_model!(model, p, val) + update_model!(model, p, val[idx]) end end -function solve_and_record(model::JuMP.Model, pairs::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Recorder, id::Int64) - update_model!(model, pairs) +function solve_and_record(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder, idx::Int64) + update_model!(model, problem_iterator.pairs, idx) optimize!(model) if recorder.filterfn(model) - record(recorder, model, id) + record(recorder, model, problem_iterator.ids[idx]) end return nothing end -function solve_batch(model::JuMP.Model, problem_iterator::Union{Base.Iterators.Zip, Base.Iterators.Pairs}, recorder::Recorder) - for (id, pairs) in problem_iterator - solve_and_record(model, pairs, recorder, id) +function solve_batch(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder) + for idx in 1:length(problem_iterator.ids) + solve_and_record(model, problem_iterator, recorder, idx) end return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index f70bfaf..b6071c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,11 +11,12 @@ import ParametricOptInterface as POI @constraint(model, cons, x + _p >= 3) @objective(model, Min, 2x) - problem_iterator = zip(1:10, (zip(p, 1.0) for i in 1:10)) + num_p = 10 + problem_iterator = ProblemIterator(collect(1:num_p), Dict(p => collect(1.0:num_p))) recorder = CSVRecorder("test.csv", primal_variables=[:x], dual_variables=[:cons]) solve_batch(model, problem_iterator, recorder) @test isfile("test.csv") - @test length(readdlm("test.csv", ',')[:, 1]) == 11 + @test length(readdlm("test.csv", ',')[:, 1]) == num_p+1 @test length(readdlm("test.csv", ',')[1, :]) == 3 rm("test.csv") end From 9b178a7cd64c0e760a514e2ceca1aff7d7330bd7 Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Thu, 13 Jul 2023 15:12:12 -0300 Subject: [PATCH 5/6] update documentation --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++ docs/src/index.md | 2 ++ src/datasetgen.jl | 55 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 08dd682..44536b1 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,51 @@ Learning to optimize (L2O) package that provides basic functionalities to help f [![Build Status](https://github.com/andrewrosemberg/L2O.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/andrewrosemberg/L2O.jl/actions/workflows/CI.yml?query=branch%3Amain) [![Build Status](https://travis-ci.com/andrewrosemberg/L2O.jl.svg?branch=main)](https://travis-ci.com/andrewrosemberg/L2O.jl) [![Coverage](https://codecov.io/gh/andrewrosemberg/L2O.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/andrewrosemberg/L2O.jl) + +## Generate Dataset +This package provides a basic way of generating a dataset of the solutions of an optimization problem by varying the values of the parameters in the problem and recording it. + +The user needs to first define a problem iterator: + +```julia +# The problem to iterate over +model = Model(() -> POI.Optimizer(HiGHS.Optimizer())) +@variable(model, x) +p = @variable(model, _p in POI.Parameter(1.0)) # The parameter (defined using POI) +@constraint(model, cons, x + _p >= 3) +@objective(model, Min, 2x) + +# The ids +problem_ids = collect(1:10) + +# The parameter values +parameter_values = Dict(p => collect(1.0:10.0)) + +# The iterator +problem_iterator = ProblemIterator(problem_ids, parameter_values) +``` + +Then chose a type of recorder and what values to record: + +```julia +# CSV recorder to save the optimal primal and dual decision values +recorder = CSVRecorder("test.csv", primal_variables=[:x], dual_variables=[:cons]) + +# Finally solve all problems described by the iterator +solve_batch(model, problem_iterator, recorder) +``` + +Which creates the following CSV: + +| id | x | dual_cons | +|----|------|-----------| +| 1 | 2.0 | 2.0 | +| 2 | 1.0 | 2.0 | +| 3 | -0.0 | 2.0 | +| 4 | -1.0 | 2.0 | +| 5 | -2.0 | 2.0 | +| 6 | -3.0 | 2.0 | +| 7 | -4.0 | 2.0 | +| 8 | -5.0 | 2.0 | +| 9 | -6.0 | 2.0 | +| 10 | -7.0 | 2.0 | diff --git a/docs/src/index.md b/docs/src/index.md index d9dc513..c5fd7d3 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -6,6 +6,8 @@ CurrentModule = L2O Documentation for [L2O](https://github.com/andrewrosemberg/L2O.jl). +Learning to optimize (L2O) package that provides basic functionalities to help fit proxy models for optimization. + ```@index ``` diff --git a/src/datasetgen.jl b/src/datasetgen.jl index e3f6501..c566e95 100644 --- a/src/datasetgen.jl +++ b/src/datasetgen.jl @@ -1,12 +1,31 @@ +""" + Recorder + +Abstract type for recorders of optimization problem solutions. +""" abstract type Recorder end +""" + CSVRecorder(filename; primal_variables=[], dual_variables=[], filterfn=(model)-> termination_status(model) == MOI.OPTIMAL) + +Recorder type of optimization problem solutions to a CSV file. +""" mutable struct CSVRecorder <: Recorder filename::String primal_variables::AbstractArray{Symbol} dual_variables::AbstractArray{Symbol} filterfn::Function + + function CSVRecorder(filename::String; primal_variables=[], dual_variables=[], filterfn=(model)-> termination_status(model) == MOI.OPTIMAL) + return new(filename, primal_variables, dual_variables, filterfn) + end end +""" + ProblemIterator(ids::Vector{Integer}, pairs::Dict{VariableRef, Vector{Real}}) + +Iterator for optimization problem instances. +""" struct ProblemIterator{T<:Real, Z<:Integer} ids::Vector{Z} pairs::Dict{VariableRef, Vector{T}} @@ -18,6 +37,11 @@ struct ProblemIterator{T<:Real, Z<:Integer} end end +""" + record(recorder::CSVRecorder, model::JuMP.Model, id::Int64) + +Record optimization problem solution to a CSV file. +""" function record(recorder::CSVRecorder, model::JuMP.Model, id::Int64) if !isfile(recorder.filename) open(recorder.filename, "w") do f @@ -45,25 +69,41 @@ function record(recorder::CSVRecorder, model::JuMP.Model, id::Int64) end end -function CSVRecorder(filename::String; primal_variables=[], dual_variables=[], filterfn=(model)-> termination_status(model) == MOI.OPTIMAL) - return CSVRecorder(filename, primal_variables, dual_variables, filterfn) -end +""" + update_model!(model::JuMP.Model, p::VariableRef, val::Real) +Update the value of a parameter in a JuMP model. +""" function update_model!(model::JuMP.Model, p::VariableRef, val::T) where {T<:Real} MOI.set(model, POI.ParameterValue(), p, val) end +""" + update_model!(model::JuMP.Model, p::VariableRef, val::AbstractArray{Real}) + +Update the value of a parameter in a JuMP model. +""" function update_model!(model::JuMP.Model, p::VariableRef, val::AbstractArray{T}) where {T<:Real} MOI.set(model, POI.ParameterValue(), p, val) end -function update_model!(model::JuMP.Model, pairs::Dict, idx::Int64) +""" + update_model!(model::JuMP.Model, pairs::Dict, idx::Integer) + +Update the values of parameters in a JuMP model. +""" +function update_model!(model::JuMP.Model, pairs::Dict, idx::Integer) for (p, val) in pairs update_model!(model, p, val[idx]) end end -function solve_and_record(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder, idx::Int64) +""" + solve_and_record(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder, idx::Integer) + +Solve an optimization problem and record the solution. +""" +function solve_and_record(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder, idx::Integer) update_model!(model, problem_iterator.pairs, idx) optimize!(model) if recorder.filterfn(model) @@ -72,6 +112,11 @@ function solve_and_record(model::JuMP.Model, problem_iterator::ProblemIterator, return nothing end +""" + solve_batch(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder) + +Solve a batch of optimization problems and record the solutions. +""" function solve_batch(model::JuMP.Model, problem_iterator::ProblemIterator, recorder::Recorder) for idx in 1:length(problem_iterator.ids) solve_and_record(model, problem_iterator, recorder, idx) From a6f20697cbe9ae8432407fe31ef72657f34ef1ef Mon Sep 17 00:00:00 2001 From: andrewrosemberg Date: Thu, 13 Jul 2023 15:13:35 -0300 Subject: [PATCH 6/6] update actions --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ad8d929..d1d83c2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -18,7 +18,7 @@ jobs: fail-fast: false matrix: version: - - '1.0' + - '1.6' - '1.9' - 'nightly' os: