Skip to content

Commit 6645cc3

Browse files
committed
Add VectorNonlinearOracle set
1 parent 25c3358 commit 6645cc3

File tree

7 files changed

+348
-0
lines changed

7 files changed

+348
-0
lines changed

docs/src/manual/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ The vector-valued set types implemented in MathOptInterface.jl are:
8282
| [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` |
8383
| [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` |
8484
| [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` |
85+
| [`VectorNonlinearOracle`](@ref MathOptInterface.VectorNonlinearOracle)| ``\{x \in \mathbb{R}^{dimension}: l \le f(x) \le u \}`` |
8586

8687
## Matrix cones
8788

docs/src/reference/standard_form.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ ACTIVATE_ON_ONE
104104
Complements
105105
HyperRectangle
106106
Scaled
107+
VectorNonlinearOracle
107108
```
108109

109110
## Constraint programming sets

src/Test/test_basic_constraint.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,27 @@ function _set(::Type{T}, ::Type{MOI.HyperRectangle}) where {T}
175175
return MOI.HyperRectangle(zeros(T, 3), ones(T, 3))
176176
end
177177

178+
function _set(::Type{T}, ::Type{MOI.VectorNonlinearOracle}) where {T}
179+
return MOI.VectorNonlinearOracle(;
180+
dimension = 3,
181+
l = [0.0, 0.0],
182+
u = [1.0, 0.0],
183+
eval_f = (ret, x) -> begin
184+
ret[1] = x[2]^2
185+
ret[2] = x[3]^2 + x[4]^3 - x[1]
186+
return
187+
end,
188+
jacobian_structure = [(1, 2), (2, 1), (2, 3), (2, 4)],
189+
eval_jacobian = (ret, x) -> begin
190+
ret[1] = 2.0 * x[2]
191+
ret[2] = -1.0
192+
ret[3] = 2.0 * x[3]
193+
ret[4] = 3.0 * x[4]^2
194+
return
195+
end,
196+
)
197+
end
198+
178199
function _test_function_modification(
179200
model::MOI.ModelLike,
180201
config::Config{T},

src/Test/test_nonlinear.jl

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,3 +2223,141 @@ function setup_test(
22232223
end
22242224

22252225
version_added(::typeof(test_nonlinear_quadratic_4)) = v"1.35.0"
2226+
2227+
function test_vector_nonlinear_oracle(
2228+
model::MOI.ModelLike,
2229+
config::Config{T},
2230+
) where {T}
2231+
@requires _supports(config, MOI.optimize!)
2232+
@requires MOI.supports_constraint(
2233+
model,
2234+
MOI.VectorOfVariables,
2235+
MOI.VectorNonlinearOracle{T},
2236+
)
2237+
set = MOI.VectorNonlinearOracle(;
2238+
dimension = 5,
2239+
l = [0.0, 0.0],
2240+
u = [0.0, 0.0],
2241+
eval_f = (ret, x) -> begin
2242+
@test length(ret) == 2
2243+
@test length(x) == 5
2244+
ret[1] = x[1]^2 - x[4]
2245+
ret[2] = x[2]^2 + x[3]^3 - x[5]
2246+
return
2247+
end,
2248+
jacobian_structure = [(1, 1), (2, 2), (2, 3), (1, 4), (2, 5)],
2249+
eval_jacobian = (ret, x) -> begin
2250+
@test length(ret) == 5
2251+
@test length(x) == 5
2252+
ret[1] = 2 * x[1]
2253+
ret[2] = 2 * x[2]
2254+
ret[3] = 3 * x[3]^2
2255+
ret[4] = -1.0
2256+
ret[5] = -1.0
2257+
return
2258+
end,
2259+
hessian_lagrangian_structure = [(1, 1), (2, 2), (3, 3)],
2260+
eval_hessian_lagrangian = (ret, x, u) -> begin
2261+
@test length(ret) == 3
2262+
@test length(x) == 5
2263+
@test length(u) == 2
2264+
ret[1] = 2 * u[1]
2265+
ret[2] = 2 * u[2]
2266+
ret[3] = 6 * x[3] * u[2]
2267+
return
2268+
end,
2269+
)
2270+
@test MOI.dimension(set) == 5
2271+
x, y = MOI.add_variables(model, 3), MOI.add_variables(model, 2)
2272+
MOI.add_constraints.(model, x, MOI.EqualTo.(1.0:3.0))
2273+
c = MOI.add_constraint(model, MOI.VectorOfVariables([x; y]), set)
2274+
MOI.optimize!(model)
2275+
x_v = MOI.get.(model, MOI.VariablePrimal(), x)
2276+
y_v = MOI.get.(model, MOI.VariablePrimal(), y)
2277+
@test (y_v, [x_v[1]^2, x_v[2]^2 + x_v[3]^3], config)
2278+
@test (MOI.get(model, MOI.ConstraintPrimal(), c), [x_v; y_v], config)
2279+
@test (MOI.get(model, MOI.ConstraintDual(), c), zeros(5), config)
2280+
return
2281+
end
2282+
2283+
function setup_test(
2284+
::typeof(test_vector_nonlinear_oracle),
2285+
model::MOIU.MockOptimizer,
2286+
config::Config{T},
2287+
) where {T}
2288+
MOI.Utilities.set_mock_optimize!(
2289+
model,
2290+
mock -> MOI.Utilities.mock_optimize!(
2291+
mock,
2292+
config.optimal_status,
2293+
T[1, 2, 3, 1, 13],
2294+
(MOI.VectorOfVariables, MOI.VectorNonlinearOracle{T}) =>
2295+
zeros(T, 5),
2296+
),
2297+
)
2298+
return
2299+
end
2300+
2301+
version_added(::typeof(test_vector_nonlinear_oracle)) = v"1.46.0"
2302+
2303+
function test_vector_nonlinear_oracle_no_hessian(
2304+
model::MOI.ModelLike,
2305+
config::Config{T},
2306+
) where {T}
2307+
@requires _supports(config, MOI.optimize!)
2308+
@requires MOI.supports_constraint(
2309+
model,
2310+
MOI.VectorOfVariables,
2311+
MOI.VectorNonlinearOracle{T},
2312+
)
2313+
set = MOI.VectorNonlinearOracle(;
2314+
dimension = 5,
2315+
l = [0.0, 0.0],
2316+
u = [0.0, 0.0],
2317+
eval_f = (ret, x) -> begin
2318+
ret[1] = x[1]^2 - x[4]
2319+
ret[2] = x[2]^2 + x[3]^3 - x[5]
2320+
return
2321+
end,
2322+
jacobian_structure = [(1, 1), (2, 2), (2, 3), (1, 4), (2, 5)],
2323+
eval_jacobian = (ret, x) -> begin
2324+
ret[1] = 2 * x[1]
2325+
ret[2] = 2 * x[2]
2326+
ret[3] = 3 * x[3]^2
2327+
ret[4] = -1.0
2328+
ret[5] = -1.0
2329+
return
2330+
end,
2331+
)
2332+
@test MOI.dimension(set) == 5
2333+
x, y = MOI.add_variables(model, 3), MOI.add_variables(model, 2)
2334+
MOI.add_constraints.(model, x, MOI.EqualTo.(1.0:3.0))
2335+
c = MOI.add_constraint(model, MOI.VectorOfVariables([x; y]), set)
2336+
MOI.optimize!(model)
2337+
x_v = MOI.get.(model, MOI.VariablePrimal(), x)
2338+
y_v = MOI.get.(model, MOI.VariablePrimal(), y)
2339+
@test (y_v, [x_v[1]^2, x_v[2]^2 + x_v[3]^3], config)
2340+
@test (MOI.get(model, MOI.ConstraintPrimal(), c), [x_v; y_v], config)
2341+
@test (MOI.get(model, MOI.ConstraintDual(), c), zeros(5), config)
2342+
return
2343+
end
2344+
2345+
function setup_test(
2346+
::typeof(test_vector_nonlinear_oracle_no_hessian),
2347+
model::MOIU.MockOptimizer,
2348+
config::Config{T},
2349+
) where {T}
2350+
MOI.Utilities.set_mock_optimize!(
2351+
model,
2352+
mock -> MOI.Utilities.mock_optimize!(
2353+
mock,
2354+
config.optimal_status,
2355+
T[1, 2, 3, 1, 13],
2356+
(MOI.VectorOfVariables, MOI.VectorNonlinearOracle{T}) =>
2357+
zeros(T, 5),
2358+
),
2359+
)
2360+
return
2361+
end
2362+
2363+
version_added(::typeof(test_vector_nonlinear_oracle_no_hessian)) = v"1.46.0"

src/Utilities/model.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ const EqualToIndicatorZero{T} =
843843
MOI.Table,
844844
MOI.BinPacking,
845845
MOI.HyperRectangle,
846+
MOI.VectorNonlinearOracle,
846847
),
847848
(MOI.ScalarNonlinearFunction,),
848849
(MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),

src/sets.jl

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,6 +2666,177 @@ function Base.:(==)(x::Reified{S}, y::Reified{S}) where {S}
26662666
return x.set == y.set
26672667
end
26682668

2669+
"""
2670+
VectorNonlinearOracle(;
2671+
dimension::Int,
2672+
l::Vector{Float64},
2673+
u::Vector{Float64},
2674+
eval_f::Function,
2675+
jacobian_structure::Vector{Tuple{Int,Int}},
2676+
eval_jacobian::Function,
2677+
hessian_lagrangian_structure::Vector{Tuple{Int,Int}} = Tuple{Int,Int}[],
2678+
eval_hessian_lagrangian::Union{Nothing,Function} = nothing,
2679+
) <: AbstractVectorSet
2680+
2681+
The set:
2682+
```math
2683+
S = \\{x \\in \\mathbb{R}^{dimension}: l \\le f(x) \\le u\\}
2684+
```
2685+
where ``f`` is defined by the vectors `l` and `u`, and the callback oracles
2686+
`eval_f`, `eval_jacobian`, and `eval_hessian_lagrangian`.
2687+
2688+
## f
2689+
2690+
The `eval_f` function must have the signature
2691+
```julia
2692+
eval_f(ret::AbstractVector, x::AbstractVector)::Nothing
2693+
```
2694+
which fills ``f(x)`` into the dense vector `ret`.
2695+
2696+
## Jacobian
2697+
2698+
The `eval_jacobian` function must have the signature
2699+
```julia
2700+
eval_jacobian(ret::AbstractVector, x::AbstractVector)::Nothing
2701+
```
2702+
which fills the sparse Jacobian ``\\nabla f(x)`` into `ret`.
2703+
2704+
The one-indexed sparsity structure must be provided in the `jacobian_structure`
2705+
argument.
2706+
2707+
## Hessian
2708+
2709+
The `eval_hessian_lagrangian` function is optional.
2710+
2711+
If `eval_hessian_lagrangian === nothing`, Ipopt will use a Hessian approximation
2712+
instead of the exact Hessian.
2713+
2714+
If `eval_hessian_lagrangian` is a function, it must have the signature
2715+
```julia
2716+
eval_hessian_lagrangian(
2717+
ret::AbstractVector,
2718+
x::AbstractVector,
2719+
μ::AbstractVector,
2720+
)::Nothing
2721+
```
2722+
which fills the sparse Hessian of the Lagrangian ``\\sum \\mu_i \\nabla^2 f_i(x)``
2723+
into `ret`.
2724+
2725+
The one-indexed sparsity structure must be provided in the
2726+
`hessian_lagrangian_structure` argument.
2727+
2728+
## Example
2729+
2730+
To model the set:
2731+
```math
2732+
\\begin{align}
2733+
0 \\le & x^2 \\le 1
2734+
0 \\le & y^2 + z^3 - w \\le 0
2735+
\\end{align}
2736+
```
2737+
do
2738+
```jldoctest
2739+
julia> import MathOptInterface as MOI
2740+
2741+
julia> set = MOI.VectorNonlinearOracle(;
2742+
dimension = 3,
2743+
l = [0.0, 0.0],
2744+
u = [1.0, 0.0],
2745+
eval_f = (ret, x) -> begin
2746+
ret[1] = x[2]^2
2747+
ret[2] = x[3]^2 + x[4]^3 - x[1]
2748+
return
2749+
end,
2750+
jacobian_structure = [(1, 2), (2, 1), (2, 3), (2, 4)],
2751+
eval_jacobian = (ret, x) -> begin
2752+
ret[1] = 2.0 * x[2]
2753+
ret[2] = -1.0
2754+
ret[3] = 2.0 * x[3]
2755+
ret[4] = 3.0 * x[4]^2
2756+
return
2757+
end,
2758+
hessian_lagrangian_structure = [(2, 2), (3, 3), (4, 4)],
2759+
eval_hessian_lagrangian = (ret, x, u) -> begin
2760+
ret[1] = 2.0 * u[1]
2761+
ret[2] = 2.0 * u[2]
2762+
ret[3] = 6.0 * x[4] * u[2]
2763+
return
2764+
end,
2765+
);
2766+
2767+
julia> set
2768+
VectorNonlinearOracle(;
2769+
dimension = 3,
2770+
l = [0.0, 0.0],
2771+
u = [1.0, 0.0],
2772+
...,
2773+
)
2774+
```
2775+
"""
2776+
struct VectorNonlinearOracle{T} <: AbstractVectorSet
2777+
input_dimension::Int
2778+
output_dimension::Int
2779+
l::Vector{T}
2780+
u::Vector{T}
2781+
eval_f::Function
2782+
jacobian_structure::Vector{Tuple{Int,Int}}
2783+
eval_jacobian::Function
2784+
hessian_lagrangian_structure::Vector{Tuple{Int,Int}}
2785+
eval_hessian_lagrangian::Union{Nothing,Function}
2786+
2787+
function VectorNonlinearOracle(;
2788+
dimension::Int,
2789+
l::Vector{T},
2790+
u::Vector{T},
2791+
eval_f::Function,
2792+
jacobian_structure::Vector{Tuple{Int,Int}},
2793+
eval_jacobian::Function,
2794+
# The hessian_lagrangian is optional.
2795+
hessian_lagrangian_structure::Vector{Tuple{Int,Int}} = Tuple{Int,Int}[],
2796+
eval_hessian_lagrangian::Union{Nothing,Function} = nothing,
2797+
) where {T}
2798+
if length(l) != length(u)
2799+
throw(DimenionMismatch())
2800+
end
2801+
return new{T}(
2802+
dimension,
2803+
length(l),
2804+
l,
2805+
u,
2806+
eval_f,
2807+
jacobian_structure,
2808+
eval_jacobian,
2809+
hessian_lagrangian_structure,
2810+
eval_hessian_lagrangian,
2811+
)
2812+
end
2813+
end
2814+
2815+
dimension(s::VectorNonlinearOracle) = s.input_dimension
2816+
2817+
function Base.copy(s::VectorNonlinearOracle)
2818+
return VectorNonlinearOracle(;
2819+
dimension = s.input_dimension,
2820+
l = copy(s.l),
2821+
u = copy(s.u),
2822+
eval_f = s.eval_f,
2823+
jacobian_structure = copy(s.jacobian_structure),
2824+
eval_jacobian = s.eval_jacobian,
2825+
hessian_lagrangian_structure = copy(s.hessian_lagrangian_structure),
2826+
eval_hessian_lagrangian = s.eval_hessian_lagrangian,
2827+
)
2828+
end
2829+
2830+
function Base.show(io::IO, s::VectorNonlinearOracle{T}) where {T}
2831+
println(io, "VectorNonlinearOracle{T}(;")
2832+
println(io, " dimension = ", s.input_dimension, ",")
2833+
println(io, " l = ", s.l, ",")
2834+
println(io, " u = ", s.u, ",")
2835+
println(io, " ...,")
2836+
print(io, ")")
2837+
return
2838+
end
2839+
26692840
# TODO(odow): these are not necessarily isbits. They may not be safe to return
26702841
# without copying if the number is BigFloat, for example.
26712842
function Base.copy(

test/General/sets.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,21 @@ function test_update_dimension()
419419
return
420420
end
421421

422+
function test_VectorNonlinearOracle()
423+
@test_throws(
424+
DimenionMismatch,
425+
MOI.VectorNonlinearOracle(;
426+
dimension = 3,
427+
l = [0.0, 0.0, 1.0],
428+
u = [1.0, 0.0],
429+
eval_f = (ret, x) -> nothing,
430+
jacobian_structure = Tuple{Int,Int}[],
431+
eval_jacobian = (ret, x) -> nothing,
432+
),
433+
)
434+
return
435+
end
436+
422437
end # module
423438

424439
TestSets.runtests()

0 commit comments

Comments
 (0)