Skip to content

Commit 2d68079

Browse files
authored
[MOF] fix writing NLPBlock (#1037)
* [MOF] Fix NLPBlock handling of interpolated variables * [MOF] Test NLP writing objectives properly
1 parent 22645a9 commit 2d68079

4 files changed

Lines changed: 82 additions & 28 deletions

File tree

src/FileFormats/MOF/nonlinear.jl

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,18 @@ function function_to_moi(
1616
return Nonlinear(expr)
1717
end
1818

19-
function substitute_variables(expr::Expr, variables::Vector{MOI.VariableIndex})
19+
function lift_variable_indices(expr::Expr)
2020
if expr.head == :ref && length(expr.args) == 2 && expr.args[1] == :x
21-
index = expr.args[2]
22-
if !(1 <= index <= length(variables))
23-
error("Oops! Your expression refers to x[$(index)] but there are " *
24-
"only $(length(variables)) variables.")
25-
end
26-
return variables[index]
21+
return expr.args[2]
2722
else
2823
for (index, arg) in enumerate(expr.args)
29-
expr.args[index] = substitute_variables(arg, variables)
24+
expr.args[index] = lift_variable_indices(arg)
3025
end
3126
end
3227
return expr
3328
end
34-
# Recursion fallback.
35-
substitute_variables(arg, variables::Vector{MOI.VariableIndex}) = arg
29+
30+
lift_variable_indices(arg) = arg # Recursion fallback.
3631

3732
function extract_function_and_set(expr::Expr)
3833
if expr.head == :call # One-sided constraint or foo-in-set.
@@ -71,7 +66,7 @@ function write_nlpblock(object::Object, model::Model,
7166
variables = MOI.get(model, MOI.ListOfVariableIndices())
7267
if nlp_block.has_objective
7368
objective = MOI.objective_expr(nlp_block.evaluator)
74-
objective = substitute_variables(objective, variables)
69+
objective = lift_variable_indices(objective)
7570
sense = MOI.get(model, MOI.ObjectiveSense())
7671
object["objective"] = Object(
7772
"sense" => moi_to_object(sense),
@@ -81,7 +76,7 @@ function write_nlpblock(object::Object, model::Model,
8176
for (row, bounds) in enumerate(nlp_block.constraint_bounds)
8277
constraint = MOI.constraint_expr(nlp_block.evaluator, row)
8378
(func, set) = extract_function_and_set(constraint)
84-
func = substitute_variables(func, variables)
79+
func = lift_variable_indices(func)
8580
push!(object["constraints"],
8681
Object("function" => moi_to_object(Nonlinear(func), model, name_map),
8782
"set" => moi_to_object(set, model, name_map))
@@ -293,7 +288,7 @@ function convert_mof_to_expr(node::Object, node_list::Vector{Object},
293288
end
294289

295290
"""
296-
convert_mof_to_expr(node::Object, node_list::Vector{Object},
291+
convert_expr_to_mof(node::Object, node_list::Vector{Object},
297292
name_map::Dict{MOI.VariableIndex, String})
298293
299294
Convert a Julia expression into a MathOptFormat representation. Any intermediate

src/FileFormats/MOF/write.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ end
3838
function write_objective(
3939
object::Object, model::Model, name_map::Dict{MOI.VariableIndex, String}
4040
)
41+
if object["objective"]["sense"] != "feasibility"
42+
return # Objective must have been written from NLPBlock.
43+
end
4144
sense = MOI.get(model, MOI.ObjectiveSense())
4245
object["objective"] = Object("sense" => moi_to_object(sense))
4346
if sense != MOI.FEASIBILITY_SENSE

test/FileFormats/MOF/nlp.mof.json

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,60 @@
2121
"objective": {
2222
"sense": "min",
2323
"function": {
24-
"head": "ScalarAffineFunction",
25-
"terms": [],
26-
"constant": 0.0
24+
"head": "ScalarNonlinearFunction",
25+
"root": {
26+
"head": "node",
27+
"index": 3
28+
},
29+
"node_list": [
30+
{
31+
"head": "+",
32+
"args": [
33+
{
34+
"head": "variable",
35+
"name": "var_1"
36+
},
37+
{
38+
"head": "variable",
39+
"name": "var_2"
40+
},
41+
{
42+
"head": "variable",
43+
"name": "var_3"
44+
}
45+
]
46+
},
47+
{
48+
"head": "*",
49+
"args": [
50+
{
51+
"head": "variable",
52+
"name": "var_1"
53+
},
54+
{
55+
"head": "variable",
56+
"name": "var_4"
57+
},
58+
{
59+
"head": "node",
60+
"index": 1
61+
}
62+
]
63+
},
64+
{
65+
"head": "+",
66+
"args": [
67+
{
68+
"head": "node",
69+
"index": 2
70+
},
71+
{
72+
"head": "variable",
73+
"name": "var_3"
74+
}
75+
]
76+
}
77+
]
2778
}
2879
},
2980
"constraints": [

test/FileFormats/MOF/nonlinear.jl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
function roundtrip_nonlinear_expression(expr, variable_to_string,
2-
string_to_variable)
1+
function roundtrip_nonlinear_expression(
2+
expr, variable_to_string, string_to_variable
3+
)
34
node_list = MOF.Object[]
4-
object = MOF.convert_expr_to_mof(expr, node_list,
5-
variable_to_string)
6-
@test MOF.convert_mof_to_expr(object, node_list,
7-
string_to_variable) == expr
5+
object = MOF.convert_expr_to_mof(expr, node_list, variable_to_string)
6+
@test MOF.convert_mof_to_expr(object, node_list, string_to_variable) == expr
87
end
98

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

24-
function HS071()
23+
function HS071(x::Vector{MOI.VariableIndex})
24+
x1, x2, x3, x4 = x
2525
return MOI.NLPBlockData(
2626
MOI.NLPBoundsPair.([25, 40], [Inf, 40]),
27-
ExprEvaluator(:(x[1] * x[4] * (x[1] + x[2] + x[3]) + x[3]),
28-
[:(x[1] * x[2] * x[3] * x[4] >= 25),
29-
:(x[1]^2 + x[2]^2 + x[3]^2 + x[4]^2 == 40)]),
30-
true)
27+
ExprEvaluator(
28+
:(x[$x1] * x[$x4] * (x[$x1] + x[$x2] + x[$x3]) + x[$x3]),
29+
[
30+
:(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25),
31+
:(x[$x1]^2 + x[$x2]^2 + x[$x3]^2 + x[$x4]^2 == 40)
32+
]
33+
),
34+
true
35+
)
3136
end
3237

3338
@testset "Nonlinear functions" begin
@@ -39,7 +44,7 @@ end
3944
end
4045
MOI.add_constraints(model, MOI.SingleVariable.(x),
4146
Ref(MOI.Interval(1.0, 5.0)))
42-
MOI.set(model, MOI.NLPBlock(), HS071())
47+
MOI.set(model, MOI.NLPBlock(), HS071(x))
4348
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
4449
MOI.write_to_file(model, TEST_MOF_FILE)
4550
@test replace(read(TEST_MOF_FILE, String), '\r' => "") ==

0 commit comments

Comments
 (0)