Skip to content

Commit d20de10

Browse files
authored
[FileFormats.NL] read ScalarAffineFunction where possible (#2512)
1 parent c060af4 commit d20de10

File tree

2 files changed

+68
-10
lines changed

2 files changed

+68
-10
lines changed

src/FileFormats/NL/read.jl

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,12 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
208208
MOI.set(model, MOI.NLPBlock(), block)
209209
else
210210
if data.objective != :()
211-
obj = _to_scalar_nonlinear_function(data.objective)
211+
obj = _expr_to_function(data.objective)
212212
MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj)
213213
end
214214
for (i, expr) in enumerate(data.constraints)
215215
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
216-
f = _to_scalar_nonlinear_function(expr)::MOI.ScalarNonlinearFunction
216+
f = _expr_to_function(expr)
217217
if lb == ub
218218
MOI.add_constraint(model, f, MOI.EqualTo(lb))
219219
elseif -Inf == lb && ub < Inf
@@ -228,16 +228,44 @@ function _to_model(data::_CacheModel; use_nlp_block::Bool)
228228
return model
229229
end
230230

231-
_to_scalar_nonlinear_function(expr) = expr
231+
_expr_to_function(expr) = expr
232232

233-
function _to_scalar_nonlinear_function(expr::Expr)
233+
function _expr_to_function(expr::Expr)
234234
@assert Meta.isexpr(expr, :call)
235+
f = _try_scalar_affine_function(expr)
236+
if f !== nothing
237+
return convert(MOI.ScalarAffineFunction{Float64}, f)
238+
end
235239
return MOI.ScalarNonlinearFunction(
236240
expr.args[1],
237-
Any[_to_scalar_nonlinear_function(arg) for arg in expr.args[2:end]],
241+
Any[_expr_to_function(arg) for arg in expr.args[2:end]],
238242
)
239243
end
240244

245+
_try_scalar_affine_function(x::Float64) = x
246+
247+
_try_scalar_affine_function(x::MOI.VariableIndex) = x
248+
249+
function _try_scalar_affine_function(expr::Expr)
250+
if expr.args[1] == :+
251+
args = _try_scalar_affine_function.(expr.args[2:end])
252+
if !any(isnothing, args)
253+
return MOI.Utilities.operate(+, Float64, args...)
254+
end
255+
elseif expr.args[1] == :*
256+
args = _try_scalar_affine_function.(expr.args[2:end])
257+
n_affine_terms = 0
258+
for arg in args
259+
n_affine_terms += arg isa MOI.VariableIndex
260+
n_affine_terms += arg isa MOI.ScalarAffineFunction{Float64}
261+
end
262+
if n_affine_terms <= 1
263+
return MOI.Utilities.operate(*, Float64, args...)
264+
end
265+
end
266+
return nothing
267+
end
268+
241269
function _parse_header(io::IO, model::_CacheModel)
242270
# Line 1
243271
# We don't support the binary format.

test/FileFormats/NL/read.jl

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module TestNonlinearRead
88

99
using Test
1010
import MathOptInterface as MOI
11-
const NL = MOI.FileFormats.NL
11+
import MathOptInterface.FileFormats: NL
1212

1313
function runtests()
1414
for name in names(@__MODULE__; all = true)
@@ -720,10 +720,10 @@ function test_hs071_free_constraint_nlexpr()
720720
open(joinpath(@__DIR__, "data", "hs071_free_constraint.nl"), "r") do io
721721
return read!(io, model)
722722
end
723-
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) == [
724-
(MOI.ScalarNonlinearFunction, MOI.GreaterThan{Float64}),
725-
(MOI.ScalarNonlinearFunction, MOI.Interval{Float64}),
726-
]
723+
types = MOI.get(model, MOI.ListOfConstraintTypesPresent())
724+
@test length(types) == 2
725+
@test (MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) in types
726+
@test (MOI.ScalarNonlinearFunction, MOI.Interval{Float64}) in types
727727
for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
728728
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 1
729729
end
@@ -783,6 +783,36 @@ function test_mac_minlp()
783783
return
784784
end
785785

786+
function test_nl_read_scalar_affine_function()
787+
for obj_fn in (x -> x, x -> 1.0 * x, x -> 2.0 * x + 3.0)
788+
src = MOI.Utilities.Model{Float64}()
789+
x = MOI.add_variable(src)
790+
MOI.set(src, MOI.ObjectiveSense(), MOI.MIN_SENSE)
791+
f = obj_fn(x)
792+
MOI.set(src, MOI.ObjectiveFunction{typeof(f)}(), f)
793+
MOI.add_constraint(src, f, MOI.LessThan(1.0))
794+
dest = MOI.FileFormats.NL.Model()
795+
MOI.copy_to(dest, src)
796+
io = IOBuffer()
797+
write(io, dest)
798+
input = MOI.FileFormats.NL.Model(; use_nlp_block = false)
799+
seekstart(io)
800+
read!(io, input)
801+
model = MOI.Utilities.Model{Float64}()
802+
MOI.copy_to(model, input)
803+
y = only(MOI.get(model, MOI.ListOfVariableIndices()))
804+
g = MOI.Utilities.substitute_variables(
805+
_ -> y,
806+
convert(MOI.ScalarAffineFunction{Float64}, f),
807+
)
808+
obj = MOI.get(model, MOI.ObjectiveFunction{typeof(g)}())
809+
@test (obj, g)
810+
@test MOI.get(model, MOI.ListOfConstraintTypesPresent()) ==
811+
[(typeof(f), MOI.LessThan{Float64})]
812+
end
813+
return
814+
end
815+
786816
end
787817

788818
TestNonlinearRead.runtests()

0 commit comments

Comments
 (0)