Skip to content

Commit

Permalink
[FileFormats.NL] read ScalarAffineFunction where possible (#2512)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow authored Jun 12, 2024
1 parent c060af4 commit d20de10
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 10 deletions.
38 changes: 33 additions & 5 deletions src/FileFormats/NL/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,12 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
MOI.set(model, MOI.NLPBlock(), block)
else
if data.objective != :()
obj = _to_scalar_nonlinear_function(data.objective)
obj = _expr_to_function(data.objective)
MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
end
for (i, expr) in enumerate(data.constraints)
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
f = _to_scalar_nonlinear_function(expr)::MOI.ScalarNonlinearFunction
f = _expr_to_function(expr)
if lb == ub
MOI.add_constraint(model, f, MOI.EqualTo(lb))
elseif -Inf == lb && ub < Inf
Expand All @@ -228,16 +228,44 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
return model
end

_to_scalar_nonlinear_function(expr) = expr
_expr_to_function(expr) = expr

function _to_scalar_nonlinear_function(expr::Expr)
function _expr_to_function(expr::Expr)
@assert Meta.isexpr(expr, :call)
f = _try_scalar_affine_function(expr)
if f !== nothing
return convert(MOI.ScalarAffineFunction{Float64}, f)
end
return MOI.ScalarNonlinearFunction(
expr.args[1],
Any[_to_scalar_nonlinear_function(arg) for arg in expr.args[2:end]],
Any[_expr_to_function(arg) for arg in expr.args[2:end]],
)
end

_try_scalar_affine_function(x::Float64) = x

_try_scalar_affine_function(x::MOI.VariableIndex) = x

function _try_scalar_affine_function(expr::Expr)
if expr.args[1] == :+
args = _try_scalar_affine_function.(expr.args[2:end])
if !any(isnothing, args)
return MOI.Utilities.operate(+, Float64, args...)
end
elseif expr.args[1] == :*
args = _try_scalar_affine_function.(expr.args[2:end])
n_affine_terms = 0
for arg in args
n_affine_terms += arg isa MOI.VariableIndex
n_affine_terms += arg isa MOI.ScalarAffineFunction{Float64}
end
if n_affine_terms <= 1
return MOI.Utilities.operate(*, Float64, args...)
end
end
return nothing
end

function _parse_header(io::IO, model::_CacheModel)
# Line 1
# We don't support the binary format.
Expand Down
40 changes: 35 additions & 5 deletions test/FileFormats/NL/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module TestNonlinearRead

using Test
import MathOptInterface as MOI
const NL = MOI.FileFormats.NL
import MathOptInterface.FileFormats: NL

function runtests()
for name in names(@__MODULE__; all = true)
Expand Down Expand Up @@ -720,10 +720,10 @@ function test_hs071_free_constraint_nlexpr()
open(joinpath(@__DIR__, "data", "hs071_free_constraint.nl"), "r") do io
return read!(io, model)
end
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) == [
(MOI.ScalarNonlinearFunction, MOI.GreaterThan{Float64}),
(MOI.ScalarNonlinearFunction, MOI.Interval{Float64}),
]
types = MOI.get(model, MOI.ListOfConstraintTypesPresent())
@test length(types) == 2
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in types
@test (MOI.ScalarNonlinearFunction, MOI.Interval{Float64}) in types
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 1
end
Expand Down Expand Up @@ -783,6 +783,36 @@ function test_mac_minlp()
return
end

function test_nl_read_scalar_affine_function()
for obj_fn in (x -> x, x -> 1.0 * x, x -> 2.0 * x + 3.0)
src = MOI.Utilities.Model{Float64}()
x = MOI.add_variable(src)
MOI.set(src, MOI.ObjectiveSense(), MOI.MIN_SENSE)
f = obj_fn(x)
MOI.set(src, MOI.ObjectiveFunction{typeof(f)}(), f)
MOI.add_constraint(src, f, MOI.LessThan(1.0))
dest = MOI.FileFormats.NL.Model()
MOI.copy_to(dest, src)
io = IOBuffer()
write(io, dest)
input = MOI.FileFormats.NL.Model(; use_nlp_block = false)
seekstart(io)
read!(io, input)
model = MOI.Utilities.Model{Float64}()
MOI.copy_to(model, input)
y = only(MOI.get(model, MOI.ListOfVariableIndices()))
g = MOI.Utilities.substitute_variables(
_ -> y,
convert(MOI.ScalarAffineFunction{Float64}, f),
)
obj = MOI.get(model, MOI.ObjectiveFunction{typeof(g)}())
@test (obj, g)
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) ==
[(typeof(f), MOI.LessThan{Float64})]
end
return
end

end

TestNonlinearRead.runtests()

0 comments on commit d20de10

Please sign in to comment.