Skip to content

Commit 17153bc

Browse files
authored
Add support for MOI.VectorNonlinearOracle (#506)
1 parent 480c9ec commit 17153bc

File tree

4 files changed

+36
-178
lines changed

4 files changed

+36
-178
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ IpoptMathOptInterfaceExt = "MathOptInterface"
1919
HSL_jll = "3, 4, 2023, 2024, 2025"
2020
Ipopt_jll = "=300.1400.1900"
2121
LinearAlgebra = "1"
22-
MathOptInterface = "1.25"
22+
MathOptInterface = "1.46"
2323
OpenBLAS32_jll = "0.3.10"
2424
PrecompileTools = "1"
2525
Test = "1"
2626
julia = "1.9"
2727

2828
[extras]
2929
HSL_jll = "017b0a0e-03f4-516a-9b91-836bbd1904dd"
30+
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
3031
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3132

3233
[targets]

ext/IpoptMathOptInterfaceExt/IpoptMathOptInterfaceExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import PrecompileTools
1212
function __init__()
1313
setglobal!(Ipopt, :Optimizer, Optimizer)
1414
setglobal!(Ipopt, :CallbackFunction, CallbackFunction)
15-
setglobal!(Ipopt, :_VectorNonlinearOracle, _VectorNonlinearOracle)
15+
setglobal!(Ipopt, :_VectorNonlinearOracle, MOI.VectorNonlinearOracle)
1616
return
1717
end
1818

ext/IpoptMathOptInterfaceExt/MOI_wrapper.jl

Lines changed: 17 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -15,169 +15,16 @@ function _is_parameter(term::MOI.ScalarQuadraticTerm)
1515
return _is_parameter(term.variable_1) || _is_parameter(term.variable_2)
1616
end
1717

18-
"""
19-
_VectorNonlinearOracle(;
20-
dimension::Int,
21-
l::Vector{Float64},
22-
u::Vector{Float64},
23-
eval_f::Function,
24-
jacobian_structure::Vector{Tuple{Int,Int}},
25-
eval_jacobian::Function,
26-
hessian_lagrangian_structure::Vector{Tuple{Int,Int}} = Tuple{Int,Int}[],
27-
eval_hessian_lagrangian::Union{Nothing,Function} = nothing,
28-
) <: MOI.AbstractVectorSet
29-
30-
The set:
31-
```math
32-
S = \\{x \\in \\mathbb{R}^{dimension}: l \\le f(x) \\le u\\}
33-
```
34-
where ``f`` is defined by the vectors `l` and `u`, and the callback oracles
35-
`eval_f`, `eval_jacobian`, and `eval_hessian_lagrangian`.
36-
37-
!!! warning
38-
This set is experimental. We will decide by September 30, 2025, whether to
39-
convert this into the public `Ipopt.VectorNonlinearOracle`, move it to
40-
`MOI.VectorNonlinearOracle`, or remove it completely.
41-
42-
## f
43-
44-
The `eval_f` function must have the signature
45-
```julia
46-
eval_f(ret::AbstractVector, x::AbstractVector)::Nothing
47-
```
48-
which fills ``f(x)`` into the dense vector `ret`.
49-
50-
## Jacobian
51-
52-
The `eval_jacobian` function must have the signature
53-
```julia
54-
eval_jacobian(ret::AbstractVector, x::AbstractVector)::Nothing
55-
```
56-
which fills the sparse Jacobian ``\\nabla f(x)`` into `ret`.
57-
58-
The one-indexed sparsity structure must be provided in the `jacobian_structure`
59-
argument.
60-
61-
## Hessian
62-
63-
The `eval_hessian_lagrangian` function is optional.
64-
65-
If `eval_hessian_lagrangian === nothing`, Ipopt will use a Hessian approximation
66-
instead of the exact Hessian.
67-
68-
If `eval_hessian_lagrangian` is a function, it must have the signature
69-
```julia
70-
eval_hessian_lagrangian(
71-
ret::AbstractVector,
72-
x::AbstractVector,
73-
μ::AbstractVector,
74-
)::Nothing
75-
```
76-
which fills the sparse Hessian of the Lagrangian ``\\sum \\mu_i \\nabla^2 f_i(x)``
77-
into `ret`.
78-
79-
The one-indexed sparsity structure must be provided in the
80-
`hessian_lagrangian_structure` argument.
81-
82-
## Example
83-
84-
To model the set:
85-
```math
86-
\\begin{align}
87-
0 \\le & x^2 \\le 1
88-
0 \\le & y^2 + z^3 - w \\le 0
89-
\\end{align}
90-
```
91-
do
92-
```jldoctest
93-
julia> import Ipopt
94-
95-
julia> set = Ipopt._VectorNonlinearOracle(;
96-
dimension = 3,
97-
l = [0.0, 0.0],
98-
u = [1.0, 0.0],
99-
eval_f = (ret, x) -> begin
100-
ret[1] = x[2]^2
101-
ret[2] = x[3]^2 + x[4]^3 - x[1]
102-
return
103-
end,
104-
jacobian_structure = [(1, 2), (2, 1), (2, 3), (2, 4)],
105-
eval_jacobian = (ret, x) -> begin
106-
ret[1] = 2.0 * x[2]
107-
ret[2] = -1.0
108-
ret[3] = 2.0 * x[3]
109-
ret[4] = 3.0 * x[4]^2
110-
return
111-
end,
112-
hessian_lagrangian_structure = [(2, 2), (3, 3), (4, 4)],
113-
eval_hessian_lagrangian = (ret, x, u) -> begin
114-
ret[1] = 2.0 * u[1]
115-
ret[2] = 2.0 * u[2]
116-
ret[3] = 6.0 * x[4] * u[2]
117-
return
118-
end,
119-
);
120-
```
121-
"""
122-
struct _VectorNonlinearOracle <: MOI.AbstractVectorSet
123-
input_dimension::Int
124-
output_dimension::Int
125-
l::Vector{Float64}
126-
u::Vector{Float64}
127-
eval_f::Function
128-
jacobian_structure::Vector{Tuple{Int,Int}}
129-
eval_jacobian::Function
130-
hessian_lagrangian_structure::Vector{Tuple{Int,Int}}
131-
eval_hessian_lagrangian::Union{Nothing,Function}
132-
133-
function _VectorNonlinearOracle(;
134-
dimension::Int,
135-
l::Vector{Float64},
136-
u::Vector{Float64},
137-
eval_f::Function,
138-
jacobian_structure::Vector{Tuple{Int,Int}},
139-
eval_jacobian::Function,
140-
# The hessian_lagrangian is optional.
141-
hessian_lagrangian_structure::Vector{Tuple{Int,Int}} = Tuple{Int,Int}[],
142-
eval_hessian_lagrangian::Union{Nothing,Function} = nothing,
143-
)
144-
@assert length(l) == length(u)
145-
return new(
146-
dimension,
147-
length(l),
148-
l,
149-
u,
150-
eval_f,
151-
jacobian_structure,
152-
eval_jacobian,
153-
hessian_lagrangian_structure,
154-
eval_hessian_lagrangian,
155-
)
156-
end
157-
end
158-
159-
MOI.dimension(s::_VectorNonlinearOracle) = s.input_dimension
160-
161-
MOI.copy(s::_VectorNonlinearOracle) = s
162-
163-
function Base.show(io::IO, s::_VectorNonlinearOracle)
164-
println(io, "Ipopt._VectorNonlinearOracle(;")
165-
println(io, " dimension = ", s.input_dimension, ",")
166-
println(io, " l = ", s.l, ",")
167-
println(io, " u = ", s.u, ",")
168-
println(io, " ...,")
169-
print(io, ")")
170-
return
171-
end
172-
17318
mutable struct _VectorNonlinearOracleCache
174-
set::_VectorNonlinearOracle
19+
set::MOI.VectorNonlinearOracle{Float64}
17520
x::Vector{Float64}
17621
eval_f_timer::Float64
17722
eval_jacobian_timer::Float64
17823
eval_hessian_lagrangian_timer::Float64
17924

180-
function _VectorNonlinearOracleCache(set::_VectorNonlinearOracle)
25+
function _VectorNonlinearOracleCache(
26+
set::MOI.VectorNonlinearOracle{Float64},
27+
)
18128
return new(set, zeros(set.input_dimension), 0.0, 0.0, 0.0)
18229
end
18330
end
@@ -423,7 +270,7 @@ function MOI.get(model::Optimizer, attr::MOI.ListOfConstraintTypesPresent)
423270
append!(ret, MOI.get(model.qp_data, attr))
424271
_add_scalar_nonlinear_constraints(ret, model.nlp_model)
425272
if !isempty(model.vector_nonlinear_oracle_constraints)
426-
push!(ret, (MOI.VectorOfVariables, _VectorNonlinearOracle))
273+
push!(ret, (MOI.VectorOfVariables, MOI.VectorNonlinearOracle{Float64}))
427274
end
428275
return ret
429276
end
@@ -817,43 +664,46 @@ function MOI.set(
817664
return
818665
end
819666

820-
### MOI.VectorOfVariables in _VectorNonlinearOracle
667+
### MOI.VectorOfVariables in MOI.VectorNonlinearOracle{Float64}
821668

822669
function MOI.supports_constraint(
823670
::Optimizer,
824671
::Type{MOI.VectorOfVariables},
825-
::Type{_VectorNonlinearOracle},
672+
::Type{MOI.VectorNonlinearOracle{Float64}},
826673
)
827674
return true
828675
end
829676

830677
function MOI.is_valid(
831678
model::Optimizer,
832-
ci::MOI.ConstraintIndex{MOI.VectorOfVariables,_VectorNonlinearOracle},
679+
ci::MOI.ConstraintIndex{
680+
MOI.VectorOfVariables,
681+
MOI.VectorNonlinearOracle{Float64},
682+
},
833683
)
834684
return 1 <= ci.value <= length(model.vector_nonlinear_oracle_constraints)
835685
end
836686

837687
function MOI.get(
838688
model::Optimizer,
839689
attr::MOI.ListOfConstraintIndices{F,S},
840-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
690+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
841691
n = length(model.vector_nonlinear_oracle_constraints)
842692
return MOI.ConstraintIndex{F,S}.(1:n)
843693
end
844694

845695
function MOI.get(
846696
model::Optimizer,
847697
attr::MOI.NumberOfConstraints{F,S},
848-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
698+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
849699
return length(model.vector_nonlinear_oracle_constraints)
850700
end
851701

852702
function MOI.add_constraint(
853703
model::Optimizer,
854704
f::F,
855705
s::S,
856-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
706+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
857707
model.inner = nothing
858708
cache = _VectorNonlinearOracleCache(s)
859709
push!(model.vector_nonlinear_oracle_constraints, (f, cache))
@@ -864,7 +714,7 @@ end
864714
function row(
865715
model::Optimizer,
866716
ci::MOI.ConstraintIndex{F,S},
867-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
717+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
868718
offset = length(model.qp_data)
869719
for i in 1:(ci.value-1)
870720
_, s = model.vector_nonlinear_oracle_constraints[i]
@@ -878,7 +728,7 @@ function MOI.get(
878728
model::Optimizer,
879729
attr::MOI.ConstraintPrimal,
880730
ci::MOI.ConstraintIndex{F,S},
881-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
731+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
882732
MOI.check_result_index_bounds(model, attr)
883733
MOI.throw_if_not_valid(model, ci)
884734
f, _ = model.vector_nonlinear_oracle_constraints[ci.value]
@@ -889,7 +739,7 @@ function MOI.get(
889739
model::Optimizer,
890740
attr::MOI.ConstraintDual,
891741
ci::MOI.ConstraintIndex{F,S},
892-
) where {F<:MOI.VectorOfVariables,S<:_VectorNonlinearOracle}
742+
) where {F<:MOI.VectorOfVariables,S<:MOI.VectorNonlinearOracle{Float64}}
893743
MOI.check_result_index_bounds(model, attr)
894744
MOI.throw_if_not_valid(model, ci)
895745
sign = -_dual_multiplier(model)

test/MOI_wrapper.jl

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ function test_scalar_nonlinear_function_attributes()
853853
end
854854

855855
function test_vector_nonlinear_oracle()
856-
set = Ipopt._VectorNonlinearOracle(;
856+
set = MOI.VectorNonlinearOracle(;
857857
dimension = 5,
858858
l = [0.0, 0.0],
859859
u = [0.0, 0.0],
@@ -889,17 +889,18 @@ function test_vector_nonlinear_oracle()
889889
return
890890
end,
891891
)
892-
@test occursin("Ipopt._VectorNonlinearOracle(;", sprint(show, set))
892+
@test occursin("VectorNonlinearOracle{Float64}(;", sprint(show, set))
893893
@test MOI.dimension(set) == 5
894-
@test MOI.copy(set) === set
894+
@test MOI.copy(set) == set
895+
@test MOI.copy(set) !== set
895896
model = Ipopt.Optimizer()
896897
MOI.set(model, MOI.Silent(), true)
897898
x = MOI.add_variables(model, 3)
898899
MOI.add_constraints.(model, x, MOI.EqualTo.(1.0:3.0))
899900
y = MOI.add_variables(model, 2)
900901
MOI.optimize!(model)
901902
f = MOI.VectorOfVariables([x; y])
902-
F, S = MOI.VectorOfVariables, Ipopt._VectorNonlinearOracle
903+
F, S = MOI.VectorOfVariables, MOI.VectorNonlinearOracle{Float64}
903904
@test MOI.supports_constraint(model, F, S)
904905
@test !((F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()))
905906
@test isempty(MOI.get(model, MOI.ListOfConstraintIndices{F,S}()))
@@ -939,7 +940,7 @@ function test_vector_nonlinear_oracle()
939940
end
940941

941942
function test_vector_nonlinear_oracle_two()
942-
set = Ipopt._VectorNonlinearOracle(;
943+
set = MOI.VectorNonlinearOracle(;
943944
dimension = 5,
944945
l = [0.0, 0.0],
945946
u = [0.0, 0.0],
@@ -978,7 +979,7 @@ function test_vector_nonlinear_oracle_two()
978979
c_z = MOI.add_constraint(model, f_z, set)
979980
@test MOI.is_valid(model, c_y)
980981
@test MOI.is_valid(model, c_z)
981-
F, S = MOI.VectorOfVariables, Ipopt._VectorNonlinearOracle
982+
F, S = MOI.VectorOfVariables, MOI.VectorNonlinearOracle{Float64}
982983
@test (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent())
983984
@test MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) == [c_y, c_z]
984985
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 2
@@ -996,7 +997,7 @@ function test_vector_nonlinear_oracle_two()
996997
end
997998

998999
function test_vector_nonlinear_oracle_optimization()
999-
set = Ipopt._VectorNonlinearOracle(;
1000+
set = MOI.VectorNonlinearOracle(;
10001001
dimension = 4,
10011002
l = [0.0, 0.0],
10021003
u = [0.0, 0.0],
@@ -1057,7 +1058,7 @@ function test_vector_nonlinear_oracle_optimization()
10571058
end
10581059

10591060
function test_vector_nonlinear_oracle_optimization_min_sense()
1060-
set = Ipopt._VectorNonlinearOracle(;
1061+
set = MOI.VectorNonlinearOracle(;
10611062
dimension = 4,
10621063
l = [0.0, 0.0],
10631064
u = [0.0, 0.0],
@@ -1155,7 +1156,7 @@ function test_vector_nonlinear_oracle_scalar_nonlinear_equivalent()
11551156
end
11561157

11571158
function test_vector_nonlinear_oracle_no_hessian()
1158-
set = Ipopt._VectorNonlinearOracle(;
1159+
set = MOI.VectorNonlinearOracle(;
11591160
dimension = 5,
11601161
l = [0.0, 0.0],
11611162
u = [0.0, 0.0],
@@ -1233,6 +1234,12 @@ function test_issue_494()
12331234
return
12341235
end
12351236

1237+
function test__VectorNonlinearOracle()
1238+
# We want to test the constructor, not the type.
1239+
@test MOI.VectorNonlinearOracle === Ipopt._VectorNonlinearOracle
1240+
return
1241+
end
1242+
12361243
end # module TestMOIWrapper
12371244

12381245
TestMOIWrapper.runtests()

0 commit comments

Comments
 (0)