From 74f7af0304b03ccb9c93ece345912e9af9249347 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Wed, 1 Nov 2023 11:09:15 -0700 Subject: [PATCH 01/10] mip gaps --- src/SolverAPI.jl | 33 +++++++++++++++++++++++- test/all_tests.jl | 6 +++++ test/inputs/abs_gap_out_of_range.json | 1 + test/inputs/rel_gap_non_mip_solvers.json | 1 + test/inputs/rel_gap_out_of_range.json | 1 + 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/inputs/abs_gap_out_of_range.json create mode 100644 test/inputs/rel_gap_non_mip_solvers.json create mode 100644 test/inputs/rel_gap_out_of_range.json diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 7822862..f03793f 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -109,6 +109,9 @@ function response( end res["solve_time_sec"] = Float64(MOI.get(model, MOI.SolveTimeSec())) + res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) + res["relative_gap_tolerance"] = Float64(MOI.get(model, MOI.RelativeGapTolerance())) + res["absolute_gap_tolerance"] = Float64(MOI.get(model, MOI.AbsoluteGapTolerance())) result_count = MOI.get(model, MOI.ResultCount()) results = [Dict{String,Any}() for _ in 1:result_count] @@ -398,8 +401,36 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing MOI.set(model, MOI.TimeLimitSec(), Float64(get(options, :time_limit_sec, 300.0))) end + if MOI.supports(model, MOI.RelativeGapTolerance()) + # Set relative gap tolerance + rel_gap_tol = Float64(get(options, :relative_gap_tolerance, 1e-4)) + if rel_gap_tol < 0 || rel_gap_tol > 1 + throw(Error(NotAllowed, "Relative gap tolerance must be within [0,1].")) + end + + @info "Setting relative gap tolerance = $rel_gap_tol" + MOI.set(model, MOI.RelativeGapTolerance(), rel_gap_tol) + end + + if MOI.supports(model, MOI.AbsoluteGapTolerance()) + # Set absolute gap tolerance + abs_gap_tol = Float64(get(options, :absolute_gap_tolerance, 1e-10)) + if abs_gap_tol < 0 + throw(Error(NotAllowed, "Absolute gap tolerance must be non-negative.")) + end + @info "Setting abs gap tolerance = $abs_gap_tol" + MOI.set(model, MOI.AbsoluteGapTolerance(), abs_gap_tol) + end + for (key, val) in options - if key in [:solver, :print_format, :print_only, :time_limit_sec] + if key in [ + :solver, + :print_format, + :print_only, + :time_limit_sec, + :relative_gap_tolerance, + :absolute_gap_tolerance, + ] # Skip - these are handled elsewhere. continue elseif key == :silent diff --git a/test/all_tests.jl b/test/all_tests.jl index 98c9d69..f47c91c 100644 --- a/test/all_tests.jl +++ b/test/all_tests.jl @@ -118,6 +118,12 @@ end ("feas_with_obj", "InvalidFormat"), # no objective function specified for a minimization problem ("min_no_obj", "InvalidFormat"), + # absolute_gap_tolerance out of range, e.g., -0.1 + ("abs_gap_out_of_range", "NotAllowed"), + # relative_gap_tolerance must be within [0,1] + ("rel_gap_out_of_range", "NotAllowed"), + # relative_gap_tolerance set for non-mip solvers such as MiniZinc or CSP2SAT + ("rel_gap_non_mip_solvers", "NotAllowed"), # unsupported sense such as 'feasibility' ("unsupported_sense", "InvalidFormat"), # range: wrong number of args diff --git a/test/inputs/abs_gap_out_of_range.json b/test/inputs/abs_gap_out_of_range.json new file mode 100644 index 0000000..3a2b7ba --- /dev/null +++ b/test/inputs/abs_gap_out_of_range.json @@ -0,0 +1 @@ +{"version":"0.1","sense":"min","variables":["x","y"],"constraints":[["and",["==",["+","x",["*",3,"y"]],1],[">=",["+","x","y"],1]],["and",["Int","x"],["Nonneg","x"]],["Int","y"]],"objectives":[["+",["*",2,"x"],"y"]],"options":{"solver":"HIGHS", "absolute_gap_tolerance":-0.1}} diff --git a/test/inputs/rel_gap_non_mip_solvers.json b/test/inputs/rel_gap_non_mip_solvers.json new file mode 100644 index 0000000..c513331 --- /dev/null +++ b/test/inputs/rel_gap_non_mip_solvers.json @@ -0,0 +1 @@ +{"version":"0.1","sense":"feas","variables":["x"],"constraints":[["==","x",1],["Int","x"]],"objectives":[], "options":{"solver":"MiniZinc", "relative_gap_tolerance":0.1}} diff --git a/test/inputs/rel_gap_out_of_range.json b/test/inputs/rel_gap_out_of_range.json new file mode 100644 index 0000000..afad52b --- /dev/null +++ b/test/inputs/rel_gap_out_of_range.json @@ -0,0 +1 @@ +{"version":"0.1","sense":"min","variables":["x","y"],"constraints":[["and",["==",["+","x",["*",3,"y"]],1],[">=",["+","x","y"],1]],["and",["Int","x"],["Nonneg","x"]],["Int","y"]],"objectives":[["+",["*",2,"x"],"y"]],"options":{"solver":"HIGHS", "relative_gap_tolerance":1.1}} From 0942e4ef6cfde9b7dc142485b3e472f75eddfd15 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Wed, 1 Nov 2023 11:24:24 -0700 Subject: [PATCH 02/10] check for abs gap --- test/all_tests.jl | 2 ++ test/inputs/abs_gap_non_mip_solvers.json | 1 + 2 files changed, 3 insertions(+) create mode 100644 test/inputs/abs_gap_non_mip_solvers.json diff --git a/test/all_tests.jl b/test/all_tests.jl index f47c91c..4590228 100644 --- a/test/all_tests.jl +++ b/test/all_tests.jl @@ -122,6 +122,8 @@ end ("abs_gap_out_of_range", "NotAllowed"), # relative_gap_tolerance must be within [0,1] ("rel_gap_out_of_range", "NotAllowed"), + # absolute_gap_tolerance set for non-mip solvers such as MiniZinc or CSP2SAT + ("abs_gap_non_mip_solvers", "NotAllowed"), # relative_gap_tolerance set for non-mip solvers such as MiniZinc or CSP2SAT ("rel_gap_non_mip_solvers", "NotAllowed"), # unsupported sense such as 'feasibility' diff --git a/test/inputs/abs_gap_non_mip_solvers.json b/test/inputs/abs_gap_non_mip_solvers.json new file mode 100644 index 0000000..ec530e2 --- /dev/null +++ b/test/inputs/abs_gap_non_mip_solvers.json @@ -0,0 +1 @@ +{"version":"0.1","sense":"feas","variables":["x"],"constraints":[["==","x",1],["Int","x"]],"objectives":[], "options":{"solver":"MiniZinc", "absolute_gap_tolerance":0.1}} From 70bfef105e5af3bfe00a96597f4cdf8a7da967b2 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Wed, 1 Nov 2023 14:48:25 -0700 Subject: [PATCH 03/10] fix broken tests --- src/SolverAPI.jl | 23 ++++++++++++++++------- test/all_tests.jl | 6 ++---- test/inputs/abs_gap_non_mip_solvers.json | 1 - test/inputs/rel_gap_non_mip_solvers.json | 1 - 4 files changed, 18 insertions(+), 13 deletions(-) delete mode 100644 test/inputs/abs_gap_non_mip_solvers.json delete mode 100644 test/inputs/rel_gap_non_mip_solvers.json diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index f03793f..0563f27 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -109,11 +109,18 @@ function response( end res["solve_time_sec"] = Float64(MOI.get(model, MOI.SolveTimeSec())) - res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) - res["relative_gap_tolerance"] = Float64(MOI.get(model, MOI.RelativeGapTolerance())) - res["absolute_gap_tolerance"] = Float64(MOI.get(model, MOI.AbsoluteGapTolerance())) result_count = MOI.get(model, MOI.ResultCount()) + + try + res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) + if isinf(res["relative_gap"]) + res["relative_gap"] = nothing # Inf cannot be serialized to JSON + end + catch + # ignore if solver does not support relative gap + end + results = [Dict{String,Any}() for _ in 1:result_count] var_names = [string('\"', v, '\"') for v in json.variables] var_idxs = MOI.get(model, MOI.ListOfVariableIndices()) @@ -401,9 +408,10 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing MOI.set(model, MOI.TimeLimitSec(), Float64(get(options, :time_limit_sec, 300.0))) end - if MOI.supports(model, MOI.RelativeGapTolerance()) + if MOI.supports(model, MOI.RelativeGapTolerance()) && + haskey(options, :relative_gap_tolerance) # Set relative gap tolerance - rel_gap_tol = Float64(get(options, :relative_gap_tolerance, 1e-4)) + rel_gap_tol = Float64(options[:relative_gap_tolerance]) if rel_gap_tol < 0 || rel_gap_tol > 1 throw(Error(NotAllowed, "Relative gap tolerance must be within [0,1].")) end @@ -412,9 +420,10 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing MOI.set(model, MOI.RelativeGapTolerance(), rel_gap_tol) end - if MOI.supports(model, MOI.AbsoluteGapTolerance()) + if MOI.supports(model, MOI.AbsoluteGapTolerance()) && + haskey(options, :absolute_gap_tolerance) # Set absolute gap tolerance - abs_gap_tol = Float64(get(options, :absolute_gap_tolerance, 1e-10)) + abs_gap_tol = Float64(options[:absolute_gap_tolerance]) if abs_gap_tol < 0 throw(Error(NotAllowed, "Absolute gap tolerance must be non-negative.")) end diff --git a/test/all_tests.jl b/test/all_tests.jl index ffe97a2..6fbcbb7 100644 --- a/test/all_tests.jl +++ b/test/all_tests.jl @@ -81,6 +81,8 @@ end # solve and check output is expected for each input json file @testset "$j" for j in json_names output = JSON3.read(run_solve(read_json("inputs", j))) + @info output + #isdefined(Main, :Infiltrator) && Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) @test output.solver_version isa String @test output.solve_time_sec isa Float64 expect = JSON3.read(read_json("outputs", j)) @@ -126,10 +128,6 @@ end ("abs_gap_out_of_range", "NotAllowed"), # relative_gap_tolerance must be within [0,1] ("rel_gap_out_of_range", "NotAllowed"), - # absolute_gap_tolerance set for non-mip solvers such as MiniZinc or CSP2SAT - ("abs_gap_non_mip_solvers", "NotAllowed"), - # relative_gap_tolerance set for non-mip solvers such as MiniZinc or CSP2SAT - ("rel_gap_non_mip_solvers", "NotAllowed"), # unsupported sense such as 'feasibility' ("unsupported_sense", "InvalidFormat"), # range: wrong number of args diff --git a/test/inputs/abs_gap_non_mip_solvers.json b/test/inputs/abs_gap_non_mip_solvers.json deleted file mode 100644 index ec530e2..0000000 --- a/test/inputs/abs_gap_non_mip_solvers.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"0.1","sense":"feas","variables":["x"],"constraints":[["==","x",1],["Int","x"]],"objectives":[], "options":{"solver":"MiniZinc", "absolute_gap_tolerance":0.1}} diff --git a/test/inputs/rel_gap_non_mip_solvers.json b/test/inputs/rel_gap_non_mip_solvers.json deleted file mode 100644 index c513331..0000000 --- a/test/inputs/rel_gap_non_mip_solvers.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"0.1","sense":"feas","variables":["x"],"constraints":[["==","x",1],["Int","x"]],"objectives":[], "options":{"solver":"MiniZinc", "relative_gap_tolerance":0.1}} From bd93b75d63c7601c235c578f0e0ad19f38f1de63 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Thu, 2 Nov 2023 20:00:17 -0700 Subject: [PATCH 04/10] remove info --- src/SolverAPI.jl | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 7822862..4d6e3f0 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -109,6 +109,9 @@ function response( end res["solve_time_sec"] = Float64(MOI.get(model, MOI.SolveTimeSec())) + res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) + res["relative_gap_tolerance"] = Float64(MOI.get(model, MOI.RelativeGapTolerance())) + res["absolute_gap_tolerance"] = Float64(MOI.get(model, MOI.AbsoluteGapTolerance())) result_count = MOI.get(model, MOI.ResultCount()) results = [Dict{String,Any}() for _ in 1:result_count] @@ -398,8 +401,27 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing MOI.set(model, MOI.TimeLimitSec(), Float64(get(options, :time_limit_sec, 300.0))) end + if MOI.supports(model, MOI.RelativeGapTolerance()) + # Set relative gap tolerance + rel_gap_tol = Float64(get(options, :relative_gap_tolerance, 1e-4)) + if rel_gap_tol < 0 || rel_gap_tol > 1 + throw(Error(InvalidParameter, "Relative gap tolerance must be within [0,1].")) + end + + MOI.set(model, MOI.RelativeGapTolerance(), rel_gap_tol) + end + + if MOI.supports(model, MOI.AbsoluteGapTolerance()) + # Set absolute gap tolerance + abs_gap_tol = Float64(get(options, :absolute_gap_tolerance, 1e-10)) + if abs_gap_tol < 0 + throw(Error(InvalidParameter, "Absolute gap tolerance must be non-negative.")) + end + MOI.set(model, MOI.AbsoluteGapTolerance(), abs_gap_tol) + end + for (key, val) in options - if key in [:solver, :print_format, :print_only, :time_limit_sec] + if key in [:solver, :print_format, :print_only, :time_limit_sec, :relative_gap_tolerance, :absolute_gap_tolerance] # Skip - these are handled elsewhere. continue elseif key == :silent From 5e50c534562d46b11ec22d5dd540561955d469d2 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Thu, 2 Nov 2023 20:58:49 -0700 Subject: [PATCH 05/10] fix broken tests --- src/SolverAPI.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 797a050..0563f27 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -109,9 +109,6 @@ function response( end res["solve_time_sec"] = Float64(MOI.get(model, MOI.SolveTimeSec())) - res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) - res["relative_gap_tolerance"] = Float64(MOI.get(model, MOI.RelativeGapTolerance())) - res["absolute_gap_tolerance"] = Float64(MOI.get(model, MOI.AbsoluteGapTolerance())) result_count = MOI.get(model, MOI.ResultCount()) From 199ed83eb49d9b3bc8c5919cb9de453b9cc721ed Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Fri, 3 Nov 2023 10:40:06 -0700 Subject: [PATCH 06/10] remove info lines --- src/SolverAPI.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 0563f27..7af6cc9 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -416,7 +416,6 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing throw(Error(NotAllowed, "Relative gap tolerance must be within [0,1].")) end - @info "Setting relative gap tolerance = $rel_gap_tol" MOI.set(model, MOI.RelativeGapTolerance(), rel_gap_tol) end @@ -427,7 +426,7 @@ function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing if abs_gap_tol < 0 throw(Error(NotAllowed, "Absolute gap tolerance must be non-negative.")) end - @info "Setting abs gap tolerance = $abs_gap_tol" + MOI.set(model, MOI.AbsoluteGapTolerance(), abs_gap_tol) end From a62035781d2b63aad7e4d166e4617d6338e9e236 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Fri, 3 Nov 2023 10:47:11 -0700 Subject: [PATCH 07/10] remove extra info and comments --- test/all_tests.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/all_tests.jl b/test/all_tests.jl index 6fbcbb7..e09a888 100644 --- a/test/all_tests.jl +++ b/test/all_tests.jl @@ -81,8 +81,6 @@ end # solve and check output is expected for each input json file @testset "$j" for j in json_names output = JSON3.read(run_solve(read_json("inputs", j))) - @info output - #isdefined(Main, :Infiltrator) && Main.infiltrate(@__MODULE__, Base.@locals, @__FILE__, @__LINE__) @test output.solver_version isa String @test output.solve_time_sec isa Float64 expect = JSON3.read(read_json("outputs", j)) From 982fef80d0ffe4f38073202b2fb462267ad300a1 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Fri, 3 Nov 2023 10:49:41 -0700 Subject: [PATCH 08/10] minor tweak per Chris' suggestion --- src/SolverAPI.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 7af6cc9..ade8309 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -113,9 +113,10 @@ function response( result_count = MOI.get(model, MOI.ResultCount()) try - res["relative_gap"] = Float64(MOI.get(model, MOI.RelativeGap())) - if isinf(res["relative_gap"]) - res["relative_gap"] = nothing # Inf cannot be serialized to JSON + res["relative_gap"] + rel_gap = Float64(MOI.get(model, MOI.RelativeGap())) + if isinf(rel_gap) + res["relative_gap"] = rel_gap# Inf cannot be serialized to JSON end catch # ignore if solver does not support relative gap From 677af3fa7aeb34fcae5a4d0d263ce7b5c9631afc Mon Sep 17 00:00:00 2001 From: zengjian-hu-rai <106710107+zengjian-hu-rai@users.noreply.github.com> Date: Fri, 3 Nov 2023 10:59:04 -0700 Subject: [PATCH 09/10] Update src/SolverAPI.jl Co-authored-by: Chris Coey --- src/SolverAPI.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index ade8309..a37295d 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -116,7 +116,7 @@ function response( res["relative_gap"] rel_gap = Float64(MOI.get(model, MOI.RelativeGap())) if isinf(rel_gap) - res["relative_gap"] = rel_gap# Inf cannot be serialized to JSON + res["relative_gap"] = rel_gap # Inf cannot be serialized to JSON end catch # ignore if solver does not support relative gap From 558fe68e51511787c83caaaa28778e2a3276d2e6 Mon Sep 17 00:00:00 2001 From: Zengjian Hu Date: Fri, 3 Nov 2023 11:05:39 -0700 Subject: [PATCH 10/10] report rel_gap if not inf --- src/SolverAPI.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index a37295d..e8f534f 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -115,7 +115,7 @@ function response( try res["relative_gap"] rel_gap = Float64(MOI.get(model, MOI.RelativeGap())) - if isinf(rel_gap) + if !isinf(rel_gap) res["relative_gap"] = rel_gap # Inf cannot be serialized to JSON end catch