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

Refactor and simplify src/utils.jl #238

Merged
merged 3 commits into from
Mar 19, 2024
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
2 changes: 0 additions & 2 deletions src/Xpress.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const libxprs = joinpath(
)

include("Lib/Lib.jl")

include("utils.jl")
include("helper.jl")
include("api.jl")
include("xprs_callbacks.jl")
Expand Down
90 changes: 85 additions & 5 deletions src/helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,91 @@ Base.cconvert(::Type{Ptr{Cvoid}}, prob::XpressProblem) = prob

Base.unsafe_convert(::Type{Ptr{Cvoid}}, prob::XpressProblem) = prob.ptr

function _invoke(f::Function, pos::Int, ::Type{Int}, args...)
out = Ref{Cint}(0)
args = collect(args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return out[]
end

function _invoke(f::Function, pos::Int, ::Type{Float64}, args...)
out = Ref{Float64}(0.0)
args = collect(args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return out[]
end

function _invoke(f::Function, pos::Int, ::Type{String}, args...)
buffer = Array{Cchar}(undef, 1024)
GC.@preserve buffer begin
out = Cstring(pointer(buffer))
args = collect(Any, args)
insert!(args, pos, out)
if (r = f(args...)) != 0
throw(XpressError(r, "Unable to invoke $f"))
end
return unsafe_string(out)
end
end

macro _invoke(expr)
@assert Meta.isexpr(expr, :(::)) "macro argument must have return type declaration"
f, return_type = expr.args
@assert Meta.isexpr(f, :call) "macro argument must have a function call"
invoke_expr = Expr(:call, _invoke, f.args[1], 0, return_type)
for i in 2:length(f.args)
if f.args[i] == :_
invoke_expr.args[3] = i - 1
else
push!(invoke_expr.args, esc(f.args[i]))
end
end
return invoke_expr
end

"""
@checked f(prob)
Lets you invoke a lower level `Lib` function and check that Xpress does not
error.
Use this macro to minimize repetition and increase readability.
The first argument must be a object that can be cast into an Xpress pointer,
e.g., `Ptr{XpressProblem}`.
This is passed to `get_xpress_error_message(xprs_ptr)` to get the error message.
## Example
```julia
@checked Lib.XPRSsetprobname(prob, name)
```
"""
macro checked(expr)
@assert expr.head == :call "Can only use @checked on function calls"
@assert (expr.args[1].head == :(.)) && (expr.args[1].args[1] == :Lib) "Can only use @checked on Lib.\$function"
@assert length(expr.args) >= 2 "Lib.\$function must be contain atleast one argument and the first argument must be of type XpressProblem"
prob = expr.args[2]
return quote
val = $(esc(expr))::Cint
if val != 0
e = get_xpress_error_message($(esc(prob)))
throw(XpressError(val, "Xpress internal error:\n\n$e.\n"))
end
end
end

function get_xpress_error_message(prob)
return lstrip(@_invoke(Lib.XPRSgetlasterror(prob, _)::String), ['?'])
end

function getattribute(prob::XpressProblem, name::String)
p_id, p_type = Ref{Cint}(), Ref{Cint}()
Lib.XPRSgetattribinfo(prob, name, p_id, p_type)
Expand Down Expand Up @@ -128,11 +213,6 @@ get_banner() = @_invoke Lib.XPRSgetbanner(_)::String

get_version() = VersionNumber(@_invoke Lib.XPRSgetversion(_)::String)

function get_xpress_error_message(prob::XpressProblem)
last_error = @_invoke Lib.XPRSgetlasterror(prob, _)::String
return lstrip(last_error, ['?'])
end

"""
show(io::IO, prob::XpressProblem)
Expand Down
135 changes: 0 additions & 135 deletions src/utils.jl

This file was deleted.

10 changes: 0 additions & 10 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,3 @@ println("Optimizer version: $(Xpress.get_version())")
@testset "$f" for f in filter!(f -> startswith(f, "test_"), readdir(@__DIR__))
include(f)
end

@testset "Xpress tests" begin
prob = Xpress.XpressProblem()
@test Xpress.getcontrol(prob, "HEURTHREADS") == 0
r = Xpress.Lib.XPRSreadprob(prob, "", "")
if Xpress.get_version() >= v"41.0.0"
msg = "Xpress internal error:\n\n85 Error: File not found: .\n"
@test_throws Xpress.XpressError(85, msg) Xpress._check(prob, r)
end
end
36 changes: 36 additions & 0 deletions test/test_helper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module TestHelper
using Xpress
using Test

import Xpress.Lib as Lib

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
Expand Down Expand Up @@ -83,6 +85,40 @@ function test_XpressProblem_show()
return
end

function test_checked()
if Xpress.get_version() < v"41.0.0"
return
end
prob = Xpress.XpressProblem()
msg = "Xpress internal error:\n\n85 Error: File not found: .\n"
@test_throws(
Xpress.XpressError(85, msg),
Xpress.@checked(Lib.XPRSreadprob(prob, "", "")),
)
return
end

function test_invoke_errors()
prob = C_NULL
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(
Lib.XPRSgetintattrib(prob, Lib.XPRS_ORIGINALCOLS, _)::Int,
),
)
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(
Lib.XPRSgetdblattrib(prob, Lib.XPRS_OBJSENSE, _)::Float64,
),
)
@test_throws(
Xpress.XpressError,
Xpress.@_invoke(Lib.XPRSgetlasterror(prob, _)::String)
)
return
end

end # TestHelper

TestHelper.runtests()
Loading