Skip to content
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
21 changes: 8 additions & 13 deletions src/FileFormats/MOF/nonlinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,18 @@ function function_to_moi(
return Nonlinear(expr)
end

function substitute_variables(expr::Expr, variables::Vector{MOI.VariableIndex})
function lift_variable_indices(expr::Expr)
if expr.head == :ref && length(expr.args) == 2 && expr.args[1] == :x
index = expr.args[2]
if !(1 <= index <= length(variables))
error("Oops! Your expression refers to x[$(index)] but there are " *
"only $(length(variables)) variables.")
end
return variables[index]
return expr.args[2]
else
for (index, arg) in enumerate(expr.args)
expr.args[index] = substitute_variables(arg, variables)
expr.args[index] = lift_variable_indices(arg)
end
end
return expr
end
# Recursion fallback.
substitute_variables(arg, variables::Vector{MOI.VariableIndex}) = arg

lift_variable_indices(arg) = arg # Recursion fallback.

function extract_function_and_set(expr::Expr)
if expr.head == :call # One-sided constraint or foo-in-set.
Expand Down Expand Up @@ -71,7 +66,7 @@ function write_nlpblock(object::Object, model::Model,
variables = MOI.get(model, MOI.ListOfVariableIndices())
if nlp_block.has_objective
objective = MOI.objective_expr(nlp_block.evaluator)
objective = substitute_variables(objective, variables)
objective = lift_variable_indices(objective)
sense = MOI.get(model, MOI.ObjectiveSense())
object["objective"] = Object(
"sense" => moi_to_object(sense),
Expand All @@ -81,7 +76,7 @@ function write_nlpblock(object::Object, model::Model,
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 = substitute_variables(func, variables)
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))
Expand Down Expand Up @@ -293,7 +288,7 @@ function convert_mof_to_expr(node::Object, node_list::Vector{Object},
end

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

Convert a Julia expression into a MathOptFormat representation. Any intermediate
Expand Down
3 changes: 3 additions & 0 deletions src/FileFormats/MOF/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ end
function write_objective(
object::Object, model::Model, name_map::Dict{MOI.VariableIndex, String}
)
if object["objective"]["sense"] != "feasibility"
return # Objective must have been written from NLPBlock.
end
sense = MOI.get(model, MOI.ObjectiveSense())
object["objective"] = Object("sense" => moi_to_object(sense))
if sense != MOI.FEASIBILITY_SENSE
Expand Down
57 changes: 54 additions & 3 deletions test/FileFormats/MOF/nlp.mof.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,60 @@
"objective": {
"sense": "min",
"function": {
"head": "ScalarAffineFunction",
"terms": [],
"constant": 0.0
"head": "ScalarNonlinearFunction",
"root": {
"head": "node",
"index": 3
},
"node_list": [
{
"head": "+",
"args": [
{
"head": "variable",
"name": "var_1"
},
{
"head": "variable",
"name": "var_2"
},
{
"head": "variable",
"name": "var_3"
}
]
},
{
"head": "*",
"args": [
{
"head": "variable",
"name": "var_1"
},
{
"head": "variable",
"name": "var_4"
},
{
"head": "node",
"index": 1
}
]
},
{
"head": "+",
"args": [
{
"head": "node",
"index": 2
},
{
"head": "variable",
"name": "var_3"
}
]
}
]
}
},
"constraints": [
Expand Down
29 changes: 17 additions & 12 deletions test/FileFormats/MOF/nonlinear.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
function roundtrip_nonlinear_expression(expr, variable_to_string,
string_to_variable)
function roundtrip_nonlinear_expression(
expr, variable_to_string, string_to_variable
)
node_list = MOF.Object[]
object = MOF.convert_expr_to_mof(expr, node_list,
variable_to_string)
@test MOF.convert_mof_to_expr(object, node_list,
string_to_variable) == expr
object = MOF.convert_expr_to_mof(expr, node_list, variable_to_string)
@test MOF.convert_mof_to_expr(object, node_list, string_to_variable) == expr
end

# hs071
Expand All @@ -21,13 +20,19 @@ MOI.initialize(::ExprEvaluator, features) = nothing
MOI.objective_expr(evaluator::ExprEvaluator) = evaluator.objective
MOI.constraint_expr(evaluator::ExprEvaluator, i::Int) = evaluator.constraints[i]

function HS071()
function HS071(x::Vector{MOI.VariableIndex})
x1, x2, x3, x4 = x
return MOI.NLPBlockData(
MOI.NLPBoundsPair.([25, 40], [Inf, 40]),
ExprEvaluator(:(x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3]),
[:(x[1] * x[2] * x[3] * x[4] >= 25),
:(x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2 == 40)]),
true)
ExprEvaluator(
:(x[$x1] * x[$x4] * (x[$x1] + x[$x2] + x[$x3]) + x[$x3]),
[
:(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25),
:(x[$x1]^2 + x[$x2]^2 + x[$x3]^2 + x[$x4]^2 == 40)
]
),
true
)
end

@testset "Nonlinear functions" begin
Expand All @@ -39,7 +44,7 @@ end
end
MOI.add_constraints(model, MOI.SingleVariable.(x),
Ref(MOI.Interval(1.0, 5.0)))
MOI.set(model, MOI.NLPBlock(), HS071())
MOI.set(model, MOI.NLPBlock(), HS071(x))
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
MOI.write_to_file(model, TEST_MOF_FILE)
@test replace(read(TEST_MOF_FILE, String), '\r' => "") ==
Expand Down