Skip to content

Commit e3969ed

Browse files
frapacblegat
andauthored
Add support for nonlinear expressions in MOI.Complements (#8)
* add support for nonlinear expressions in MOI.Complements * operate! --------- Co-authored-by: Benoît Legat <benoit.legat@gmail.com>
1 parent 85354dd commit e3969ed

2 files changed

Lines changed: 50 additions & 21 deletions

File tree

src/vertical.jl

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const VF = Union{
33
MOI.VectorAffineFunction,
44
MOI.VectorQuadraticFunction,
5+
MOI.VectorNonlinearFunction,
56
}
67

78
function has_complementarity(model::MOI.ModelLike)
@@ -29,28 +30,13 @@ function _is_single_variable(func::MOI.ScalarQuadraticFunction)
2930
false
3031
end
3132
end
33+
function _is_single_variable(func::MOI.ScalarNonlinearFunction)
34+
return func.head == :+ && length(func.args) == 1 && isa(func.args[1], MOI.VariableIndex)
35+
end
3236
_get_variable(func::MOI.ScalarAffineFunction) = func.terms[1].variable
3337
_get_variable(func::MOI.ScalarQuadraticFunction) = func.affine_terms[1].variable
38+
_get_variable(func::MOI.ScalarNonlinearFunction) = func.args[1]
3439

35-
# Reformulate
36-
function _vertical_formulation!(model, terms::Vector{MOI.ScalarAffineTerm{T}}, c::T) where T
37-
x = MOI.add_variable(model)
38-
push!(terms, MOI.ScalarAffineTerm{T}(-one(T), x))
39-
func = MOI.ScalarAffineFunction{T}(terms, zero(T))
40-
MOI.add_constraint(model, func, MOI.EqualTo{T}(-c))
41-
return x
42-
end
43-
44-
function _add_slack!(model, func::MOI.ScalarAffineFunction{T}) where T
45-
x = MOI.add_variable(model)
46-
push!(func.terms, MOI.ScalarAffineTerm{T}(-one(T), x))
47-
return x
48-
end
49-
function _add_slack!(model, func::MOI.ScalarQuadraticFunction{T}) where T
50-
x = MOI.add_variable(model)
51-
push!(func.affine_terms, MOI.ScalarAffineTerm{T}(-one(T), x))
52-
return x
53-
end
5440

5541
# TODO: add support for ScalarNonlinearTerm
5642
function _parse_complementarity_constraint(fun::MOI.AbstractVectorFunction, n_comp)
@@ -129,8 +115,9 @@ function reformulate_to_vertical!(model::MOI.ModelLike)
129115
push!(ind_cc2, x2)
130116
else
131117
# Else, reformulate LHS using vertical form
132-
x1 = _add_slack!(model, lhs)
133-
MOI.add_constraint(model, lhs, MOI.EqualTo{Float64}(0))
118+
x1 = MOI.add_variable(model)
119+
new_lhs = MOIU.operate!(-, Float64, lhs, x1)
120+
MOI.add_constraint(model, new_lhs, MOI.EqualTo{Float64}(0))
134121
push!(ind_cc1, x1)
135122
push!(ind_cc2, x2)
136123
end

test/runtests.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ function fletcher_leyffer_ex1_nonlinear_model()
2121
return model
2222
end
2323

24+
function nonlinear_test_model()
25+
model = Model()
26+
@variable(model, x >= 0.0)
27+
@variable(model, y >= 0.0)
28+
@objective(model, Min, x^2 + y^2 - 4*x*y)
29+
# Build complementarity constraints with nonlinear expression
30+
@constraint(model, [sin(x), y] MOI.Complements(2))
31+
return model
32+
end
33+
34+
function nonlinear_test_reformulated_model()
35+
model = Model()
36+
@variable(model, x >= 0.0)
37+
@variable(model, y >= 0.0)
38+
@variable(model, slack)
39+
@objective(model, Min, x^2 + y^2 - 4*x*y)
40+
# Build complementarity constraints with nonlinear expression
41+
@constraint(model, sin(x) == slack)
42+
@constraint(model, slack * y <= 0.0)
43+
return model
44+
end
45+
2446
expected_models = Dict(
2547
Instances.fletcher_leyffer_ex1_model => fletcher_leyffer_ex1_nonlinear_model,
2648
)
@@ -41,6 +63,20 @@ function test_model(model_func)
4163
end
4264
end
4365

66+
function test_nonlinear_expr()
67+
model = nonlinear_test_model()
68+
inner = MOI.Utilities.Model{Float64}()
69+
set_optimizer(model, () -> ComplementOpt.Optimizer(inner))
70+
MOI.Utilities.attach_optimizer(model)
71+
MOI.Utilities.attach_optimizer(backend(model).optimizer.model)
72+
73+
expected = nonlinear_test_reformulated_model()
74+
MOI.Bridges._test_structural_identical(
75+
unsafe_backend(model).optimizer,
76+
backend(expected),
77+
)
78+
end
79+
4480
instances = filter(names(Instances; all = true)) do name
4581
# The function types start with `#`
4682
s = String(name)
@@ -50,3 +86,9 @@ end
5086
@testset "$name" for name in instances
5187
test_model(getfield(Instances, name))
5288
end
89+
90+
@testset "Unit-tests" begin
91+
test_nonlinear_expr()
92+
end
93+
94+

0 commit comments

Comments
 (0)