From e347cf85f2ae96f9a8d981c666714261f2df820e Mon Sep 17 00:00:00 2001 From: David Bach Date: Fri, 5 Jan 2024 13:32:59 +0200 Subject: [PATCH] Solver options via params argument (#30) * add a params argument to pass solver options * add test --- CHANGELOG.md | 2 +- src/SolverAPI.jl | 32 ++++++++++++++++++++++---------- test/all_tests.jl | 29 +++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef6345..23fd2dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make `options` field optional [#22](https://github.com/RelationalAI/SolverAPI.jl/pull/22) - Remove `names` field in results [#20](https://github.com/RelationalAI/SolverAPI.jl/pull/20) - Update format to use JSON vector for relational appl constraint - args [#21](https://github.com/RelationalAI/SolverAPI.jl/pull/21) + args [#21](https://github.com/RelationalAI/SolverAPI.jl/pull/21) ## [0.2.2] diff --git a/src/SolverAPI.jl b/src/SolverAPI.jl index 517d580..f7a521f 100644 --- a/src/SolverAPI.jl +++ b/src/SolverAPI.jl @@ -177,7 +177,13 @@ deserialize(body::String) = deserialize(Vector{UInt8}(body)) # Internal version of `solve`. We do this to make handling keyword # arguments easier. -function _solve(fn, json::Request, solver::MOI.AbstractOptimizer; kw...) +function _solve( + fn, + json::Request, + solver::MOI.AbstractOptimizer, + params::Dict{Symbol,Any}; + kw..., +) errors = validate(json) isempty(errors) || return response(; errors) @@ -191,9 +197,10 @@ function _solve(fn, json::Request, solver::MOI.AbstractOptimizer; kw...) try options = if haskey(json, :options) - json.options + # Copy the JSON to make it into a `Dict{Symbol,Any}`. + merge(copy(json.options), params) else - JSON3.Object() + params end set_options!(model, options) @@ -228,8 +235,8 @@ function _solve(fn, json::Request, solver::MOI.AbstractOptimizer; kw...) MOI.empty!(model) end end -_solve(fn, request::Dict, solver::MOI.AbstractOptimizer; kw...) = - _solve(fn, JSON3.read(JSON3.write(request)), solver; kw...) +_solve(fn, request::Dict, solver::MOI.AbstractOptimizer, params::Dict{Symbol,Any}; kw...) = + _solve(fn, JSON3.read(JSON3.write(request)), solver, params; kw...) """ solve([fn], request::Request, solver::MOI.AbstractOptimizer; )::Response @@ -244,6 +251,9 @@ gracefully and included in `Response`. the `MOI.ModelLike` model and should return `Nothing`. - `request`: A `SolverAPI.Request` specifying the optimization problem. - `solver`: A `MOI.AbstractOptimizer` to use to solve the problem. + - `params = Dict{Symbol,Any}()`: Parameters to pass to the + solver. This can be used with/instead of the `options` field in + `request`. ## Keyword Args @@ -251,15 +261,17 @@ gracefully and included in `Response`. - `numerical_type=Float64`: The numerical type to use for the solver. """ function solve( - fn, + fn::Function, request, - solver; + solver, + params = Dict{Symbol,Any}(); use_indicator::Bool = true, numerical_type::Type{<:Real} = Float64, ) - return _solve(fn, request, solver; use_indicator, numerical_type) + return _solve(fn, request, solver, params; use_indicator, numerical_type) end -solve(request, solver; kw...) = solve(model -> nothing, request, solver; kw...) +solve(request, solver, params = Dict{Symbol,Any}(); kw...) = + solve(model -> nothing, request, solver, params; kw...) """ print_model(request::Request; )::String @@ -414,7 +426,7 @@ function validate(json::Request)#::Vector{Error} end # Set solver options. -function set_options!(model::MOI.ModelLike, options::JSON3.Object)#::Nothing +function set_options!(model::MOI.ModelLike, options::Dict{Symbol,Any})#::Nothing if MOI.supports(model, MOI.TimeLimitSec()) # Set time limit, defaulting to 5min. MOI.set(model, MOI.TimeLimitSec(), Float64(get(options, :time_limit_sec, 300.0))) diff --git a/test/all_tests.jl b/test/all_tests.jl index c92d83d..056427e 100644 --- a/test/all_tests.jl +++ b/test/all_tests.jl @@ -92,6 +92,35 @@ end end end +@testitem "solve - params" setup = [SolverSetup] begin + import JSON3 + import HiGHS + + using SolverAPI + + tiny_min = JSON3.read( + JSON3.write( + Dict( + "version" => "0.1", + "sense" => "min", + "variables" => ["x"], + "constraints" => [["==", "x", 1], ["Int", "x"]], + "objectives" => ["x"], + ), + ), + ) + + # We can pass options to the solver via `params` as well. The + # `solver` key will be ignored. + result = solve( + tiny_min, + HiGHS.Optimizer(), + Dict{Symbol,Any}(:time_limit_sec => 0, :presolve => "off", :solver => "highs"), + ) + + @test result["termination_status"] == "TIME_LIMIT" +end + @testitem "errors" setup = [SolverSetup] begin using SolverAPI import JSON3