Skip to content

Commit c8d89a4

Browse files
committed
Add support for MOI.VectorNonlinearOracle
1 parent 88c6e68 commit c8d89a4

File tree

3 files changed

+115
-1
lines changed

3 files changed

+115
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ List of supported constraint types:
185185
* [`MOI.VectorAffineFunction{Float64}`](@ref) in [`MOI.SecondOrderCone`](@ref)
186186
* [`MOI.VectorOfVariables`](@ref) in [`MOI.Complements`](@ref)
187187
* [`MOI.VectorOfVariables`](@ref) in [`MOI.SecondOrderCone`](@ref)
188+
* [`MOI.VectorOfVariables`](@ref) in [`MOI.VectorNonlinearOracle{Float64}`](@ref)
188189

189190
List of supported model attributes:
190191

ext/KNITROMathOptInterfaceExt.jl

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
103103
}
104104
# Constraint mappings.
105105
constraint_mapping::Dict{MOI.ConstraintIndex,Union{Cint,Vector{Cint}}}
106+
vector_nonlinear_oracle_constraints::Vector{
107+
Tuple{MOI.VectorOfVariables,MOI.VectorNonlinearOracle{Float64}},
108+
}
106109
license_manager::Union{KNITRO.LMcontext,Nothing}
107110
options::Dict{String,Any}
108111
# Cache for the solution
@@ -129,6 +132,7 @@ function Optimizer(; license_manager::Union{KNITRO.LMcontext,Nothing}=nothing, k
129132
MOI.FEASIBILITY_SENSE,
130133
nothing,
131134
Dict{MOI.ConstraintIndex,Union{Cint,Vector{Cint}}}(),
135+
Tuple{MOI.VectorOfVariables,MOI.VectorNonlinearOracle{Float64}}[],
132136
license_manager,
133137
Dict{String,Any}(),
134138
Float64[],
@@ -167,6 +171,7 @@ function MOI.empty!(model::Optimizer)
167171
model.sense = MOI.FEASIBILITY_SENSE
168172
model.objective = nothing
169173
model.constraint_mapping = Dict()
174+
empty!(model.vector_nonlinear_oracle_constraints)
170175
model.license_manager = model.license_manager
171176
for (name, value) in model.options
172177
MOI.set(model, MOI.RawOptimizerAttribute(name), value)
@@ -184,6 +189,7 @@ function MOI.is_empty(model::Optimizer)
184189
model.sense == MOI.FEASIBILITY_SENSE &&
185190
model.number_solved == 0 &&
186191
model.objective === nothing &&
192+
isempty(model.vector_nonlinear_oracle_constraints) &&
187193
!model.nlp_loaded
188194
end
189195

@@ -1012,6 +1018,105 @@ function MOI.add_constraint(
10121018
return ci
10131019
end
10141020

1021+
# MOI.VectorOfVariables-in-MOI.VectorNonlinearOracle
1022+
1023+
function MOI.supports_constraint(
1024+
::Optimizer,
1025+
::Type{MOI.VectorOfVariables},
1026+
::Type{MOI.VectorNonlinearOracle{Float64}},
1027+
)
1028+
return true
1029+
end
1030+
1031+
function MOI.add_constraint(
1032+
model::Optimizer,
1033+
f::MOI.VectorOfVariables,
1034+
s::MOI.VectorNonlinearOracle{Float64},
1035+
)
1036+
_throw_if_solved(model, f, s)
1037+
p = Ref{Cint}(0)
1038+
KNITRO.@_checked KNITRO.KN_get_number_cons(model, p)
1039+
offset = p[]
1040+
rows = zeros(Cint, s.output_dimension)
1041+
KNITRO.@_checked KNITRO.KN_add_cons(model, s.output_dimension, rows)
1042+
for (r, l, u) in zip(rows, s.l, s.u)
1043+
KNITRO.@_checked KNITRO.KN_set_con_upbnd(model, r, _clamp_inf(l))
1044+
KNITRO.@_checked KNITRO.KN_set_con_lobnd(model, r, _clamp_inf(u))
1045+
end
1046+
KNITRO.@_checked KNITRO.KN_get_number_cons(model, p)
1047+
num_cons = p[]
1048+
tmp_x = zeros(Cdouble, s.input_dimension)
1049+
function eval_f_cb(::Any, ::Any, evalRequest, evalResult, ::Any)
1050+
for i in 1:s.input_dimension
1051+
tmp_x[i] = evalRequest.x[f.variables[i].value]
1052+
end
1053+
s.eval_f(evalResult.c, tmp_x)
1054+
return 0
1055+
end
1056+
function eval_grad_cb(::Any, ::Any, evalRequest, evalResult, ::Any)
1057+
for i in 1:s.input_dimension
1058+
tmp_x[i] = evalRequest.x[f.variables[i].value]
1059+
end
1060+
s.eval_jacobian(evalResult.jac, tmp_x)
1061+
return 0
1062+
end
1063+
cb = KNITRO.KN_add_eval_callback(model.inner, false, rows, eval_f_cb)
1064+
KNITRO.@_checked KNITRO.KN_set_cb_grad(
1065+
model.inner,
1066+
cb,
1067+
eval_grad_cb;
1068+
nV=Cint(0),
1069+
jacIndexCons=Cint[i - 1 + offset for (i, _) in s.jacobian_structure],
1070+
jacIndexVars=Cint[f.variables[j].value - 1 for (_, j) in s.jacobian_structure],
1071+
)
1072+
if s.eval_hessian_lagrangian !== nothing
1073+
function eval_h_cb(::Any, ::Any, evalRequest, evalResult, ::Any)
1074+
for i in 1:s.input_dimension
1075+
tmp_x[i] = evalRequest.x[f.variables[i].value]
1076+
end
1077+
@show evalRequest.lambda
1078+
lambda = view(evalRequest.lambda, (offset+1):num_cons)
1079+
s.eval_hessian_lagrangian(evalResult.hess, tmp_x, lambda)
1080+
return 0
1081+
end
1082+
I = Cint[f.variables[i].value - 1 for (i, _) in s.hessian_lagrangian_structure]
1083+
J = Cint[f.variables[j].value - 1 for (_, j) in s.hessian_lagrangian_structure]
1084+
KNITRO.@_checked KNITRO.KN_set_cb_hess(
1085+
model.inner,
1086+
cb,
1087+
length(s.hessian_lagrangian_structure),
1088+
eval_h_cb;
1089+
hessIndexVars1=I,
1090+
hessIndexVars2=J,
1091+
)
1092+
end
1093+
push!(model.vector_nonlinear_oracle_constraints, (f, s))
1094+
ci = MOI.ConstraintIndex{typeof(f),typeof(s)}(
1095+
length(model.vector_nonlinear_oracle_constraints),
1096+
)
1097+
model.constraint_mapping[ci] = rows
1098+
return ci
1099+
end
1100+
1101+
function MOI.get(
1102+
model::Optimizer,
1103+
attr::MOI.ConstraintDual,
1104+
ci::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.VectorNonlinearOracle{Float64}},
1105+
)
1106+
MOI.check_result_index_bounds(model, attr)
1107+
MOI.throw_if_not_valid(model, ci)
1108+
f, s = model.vector_nonlinear_oracle_constraints[ci.value]
1109+
J = zeros(length(s.jacobian_structure))
1110+
x = [MOI.get(model, MOI.VariablePrimal(), xi) for xi in f.variables]
1111+
s.eval_jacobian(J, x)
1112+
λ = [_sense_dual(model) * _get_dual(model, r + 1) for r in model.constraint_mapping[ci]]
1113+
dual = zeros(MOI.dimension(s))
1114+
for ((row, col), J_rc) in zip(s.jacobian_structure, J)
1115+
dual[col] += λ[row] * J_rc
1116+
end
1117+
return dual
1118+
end
1119+
10151120
# MOI.NLPBlock
10161121

10171122
MOI.supports(::Optimizer, ::MOI.NLPBlock) = true

test/MOI_wrapper.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ function test_runtests()
3030
infeasible_status=MOI.LOCALLY_INFEASIBLE,
3131
exclude=Any[MOI.VariableBasisStatus, MOI.ConstraintBasisStatus, MOI.ConstraintName],
3232
)
33-
MOI.Test.runtests(model, config; include=["test_basic_"])
33+
MOI.Test.runtests(
34+
model,
35+
config;
36+
include=["test_basic_"],
37+
# Upstream bug because @odow is a muppet
38+
exclude=["test_basic_VectorOfVariables_VectorNonlinearOracle"],
39+
)
3440
return
3541
end
3642

@@ -65,6 +71,8 @@ function test_MOI_Test_cached()
6571
model,
6672
config;
6773
exclude=Union{String,Regex}[
74+
# Upstream bug because @odow is a muppet
75+
r"^test_basic_VectorOfVariables_VectorNonlinearOracle$",
6876
# This is an upstream issue in MOI with bridges and support
6977
# comparing VectorNonlinear and VectorQuadratic
7078
r"^test_basic_VectorNonlinearFunction_GeometricMeanCone$",

0 commit comments

Comments
 (0)