Skip to content

Commit

Permalink
[FileFormats.MOF] add support for MOF@1.7 (#2298)
Browse files Browse the repository at this point in the history
  • Loading branch information
odow committed Sep 28, 2023
1 parent 44018e8 commit be4d854
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 111 deletions.
2 changes: 1 addition & 1 deletion docs/src/submodules/FileFormats/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ julia> print(read("file.mof.json", String))
"name": "MathOptFormat Model",
"version": {
"major": 1,
"minor": 6
"minor": 7
},
"variables": [
{
Expand Down
1 change: 1 addition & 0 deletions src/FileFormats/MOF/MOF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ MathOptInterface.
const SCHEMA_PATH = joinpath(@__DIR__, "mof.schema.json")

const _SUPPORTED_VERSIONS = (
v"1.7",
v"1.6",
v"1.5",
v"1.4",
Expand Down
35 changes: 24 additions & 11 deletions src/FileFormats/MOF/mof.schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/schema#",
"$id": "https://jump.dev/MathOptFormat/schemas/mof.1.6.schema.json",
"$id": "https://jump.dev/MathOptFormat/schemas/mof.1.7.schema.json",
"title": "The schema for MathOptFormat",
"type": "object",
"required": ["version", "variables", "objective", "constraints"],
Expand All @@ -11,7 +11,7 @@
"required": ["minor", "major"],
"properties": {
"minor": {
"enum": [0, 1, 2, 3, 4, 5, 6]
"enum": [0, 1, 2, 3, 4, 5, 6, 7]
},
"major": {
"const": 1
Expand Down Expand Up @@ -191,11 +191,10 @@
},
"NonlinearTerm": {
"description": "A node in an expresion graph representing a nonlinear function.",
"type": "object",
"required": ["type"],
"oneOf": [{
"description": "Unary operators",
"required": ["args"],
"type": "object",
"required": ["type", "args"],
"properties": {
"type": {
"enum": [
Expand Down Expand Up @@ -284,7 +283,8 @@
}
}, {
"description": "Binary operators",
"required": ["args"],
"type": "object",
"required": ["type", "args"],
"properties": {
"type": {
"enum": [
Expand All @@ -311,7 +311,8 @@
}
}, {
"description": "N-ary operators",
"required": ["args"],
"type": "object",
"required": ["type", "args"],
"properties": {
"type": {
"enum": ["+", "-", "*", "ifelse", "min", "max"]
Expand All @@ -327,7 +328,8 @@
}, {
"description": "A real-valued numeric constant",
"examples": ["{\"type\": \"real\", \"value\": 1.0}"],
"required": ["value"],
"type": "object",
"required": ["type", "value"],
"properties": {
"type": {
"const": "real"
Expand All @@ -339,7 +341,8 @@
}, {
"description": "A complex-valued numeric constant",
"examples": ["{\"type\": \"complex\", \"real\": 1.0, \"imag\": 2.0}"],
"required": ["real", "imag"],
"type": "object",
"required": ["type", "real", "imag"],
"properties": {
"type": {
"const": "complex"
Expand All @@ -354,7 +357,8 @@
}, {
"description": "A reference to an optimization variable",
"examples": ["{\"type\": \"variable\", \"name\": \"x\"}"],
"required": ["name"],
"type": "object",
"required": ["type", "name"],
"properties": {
"type": {
"const": "variable"
Expand All @@ -366,7 +370,8 @@
}, {
"description": "A pointer to a (1-indexed) element in the `node_list` field in a nonlinear function",
"examples": ["{\"type\": \"node\", \"index\": 2}"],
"required": ["index"],
"type": "object",
"required": ["type", "index"],
"properties": {
"type": {
"const": "node"
Expand All @@ -376,6 +381,14 @@
"minimum": 1
}
}
}, {
"description": "A reference to an optimization variable",
"examples": ["\"x\""],
"type": "string"
}, {
"description": "A real-valued numeric constant",
"examples": [1.0],
"type": "number"
}]
},
"scalar_functions": {
Expand Down
28 changes: 24 additions & 4 deletions src/FileFormats/MOF/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -248,22 +248,42 @@ function function_to_moi(
object::T,
name_map::Dict{String,MOI.VariableIndex},
) where {T<:Object}
node_list = T.(object["node_list"])
f = _parse_scalar_nonlinear_function(object["root"], node_list, name_map)
return f::MOI.ScalarNonlinearFunction
return _parse_scalar_nonlinear_function(
object["root"],
object["node_list"],
name_map,
)::MOI.ScalarNonlinearFunction
end

function _parse_scalar_nonlinear_function(
node::Real,
::Vector,
::Dict{String,MOI.VariableIndex},
)
return node
end

function _parse_scalar_nonlinear_function(
node::String,
::Vector,
name_map::Dict{String,MOI.VariableIndex},
)
return name_map[node]
end

function _parse_scalar_nonlinear_function(
node::T,
node_list::Vector{T},
node_list::Vector,
name_map::Dict{String,MOI.VariableIndex},
) where {T<:Object}
head = node["type"]
if head == "real"
# Required for v1.6 and earlier
return node["value"]
elseif head == "complex"
return Complex(node["real"], node["imag"])
elseif head == "variable"
# Required for v1.6 and earlier
return name_map[node["name"]]
elseif head == "node"
return _parse_scalar_nonlinear_function(
Expand Down
53 changes: 33 additions & 20 deletions src/FileFormats/MOF/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,65 +212,76 @@ function moi_to_object(
end

function _convert_nonlinear_to_mof(
::Type{T},
expr::Expr,
node_list::Vector{T},
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" => T[])
node = T("type" => string(expr.args[1]), "args" => Any[])
for i in 2:length(expr.args)
arg = expr.args[i]
push!(node["args"], _convert_nonlinear_to_mof(arg, node_list, name_map))
push!(
node["args"],
_convert_nonlinear_to_mof(T, expr.args[i], node_list, name_map),
)
end
push!(node_list, node)
return T("type" => "node", "index" => length(node_list))
end

function _convert_nonlinear_to_mof(
::Type{T},
f::MOI.ScalarNonlinearFunction,
node_list::Vector{T},
node_list::Vector{Any},
name_map::Dict{MOI.VariableIndex,String},
) where {T<:Object}
node = T("type" => string(f.head), "args" => T[])
node = T("type" => string(f.head), "args" => Any[])
for arg in f.args
push!(node["args"], _convert_nonlinear_to_mof(arg, node_list, name_map))
push!(
node["args"],
_convert_nonlinear_to_mof(T, arg, node_list, name_map),
)
end
push!(node_list, node)
return T("type" => "node", "index" => length(node_list))
end

function _convert_nonlinear_to_mof(
::Type{T},
variable::MOI.VariableIndex,
::Vector{T},
::Vector{Any},
name_map::Dict{MOI.VariableIndex,String},
) where {T<:Object}
return T("type" => "variable", "name" => name_map[variable])
return name_map[variable]
end

function _convert_nonlinear_to_mof(
::Type{T},
value::Real,
::Vector{T},
name_map::Dict{MOI.VariableIndex,String},
::Vector{Any},
::Dict{MOI.VariableIndex,String},
) where {T<:Object}
return T("type" => "real", "value" => value)
return value
end

function _convert_nonlinear_to_mof(
::Type{T},
value::Complex,
::Vector{T},
::Vector{Any},
::Dict{MOI.VariableIndex,String},
) where {T<:Object}
return T("type" => "complex", "real" => real(value), "imag" => imag(value))
end

function moi_to_object(foo::Nonlinear, name_map::Dict{MOI.VariableIndex,String})
node_list = OrderedObject[]
foo_object = _convert_nonlinear_to_mof(foo.expr, node_list, name_map)
node_list = Any[]
root =
_convert_nonlinear_to_mof(OrderedObject, foo.expr, node_list, name_map)
return OrderedObject(
"type" => "ScalarNonlinearFunction",
"root" => foo_object,
"root" => root,
"node_list" => node_list,
)
end
Expand All @@ -279,8 +290,8 @@ function moi_to_object(
foo::MOI.ScalarNonlinearFunction,
name_map::Dict{MOI.VariableIndex,String},
)
node_list = OrderedObject[]
root = _convert_nonlinear_to_mof(foo, node_list, name_map)
node_list = Any[]
root = _convert_nonlinear_to_mof(OrderedObject, foo, node_list, name_map)
return OrderedObject(
"type" => "ScalarNonlinearFunction",
"root" => root,
Expand Down Expand Up @@ -350,8 +361,10 @@ function moi_to_object(
foo::MOI.VectorNonlinearFunction,
name_map::Dict{MOI.VariableIndex,String},
)
node_list = OrderedObject[]
rows = [_convert_nonlinear_to_mof(f, node_list, name_map) for f in foo.rows]
node_list = Any[]
rows = map(foo.rows) do f
return _convert_nonlinear_to_mof(OrderedObject, f, node_list, name_map)
end
return OrderedObject(
"type" => "VectorNonlinearFunction",
"rows" => rows,
Expand Down
52 changes: 50 additions & 2 deletions test/FileFormats/MOF/MOF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,36 +144,42 @@ 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,
Expand All @@ -195,6 +201,22 @@ function test_nonlinear_error_handling()
@test MOF._extract_function_and_set(:(x <= 2)) == (:x, MOI.LessThan(2))
end

function _convert_mof_to_expr(
node::String,
::Vector,
name_map::Dict{String,MOI.VariableIndex},
)
return name_map[node]
end

function _convert_mof_to_expr(
node::Real,
::Vector,
::Dict{String,MOI.VariableIndex},
)
return node
end

function _convert_mof_to_expr(
node::T,
node_list::Vector{T},
Expand Down Expand Up @@ -263,8 +285,13 @@ function test_Roundtrip_nonlinear_expressions()
# :($x && $y), :($x || $y),
:(ifelse($x > 0, 1, $y)),
]
node_list = MOF.OrderedObject[]
object = MOF._convert_nonlinear_to_mof(expr, node_list, var_to_string)
node_list = Any[]
object = MOF._convert_nonlinear_to_mof(
MOF.OrderedObject,
expr,
node_list,
var_to_string,
)
@test _convert_mof_to_expr(object, node_list, string_to_var) == expr
end
return
Expand Down Expand Up @@ -1463,6 +1490,27 @@ function test_ScaledPositiveSemidefiniteConeTriangle()
return
end

function test_nonlinear_variable_real_nodes()
x = MOI.VariableIndex(1)
object = MOF.OrderedObject(
"type" => "ScalarNonlinearFunction",
"root" => MOF.OrderedObject(
"type" => "^",
"args" => Any[
MOF.OrderedObject("type" => "node", "index" => 1),
MOF.OrderedObject("type" => "node", "index" => 2),
],
),
"node_list" => Any[
MOF.OrderedObject("type" => "variable", "name" => "x"),
MOF.OrderedObject("type" => "real", "value" => 2.0),
],
)
f = MOI.ScalarNonlinearFunction(:^, Any[x, 2.0])
@test MOF.function_to_moi(object, Dict("x" => x)) f
return
end

end

TestMOF.runtests()

0 comments on commit be4d854

Please sign in to comment.