From 9608d0987e3b5fb7a51d9d053c38ec1f88c45075 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 26 Feb 2020 09:59:06 -0600 Subject: [PATCH 1/2] [MOF] Fix NLPBlock handling of interpolated variables --- src/FileFormats/MOF/nonlinear.jl | 21 ++++++++------------- test/FileFormats/MOF/nonlinear.jl | 29 +++++++++++++++++------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/FileFormats/MOF/nonlinear.jl b/src/FileFormats/MOF/nonlinear.jl index e352c32bac..ecad157725 100644 --- a/src/FileFormats/MOF/nonlinear.jl +++ b/src/FileFormats/MOF/nonlinear.jl @@ -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. @@ -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), @@ -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)) @@ -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 diff --git a/test/FileFormats/MOF/nonlinear.jl b/test/FileFormats/MOF/nonlinear.jl index dc994a7cf4..82a97d6cd2 100644 --- a/test/FileFormats/MOF/nonlinear.jl +++ b/test/FileFormats/MOF/nonlinear.jl @@ -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 @@ -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 @@ -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' => "") == From a808701a3b02bc4cbe438ad38509c7c29ef8b134 Mon Sep 17 00:00:00 2001 From: odow Date: Wed, 26 Feb 2020 10:16:32 -0600 Subject: [PATCH 2/2] [MOF] Test NLP writing objectives properly --- src/FileFormats/MOF/write.jl | 3 ++ test/FileFormats/MOF/nlp.mof.json | 57 +++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/FileFormats/MOF/write.jl b/src/FileFormats/MOF/write.jl index e32dfbf19f..97120c4840 100644 --- a/src/FileFormats/MOF/write.jl +++ b/src/FileFormats/MOF/write.jl @@ -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 diff --git a/test/FileFormats/MOF/nlp.mof.json b/test/FileFormats/MOF/nlp.mof.json index 28746611ea..222770b9e3 100644 --- a/test/FileFormats/MOF/nlp.mof.json +++ b/test/FileFormats/MOF/nlp.mof.json @@ -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": [