diff --git a/src/FileFormats/MOF/write.jl b/src/FileFormats/MOF/write.jl index 99b8a16738..59f3d0c02e 100644 --- a/src/FileFormats/MOF/write.jl +++ b/src/FileFormats/MOF/write.jl @@ -11,15 +11,16 @@ Write `model` to `io` in the MathOptFormat file format. """ function Base.write(io::IO, model::Model) options = get_options(model) + # remains a OrderedObject as the "objective" field is modified object = OrderedObject( "name" => "MathOptFormat Model", - "version" => OrderedObject( - "major" => Int(_SUPPORTED_VERSIONS[1].major), - "minor" => Int(_SUPPORTED_VERSIONS[1].minor), + "version" => ( + major = Int(_SUPPORTED_VERSIONS[1].major), + minor = Int(_SUPPORTED_VERSIONS[1].minor), ), - "variables" => Object[], - "objective" => OrderedObject("sense" => "feasibility"), - "constraints" => Object[], + "variables" => NamedTuple[], + "objective" => (sense = "feasibility",), + "constraints" => NamedTuple[], ) FileFormats.create_unique_names(model, warn = options.warn) name_map = write_variables(object, model) @@ -31,11 +32,11 @@ function Base.write(io::IO, model::Model) return end -function write_variables(object, model::Model) +function write_variables(object::Object, model::Model) name_map = Dict{MOI.VariableIndex,String}() for index in MOI.get(model, MOI.ListOfVariableIndices()) variable = moi_to_object(index, model) - name_map[index] = variable["name"] + name_map[index] = variable[:name] push!(object["variables"], variable) end return name_map @@ -80,10 +81,10 @@ function extract_function_and_set(expr::Expr) end function write_nlpblock( - object::T, + object::Object, model::Model, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} +) nlp_block = MOI.get(model, MOI.NLPBlock()) if nlp_block === nothing return @@ -94,9 +95,9 @@ function write_nlpblock( objective = MOI.objective_expr(nlp_block.evaluator) objective = _lift_variable_indices(objective) sense = MOI.get(model, MOI.ObjectiveSense()) - object["objective"] = T( - "sense" => moi_to_object(sense), - "function" => moi_to_object(Nonlinear(objective), name_map), + object["objective"] = (; + :sense => moi_to_object(sense), + :function => moi_to_object(Nonlinear(objective), name_map), ) end for (row, bounds) in enumerate(nlp_block.constraint_bounds) @@ -105,9 +106,9 @@ function write_nlpblock( func = _lift_variable_indices(func) push!( object["constraints"], - T( - "function" => moi_to_object(Nonlinear(func), name_map), - "set" => moi_to_object(set, name_map), + (; + :function => moi_to_object(Nonlinear(func), name_map), + :set => moi_to_object(set, name_map), ), ) end @@ -115,26 +116,27 @@ function write_nlpblock( end function write_objective( - object::T, + object::Object, model::Model, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} - if object["objective"]["sense"] != "feasibility" +) + if object["objective"][:sense] != "feasibility" return # Objective must have been written from NLPBlock. end sense = MOI.get(model, MOI.ObjectiveSense()) - object["objective"] = T("sense" => moi_to_object(sense)) if sense != MOI.FEASIBILITY_SENSE F = MOI.get(model, MOI.ObjectiveFunctionType()) objective_function = MOI.get(model, MOI.ObjectiveFunction{F}()) - object["objective"]["function"] = - moi_to_object(objective_function, name_map) + object["objective"] = (; + :sense => moi_to_object(sense), + :function => moi_to_object(objective_function, name_map), + ) end return end function write_constraints( - object, + object::Object, model::Model, name_map::Dict{MOI.VariableIndex,String}, ) @@ -159,9 +161,9 @@ function moi_to_object(index::MOI.VariableIndex, model::Model) if name == "" error("Variable name for $(index) cannot be blank in an MOF file.") elseif isnothing(primal_start) - return OrderedObject("name" => name) + return (name = name,) else - return OrderedObject("name" => name, "primal_start" => primal_start) + return (name = name, primal_start = primal_start) end end @@ -174,22 +176,22 @@ function moi_to_object( set = MOI.get(model, MOI.ConstraintSet(), index) dual_start = MOI.get(model, MOI.ConstraintDualStart(), index) primal_start = MOI.get(model, MOI.ConstraintPrimalStart(), index) - object = OrderedObject() + pairs = Pair{Symbol,Any}[] if F != MOI.VariableIndex name = MOI.get(model, MOI.ConstraintName(), index) if name != "" - object["name"] = name + push!(pairs, :name => name) end end - object["function"] = moi_to_object(func, name_map) - object["set"] = moi_to_object(set, name_map) + push!(pairs, :function => moi_to_object(func, name_map)) + push!(pairs, :set => moi_to_object(set, name_map)) if !isnothing(dual_start) - object["dual_start"] = dual_start + push!(pairs, :dual_start => dual_start) end if !isnothing(primal_start) - object["primal_start"] = primal_start + push!(pairs, :primal_start => primal_start) end - return object + return NamedTuple(pairs) end function moi_to_object(sense::MOI.OptimizationSense) @@ -209,63 +211,55 @@ function moi_to_object( foo::MOI.VariableIndex, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject("type" => "Variable", "name" => name_map[foo]) + return (type = "Variable", name = name_map[foo]) end function _convert_nonlinear_to_mof( - ::Type{T}, expr::Expr, node_list::Vector{Any}, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} +) if expr.head != :call error("Expected an expression that was a function. Got $(expr).") end - node = T("type" => string(expr.args[1]), "args" => Any[]) + node = (type = string(expr.args[1]), args = Any[]) for i in 2:length(expr.args) push!( - node["args"], - _convert_nonlinear_to_mof(T, expr.args[i], node_list, name_map), + node[:args], + _convert_nonlinear_to_mof(expr.args[i], node_list, name_map), ) end push!(node_list, node) - return T("type" => "node", "index" => length(node_list)) + return (type = "node", index = length(node_list)) end function _convert_nonlinear_to_mof( - ::Type{T}, f::MOI.ScalarNonlinearFunction, node_list::Vector{Any}, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} - node = T("type" => string(f.head), "args" => Any[]) +) + node = (type = string(f.head), args = Any[]) for arg in f.args - push!( - node["args"], - _convert_nonlinear_to_mof(T, arg, node_list, name_map), - ) + push!(node[:args], _convert_nonlinear_to_mof(arg, node_list, name_map)) end push!(node_list, node) - return T("type" => "node", "index" => length(node_list)) + return (type = "node", index = length(node_list)) end function _convert_nonlinear_to_mof( - ::Type{T}, variable::MOI.VariableIndex, ::Vector{Any}, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} +) return name_map[variable] end function _convert_nonlinear_to_mof( - ::Type{T}, f::MOI.AbstractScalarFunction, node_list::Vector{Any}, name_map::Dict{MOI.VariableIndex,String}, -) where {T<:Object} +) return _convert_nonlinear_to_mof( - T, convert(MOI.ScalarNonlinearFunction, f), node_list, name_map, @@ -273,31 +267,28 @@ function _convert_nonlinear_to_mof( end function _convert_nonlinear_to_mof( - ::Type{T}, value::Real, ::Vector{Any}, ::Dict{MOI.VariableIndex,String}, -) where {T<:Object} +) return value end function _convert_nonlinear_to_mof( - ::Type{T}, value::Complex, ::Vector{Any}, ::Dict{MOI.VariableIndex,String}, -) where {T<:Object} - return T("type" => "complex", "real" => real(value), "imag" => imag(value)) +) + return (type = "complex", real = real(value), imag = imag(value)) end function moi_to_object(foo::Nonlinear, name_map::Dict{MOI.VariableIndex,String}) node_list = Any[] - root = - _convert_nonlinear_to_mof(OrderedObject, foo.expr, node_list, name_map) - return OrderedObject( - "type" => "ScalarNonlinearFunction", - "root" => root, - "node_list" => node_list, + root = _convert_nonlinear_to_mof(foo.expr, node_list, name_map) + return ( + type = "ScalarNonlinearFunction", + root = root, + node_list = node_list, ) end @@ -306,11 +297,11 @@ function moi_to_object( name_map::Dict{MOI.VariableIndex,String}, ) node_list = Any[] - root = _convert_nonlinear_to_mof(OrderedObject, foo, node_list, name_map) - return OrderedObject( - "type" => "ScalarNonlinearFunction", - "root" => root, - "node_list" => node_list, + root = _convert_nonlinear_to_mof(foo, node_list, name_map) + return ( + type = "ScalarNonlinearFunction", + root = root, + node_list = node_list, ) end @@ -320,20 +311,17 @@ function moi_to_object( foo::MOI.ScalarAffineTerm, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "coefficient" => foo.coefficient, - "variable" => name_map[foo.variable], - ) + return (coefficient = foo.coefficient, variable = name_map[foo.variable]) end function moi_to_object( foo::MOI.ScalarAffineFunction, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "ScalarAffineFunction", - "terms" => moi_to_object.(foo.terms, Ref(name_map)), - "constant" => foo.constant, + return ( + type = "ScalarAffineFunction", + terms = moi_to_object.(foo.terms, Ref(name_map)), + constant = foo.constant, ) end @@ -341,10 +329,10 @@ function moi_to_object( foo::MOI.ScalarQuadraticTerm, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "coefficient" => foo.coefficient, - "variable_1" => name_map[foo.variable_1], - "variable_2" => name_map[foo.variable_2], + return ( + coefficient = foo.coefficient, + variable_1 = name_map[foo.variable_1], + variable_2 = name_map[foo.variable_2], ) end @@ -352,11 +340,11 @@ function moi_to_object( foo::MOI.ScalarQuadraticFunction, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "ScalarQuadraticFunction", - "affine_terms" => moi_to_object.(foo.affine_terms, Ref(name_map)), - "quadratic_terms" => moi_to_object.(foo.quadratic_terms, Ref(name_map)), - "constant" => foo.constant, + return ( + type = "ScalarQuadraticFunction", + affine_terms = moi_to_object.(foo.affine_terms, Ref(name_map)), + quadratic_terms = moi_to_object.(foo.quadratic_terms, Ref(name_map)), + constant = foo.constant, ) end @@ -366,9 +354,9 @@ function moi_to_object( foo::MOI.VectorOfVariables, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "VectorOfVariables", - "variables" => [name_map[variable] for variable in foo.variables], + return ( + type = "VectorOfVariables", + variables = [name_map[variable] for variable in foo.variables], ) end @@ -378,12 +366,12 @@ function moi_to_object( ) node_list = Any[] rows = map(foo.rows) do f - return _convert_nonlinear_to_mof(OrderedObject, f, node_list, name_map) + return _convert_nonlinear_to_mof(f, node_list, name_map) end - return OrderedObject( - "type" => "VectorNonlinearFunction", - "rows" => rows, - "node_list" => node_list, + return ( + type = "VectorNonlinearFunction", + rows = rows, + node_list = node_list, ) end @@ -393,9 +381,9 @@ function moi_to_object( foo::MOI.VectorAffineTerm, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "output_index" => foo.output_index, - "scalar_term" => moi_to_object(foo.scalar_term, name_map), + return ( + output_index = foo.output_index, + scalar_term = moi_to_object(foo.scalar_term, name_map), ) end @@ -403,10 +391,10 @@ function moi_to_object( foo::MOI.VectorAffineFunction, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "VectorAffineFunction", - "terms" => moi_to_object.(foo.terms, Ref(name_map)), - "constants" => foo.constants, + return ( + type = "VectorAffineFunction", + terms = moi_to_object.(foo.terms, Ref(name_map)), + constants = foo.constants, ) end @@ -414,9 +402,9 @@ function moi_to_object( foo::MOI.VectorQuadraticTerm, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "output_index" => foo.output_index, - "scalar_term" => moi_to_object(foo.scalar_term, name_map), + return ( + output_index = foo.output_index, + scalar_term = moi_to_object(foo.scalar_term, name_map), ) end @@ -424,11 +412,11 @@ function moi_to_object( foo::MOI.VectorQuadraticFunction, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "VectorQuadraticFunction", - "affine_terms" => moi_to_object.(foo.affine_terms, Ref(name_map)), - "quadratic_terms" => moi_to_object.(foo.quadratic_terms, Ref(name_map)), - "constants" => foo.constants, + return ( + type = "VectorQuadraticFunction", + affine_terms = moi_to_object.(foo.affine_terms, Ref(name_map)), + quadratic_terms = moi_to_object.(foo.quadratic_terms, Ref(name_map)), + constants = foo.constants, ) end @@ -448,11 +436,11 @@ function moi_to_object( set::SetType, ::Dict{MOI.VariableIndex,String}, ) where {SetType} - object = OrderedObject("type" => head_name(SetType)) + pairs = Pair{Symbol,Any}[:type=>head_name(SetType)] for key in fieldnames(SetType) - object[string(key)] = getfield(set, key) + push!(pairs, Symbol(string(key)) => getfield(set, key)) end - return object + return NamedTuple(pairs) end # ========== Non-typed scalar sets ========== @@ -521,10 +509,10 @@ function moi_to_object( name_map::Dict{MOI.VariableIndex,String}, ) where {I,S} @assert I == MOI.ACTIVATE_ON_ONE || I == MOI.ACTIVATE_ON_ZERO - return OrderedObject( - "type" => "Indicator", - "set" => moi_to_object(set.set, name_map), - "activate_on" => (I == MOI.ACTIVATE_ON_ONE) ? "one" : "zero", + return ( + type = "Indicator", + set = moi_to_object(set.set, name_map), + activate_on = (I == MOI.ACTIVATE_ON_ONE) ? "one" : "zero", ) end @@ -532,19 +520,16 @@ function moi_to_object( set::MOI.Reified, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "Reified", - "set" => moi_to_object(set.set, name_map), - ) + return (type = "Reified", set = moi_to_object(set.set, name_map)) end function moi_to_object( set::MOI.Table{T}, ::Dict{MOI.VariableIndex,String}, ) where {T} - return OrderedObject( - "type" => "Table", - "table" => [set.table[i, :] for i in 1:size(set.table, 1)], + return ( + type = "Table", + table = [set.table[i, :] for i in 1:size(set.table, 1)], ) end @@ -552,8 +537,5 @@ function moi_to_object( set::MOI.Scaled, name_map::Dict{MOI.VariableIndex,String}, ) - return OrderedObject( - "type" => "Scaled", - "set" => moi_to_object(set.set, name_map), - ) + return (type = "Scaled", set = moi_to_object(set.set, name_map)) end diff --git a/test/FileFormats/MOF/MOF.jl b/test/FileFormats/MOF/MOF.jl index efd1320e27..991388b39d 100644 --- a/test/FileFormats/MOF/MOF.jl +++ b/test/FileFormats/MOF/MOF.jl @@ -144,42 +144,36 @@ function test_nonlinear_error_handling() variable_to_string = Dict{MOI.VariableIndex,String}() # Test unsupported function for Expr -> MOF. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(not_supported_function(x)), node_list, variable_to_string, ) # Test n-ary function with no arguments. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(min()), node_list, variable_to_string, ) # Test unary function with two arguments. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(sin(x, y)), node_list, variable_to_string, ) # Test binary function with one arguments. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(^(x)), node_list, variable_to_string, ) # An expression with something other than :call as the head. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(a <= b <= c), node_list, variable_to_string, ) # Hit the default fallback with an un-interpolated complex number. @test_throws Exception MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, :(1 + 2im), node_list, variable_to_string, @@ -222,22 +216,22 @@ function _convert_mof_to_expr( node_list::Vector{T}, name_map::Dict{String,MOI.VariableIndex}, ) where {T} - head = haskey(node, "type") ? node["type"] : node["head"] + head = haskey(node, :type) ? node[:type] : node[:head] if head == "real" - return node["value"] + return node[:value] elseif head == "complex" - return Complex(node["real"], node["imag"]) + return Complex(node[:real], node[:imag]) elseif head == "variable" - return name_map[node["name"]] + return name_map[node[:name]] elseif head == "node" return _convert_mof_to_expr( - node_list[node["index"]], + node_list[node[:index]], node_list, name_map, ) else expr = Expr(:call, Symbol(head)) - for arg in node["args"] + for arg in node[:args] push!(expr.args, _convert_mof_to_expr(arg, node_list, name_map)) end return expr @@ -286,12 +280,7 @@ function test_Roundtrip_nonlinear_expressions() :(ifelse($x > 0, 1, $y)), ] node_list = Any[] - object = MOF._convert_nonlinear_to_mof( - MOF.OrderedObject, - expr, - node_list, - var_to_string, - ) + object = MOF._convert_nonlinear_to_mof(expr, node_list, var_to_string) @test _convert_mof_to_expr(object, node_list, string_to_var) == expr end return @@ -361,8 +350,7 @@ function test_Blank_variable_name() variable = MOI.add_variable(model) @test_throws Exception MOF.moi_to_object(variable, model) MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(variable, model) == - MOF.OrderedObject("name" => "x1") + @test MOF.moi_to_object(variable, model) == (name = "x1",) end function test_Duplicate_variable_name() @@ -371,11 +359,11 @@ function test_Duplicate_variable_name() MOI.set(model, MOI.VariableName(), x, "x") y = MOI.add_variable(model) MOI.set(model, MOI.VariableName(), y, "x") - @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") - @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x") + @test MOF.moi_to_object(x, model) == (name = "x",) + @test MOF.moi_to_object(y, model) == (name = "x",) MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(x, model) == MOF.OrderedObject("name" => "x") - @test MOF.moi_to_object(y, model) == MOF.OrderedObject("name" => "x_1") + @test MOF.moi_to_object(x, model) == (name = "x",) + @test MOF.moi_to_object(y, model) == (name = "x_1",) end function test_Blank_constraint_name() @@ -386,7 +374,7 @@ function test_Blank_constraint_name() c = MOI.add_constraint(model, f, MOI.ZeroOne()) name_map = Dict(x => "x") MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(c, model, name_map)["name"] == "c1" + @test MOF.moi_to_object(c, model, name_map)[:name] == "c1" end function test_Duplicate_constraint_name() @@ -399,11 +387,11 @@ function test_Duplicate_constraint_name() MOI.set(model, MOI.ConstraintName(), c1, "c") MOI.set(model, MOI.ConstraintName(), c2, "c") name_map = Dict(x => "x") - @test MOF.moi_to_object(c1, model, name_map)["name"] == "c" - @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" + @test MOF.moi_to_object(c1, model, name_map)[:name] == "c" + @test MOF.moi_to_object(c2, model, name_map)[:name] == "c" MOI.FileFormats.create_unique_names(model, warn = true) - @test MOF.moi_to_object(c1, model, name_map)["name"] == "c_1" - @test MOF.moi_to_object(c2, model, name_map)["name"] == "c" + @test MOF.moi_to_object(c1, model, name_map)[:name] == "c_1" + @test MOF.moi_to_object(c2, model, name_map)[:name] == "c" end function test_empty_model() @@ -1464,18 +1452,13 @@ function test_integer_coefficients() x = MOI.VariableIndex(1) names = Dict(x => "x") f = 2 * x * x + 3 * x + 4 - @test MOF.moi_to_object(f, names) == MOF.OrderedObject( - "type" => "ScalarQuadraticFunction", - "affine_terms" => - [MOF.OrderedObject("coefficient" => 3, "variable" => "x")], - "quadratic_terms" => [ - MOF.OrderedObject( - "coefficient" => 4, - "variable_1" => "x", - "variable_2" => "x", - ), + @test MOF.moi_to_object(f, names) == ( + type = "ScalarQuadraticFunction", + affine_terms = [(coefficient = 3, variable = "x")], + quadratic_terms = [ + (coefficient = 4, variable_1 = "x", variable_2 = "x"), ], - "constant" => 4, + constant = 4, ) return end @@ -1517,25 +1500,13 @@ function test_mof_scalaraffinefunction() g = MOI.ScalarNonlinearFunction(:log, Any[f]) name_map = Dict(x => "x") object = MOF.moi_to_object(g, name_map) - object_dest = MOF.OrderedObject( - "type" => "ScalarNonlinearFunction", - "root" => MOF.OrderedObject("type" => "node", "index" => 3), - "node_list" => Any[ - MOF.OrderedObject("type" => "*", "args" => [1.0, "x"]), - MOF.OrderedObject( - "type" => "+", - "args" => [ - MOF.OrderedObject("type" => "node", "index" => 1), - 2.0, - ], - ), - MOF.OrderedObject( - "type" => "log", - "args" => Any[MOF.OrderedObject( - "type" => "node", - "index" => 2, - )], - ), + object_dest = ( + type = "ScalarNonlinearFunction", + root = (type = "node", index = 3), + node_list = Any[ + (type = "*", args = [1.0, "x"]), + (type = "+", args = [(type = "node", index = 1), 2.0]), + (type = "log", args = Any[(type = "node", index = 2)]), ], ) @test object == object_dest @@ -1548,25 +1519,13 @@ function test_mof_scalarquadraticfunction() g = MOI.ScalarNonlinearFunction(:log, Any[f]) name_map = Dict(x => "x") object = MOF.moi_to_object(g, name_map) - object_dest = MOF.OrderedObject( - "type" => "ScalarNonlinearFunction", - "root" => MOF.OrderedObject("type" => "node", "index" => 3), - "node_list" => Any[ - MOF.OrderedObject("type" => "*", "args" => [1.0, "x", "x"]), - MOF.OrderedObject( - "type" => "+", - "args" => [ - MOF.OrderedObject("type" => "node", "index" => 1), - 2.0, - ], - ), - MOF.OrderedObject( - "type" => "log", - "args" => Any[MOF.OrderedObject( - "type" => "node", - "index" => 2, - )], - ), + object_dest = ( + type = "ScalarNonlinearFunction", + root = (type = "node", index = 3), + node_list = Any[ + (type = "*", args = [1.0, "x", "x"]), + (type = "+", args = [(type = "node", index = 1), 2.0]), + (type = "log", args = Any[(type = "node", index = 2)]), ], ) @test object == object_dest