diff --git a/Project.toml b/Project.toml index 98547d5..182f72d 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,17 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PRIMA_jll = "eead6e0c-2d5b-5641-a95c-b722de96d551" TypeUtils = "c3b1956e-8857-4d84-9b79-890df85b1e67" +[weakdeps] +CUTEst = "1b53aba6-35b6-5f92-a507-53c67d53f819" +NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" + +[extensions] +PRIMACUTEstExt = "CUTEst" +PRIMANLPModelsExt = "NLPModels" + [compat] +CUTEst = "0.13" +NLPModels = "0.20" PRIMA_jll = "0.7.1" TypeUtils = "0.3" julia = "1.6" diff --git a/ext/PRIMACUTEstExt.jl b/ext/PRIMACUTEstExt.jl new file mode 100644 index 0000000..7848785 --- /dev/null +++ b/ext/PRIMACUTEstExt.jl @@ -0,0 +1,24 @@ +module PRIMACUTEstExt + +if isdefined(Base, :get_extension) + using NLPModels + using CUTEst + using PRIMA +else + using ..NLPModels + using ..CUTEst + using ..PRIMA +end + +for func in (:uobyqa, :newuoa, :bobyqa, :lincoa, :cobyla, :prima) + @eval function PRIMA.$(Symbol(func,"_CUTEst"))(name::AbstractString; kwds...) + nlp = CUTEstModel(name) + try + return $func(nlp; kwds...) + finally + finalize(nlp) + end + end +end + +end # module diff --git a/ext/PRIMANLPModelsExt.jl b/ext/PRIMANLPModelsExt.jl new file mode 100644 index 0000000..1563582 --- /dev/null +++ b/ext/PRIMANLPModelsExt.jl @@ -0,0 +1,82 @@ +module PRIMANLPModelsExt + +if isdefined(Base, :get_extension) + using NLPModels + using PRIMA +else + using ..NLPModels + using ..PRIMA +end + +# This structure is to wrap a non-linear problem model into a callable object. +struct ObjectiveFunction{F<:AbstractNLPModel} <: Function + nlp::F +end +(f::ObjectiveFunction)(x::AbstractVector) = obj(f.nlp, x) + +function check_variables(nlp::AbstractNLPModel, x0::AbstractVector) + length(x0) == get_nvar(nlp) || error( + "initial variables must have $(get_nvar(nlp)) elements") + return x0 +end + +const Variables = AbstractVector{<:Real} + +for func in (:uobyqa, :newuoa) + @eval function PRIMA.$func(nlp::AbstractNLPModel, x0::Variables = get_x0(nlp); kwds...) + has_bounds(nlp) && error("`$($func)` cannot solve problems with bound constraints") + has_equalities(nlp) && error("`$($func)` cannot solve problems with equality constraints") + has_inequalities(nlp) && error("`$($func)` cannot solve problems with inequality constraints") + return uobyqa(ObjectiveFunction(nlp), check_variables(nlp, x0); kwds...) + end +end + +function PRIMA.bobyqa(nlp::AbstractNLPModel, x0::Variables = get_x0(nlp); kwds...) + has_equalities(nlp) && error("`bobyqa` cannot solve problems with equality constraints") + has_inequalities(nlp) && error("`bobyqa` cannot solve problems with inequality constraints") + return bobyqa(ObjectiveFunction(nlp), check_variables(nlp, x0); kwds..., + xl = get_lvar(nlp), xu = get_uvar(nlp)) +end + +function PRIMA.lincoa(nlp::AbstractNLPModel, x0::Variables = get_x0(nlp); kwds...) + nlin = get_nlin(nlp) # number of linear constraints + nnln = get_nnln(nlp) # number of non-linear constraints + nlin == 0 || error("linear constraints not yet implemented for NLPModels in `lincoa`") + nnln == 0 || error("`lincoa` cannot solve problems with non-linear constraints") + return lincoa(ObjectiveFunction(nlp), check_variables(nlp, x0); kwds..., + xl = get_lvar(nlp), xu = get_uvar(nlp), + linear_eq = nothing, linear_ineq = nothing) +end + +function PRIMA.cobyla(nlp::AbstractNLPModel, x0::Variables = get_x0(nlp); kwds...) + nlin = get_nlin(nlp) # number of linear constraints + nnln = get_nnln(nlp) # number of non-linear constraints + nlin == 0 || error("linear constraints not yet implemented for NLPModels in `cobyla`") + nnln == 0 || error("non-linear constraints not yet implemented for NLPModels in `cobyla`") + return cobyla(ObjectiveFunction(nlp), check_variables(nlp, x0); kwds..., + xl = get_lvar(nlp), xu = get_uvar(nlp), + linear_eq = nothing, linear_ineq = nothing, + nonlinear_eq = nothing, nonlinear_ineq = nothing) +end + +function PRIMA.prima(nlp::AbstractNLPModel, x0::Variables = get_x0(nlp); kwds...) + nlin = get_nlin(nlp) # number of linear constraints + nnln = get_nnln(nlp) # number of non-linear constraints + if nnln > 0 + # Only COBYLA can deal with non-linear constraints. + error("solving problem with non-linear constraints by COBYLA not yet implemented") + return cobyla(nlp, x0; kwds...) + elseif nlin > 0 + # LINCOA can deal with bounds and linear constraints. + error("solving problem with linear constraints by LINCOA not yet implemented") + return lincoa(nlp, x0; kwds...) + elseif has_bounds(nlp) + # BOBYQA can deal with bounds. + return bobyqa(nlp, x0; kwds...) + else + # Use NEWUOA for unconstrained problems. + return newuoa(nlp, x0; kwds...) + end +end + +end # module diff --git a/src/PRIMA.jl b/src/PRIMA.jl index feae3ad..d1e4d1f 100644 --- a/src/PRIMA.jl +++ b/src/PRIMA.jl @@ -985,4 +985,21 @@ function _get_scaling(scl::AbstractVector{<:Real}, n::Int) return convert(Vector{Cdouble}, scl) end +for func in (:uobyqa, :newuoa, :bobyqa, :lincoa, :cobyla, :prima) + @eval $(Symbol(func,"_CUTEst"))(args...; kwds...) = + error("invalid arguments or `CUTEst` package not yet loaded") +end + +@static if !isdefined(Base, :get_extension) + using Requires + function __init__() + if !isdefined(Base, :get_extension) + @require CUTEst = "1b53aba6-35b6-5f92-a507-53c67d53f819" include( + "../ext/PRIMACUTEstExt.jl") + @require NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" include( + "../ext/PRIMANLPModelsExt.jl") + end + end +end + end # module