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

Use the Julia wrappers in CUTEst.jl -- Part I #333

Merged
merged 2 commits into from
Aug 17, 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
1 change: 0 additions & 1 deletion gen/wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ function main()
code = replace(code, "Ptr{ip_}" => "Ptr{Cint}")
code = replace(code, "Ptr{ipc_}" => "Ptr{Cint}")
write(path, code)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted it without the space ;)

format_file(path, YASStyle())

code = read(path, String)
Expand Down
327 changes: 5 additions & 322 deletions src/CUTEst.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,59 +13,17 @@ using Libdl
using NLPModels
import Libdl.dlsym

global cutest_lib_path = ""
global cutest_lib = C_NULL
include("libcutest.jl")

# Only one problem can be interfaced at any given time.
global cutest_instances = 0
global cutest_lib = C_NULL

export CUTEstModel, sifdecoder, set_mastsif

mutable struct CUTEstModel <: AbstractNLPModel{Float64, Vector{Float64}}
meta::NLPModelMeta{Float64, Vector{Float64}}
counters::Counters
hrows::Vector{Int32}
hcols::Vector{Int32}

jac_structure_reliable::Bool
jrows::Vector{Int32}
jcols::Vector{Int32}

lin_structure_reliable::Bool
blin::Vector{Float64}
clinrows::Vector{Int32}
clincols::Vector{Int32}
clinvals::Vector{Float64}

work::Vector{Float64}
Jval::Vector{Cdouble}
Jvar::Vector{Cint}
end
export CUTEstModel, sifdecoder, build_libsif, set_mastsif

const funit = convert(Int32, 42)
@static Sys.isapple() ? (const linker = "gfortran") : (const linker = "ld")
@static Sys.isapple() ? (const sh_flags = ["-dynamiclib", "-undefined", "dynamic_lookup"]) :
(const sh_flags = ["-shared"])

struct CUTEstException <: Exception
info::Int32
msg::String

function CUTEstException(info::Int32)
if info == 1
msg = "memory allocation error"
elseif info == 2
msg = "array bound error"
elseif info == 3
msg = "evaluation error"
else
msg = "unknown error"
end
return new(info, msg)
end
end

const cutest_problems_path = joinpath(dirname(@__FILE__), "..", "deps", "files")
isdir(cutest_problems_path) || mkpath(cutest_problems_path)

Expand All @@ -90,290 +48,15 @@ function __init__()
push!(Libdl.DL_LOAD_PATH, cutest_problems_path)
end

CUTEstException(info::Integer) = CUTEstException(convert(Int32, info))

macro cutest_error() # Handle nonzero exit codes.
esc(:(io_err[1] > 0 && throw(CUTEstException(io_err[1]))))
end

# to allow view inputs with stride one
StrideOneVector{T} =
Union{Vector{T}, SubArray{T, 1, Vector{T}, Tuple{UnitRange{U}}, true} where {U <: Integer}}
Union{Ref{T}, Vector{T}, SubArray{T, 1, Vector{T}, Tuple{UnitRange{U}}, true} where {U <: Integer}}

include("libcutest.jl")
include("model.jl")
include("sifdecoder.jl")
include("core_interface.jl")
include("julia_interface.jl")
include("classification.jl")

"""
set_mastsif(set::String="sifcollection")

Set the MASTSIF environment variable to point to a set of SIF problems.
The supported sets are:
- "sifcollection": the CUTEst NLP test set;
- "maros-meszaros": the Maros-Meszaros QP test set;
- "netlib-lp": the Netlib LP test set.

"""
function set_mastsif(set::String = "sifcollection")
if set == "sifcollection"
ENV["MASTSIF"] = joinpath(artifact"sifcollection", "optrove-sif-229e00b81891")
elseif set == "maros-meszaros"
ENV["MASTSIF"] = joinpath(artifact"maros-meszaros", "optrove-maros-meszaros-9adfb5707b1e")
elseif set == "netlib-lp"
ENV["MASTSIF"] = joinpath(artifact"netlib-lp", "optrove-netlib-lp-f83996fca937")
else
error("The set $set is not supported.")
end
@info "using full SIF collection located at" ENV["MASTSIF"]
return nothing
end

"""
nlp = CUTEstModel(name, args...; kwargs...)

Creates a CUTEst model following the NLPModels API.
This model needs to be finalized before a new one is created (e.g., calling `finalize(nlp)`).

## Optional arguments

Any extra arguments will be passed to `sifdecoder`.
You can, for instance, change parameters of the model:

```jldoctest
using CUTEst

nlp = CUTEstModel("CHAIN", "-param", "NH=50")
println(nlp.meta.nnzh)
finalize(nlp)
nlp = CUTEstModel("CHAIN", "-param", "NH=100")
println(nlp.meta.nnzh)
finalize(nlp)

# output

153
303
```

## Keyword arguments

- `decode::Bool = true`: Whether to call sifdecoder.
- `verbose::Bool = false`: Passed to sifdecoder.
- `efirst`::Bool = true`: Equalities first?
- `lfirst`::Bool = true`: Linear (or affine) constraints first?
- `lvfirst::Bool = true`: Nonlinear variables should appear first?
"""
function CUTEstModel(
name::AbstractString,
args...;
decode::Bool = true,
verbose::Bool = false,
efirst::Bool = true,
lfirst::Bool = true,
lvfirst::Bool = true,
)
sifname = (length(name) < 4 || name[(end - 3):end] != ".SIF") ? "$name.SIF" : name
if isfile(sifname)
# This file is local so make sure the full path is maintained for sifdecoder
path_sifname = joinpath(pwd(), sifname)
else
path_sifname = joinpath(ENV["MASTSIF"], sifname)
if !isfile(path_sifname)
error("$name not found")
end
end

pname, sif = basename(name) |> splitext
outsdif = "OUTSDIF_$pname.d"
global cutest_instances
cutest_instances > 0 && error("CUTEst: call finalize on current model first")
io_err = Cint[0]
global cutest_lib
cd(cutest_problems_path) do
if !decode
isfile(outsdif) || error("CUTEst: no decoded problem found")
libname = "lib$pname"
isfile("$libname.$(Libdl.dlext)") || error("CUTEst: lib not found; decode problem first")
cutest_lib = Libdl.dlopen(libname, Libdl.RTLD_NOW | Libdl.RTLD_DEEPBIND | Libdl.RTLD_GLOBAL)
else
sifdecoder(path_sifname, args..., verbose = verbose, outsdif = outsdif, precision = :double)
build_libsif(path_sifname, precision = :double)
end
ccall(
dlsym(cutest_lib, :fortran_open_),
Nothing,
(Ref{Int32}, Ptr{UInt8}, Ptr{Int32}),
[funit],
outsdif,
io_err,
)
@cutest_error
end

# Obtain problem size.
nvar = Cint[0]
ncon = Cint[0]

cdimen(io_err, [funit], nvar, ncon)
@cutest_error
nvar = nvar[1]
ncon = ncon[1]

x = Vector{Float64}(undef, nvar)
bl = Vector{Float64}(undef, nvar)
bu = Vector{Float64}(undef, nvar)
v = Vector{Float64}(undef, ncon)
cl = Vector{Float64}(undef, ncon)
cu = Vector{Float64}(undef, ncon)
equatn = Vector{Int32}(undef, ncon)
linear = Vector{Int32}(undef, ncon)

if ncon > 0
e_order = efirst ? Cint[1] : Cint[0]
l_order = lfirst ? Cint[1] : Cint[0]
v_order = lvfirst ? Cint[1] : Cint[0]
# Equality constraints first, linear constraints first, nonlinear variables first.
csetup(
io_err,
[funit],
Cint[0],
Cint[6],
[nvar],
[ncon],
x,
bl,
bu,
v,
cl,
cu,
equatn,
linear,
e_order,
l_order,
v_order,
)
else
usetup(io_err, [funit], Cint[0], Cint[6], [nvar], x, bl, bu)
end
@cutest_error

for lim in Any[bl, bu, cl, cu]
I = findall(abs.(lim) .>= 1e20)
lim[I] = Inf * lim[I]
end

lin = findall(linear .!= 0)
nlin = length(lin)

nnzh = Cint[0]
nnzj = Cint[0]

if ncon > 0
cdimsh(io_err, nnzh)
cdimsj(io_err, nnzj)
nnzj[1] -= nvar # nnzj also counts the nonzeros in the objective gradient.
else
udimsh(io_err, nnzh)
end
@cutest_error

nnzh = Int(nnzh[1])
nnzj = Int(nnzj[1])

ccall(dlsym(cutest_lib, :fortran_close_), Nothing, (Ref{Int32}, Ptr{Int32}), funit, io_err)
@cutest_error

ncon = Int(ncon)
nvar = Int(nvar)

lin_nnzj = min(nvar * nlin, nnzj)
nln_nnzj = min(nvar * (ncon - nlin), nnzj)

meta = NLPModelMeta(
nvar,
x0 = x,
lvar = bl,
uvar = bu,
ncon = ncon,
y0 = v,
lcon = cl,
ucon = cu,
nnzj = nnzj,
nnzh = nnzh,
lin = lin,
lin_nnzj = lin_nnzj,
nln_nnzj = nln_nnzj,
name = splitext(name)[1],
)

hrows = Vector{Int32}(undef, nnzh)
hcols = Vector{Int32}(undef, nnzh)

jac_structure_reliable = false
jrows = Vector{Int32}(undef, nnzj)
jcols = Vector{Int32}(undef, nnzj)
work = Vector{Int32}(undef, ncon)

lin_structure_reliable = false
blin = Vector{Float64}(undef, nlin)
clinrows = Vector{Int32}(undef, lin_nnzj)
clincols = Vector{Int32}(undef, lin_nnzj)
clinvals = Vector{Float64}(undef, lin_nnzj)

Jval = Array{Cdouble}(undef, nvar)
Jvar = Array{Cint}(undef, nvar)

nlp = CUTEstModel(
meta,
Counters(),
hrows,
hcols,
jac_structure_reliable,
jrows,
jcols,
lin_structure_reliable,
blin,
clinrows,
clincols,
clinvals,
work,
Jval,
Jvar,
)

cutest_instances += 1
finalizer(cutest_finalize, nlp)

return nlp
end

function cutest_finalize(nlp::CUTEstModel)
global cutest_instances
cutest_instances == 0 && return
global cutest_lib
io_err = Cint[0]
if nlp.meta.ncon > 0
cterminate(io_err)
else
uterminate(io_err)
end
@cutest_error
Libdl.dlclose(cutest_lib)
cutest_instances -= 1
cutest_lib = C_NULL
return
end

# Displaying CUTEstModel instances.

import Base.show, Base.print
function show(io::IO, nlp::CUTEstModel)
show(io, nlp.meta)
end

function print(io::IO, nlp::CUTEstModel)
print(io, nlp.meta)
end

end # module CUTEst.
Loading
Loading