|
| 1 | +""" |
| 2 | + NormSpectralBridge{T} |
| 3 | +
|
| 4 | +The `NormSpectralCone` is representable with a PSD constraint, since |
| 5 | +``t \\ge \\sigma_1(X)`` if and only if ``[tI X^\\top; X tI] \\succ 0``. |
| 6 | +""" |
| 7 | +struct NormSpectralBridge{T, F, G} <: AbstractBridge |
| 8 | + row_dim::Int # row dimension of X |
| 9 | + column_dim::Int # column dimension of X |
| 10 | + psd_index::CI{F, MOI.PositiveSemidefiniteConeTriangle} |
| 11 | +end |
| 12 | +function bridge_constraint(::Type{NormSpectralBridge{T, F, G}}, model::MOI.ModelLike, f::G, s::MOI.NormSpectralCone) where {T, F, G} |
| 13 | + f_scalars = MOIU.eachscalar(f) |
| 14 | + t = f_scalars[1] |
| 15 | + row_dim = s.row_dim |
| 16 | + column_dim = s.column_dim |
| 17 | + side_dim = row_dim + column_dim |
| 18 | + psd_set = MOI.PositiveSemidefiniteConeTriangle(side_dim) |
| 19 | + psd_func = MOIU.zero_with_output_dimension(F, MOI.dimension(psd_set)) |
| 20 | + for i in 1:side_dim |
| 21 | + MOIU.operate_output_index!(+, T, trimap(i, i), psd_func, t) |
| 22 | + end |
| 23 | + X_idx = 2 |
| 24 | + for j in 1:column_dim, i in (column_dim + 1):side_dim |
| 25 | + MOIU.operate_output_index!(+, T, trimap(i, j), psd_func, f_scalars[X_idx]) |
| 26 | + X_idx += 1 |
| 27 | + end |
| 28 | + psd_index = MOI.add_constraint(model, psd_func, psd_set) |
| 29 | + return NormSpectralBridge{T, F, G}(row_dim, column_dim, psd_index) |
| 30 | +end |
| 31 | + |
| 32 | +MOI.supports_constraint(::Type{NormSpectralBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormSpectralCone}) where T = true |
| 33 | +MOIB.added_constrained_variable_types(::Type{<:NormSpectralBridge}) = Tuple{DataType}[] |
| 34 | +MOIB.added_constraint_types(::Type{NormSpectralBridge{T, F, G}}) where {T, F, G} = [(F, MOI.PositiveSemidefiniteConeTriangle)] |
| 35 | +function concrete_bridge_type(::Type{<:NormSpectralBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormSpectralCone}) where T |
| 36 | + F = MOIU.promote_operation(vcat, T, MOIU.scalar_type(G), T) |
| 37 | + return NormSpectralBridge{T, F, G} |
| 38 | +end |
| 39 | + |
| 40 | +# Attributes, Bridge acting as a model |
| 41 | +MOI.get(bridge::NormSpectralBridge{T, F, G}, ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F, G} = 1 |
| 42 | +MOI.get(bridge::NormSpectralBridge{T, F, G}, ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F, G} = [bridge.psd_index] |
| 43 | + |
| 44 | +# References |
| 45 | +MOI.delete(model::MOI.ModelLike, bridge::NormSpectralBridge) = MOI.delete(model, bridge.psd_index) |
| 46 | + |
| 47 | +# Attributes, Bridge acting as a constraint |
| 48 | +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, bridge::NormSpectralBridge{T, F, G}) where {T, F, G} |
| 49 | + psd_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), bridge.psd_index)) |
| 50 | + t = psd_func[1] |
| 51 | + side_dim = bridge.row_dim + bridge.column_dim |
| 52 | + X = psd_func[[trimap(i, j) for j in 1:bridge.column_dim for i in (bridge.column_dim + 1):side_dim]] |
| 53 | + return MOIU.convert_approx(G, MOIU.operate(vcat, T, t, X)) |
| 54 | +end |
| 55 | +MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, bridge::NormSpectralBridge) = MOI.NormSpectralCone(bridge.row_dim, bridge.column_dim) |
| 56 | +MOI.supports(::MOI.ModelLike, ::MOI.ConstraintPrimalStart, ::Type{<:NormSpectralBridge}) = true |
| 57 | +function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart}, bridge::NormSpectralBridge) |
| 58 | + primal = MOI.get(model, attr, bridge.psd_index) |
| 59 | + t = primal[1] |
| 60 | + side_dim = bridge.row_dim + bridge.column_dim |
| 61 | + X = primal[[trimap(i, j) for j in 1:bridge.column_dim for i in (bridge.column_dim + 1):side_dim]] |
| 62 | + return vcat(t, X) |
| 63 | +end |
| 64 | +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintPrimalStart, bridge::NormSpectralBridge{T}, value) where T |
| 65 | + column_dim = bridge.column_dim |
| 66 | + side_dim = bridge.row_dim + column_dim |
| 67 | + primal = zeros(T, div(side_dim * (side_dim + 1), 2)) |
| 68 | + for i in 1:side_dim |
| 69 | + primal[trimap(i, i)] = value[1] |
| 70 | + end |
| 71 | + X_idx = 2 |
| 72 | + for j in 1:column_dim, i in (column_dim + 1):side_dim |
| 73 | + primal[trimap(i, j)] = value[X_idx] |
| 74 | + X_idx += 1 |
| 75 | + end |
| 76 | + MOI.set(model, MOI.ConstraintPrimalStart(), bridge.psd_index, primal) |
| 77 | + return |
| 78 | +end |
| 79 | +# Given [U X'; X V] is dual on PSD constraint, the dual on NormSpectralCone |
| 80 | +# constraint is (tr(U) + tr(V), 2X) in NormNuclearCone. |
| 81 | +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintDual, bridge::NormSpectralBridge) |
| 82 | + dual = MOI.get(model, MOI.ConstraintDual(), bridge.psd_index) |
| 83 | + column_dim = bridge.column_dim |
| 84 | + side_dim = bridge.row_dim + column_dim |
| 85 | + t = sum(dual[trimap(i, i)] for i in 1:side_dim) |
| 86 | + X = 2 * dual[[trimap(i, j) for j in 1:column_dim for i in (column_dim + 1):side_dim]] |
| 87 | + return vcat(t, X) |
| 88 | +end |
| 89 | + |
| 90 | +""" |
| 91 | + NormNuclearBridge{T} |
| 92 | +
|
| 93 | +The `NormNuclearCone` is representable with an SDP constraint and extra variables, |
| 94 | +since ``t \\ge \\sum_i \\sigma_i (X)`` if and only if there exists symmetric |
| 95 | +matrices ``U, V`` such that ``[U X^\\top; X V] \\succ 0`` and ``t \\ge (tr(U) + tr(V)) / 2``. |
| 96 | +""" |
| 97 | +struct NormNuclearBridge{T, F, G, H} <: AbstractBridge |
| 98 | + row_dim::Int # row dimension of X |
| 99 | + column_dim::Int # column dimension of X |
| 100 | + U::Vector{MOI.VariableIndex} |
| 101 | + V::Vector{MOI.VariableIndex} |
| 102 | + ge_index::CI{F, MOI.GreaterThan{T}} |
| 103 | + psd_index::CI{G, MOI.PositiveSemidefiniteConeTriangle} |
| 104 | +end |
| 105 | +function bridge_constraint(::Type{NormNuclearBridge{T, F, G, H}}, model::MOI.ModelLike, f::H, s::MOI.NormNuclearCone) where {T, F, G, H} |
| 106 | + f_scalars = MOIU.eachscalar(f) |
| 107 | + row_dim = s.row_dim |
| 108 | + column_dim = s.column_dim |
| 109 | + side_dim = row_dim + column_dim |
| 110 | + U_dim = div(column_dim * (column_dim + 1), 2) |
| 111 | + V_dim = div(row_dim * (row_dim + 1), 2) |
| 112 | + U = MOI.add_variables(model, U_dim) |
| 113 | + V = MOI.add_variables(model, V_dim) |
| 114 | + diag_vars = vcat([U[trimap(i, i)] for i in 1:column_dim], [V[trimap(i, i)] for i in 1:row_dim]) |
| 115 | + ge_index = MOIU.normalize_and_add_constraint(model, MOIU.operate(-, T, f_scalars[1], MOIU.operate!(/, T, MOIU.operate(sum, T, diag_vars), T(2))), MOI.GreaterThan(zero(T)), allow_modify_function=true) |
| 116 | + psd_set = MOI.PositiveSemidefiniteConeTriangle(side_dim) |
| 117 | + psd_func = MOI.VectorOfVariables(U) |
| 118 | + nuc_dim = 1 + row_dim * column_dim |
| 119 | + for i in 1:row_dim |
| 120 | + row_i = (1 + i):row_dim:nuc_dim |
| 121 | + psd_func = MOIU.operate(vcat, T, psd_func, f_scalars[row_i]) |
| 122 | + psd_func = MOIU.operate(vcat, T, psd_func, MOI.VectorOfVariables(V[[trimap(i, j) for j in 1:i]])) |
| 123 | + end |
| 124 | + psd_index = MOI.add_constraint(model, psd_func, psd_set) |
| 125 | + return NormNuclearBridge{T, F, G, H}(row_dim, column_dim, U, V, ge_index, psd_index) |
| 126 | +end |
| 127 | + |
| 128 | +MOI.supports_constraint(::Type{NormNuclearBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormNuclearCone}) where T = true |
| 129 | +MOIB.added_constrained_variable_types(::Type{<:NormNuclearBridge}) = Tuple{DataType}[] |
| 130 | +MOIB.added_constraint_types(::Type{NormNuclearBridge{T, F, G, H}}) where {T, F, G, H} = [(F, MOI.GreaterThan{T}), (G, MOI.PositiveSemidefiniteConeTriangle)] |
| 131 | +function concrete_bridge_type(::Type{<:NormNuclearBridge{T}}, H::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.NormNuclearCone}) where T |
| 132 | + S = MOIU.scalar_type(H) |
| 133 | + F = MOIU.promote_operation(-, T, S, MOIU.promote_operation(/, T, MOIU.promote_operation(+, T, T, T), T)) |
| 134 | + G = MOIU.promote_operation(vcat, T, MOI.VectorOfVariables, H) |
| 135 | + return NormNuclearBridge{T, F, G, H} |
| 136 | +end |
| 137 | + |
| 138 | +# Attributes, Bridge acting as a model |
| 139 | +MOI.get(bridge::NormNuclearBridge, ::MOI.NumberOfVariables) = length(bridge.U) + length(bridge.V) |
| 140 | +MOI.get(bridge::NormNuclearBridge, ::MOI.ListOfVariableIndices) = vcat(bridge.U, bridge.V) |
| 141 | +MOI.get(bridge::NormNuclearBridge{T, F, G, H}, ::MOI.NumberOfConstraints{F, MOI.GreaterThan{T}}) where {T, F, G, H} = 1 |
| 142 | +MOI.get(bridge::NormNuclearBridge{T, F, G, H}, ::MOI.NumberOfConstraints{G, MOI.PositiveSemidefiniteConeTriangle}) where {T, F, G, H} = 1 |
| 143 | +MOI.get(bridge::NormNuclearBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{F, MOI.GreaterThan{T}}) where {T, F, G, H} = [bridge.ge_index] |
| 144 | +MOI.get(bridge::NormNuclearBridge{T, F, G, H}, ::MOI.ListOfConstraintIndices{G, MOI.PositiveSemidefiniteConeTriangle}) where {T, F, G, H} = [bridge.psd_index] |
| 145 | + |
| 146 | +# References |
| 147 | +function MOI.delete(model::MOI.ModelLike, bridge::NormNuclearBridge) |
| 148 | + MOI.delete(model, bridge.ge_index) |
| 149 | + MOI.delete(model, bridge.psd_index) |
| 150 | + MOI.delete(model, bridge.U) |
| 151 | + MOI.delete(model, bridge.V) |
| 152 | +end |
| 153 | + |
| 154 | +# Attributes, Bridge acting as a constraint |
| 155 | +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintFunction, bridge::NormNuclearBridge{T, F, G, H}) where {T, F, G, H} |
| 156 | + ge_func = MOI.get(model, MOI.ConstraintFunction(), bridge.ge_index) |
| 157 | + psd_func = MOIU.eachscalar(MOI.get(model, MOI.ConstraintFunction(), bridge.psd_index)) |
| 158 | + column_dim = bridge.column_dim |
| 159 | + side_dim = bridge.row_dim + column_dim |
| 160 | + t = MOIU.operate(+, T, ge_func, MOIU.operate(/, T, MOIU.operate(+, T, [psd_func[trimap(i, i)] for i in 1:side_dim]...), T(2))) |
| 161 | + t = MOIU.remove_variable(MOIU.remove_variable(t, bridge.U), bridge.V) |
| 162 | + X = psd_func[[trimap(i, j) for j in 1:bridge.column_dim for i in (bridge.column_dim + 1):side_dim]] |
| 163 | + return MOIU.convert_approx(H, MOIU.operate(vcat, T, t, X)) |
| 164 | +end |
| 165 | +MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet, bridge::NormNuclearBridge) = MOI.NormNuclearCone(bridge.row_dim, bridge.column_dim) |
| 166 | +MOI.supports(::MOI.ModelLike, ::MOI.ConstraintDualStart, ::Type{<:NormNuclearBridge}) = true |
| 167 | +function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintPrimal, bridge::NormNuclearBridge) |
| 168 | + ge_primal = MOI.get(model, MOI.ConstraintPrimal(), bridge.ge_index) |
| 169 | + psd_primal = MOI.get(model, MOI.ConstraintPrimal(), bridge.psd_index) |
| 170 | + side_dim = bridge.row_dim + bridge.column_dim |
| 171 | + t = ge_primal + sum(psd_primal[trimap(i, i)] for i in 1:side_dim) / 2 |
| 172 | + X = psd_primal[[trimap(i, j) for j in 1:bridge.column_dim for i in (bridge.column_dim + 1):side_dim]] |
| 173 | + return vcat(t, X) |
| 174 | +end |
| 175 | +# Given t is dual on GreaterThan constraint and [U X'; X V] is dual on PSD constraint, |
| 176 | +# the dual on NormNuclearCone constraint is (t, 2X) in NormNuclearCone. |
| 177 | +function MOI.get(model::MOI.ModelLike, attr::Union{MOI.ConstraintDual, MOI.ConstraintDualStart}, bridge::NormNuclearBridge) |
| 178 | + t = MOI.get(model, attr, bridge.ge_index) |
| 179 | + psd_dual = MOI.get(model, attr, bridge.psd_index) |
| 180 | + side_dim = bridge.row_dim + bridge.column_dim |
| 181 | + X = 2 * psd_dual[[trimap(i, j) for j in 1:bridge.column_dim for i in (bridge.column_dim + 1):side_dim]] |
| 182 | + return vcat(t, X) |
| 183 | +end |
| 184 | +function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintDualStart, bridge::NormNuclearBridge{T}, value) where T |
| 185 | + MOI.set(model, MOI.ConstraintDualStart(), bridge.ge_index, value[1]) |
| 186 | + column_dim = bridge.column_dim |
| 187 | + side_dim = bridge.row_dim + column_dim |
| 188 | + dual = zeros(T, div(side_dim * (side_dim + 1), 2)) |
| 189 | + for i in 1:side_dim |
| 190 | + dual[trimap(i, i)] = value[1] |
| 191 | + end |
| 192 | + X_idx = 2 |
| 193 | + for j in 1:column_dim, i in (column_dim + 1):side_dim |
| 194 | + dual[trimap(i, j)] = value[X_idx] / 2 |
| 195 | + X_idx += 1 |
| 196 | + end |
| 197 | + MOI.set(model, MOI.ConstraintDualStart(), bridge.psd_index, dual) |
| 198 | + return |
| 199 | +end |
0 commit comments