From 27f88670d8957646bc0bf262c132091fcedeaa03 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:14:42 +0000 Subject: [PATCH 01/13] Add mip_model as an option If use we need to dissect it in a similar way to jump; hence, `jp.mip_model = model.options.mip_model.moi_backend.optimizer.model` --- src/bb_inits_and_defaults.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bb_inits_and_defaults.jl b/src/bb_inits_and_defaults.jl index 31c3509..e7b9120 100644 --- a/src/bb_inits_and_defaults.jl +++ b/src/bb_inits_and_defaults.jl @@ -220,10 +220,14 @@ function init_juniper_problem!(jp::JuniperProblem, model::MOI.AbstractOptimizer) jp.start_time = time() jp.nl_solver = model.options.nl_solver + jp.mip_model = nothing if model.options.mip_solver !== nothing jp.mip_solver = model.options.mip_solver end + if model.options.mip_model !== nothing + jp.mip_model = model.options.mip_model.moi_backend.optimizer.model + end jp.options = model.options if MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE jp.obj_sense = :Min From 4f36aa0334669893193cc560f6f19c12994fe223 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:14:54 +0000 Subject: [PATCH 02/13] solver needs to accept mip_model as an opton --- src/solver.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/solver.jl b/src/solver.jl index 7d688a7..00a2e17 100644 --- a/src/solver.jl +++ b/src/solver.jl @@ -39,6 +39,7 @@ function get_default_options() tabu_list_length = 30 num_resolve_nlp_feasibility_pump = 1 mip_solver = nothing + mip_model = nothing allow_almost_solved = true allow_almost_solved_integral = true registered_functions = nothing @@ -82,6 +83,7 @@ function get_default_options() tabu_list_length, num_resolve_nlp_feasibility_pump, mip_solver, + mip_model, allow_almost_solved, allow_almost_solved_integral, registered_functions, From 04042fa422a1508432e67d49b80ccb64df7fbcbe Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:15:12 +0000 Subject: [PATCH 03/13] Establish mip_model as a solver option --- src/types.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types.jl b/src/types.jl index 197b4b1..7bf55da 100644 --- a/src/types.jl +++ b/src/types.jl @@ -38,6 +38,7 @@ mutable struct SolverOptions tabu_list_length::Int64 num_resolve_nlp_feasibility_pump::Int64 mip_solver::Any + mip_model::Any allow_almost_solved::Bool allow_almost_solved_integral::Bool registered_functions::Union{Nothing,Vector{RegisteredFunction}} @@ -97,6 +98,7 @@ mutable struct JuniperProblem nsolutions::Int64 mip_solver::Any + mip_model::Any relaxation_time::Float64 start_time::Float64 From 1b0edf1dc796df73fb6bf26c1fd288210a6300db Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:16:16 +0000 Subject: [PATCH 04/13] use provided mip model if provided Check if a mip model has been provided, if so use it for the fpump --- src/fpump.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fpump.jl b/src/fpump.jl index 1db7dc0..80e77a5 100644 --- a/src/fpump.jl +++ b/src/fpump.jl @@ -337,6 +337,7 @@ function fpump(optimizer, m) # the tolerance can be changed => current atol catol = m.options.atol atol_counter = 0 + mip_model = isnothing(m.mip_model) ? optimizer : m.mip_model while !are_type_correct(nlp_sol, m.var_type, m.disc2var_idx, catol) && time() - start_fpump < tl && time() - m.start_time < m.options.time_limit @@ -345,12 +346,12 @@ function fpump(optimizer, m) if any( FS -> FS[1] != MOI.VariableIndex, MOI.get( - LinearFilter(optimizer), + LinearFilter(mip_model), MOI.ListOfConstraintTypesPresent(), ), ) mip_status, mip_sol, mip_obj = - generate_mip(optimizer, m, nlp_sol, tabu_list, start_fpump) + generate_mip(mip_model, m, nlp_sol, tabu_list, start_fpump) else # if no linear constraints just round the discrete variables mip_obj = NaN From 267d08c2f1e46970d2940271488e0a3f61556657 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:09:10 +0000 Subject: [PATCH 05/13] Check optimize has been called as this is required --- src/bb_inits_and_defaults.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bb_inits_and_defaults.jl b/src/bb_inits_and_defaults.jl index e7b9120..3ac80ee 100644 --- a/src/bb_inits_and_defaults.jl +++ b/src/bb_inits_and_defaults.jl @@ -226,6 +226,9 @@ function init_juniper_problem!(jp::JuniperProblem, model::MOI.AbstractOptimizer) jp.mip_solver = model.options.mip_solver end if model.options.mip_model !== nothing + if MOI.get(model.options.mip_model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + error("The MIP model has not been solved, optimize it before setting `mip_model`.") + end jp.mip_model = model.options.mip_model.moi_backend.optimizer.model end jp.options = model.options From 609f12ce0b20de2cf4121d32d3516d206a2dac79 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:09:29 +0000 Subject: [PATCH 06/13] correct type for mip model --- src/types.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 7bf55da..7398643 100644 --- a/src/types.jl +++ b/src/types.jl @@ -98,7 +98,7 @@ mutable struct JuniperProblem nsolutions::Int64 mip_solver::Any - mip_model::Any + mip_model::Union{MOI.AbstractOptimizer, Nothing} relaxation_time::Float64 start_time::Float64 From add812feb627a8d5750cc1f4bbea1de33dc6a71f Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:09:50 +0000 Subject: [PATCH 07/13] Use full model to look for linear constraints. --- src/fpump.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fpump.jl b/src/fpump.jl index 80e77a5..21fde53 100644 --- a/src/fpump.jl +++ b/src/fpump.jl @@ -346,7 +346,7 @@ function fpump(optimizer, m) if any( FS -> FS[1] != MOI.VariableIndex, MOI.get( - LinearFilter(mip_model), + LinearFilter(optimizer), MOI.ListOfConstraintTypesPresent(), ), ) From 7dd70617888f8980d7085ea798d5ee784eaa4c3e Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:15:54 +0000 Subject: [PATCH 08/13] change error type raised --- src/bb_inits_and_defaults.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bb_inits_and_defaults.jl b/src/bb_inits_and_defaults.jl index 3ac80ee..91b73e9 100644 --- a/src/bb_inits_and_defaults.jl +++ b/src/bb_inits_and_defaults.jl @@ -227,7 +227,7 @@ function init_juniper_problem!(jp::JuniperProblem, model::MOI.AbstractOptimizer) end if model.options.mip_model !== nothing if MOI.get(model.options.mip_model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED - error("The MIP model has not been solved, optimize it before setting `mip_model`.") + throw(ErrorException("The MIP model has not been solved, optimize it before setting `mip_model`.")) end jp.mip_model = model.options.mip_model.moi_backend.optimizer.model end From 1c47a99d26931d2ba893cb32f0b5c92a07cd3d0c Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:16:06 +0000 Subject: [PATCH 09/13] Add a very basic test --- test/fpump.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/fpump.jl b/test/fpump.jl index 3451507..85a42ef 100644 --- a/test/fpump.jl +++ b/test/fpump.jl @@ -292,4 +292,29 @@ include("basic/gamsworld.jl") end @test JuMP.objective_value(m) ≈ 0.0 end + @testset "Custom linear relaxation" begin + _nl_solver = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) + + juniper_opt = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => _nl_solver, "mip_solver" => HiGHS.Optimizer) + model = Model(juniper_opt) + @variable(model, a, integer=true) + @constraint(model, 0<=model[:a] <= 10) + @NLconstraint(model, model[:a] * abs(model[:a]) >=3) + @objective(model, Min, model[:a]) + juniper_opt = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => _nl_solver, "mip_solver" => Gurobi.Optimizer, "time_limit"=>64) + + mip = Model(juniper_opt) + @variable(mip, a, integer=true) + @constraint(mip, mip[:a] * mip[:a] ==0) + @constraint(mip, mip[:a] <= 10) + @objective(mip, Min, mip[:a]) + set_silent(mip) + set_optimizer_attribute(model, "mip_model", mip) + + @test_throws ErrorException JuMP.optimize!(model) + JuMP.optimize!(mip) + set_optimizer_attribute(model, "mip_model", mip) + JuMP.optimize!(model) + @test JuMP.termination_status(model) == MOI.LOCALLY_SOLVED + end end From 8db52d90dccbad13c2d43a08277e8b5d5145fb1c Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:37:19 +0000 Subject: [PATCH 10/13] get test working --- test/fpump.jl | 35 +++++++++++++---------------------- test/runtests.jl | 14 +++++++------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/test/fpump.jl b/test/fpump.jl index 85a42ef..1c1f88e 100644 --- a/test/fpump.jl +++ b/test/fpump.jl @@ -276,41 +276,32 @@ include("basic/gamsworld.jl") "output_flag" => false, ) juniper_solver = JuMP.optimizer_with_attributes( + @testset "Custom linear relaxation" begin + optimizer = optimizer_with_attributes( Juniper.Optimizer, - "nl_solver" => ipopt_solver, - "mip_solver" => highs_solver, - "log_levels" => [], + DefaultTestSolver( + branch_strategy = :MostInfeasible, + mip_solver = optimizer_with_attributes( + HiGHS.Optimizer, + "output_flag" => false, + ), + )..., ) - m = Model(juniper_solver) - @variable(m, x[1:3], Int) - @variable(m, y) - @NLconstraint(m, x[1] * x[2] * x[3] * y >= 5) - optimize!(m) - for i in 1:3 - xval = JuMP.value(x[i]) - @test isapprox(round(xval) - xval, 0; atol = sol_atol) - end - @test JuMP.objective_value(m) ≈ 0.0 - end - @testset "Custom linear relaxation" begin - _nl_solver = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0) - - juniper_opt = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => _nl_solver, "mip_solver" => HiGHS.Optimizer) - model = Model(juniper_opt) + model = Model(optimizer) @variable(model, a, integer=true) @constraint(model, 0<=model[:a] <= 10) @NLconstraint(model, model[:a] * abs(model[:a]) >=3) @objective(model, Min, model[:a]) - juniper_opt = optimizer_with_attributes(Juniper.Optimizer, "nl_solver" => _nl_solver, "mip_solver" => Gurobi.Optimizer, "time_limit"=>64) - mip = Model(juniper_opt) + mip = Model(optimizer) @variable(mip, a, integer=true) @constraint(mip, mip[:a] * mip[:a] ==0) @constraint(mip, mip[:a] <= 10) @objective(mip, Min, mip[:a]) set_silent(mip) - set_optimizer_attribute(model, "mip_model", mip) + set_optimizer_attribute(model, "mip_model", mip) + # optimize!(mip) hasn't been run so we expect an error @test_throws ErrorException JuMP.optimize!(model) JuMP.optimize!(mip) set_optimizer_attribute(model, "mip_model", mip) diff --git a/test/runtests.jl b/test/runtests.jl index 018f301..0665f4f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -84,13 +84,13 @@ include("debug.jl") start = time() @testset "Juniper" begin - include("functions.jl") - include("basic.jl") - include("user_limits.jl") - include("parallel.jl") + # include("functions.jl") + # include("basic.jl") + # include("user_limits.jl") + # include("parallel.jl") include("fpump.jl") - include("pod.jl") - include("MOI_wrapper.jl") - include("conic.jl") + # include("pod.jl") + # include("MOI_wrapper.jl") + # include("conic.jl") end println("Time for all tests: ", time() - start) From 58db64b1b55c5045886a49a18a6894943f0b165d Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:38:04 +0000 Subject: [PATCH 11/13] reintroduce all tests --- test/runtests.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 0665f4f..018f301 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -84,13 +84,13 @@ include("debug.jl") start = time() @testset "Juniper" begin - # include("functions.jl") - # include("basic.jl") - # include("user_limits.jl") - # include("parallel.jl") + include("functions.jl") + include("basic.jl") + include("user_limits.jl") + include("parallel.jl") include("fpump.jl") - # include("pod.jl") - # include("MOI_wrapper.jl") - # include("conic.jl") + include("pod.jl") + include("MOI_wrapper.jl") + include("conic.jl") end println("Time for all tests: ", time() - start) From 6d42928aa5cdd79e6ba70ebf088000da204c2329 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:41:41 +0000 Subject: [PATCH 12/13] recover "FP: Issue 195: FPump without objective" accidentally removed in 8db52d9 --- test/fpump.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/fpump.jl b/test/fpump.jl index 1c1f88e..26ee15b 100644 --- a/test/fpump.jl +++ b/test/fpump.jl @@ -276,6 +276,23 @@ include("basic/gamsworld.jl") "output_flag" => false, ) juniper_solver = JuMP.optimizer_with_attributes( + Juniper.Optimizer, + "nl_solver" => ipopt_solver, + "mip_solver" => highs_solver, + "log_levels" => [], + ) + m = Model(juniper_solver) + @variable(m, x[1:3], Int) + @variable(m, y) + @NLconstraint(m, x[1] * x[2] * x[3] * y >= 5) + optimize!(m) + for i in 1:3 + xval = JuMP.value(x[i]) + @test isapprox(round(xval) - xval, 0; atol = sol_atol) + end + @test JuMP.objective_value(m) ≈ 0.0 + end + @testset "Custom linear relaxation" begin optimizer = optimizer_with_attributes( Juniper.Optimizer, From a24b69ed0f6a8b11ea3194d86ec8a3bd626055d7 Mon Sep 17 00:00:00 2001 From: Josh Kirk <59419126+this-josh@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:51:29 +0000 Subject: [PATCH 13/13] format files --- src/bb_inits_and_defaults.jl | 9 +++++++-- src/types.jl | 2 +- test/fpump.jl | 14 +++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/bb_inits_and_defaults.jl b/src/bb_inits_and_defaults.jl index 91b73e9..43e53d6 100644 --- a/src/bb_inits_and_defaults.jl +++ b/src/bb_inits_and_defaults.jl @@ -226,8 +226,13 @@ function init_juniper_problem!(jp::JuniperProblem, model::MOI.AbstractOptimizer) jp.mip_solver = model.options.mip_solver end if model.options.mip_model !== nothing - if MOI.get(model.options.mip_model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED - throw(ErrorException("The MIP model has not been solved, optimize it before setting `mip_model`.")) + if MOI.get(model.options.mip_model, MOI.TerminationStatus()) == + MOI.OPTIMIZE_NOT_CALLED + throw( + ErrorException( + "The MIP model has not been solved, optimize it before setting `mip_model`.", + ), + ) end jp.mip_model = model.options.mip_model.moi_backend.optimizer.model end diff --git a/src/types.jl b/src/types.jl index 7398643..0e69d2c 100644 --- a/src/types.jl +++ b/src/types.jl @@ -98,7 +98,7 @@ mutable struct JuniperProblem nsolutions::Int64 mip_solver::Any - mip_model::Union{MOI.AbstractOptimizer, Nothing} + mip_model::Union{MOI.AbstractOptimizer,Nothing} relaxation_time::Float64 start_time::Float64 diff --git a/test/fpump.jl b/test/fpump.jl index 26ee15b..5f13f52 100644 --- a/test/fpump.jl +++ b/test/fpump.jl @@ -291,7 +291,7 @@ include("basic/gamsworld.jl") @test isapprox(round(xval) - xval, 0; atol = sol_atol) end @test JuMP.objective_value(m) ≈ 0.0 - end + end @testset "Custom linear relaxation" begin optimizer = optimizer_with_attributes( @@ -305,14 +305,14 @@ include("basic/gamsworld.jl") )..., ) model = Model(optimizer) - @variable(model, a, integer=true) - @constraint(model, 0<=model[:a] <= 10) - @NLconstraint(model, model[:a] * abs(model[:a]) >=3) + @variable(model, a, integer = true) + @constraint(model, 0 <= model[:a] <= 10) + @NLconstraint(model, model[:a] * abs(model[:a]) >= 3) @objective(model, Min, model[:a]) - + mip = Model(optimizer) - @variable(mip, a, integer=true) - @constraint(mip, mip[:a] * mip[:a] ==0) + @variable(mip, a, integer = true) + @constraint(mip, mip[:a] * mip[:a] == 0) @constraint(mip, mip[:a] <= 10) @objective(mip, Min, mip[:a]) set_silent(mip)