Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3336 - Cache default LP model #3340

Merged
merged 4 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 48 additions & 13 deletions src/Initialization/init_JuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,60 @@ function has_lp_infeasibility_ray(model)
return primal_status(model) == JuMP.INFEASIBILITY_CERTIFICATE
end

# solve a linear program (in the old MathProgBase interface)
function linprog(c, A, sense::Char, b, l::Number, u::Number, solver)
# solve a linear program (in the MathOptInterface)
function linprog(c, A, sense, b, l, u, solver_or_model)
schillic marked this conversation as resolved.
Show resolved Hide resolved
n = length(c)
m = length(b)
return linprog(c, A, fill(sense, m), b, fill(l, n), fill(u, n), solver)
end
model = linprog_model(solver_or_model)

x = lin_prog_variable!(model, n, l, u)
lin_prog_constraints!(model, A, sense, b, x)

function linprog(c, A, sense, b, l, u, solver)
n = length(c)
model = Model(solver)
@variable(model, l[i] <= x[i=1:n] <= u[i])
@objective(model, Min, c' * x)
eq_rows, ge_rows, le_rows = sense .== '=', sense .== '>', sense .== '<'
@constraint(model, A[eq_rows, :] * x .== b[eq_rows])
@constraint(model, A[ge_rows, :] * x .>= b[ge_rows])
@constraint(model, A[le_rows, :] * x .<= b[le_rows])

optimize!(model)
return (status = termination_status(model),
objval = objective_value(model),
sol = value.(x),
model = model)
end

@inline function linprog_model(model::JuMP.Model)
Base.empty!(model)
return model
end
@inline linprog_model(solver) = Model(solver)

function lin_prog_variable!(model, n, l::Number, u::Number)
if isfinite(l) && isfinite(u)
return @variable(model, x[1:n], lower_bound=l, upper_bound=u)
elseif isfinite(l)
return @variable(model, x[1:n], lower_bound=l)
elseif isfinite(u)
return @variable(model, x[1:n], upper_bound=u)
else
return @variable(model, x[1:n])
end
end

function lin_prog_variable!(model, n, l::Vector{T}, u::Vector{T}) where {T}
return @variable(model, l[i] <= x[i=1:n] <= u[i])
end

function lin_prog_constraints!(model, A, sense::Char, b, x)
if sense == '='
@constraint(model, A * x == b)
elseif sense == '>'
@constraint(model, A * x >= b)
elseif sense == '<'
@constraint(model, A * x <= b)
else
throw("Invalid sense ($sense) for constraints in lin_prog.")
end
end

function lin_prog_constraints!(model, A, sense, b, x)
eq_rows, ge_rows, le_rows = sense .== '=', sense .== '>', sense .== '<'
@constraint(model, view(A, eq_rows, :) * x == view(b, eq_rows))
@constraint(model, view(A, ge_rows, :) * x >= view(b, ge_rows))
@constraint(model, view(A, le_rows, :) * x <= view(b, le_rows))
end
38 changes: 33 additions & 5 deletions src/Utils/lp_solvers.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
const THREAD_FLOAT_LP_SOLVERs = JuMP.Model[]
const THREAD_EXACT_LP_SOLVERs = JuMP.Model[]

@inline default_lp_solver(::Type{T}) where {T} = default_lp_solver(T, Threads.threadid())
@noinline function default_lp_solver(::Type{T}, tid::Int) where {T}
THREAD_LP_SOLVERs = thread_specific_lp_solvers(T)

0 < tid <= length(THREAD_LP_SOLVERs) || _rng_length_assert()
if @inbounds isassigned(THREAD_LP_SOLVERs, tid)
@inbounds LP = THREAD_LP_SOLVERs[tid]
else
LP = Model(default_lp_solver_factory(T))
@inbounds THREAD_LP_SOLVERs[tid] = LP
end
return LP
end
@noinline _rng_length_assert() = @assert false "0 < tid <= length(THREAD_RNGs)"

function init_lp_solvers()
# Code is heavily inspired by the global state of RNGs in Random.
# That code contains the following comment, although it is unclear
# what this pertains to (most likely empty!).
# "it ensures that we didn't save a bad object"
resize!(empty!(THREAD_FLOAT_LP_SOLVERs), Threads.nthreads())
resize!(empty!(THREAD_EXACT_LP_SOLVERs), Threads.nthreads())
end

# default LP solver for floating-point numbers
function default_lp_solver(::Type{<:AbstractFloat})
@inline function default_lp_solver_factory(::Type{<:AbstractFloat})
return JuMP.optimizer_with_attributes(() -> GLPK.Optimizer(; method=GLPK.SIMPLEX))
end

# default LP solver for rational numbers
function default_lp_solver(::Type{<:Rational})
@inline function default_lp_solver_factory(::Type{<:Rational})
return JuMP.optimizer_with_attributes(() -> GLPK.Optimizer(; method=GLPK.EXACT))
end

@inline thread_specific_lp_solvers(::Type{<:AbstractFloat}) = THREAD_FLOAT_LP_SOLVERs
@inline thread_specific_lp_solvers(::Type{<:Rational}) = THREAD_EXACT_LP_SOLVERs

# default LP solver given two possibly different numeric types
function default_lp_solver(M::Type{<:Number}, N::Type{<:Number})
return default_lp_solver(promote_type(M, N))
end
@inline default_lp_solver(M::Type{<:Number}, N::Type{<:Number}) = default_lp_solver(promote_type(M, N))

# check for Polyhedra backend (fallback method)
function _is_polyhedra_backend(backend)
Expand Down
2 changes: 2 additions & 0 deletions src/init.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ function __init__()
@require RangeEnclosures = "1b4d18b6-9e5d-11e9-236c-f792b01831f8" include("Initialization/init_RangeEnclosures.jl")
@require Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" include("Initialization/init_Symbolics.jl")
@require WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192" include("Initialization/init_WriteVTK.jl")

init_lp_solvers()
end

# there is a conflict with `Revise` and `StaticArrays(Core)`, so it is loaded without `Requires`
Expand Down
3 changes: 2 additions & 1 deletion test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expokit = "a1e7a1ef-7a5d-5822-a38c-be74e1bb89f4"
ExponentialUtilities = "d4d017d3-3776-5f7e-afef-a10c40355c18"
GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6"
GR = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71"
IntervalArithmetic = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253"
# IntervalConstraintProgramming = "138f1668-1576-5ad7-91b9-7425abbf3153"
IntervalMatrices = "5c1f47dc-42dd-5697-8aaa-4d102d140ba9"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Javis = "78b212ba-a7f9-42d4-b726-60726080707e"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
MiniQhull = "978d7f02-9e05-4691-894f-ae31a51d76ca"
Expand All @@ -36,7 +38,6 @@ Expokit = "0.2"
ExponentialUtilities = "1"
GR = "0"
IntervalArithmetic = "0.15 - 0.20"
# IntervalConstraintProgramming = "0.9 - 0.12"
IntervalMatrices = "0.8"
Ipopt = "1"
Javis = "0.5 - 0.9"
Expand Down
47 changes: 47 additions & 0 deletions test/Utils/lp_solvers.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Check that the default model and explicit solver
# specification works for linprog

for N in [Float64]
p = HPolyhedron{N}()
c1 = LinearConstraint(N[2, 2], N(12))
c2 = LinearConstraint(N[-3, 3], N(6))
c3 = LinearConstraint(N[-1, -1], N(0))
c4 = LinearConstraint(N[2, -4], N(0))
addconstraint!(p, c3)
addconstraint!(p, c1)
addconstraint!(p, c4)
addconstraint!(p, c2)

P = HPolyhedron([HalfSpace(N[3 // 50, -4 // 10], N(1)),
HalfSpace(N[-1 // 50, 1 // 10], N(-1))])

# With default model
# support vector
d = N[1, 0]
@test σ(d, p) == N[4, 2]
d = N[0, 1]
@test σ(d, p) == N[2, 4]
d = N[-1, 0]
@test σ(d, p) == N[-1, 1]
d = N[0, -1]
@test σ(d, p) == N[0, 0]

# an_element
@test an_element(P) ∈ P

# With explicit solver
solver = optimizer_with_attributes(() -> GLPK.Optimizer(; method=GLPK.SIMPLEX))

# support vector
d = N[1, 0]
@test σ(d, p; solver=solver) == N[4, 2]
d = N[0, 1]
@test σ(d, p; solver=solver) == N[2, 4]
d = N[-1, 0]
@test σ(d, p; solver=solver) == N[-1, 1]
d = N[0, -1]
@test σ(d, p; solver=solver) == N[0, 0]

# an_element
@test an_element(P; solver=solver) ∈ P
end
10 changes: 9 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using LazySets, LazySets.Approximations, Test, LinearAlgebra, SparseArrays,
StaticArrays
StaticArrays, GLPK
using JuMP: optimizer_with_attributes

# fix random number generator seed
using Random
Expand Down Expand Up @@ -287,6 +288,13 @@ if test_suite_basic
include("Approximations/hausdorff_distance.jl")
end

# ================================
# Testing shared utility functions
# ================================
@time @testset "LazySets.lp_solvers" begin
include("Utils/lp_solvers.jl")
end

# ========================
# Testing method ambiguity
# ========================
Expand Down