Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ version = "1.0.0-DEV"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
ParametricOptInterface = "0ce4ce61-57bf-432b-a095-efac525d185e"

[compat]
JuMP = "1"
Arrow = "2"
CSV = "0.10"
JuMP = "1"
ParametricOptInterface = "0.5"
julia = "1.6"

[extras]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
PowerModels = "c36e90e8-916a-50a6-bd94-075b64ef4655"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "DelimitedFiles", "Downloads", "HiGHS", "PowerModels"]
test = ["Test", "DelimitedFiles", "Downloads", "HiGHS", "PowerModels", "Flux", "DataFrames"]
78 changes: 73 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ Learning to optimize (L2O) package that provides basic functionalities to help f
## 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 Problem Iterator

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)
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
Expand All @@ -29,11 +31,34 @@ parameter_values = Dict(p => collect(1.0:10.0))
problem_iterator = ProblemIterator(problem_ids, parameter_values)
```

Then chose a type of recorder and what values to record:
The parameter values of the problem iterator can be saved by simply:

```julia
save(problem_iterator, "input_file.csv", CSVFile)
```

Which creates the following CSV:

| id | p |
|----|-----|
| 1 | 1.0 |
| 2 | 2.0 |
| 3 | 3.0 |
| 4 | 4.0 |
| 5 | 5.0 |
| 6 | 6.0 |
| 7 | 7.0 |
| 8 | 8.0 |
| 9 | 9.0 |
| 10 | 10.0|

### The Recorder

Then chose what values to record:

```julia
# CSV recorder to save the optimal primal and dual decision values
recorder = Recorder{CSVFile}("test.csv", primal_variables=[:x], dual_variables=[:cons])
recorder = Recorder{CSVFile}("output_file.csv", primal_variables=[x], dual_variables=[cons])

# Finally solve all problems described by the iterator
solve_batch(model, problem_iterator, recorder)
Expand All @@ -57,5 +82,48 @@ Which creates the following CSV:
Similarly, there is also the option to save the database in arrow files:

```julia
recorder = Recorder{ArrowFile}("test.arrow", primal_variables=[:x], dual_variables=[:cons])
recorder = Recorder{ArrowFile}("output_file.arrow", primal_variables=[x], dual_variables=[cons])
```

## Learning proxies

In order to train models to be able to forecast optimization solutions from parameter values, one option is to use the package Flux.jl:

```julia
# read input and output data
input_data = CSV.read("input_file.csv", DataFrame)
output_data = CSV.read("output_file.csv", DataFrame)

# Separate input and output variables
output_variables = output_data[:, 2:end]
input_features = input_data[output_data[:, 1], 2:end] # just use success solves

# Define model
model = Chain(
Dense(size(input_features, 2), 64, relu),
Dense(64, 32, relu),
Dense(32, size(output_variables, 2))
)

# Define loss function
loss(x, y) = Flux.mse(model(x), y)

# Convert the data to matrices
input_features = Matrix(input_features)'
output_variables = Matrix(output_variables)'

# Define the optimizer
optimizer = Flux.ADAM()

# Train the model
Flux.train!(loss, Flux.params(model), [(input_features, output_variables)], optimizer)

# Make predictions
predictions = model(input_features)
```

## Comming Soon

Future features:
- ML objectives that penalize infeasible predictions;
- Warm-start from predicted solutions.
42 changes: 42 additions & 0 deletions examples/powermodels/flux_forecaster.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Flux
using CSV
using DataFrames
using L2O

function test_flux_forecaster(file_in::AbstractString, file_out::AbstractString)
@testset "Flux.jl" begin
# read input and output data
input_data = CSV.read(file_in, DataFrame)
output_data = CSV.read(file_out, DataFrame)

# Separate input and output variables
output_variables = output_data[:, 2:end]
input_features = input_data[output_data[:, 1], 2:end] # just use success solves

# Define model
model = Chain(
Dense(size(input_features, 2), 64, relu),
Dense(64, 32, relu),
Dense(32, size(output_variables, 2)),
)

# Define loss function
loss(x, y) = Flux.mse(model(x), y)

# Convert the data to matrices
input_features = Matrix(input_features)'
output_variables = Matrix(output_variables)'

# Define the optimizer
optimizer = Flux.ADAM()

# Train the model
Flux.train!(
loss, Flux.params(model), [(input_features, output_variables)], optimizer
)

# Make predictions
predictions = model(input_features)
@test predictions isa Matrix
end
end
79 changes: 0 additions & 79 deletions examples/powermodels/pg_lib.jl

This file was deleted.

135 changes: 135 additions & 0 deletions examples/powermodels/pglib_datagen.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Downloads
using PowerModels
using JuMP, HiGHS
import ParametricOptInterface as POI

"""
return_variablerefs(pm::AbstractPowerModel)

return all variablerefs on pm
"""
function return_variablerefs(pm::AbstractPowerModel)
return vcat(
[
[
variableref for
variableref in values(listvarref) if typeof(variableref) == JuMP.VariableRef
] for listvarref in values(PowerModels.var(pm))
]...,
)
end

"""
load_sampler(original_load::T, num_p::Int, max_multiplier::T=3.0, min_multiplier::T=0.0, step_multiplier::T=0.1)

Load sampling
"""
function load_sampler(
original_load::T,
num_p::Int;
max_multiplier::T=2.5,
min_multiplier::T=0.0,
step_multiplier::T=0.1,
) where {T<:Real}
# Load sampling
load_samples =
original_load * rand(min_multiplier:step_multiplier:max_multiplier, num_p)
return load_samples
end

"""
generate_dataset_pglib(data_dir::AbstractString, case_name::AbstractString; download_files::Bool=true, filetype::Type{RecorderFile},
num_p::Int=10
)

Generate dataset for pglib case_name with num_p problems and save it in data_dir
"""
function generate_dataset_pglib(
data_dir,
case_name;
filetype=CSVFile,
download_files=true,
num_p=10,
load_sampler=load_sampler,
)
case_file_path = joinpath(data_dir, case_name)
if download_files && !isfile(case_file_path)
Downloads.download(
"https://github.com/power-grid-lib/pglib-opf/01681386d084d8bd03b429abcd1ee6966f68b9a3/" *
case_name,
case_file_path,
)
end

# Read data
network_data = PowerModels.parse_file(case_file_path)

# The problem to iterate over
model = Model(() -> POI.Optimizer(HiGHS.Optimizer()))

# Save original load value and Link POI
original_load = [l["pd"] for l in values(network_data["load"])]
p = @variable(
model, _p[i=1:length(network_data["load"])] in POI.Parameter.(original_load)
) # vector of parameters
for (i, l) in enumerate(values(network_data["load"]))
l["pd"] = p[i]
end

# Instantiate the model
pm = instantiate_model(
network_data,
DCPPowerModel,
PowerModels.build_opf;
setting=Dict("output" => Dict("duals" => true)),
jump_model=model,
)

# The problem iterator
problem_iterator = ProblemIterator(
collect(1:num_p),
Dict(
p .=> [
load_sampler(original_load[i], num_p) for
i in 1:length(network_data["load"])
],
),
)
save(
problem_iterator,
joinpath(data_dir, case_name * "_input." * string(filetype)),
filetype,
)

# Solve the problem and return the number of successfull solves
file = joinpath(data_dir, case_name * "_output." * string(filetype))
variable_refs = return_variablerefs(pm)
for variableref in variable_refs
set_name(variableref, replace(name(variableref), "," => "_"))
end
number_vars = length(variable_refs)
recorder = Recorder{filetype}(file; primal_variables=variable_refs)
return solve_batch(model, problem_iterator, recorder),
number_vars,
length(original_load)
end

function test_pglib_datasetgen(path::AbstractString, case_name::AbstractString, num_p::Int)
file_in = joinpath(path, case_name * "_input.csv")
file_out = joinpath(path, case_name * "_output.csv")
@testset "Dataset Generation pglib case" begin
success_solves, number_variables, number_loads = generate_dataset_pglib(
path, case_name; num_p=num_p
)
# Check if problem iterator was saved
@test isfile(file_in)
@test length(readdlm(file_in, ',')[:, 1]) == num_p + 1
@test length(readdlm(file_in, ',')[1, :]) == 1 + number_loads

# Check if the number of successfull solves is equal to the number of problems saved
@test isfile(file_out)
@test length(readdlm(file_out, ',')[:, 1]) == num_p * success_solves + 1
@test length(readdlm(file_out, ',')[1, :]) == number_variables + 1
end
return file_in, file_out
end
Loading