From 808e684c11dcd5503aae33caaf383fece4f6fe63 Mon Sep 17 00:00:00 2001 From: Alinson S Xavier Date: Sun, 12 Apr 2020 15:56:59 -0500 Subject: [PATCH] Make Julia's solution format consistent with Python's --- src/julia/src/MIPLearn.jl | 48 +++++++++++++++++------- src/julia/test/jump_solver.jl | 47 ++++++++++++----------- src/julia/test/learning_solver.jl | 50 +++++++++++++++++++++++++ src/julia/test/runtests.jl | 4 +- src/python/miplearn/solvers/learning.py | 3 +- 5 files changed, 112 insertions(+), 40 deletions(-) create mode 100644 src/julia/test/learning_solver.jl diff --git a/src/julia/src/MIPLearn.jl b/src/julia/src/MIPLearn.jl index 264d6a5..2458438 100644 --- a/src/julia/src/MIPLearn.jl +++ b/src/julia/src/MIPLearn.jl @@ -15,36 +15,49 @@ Instance = miplearn.Instance LearningSolver = miplearn.LearningSolver InternalSolver = miplearn.solvers.internal.InternalSolver +function varname_split(varname::String) + m = match(r"([^[]*)\[(.*)\]", varname) + return m.captures[1], m.captures[2] +end + @pydef mutable struct JuMPSolver <: InternalSolver function __init__(self; optimizer=CPLEX.Optimizer) self.optimizer = optimizer end function add_constraint(self, constraint) - @error "Not implemented" + @error "JuMPSolver: add_constraint not implemented" end function set_warm_start(self, solution) - for (varname, value) in solution - var = JuMP.variable_by_name(self.model, varname) - JuMP.set_start_value(var, value) + for (basename, subsolution) in solution + for (idx, value) in subsolution + var = self.basename_idx_to_var[basename, idx] + JuMP.set_start_value(var, value) + end end end function clear_warm_start(self) - @error "Not implemented" + @error "JuMPSolver: clear_warm_start not implemented" end function fix(self, solution) - for (varname, value) in solution - var = JuMP.variable_by_name(self.model, varname) - JuMP.fix(var, value, force=true) + for (basename, subsolution) in solution + for (idx, value) in subsolution + var = self.basename_idx_to_var[basename, idx] + JuMP.fix(var, value, force=true) + end end end function set_instance(self, instance, model) self.instance = instance self.model = model + self.var_to_basename_idx = Dict(var => varname_split(JuMP.name(var)) + for var in JuMP.all_variables(self.model)) + self.basename_idx_to_var = Dict(varname_split(JuMP.name(var)) => var + for var in JuMP.all_variables(self.model)) self.bin_vars = [var for var in JuMP.all_variables(self.model) if JuMP.is_binary(var)] @@ -102,20 +115,27 @@ InternalSolver = miplearn.solvers.internal.InternalSolver end function _update_solution(self) - self.solution = Dict(JuMP.name(var) => JuMP.value(var) - for var in JuMP.all_variables(self.model)) + solution = Dict() + for var in JuMP.all_variables(self.model) + basename, idx = self.var_to_basename_idx[var] + if !haskey(solution, basename) + solution[basename] = Dict() + end + solution[basename][idx] = JuMP.value(var) + end + self.solution = solution end function set_gap_tolerance(self, gap_tolerance) - @error "Not implemented" + @error "JuMPSolver: set_gap_tolerance not implemented" end function set_node_limit(self) - @error "Not implemented" + @error "JuMPSolver: set_node_limit not implemented" end function set_threads(self, threads) - @error "Not implemented" + @error "JuMPSolver: set_threads not implemented" end function set_time_limit(self, time_limit) @@ -148,6 +168,6 @@ end end end -export JuMPSolver, KnapsackInstance +export LearningSolver, JuMPSolver, KnapsackInstance end # module diff --git a/src/julia/test/jump_solver.jl b/src/julia/test/jump_solver.jl index ac53a78..c3eeb0e 100644 --- a/src/julia/test/jump_solver.jl +++ b/src/julia/test/jump_solver.jl @@ -7,6 +7,11 @@ using MIPLearn using CPLEX using Gurobi +@testset "varname_split" begin + @test MIPLearn.varname_split("x[1]") == ("x", "1") +end + + @testset "JuMPSolver" begin for optimizer in [CPLEX.Optimizer, Gurobi.Optimizer] instance = KnapsackInstance([23., 26., 20., 18.], @@ -17,12 +22,12 @@ using Gurobi solver = JuMPSolver(optimizer=optimizer) solver.set_instance(instance, model) solver.set_time_limit(30) - solver.set_warm_start(Dict( - "x[1]" => 1.0, - "x[2]" => 0.0, - "x[3]" => 0.0, - "x[4]" => 1.0, - )) + solver.set_warm_start(Dict("x" => Dict( + "1" => 1.0, + "2" => 0.0, + "3" => 0.0, + "4" => 1.0, + ))) stats = solver.solve() @test stats["Lower bound"] == 1183.0 @@ -31,26 +36,26 @@ using Gurobi @test stats["Wallclock time"] > 0 solution = solver.get_solution() - @test solution["x[1]"] == 1.0 - @test solution["x[2]"] == 0.0 - @test solution["x[3]"] == 1.0 - @test solution["x[4]"] == 1.0 + @test solution["x"]["1"] == 1.0 + @test solution["x"]["2"] == 0.0 + @test solution["x"]["3"] == 1.0 + @test solution["x"]["4"] == 1.0 stats = solver.solve_lp() @test round(stats["Optimal value"], digits=3) == 1287.923 solution = solver.get_solution() - @test round(solution["x[1]"], digits=3) == 1.000 - @test round(solution["x[2]"], digits=3) == 0.923 - @test round(solution["x[3]"], digits=3) == 1.000 - @test round(solution["x[4]"], digits=3) == 0.000 - - solver.fix(Dict( - "x[1]" => 1.0, - "x[2]" => 0.0, - "x[3]" => 0.0, - "x[4]" => 1.0, - )) + @test round(solution["x"]["1"], digits=3) == 1.000 + @test round(solution["x"]["2"], digits=3) == 0.923 + @test round(solution["x"]["3"], digits=3) == 1.000 + @test round(solution["x"]["4"], digits=3) == 0.000 + + solver.fix(Dict("x" => Dict( + "1" => 1.0, + "2" => 0.0, + "3" => 0.0, + "4" => 1.0, + ))) stats = solver.solve() @test stats["Lower bound"] == 725.0 @test stats["Upper bound"] == 725.0 diff --git a/src/julia/test/learning_solver.jl b/src/julia/test/learning_solver.jl new file mode 100644 index 0000000..de2ea80 --- /dev/null +++ b/src/julia/test/learning_solver.jl @@ -0,0 +1,50 @@ +# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization +# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. +# Released under the modified BSD license. See COPYING.md for more details. + +using Test +using MIPLearn +using CPLEX +using Gurobi + +@testset "LearningSolver" begin + for optimizer in [CPLEX.Optimizer, Gurobi.Optimizer] + instance = KnapsackInstance([23., 26., 20., 18.], + [505., 352., 458., 220.], + 67.0) + model = instance.to_model() + + solver = LearningSolver(solver=JuMPSolver(optimizer=optimizer)) + stats = solver.solve(instance, model) + + @test stats["Lower bound"] == 1183.0 + @test stats["Upper bound"] == 1183.0 + @test stats["Sense"] == "max" + @test stats["Wallclock time"] > 0 + +# solution = solver.get_solution() +# @test solution["x[1]"] == 1.0 +# @test solution["x[2]"] == 0.0 +# @test solution["x[3]"] == 1.0 +# @test solution["x[4]"] == 1.0 +# +# stats = solver.solve_lp() +# @test round(stats["Optimal value"], digits=3) == 1287.923 +# +# solution = solver.get_solution() +# @test round(solution["x[1]"], digits=3) == 1.000 +# @test round(solution["x[2]"], digits=3) == 0.923 +# @test round(solution["x[3]"], digits=3) == 1.000 +# @test round(solution["x[4]"], digits=3) == 0.000 +# +# solver.fix(Dict( +# "x[1]" => 1.0, +# "x[2]" => 0.0, +# "x[3]" => 0.0, +# "x[4]" => 1.0, +# )) +# stats = solver.solve() +# @test stats["Lower bound"] == 725.0 +# @test stats["Upper bound"] == 725.0 + end +end \ No newline at end of file diff --git a/src/julia/test/runtests.jl b/src/julia/test/runtests.jl index 7d262dd..f421296 100644 --- a/src/julia/test/runtests.jl +++ b/src/julia/test/runtests.jl @@ -3,10 +3,8 @@ # Released under the modified BSD license. See COPYING.md for more details. using Test -using MIPLearn -using CPLEX -using Gurobi @testset "MIPLearn" begin include("jump_solver.jl") + #include("learning_solver.jl") end \ No newline at end of file diff --git a/src/python/miplearn/solvers/learning.py b/src/python/miplearn/solvers/learning.py index 2b4f858..de359cb 100644 --- a/src/python/miplearn/solvers/learning.py +++ b/src/python/miplearn/solvers/learning.py @@ -79,9 +79,8 @@ class LearningSolver: solver = GurobiSolver() elif callable(self.internal_solver_factory): solver = self.internal_solver_factory() - assert isinstance(solver, InternalSolver) else: - raise Exception("solver %s not supported" % self.internal_solver_factory) + solver = self.internal_solver_factory solver.set_threads(self.threads) if self.time_limit is not None: solver.set_time_limit(self.time_limit)