├── test ├── REQUIRE ├── runtests.jl ├── prob.dat-s └── sdinterface.jl ├── .codecov.yml ├── .gitignore ├── REQUIRE ├── .github └── workflows │ └── TagBot.yml ├── src ├── SD.jl ├── SemidefiniteModels.jl ├── conic_sdpa.jl └── sd_to_conic.jl ├── README.md ├── .travis.yml ├── LICENSE.md └── appveyor.yml /test/REQUIRE: -------------------------------------------------------------------------------- 1 | CSDP 2 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.*.cov 3 | *.jl.mem 4 | -------------------------------------------------------------------------------- /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.6 2 | MathProgBase 3 | GZip 4 | Compat 0.17 5 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using SemidefiniteModels 2 | using Compat 3 | using Compat.Test 4 | using Compat.LinearAlgebra 5 | 6 | include("sdinterface.jl") 7 | using CSDP 8 | sdtest(CSDP.CSDPSolver(), duals=true) 9 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | schedule: 4 | - cron: 0 * * * * 5 | jobs: 6 | TagBot: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: JuliaRegistries/TagBot@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /test/prob.dat-s: -------------------------------------------------------------------------------- 1 | 2 2 | 3 3 | 2 3 -2 4 | 1 2 5 | 0 1 1 1 2 6 | 0 1 1 2 1 7 | 0 1 2 2 2 8 | 0 2 1 1 3 9 | 0 2 1 3 1 10 | 0 2 2 2 2 11 | 0 2 3 3 3 12 | 1 1 1 1 3 13 | 1 1 1 2 1 14 | 1 1 2 2 3 15 | 1 3 1 1 1 16 | 2 2 1 1 3 17 | 2 2 2 2 4 18 | 2 2 1 3 1 19 | 2 2 3 3 5 20 | 2 3 2 2 1 21 | -------------------------------------------------------------------------------- /src/SD.jl: -------------------------------------------------------------------------------- 1 | # Methods for the Semidefinite interface 2 | 3 | @compat abstract type AbstractSDModel <: MPB.AbstractMathProgModel end 4 | export AbstractSDModel 5 | 6 | MPB.@define_interface begin 7 | SDModel 8 | setconstrB! 9 | setconstrentry! 10 | setobjentry! 11 | end 12 | -------------------------------------------------------------------------------- /src/SemidefiniteModels.jl: -------------------------------------------------------------------------------- 1 | module SemidefiniteModels 2 | 3 | using Compat 4 | using Compat.LinearAlgebra 5 | using Compat.SparseArrays 6 | 7 | import MathProgBase 8 | const MPB = MathProgBase.SolverInterface 9 | 10 | include("SD.jl") 11 | 12 | include("sd_to_conic.jl") 13 | include("conic_sdpa.jl") 14 | 15 | end # module 16 | -------------------------------------------------------------------------------- /test/sdinterface.jl: -------------------------------------------------------------------------------- 1 | using Compat 2 | using Compat.Test 3 | using SemidefiniteModels 4 | import MathProgBase 5 | const MPB = MathProgBase.SolverInterface 6 | 7 | function sdtest(solver::MathProgBase.AbstractMathProgSolver; duals=false, tol=1e-6) 8 | @testset "Testing SDModel with $solver" begin 9 | m = MPB.ConicModel(solver) 10 | MPB.loadproblem!(m, "prob.dat-s") 11 | MPB.optimize!(m) 12 | @test MPB.status(m) == :Optimal 13 | @test isapprox(MPB.getobjval(m), 2.75) 14 | @test norm(MPB.getsolution(m) - [.75, 1.]) < tol 15 | if duals 16 | @test norm(MPB.getdual(m) - [0, 0, .125, .125*sqrt(2), .125, 2/3, 0, 0, 0, 0, 0]) < tol 17 | @test norm(MPB.getvardual(m)) < tol 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SemidefiniteModels 2 | 3 | [![Build Status](https://travis-ci.org/JuliaOpt/SemidefiniteModels.jl.svg?branch=master)](https://travis-ci.org/JuliaOpt/SemidefiniteModels.jl) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/cxyq8mnav06gkcw9?svg=true)](https://ci.appveyor.com/project/blegat/semidefinitemodels-jl) 5 | [![Coverage Status](https://coveralls.io/repos/JuliaOpt/SemidefiniteModels.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/JuliaOpt/SemidefiniteModels.jl?branch=master) 6 | [![codecov.io](http://codecov.io/github/JuliaOpt/SemidefiniteModels.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaOpt/SemidefiniteModels.jl?branch=master) 7 | 8 | This package extends MathProgBase with `SDModel` representing a semidefinite programming problem in the following form 9 | ``` 10 | max ⟨C, X⟩ min ⟨b, y⟩ 11 | ⟨A_i, X⟩ = b_i ∑ A_i y_i ⪯ C 12 | X ⪰ 0 13 | ``` 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Documentation: http://docs.travis-ci.com/user/languages/julia/ 2 | language: julia 3 | os: 4 | - linux 5 | - osx 6 | julia: 7 | - 0.6 8 | - 0.7 9 | - 1.0 10 | notifications: 11 | email: false 12 | # uncomment the following lines to override the default test script 13 | #script: 14 | # - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi 15 | # - julia -e 'Pkg.clone(pwd()); Pkg.build("SemidefiniteModels"); Pkg.test("SemidefiniteModels"; coverage=true)' 16 | before_install: 17 | - | 18 | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 19 | sudo apt-get install liblapack-dev libblas-dev; # For CSDP 20 | fi 21 | after_success: 22 | # push coverage results to Coveralls 23 | - julia -e 'cd(Pkg.dir("SemidefiniteModels")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' 24 | # push coverage results to Codecov 25 | - julia -e 'cd(Pkg.dir("SemidefiniteModels")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The SemidefiniteModels.jl package is licensed under the MIT "Expat" License: 2 | 3 | > Copyright (c) 2016: Benoît Legat. 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in all 13 | > copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | > SOFTWARE. 22 | > 23 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - julia_version: 0.6 4 | - julia_version: 0.7 5 | - julia_version: 1 6 | 7 | platform: 8 | - x86 # 32-bit 9 | - x64 # 64-bit 10 | 11 | # # Uncomment the following lines to allow failures on nightly julia 12 | # # (tests will run but not make your overall status red) 13 | # matrix: 14 | # allow_failures: 15 | # - julia_version: latest 16 | 17 | branches: 18 | only: 19 | - master 20 | - /release-.*/ 21 | 22 | notifications: 23 | - provider: Email 24 | on_build_success: false 25 | on_build_failure: false 26 | on_build_status_changed: false 27 | 28 | install: 29 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) 30 | 31 | build_script: 32 | - echo "%JL_BUILD_SCRIPT%" 33 | - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" 34 | 35 | test_script: 36 | - echo "%JL_TEST_SCRIPT%" 37 | - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" 38 | 39 | # # Uncomment to support code coverage upload. Should only be enabled for packages 40 | # # which would have coverage gaps without running on Windows 41 | # on_success: 42 | # - echo "%JL_CODECOV_SCRIPT%" 43 | # - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" 44 | -------------------------------------------------------------------------------- /src/conic_sdpa.jl: -------------------------------------------------------------------------------- 1 | # Code written by Christopher Coey 2 | 3 | # Reads a SDPA file to create a MathProgBase conic model 4 | # Detects an LP block but does not currently detect SOC or SOCRotated cones 5 | # SDPA sparse format modified for integer constraints described at 6 | # http://www.opt.tu-darmstadt.de/scipsdp/downloads/data_format.txt 7 | 8 | using GZip 9 | 10 | function MPB.loadproblem!(m::MPB.AbstractConicModel, filename::AbstractString; all_bin=false) 11 | if endswith(filename,".dat-s") || endswith(filename,".dat-s.gz") 12 | loadsdpa!(m, filename) 13 | else 14 | error("unrecognized input format extension in $filename") 15 | end 16 | end 17 | 18 | function loadsdpa!(m::MPB.AbstractConicModel, filename::String; all_bin=false) 19 | if endswith(filename, ".gz") 20 | fd = gzopen(filename, "r") 21 | else 22 | fd = open(filename, "r") 23 | end 24 | (c, A, b, concones, varcones, vartypes) = sdpatoconicdata(fd, all_bin) 25 | close(fd) 26 | 27 | MathProgBase.loadproblem!(m, c, A, b, concones, varcones) 28 | MathProgBase.setvartype!(m, vartypes) 29 | end 30 | 31 | function sdpatoconicdata(io::IO, all_bin::Bool) 32 | numvars = 0 33 | numblocks = 0 34 | sizeblocks = Vector{Int}() 35 | c = Vector{Float64}() 36 | indline = 0 37 | 38 | while !eof(io) 39 | line = strip(readline(io)) 40 | 41 | if !startswith(line, '*') && !startswith(line, '#') && !startswith(line, '\"') && length(line) > 0 42 | indline += 1 43 | spline = split(line) 44 | if indline == 1 45 | numvars = parse(Int, spline[1]) 46 | elseif indline == 2 47 | numblocks = parse(Int, spline[1]) 48 | elseif indline == 3 49 | sizeblocks = [parse(Int, spline[block]) for block in 1:numblocks] 50 | elseif indline == 4 51 | c = [parse(Float64, spline[col]) for col in 1:numvars] 52 | break 53 | end 54 | end 55 | end 56 | 57 | LPblocks = findall(size -> (sign(size) == -1), sizeblocks) 58 | LPblock = isempty(LPblocks) ? 0 : first(LPblocks) 59 | PSDblocks = findall(size -> (sign(size) == 1), sizeblocks) 60 | 61 | if LPblock != 0 62 | sizeblocks[LPblock] *= -1 63 | end 64 | 65 | numcons = sum(binomial((sizeblocks[block] + 1), 2) for block in PSDblocks) 66 | if LPblock != 0 67 | numcons += sizeblocks[LPblock] 68 | end 69 | b = zeros(numcons) 70 | A = spzeros(numcons, numvars) 71 | 72 | bPSDs = SparseMatrixCSC{Float64,Int64}[spzeros(size, size) for size in sizeblocks] 73 | APSDs = SparseMatrixCSC{Float64,Int64}[spzeros(size, size) for size in sizeblocks, col in 1:numvars] 74 | 75 | while !eof(io) 76 | line = strip(readline(io)) 77 | 78 | if !startswith(line, '*') && !startswith(line, '#') && !startswith(line, '"') && length(line) > 0 79 | linevec = split(line) 80 | col = parse(Int, linevec[1]) 81 | block = parse(Int, linevec[2]) 82 | i = parse(Int, linevec[3]) 83 | j = parse(Int, linevec[4]) 84 | v = parse(Float64, linevec[5]) 85 | 86 | if block == LPblock 87 | @assert i == j 88 | if col == 0 89 | b[i] = -v 90 | else 91 | A[i,col] = -v 92 | end 93 | else 94 | if col == 0 95 | bPSDs[block][i,j] = -v 96 | else 97 | APSDs[block,col][i,j] = -v 98 | end 99 | end 100 | elseif startswith(line, "*INTEGER") 101 | break 102 | end 103 | end 104 | 105 | dropzeros!(A) 106 | 107 | function ind_svec(n::Int, i::Int, j::Int)::Int 108 | indmin = min(i,j) 109 | indmax = max(i,j) 110 | 111 | accum = indmax + 1 - indmin 112 | for k in 1:(indmin - 1) 113 | accum += n + 1 - k 114 | end 115 | return accum 116 | end 117 | 118 | varcones = Tuple{Symbol,Vector{Int}}[(:Free, collect(1:numvars))] 119 | 120 | concones = Tuple{Symbol,Vector{Int}}[] 121 | if LPblock != 0 122 | rowend = sizeblocks[LPblock] 123 | push!(concones, (:NonNeg, collect(1:rowend))) 124 | else 125 | rowend = 0 126 | end 127 | 128 | sqrt2 = sqrt(2) 129 | 130 | for block in 1:numblocks 131 | if block == LPblock 132 | continue 133 | end 134 | 135 | rowendprev = rowend 136 | 137 | dropzeros!(bPSDs[block]) 138 | (I,J,V) = findnz(bPSDs[block]) 139 | for nzind in 1:length(V) 140 | row = rowendprev + ind_svec(sizeblocks[block], I[nzind], J[nzind]) 141 | b[row] = (I[nzind] == J[nzind]) ? V[nzind] : V[nzind]*sqrt2 142 | end 143 | 144 | for col in 1:numvars 145 | dropzeros!(APSDs[block,col]) 146 | (I,J,V) = findnz(APSDs[block,col]) 147 | for nzind in 1:length(V) 148 | row = rowendprev + ind_svec(sizeblocks[block], I[nzind], J[nzind]) 149 | A[row,col] = (I[nzind] == J[nzind]) ? V[nzind] : V[nzind]*sqrt2 150 | end 151 | end 152 | 153 | rowend = rowendprev + binomial((sizeblocks[block] + 1), 2) 154 | push!(concones, (:SDP, collect(rowendprev+1:rowend))) 155 | end 156 | 157 | vartypes = fill(:Cont, numvars) 158 | 159 | while !eof(io) 160 | line = strip(readline(io)) 161 | 162 | if startswith(line, '*') 163 | col = parse(Int, split(strip(line[2:end]))[1]) 164 | vartypes[col] = all_bin ? :Bin : :Int 165 | end 166 | end 167 | 168 | return (c, A, b, concones, varcones, vartypes) 169 | end 170 | -------------------------------------------------------------------------------- /src/sd_to_conic.jl: -------------------------------------------------------------------------------- 1 | # wrapper to convert SD solver into Conic solver 2 | 3 | # To enable Conic support from an SD solver, define, e.g., 4 | # ConicModel(s::CSDPSolver) = SDtoConicBridge(SDModel(s)) 5 | 6 | mutable struct SDtoConicBridge <: MPB.AbstractConicModel 7 | sdmodel::AbstractSDModel 8 | varmap 9 | varnewconstrmap 10 | constrmap 11 | constrscaling 12 | c 13 | A 14 | b 15 | constr_cones 16 | var_cones 17 | end 18 | 19 | # FIXME implements supportedcones 20 | 21 | SDtoConicBridge(m::AbstractSDModel) = SDtoConicBridge(m, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing) 22 | 23 | export SDtoConicBridge 24 | 25 | MPB.numvar(m::SDtoConicBridge) = size(m.A,2) 26 | MPB.numconstr(m::SDtoConicBridge) = size(m.A,1) 27 | 28 | function getmatdim(k) 29 | # n*(n+1)/2 = k 30 | # n^2+n-2k = 0 31 | # (-1 + sqrt(1 + 8k))/2 32 | n = (-1 + sqrt(1 + 8k)) / 2 33 | if n * (n+1) != 2*k 34 | error("sd dim not consistent") 35 | end 36 | convert(Int, n) 37 | end 38 | 39 | # To transform Conic problems into SD problems 40 | function MPB.loadproblem!(model::SDtoConicBridge, c, A, b, constr_cones, var_cones) 41 | m, n = size(A) 42 | model.c = c 43 | model.A = A 44 | model.b = b 45 | model.constr_cones = constr_cones 46 | model.var_cones = var_cones 47 | 48 | # Conic form LP form 49 | # min c'x min c'x 50 | # st b-Ax ∈ K_1 st lb <= Ax <= b 51 | # x ∈ K_2 l <= x <= u 52 | 53 | # If a cone is anything other than [:Free,:Zero,:NonNeg,:NonPos,:SOC,:SOCRotated,:SDP], give up. 54 | bad_cones = [:ExpPrimal, :ExpDual] 55 | for (cone,_) in var_cones 56 | cone in bad_cones && error("Cone type $(cone) not supported") 57 | end 58 | for (cone,_) in constr_cones 59 | cone in bad_cones && error("Cone type $(cone) not supported") 60 | end 61 | 62 | blk = 0 63 | blkdims = Int[] 64 | socblks = Int[] 65 | socblksrotated = BitSet() 66 | socblksvarconemap = Int[] 67 | # For a variable at column index `col' in the conic model, 68 | # varmap[col] gives an array such that each coefficient A[.,col] should be replaced by the sum, 69 | # over each element (blk, i, j, coef) of the array of 70 | # X[blk][i,j] * (A[.,col] * coef) 71 | # Where X[blk] is the blk'th block of X 72 | model.varmap = varmap = Vector{Vector{Tuple{Int,Int,Int,Float64}}}(undef, n) 73 | varconeidx = 0 74 | for (cone,idxs) in var_cones 75 | varconeidx += 1 76 | # If a cone is anything other than [:Free,:Zero,:NonNeg,:NonPos,:SOC,:SDP], give up. 77 | if cone == :Free 78 | for i in idxs 79 | blk += 2 80 | push!(blkdims, 1) 81 | push!(blkdims, 1) 82 | # x free transformed into x = y - z with y, z >= 0 83 | varmap[i] = [(blk-1,1,1,1.), (blk,1,1,-1.)] 84 | end 85 | elseif cone == :Zero 86 | for i in idxs 87 | varmap[i] = [] 88 | end 89 | elseif cone == :NonNeg 90 | for i in idxs 91 | blk += 1 92 | push!(blkdims, 1) 93 | varmap[i] = [(blk,1,1,1.)] 94 | end 95 | elseif cone == :NonPos 96 | for i in idxs 97 | blk += 1 98 | push!(blkdims, 1) 99 | varmap[i] = [(blk,1,1,-1.)] 100 | end 101 | elseif cone == :SOC 102 | blk += 1 103 | push!(blkdims, length(idxs)) 104 | for i in 1:length(idxs) 105 | varmap[idxs[i]] = [(blk, 1, i, i == 1 ? 1.0 : 0.5)] 106 | end 107 | push!(socblks, blk) 108 | push!(socblksvarconemap, varconeidx) 109 | elseif cone == :SOCRotated 110 | blk += 1 111 | push!(blkdims, length(idxs)-1) 112 | varmap[idxs[1]] = [(blk,1,1,1.)] 113 | varmap[idxs[2]] = [(blk,2,2,.5)] 114 | for i in 3:length(idxs) 115 | varmap[idxs[i]] = [(blk,1,i-1,.5)] 116 | end 117 | push!(socblks, blk) 118 | push!(socblksrotated, length(socblks)) 119 | push!(socblksvarconemap, varconeidx) 120 | elseif cone == :SDP 121 | d = getmatdim(length(idxs)) 122 | k = 0 123 | blk += 1 124 | push!(blkdims, d) 125 | for i in 1:d 126 | for j in i:d 127 | k += 1 128 | # In the MPB conic model, those are scaled by sqrt(2) 129 | coef = i == j ? 1.0 : inv(√2) 130 | varmap[idxs[k]] = [(blk,i,j,coef)] 131 | end 132 | end 133 | else 134 | throw(ArgumentError("Unrecognized cone $cone")) 135 | end 136 | end 137 | @assert blk == length(blkdims) 138 | constr = 0 139 | # For the constraint at row index `row' in the conic model, 140 | # constrmap[row] gives the index of the constraint in the SD model, 141 | # a value of 0 meaning that it does not correspond to any constraint 142 | model.constrmap = constrmap = Vector{Int}(undef, m) 143 | constrmapcheck = BitSet() 144 | # slackmap[row] gives (blk,i,j,coef) indicating that a slack variable has been created at X[blk][i,j] with coefficient coef 145 | # blk=0 corresponds to no slack 146 | slackmap = Vector{Tuple{Int,Int,Int,Float64}}(undef, m) 147 | model.constrscaling = constrscaling = ones(Float64, m) 148 | for (cone,idxs) in constr_cones 149 | if cone == :Free 150 | for idx in idxs 151 | push!(constrmapcheck, idx) 152 | end 153 | constrmap[idxs] = 0 154 | slackmap[idxs] = 0 155 | else 156 | for idx in idxs 157 | constr += 1 158 | push!(constrmapcheck, idx) 159 | constrmap[idx] = constr 160 | end 161 | if cone == :Zero 162 | for idx in idxs 163 | slackmap[idx] = (0,0,0,0.) 164 | end 165 | elseif cone == :NonNeg 166 | for idx in idxs 167 | blk += 1 168 | push!(blkdims, 1) 169 | slackmap[idx] = (blk,1,1,1.) 170 | end 171 | elseif cone == :NonPos 172 | for idx in idxs 173 | blk += 1 174 | push!(blkdims, 1) 175 | slackmap[idx] = (blk,1,1,-1.) 176 | end 177 | elseif cone == :SOC 178 | blk += 1 179 | push!(blkdims, length(idxs)) 180 | for i in 1:length(idxs) 181 | slackmap[idxs[i]] = (blk,1,i,i == 1 ? 1. : .5) 182 | end 183 | push!(socblks, blk) 184 | push!(socblksvarconemap, 0) 185 | elseif cone == :SOCRotated 186 | blk += 1 187 | push!(blkdims, length(idxs)-1) 188 | slackmap[idxs[1]] = (blk,1,1,1.) 189 | slackmap[idxs[2]] = (blk,2,2,.5) 190 | for i in 3:length(idxs) 191 | slackmap[idxs[i]] = (blk,1,i-1,.5) 192 | end 193 | push!(socblks, blk) 194 | push!(socblksrotated, length(socblks)) 195 | push!(socblksvarconemap, 0) 196 | elseif cone == :SDP 197 | d = getmatdim(length(idxs)) 198 | k = 0 199 | blk += 1 200 | push!(blkdims, d) 201 | for i in 1:d 202 | for j in i:d 203 | k += 1 204 | slackmap[idxs[k]] = (blk,i,j,i == j ? 1. : .5) 205 | if i != j 206 | constrscaling[idxs[k]] = 1/sqrt(2) 207 | end 208 | end 209 | end 210 | else 211 | throw(ArgumentError("Unrecognized cone $cone")) 212 | end 213 | end 214 | end 215 | if constrmapcheck != BitSet(1:m) 216 | throw(ArgumentError("Some variable have no cone")) 217 | end 218 | @assert blk == length(blkdims) 219 | 220 | socconstr = Vector{Int}(undef, length(socblks)) 221 | for i in 1:length(socblks) 222 | blk = socblks[i] 223 | d = blkdims[blk] 224 | socconstr[i] = constr 225 | nconstr = d*(d-1) 226 | if i in socblksrotated 227 | nconstr -= 1 228 | end 229 | constr += div(nconstr, 2) 230 | end 231 | 232 | # Writing the sparse block diagonal matrices 233 | sdmodel = model.sdmodel 234 | MPB.loadproblem!(sdmodel, blkdims, constr) 235 | for row in 1:m 236 | if constrmap[row] != 0 237 | setconstrB!(sdmodel, b[row] * constrscaling[row], constrmap[row]) 238 | end 239 | blk, i, j, coef = slackmap[row] 240 | if blk != 0 241 | @assert coef != 0 242 | setconstrentry!(sdmodel, coef, constrmap[row], blk, i, j) 243 | end 244 | end 245 | if isa(A, SparseMatrixCSC) 246 | rows = rowvals(A) 247 | vals = nonzeros(A) 248 | for col in 1:n 249 | for k in nzrange(A, col) 250 | row = rows[k] 251 | if constrmap[row] != 0 # Free constraint 252 | val = vals[k] 253 | for (blk, i, j, coef) in varmap[col] 254 | @assert coef != 0 255 | setconstrentry!(sdmodel, val*coef*constrscaling[row], constrmap[row], blk, i, j) 256 | end 257 | end 258 | end 259 | end 260 | else 261 | for row in 1:m 262 | if constrmap[row] != 0 263 | for col in 1:n 264 | val = A[row,col] 265 | if val != 0 266 | for (blk, i, j, coef) in varmap[col] 267 | @assert coef != 0 268 | setconstrentry!(sdmodel, val*coef*constrscaling[row], constrmap[row], blk, i, j) 269 | end 270 | end 271 | end 272 | end 273 | end 274 | end 275 | model.varnewconstrmap = varnewconstrmap = Dict{NTuple{3, Int}, Vector{Tuple{Int,Float64}}}() 276 | for k in 1:length(socblks) 277 | constr = socconstr[k] 278 | blk = socblks[k] 279 | diageq = k in socblksrotated ? 2 : 1 280 | d = blkdims[blk] 281 | for i in 2:d 282 | for j in i:d 283 | if i != j || i > diageq 284 | constr += 1 285 | setconstrB!(sdmodel, 0, constr) 286 | setconstrentry!(sdmodel, i==j ? 1. : .5, constr, blk, i, j) 287 | if i == j && i > diageq 288 | varcone = socblksvarconemap[k] 289 | if varcone != 0 290 | _, idxs = var_cones[varcone] 291 | key = (blk, diageq, diageq) 292 | if !haskey(varnewconstrmap, key) 293 | varnewconstrmap[key] = Tuple{Int, Float64}[] 294 | end 295 | push!(varnewconstrmap[key], (constr, -1.)) 296 | end 297 | setconstrentry!(sdmodel, -1., constr, blk, diageq, diageq) 298 | end 299 | end 300 | end 301 | end 302 | end 303 | for col in 1:n 304 | if c[col] != 0 305 | for (blk, i, j, coef) in varmap[col] 306 | # in SDP format, it is max and in MPB Conic format it is min 307 | setobjentry!(sdmodel, -coef*c[col], blk, i, j) 308 | end 309 | end 310 | end 311 | end 312 | 313 | MPB.optimize!(model::SDtoConicBridge) = MPB.optimize!(model.sdmodel) 314 | MPB.status(model::SDtoConicBridge) = MPB.status(model.sdmodel) 315 | 316 | function MPB.getobjval(model::SDtoConicBridge) 317 | -MPB.getobjval(model.sdmodel) 318 | end 319 | 320 | function MPB.getsolution(model::SDtoConicBridge) 321 | X = MPB.getsolution(model.sdmodel) 322 | n = size(model.A, 2) 323 | x = zeros(Float64, n) 324 | for col in 1:n 325 | for (blk, i, j, coef) in model.varmap[col] 326 | if i != j 327 | coef *= 2 328 | end 329 | x[col] += X[blk][i,j] * coef 330 | end 331 | end 332 | x 333 | end 334 | 335 | function MPB.getdual(model::SDtoConicBridge) 336 | y = MPB.getdual(model.sdmodel) 337 | constrmap = model.constrmap 338 | constrscaling = model.constrscaling 339 | m = size(model.A, 1) 340 | dual = Vector{Float64}(undef, m) 341 | for row in 1:m 342 | if constrmap[row] != 0 343 | dual[row] = y[constrmap[row]] * constrscaling[row] 344 | else 345 | dual[row] = 0 346 | end 347 | end 348 | dual 349 | end 350 | 351 | function MPB.getvardual(model::SDtoConicBridge) 352 | y = MPB.getdual(model.sdmodel) 353 | Z = MPB.getvardual(model.sdmodel) 354 | n = size(model.A, 2) 355 | z = zeros(Float64, n) 356 | for col in 1:n 357 | for (blk, i, j, coef) in model.varmap[col] 358 | cur = Z[blk][i,j] 359 | if haskey(model.varnewconstrmap, (blk, i, j)) 360 | for (constr, ccoef) in model.varnewconstrmap[(blk, i, j)] 361 | cur -= y[constr] * ccoef 362 | end 363 | end 364 | z[col] += cur / coef 365 | end 366 | end 367 | z 368 | end 369 | 370 | function MPB.getvartype(model::SDtoConicBridge, col) 371 | # they are all supposed to be the same so we take the first one 372 | blk, i, j, _ = first(model.varmap[col]) 373 | MPB.getvartype(model.sdmodel, blk, i, j) 374 | end 375 | 376 | function MPB.setvartype!(model::SDtoConicBridge, vtype) 377 | for (col, vt) in enumerate(vtype) 378 | for (blk, i, j, _) in model.varmap[col] 379 | MPB.setvartype!(model.sdmodel, vt, blk, i, j) 380 | end 381 | end 382 | end 383 | 384 | for f in MPB.methods_by_tag[:rewrap] 385 | @eval MPB.$f(model::SDtoConicBridge) = MPB.$f(model.sdmodel) 386 | end 387 | --------------------------------------------------------------------------------