Partial implementation of parallel_solve

master
Alinson S. Xavier 4 years ago
parent e72831039c
commit 98024dea95

@ -5,6 +5,7 @@ version = "0.2.0"
[deps] [deps]
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"

@ -108,12 +108,12 @@ end
fit!(solver, training_instances) fit!(solver, training_instances)
# Save trained solver to disk # Save trained solver to disk
save("solver.mls", solver) save("solver.bin", solver)
# Application restarts... # Application restarts...
# Load trained solver from disk # Load trained solver from disk
solver = load("solver.mls") solver = load_solver("solver.bin")
# Solve additional instances # Solve additional instances
test_instances = [...] test_instances = [...]
@ -123,46 +123,43 @@ end
``` ```
### 1.5 Solving training instances in parallel ### 1.5 Solving instances from disk
```julia
using MIPLearn
using Cbc
# Solve training instances in parallel In all examples above, we have assumed that instances are available as `JuMPInstance` objects, stored in memory. When problem instances are very large, or when there is a large number of problem instances, this approach may require an excessive amount of memory. To reduce memory requirements, MIPLearn.jl can also operate on instances that are stored on disk, through the `FileInstance` class, as the next example illustrates.
training_instances = [...]
solver = LearningSolver(Cbc.Optimizer)
parallel_solve!(solver, training_instances, n_jobs=4)
fit!(solver, training_instances)
# Solve test instances in parallel
test_instances = [...]
parallel_solve!(solver, test_instances)
```
### 1.6 Solving instances from disk
```julia ```julia
using MIPLearn using MIPLearn
using JuMP using JuMP
using Cbc using Cbc
# Create 600 problem instances and save them to files # Create a large number of problem instances
for i in 1:600 for i in 1:600
m = Model()
@variable(m, x, Bin)
@objective(m, Min, x)
@feature(x, [1.0])
# Build JuMP model
model = Model()
@variable(...)
@objective(...)
@constraint(...)
# Add ML features and categories
@feature(...)
@category(...)
# Save instances to a file
instance = JuMPInstance(m) instance = JuMPInstance(m)
save("instance-$i.bin", instance) save("instance-$i.bin", instance)
end end
# Initialize instances and solver # Initialize training and test instances
training_instances = [FileInstance("instance-$i.bin") for i in 1:500] training_instances = [FileInstance("instance-$i.bin") for i in 1:500]
test_instances = [FileInstance("instance-$i.bin") for i in 501:600] test_instances = [FileInstance("instance-$i.bin") for i in 501:600]
# Initialize solver
solver = LearningSolver(Cbc.Optimizer) solver = LearningSolver(Cbc.Optimizer)
# Solve training instances # Solve training instances. Files are modified in-place, and at most one
# file is loaded to memory at a time.
for instance in training_instances for instance in training_instances
solve!(solver, instance) solve!(solver, instance)
end end
@ -176,6 +173,39 @@ for instance in test_instances
end end
``` ```
### 1.6 Solving training instances in parallel
In many situations, instances can be solved in parallel to accelerate the training process. MIPLearn.jl provides the method `parallel_solve!(solver, instances)` to easily achieve this.
First, launch Julia in multi-process mode:
```
julia --procs 4
```
Then run the following script:
```julia
@everywhere using MIPLearn
@everywhere using Cbc
# Initialize training and test instances
training_instances = [...]
test_instances = [...]
# Initialize the solver
solver = LearningSolver(Cbc.Optimizer)
# Solve training instances in parallel. The number of instances solved
# simultaneously is the same as the `--procs` specified when running Julia.
parallel_solve!(solver, training_instances)
# Train machine learning models
fit!(solver, training_instances)
# Solve test instances in parallel
parallel_solve!(solver, test_instances)
```
## 2. Customization ## 2. Customization
### 2.1 Selecting solver components ### 2.1 Selecting solver components

@ -6,20 +6,16 @@ __precompile__(false)
module MIPLearn module MIPLearn
using PyCall using PyCall
global miplearn = pyimport("miplearn")
export JuMPInstance global traceback = pyimport("traceback")
export LearningSolver
export @feature
export @category
miplearn = pyimport("miplearn")
include("utils/log.jl") include("utils/log.jl")
include("utils/exceptions.jl") include("utils/exceptions.jl")
include("instance/abstract.jl")
include("instance/jump.jl")
include("instance/file.jl")
include("solvers/jump.jl") include("solvers/jump.jl")
include("solvers/learning.jl") include("solvers/learning.jl")
include("solvers/macros.jl") include("solvers/macros.jl")
include("instance/jump.jl")
include("instance/file.jl")
end # module end # module

@ -0,0 +1,8 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
abstract type Instance
end

@ -55,12 +55,16 @@ end
struct FileInstance <: Instance struct FileInstance <: Instance
py::PyCall.PyObject py::PyCall.PyObject
filename::AbstractString
end end
function FileInstance(filename)::FileInstance function FileInstance(filename)::FileInstance
filename isa AbstractString || error("filename should be a string. Found $(typeof(filename)) instead.") filename isa AbstractString || error("filename should be a string. Found $(typeof(filename)) instead.")
return FileInstance(PyFileInstance(filename)) return FileInstance(
PyFileInstance(filename),
filename,
)
end end

@ -2,18 +2,21 @@
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
struct LearningSolver using Distributed
py::PyCall.PyObject
end
abstract type Instance struct LearningSolver
py::PyCall.PyObject
optimizer_factory
end end
function LearningSolver(optimizer_factory)::LearningSolver function LearningSolver(optimizer_factory)::LearningSolver
py = miplearn.LearningSolver(solver=JuMPSolver(optimizer_factory)) py = miplearn.LearningSolver(solver=JuMPSolver(optimizer_factory))
return LearningSolver(py) return LearningSolver(
py,
optimizer_factory,
)
end end
@ -31,7 +34,19 @@ function fit!(solver::LearningSolver, instances::Vector{<:Instance})
end end
function parallel_solve!(solver::LearningSolver, instances::Vector{FileInstance})
filenames = [instance.filename for instance in instances]
optimizer_factory = solver.optimizer_factory
@sync @distributed for filename in filenames
s = LearningSolver(optimizer_factory)
solve!(s, FileInstance(filename))
nothing
end
end
export Instance, export Instance,
LearningSolver, LearningSolver,
solve!, solve!,
fit! fit!,
parallel_solve!

@ -62,3 +62,7 @@ macro category(obj, category)
set_category!($(esc(obj)), $(esc(category))) set_category!($(esc(obj)), $(esc(category)))
end end
end end
export @feature,
@category

@ -3,7 +3,6 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
using PyCall using PyCall
traceback = pyimport("traceback")
macro python_call(expr) macro python_call(expr)

@ -7,7 +7,7 @@ using MIPLearn
using Gurobi using Gurobi
@testset "LearningSolver" begin @testset "LearningSolver" begin
@testset "model with annotations" begin @testset "Model with annotations" begin
# Create standard JuMP model # Create standard JuMP model
weights = [1.0, 2.0, 3.0] weights = [1.0, 2.0, 3.0]
prices = [5.0, 6.0, 7.0] prices = [5.0, 6.0, 7.0]
@ -48,7 +48,7 @@ using Gurobi
solve!(solver, instance) solve!(solver, instance)
end end
@testset "model without annotations" begin @testset "Model without annotations" begin
model = Model() model = Model()
@variable(model, x, Bin) @variable(model, x, Bin)
@variable(model, y, Bin) @variable(model, y, Bin)
@ -57,5 +57,4 @@ using Gurobi
instance = JuMPInstance(model) instance = JuMPInstance(model)
stats = solve!(solver, instance) stats = solve!(solver, instance)
end end
end end

Loading…
Cancel
Save