Skip to content
Closed
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
80 changes: 80 additions & 0 deletions src/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,84 @@ function Base.write(io::IO, nlmodel::Model)
return nlmodel
end

# This part is needed for JuMP's write_to_file method.
#
# JuMP calls:
# ```julia
# dest = MOI.FileFormats.Model(format = format, filename = filename)
# bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64)
# MOI.copy_to(bridged_dest, model)
# MOI.write_to_file(dest, filename)
# ```
# So we need a way of calling `copy_to` from a model to a bridged NL.Model.
# However because we don't support the incremental interface, this would require
# a CachingOptimizer. Except we can't use a CachingOptimizer because NL.Model
# isn't an AbstractOptimizer!
#
# The solution, at least as a temporary measure, is to write a simplified
# _CachingModel that acts as a cache during copy_to.
#
# The other FileFormats don't have this problem because they use
# Utilities.@model, which supports the incremental interface.

struct _CachingModel{C} <: MOI.AbstractOptimizer
inner::MOI.FileFormats.NL.Model
cache::C
function _CachingModel(model::MOI.FileFormats.NL.Model)
cache = MOI.Bridges.full_bridge_optimizer(
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
Float64,
)
return new{typeof(cache)}(model, cache)
end
end

MOI.supports_incremental_interface(::_CachingModel) = true

MOI.is_empty(model::_CachingModel) = MOI.is_empty(model.cache)

MOI.empty!(model::_CachingModel) = MOI.empty!(model.cache)

function MOI.add_constraint(
model::_CachingModel,
f::MOI.AbstractFunction,
s::MOI.AbstractSet,
)
return MOI.add_constraint(model.cache, f, s)
end

MOI.add_variable(model::_CachingModel) = MOI.add_variable(model.cache)

function MOI.set(model::_CachingModel, attr::MOI.AnyAttribute, args...)
return MOI.set(model.cache, attr, args...)
end

function MOI.get(model::_CachingModel, attr::MOI.AnyAttribute, args...)
return MOI.get(model.cache, attr, args...)
end

function MOI.supports(model::_CachingModel, attr::MOI.AnyAttribute, args...)
return MOI.supports(model.inner, attr, args...)
end

function MOI.supports_constraint(
model::_CachingModel,
f::MOI.AbstractFunction,
s::MOI.AbstractSet,
)
return MOI.supports_constraint(model.inner, f, s)
end

function MOI.copy_to(
dest::MOI.Bridges.LazyBridgeOptimizer{MOI.FileFormats.NL.Model},
src::MOI.ModelLike,
)
model = _CachingModel(dest.model)
# This needs to be `default_copy_to` so that it uses the incremental
# interface.
index_map = MOI.Utilities.default_copy_to(model, src)
MOI.copy_to(model.inner, model.cache)
return index_map
end

end
150 changes: 150 additions & 0 deletions test/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,156 @@ function test_nlmodel_hs071()
return
end

function test_nlmodel_hs071_bridged()
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
v = MOI.add_variables(model, 4)
l = [1.1, 1.2, 1.3, 1.4]
u = [5.1, 5.2, 5.3, 5.4]
start = [2.1, 2.2, 2.3, 2.4]
MOI.add_constraint.(model, v, MOI.GreaterThan.(l))
MOI.add_constraint.(model, v, MOI.LessThan.(u))
MOI.set.(model, MOI.VariablePrimalStart(), v, start)
lb, ub = [25.0, 40.0], [Inf, 40.0]
evaluator = MOI.Test.HS071(true)
block_data = MOI.NLPBlockData(MOI.NLPBoundsPair.(lb, ub), evaluator, true)
MOI.set(model, MOI.NLPBlock(), block_data)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
n = NL.Model()
@test MOI.supports(n, MOI.NLPBlock())
@test MOI.supports(n, MOI.ObjectiveSense())
@test MOI.is_empty(n)
# Replicate what JuMP does in write_to_file!
bridged = MOI.Bridges.full_bridge_optimizer(n, Float64)
MOI.copy_to(bridged, model)
@test !MOI.is_empty(n)
@test n.sense == MOI.MIN_SENSE
@test n.f == NL._NLExpr(MOI.objective_expr(evaluator))
_test_nlexpr(
n.g[1].expr,
[NL.OPMULT, v[1], NL.OPMULT, v[2], NL.OPMULT, v[3], v[4]],
Dict(v .=> 0.0),
0.0,
)
@test n.g[1].lower == 25.0
@test n.g[1].upper == Inf
@test n.g[1].opcode == 2
_test_nlexpr(
n.g[2].expr,
[
NL.OPSUMLIST,
4,
NL.OPPOW,
v[1],
2,
NL.OPPOW,
v[2],
2,
NL.OPPOW,
v[3],
2,
NL.OPPOW,
v[4],
2,
],
Dict(v .=> 0.0),
0.0,
)
@test n.g[2].lower == 40.0
@test n.g[2].upper == 40.0
@test n.g[2].opcode == 4
@test length(n.h) == 0
for i in 1:4
@test n.x[v[i]].lower == l[i]
@test n.x[v[i]].upper == u[i]
@test n.x[v[i]].type == NL._CONTINUOUS
@test n.x[v[i]].jacobian_count == 2
@test n.x[v[i]].in_nonlinear_constraint
@test n.x[v[i]].in_nonlinear_objective
@test 0 <= n.x[v[i]].order <= 3
end
@test length(n.types[1]) == 4
@test sprint(write, n) == """
g3 1 1 0
4 2 1 0 1 0
2 1
0 0
4 4 4
0 0 0 1
0 0 0 0 0
8 4
0 0
0 0 0 0 0
C0
o2
v0
o2
v1
o2
v2
v3
C1
o54
4
o5
v0
n2
o5
v1
n2
o5
v2
n2
o5
v3
n2
O0 0
o0
o2
v0
o2
v3
o54
3
v0
v1
v2
v2
x4
0 2.1
1 2.2
2 2.3
3 2.4
r
2 25
4 40
b
0 1.1 5.1
0 1.2 5.2
0 1.3 5.3
0 1.4 5.4
k3
2
4
6
J0 4
0 0
1 0
2 0
3 0
J1 4
0 0
1 0
2 0
3 0
G0 4
0 0
1 0
2 0
3 0
"""
return
end

function test_nlmodel_hs071_linear_obj()
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
v = MOI.add_variables(model, 4)
Expand Down