Skip to content

Commit c18c8b2

Browse files
authored
Fix support for external sets in loadfromstring (#2177)
1 parent 936e475 commit c18c8b2

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

src/Utilities/parser.jl

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,36 @@ function _parsed_to_moi(model, f::_ParsedVariableIndex)
267267
return _parsed_to_moi(model, f.variable)
268268
end
269269

270+
_walk_expr(f::F, expr) where {F<:Function} = f(expr)
271+
272+
function _walk_expr(f::F, expr::Expr) where {F<:Function}
273+
for (i, arg) in enumerate(expr.args)
274+
expr.args[i] = _walk_expr(f, arg)
275+
end
276+
return expr
277+
end
278+
279+
function _parse_set(expr::Expr)
280+
expr = _walk_expr(expr) do arg
281+
if arg isa Symbol && arg in (:MOI, :MathOptInterface)
282+
return MOI
283+
end
284+
return arg
285+
end
286+
@assert Meta.isexpr(expr, :call)
287+
if expr.args[1] isa Symbol
288+
# If the set is a Symbol, it must be one of the MOI sets. We need to
289+
# eval this in the MOI module.
290+
return Core.eval(MOI, expr)
291+
elseif Meta.isexpr(expr.args[1], :curly) && expr.args[1].args[1] isa Symbol
292+
# Something like Indicator{}()
293+
return Core.eval(MOI, expr)
294+
end
295+
# If the set is an expression, it must be something like
296+
# `SCS.ScaledPSDCone()`. We need to eval this in `Main`.
297+
return Core.eval(Main, expr)
298+
end
299+
270300
# Ideally, this should be load_from_string
271301
"""
272302
loadfromstring!(model, s)
@@ -333,7 +363,7 @@ function loadfromstring!(model, s)
333363
elseif label == :constrainedvariable
334364
@assert length(ex.args) == 3
335365
@assert ex.args[1] == :in
336-
set = Core.eval(MOI, ex.args[3])
366+
set = _parse_set(ex.args[3])
337367
if isa(ex.args[2], Symbol)
338368
# constrainedvariable: x in LessThan(1.0)
339369
x, _ = MOI.add_constrained_variable(model, set)
@@ -362,7 +392,7 @@ function loadfromstring!(model, s)
362392
f = _parsed_to_moi(model, _parse_function(ex.args[2], T))
363393
if ex.args[1] == :in
364394
# Could be safer here
365-
set = Core.eval(MOI, ex.args[3])
395+
set = _parse_set(ex.args[3])
366396
elseif ex.args[1] == :<=
367397
set = MOI.LessThan(Core.eval(Base, ex.args[3]))
368398
elseif ex.args[1] == :>=

test/Utilities/parser.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,42 @@ function test_eltypes_complex_float64()
399399
return
400400
end
401401

402+
struct Set2175 <: MOI.AbstractScalarSet end
403+
404+
function test_parse_external_set_constraint()
405+
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
406+
MOI.Utilities.loadfromstring!(
407+
model,
408+
"variables: x\nx in $(@__MODULE__).Set2175()",
409+
)
410+
constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
411+
@test (MOI.VariableIndex, Set2175) in constraints
412+
return
413+
end
414+
415+
function test_parse_external_set_constrained_variable()
416+
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
417+
MOI.Utilities.loadfromstring!(
418+
model,
419+
"constrainedvariable: x in $(@__MODULE__).Set2175()",
420+
)
421+
constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent())
422+
@test (MOI.VariableIndex, Set2175) in constraints
423+
return
424+
end
425+
426+
function test_parse_scope()
427+
@test !isdefined(@__MODULE__, :MathOptInterface)
428+
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
429+
MOI.Utilities.loadfromstring!(
430+
model,
431+
"variables: x\nx in MathOptInterface.ZeroOne()",
432+
)
433+
attr = MOI.NumberOfConstraints{MOI.VariableIndex,MOI.ZeroOne}()
434+
@test MOI.get(model, attr) == 1
435+
return
436+
end
437+
402438
end # module
403439

404440
TestParser.runtests()

0 commit comments

Comments
 (0)