Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various improvements to the FilesFormats.MOF subpackage. #1111

Merged
merged 6 commits into from
Jul 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/FileFormats/MOF/MOF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import JSONSchema
import MathOptInterface

const MOI = MathOptInterface
const Object = OrderedCollections.OrderedDict{String, Any}
const SCHEMA_PATH = joinpath(@__DIR__, "v0.4.0.json")
const VERSION = let data = JSON.parsefile(SCHEMA_PATH, use_mmap=false)
VersionNumber(
Expand All @@ -16,7 +15,11 @@ const VERSION = let data = JSON.parsefile(SCHEMA_PATH, use_mmap=false)
)
end

function _parse_mof_version(version::Object)
const OrderedObject = OrderedCollections.OrderedDict{String, Any}
const UnorderedObject = Dict{String, Any}
const Object = Union{OrderedObject, UnorderedObject}

function _parse_mof_version(version)
return VersionNumber(version["major"], version["minor"])
end

Expand Down Expand Up @@ -54,7 +57,7 @@ struct Options
end

function get_options(m::Model)
return get(m.model.ext, :MOF_OPTIONS, Options(false, true, false))
return get(m.model.ext, :MOF_OPTIONS, Options(false, false, false))
end

"""
Expand All @@ -67,13 +70,14 @@ Keyword arguments are:
- `print_compact::Bool=false`: print the JSON file in a compact format without
spaces or newlines.

- `validate::Bool=true`: validate each file prior to reading against the MOF
schema
- `validate::Bool=false`: validate each file prior to reading against the MOF
schema. Defaults to `false` because this can take a long time for large
models.

- `warn::Bool=false`: print a warning when variables or constraints are renamed
"""
function Model(;
print_compact::Bool = false, validate::Bool = true, warn::Bool = false
print_compact::Bool = false, validate::Bool = false, warn::Bool = false
)
model = MOI.Utilities.UniversalFallback(InnerModel{Float64}())
model.model.ext[:MOF_OPTIONS] = Options(print_compact, validate, warn)
Expand Down
98 changes: 60 additions & 38 deletions src/FileFormats/MOF/nonlinear.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# Overload for writing.
function moi_to_object(foo::Nonlinear, model::Model,
name_map::Dict{MOI.VariableIndex, String})
node_list = Object[]
function moi_to_object(
foo::Nonlinear, name_map::Dict{MOI.VariableIndex, String}
)
node_list = OrderedObject[]
foo_object = convert_expr_to_mof(foo.expr, node_list, name_map)
return Object("head" => "ScalarNonlinearFunction", "root" => foo_object,
"node_list" => node_list)
return OrderedObject(
"head" => "ScalarNonlinearFunction",
"root" => foo_object,
"node_list" => node_list,
)
end

# Overload for reading.
function function_to_moi(
::Val{:ScalarNonlinearFunction}, object::Object, model::Model,
name_map::Dict{String, MOI.VariableIndex})
node_list = Object.(object["node_list"])
::Val{:ScalarNonlinearFunction},
object::T,
name_map::Dict{String, MOI.VariableIndex}
) where {T <: Object}
node_list = T.(object["node_list"])
expr = convert_mof_to_expr(object["root"], node_list, name_map)
return Nonlinear(expr)
end
Expand Down Expand Up @@ -53,8 +59,9 @@ function extract_function_and_set(expr::Expr)
error("Oops. The constraint $(expr) wasn't recognised.")
end

function write_nlpblock(object::Object, model::Model,
name_map::Dict{MOI.VariableIndex, String})
function write_nlpblock(
object::T, model::Model, name_map::Dict{MOI.VariableIndex, String}
) where {T <: Object}
# TODO(odow): is there a better way of checking if the NLPBlock is set?
nlp_block = try
MOI.get(model, MOI.NLPBlock())
Expand All @@ -68,18 +75,21 @@ function write_nlpblock(object::Object, model::Model,
objective = MOI.objective_expr(nlp_block.evaluator)
objective = lift_variable_indices(objective)
sense = MOI.get(model, MOI.ObjectiveSense())
object["objective"] = Object(
object["objective"] = T(
"sense" => moi_to_object(sense),
"function" => moi_to_object(Nonlinear(objective), model, name_map)
"function" => moi_to_object(Nonlinear(objective), name_map)
)
end
for (row, bounds) in enumerate(nlp_block.constraint_bounds)
constraint = MOI.constraint_expr(nlp_block.evaluator, row)
(func, set) = extract_function_and_set(constraint)
func = lift_variable_indices(func)
push!(object["constraints"],
Object("function" => moi_to_object(Nonlinear(func), model, name_map),
"set" => moi_to_object(set, model, name_map))
push!(
object["constraints"],
T(
"function" => moi_to_object(Nonlinear(func), name_map),
"set" => moi_to_object(set, name_map)
)
)
end
end
Expand Down Expand Up @@ -255,15 +265,17 @@ for (mathoptformat_string, (julia_symbol, num_arguments)) in SUPPORTED_FUNCTIONS
end

"""
convert_mof_to_expr(node::Object, node_list::Vector{Object},
name_map::Dict{String, MOI.VariableIndex})
convert_mof_to_expr(
node::T, node_list::Vector{T}, name_map::Dict{String, MOI.VariableIndex}
)

Convert a MathOptFormat node `node` into a Julia expression given a list of
MathOptFormat nodes in `node_list`. Variable names are mapped through `name_map`
to their variable index.
"""
function convert_mof_to_expr(node::Object, node_list::Vector{Object},
name_map::Dict{String, MOI.VariableIndex})
function convert_mof_to_expr(
node::T, node_list::Vector{T}, name_map::Dict{String, MOI.VariableIndex}
) where {T <: Object}
head = node["head"]
if head == "real"
return node["value"]
Expand All @@ -288,15 +300,21 @@ function convert_mof_to_expr(node::Object, node_list::Vector{Object},
end

"""
convert_expr_to_mof(node::Object, node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
convert_expr_to_mof(
node::T,
node_list::Vector{T},
name_map::Dict{MOI.VariableIndex, String}
)

Convert a Julia expression into a MathOptFormat representation. Any intermediate
nodes that are required are appended to `node_list`. Variable indices are mapped
through `name_map` to their string name.
"""
function convert_expr_to_mof(expr::Expr, node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
function convert_expr_to_mof(
expr::Expr,
node_list::Vector{T},
name_map::Dict{MOI.VariableIndex, String},
) where {T <: Object}
if expr.head != :call
error("Expected an expression that was a function. Got $(expr).")
end
Expand All @@ -306,36 +324,40 @@ function convert_expr_to_mof(expr::Expr, node_list::Vector{Object},
end
(mathoptformat_string, arity) = FUNCTION_TO_STRING[function_name]
validate_arguments(function_name, arity, length(expr.args) - 1)
node = Object("head" => mathoptformat_string, "args" => Object[])
node = T("head" => mathoptformat_string, "args" => T[])
for arg in @view(expr.args[2:end])
push!(node["args"], convert_expr_to_mof(arg, node_list, name_map))
end
push!(node_list, node)
return Object("head" => "node", "index" => length(node_list))
return T("head" => "node", "index" => length(node_list))
end

# Recursion end for variables.
function convert_expr_to_mof(variable::MOI.VariableIndex,
node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
return Object("head" => "variable", "name" => name_map[variable])
function convert_expr_to_mof(
variable::MOI.VariableIndex,
::Vector{T},
name_map::Dict{MOI.VariableIndex, String},
) where {T <: Object}
return T("head" => "variable", "name" => name_map[variable])
end

# Recursion end for real constants.
function convert_expr_to_mof(value::Real, node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
return Object("head" => "real", "value" => value)
function convert_expr_to_mof(
value::Real, ::Vector{T}, name_map::Dict{MOI.VariableIndex, String}
) where {T <: Object}
return T("head" => "real", "value" => value)
end

# Recursion end for complex numbers.
function convert_expr_to_mof(value::Complex, node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
return Object("head" => "complex", "real" => real(value),
"imag" => imag(value))
function convert_expr_to_mof(
value::Complex, ::Vector{T}, ::Dict{MOI.VariableIndex, String}
) where {T <: Object}
return T("head" => "complex", "real" => real(value), "imag" => imag(value))
end

# Recursion fallback.
function convert_expr_to_mof(fallback, node_list::Vector{Object},
name_map::Dict{MOI.VariableIndex, String})
function convert_expr_to_mof(
fallback, ::Vector{<:Object}, ::Dict{MOI.VariableIndex, String}
)
error("Unexpected $(typeof(fallback)) encountered: $(fallback).")
end
Loading