Skip to content

Commit b0d32ab

Browse files
committed
Add support for MOI.ScalarNonlinearFunction
1 parent 0e0cc7d commit b0d32ab

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed

src/MOI_wrapper/constraints.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ function MOI.supports(
1111
F<:Union{
1212
MOI.ScalarAffineFunction{Float64},
1313
MOI.ScalarQuadraticFunction{Float64},
14+
MOI.ScalarNonlinearFunction,
1415
MOI.VectorAffineFunction{Float64},
1516
MOI.VectorOfVariables,
1617
},
@@ -27,6 +28,7 @@ function MOI.get(
2728
F<:Union{
2829
MOI.ScalarAffineFunction{Float64},
2930
MOI.ScalarQuadraticFunction{Float64},
31+
MOI.ScalarNonlinearFunction,
3032
MOI.VectorAffineFunction{Float64},
3133
MOI.VectorOfVariables,
3234
},
@@ -43,6 +45,7 @@ function MOI.set(
4345
F<:Union{
4446
MOI.ScalarAffineFunction{Float64},
4547
MOI.ScalarQuadraticFunction{Float64},
48+
MOI.ScalarNonlinearFunction,
4649
MOI.VectorAffineFunction{Float64},
4750
MOI.VectorOfVariables,
4851
},

src/MOI_wrapper/nonlinear_constraints.jl

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,218 @@ function MOI.set(o::Optimizer, attr::MOI.NLPBlock, data::MOI.NLPBlockData)
2323
end
2424
return nothing
2525
end
26+
27+
# MOI.ScalarNonlinearFunction
28+
29+
function MOI.supports_constraint(
30+
::Optimizer,
31+
::Type{MOI.ScalarNonlinearFunction},
32+
::Type{S},
33+
) where {S<:BOUNDS}
34+
return true
35+
end
36+
37+
function MOI.add_constraint(
38+
model::Optimizer,
39+
f::MOI.ScalarNonlinearFunction,
40+
s::BOUNDS,
41+
)
42+
allow_modification(model)
43+
expr = NonlinExpr()
44+
root = _SCIPcreateExpr(model, f, expr)
45+
l, u = bounds(model, s)
46+
cons__ = Ref{Ptr{SCIP_CONS}}(C_NULL)
47+
@SCIP_CALL SCIPcreateConsBasicNonlinear(model, cons__, "", root[], l, u)
48+
@SCIP_CALL SCIPaddCons(model, cons__[])
49+
push!(model.inner.nonlinear_storage, expr)
50+
cr = store_cons!(model.inner, cons__)
51+
ci = MOI.ConstraintIndex{MOI.ScalarNonlinearFunction,typeof(s)}(cr.val)
52+
register!(model, ci)
53+
register!(model, cons(model, ci), cr)
54+
return ci
55+
end
56+
57+
function _SCIPcreateExpr(model::Optimizer, f::Real, expr::NonlinExpr)
58+
ret = Ref{Ptr{SCIP_EXPR}}(C_NULL)
59+
@SCIP_CALL SCIPcreateExprValue(model, ret, f, C_NULL, C_NULL)
60+
push!(expr.exprs, ret)
61+
return ret
62+
end
63+
64+
function _SCIPcreateExpr(
65+
model::Optimizer,
66+
f::MOI.VariableIndex,
67+
expr::NonlinExpr,
68+
)
69+
ret = Ref{Ptr{SCIP_EXPR}}(C_NULL)
70+
v = var(model, f)
71+
@SCIP_CALL SCIPcreateExprVar(model, ret, v, C_NULL, C_NULL)
72+
push!(expr.exprs, ret)
73+
return ret
74+
end
75+
76+
function _SCIPcreateExpr(
77+
model::Optimizer,
78+
f::MOI.ScalarNonlinearFunction,
79+
expr::NonlinExpr,
80+
)
81+
ret = Ref{Ptr{SCIP_EXPR}}(C_NULL)
82+
if f.head == :+
83+
children = map(arg -> _SCIPcreateExpr(model, arg, expr)[], f.args)
84+
@SCIP_CALL SCIPcreateExprSum(
85+
model,
86+
ret,
87+
length(f.args), # int nchildren
88+
children, # SCIP_EXPR **children,
89+
ones(Float64, length(f.args)), # SCIP_REAL *coefficients
90+
0.0, # SCIP_Real constant
91+
C_NULL,
92+
C_NULL,
93+
)
94+
elseif f.head == :*
95+
x = map(arg -> _SCIPcreateExpr(model, arg, expr)[], f.args)
96+
n = length(f.args)
97+
@SCIP_CALL SCIPcreateExprProduct(model, ret, n, x, 1.0, C_NULL, C_NULL)
98+
elseif f.head == :/
99+
# Convert x / y --> x * y^-1
100+
@assert length(f.args) == 2
101+
xy = map(f.args) do arg
102+
return _SCIPcreateExpr(model, arg, expr)[]
103+
end
104+
ret_y = Ref{Ptr{SCIP_EXPR}}(C_NULL)
105+
@SCIP_CALL SCIPcreateExprPow(model, ret_y, xy[2], -1, C_NULL, C_NULL)
106+
@SCIP_CALL SCIPcreateExprProduct(model, ret, 2, xy, 1.0, C_NULL, C_NULL)
107+
elseif f.head == :-
108+
@assert 1 <= length(f.args) <= 2
109+
children = map(f.args) do arg
110+
return _SCIPcreateExpr(model, arg, expr)[]
111+
end
112+
n = length(f.args)
113+
coefficients = ones(Float64, n)
114+
coefficients[end] = -1.0
115+
@SCIP_CALL SCIPcreateExprSum(
116+
model,
117+
ret,
118+
n,
119+
children,
120+
coefficients,
121+
0.0,
122+
C_NULL,
123+
C_NULL,
124+
)
125+
elseif f.head == :^
126+
@assert length(f.args) == 2
127+
if !(f.args[2] isa Real)
128+
throw(MOI.UnsupportedNonlinearOperator(f.head))
129+
end
130+
child = _SCIPcreateExpr(model, first(f.args), expr)
131+
expon = convert(Float64, f.args[2])
132+
@SCIP_CALL SCIPcreateExprPow(model, ret, child[], expon, C_NULL, C_NULL)
133+
elseif f.head == :abs
134+
child = _SCIPcreateExpr(model, only(f.args), expr)
135+
@SCIP_CALL SCIPcreateExprAbs(model, ret, child[], C_NULL, C_NULL)
136+
elseif f.head == :exp
137+
child = _SCIPcreateExpr(model, only(f.args), expr)
138+
@SCIP_CALL SCIPcreateExprExp(model, ret, child[], C_NULL, C_NULL)
139+
elseif f.head == :log
140+
child = _SCIPcreateExpr(model, only(f.args), expr)
141+
@SCIP_CALL SCIPcreateExprLog(model, ret, child[], C_NULL, C_NULL)
142+
elseif f.head == :sin
143+
child = _SCIPcreateExpr(model, only(f.args), expr)
144+
@SCIP_CALL SCIPcreateExprSin(model, ret, child[], C_NULL, C_NULL)
145+
elseif f.head == :cos
146+
child = _SCIPcreateExpr(model, only(f.args), expr)
147+
@SCIP_CALL SCIPcreateExprCos(model, ret, child[], C_NULL, C_NULL)
148+
elseif f.head == :sqrt
149+
child = _SCIPcreateExpr(model, only(f.args), expr)
150+
@SCIP_CALL SCIPcreateExprPow(model, ret, child[], 0.5, C_NULL, C_NULL)
151+
else
152+
throw(MOI.UnsupportedNonlinearOperator(f.head))
153+
end
154+
push!(expr.exprs, ret)
155+
return ret
156+
end
157+
158+
function _SCIPcreateExpr(
159+
model::Optimizer,
160+
f::MOI.ScalarAffineFunction,
161+
expr::NonlinExpr,
162+
)
163+
ret = Ref{Ptr{SCIP_EXPR}}(C_NULL)
164+
n = length(f.terms)
165+
children = map(f.terms) do term
166+
return _SCIPcreateExpr(model, term.variable, expr)[]
167+
end
168+
coefficients = map(f.terms) do term
169+
return convert(Float64, term.coefficient)
170+
end
171+
@SCIP_CALL SCIPcreateExprSum(
172+
model,
173+
ret,
174+
n,
175+
children,
176+
coefficients,
177+
f.constant,
178+
C_NULL,
179+
C_NULL,
180+
)
181+
push!(expr.exprs, ret)
182+
return ret
183+
end
184+
185+
function _SCIPcreateExpr(
186+
model::Optimizer,
187+
f::MOI.ScalarQuadraticFunction,
188+
expr::NonlinExpr,
189+
)
190+
ret = Ref{Ptr{SCIP_EXPR}}(C_NULL)
191+
children = map(f.affine_terms) do term
192+
return _SCIPcreateExpr(model, term.variable, expr)[]
193+
end
194+
coefficients = map(f.affine_terms) do term
195+
return convert(Float64, term.coefficient)
196+
end
197+
for term in f.quadratic_terms
198+
ret_xy = Ref{Ptr{SCIP_EXPR}}(C_NULL)
199+
x = _SCIPcreateExpr(model, term.variable_1, expr)
200+
y = _SCIPcreateExpr(model, term.variable_2, expr)
201+
scale = ifelse(term.variable_1 == term.variable_2, 0.5, 1.0)
202+
@SCIP_CALL SCIPcreateExprProduct(
203+
model,
204+
ret_xy,
205+
2,
206+
[x[], y[]],
207+
1.0,
208+
C_NULL,
209+
C_NULL,
210+
)
211+
push!(children, ret_xy[])
212+
push!(expr.exprs, ret_xy)
213+
push!(coefficients, scale * term.coefficient)
214+
end
215+
@SCIP_CALL SCIPcreateExprSum(
216+
model,
217+
ret,
218+
length(children),
219+
children,
220+
coefficients,
221+
f.constant,
222+
C_NULL,
223+
C_NULL,
224+
)
225+
push!(expr.exprs, ret)
226+
return ret
227+
end
228+
229+
function MOI.get(
230+
o::Optimizer,
231+
::MOI.ConstraintPrimal,
232+
ci::MOI.ConstraintIndex{MOI.ScalarNonlinearFunction},
233+
)
234+
_throw_if_invalid(o, ci)
235+
c = cons(o, ci)
236+
expr_ref = SCIPgetExprNonlinear(c)
237+
sol = SCIPgetBestSol(o)
238+
@SCIP_CALL SCIPevalExpr(o, expr_ref, sol, Clonglong(0))
239+
return SCIPexprGetEvalValue(expr_ref)
240+
end

test/MINLPTests/run_minlptests.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ MINLPTests.test_directory(
3333
],
3434
)
3535

36+
MINLPTests.test_directory(
37+
"nlp-expr",
38+
JuMP.optimizer_with_attributes(SCIP.Optimizer, "display/verblevel" => 0);
39+
objective_tol=1e-5,
40+
primal_tol=1e-5,
41+
dual_tol=NaN,
42+
termination_target=MINLPTests.TERMINATION_TARGET_GLOBAL,
43+
primal_target=MINLPTests.PRIMAL_TARGET_GLOBAL,
44+
# User-defined function
45+
exclude=["006_010"],
46+
)
47+
3648
MINLPTests.test_directory(
3749
"nlp-cvx",
3850
JuMP.optimizer_with_attributes(SCIP.Optimizer, "display/verblevel" => 0);
@@ -52,6 +64,16 @@ MINLPTests.test_directory(
5264
],
5365
)
5466

67+
MINLPTests.test_directory(
68+
"nlp-cvx-expr",
69+
JuMP.optimizer_with_attributes(SCIP.Optimizer, "display/verblevel" => 0);
70+
objective_tol=1e-4,
71+
primal_tol=1e-3,
72+
dual_tol=NaN,
73+
termination_target=MINLPTests.TERMINATION_TARGET_GLOBAL,
74+
primal_target=MINLPTests.PRIMAL_TARGET_GLOBAL,
75+
)
76+
5577
MINLPTests.test_directory(
5678
"nlp-mi",
5779
JuMP.optimizer_with_attributes(SCIP.Optimizer, "display/verblevel" => 0);
@@ -73,3 +95,15 @@ MINLPTests.test_directory(
7395
"006_010",
7496
],
7597
)
98+
99+
MINLPTests.test_directory(
100+
"nlp-mi-expr",
101+
JuMP.optimizer_with_attributes(SCIP.Optimizer, "display/verblevel" => 0);
102+
objective_tol=1e-4,
103+
primal_tol=1e-3,
104+
dual_tol=NaN,
105+
termination_target=MINLPTests.TERMINATION_TARGET_GLOBAL,
106+
primal_target=MINLPTests.PRIMAL_TARGET_GLOBAL,
107+
# User-defined function
108+
exclude=["006_010"],
109+
)

0 commit comments

Comments
 (0)