Skip to content
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
93 changes: 88 additions & 5 deletions src/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ mutable struct Model <: MOI.ModelLike
MOI.Utilities.UniversalFallback{MOI.Utilities.Model{Float64}},
}
use_nlp_block::Bool
complementarity_constraints::Vector{Vector{Int}}

function Model(; use_nlp_block::Bool = true)
return new(
Expand All @@ -160,6 +161,7 @@ mutable struct Model <: MOI.ModelLike
MOI.VariableIndex[],
nothing,
use_nlp_block,
Vector{Int}[],
)
end
end
Expand All @@ -185,6 +187,7 @@ function MOI.empty!(model::Model)
end
empty!(model.order)
model.model = nothing
empty!(model.complementarity_constraints)
return
end

Expand Down Expand Up @@ -222,6 +225,21 @@ function MOI.supports_constraint(
return true
end

function MOI.supports_constraint(
::Model,
::Type{F},
::Type{MOI.Complements},
) where {
F<:Union{
MOI.VectorOfVariables,
MOI.VectorAffineFunction{Float64},
MOI.VectorQuadraticFunction{Float64},
MOI.VectorNonlinearFunction,
},
}
return true
end

MOI.supports(::Model, ::MOI.ObjectiveSense) = true
MOI.supports(::Model, ::MOI.ObjectiveFunction{<:_SCALAR_FUNCTIONS}) = true

Expand Down Expand Up @@ -493,6 +511,48 @@ function _process_constraint(
return
end

_to_x(f) = convert(MOI.VariableIndex, f)

function _to_x(f::MOI.ScalarNonlinearFunction)
# Hacky way to ensure that f is a standalone variable
@assert f isa MOI.ScalarNonlinearFunction
@assert f.head == :+ && length(f.args) == 1
@assert f.args[1] isa MOI.VariableIndex
return return f.args[1]
end

function _process_constraint(
dest::Model,
model,
::Type{F},
::Type{S},
mapping,
) where {F,S<:MOI.Complements}
ci_src = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
for ci in ci_src
f_vec = MOI.get(model, MOI.ConstraintFunction(), ci)
f_scalars = MOI.Utilities.scalarize(f_vec)
n = div(MOI.output_dimension(f_vec), 2)
rows = Int[]
for i in 1:n
fi, xi = f_scalars[i], _to_x(f_scalars[i+n])
con = _NLConstraint(Float64(xi.value), Inf, 5, _NLExpr(fi))
if con.expr.is_linear
push!(dest.h, con)
push!(rows, -length(dest.h))
else
push!(dest.g, con)
push!(rows, length(dest.g))
end
end
push!(dest.complementarity_constraints, rows)
mapping[ci] =
MOI.ConstraintIndex{F,S}(length(dest.complementarity_constraints))
end
MOI.Utilities.pass_attributes(dest, model, mapping, ci_src)
return
end

function _str(x::Float64)
if isinteger(x) && (typemin(Int) <= x <= typemax(Int))
return string(round(Int, x))
Expand Down Expand Up @@ -571,8 +631,19 @@ function Base.write(io::IO, model::Model)
# Line 3: nonlinear constraints, objectives
# Notes:
# * We assume there is always one objective, even if it is just `min 0`.
# * `Writing .nl Files` lies: there are four extra integers here
# * Number of linear complementarity constraints
# * Number of nonlinear complementarity constraints
# * nd: I have no idea
# * nzlb: I have no idea
n_nlcon = length(model.g)
println(io, " ", n_nlcon, " ", 1)
ccon_lin = sum(c.opcode == 5 for c in model.h; init = 0)
ccon_nl = sum(c.opcode == 5 for c in model.g; init = 0)
if ccon_lin + ccon_nl > 0
println(io, " ", n_nlcon, " 1 ", ccon_lin, " ", ccon_nl, " 0 0")
else
println(io, " ", n_nlcon, " ", 1)
end

# Line 4: network constraints: nonlinear, linear
# Notes:
Expand Down Expand Up @@ -694,9 +765,15 @@ function Base.write(io::IO, model::Model)
println(io, " ", _str(g.lower))
elseif g.opcode == 3
println(io)
else
@assert g.opcode == 4
elseif g.opcode == 4
println(io, " ", _str(g.lower))
else
@assert g.opcode == 5
@assert !isfinite(g.upper)
x = MOI.VariableIndex(g.lower)
v = model.x[x]
k = (-Inf < v.lower) + 2 * (v.upper < Inf)
println(io, " ", k, " ", v.order + 1)
end
end
# Linear constraints
Expand All @@ -710,9 +787,15 @@ function Base.write(io::IO, model::Model)
println(io, " ", _str(h.lower))
elseif h.opcode == 3
println(io)
else
@assert h.opcode == 4
elseif h.opcode == 4
println(io, " ", _str(h.lower))
else
@assert h.opcode == 5
@assert !isfinite(h.upper)
x = MOI.VariableIndex(h.lower)
v = model.x[x]
k = (-Inf < v.lower) + 2 * (v.upper < Inf)
println(io, " ", k, " ", v.order + 1)
end
end
end
Expand Down
22 changes: 20 additions & 2 deletions src/FileFormats/NL/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ mutable struct _CacheModel
constraint_upper::Vector{Float64}
objective::Expr
sense::MOI.OptimizationSense
complements_map::Dict{Int,Int}

function _CacheModel()
return new(
false,
Expand All @@ -29,6 +31,7 @@ mutable struct _CacheModel
Float64[],
:(),
MOI.FEASIBILITY_SENSE,
Dict{Int,Int}(),
)
end
end
Expand Down Expand Up @@ -208,6 +211,7 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
MOI.set(model, MOI.ObjectiveSense(), data.sense)
end
if use_nlp_block
@assert isempty(data.complements_map)
nlp = MOI.Nonlinear.Model()
if data.objective != :()
MOI.Nonlinear.set_objective(nlp, data.objective)
Expand All @@ -234,6 +238,16 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
end
for (i, expr) in enumerate(data.constraints)
if haskey(data.complements_map, i)
g = MOI.Utilities.operate(
vcat,
Float64,
_expr_to_function(expr),
x[data.complements_map[i]],
)
MOI.add_constraint(model, g, MOI.Complements(2))
continue
end
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
f = _expr_to_function(expr)
if lb == ub
Expand Down Expand Up @@ -551,11 +565,15 @@ function _parse_section(io::IO, ::Val{'r'}, model::_CacheModel)
model.constraint_lower[i] = _next(Float64, io, model)
elseif type == Cchar('3')
# Free constraint
else
@assert type == Cchar('4')
elseif type == Cchar('4')
value = _next(Float64, io, model)
model.constraint_lower[i] = value
model.constraint_upper[i] = value
else
@assert type == Cchar('5')
_ = _next(Int, io, model) # k
j = _next(Int, io, model) # variable i-1
push!(model.complements_map, i => j)
end
_read_til_newline(io, model)
end
Expand Down
138 changes: 138 additions & 0 deletions test/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,144 @@ function test_unsupported_objectives()
return
end

function test_write_complements_VectorOfVariables()
for (set, k, b) in (
(MOI.GreaterThan(0.0), 1, "2 0"),
(MOI.LessThan(1.0), 2, "1 1"),
(MOI.EqualTo(1.0), 3, "4 1"),
(MOI.Interval(0.0, 1.0), 3, "0 0 1"),
)
src = MOI.Utilities.Model{Float64}()
x = MOI.add_variable(src)
y, _ = MOI.add_constrained_variable(src, MOI.Interval(0.0, 1.0))
MOI.add_constraint(src, x, set)
MOI.add_constraint(
src,
MOI.Utilities.vectorize([y, x]),
MOI.Complements(2),
)
dest = NL.Model()
MOI.copy_to(dest, src)
@test sprint(write, dest) == """
g3 1 1 0
2 1 1 0 0 0
0 1 1 0 0 0
0 0
0 0 0
0 0 0 1
0 0 0 0 0
1 0
0 0
0 0 0 0 0
C0
n0
O0 0
n0
x2
0 0
1 0
r
5 $k 1
b
$b
0 0 1
k1
0
J0 1
1 1
"""
end
return
end

function test_write_complements_VectorAffineFunction()
for (set, k, b) in (
(MOI.GreaterThan(0.0), 1, "2 0"),
(MOI.LessThan(1.0), 2, "1 1"),
(MOI.EqualTo(1.0), 3, "4 1"),
(MOI.Interval(0.0, 1.0), 3, "0 0 1"),
)
src = MOI.Utilities.Model{Float64}()
x = MOI.add_variable(src)
MOI.add_constraint(src, x, set)
MOI.add_constraint(
src,
MOI.Utilities.vectorize([1.0 - x, x]),
MOI.Complements(2),
)
dest = NL.Model()
MOI.copy_to(dest, src)
@test sprint(write, dest) == """
g3 1 1 0
1 1 1 0 0 0
0 1 1 0 0 0
0 0
0 0 0
0 0 0 1
0 0 0 0 0
1 0
0 0
0 0 0 0 0
C0
n1
O0 0
n0
x1
0 0
r
5 $k 1
b
$b
k0
J0 1
0 -1
"""
end
return
end

function test_write_complements_VectorNonlinearFunction()
src = MOI.Utilities.Model{Float64}()
x, _ = MOI.add_constrained_variable(src, MOI.Interval(0.0, 1.0))
MOI.add_constraint(
src,
MOI.VectorNonlinearFunction([
MOI.ScalarNonlinearFunction(:sin, Any[x]),
MOI.ScalarNonlinearFunction(:+, Any[x]),
]),
MOI.Complements(2),
)
dest = NL.Model()
MOI.copy_to(dest, src)
@test sprint(write, dest) == """
g3 1 1 0
1 1 1 0 0 0
1 1 0 1 0 0
0 0
1 0 0
0 0 0 1
0 0 0 0 0
1 0
0 0
0 0 0 0 0
C0
o41
v0
O0 0
n0
x1
0 0
r
5 3 1
b
0 0 1
k0
J0 1
0 0
"""
return
end

end

TestNLModel.runtests()
Expand Down
Loading
Loading