diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 859e0ef766..197e1e5bfe 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -56,6 +56,7 @@ function add_all_bridges(model, ::Type{T}) where {T} end MOI.Bridges.add_bridge(model, GreaterToLessBridge{T}) MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T}) + MOI.Bridges.add_bridge(model, HermitianToComplexSymmetricBridge{T}) MOI.Bridges.add_bridge(model, IndicatorActiveOnFalseBridge{T}) MOI.Bridges.add_bridge(model, IndicatorGreaterToLessThanBridge{T}) MOI.Bridges.add_bridge(model, IndicatorLessToGreaterThanBridge{T}) diff --git a/src/Bridges/Constraint/bridges/HermitianToComplexSymmetricBridge.jl b/src/Bridges/Constraint/bridges/HermitianToComplexSymmetricBridge.jl new file mode 100644 index 0000000000..ff5a02f94e --- /dev/null +++ b/src/Bridges/Constraint/bridges/HermitianToComplexSymmetricBridge.jl @@ -0,0 +1,165 @@ +# 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. + +""" + HermitianToComplexSymmetricBridge{T,F,G} <: Bridges.Constraint.AbstractBridge + +`HermitianToSymmetricBridge` implements the following reformulation: + + * Hermitian positive semidefinite `n x n` represented as a vector of real + entries with real and imaginary parts on different entries to a vector + of complex entries. + +See also [`MOI.Bridges.Constraint.HermitianToSymmetricPSDBridge`](@ref). + +## Source node + +`HermitianToComplexSymmetricBridge` supports: + + * `G` in [`MOI.HermitianPositiveSemidefiniteConeTriangle`](@ref) + +## Target node + +`HermitianToComplexSymmetricBridge` creates: + + * `F` in [`MOI.PositiveSemidefiniteConeTriangle`](@ref) + +Note that if `G` is `MOI.VectorAffineFunction{T}` then `F` will be +`MOI.VectorAffineFunction{Complex{T}}` +""" +struct HermitianToComplexSymmetricBridge{T,F,G} <: SetMapBridge{ + T, + MOI.PositiveSemidefiniteConeTriangle, + MOI.HermitianPositiveSemidefiniteConeTriangle, + F, + G, +} + constraint::MOI.ConstraintIndex{F,MOI.PositiveSemidefiniteConeTriangle} +end + +const HermitianToComplexSymmetric{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{HermitianToComplexSymmetricBridge{T},OT} + +# Should be favored over `HermitianToSymmetricPSDBridge` +MOI.Bridges.bridging_cost(::Type{<:HermitianToComplexSymmetricBridge}) = 0.5 + +function _promote_complex_vcat(::Type{T}, ::Type{G}) where {T<:Real,G} + S = MOI.Utilities.scalar_type(G) + if S === T + M = Complex{T} + elseif S <: MOI.Utilities.TypedLike{T} + M = MOI.Utilities.similar_type(S, Complex{T}) + else + M = MOI.Utilities.promote_operation(*, Complex{T}, Complex{T}, S) + end + return MOI.Utilities.promote_operation(vcat, Complex{T}, M) +end + +function concrete_bridge_type( + ::Type{<:HermitianToComplexSymmetricBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.HermitianPositiveSemidefiniteConeTriangle}, +) where {T} + F = _promote_complex_vcat(T, G) + return HermitianToComplexSymmetricBridge{T,F,G} +end + +function MOI.Bridges.map_set( + ::Type{<:HermitianToComplexSymmetricBridge}, + set::MOI.HermitianPositiveSemidefiniteConeTriangle, +) + return MOI.PositiveSemidefiniteConeTriangle(set.side_dimension) +end + +function MOI.Bridges.inverse_map_set( + ::Type{<:HermitianToComplexSymmetricBridge}, + set::MOI.PositiveSemidefiniteConeTriangle, +) + return MOI.HermitianPositiveSemidefiniteConeTriangle(set.side_dimension) +end + +function MOI.Bridges.map_function( + ::Type{<:HermitianToComplexSymmetricBridge{T}}, + func, +) where {T} + complex_scalars = MOI.Utilities.eachscalar(func) + S = MOI.Utilities.scalar_type(_promote_complex_vcat(T, typeof(func))) + complex_dim = length(complex_scalars) + complex_set = MOI.Utilities.set_with_dimension( + MOI.HermitianPositiveSemidefiniteConeTriangle, + complex_dim, + ) + n = complex_set.side_dimension + real_set = MOI.PositiveSemidefiniteConeTriangle(n) + real_dim = MOI.dimension(real_set) + real_scalars = Vector{S}(undef, real_dim) + real_index = 0 + imag_index = real_dim + for j in 1:n + for i in 1:j + real_index += 1 + if i == j + real_scalars[real_index] = complex_scalars[real_index] + else + imag_index += 1 + real_scalars[real_index] = + one(Complex{T}) * complex_scalars[real_index] + + (one(T) * im) * complex_scalars[imag_index] + end + end + end + @assert length(real_scalars) == real_index + @assert length(complex_scalars) == imag_index + return MOI.Utilities.vectorize(real_scalars) +end + +function MOI.Bridges.inverse_adjoint_map_function( + BT::Type{<:HermitianToComplexSymmetricBridge}, + func, +) + return MOI.Bridges.map_function(BT, func) +end + +function MOI.Bridges.inverse_map_function( + ::Type{<:HermitianToComplexSymmetricBridge}, + func, +) + real_scalars = MOI.Utilities.eachscalar(func) + real_set = MOI.Utilities.set_with_dimension( + MOI.PositiveSemidefiniteConeTriangle, + length(real_scalars), + ) + n = real_set.side_dimension + complex_set = MOI.HermitianPositiveSemidefiniteConeTriangle(n) + complex_scalars = Vector{ + MA.promote_operation(real, MOI.Utilities.scalar_type(typeof(func))), + }( + undef, + MOI.dimension(complex_set), + ) + real_index = 0 + imag_index = MOI.dimension(real_set) + for j in 1:n + for i in 1:j + real_index += 1 + complex_scalars[real_index] = real(real_scalars[real_index]) + if i != j + imag_index += 1 + complex_scalars[imag_index] = imag(real_scalars[real_index]) + end + end + end + @assert length(real_scalars) == real_index + @assert length(complex_scalars) == imag_index + return MOI.Utilities.vectorize(complex_scalars) +end + +function MOI.Bridges.adjoint_map_function( + BT::Type{<:HermitianToComplexSymmetricBridge}, + func, +) + return MOI.Bridges.inverse_map_function(BT, func) +end diff --git a/src/Bridges/Constraint/bridges/HermitianToSymmetricPSDBridge.jl b/src/Bridges/Constraint/bridges/HermitianToSymmetricPSDBridge.jl index 0283e29845..81ad558682 100644 --- a/src/Bridges/Constraint/bridges/HermitianToSymmetricPSDBridge.jl +++ b/src/Bridges/Constraint/bridges/HermitianToSymmetricPSDBridge.jl @@ -153,7 +153,7 @@ function MOI.Bridges.map_function( end function MOI.Bridges.inverse_map_function( - BT::Type{<:HermitianToSymmetricPSDBridge}, + ::Type{<:HermitianToSymmetricPSDBridge}, func, ) real_scalars = MOI.Utilities.eachscalar(func) diff --git a/src/Bridges/Constraint/bridges/SetDotScalingBridge.jl b/src/Bridges/Constraint/bridges/SetDotScalingBridge.jl index 9fff52e8ba..e778d4019b 100644 --- a/src/Bridges/Constraint/bridges/SetDotScalingBridge.jl +++ b/src/Bridges/Constraint/bridges/SetDotScalingBridge.jl @@ -194,10 +194,10 @@ end # for `SetMapBridge` does not work function MOI.supports_constraint( ::Type{<:SetDotScalingBridge}, - ::Type{<:MOI.AbstractVectorFunction}, + F::Type{<:MOI.AbstractVectorFunction}, S::Type{<:MOI.AbstractVectorSet}, ) - return MOI.is_set_dot_scaled(S) + return !MOI.Utilities.is_complex(F) && MOI.is_set_dot_scaled(S) end function MOI.supports_constraint( diff --git a/test/Bridges/Constraint/HermitianToComplexSymmetricBridge.jl b/test/Bridges/Constraint/HermitianToComplexSymmetricBridge.jl new file mode 100644 index 0000000000..0a02e1574a --- /dev/null +++ b/test/Bridges/Constraint/HermitianToComplexSymmetricBridge.jl @@ -0,0 +1,88 @@ +# 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 TestConstraintHermitianToComplexSymmetric + +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 + +function test_dimension_2() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.HermitianToComplexSymmetricBridge, + model -> begin + a, b, c = MOI.add_variables(model, 3) + MOI.add_constraint( + model, + MOI.Utilities.vectorize([ + 1.0 * a + 2.0 * b, + 3.0 * c, + 4.0 * b, + 5.0 * a, + ]), + MOI.HermitianPositiveSemidefiniteConeTriangle(2), + ) + end, + model -> begin + a, b, c = MOI.add_variables(model, 3) + MOI.add_constraint( + model, + MOI.Utilities.vectorize([ + Complex(1.0) * a + Complex(2.0) * b, + Complex(3.0) * c + 5.0 * im * a, + Complex(4.0) * b, + ]), + MOI.PositiveSemidefiniteConeTriangle(2), + ) + end, + ) + return +end + +function test_dimension_3() + MOI.Bridges.runtests( + MOI.Bridges.Constraint.HermitianToComplexSymmetricBridge, + model -> begin + x = MOI.add_variables(model, 9) + MOI.add_constraint( + model, + MOI.VectorOfVariables(x), + MOI.HermitianPositiveSemidefiniteConeTriangle(3), + ) + end, + model -> begin + x = MOI.add_variables(model, 9) + MOI.add_constraint( + model, + MOI.Utilities.vectorize([ + Complex(1.0) * x[1], + Complex(1.0) * x[2] + 1.0 * im * x[7], + Complex(1.0) * x[3], + Complex(1.0) * x[4] + 1.0 * im * x[8], + Complex(1.0) * x[5] + 1.0 * im * x[9], + Complex(1.0) * x[6], + ]), + MOI.PositiveSemidefiniteConeTriangle(3), + ) + end, + ) + return +end + +end # module + +TestConstraintHermitianToComplexSymmetric.runtests()