diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index c1817d33f1..7cbc6f4e43 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -75,13 +75,14 @@ The vector-valued set types implemented in MathOptInterface.jl are: | [`RotatedSecondOrderCone(d)`](@ref) | ``\{ (t,u,x) \in \mathbb{R}^{d} : 2tu \ge \lVert x \rVert_2^2, t \ge 0,u \ge 0 \}`` | | [`ExponentialCone()`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : y \exp (x/y) \le z, y > 0 \}`` | | [`DualExponentialCone()`](@ref) | ``\{ (u,v,w) \in \mathbb{R}^3 : -u \exp (v/u) \le \exp(1) w, u < 0 \}`` | -| [`GeometricMeanCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{1+n} : x \ge 0, t \le \sqrt[n]{x_1 x_2 \cdots x_n} \}`` where ``n`` is ``d - 1`` | -| [`DualGeometricMeanCone(d)`](@ref) | ``\{ (u,v) \in \mathbb{R}^{1+n} : v \ge 0, 0 \ge u \ge -n \sqrt[n]{\prod_i v_i} \}``, where ``n`` is ``d - 1`` | +| [`GeometricMeanCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{1+n} : x \ge 0, t \le \sqrt[n]{\prod_i x_i} \}`` where ``n`` is ``d - 1`` | +| [`DualGeometricMeanCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{1+n} : x \ge 0, 0 \ge t \ge -n \sqrt[n]{\prod_i x_i} \}`` where ``n`` is ``d - 1`` | | [`PowerCone(α)`](@ref) | ``\{ (x,y,z) \in \mathbb{R}^3 : x^{\alpha} y^{1-\alpha} \ge \|z\|, x \ge 0,y \ge 0 \}`` | | [`DualPowerCone(α)`](@ref) | ``\{ (u,v,w) \in \mathbb{R}^3 : \left(\frac{u}{\alpha}\right)^{\alpha}\left(\frac{v}{1-\alpha}\right)^{1-\alpha} \ge \|w\|, u,v \ge 0 \}`` | | [`NormOneCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \sum_i \lvert x_i \rvert \}`` | | [`NormInfinityCone(d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \max_i \lvert x_i \rvert \}`` | -| [`RelativeEntropyCone(d)`](@ref) | ``\{ (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 \}`` | +| [`RelativeEntropyCone(d)`](@ref) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i > 0, w_i > 0 \}`` | +| [`DualRelativeEntropyCone(d)`](@ref)| ``\{ (u, v, w) \in \mathbb{R}^{d} : \forall i, w_i \ge u(\log(\frac{u}{v_i}) - 1), v_i > 0, u > 0 \}`` | | [`HyperRectangle(l, u)`](@ref) | ``\{ x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d \}`` | | [`NormCone(p, d)`](@ref) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` | | [`VectorNonlinearOracle`](@ref) | ``\{x \in \mathbb{R}^{dimension}: l \le f(x) \le u \}`` | diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index 2008be8cd0..bcf595bb4e 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -94,6 +94,7 @@ DualExponentialCone PowerCone DualPowerCone RelativeEntropyCone +DualRelativeEntropyCone NormSpectralCone NormNuclearCone SOS1 diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index c2f4338110..2a24c686e3 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -40,6 +40,7 @@ function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, CountDistinctToMILPBridge{T}) MOI.Bridges.add_bridge(model, CountGreaterThanToMILPBridge{T}) MOI.Bridges.add_bridge(model, DualGeoMeanBridge{T}) + MOI.Bridges.add_bridge(model, DualRelativeEntropyBridge{T}) # * ExponentialConeToScalarNonlinearFunctionBridge{T} # This bridge is not added by default because it starts with a convex # conic constraint and adds a nonlinear constraint that local NLP diff --git a/src/Bridges/Constraint/bridges/DualRelativeEntropyBridge.jl b/src/Bridges/Constraint/bridges/DualRelativeEntropyBridge.jl new file mode 100644 index 0000000000..ffbfaed4ad --- /dev/null +++ b/src/Bridges/Constraint/bridges/DualRelativeEntropyBridge.jl @@ -0,0 +1,172 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +""" + DualRelativeEntropyBridge{T,F,G} <: Bridges.Constraint.AbstractBridge + +`DualRelativeEntropyBridge` implements the following reformulation that converts a +[`MOI.DualRelativeEntropyCone`](@ref) into an [`MOI.DualExponentialCone`](@ref): + + * ``w_i \\ge u \\left(\\log \\left(\\frac{u}{v_i}\\right) - 1\\right)`` into + ``(-u, w_i, v_i) \\in DualExponentialCone``. + +## Source node + +`DualRelativeEntropyBridge` supports: + + * `G` in [`MOI.DualRelativeEntropyCone`](@ref) + +## Target nodes + +`DualRelativeEntropyBridge` creates: + + * `F` in [`MOI.DualExponentialCone`](@ref) +""" +struct DualRelativeEntropyBridge{T,F,G} <: AbstractBridge + exp_indices::Vector{MOI.ConstraintIndex{F,MOI.DualExponentialCone}} +end + +const DualRelativeEntropy{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{DualRelativeEntropyBridge{T},OT} + +function bridge_constraint( + ::Type{DualRelativeEntropyBridge{T,F,G}}, + model::MOI.ModelLike, + f::G, + s::MOI.DualRelativeEntropyCone, +) where {T,F,G} + f_scalars = MOI.Utilities.eachscalar(f) + d = MOI.dimension(s) + v_dim = div(d - 1, 2) + exp_indices = MOI.ConstraintIndex{F,MOI.DualExponentialCone}[] + for i in 1:v_dim + fi = MOI.Utilities.operate( + vcat, + T, + MOI.Utilities.operate(-, T, f_scalars[1]), # -u + f_scalars[1+i+v_dim], # w_i + f_scalars[1+i], # v_i + ) + push!( + exp_indices, + MOI.add_constraint(model, fi, MOI.DualExponentialCone()), + ) + end + return DualRelativeEntropyBridge{T,F,G}(exp_indices) +end + +function MOI.supports_constraint( + ::Type{DualRelativeEntropyBridge{T}}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.DualRelativeEntropyCone}, +) where {T} + return true +end + +function MOI.Bridges.added_constrained_variable_types( + ::Type{<:DualRelativeEntropyBridge}, +) + return Tuple{Type}[] +end + +function MOI.Bridges.added_constraint_types( + ::Type{DualRelativeEntropyBridge{T,F,G}}, +) where {T,F,G} + return Tuple{Type,Type}[(F, MOI.DualExponentialCone)] +end + +function concrete_bridge_type( + ::Type{<:DualRelativeEntropyBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.DualRelativeEntropyCone}, +) where {T} + S = MOI.Utilities.scalar_type(G) + U = MOI.Utilities.promote_operation(-, T, S, S) + F = MOI.Utilities.promote_operation(vcat, T, U, S) + return DualRelativeEntropyBridge{T,F,G} +end + +function MOI.get( + bridge::DualRelativeEntropyBridge{T,F}, + ::MOI.NumberOfConstraints{F,MOI.DualExponentialCone}, +)::Int64 where {T,F} + return length(bridge.exp_indices) +end + +function MOI.get( + bridge::DualRelativeEntropyBridge{T,F}, + ::MOI.ListOfConstraintIndices{F,MOI.DualExponentialCone}, +) where {T,F} + return copy(bridge.exp_indices) +end + +function MOI.delete(model::MOI.ModelLike, bridge::DualRelativeEntropyBridge) + for exp_index_i in bridge.exp_indices + MOI.delete(model, exp_index_i) + end + return +end + +function MOI.get( + model::MOI.ModelLike, + ::MOI.ConstraintFunction, + bridge::DualRelativeEntropyBridge{T,F,G}, +) where {T,F,G} + d = length(bridge.exp_indices) + func = MOI.Utilities.zero_with_output_dimension(F, 1 + 2 * d) + exp_1 = MOI.Utilities.eachscalar( + MOI.get(model, MOI.ConstraintFunction(), bridge.exp_indices[begin]), + ) + MOI.Utilities.operate_output_index!(-, T, 1, func, exp_1[1]) + for i in eachindex(bridge.exp_indices) + exp_i = MOI.Utilities.eachscalar( + MOI.get(model, MOI.ConstraintFunction(), bridge.exp_indices[i]), + ) + MOI.Utilities.operate_output_index!(+, T, 1 + i, func, exp_i[3]) + MOI.Utilities.operate_output_index!(+, T, 1 + d + i, func, exp_i[2]) + end + return MOI.Utilities.convert_approx(G, func) +end + +function MOI.get( + ::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::DualRelativeEntropyBridge, +) + return MOI.DualRelativeEntropyCone(1 + 2 * length(bridge.exp_indices)) +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimal, + bridge::DualRelativeEntropyBridge{T}, +) where {T} + d = length(bridge.exp_indices) + primal = zeros(T, 1 + 2d) + primal[1] = -MOI.get(model, attr, bridge.exp_indices[begin])[1] + for i in eachindex(bridge.exp_indices) + primal_i = MOI.get(model, attr, bridge.exp_indices[i]) + primal[1+i] = primal_i[3] + primal[1+i+d] = primal_i[2] + end + return primal +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintDual, + bridge::DualRelativeEntropyBridge{T}, +) where {T} + d = length(bridge.exp_indices) + dual = zeros(T, 1 + 2d) + for (i, ci) in enumerate(bridge.exp_indices) + dual_i = MOI.get(model, attr, ci) + dual[1] -= dual_i[1] + dual[1+i] = dual_i[3] + dual[1+i+d] = dual_i[2] + end + return dual +end diff --git a/src/Test/test_basic_constraint.jl b/src/Test/test_basic_constraint.jl index a8bd8588ca..f7567ed25b 100644 --- a/src/Test/test_basic_constraint.jl +++ b/src/Test/test_basic_constraint.jl @@ -123,6 +123,7 @@ function _set(::Type{T}, ::Type{MOI.DualPowerCone}) where {T} return MOI.DualPowerCone(T(1//2)) end _set(::Type{MOI.RelativeEntropyCone}) = MOI.RelativeEntropyCone(3) +_set(::Type{MOI.DualRelativeEntropyCone}) = MOI.DualRelativeEntropyCone(3) _set(::Type{MOI.NormSpectralCone}) = MOI.NormSpectralCone(2, 3) _set(::Type{MOI.NormNuclearCone}) = MOI.NormNuclearCone(2, 3) function _set(::Type{MOI.PositiveSemidefiniteConeTriangle}) @@ -410,6 +411,7 @@ for s in [ :PowerCone, :DualPowerCone, :RelativeEntropyCone, + :DualRelativeEntropyCone, :NormSpectralCone, :NormNuclearCone, :PositiveSemidefiniteConeSquare, diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index ae655fc588..92418b1110 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -4469,11 +4469,6 @@ function test_conic_RelativeEntropyCone( model::MOI.ModelLike, config::Config{T}, ) where {T<:Real} - @requires MOI.supports_constraint( - model, - MOI.VectorAffineFunction{T}, - MOI.RelativeEntropyCone, - ) @requires MOI.supports_incremental_interface(model) @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.VariableIndex}()) @requires MOI.supports(model, MOI.ObjectiveSense()) @@ -4541,6 +4536,106 @@ function setup_test( return end +""" + test_conic_DualRelativeEntropyCone( + model::MOI.ModelLike, + config::Config{T}, + ) where {T<:Real} + +Test the problem: +``` +max -dot(v, [1, 5]) - dot(w, [2, 3]) + st w[i] >= log(1/v[i]) - 1 (that is, (1, v, w) in DualRelativeEntropyCone(5)) +Optimal solution is: +u = 2*log(2/1) + 3*log(3/5) ≈ -0.1461825 +``` +""" +function test_conic_DualRelativeEntropyCone( + model::MOI.ModelLike, + config::Config{T}, +) where {T<:Real} + @requires MOI.supports_incremental_interface(model) + @requires MOI.supports( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), + ) + @requires MOI.supports(model, MOI.ObjectiveSense()) + @requires MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.DualRelativeEntropyCone, + ) + v = MOI.add_variables(model, 2) + w = MOI.add_variables(model, 2) + @test MOI.get(model, MOI.NumberOfVariables()) == 4 + relentr = MOI.add_constraint( + model, + MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(T(1), v[1])), + MOI.VectorAffineTerm(3, MOI.ScalarAffineTerm(T(1), v[2])), + MOI.VectorAffineTerm(4, MOI.ScalarAffineTerm(T(1), w[1])), + MOI.VectorAffineTerm(5, MOI.ScalarAffineTerm(T(1), w[2])), + ], + T[1, 0, 0, 0, 0], + ), + MOI.DualRelativeEntropyCone(5), + ) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), + MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.(T[-1, -5, -2, -3], [v[1], v[2], w[1], w[2]]), + T(0), + ), + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + if _supports(config, MOI.optimize!) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + if _supports(config, MOI.ConstraintDual) + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + end + u_opt = 2 * log(T(2)) + 3 * log(T(3 // 5)) + @test ≈(MOI.get(model, MOI.ObjectiveValue()), u_opt, config) + @test ≈( + MOI.get(model, MOI.ConstraintPrimal(), relentr), + T[1, 2, 3//5, log(T(1//2))-1, log(T(5//3))-1], + config, + ) + if _supports(config, MOI.ConstraintDual) + @test ≈( + MOI.get(model, MOI.ConstraintDual(), relentr), + T[u_opt, 1, 5, 2, 3], + config, + ) + end + end + return +end + +function setup_test( + ::typeof(test_conic_DualRelativeEntropyCone), + model::MOIU.MockOptimizer, + ::Config{T}, +) where {T<:Real} + u_opt = 2 * log(T(2 // 1)) + 3 * log(T(3 // 5)) + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + T[2, 3//5, log(T(1//2))-1, log(T(5//3))-1], + (MOI.VectorAffineFunction{T}, MOI.DualRelativeEntropyCone) => + [T[u_opt, 1, 5, 2, 3]], + ), + ) + return +end + +version_added(::typeof(test_conic_DualRelativeEntropyCone)) = v"1.53.0" + """ test_conic_NormSpectralCone(model::MOI.ModelLike, config::Config{T}) where {T<:Real} diff --git a/src/Utilities/distance_to_set.jl b/src/Utilities/distance_to_set.jl index a200f5d2f0..ae67e37fad 100644 --- a/src/Utilities/distance_to_set.jl +++ b/src/Utilities/distance_to_set.jl @@ -339,7 +339,7 @@ end Let `(u, v, w) = x`. If `u < 0`, return the epigraph distance `d` such that `(u, v, w + d)` belongs to the set. -If `u >= 0` return the 2-norm of the vector `d` such that `x + d = (u, -1, z)` +If `u >= 0` return the 2-norm of the vector `d` such that `x + d = (-1, v, z)` where `z` satisfies the constraints. """ function distance_to_set( @@ -494,7 +494,7 @@ end """ distance_to_set(::ProjectionUpperBoundDistance, x, ::MOI.RelativeEntropyCone) -Let `(u, v..., w...) = x`. If `v` and `w` are strictly positive, return the +Let `(u, v, w) = x`. If `v` and `w` are strictly positive, return the epigraph distance required to increase `u` such that the constraint is satisfied. If any elements in `v` or `w` are non-positive, return the 2-norm of the vector @@ -527,6 +527,34 @@ function distance_to_set( return max(sum(w[i] * log(w[i] / v[i]) for i in eachindex(w)) - u, zero(T)) end +""" + distance_to_set(::ProjectionUpperBoundDistance, x, ::MOI.DualRelativeEntropyCone) + +Let `(u, v, w) = x`. If `u` is strictly positive, return the 2-norm of the vector +`d` such `(u, v + d, w)` satisfies the constraints. + +If `u` is non-positive return the 2-norm of the vector `d` such that +`x + d = (1, z, w)` and `z` is such that the constraints are satisfied. +""" +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::MOI.DualRelativeEntropyCone, +) where {T<:Real} + Base.require_one_based_indexing(x) + _check_dimension(x, set) + n = div(MOI.dimension(set) - 1, 2) + u, v, w = x[1], @view(x[2:(n+1)]), @view(x[(n+2):end]) + if u > 0 + d = max.(Ref(zero(T)), u * exp.(-w ./ u .- 1) .- v) + return LinearAlgebra.norm(d) + else + d1 = 1 - u + drest = max.(Ref(zero(T)), exp.(-w .- 1) - v) + return LinearAlgebra.norm((d1, drest)) + end +end + # This is the minimal L2-norm. function distance_to_set( ::ProjectionUpperBoundDistance, diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index b3285189ae..a0ff04ddbc 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -817,6 +817,7 @@ const EqualToIndicatorZero{T} = MOI.ExponentialCone, MOI.DualExponentialCone, MOI.RelativeEntropyCone, + MOI.DualRelativeEntropyCone, MOI.NormSpectralCone, MOI.NormNuclearCone, MOI.PositiveSemidefiniteConeTriangle, diff --git a/src/sets.jl b/src/sets.jl index 4a13957f8f..ac45a49bae 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -959,15 +959,9 @@ end RelativeEntropyCone(dimension::Int) The relative entropy cone -``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : u \\ge \\sum_{i=1}^n w_i \\log(\\frac{w_i}{v_i}), v_i \\ge 0, w_i \\ge 0 \\}``, +``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : u \\ge \\sum_{i=1}^n w_i \\log(\\frac{w_i}{v_i}), v_i > 0, w_i > 0 \\}``, where `dimension = 2n + 1 >= 1`. -## Duality note - -The dual of the relative entropy cone is -``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : \\forall i, w_i \\ge u (\\log (\\frac{u}{v_i}) - 1), v_i \\ge 0, u > 0 \\}`` -of dimension `dimension```{}=2n+1``. - ## Example ```jldoctest @@ -1002,6 +996,53 @@ struct RelativeEntropyCone <: AbstractVectorSet end end +dual_set(s::RelativeEntropyCone) = DualRelativeEntropyCone(s.dimension) +dual_set_type(::Type{RelativeEntropyCone}) = DualRelativeEntropyCone + +""" + DualRelativeEntropyCone(dimension::Int) + +The dual relative entropy cone +``\\{ (u, v, w) \\in \\mathbb{R}^{1+2n} : \\forall i, w_i \\ge u (\\log (\\frac{u}{v_i}) - 1), v_i > 0, u > 0 \\}`` +where `dimension = 2n + 1 >= 1`. + +## Example + +```jldoctest +julia> model = MOI.Utilities.Model{Float64}(); + +julia> u = MOI.add_variable(model); + +julia> v = MOI.add_variables(model, 3); + +julia> w = MOI.add_variables(model, 3); + +julia> MOI.add_constraint( + model, + MOI.VectorOfVariables([u; v; w]), + MOI.DualRelativeEntropyCone(7), + ) +MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.DualRelativeEntropyCone}(1) +``` +""" +struct DualRelativeEntropyCone <: AbstractVectorSet + dimension::Int + function DualRelativeEntropyCone(dimension::Base.Integer) + if !(dimension >= 1 && isodd(dimension)) + throw( + DimensionMismatch( + "Dimension of RelativeEntropyCone must be an odd integer " * + ">= 1, not $(dimension).", + ), + ) + end + return new(dimension) + end +end + +dual_set(s::DualRelativeEntropyCone) = RelativeEntropyCone(s.dimension) +dual_set_type(::Type{DualRelativeEntropyCone}) = RelativeEntropyCone + """ NormSpectralCone(row_dim::Int, column_dim::Int) diff --git a/test/Bridges/Constraint/test_DualRelativeEntropyBridge.jl b/test/Bridges/Constraint/test_DualRelativeEntropyBridge.jl new file mode 100644 index 0000000000..905b69e345 --- /dev/null +++ b/test/Bridges/Constraint/test_DualRelativeEntropyBridge.jl @@ -0,0 +1,139 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestConstraintDualRelativeEntropy + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +include("../utilities.jl") + +function test_DualRelativeEntropy() + mock = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + config = MOI.Test.Config() + bridged_mock = MOI.Bridges.Constraint.DualRelativeEntropy{Float64}(mock) + MOI.Test.test_basic_VectorOfVariables_DualRelativeEntropyCone( + bridged_mock, + config, + ) + MOI.empty!(bridged_mock) + MOI.Test.test_basic_VectorAffineFunction_DualRelativeEntropyCone( + bridged_mock, + config, + ) + MOI.empty!(bridged_mock) + MOI.Test.test_basic_VectorQuadraticFunction_DualRelativeEntropyCone( + bridged_mock, + config, + ) + MOI.empty!(bridged_mock) + var_primal = [2.0, 3/5, log(1/2)-1, log(5/3)-1] + exps_duals = [[2log(1/2), 2.0, 1.0], [3log(5/3), 3.0, 5.0]] + mock.optimize! = + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + var_primal, + (MOI.VectorAffineFunction{Float64}, MOI.DualExponentialCone) => + exps_duals, + ) + + MOI.Test.test_conic_DualRelativeEntropyCone(bridged_mock, config) + var_names = ["v1", "v2", "w1", "w2"] + MOI.set( + bridged_mock, + MOI.VariableName(), + MOI.get(bridged_mock, MOI.ListOfVariableIndices()), + var_names, + ) + exps = MOI.get( + mock, + MOI.ListOfConstraintIndices{ + MOI.VectorAffineFunction{Float64}, + MOI.DualExponentialCone, + }(), + ) + @test length(exps) == 2 + MOI.set(mock, MOI.ConstraintName(), exps[1], "exps1") + MOI.set(mock, MOI.ConstraintName(), exps[2], "exps2") + + s = """ + variables: v1, v2, w1, w2 + exps1: [-1.0, w1, v1] in DualExponentialCone() + exps2: [-1.0, w2, v2] in DualExponentialCone() + maxobjective: -1.0v1 + -5.0v2 + -2.0w1 + -3.0w2 + """ + model = MOI.Utilities.Model{Float64}() + MOI.Utilities.loadfromstring!(model, s) + MOI.Test.util_test_models_equal(mock, model, var_names, ["exps1", "exps2"]) + relentr = MOI.get( + bridged_mock, + MOI.ListOfConstraintIndices{ + MOI.VectorAffineFunction{Float64}, + MOI.DualRelativeEntropyCone, + }(), + ) + @test length(relentr) == 1 + MOI.set(bridged_mock, MOI.ConstraintName(), relentr[1], "relentr") + + s = """ + variables: v1, v2, w1, w2 + relentr: [1.0, v1, v2, w1, w2] in DualRelativeEntropyCone(5) + maxobjective: -1.0v1 + -5.0v2 + -2.0w1 + -3.0w2 + """ + model = MOI.Utilities.Model{Float64}() + MOI.Utilities.loadfromstring!(model, s) + MOI.Test.util_test_models_equal(bridged_mock, model, var_names, ["relentr"]) + ci = first( + MOI.get( + bridged_mock, + MOI.ListOfConstraintIndices{ + MOI.VectorAffineFunction{Float64}, + MOI.DualRelativeEntropyCone, + }(), + ), + ) + _test_delete_bridge( + bridged_mock, + ci, + 4, + ((MOI.VectorAffineFunction{Float64}, MOI.DualExponentialCone, 0),), + ) + return +end + +function test_runtests() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.DualRelativeEntropyBridge, + """ + variables: u, v1, v2, w1, w2 + [u, v1, v2, w1, w2] in DualRelativeEntropyCone(5) + """, + """ + variables: u, v1, v2, w1, w2 + [-1.0 * u, w1, v1] in DualExponentialCone() + [-1.0 * u, w2, v2] in DualExponentialCone() + """, + ) + return +end + +end # module + +TestConstraintDualRelativeEntropy.runtests() diff --git a/test/General/test_sets.jl b/test/General/test_sets.jl index f931edca8c..853dc97eda 100644 --- a/test/General/test_sets.jl +++ b/test/General/test_sets.jl @@ -140,6 +140,7 @@ function test_sets_DimensionMismatch() (MOI.DualGeometricMeanCone, 2), (MOI.Complements, 0), (MOI.RelativeEntropyCone, 1), + (MOI.DualRelativeEntropyCone, 1), (MOI.ScaledPositiveSemidefiniteConeTriangle, 0), (MOI.PositiveSemidefiniteConeTriangle, 0), (MOI.PositiveSemidefiniteConeSquare, 0), @@ -164,6 +165,7 @@ function test_sets_DimensionMismatch() @test MOI.NormNuclearCone(0, 0) isa MOI.NormNuclearCone # Other dimension checks @test_throws DimensionMismatch MOI.RelativeEntropyCone(2) + @test_throws DimensionMismatch MOI.DualRelativeEntropyCone(2) @test_throws DimensionMismatch MOI.Complements(-3) @test_throws DimensionMismatch MOI.Complements(3) @test_throws DimensionMismatch MOI.CountBelongs(0, Set([1, 2])) @@ -332,6 +334,16 @@ function test_sets_dual_power() return end +function test_sets_dual_relativeentropy() + relent = MOI.RelativeEntropyCone(5) + dual_relent = MOI.DualRelativeEntropyCone(5) + _dual_set_test(relent, dual_relent) + @test MOI.dual_set(relent) != relent + _dual_set_test(dual_relent, relent) + @test MOI.dual_set(dual_relent) != dual_relent + return +end + function test_sets_dual_psdsquare() s = MOI.PositiveSemidefiniteConeSquare(4) err = ErrorException( diff --git a/test/Utilities/test_distance_to_set.jl b/test/Utilities/test_distance_to_set.jl index a4d9f28d05..5cbd5d49b5 100644 --- a/test/Utilities/test_distance_to_set.jl +++ b/test/Utilities/test_distance_to_set.jl @@ -277,6 +277,20 @@ function test_relativeentropycone() return end +function test_dualrelativeentropycone() + _test_set( + MOI.DualRelativeEntropyCone(5), + [1.0, 1.0, 1.0, 1.0, 1.0] => 0.0, + [-2.0, 1.0, 1.0, 1.0, 1.0] => 3.0, + [1.0, 1.0, 2.0, 3.0, 1.0] => 0.0, + [1.0, -1.0, 2.0, 3.0, 1.0] => 1 + exp(-4), + [-1.0, -1.0, 2.0, 3.0, 1.0] => sqrt(2^2 + (1 + exp(-4))^2), + [2.0, 0.1, 2.0, 3.0, -1.0] => 2exp(-5/2) - 0.1, + mismatch = [1.0], + ) + return +end + function test_hyperrectangle() _test_set( MOI.HyperRectangle([0.0, 1.0], [1.0, 2.0]),