From 4c430f6bea371a799f1759923cf313001971deea Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 9 Nov 2021 15:45:46 +1300 Subject: [PATCH 1/3] [FileFormats] fix copy_to for FileFormats.NL --- src/FileFormats/NL/NL.jl | 66 ++++++++++++++++++++++----------------- test/FileFormats/NL/NL.jl | 9 ++++++ 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/src/FileFormats/NL/NL.jl b/src/FileFormats/NL/NL.jl index 8cc160983d..9496a891e6 100644 --- a/src/FileFormats/NL/NL.jl +++ b/src/FileFormats/NL/NL.jl @@ -241,30 +241,33 @@ MOI.features_available(::_LinearNLPEvaluator) = [:ExprGraph] MOI.initialize(::_LinearNLPEvaluator, ::Vector{Symbol}) = nothing function MOI.copy_to(dest::Model, model::MOI.ModelLike) + if !MOI.is_empty(dest) + MOI.empty!(dest) + end mapping = MOI.Utilities.IndexMap() - # Initialize the NLP block. - has_nlp = MOI.NLPBlock() in MOI.get(model, MOI.ListOfModelAttributesSet()) - nlp_block = if has_nlp - MOI.get(model, MOI.NLPBlock()) - else + nlp_block = MOI.NLPBlockData(MOI.NLPBoundsPair[], _LinearNLPEvaluator(), false) - end - if !(:ExprGraph in MOI.features_available(nlp_block.evaluator)) - error( - "Unable to use AmplNLWriter because the nonlinear evaluator " * - "does not supply expression graphs.", - ) + for attr in MOI.get(model, MOI.ListOfModelAttributesSet()) + if attr == MOI.NLPBlock() + nlp_block = MOI.get(model, MOI.NLPBlock()) + if !(:ExprGraph in MOI.features_available(nlp_block.evaluator)) + error( + "Unable to use AmplNLWriter because the nonlinear " * + "evaluator does not supply expression graphs.", + ) + end + elseif attr == MOI.ObjectiveSense() + dest.sense = MOI.get(model, MOI.ObjectiveSense()) + elseif attr isa MOI.ObjectiveFunction + dest.f = _NLExpr(MOI.get(model, attr)) + else + throw(MOI.UnsupportedAttribute(attr)) + end end MOI.initialize(nlp_block.evaluator, [:ExprGraph]) - # Objective function. - if nlp_block.has_objective + if nlp_block.has_objective # Nonlinear objective takes precedence. dest.f = _NLExpr(MOI.objective_expr(nlp_block.evaluator)) - else - F = MOI.get(model, MOI.ObjectiveFunctionType()) - obj = MOI.get(model, MOI.ObjectiveFunction{F}()) - dest.f = _NLExpr(obj) end - # Nonlinear constraints for (i, bound) in enumerate(nlp_block.constraint_bounds) push!( dest.g, @@ -272,25 +275,24 @@ function MOI.copy_to(dest::Model, model::MOI.ModelLike) ) end dest.nlpblock_dim = length(dest.g) - starts = MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) - for x in MOI.get(model, MOI.ListOfVariableIndices()) + x_src = MOI.get(model, MOI.ListOfVariableIndices()) + for x in x_src dest.x[x] = _VariableInfo() - if starts - start = MOI.get(model, MOI.VariablePrimalStart(), x) - MOI.set(dest, MOI.VariablePrimalStart(), x, start) - end mapping[x] = x end - dest.sense = MOI.get(model, MOI.ObjectiveSense()) + MOI.Utilities.pass_attributes(dest, model, mapping, x_src) resize!(dest.order, length(dest.x)) # Now deal with the normal MOI constraints. for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) + if !MOI.supports_constraint(dest, F, S) + throw(MOI.UnsupportedConstraint{F,S}()) + end _process_constraint(dest, model, F, S, mapping) end # Correct bounds of binary variables. Mainly because AMPL doesn't have the # concept of binary nonlinear variables, but it does have binary linear # variables! How annoying. - for (x, v) in dest.x + for (_, v) in dest.x if v.type == _BINARY v.lower = max(0.0, v.lower) v.upper = min(1.0, v.upper) @@ -438,7 +440,8 @@ function _process_constraint( ::Type{S}, mapping, ) where {F,S} - for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + ci_src = MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + for ci in ci_src f = MOI.get(model, MOI.ConstraintFunction(), ci) s = MOI.get(model, MOI.ConstraintSet(), ci) op, l, u = _set_to_bounds(s) @@ -461,6 +464,7 @@ function _process_constraint( mapping[ci] = MOI.ConstraintIndex{F,S}(length(dest.g)) end end + MOI.Utilities.pass_attributes(dest, model, mapping, ci_src) return end @@ -471,7 +475,8 @@ function _process_constraint( S::Type{<:_SCALAR_SETS}, mapping, ) - for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + ci_src = MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + for ci in ci_src mapping[ci] = ci f = MOI.get(model, MOI.ConstraintFunction(), ci) s = MOI.get(model, MOI.ConstraintSet(), ci) @@ -483,6 +488,7 @@ function _process_constraint( dest.x[f].upper = u end end + MOI.Utilities.pass_attributes(dest, model, mapping, ci_src) return end @@ -493,11 +499,13 @@ function _process_constraint( S::Type{<:Union{MOI.ZeroOne,MOI.Integer}}, mapping, ) - for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + ci_src = MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) + for ci in ci_src mapping[ci] = ci f = MOI.get(model, MOI.ConstraintFunction(), ci) dest.x[f].type = S == MOI.ZeroOne ? _BINARY : _INTEGER end + MOI.Utilities.pass_attributes(dest, model, mapping, ci_src) return end diff --git a/test/FileFormats/NL/NL.jl b/test/FileFormats/NL/NL.jl index 7af51587e8..e4b24da4fa 100644 --- a/test/FileFormats/NL/NL.jl +++ b/test/FileFormats/NL/NL.jl @@ -1002,6 +1002,15 @@ function test_empty() @test MOI.is_empty(n) end +function test_moi() + MOI.Test.runtests( + NL.Model(), + MOI.Test.Config(exclude = Any[MOI.optimize!]), + include = ["test_model_copy_to_Unsupported"], + ) + return +end + function runtests() for name in names(@__MODULE__; all = true) if startswith("$(name)", "test_") From 7c820d8f3ce3505dce0f3051f13bb8635ba7b917 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 10 Nov 2021 09:11:16 +1300 Subject: [PATCH 2/3] Update coverage --- src/FileFormats/NL/NL.jl | 3 +-- test/FileFormats/NL/NL.jl | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/FileFormats/NL/NL.jl b/src/FileFormats/NL/NL.jl index 9496a891e6..687b4d42d1 100644 --- a/src/FileFormats/NL/NL.jl +++ b/src/FileFormats/NL/NL.jl @@ -221,7 +221,7 @@ function MOI.supports( end function MOI.set(model::Model, ::MOI.VariablePrimalStart, x, v::Real) - model.x[x].start = Float64(v) + model.x[x].start = convert(Float64, v)::Float64 return end @@ -237,7 +237,6 @@ end # ============================================================================== struct _LinearNLPEvaluator <: MOI.AbstractNLPEvaluator end -MOI.features_available(::_LinearNLPEvaluator) = [:ExprGraph] MOI.initialize(::_LinearNLPEvaluator, ::Vector{Symbol}) = nothing function MOI.copy_to(dest::Model, model::MOI.ModelLike) diff --git a/test/FileFormats/NL/NL.jl b/test/FileFormats/NL/NL.jl index e4b24da4fa..24b37e1ea2 100644 --- a/test/FileFormats/NL/NL.jl +++ b/test/FileFormats/NL/NL.jl @@ -464,7 +464,10 @@ function test_nlmodel_hs071_linear_obj() n = NL.Model() @test MOI.supports(n, MOI.VariablePrimalStart(), MOI.VariableIndex) @test MOI.supports(n, MOI.ObjectiveFunction{typeof(f)}()) - MOI.copy_to(n, model) + index_map = MOI.copy_to(n, model) + for (vi, starti) in zip(v, start) + @test MOI.get(n, MOI.VariablePrimalStart(), index_map[vi]) == starti + end @test n.sense == MOI.MAX_SENSE @test n.f == NL._NLExpr(f) _test_nlexpr( From 7b5429f8be37618cd198878fc25923c7a58ca6ec Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 10 Nov 2021 09:13:04 +1300 Subject: [PATCH 3/3] Simplify methods --- src/FileFormats/NL/NL.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/FileFormats/NL/NL.jl b/src/FileFormats/NL/NL.jl index 687b4d42d1..d361e3f75d 100644 --- a/src/FileFormats/NL/NL.jl +++ b/src/FileFormats/NL/NL.jl @@ -220,13 +220,13 @@ function MOI.supports( return true end -function MOI.set(model::Model, ::MOI.VariablePrimalStart, x, v::Real) - model.x[x].start = convert(Float64, v)::Float64 - return -end - -function MOI.set(model::Model, ::MOI.VariablePrimalStart, x, ::Nothing) - model.x[x].start = nothing +function MOI.set( + model::Model, + ::MOI.VariablePrimalStart, + x::MOI.VariableIndex, + v::Union{Nothing,Real}, +) + model.x[x].start = v === nothing ? nothing : convert(Float64, v)::Float64 return end