Skip to content

Commit 4ae2e78

Browse files
authored
Merge 6271c21 into 25c3358
2 parents 25c3358 + 6271c21 commit 4ae2e78

7 files changed

Lines changed: 365 additions & 0 deletions

File tree

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: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,3 +2223,143 @@ 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 = T[0, 0],
2240+
u = T[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] = T(2) * x[1]
2253+
ret[2] = T(2) * x[2]
2254+
ret[3] = T(3) * x[3]^2
2255+
ret[4] = -T(1)
2256+
ret[5] = -T(1)
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] = T(2) * u[1]
2265+
ret[2] = T(2) * u[2]
2266+
ret[3] = T(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.(T(1):T(3)))
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(T, 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, 31],
2294+
(MOI.VectorOfVariables, MOI.VectorNonlinearOracle{T}) =>
2295+
[zeros(T, 5)],
2296+
),
2297+
)
2298+
model.eval_variable_constraint_dual = false
2299+
return () -> model.eval_variable_constraint_dual = true
2300+
end
2301+
2302+
version_added(::typeof(test_vector_nonlinear_oracle)) = v"1.46.0"
2303+
2304+
function test_vector_nonlinear_oracle_no_hessian(
2305+
model::MOI.ModelLike,
2306+
config::Config{T},
2307+
) where {T}
2308+
@requires _supports(config, MOI.optimize!)
2309+
@requires MOI.supports_constraint(
2310+
model,
2311+
MOI.VectorOfVariables,
2312+
MOI.VectorNonlinearOracle{T},
2313+
)
2314+
set = MOI.VectorNonlinearOracle(;
2315+
dimension = 5,
2316+
l = T[0, 0],
2317+
u = T[0, 0],
2318+
eval_f = (ret, x) -> begin
2319+
ret[1] = x[1]^2 - x[4]
2320+
ret[2] = x[2]^2 + x[3]^3 - x[5]
2321+
return
2322+
end,
2323+
jacobian_structure = [(1, 1), (2, 2), (2, 3), (1, 4), (2, 5)],
2324+
eval_jacobian = (ret, x) -> begin
2325+
ret[1] = T(2) * x[1]
2326+
ret[2] = T(2) * x[2]
2327+
ret[3] = T(3) * x[3]^2
2328+
ret[4] = -T(1)
2329+
ret[5] = -T(1)
2330+
return
2331+
end,
2332+
)
2333+
@test MOI.dimension(set) == 5
2334+
x, y = MOI.add_variables(model, 3), MOI.add_variables(model, 2)
2335+
MOI.add_constraints.(model, x, MOI.EqualTo.(T(1):T(3)))
2336+
c = MOI.add_constraint(model, MOI.VectorOfVariables([x; y]), set)
2337+
MOI.optimize!(model)
2338+
x_v = MOI.get.(model, MOI.VariablePrimal(), x)
2339+
y_v = MOI.get.(model, MOI.VariablePrimal(), y)
2340+
@test (y_v, [x_v[1]^2, x_v[2]^2 + x_v[3]^3], config)
2341+
@test (MOI.get(model, MOI.ConstraintPrimal(), c), [x_v; y_v], config)
2342+
@test (MOI.get(model, MOI.ConstraintDual(), c), zeros(T, 5), config)
2343+
return
2344+
end
2345+
2346+
function setup_test(
2347+
::typeof(test_vector_nonlinear_oracle_no_hessian),
2348+
model::MOIU.MockOptimizer,
2349+
config::Config{T},
2350+
) where {T}
2351+
MOI.Utilities.set_mock_optimize!(
2352+
model,
2353+
mock -> MOI.Utilities.mock_optimize!(
2354+
mock,
2355+
config.optimal_status,
2356+
T[1, 2, 3, 1, 31],
2357+
(MOI.VectorOfVariables, MOI.VectorNonlinearOracle{T}) =>
2358+
[zeros(T, 5)],
2359+
),
2360+
)
2361+
model.eval_variable_constraint_dual = false
2362+
return () -> model.eval_variable_constraint_dual = true
2363+
end
2364+
2365+
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: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,6 +2666,192 @@ 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{Float64}(;
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(DimensionMismatch())
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.:(==)(
2831+
x::VectorNonlinearOracle{T},
2832+
y::VectorNonlinearOracle{T},
2833+
) where {T}
2834+
return x.input_dimension == y.input_dimension &&
2835+
x.output_dimension == y.output_dimension &&
2836+
x.l == y.l &&
2837+
x.u == y.u &&
2838+
x.eval_f == y.eval_f &&
2839+
x.jacobian_structure == y.jacobian_structure &&
2840+
x.eval_jacobian == y.eval_jacobian &&
2841+
x.hessian_lagrangian_structure == y.hessian_lagrangian_structure &&
2842+
x.eval_hessian_lagrangian == y.eval_hessian_lagrangian
2843+
end
2844+
2845+
function Base.show(io::IO, s::VectorNonlinearOracle{T}) where {T}
2846+
println(io, "VectorNonlinearOracle{$T}(;")
2847+
println(io, " dimension = ", s.input_dimension, ",")
2848+
println(io, " l = ", s.l, ",")
2849+
println(io, " u = ", s.u, ",")
2850+
println(io, " ...,")
2851+
print(io, ")")
2852+
return
2853+
end
2854+
26692855
# TODO(odow): these are not necessarily isbits. They may not be safe to return
26702856
# without copying if the number is BigFloat, for example.
26712857
function Base.copy(

0 commit comments

Comments
 (0)