Skip to content

Commit

Permalink
Add MOI wrapper (#157)
Browse files Browse the repository at this point in the history
* Add MOI wrapper

* Remove NLPModelsTest

Co-authored-by: tmigot <tangi.migot@gmail.com>

* Update src/MOI_wrapper.jl

Co-authored-by: tmigot <tangi.migot@gmail.com>

* Update src/MOI_wrapper.jl

Co-authored-by: tmigot <tangi.migot@gmail.com>

* Refactor stats

* Update src/MOI_wrapper.jl

Co-authored-by: tmigot <tangi.migot@gmail.com>

---------

Co-authored-by: tmigot <tangi.migot@gmail.com>
  • Loading branch information
blegat and tmigot committed Jul 28, 2023
1 parent a327c1a commit be53ac6
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 33 deletions.
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
JuMP = "^1.2"
MathOptInterface = "^1.7"
NLPModels = "0.18, 0.19, 0.20"
SolverCore = "0.3"
julia = "^1.6"

[extras]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856"
Percival = "01435c0c-c90d-11e9-3788-63660f8fbccc"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["LinearAlgebra", "NLPModelsTest", "Test"]
test = ["LinearAlgebra", "NLPModelsTest", "Percival", "Test"]
188 changes: 188 additions & 0 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import SolverCore

mutable struct Optimizer <: MOI.AbstractOptimizer
options::Dict{String,Any}
silent::Bool
solver
nlp::Union{Nothing,MathOptNLPModel}
stats::SolverCore.GenericExecutionStats{Float64,Vector{Float64},Vector{Float64},Any}
function Optimizer()
return new(
Dict{String,Any}(),
false,
nothing,
nothing,
SolverCore.GenericExecutionStats{Float64,Vector{Float64},Vector{Float64},Any}(),
)
end
end

# FIXME return the name of the underlying NLPModel solver
MOI.get(::Optimizer, ::MOI.SolverName) = "NLPModels"

MOI.is_empty(optimizer::Optimizer) = isnothing(optimizer.solver) && isnothing(optimizer.nlp)

function MOI.empty!(optimizer::Optimizer)
optimizer.solver = nothing
optimizer.nlp = nothing
reset!(stats)
return
end

###
### MOI.RawOptimizerAttribute
###

function MOI.set(optimizer::Optimizer, param::MOI.RawOptimizerAttribute, value)
return optimizer.options[param.name] = value
end

function MOI.get(optimizer::Optimizer, param::MOI.RawOptimizerAttribute)
return optimizer.options[param.name]
end

###
### MOI.Silent
###

MOI.supports(::Optimizer, ::MOI.Silent) = true

function MOI.set(optimizer::Optimizer, ::MOI.Silent, value::Bool)
optimizer.silent = value
return
end

MOI.get(optimizer::Optimizer, ::MOI.Silent) = optimizer.silent

###
### MOI.AbstractModelAttribute
###

function MOI.supports(
::Optimizer,
::Union{
MOI.ObjectiveSense,
MOI.ObjectiveFunction{<:OBJ},
MOI.NLPBlock,
},
)
return true
end

###
### MOI.AbstractVariableAttribute
###

function MOI.supports(
::Optimizer,
::MOI.VariablePrimalStart,
::Type{MOI.VariableIndex},
)
return true
end

###
### `supports_constraint`
###

MOI.supports_constraint(::Optimizer, ::Type{VI}, ::Type{<:ALS}) = true
MOI.supports_constraint(::Optimizer, ::Type{SAF}, ::Type{<:ALS}) = true
MOI.supports_constraint(::Optimizer, ::Type{VAF}, ::Type{<:VLS}) = true

function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
if !haskey(dest.options, "solver")
error("No solver specified, use for instance `using Percival; JuMP.set_attribute(model, \"solver\", PercivalSolver)`")
end
dest.nlp, index_map = nlp_model(src)
dest.solver = dest.options["solver"](dest.nlp)
return index_map
end

function MOI.optimize!(model::Optimizer)
options = Dict{Symbol,Any}(
Symbol(key) => model.options[key]
for key in keys(model.options) if key != "solver"
)
if model.silent
options[:verbose] = 0
else
options[:verbose] = 1
end
SolverCore.solve!(model.solver, model.nlp, model.stats; options...)
return
end

function MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec)
return optimizer.stats.elapsed_time
end

function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString)
return SolverCore.STATUSES[optimizer.stats.status]
end

struct RawStatus <: MOI.AbstractModelAttribute
name::Symbol
end

MOI.is_set_by_optimize(::RawStatus) = true

function MOI.get(optimizer::Optimizer, attr::RawStatus)
return getfield(optimizer.stats, attr.name)
end

const TERMINATION_STATUS = Dict(
:exception => MOI.INTERRUPTED,
:first_order => MOI.LOCALLY_SOLVED,
:acceptable => MOI.ALMOST_LOCALLY_SOLVED,
:infeasible => MOI.LOCALLY_INFEASIBLE,
:max_eval => MOI.OTHER_LIMIT,
:max_iter => MOI.ITERATION_LIMIT,
:max_time => MOI.TIME_LIMIT,
:neg_pred => MOI.NUMERICAL_ERROR,
:not_desc => MOI.NUMERICAL_ERROR,
:small_residual => MOI.NUMERICAL_ERROR,
:small_step => MOI.SLOW_PROGRESS,
:stalled => MOI.SLOW_PROGRESS,
:unbounded => MOI.NORM_LIMIT,
:unknown => MOI.OPTIMIZE_NOT_CALLED,
:user => MOI.INTERRUPTED,
)

function MOI.get(optimizer::Optimizer, ::MOI.TerminationStatus)
if isnothing(optimizer.stats)
return MOI.OPTIMIZE_NOT_CALLED
end
return TERMINATION_STATUS[optimizer.stats.status]
end

function MOI.get(optimizer::Optimizer, attr::MOI.ObjectiveValue)
MOI.check_result_index_bounds(optimizer, attr)
return optimizer.stats.objective
end

function MOI.get(optimizer::Optimizer, attr::MOI.PrimalStatus)
if attr.result_index > MOI.get(optimizer, MOI.ResultCount())
return MOI.NO_SOLUTION
elseif MOI.get(optimizer, MOI.TerminationStatus()) == MOI.LOCALLY_SOLVED
return MOI.FEASIBLE_POINT
else
# TODO
return MOI.UNKNOWN_RESULT_STATUS
end
end

function MOI.get(::Optimizer, ::MOI.DualStatus)
# TODO
return MOI.NO_SOLUTION
end

function MOI.get(
optimizer::Optimizer,
attr::MOI.VariablePrimal,
vi::MOI.VariableIndex,
)
MOI.check_result_index_bounds(optimizer, attr)
return optimizer.stats.solution[vi.value]
end

MOI.get(optimizer::Optimizer, ::MOI.ResultCount) = 1
1 change: 1 addition & 0 deletions src/NLPModelsJuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module NLPModelsJuMP
include("utils.jl")
include("moi_nlp_model.jl")
include("moi_nls_model.jl")
include("MOI_wrapper.jl")

end
16 changes: 10 additions & 6 deletions src/moi_nlp_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@ function MathOptNLPModel(jmodel::JuMP.Model; kws...)
return MathOptNLPModel(backend(jmodel); kws...)
end

function MathOptNLPModel(moimodel::MOI.ModelLike; hessian::Bool = true, name::String = "Generic")
nvar, lvar, uvar, x0 = parser_variables(moimodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel)
function MathOptNLPModel(moimodel::MOI.ModelLike; kws...)
return nlp_model(moimodel; kws...)[1]
end

function nlp_model(moimodel::MOI.ModelLike; hessian::Bool = true, name::String = "Generic")
index_map, nvar, lvar, uvar, x0 = parser_variables(moimodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel, index_map)

nlp_data = MOI.get(moimodel, MOI.NLPBlock())
nlp_data = _nlp_block(moimodel)
nnln, nlcon, nl_lcon, nl_ucon = parser_NL(nlp_data, hessian = hessian)

if nlp_data.has_objective
obj = Objective("NONLINEAR", 0.0, spzeros(Float64, nvar), COO(), 0)
else
obj = parser_objective_MOI(moimodel, nvar)
obj = parser_objective_MOI(moimodel, nvar, index_map)
end

ncon = nlin + nnln
Expand Down Expand Up @@ -59,7 +63,7 @@ function MathOptNLPModel(moimodel::MOI.ModelLike; hessian::Bool = true, name::St
name = name,
)

return MathOptNLPModel(meta, nlp_data.evaluator, lincon, nlcon, obj, Counters())
return MathOptNLPModel(meta, nlp_data.evaluator, lincon, nlcon, obj, Counters()), index_map
end

function NLPModels.obj(nlp::MathOptNLPModel, x::AbstractVector)
Expand Down
8 changes: 4 additions & 4 deletions src/moi_nls_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ Construct a `MathOptNLSModel` from a `JuMP` model and a container of JuMP
"""
function MathOptNLSModel(cmodel::JuMP.Model, F; hessian::Bool = true, name::String = "Generic")
moimodel = backend(cmodel)
nvar, lvar, uvar, x0 = parser_variables(moimodel)
index_map, nvar, lvar, uvar, x0 = parser_variables(moimodel)

lls, linequ, nlinequ = parser_linear_expression(cmodel, nvar, F)
lls, linequ, nlinequ = parser_linear_expression(cmodel, nvar, index_map, F)
Feval, nlequ, nnlnequ = parser_nonlinear_expression(cmodel, nvar, F, hessian = hessian)

_nlp_sync!(cmodel)
moimodel = backend(cmodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel, index_map)

nlp_data = MOI.get(moimodel, MOI.NLPBlock())
nlp_data = _nlp_block(moimodel)
nnln, nlcon, nl_lcon, nl_ucon = parser_NL(nlp_data, hessian = hessian)

nequ = nlinequ + nnlnequ
Expand Down
Loading

0 comments on commit be53ac6

Please sign in to comment.