From 98024dea95610dce157ecbef2bdf9ba5c9e49c57 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Tue, 25 May 2021 11:23:24 -0500 Subject: [PATCH] Partial implementation of parallel_solve --- Project.toml | 1 + README.md | 78 ++++++++++++++++++++++++----------- src/MIPLearn.jl | 14 +++---- src/instance/abstract.jl | 8 ++++ src/instance/file.jl | 6 ++- src/solvers/learning.jl | 27 +++++++++--- src/solvers/macros.jl | 4 ++ src/utils/exceptions.jl | 1 - test/solvers/learning_test.jl | 5 +-- 9 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 src/instance/abstract.jl diff --git a/Project.toml b/Project.toml index 5b7e658..6e1bf52 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.2.0" [deps] Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d" +Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" diff --git a/README.md b/README.md index 34d5193..7ad515d 100644 --- a/README.md +++ b/README.md @@ -108,12 +108,12 @@ end fit!(solver, training_instances) # Save trained solver to disk -save("solver.mls", solver) +save("solver.bin", solver) # Application restarts... # Load trained solver from disk -solver = load("solver.mls") +solver = load_solver("solver.bin") # Solve additional instances test_instances = [...] @@ -123,46 +123,43 @@ end ``` -### 1.5 Solving training instances in parallel -```julia -using MIPLearn -using Cbc +### 1.5 Solving instances from disk -# Solve training instances in parallel -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) -``` +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. -### 1.6 Solving instances from disk ```julia using MIPLearn using JuMP using Cbc -# Create 600 problem instances and save them to files +# Create a large number of problem instances 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) save("instance-$i.bin", instance) end -# Initialize instances and solver +# Initialize training and test instances training_instances = [FileInstance("instance-$i.bin") for i in 1:500] test_instances = [FileInstance("instance-$i.bin") for i in 501:600] + +# Initialize solver 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 solve!(solver, instance) end @@ -176,6 +173,39 @@ for instance in test_instances 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.1 Selecting solver components diff --git a/src/MIPLearn.jl b/src/MIPLearn.jl index d705859..4957a9e 100644 --- a/src/MIPLearn.jl +++ b/src/MIPLearn.jl @@ -6,20 +6,16 @@ __precompile__(false) module MIPLearn using PyCall - -export JuMPInstance -export LearningSolver -export @feature -export @category - -miplearn = pyimport("miplearn") +global miplearn = pyimport("miplearn") +global traceback = pyimport("traceback") include("utils/log.jl") include("utils/exceptions.jl") +include("instance/abstract.jl") +include("instance/jump.jl") +include("instance/file.jl") include("solvers/jump.jl") include("solvers/learning.jl") include("solvers/macros.jl") -include("instance/jump.jl") -include("instance/file.jl") end # module diff --git a/src/instance/abstract.jl b/src/instance/abstract.jl new file mode 100644 index 0000000..439477e --- /dev/null +++ b/src/instance/abstract.jl @@ -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 + + diff --git a/src/instance/file.jl b/src/instance/file.jl index 36b2bc9..a475656 100644 --- a/src/instance/file.jl +++ b/src/instance/file.jl @@ -55,12 +55,16 @@ end struct FileInstance <: Instance py::PyCall.PyObject + filename::AbstractString end function FileInstance(filename)::FileInstance filename isa AbstractString || error("filename should be a string. Found $(typeof(filename)) instead.") - return FileInstance(PyFileInstance(filename)) + return FileInstance( + PyFileInstance(filename), + filename, + ) end diff --git a/src/solvers/learning.jl b/src/solvers/learning.jl index 94b0476..5ab3ba6 100644 --- a/src/solvers/learning.jl +++ b/src/solvers/learning.jl @@ -2,18 +2,21 @@ # Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved. # Released under the modified BSD license. See COPYING.md for more details. -struct LearningSolver - py::PyCall.PyObject -end +using Distributed -abstract type Instance +struct LearningSolver + py::PyCall.PyObject + optimizer_factory end function LearningSolver(optimizer_factory)::LearningSolver py = miplearn.LearningSolver(solver=JuMPSolver(optimizer_factory)) - return LearningSolver(py) + return LearningSolver( + py, + optimizer_factory, + ) end @@ -31,7 +34,19 @@ function fit!(solver::LearningSolver, instances::Vector{<:Instance}) 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, LearningSolver, solve!, - fit! + fit!, + parallel_solve! diff --git a/src/solvers/macros.jl b/src/solvers/macros.jl index b4755c6..8341f5d 100644 --- a/src/solvers/macros.jl +++ b/src/solvers/macros.jl @@ -62,3 +62,7 @@ macro category(obj, category) set_category!($(esc(obj)), $(esc(category))) end end + + +export @feature, + @category diff --git a/src/utils/exceptions.jl b/src/utils/exceptions.jl index b8886f8..e9234b8 100644 --- a/src/utils/exceptions.jl +++ b/src/utils/exceptions.jl @@ -3,7 +3,6 @@ # Released under the modified BSD license. See COPYING.md for more details. using PyCall -traceback = pyimport("traceback") macro python_call(expr) diff --git a/test/solvers/learning_test.jl b/test/solvers/learning_test.jl index 321deba..36c72b0 100644 --- a/test/solvers/learning_test.jl +++ b/test/solvers/learning_test.jl @@ -7,7 +7,7 @@ using MIPLearn using Gurobi @testset "LearningSolver" begin - @testset "model with annotations" begin + @testset "Model with annotations" begin # Create standard JuMP model weights = [1.0, 2.0, 3.0] prices = [5.0, 6.0, 7.0] @@ -48,7 +48,7 @@ using Gurobi solve!(solver, instance) end - @testset "model without annotations" begin + @testset "Model without annotations" begin model = Model() @variable(model, x, Bin) @variable(model, y, Bin) @@ -57,5 +57,4 @@ using Gurobi instance = JuMPInstance(model) stats = solve!(solver, instance) end - end