From c18c8b247c40e5ce9536cb82cbada6c4edb1ef07 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 29 May 2023 13:55:16 +1200 Subject: [PATCH] Fix support for external sets in loadfromstring (#2177) --- src/Utilities/parser.jl | 34 ++++++++++++++++++++++++++++++++-- test/Utilities/parser.jl | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/Utilities/parser.jl b/src/Utilities/parser.jl index 5c0704bbc6..ebe80ba654 100644 --- a/src/Utilities/parser.jl +++ b/src/Utilities/parser.jl @@ -267,6 +267,36 @@ function _parsed_to_moi(model, f::_ParsedVariableIndex) return _parsed_to_moi(model, f.variable) end +_walk_expr(f::F, expr) where {F<:Function} = f(expr) + +function _walk_expr(f::F, expr::Expr) where {F<:Function} + for (i, arg) in enumerate(expr.args) + expr.args[i] = _walk_expr(f, arg) + end + return expr +end + +function _parse_set(expr::Expr) + expr = _walk_expr(expr) do arg + if arg isa Symbol && arg in (:MOI, :MathOptInterface) + return MOI + end + return arg + end + @assert Meta.isexpr(expr, :call) + if expr.args[1] isa Symbol + # If the set is a Symbol, it must be one of the MOI sets. We need to + # eval this in the MOI module. + return Core.eval(MOI, expr) + elseif Meta.isexpr(expr.args[1], :curly) && expr.args[1].args[1] isa Symbol + # Something like Indicator{}() + return Core.eval(MOI, expr) + end + # If the set is an expression, it must be something like + # `SCS.ScaledPSDCone()`. We need to eval this in `Main`. + return Core.eval(Main, expr) +end + # Ideally, this should be load_from_string """ loadfromstring!(model, s) @@ -333,7 +363,7 @@ function loadfromstring!(model, s) elseif label == :constrainedvariable @assert length(ex.args) == 3 @assert ex.args[1] == :in - set = Core.eval(MOI, ex.args[3]) + set = _parse_set(ex.args[3]) if isa(ex.args[2], Symbol) # constrainedvariable: x in LessThan(1.0) x, _ = MOI.add_constrained_variable(model, set) @@ -362,7 +392,7 @@ function loadfromstring!(model, s) f = _parsed_to_moi(model, _parse_function(ex.args[2], T)) if ex.args[1] == :in # Could be safer here - set = Core.eval(MOI, ex.args[3]) + set = _parse_set(ex.args[3]) elseif ex.args[1] == :<= set = MOI.LessThan(Core.eval(Base, ex.args[3])) elseif ex.args[1] == :>= diff --git a/test/Utilities/parser.jl b/test/Utilities/parser.jl index 87ce99b8dd..6f5493d2df 100644 --- a/test/Utilities/parser.jl +++ b/test/Utilities/parser.jl @@ -399,6 +399,42 @@ function test_eltypes_complex_float64() return end +struct Set2175 <: MOI.AbstractScalarSet end + +function test_parse_external_set_constraint() + model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + model, + "variables: x\nx in $(@__MODULE__).Set2175()", + ) + constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent()) + @test (MOI.VariableIndex, Set2175) in constraints + return +end + +function test_parse_external_set_constrained_variable() + model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + model, + "constrainedvariable: x in $(@__MODULE__).Set2175()", + ) + constraints = MOI.get(model, MOI.ListOfConstraintTypesPresent()) + @test (MOI.VariableIndex, Set2175) in constraints + return +end + +function test_parse_scope() + @test !isdefined(@__MODULE__, :MathOptInterface) + model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + MOI.Utilities.loadfromstring!( + model, + "variables: x\nx in MathOptInterface.ZeroOne()", + ) + attr = MOI.NumberOfConstraints{MOI.VariableIndex,MOI.ZeroOne}() + @test MOI.get(model, attr) == 1 + return +end + end # module TestParser.runtests()