├── .gitignore ├── test ├── psd_var_only.cbf ├── example4.cbf ├── example3.cbf ├── example1.cbf └── runtests.jl ├── .travis.yml ├── src ├── jump_to_cbf.jl ├── convex_to_cbf.jl ├── ConicBenchmarkUtilities.jl ├── cbf_output.jl ├── cbf_input.jl ├── preprocess_mpb.jl └── mpb.jl ├── appveyor.yml ├── Project.toml ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Manifest.toml 2 | 3 | *.jl.cov 4 | *.jl.*.cov 5 | *.jl.mem 6 | -------------------------------------------------------------------------------- /test/psd_var_only.cbf: -------------------------------------------------------------------------------- 1 | # Generated by ConicBenchmarkUtilities.jl 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MIN 7 | 8 | PSDVAR 9 | 1 10 | 2 11 | 12 | VAR 13 | 0 0 14 | 15 | CON 16 | 0 0 17 | 18 | OBJFCOORD 19 | 1 20 | 0 0 0 1.0 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | os: 3 | - linux 4 | - osx 5 | julia: 6 | - 1.0 7 | - 1.3 8 | notifications: 9 | email: false 10 | after_success: 11 | - julia -e '(VERSION >= v"0.7" && using Pkg); Pkg.add("Coverage"); cd(Pkg.dir("ConicBenchmarkUtilities")); using Coverage; Codecov.submit(process_folder())' 12 | -------------------------------------------------------------------------------- /test/example4.cbf: -------------------------------------------------------------------------------- 1 | # Example C.4 from the CBF documentation version 2 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MAX 7 | 8 | VAR 9 | 2 1 10 | L+ 2 11 | 12 | CON 13 | 2 2 14 | L- 1 15 | L+ 1 16 | 17 | OBJACOORD 18 | 2 19 | 0 1.0 20 | 1 0.64 21 | 22 | ACOORD 23 | 4 24 | 0 0 50.0 25 | 1 0 3.0 26 | 0 1 31.0 27 | 1 1 -2.0 28 | 29 | BCOORD 30 | 2 31 | 0 -250.0 32 | 1 4.0 33 | -------------------------------------------------------------------------------- /src/jump_to_cbf.jl: -------------------------------------------------------------------------------- 1 | 2 | function jump_to_cbf(problem, name, filename) 3 | # these are internal functions 4 | c, A, b, var_cones, con_cones = Core.eval(Main, :(JuMP.conicdata($problem))) 5 | vartypes = Core.eval(Main, :(JuMP.vartypes_without_fixed($problem))) 6 | 7 | dat = mpbtocbf(name, c, A, b, con_cones, var_cones, vartypes, :Min) 8 | 9 | writecbfdata(filename, dat) 10 | end 11 | -------------------------------------------------------------------------------- /src/convex_to_cbf.jl: -------------------------------------------------------------------------------- 1 | 2 | function convex_to_cbf(problem, name, filename) 3 | # work-around for optional dependency on Convex 4 | c, A, b, cones, var_to_ranges, vartypes, conic_constraints = eval(Main,:(Convex.conic_problem($problem))) 5 | var_cones = fill((:Free, 1:size(A, 2)),1) 6 | 7 | dat = mpbtocbf(name, vec(full(c)), A, vec(full(b)), cones, var_cones, vartypes, :Min) 8 | 9 | writecbfdata(filename, dat) 10 | end 11 | -------------------------------------------------------------------------------- /test/example3.cbf: -------------------------------------------------------------------------------- 1 | # Example C.3 from the CBF documentation version 2 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MIN 7 | 8 | PSDVAR 9 | 1 10 | 2 11 | 12 | VAR 13 | 2 1 14 | F 2 15 | 16 | PSDCON 17 | 1 18 | 2 19 | 20 | CON 21 | 1 1 22 | L+ 1 23 | 24 | OBJFCOORD 25 | 2 26 | 0 0 0 1.0 27 | 0 1 1 1.0 28 | 29 | OBJACOORD 30 | 2 31 | 0 1.0 32 | 1 1.0 33 | 34 | OBJBCOORD 35 | 1.0 36 | 37 | FCOORD 38 | 1 39 | 0 0 1 0 1.0 40 | 41 | ACOORD 42 | 2 43 | 0 0 -1.0 44 | 0 1 -1.0 45 | 46 | HCOORD 47 | 4 48 | 0 0 1 0 1.0 49 | 0 0 1 1 3.0 50 | 0 1 0 0 3.0 51 | 0 1 1 0 1.0 52 | 53 | DCOORD 54 | 2 55 | 0 0 0 -1.0 56 | 0 1 1 -1.0 57 | -------------------------------------------------------------------------------- /test/example1.cbf: -------------------------------------------------------------------------------- 1 | # Example C.1 from the CBF documentation version 2 2 | VER 3 | 2 4 | 5 | OBJSENSE 6 | MIN 7 | 8 | PSDVAR 9 | 1 10 | 3 11 | 12 | VAR 13 | 3 1 14 | F 3 15 | 16 | CON 17 | 5 2 18 | L= 2 19 | Q 3 20 | 21 | OBJFCOORD 22 | 5 23 | 0 0 0 2.0 24 | 0 1 0 1.0 25 | 0 1 1 2.0 26 | 0 2 1 1.0 27 | 0 2 2 2.0 28 | 29 | OBJACOORD 30 | 1 31 | 1 1.0 32 | 33 | FCOORD 34 | 9 35 | 0 0 0 0 1.0 36 | 0 0 1 1 1.0 37 | 0 0 2 2 1.0 38 | 1 0 0 0 1.0 39 | 1 0 1 0 1.0 40 | 1 0 2 0 1.0 41 | 1 0 1 1 1.0 42 | 1 0 2 1 1.0 43 | 1 0 2 2 1.0 44 | 45 | ACOORD 46 | 6 47 | 0 1 1.0 48 | 1 0 1.0 49 | 1 2 1.0 50 | 2 1 1.0 51 | 3 0 1.0 52 | 4 2 1.0 53 | 54 | BCOORD 55 | 2 56 | 0 -1.0 57 | 1 -0.5 58 | -------------------------------------------------------------------------------- /src/ConicBenchmarkUtilities.jl: -------------------------------------------------------------------------------- 1 | module ConicBenchmarkUtilities 2 | 3 | using GZip 4 | 5 | using SparseArrays 6 | using LinearAlgebra 7 | 8 | # this is required because findnz does not support arrays by default in julia v1 9 | function SparseArrays.findnz(A::AbstractMatrix) 10 | I = findall(!iszero, A) 11 | return (getindex.(I, 1), getindex.(I, 2), A[I]) 12 | end 13 | 14 | export readcbfdata, cbftompb, mpbtocbf, writecbfdata 15 | export remove_zero_varcones, socrotated_to_soc, remove_ints_in_nonlinear_cones, dualize 16 | 17 | include("cbf_input.jl") 18 | include("cbf_output.jl") 19 | include("mpb.jl") 20 | include("preprocess_mpb.jl") 21 | include("convex_to_cbf.jl") 22 | include("jump_to_cbf.jl") 23 | 24 | end # module 25 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - julia_version: 1.0 4 | 5 | platform: 6 | - x86 # 32-bit 7 | - x64 # 64-bit 8 | 9 | ## uncomment the following lines to allow failures on nightly julia 10 | ## (tests will run but not make your overall status red) 11 | #matrix: 12 | # allow_failures: 13 | # - julia_version: latest 14 | 15 | branches: 16 | only: 17 | - master 18 | - /release-.*/ 19 | 20 | notifications: 21 | - provider: Email 22 | on_build_success: false 23 | on_build_failure: false 24 | on_build_status_changed: false 25 | 26 | install: 27 | - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) 28 | 29 | build_script: 30 | - echo "%JL_BUILD_SCRIPT%" 31 | - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" 32 | 33 | test_script: 34 | - echo "%JL_TEST_SCRIPT%" 35 | - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" 36 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "ConicBenchmarkUtilities" 2 | uuid = "e95a7839-07fb-532d-9a0e-071766bb5168" 3 | authors = ["Miles Lubin"] 4 | repo = "https://github.com/JuliaOpt/ConicBenchmarkUtilities.jl.git" 5 | version = "0.4.0" 6 | 7 | [deps] 8 | GZip = "92fee26a-97fe-5a0c-ad85-20a5f3185b63" 9 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 10 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 11 | 12 | [compat] 13 | GZip = "~0.5" 14 | JuMP = "~0.18" 15 | julia = "1" 16 | 17 | [extras] 18 | ECOS = "e2685f51-7e38-5353-a97d-a921fd2c8199" 19 | JuMP = "4076af6c-e467-56ae-b986-b466b2749572" 20 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 21 | MathProgBase = "fdba3010-5040-5b88-9595-932c9decdf73" 22 | SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13" 23 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 24 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 25 | 26 | [targets] 27 | test = ["SCS", "LinearAlgebra", "SparseArrays", "Test", "ECOS", "MathProgBase", "JuMP"] 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The ConicBenchmarkUtilities.jl package is licensed under the MIT "Expat" License: 2 | 3 | > Copyright (c) 2016: Miles Lubin. 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConicBenchmarkUtilities 2 | 3 | [![Build Status](https://travis-ci.org/JuliaOpt/ConicBenchmarkUtilities.jl.svg?branch=master)](https://travis-ci.org/JuliaOpt/ConicBenchmarkUtilities.jl) 4 | [![codecov](https://codecov.io/gh/JuliaOpt/ConicBenchmarkUtilities.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaOpt/ConicBenchmarkUtilities.jl) 5 | 6 | Utitilies to convert between [CBF](http://cblib.zib.de/) and [MathProgBase conic format](http://mathprogbasejl.readthedocs.io/en/latest/conic.html). 7 | 8 | ## How to read and solve a CBF instance: 9 | 10 | ```jl 11 | dat = readcbfdata("/path/to/instance.cbf") # .cbf.gz extension also accepted 12 | 13 | # In MathProgBase format: 14 | c, A, b, con_cones, var_cones, vartypes, sense, objoffset = cbftompb(dat) 15 | # Note: The sense in MathProgBase form is always minimization, and the objective offset is zero. 16 | # If sense == :Max, you should flip the sign of c before handing off to a solver. 17 | 18 | # Given the data in MathProgBase format, you can solve it using any corresponding solver which supports the cones present in the problem. 19 | # To use ECOS, for example, 20 | using ECOS 21 | solver = ECOSSolver() 22 | # Now load and solve 23 | m = MathProgBase.ConicModel(ECOSSolver(verbose=0)) 24 | MathProgBase.loadproblem!(m, c, A, b, con_cones, var_cones) 25 | # Continuous solvers need not implement setvartype! 26 | if !all(vartypes .== :Cont) 27 | MathProgBase.setvartype!(m, vartypes) 28 | end 29 | MathProgBase.optimize!(m) 30 | # Solution accessible through: 31 | x_sol = MathProgBase.getsolution(m) 32 | objval = MathProgBase.getobjval(m) 33 | # If PSD vars are present, you can use the following utility to extract the solution in CBF form: 34 | scalar_solution, psdvar_solution = ConicBenchmarkUtilities.mpb_sol_to_cbf(dat,x_sol) 35 | ``` 36 | 37 | ## How to write a CBF instance: 38 | 39 | ```jl 40 | newdat = mpbtocbf("example", c, A, b, con_cones, var_cones, vartypes, sense) 41 | writecbfdata("example.cbf",newdat,"# Comment for the CBF header") 42 | ``` 43 | 44 | Note that because MathProgBase conic format is more general than CBF in specifying the mapping between variables and cones, *the order of the variables in the CBF file may not match the original order*. No reordering takes place if ``var_cones`` is trivial, i.e., ``[(:Free,1:N)]`` where ``N`` is the total number of variables. 45 | 46 | ## How to write a JuMP model to CBF form: 47 | 48 | ```jl 49 | m = JuMP.Model() 50 | @variable(m, x[1:2]) 51 | @variable(m, t) 52 | @constraint(m, norm(x) <= t) 53 | ConicBenchmarkUtilities.jump_to_cbf(m, "soctest", "soctest.cbf") 54 | ``` 55 | -------------------------------------------------------------------------------- /src/cbf_output.jl: -------------------------------------------------------------------------------- 1 | 2 | function writecbfdata(filename,dat::CBFData, comments="") 3 | if endswith(filename,"cbf.gz") 4 | fd = gzopen(filename,"w") 5 | else 6 | @assert endswith(filename, "cbf") 7 | fd = open(filename,"w") 8 | end 9 | 10 | if comments == "" 11 | println(fd,"# Generated by ConicBenchmarkUtilities.jl") 12 | else 13 | println(fd,comments) 14 | end 15 | println(fd,"VER\n2\n") 16 | 17 | println(fd,"OBJSENSE") 18 | if dat.sense == :Min 19 | println(fd,"MIN") 20 | else 21 | @assert dat.sense == :Max 22 | println(fd,"MAX") 23 | end 24 | println(fd) 25 | 26 | if length(dat.psdvar) > 0 27 | println(fd, "PSDVAR") 28 | println(fd, length(dat.psdvar)) 29 | for v in dat.psdvar 30 | println(fd, v) 31 | end 32 | println(fd) 33 | end 34 | 35 | println(fd,"VAR") 36 | println(fd,dat.nvar, " ", length(dat.var)) 37 | for (cone,nvar) in dat.var 38 | println(fd, cone, " ", nvar) 39 | end 40 | println(fd) 41 | 42 | if length(dat.intlist) > 0 43 | println(fd, "INT") 44 | println(fd, length(dat.intlist)) 45 | for k in dat.intlist 46 | println(fd, k-1) 47 | end 48 | println(fd) 49 | end 50 | 51 | if length(dat.psdcon) > 0 52 | println(fd, "PSDCON") 53 | println(fd, length(dat.psdcon)) 54 | for v in dat.psdcon 55 | println(fd, v) 56 | end 57 | println(fd) 58 | end 59 | 60 | println(fd,"CON") 61 | println(fd,dat.nconstr, " ", length(dat.con)) 62 | for (cone,ncon) in dat.con 63 | println(fd, cone, " ", ncon) 64 | end 65 | println(fd) 66 | 67 | if length(dat.objfcoord) > 0 68 | println(fd,"OBJFCOORD") 69 | println(fd,length(dat.objfcoord)) 70 | for (a,b,c,d) in dat.objfcoord 71 | println(fd, a-1, " ", b-1, " ", c-1, " ", d) 72 | end 73 | println(fd) 74 | end 75 | 76 | if length(dat.objacoord) > 0 77 | println(fd,"OBJACOORD") 78 | println(fd,length(dat.objacoord)) 79 | for (i,v) in dat.objacoord 80 | println(fd,i-1," ",v) 81 | end 82 | println(fd) 83 | end 84 | 85 | if length(dat.fcoord) > 0 86 | println(fd,"FCOORD") 87 | println(fd,length(dat.fcoord)) 88 | for (a,b,c,d,e) in dat.fcoord 89 | println(fd,a-1," ",b-1," ",c-1," ",d-1," ",e) 90 | end 91 | println(fd) 92 | end 93 | 94 | if length(dat.acoord) > 0 95 | println(fd,"ACOORD") 96 | println(fd,length(dat.acoord)) 97 | for (i,j,v) in dat.acoord 98 | println(fd,i-1," ",j-1," ",v) 99 | end 100 | println(fd) 101 | end 102 | 103 | if length(dat.hcoord) > 0 104 | println(fd,"HCOORD") 105 | println(fd,length(dat.hcoord)) 106 | for (a,b,c,d,e) in dat.hcoord 107 | println(fd,a-1," ",b-1," ",c-1," ",d-1," ",e) 108 | end 109 | println(fd) 110 | end 111 | 112 | if length(dat.dcoord) > 0 113 | println(fd,"DCOORD") 114 | println(fd,length(dat.dcoord)) 115 | for (a,b,c,d) in dat.dcoord 116 | println(fd, a-1, " ", b-1, " ", c-1, " ", d) 117 | end 118 | println(fd) 119 | end 120 | 121 | if length(dat.bcoord) > 0 122 | println(fd,"BCOORD") 123 | println(fd,length(dat.bcoord)) 124 | for (i,v) in dat.bcoord 125 | println(fd,i-1," ",v) 126 | end 127 | println(fd) 128 | end 129 | 130 | close(fd) 131 | end 132 | -------------------------------------------------------------------------------- /src/cbf_input.jl: -------------------------------------------------------------------------------- 1 | mutable struct CBFData 2 | name::String 3 | sense::Symbol 4 | var::Vector{Tuple{String,Int}} 5 | psdvar::Vector{Int} 6 | con::Vector{Tuple{String,Int}} 7 | psdcon::Vector{Int} 8 | objacoord::Vector{Tuple{Int,Float64}} 9 | objfcoord::Vector{Tuple{Int,Int,Int,Float64}} 10 | objoffset::Float64 11 | fcoord::Vector{Tuple{Int,Int,Int,Int,Float64}} 12 | acoord::Vector{Tuple{Int,Int,Float64}} # linear coefficients 13 | bcoord::Vector{Tuple{Int,Float64}} # linear offsets 14 | hcoord::Vector{Tuple{Int,Int,Int,Int,Float64}} 15 | dcoord::Vector{Tuple{Int,Int,Int,Float64}} 16 | intlist::Vector{Int} 17 | nvar::Int 18 | nconstr::Int 19 | end 20 | 21 | CBFData() = CBFData("",:xxx,[],[],[],[],[],[],0.0,[],[],[],[],[],[],0,0) 22 | 23 | function parse_matblock(fd,outputmat,num_indices) 24 | nextline = readline(fd) 25 | nnz = parse(Int,strip(nextline)) 26 | for k in 1:nnz 27 | nextline = readline(fd) 28 | tup = split(strip(nextline)) 29 | push!(outputmat, (map(s->parse(Int,s)+1,tup[1:num_indices])...,parse(Float64,tup[end]))) 30 | end 31 | end 32 | 33 | function readcbfdata(filename) 34 | if endswith(filename,"cbf.gz") 35 | fd = gzopen(filename,"r") 36 | else 37 | @assert endswith(filename, "cbf") 38 | fd = open(filename,"r") 39 | end 40 | 41 | dat = CBFData() 42 | dat.name = split(basename(filename),".")[1] 43 | 44 | while !eof(fd) 45 | line = readline(fd) 46 | startswith(line,"#") && continue # comments 47 | length(line) == 1 && continue # blank lines 48 | 49 | # new block 50 | 51 | if startswith(line,"VER") 52 | nextline = readline(fd) 53 | @assert startswith(nextline,"1") || startswith(nextline,"2") 54 | continue 55 | end 56 | 57 | if startswith(line,"OBJSENSE") 58 | nextline = readline(fd) 59 | if strip(nextline) == "MIN" 60 | dat.sense = :Min 61 | else 62 | dat.sense = :Max 63 | end 64 | continue 65 | end 66 | 67 | if startswith(line,"VAR") 68 | nextline = readline(fd) 69 | totalvars, lines = split(nextline) 70 | totalvars = parse(Int,strip(totalvars)) 71 | lines = parse(Int,strip(lines)) 72 | varcnt = 0 73 | 74 | for k in 1:lines 75 | nextline = readline(fd) 76 | cone, sz = split(nextline) 77 | sz = parse(Int,strip(sz)) 78 | push!(dat.var, (cone, sz)) 79 | varcnt += sz 80 | end 81 | @assert totalvars == varcnt 82 | dat.nvar = varcnt 83 | continue 84 | end 85 | 86 | if startswith(line, "INT") 87 | nextline = readline(fd) 88 | intvar = parse(Int,strip(nextline)) 89 | for k in 1:intvar 90 | nextline = readline(fd) 91 | idx = parse(Int,strip(nextline)) 92 | push!(dat.intlist,idx+1) 93 | end 94 | continue 95 | end 96 | 97 | if startswith(line,"CON") 98 | nextline = readline(fd) 99 | totalconstr, lines = split(nextline) 100 | totalconstr = parse(Int,strip(totalconstr)) 101 | lines = parse(Int,strip(lines)) 102 | constrcnt = 0 103 | 104 | for k in 1:lines 105 | nextline = readline(fd) 106 | cone, sz = split(nextline) 107 | sz = parse(Int,strip(sz)) 108 | push!(dat.con, (cone, sz)) 109 | constrcnt += sz 110 | end 111 | @assert totalconstr == constrcnt 112 | dat.nconstr = constrcnt 113 | continue 114 | end 115 | 116 | if startswith(line,"PSDVAR") 117 | nextline = readline(fd) 118 | lines = parse(Int,strip(nextline)) 119 | 120 | for k in 1:lines 121 | nextline = readline(fd) 122 | sz = parse(Int,strip(nextline)) 123 | push!(dat.psdvar, sz) 124 | end 125 | continue 126 | end 127 | 128 | if startswith(line,"PSDCON") 129 | nextline = readline(fd) 130 | lines = parse(Int,strip(nextline)) 131 | 132 | for k in 1:lines 133 | nextline = readline(fd) 134 | sz = parse(Int,strip(nextline)) 135 | push!(dat.psdcon, sz) 136 | end 137 | continue 138 | end 139 | 140 | if startswith(line,"OBJACOORD") 141 | parse_matblock(fd,dat.objacoord,1) 142 | end 143 | 144 | if startswith(line,"OBJBCOORD") 145 | nextline = readline(fd) 146 | dat.objoffset = parse(Float64, strip(nextline)) 147 | @warn "Instance has objective offset" 148 | end 149 | 150 | if startswith(line,"BCOORD") 151 | parse_matblock(fd,dat.bcoord,1) 152 | end 153 | 154 | if startswith(line,"ACOORD") 155 | parse_matblock(fd,dat.acoord,2) 156 | end 157 | 158 | if startswith(line,"OBJFCOORD") 159 | parse_matblock(fd,dat.objfcoord,3) 160 | end 161 | 162 | if startswith(line,"FCOORD") 163 | parse_matblock(fd,dat.fcoord,4) 164 | end 165 | 166 | if startswith(line,"HCOORD") 167 | parse_matblock(fd,dat.hcoord,4) 168 | end 169 | 170 | if startswith(line,"DCOORD") 171 | parse_matblock(fd,dat.dcoord,3) 172 | end 173 | end 174 | 175 | return dat 176 | end 177 | -------------------------------------------------------------------------------- /src/preprocess_mpb.jl: -------------------------------------------------------------------------------- 1 | # Some MPB conic solvers don't support all equivalent representatons, 2 | # although they should. 3 | 4 | # A few utilities to normalize the MPB representation 5 | 6 | # Remove :Zero cones from the variable cones 7 | function remove_zero_varcones(c, A, b, con_cones, var_cones, vartypes) 8 | old_to_new_idx = zeros(Int,length(c)) 9 | last_idx = 1 10 | new_varcones = Vector{Tuple{Symbol,Vector{Int}}}() 11 | for (cname, cidx) in var_cones 12 | if cname != :Zero 13 | for i in cidx 14 | old_to_new_idx[i] = last_idx 15 | last_idx += 1 16 | end 17 | push!(new_varcones,(cname, old_to_new_idx[cidx])) 18 | else 19 | # dropped 20 | end 21 | end 22 | keep_indices = find(old_to_new_idx) 23 | c = c[keep_indices] 24 | A = A[:,keep_indices] 25 | vartypes = vartypes[keep_indices] 26 | var_cones = new_varcones 27 | 28 | return c, A, b, con_cones, var_cones, vartypes 29 | end 30 | 31 | # Introduce extra variables so that integer-constrained variables don't appear in nonlinear cones 32 | function remove_ints_in_nonlinear_cones(c, A, b, con_cones, var_cones, vartypes) 33 | c = copy(c) 34 | b = copy(b) 35 | new_var_cones = Vector{Tuple{Symbol,Vector{Int}}}() 36 | con_cones = map((a) -> (a[1],vec(collect(a[2]))), con_cones) 37 | vartypes = copy(vartypes) 38 | 39 | nslack = 0 40 | dropped_idx = Int[] 41 | I = Int[] 42 | J = Int[] 43 | V = Float64[] 44 | for k in 1:length(var_cones) 45 | cname, cidx = var_cones[k] 46 | if !(cname ∈ (:Zero, :NonPos, :NonNeg, :Free)) 47 | new_cidx = Int[] 48 | for i in cidx 49 | if vartypes[i] != :Cont 50 | nslack += 1 51 | push!(I,nslack) 52 | push!(J,i) 53 | push!(V,1.0) 54 | push!(I,nslack) 55 | push!(J,nslack+length(c)) 56 | push!(V,-1.0) 57 | push!(new_cidx,nslack+length(c)) 58 | push!(dropped_idx, i) 59 | else 60 | push!(new_cidx,i) 61 | end 62 | end 63 | push!(new_var_cones, (cname,new_cidx)) 64 | else 65 | push!(new_var_cones, (cname, collect(cidx))) 66 | end 67 | end 68 | if length(dropped_idx) > 0 69 | push!(new_var_cones, (:Free, dropped_idx)) 70 | append!(c, zeros(nslack)) 71 | append!(b, zeros(nslack)) 72 | append!(vartypes, fill(:Cont, nslack)) 73 | push!(con_cones, (:Zero, collect((size(A,1)+1):(size(A,1)+nslack)))) 74 | 75 | A = [A spzeros(size(A,1),nslack)] 76 | A = vcat(A, sparse(I,J,V,nslack,size(A,2))) 77 | end 78 | 79 | return c, A, b, con_cones, new_var_cones, vartypes 80 | 81 | end 82 | 83 | # Translate all SOCRotated cones to SOCs 84 | function socrotated_to_soc(c, A, b, con_cones, var_cones, vartypes) 85 | con_cones = copy(con_cones) 86 | var_cones = copy(var_cones) 87 | vartypes = copy(vartypes) 88 | c = copy(c) 89 | b = copy(b) 90 | I, J, V = findnz(A) 91 | nslack = 0 92 | # introduce slack variables and put them into SOCRotated cones 93 | for i in 1:length(con_cones) 94 | cname, cidx = con_cones[i] 95 | if cname == :SOCRotated 96 | for j in cidx 97 | nslack += 1 98 | push!(I, j) 99 | push!(J, nslack+length(c)) 100 | push!(V, 1.0) 101 | push!(vartypes,:Cont) 102 | end 103 | con_cones[i] = (:Zero, cidx) 104 | push!(var_cones, (:SOCRotated, (length(c)+nslack-length(cidx)+1):(length(c)+nslack))) 105 | end 106 | end 107 | append!(c, zeros(nslack)) 108 | A = sparse(I,J,V,size(A,1),size(A,2)+nslack) 109 | 110 | # new rows to add to constraint matrix 111 | I = Int[] 112 | J = Int[] 113 | V = Float64[] 114 | rowidx = 1 115 | for i in 1:length(var_cones) 116 | cname, cidx = var_cones[i] 117 | if cname == :SOCRotated 118 | var_cones[i] = (:Free,cidx) 119 | # (y,z,x) in RSOC <=> (y+z,y-z,sqrt(2)*x) in SOC 120 | push!(I, rowidx) 121 | push!(J, cidx[1]) 122 | push!(V, -1.0) 123 | push!(I, rowidx) 124 | push!(J, cidx[2]) 125 | push!(V, -1.0) 126 | 127 | push!(I, rowidx+1) 128 | push!(J, cidx[1]) 129 | push!(V, -1.0) 130 | push!(I, rowidx+1) 131 | push!(J, cidx[2]) 132 | push!(V, 1.0) 133 | 134 | append!(I, (rowidx+2):(rowidx+length(cidx)-1)) 135 | append!(J, cidx[3:end]) 136 | append!(V, fill(-sqrt(2), length(cidx)-2)) 137 | push!(con_cones, (:SOC, (size(A,1)+rowidx):(size(A,1)+rowidx+length(cidx)-1))) 138 | rowidx += length(cidx) 139 | append!(b, zeros(length(cidx))) 140 | end 141 | end 142 | 143 | A = vcat(A, sparse(I,J,V, rowidx-1, size(A,2))) 144 | 145 | return c, A, b, con_cones, var_cones, vartypes 146 | end 147 | 148 | 149 | const conedual = Dict{Symbol,Symbol}( 150 | :Zero => :Free, 151 | :Free => :Zero, 152 | :NonNeg => :NonNeg, 153 | :NonPos => :NonPos, 154 | :ExpPrimal => :ExpDual, 155 | :ExpDual => :ExpPrimal, 156 | :SDP => :SDP, 157 | :SOC => :SOC, 158 | :SOCRotated => :SOCRotated 159 | ) 160 | 161 | # Dualize the conic (continuous relaxation) problem 162 | function dualize(c, A, b, con_cones, var_cones) 163 | # Strong duality must hold between the primal and dual 164 | # NOTE: objective sign reversed; may return incorrect status (eg if both infeasible) 165 | # 166 | # Primal: 167 | # min_x c^Tx 168 | # s.t. b - Ax \in K_1 169 | # x \in K_2 170 | # 171 | # Dual: 172 | # -min_y b^Ty 173 | # s.t. c + A^Ty \in K_2^* 174 | # y \in K_1^* 175 | 176 | givedual = (coneinds -> (conedual[coneinds[1]], coneinds[2])) 177 | 178 | return (b, -A', c, map(givedual, var_cones), map(givedual, con_cones)) 179 | end 180 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using ECOS, SCS, MathProgBase 2 | import JuMP 3 | using ConicBenchmarkUtilities 4 | 5 | using Test 6 | using SparseArrays 7 | using LinearAlgebra 8 | 9 | 10 | @testset "ConicBenchmarkUtilities Tests" begin 11 | 12 | @testset "example4.cbf" begin 13 | 14 | dat = readcbfdata("example4.cbf") 15 | 16 | c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset = cbftompb(dat) 17 | 18 | @test c ≈ [1.0, 0.64] 19 | @test A ≈ [-50.0 -31; -3.0 2.0] 20 | @test b ≈ [-250.0, 4.0] 21 | @test vartypes == [:Cont, :Cont] 22 | @test dat.sense == :Max 23 | @test dat.objoffset == 0.0 24 | @test con_cones == [(:NonPos,[1]),(:NonNeg,[2])] 25 | 26 | m = MathProgBase.ConicModel(ECOSSolver(verbose=0)) 27 | MathProgBase.loadproblem!(m, -c, A, b, con_cones, var_cones) 28 | MathProgBase.optimize!(m) 29 | 30 | x_sol = MathProgBase.getsolution(m) 31 | objval = MathProgBase.getobjval(m) 32 | 33 | # test CBF writer 34 | newdat = mpbtocbf("example", c, A, b, con_cones, var_cones, vartypes, dat.sense) 35 | writecbfdata("example_out.cbf",newdat,"# Example C.4 from the CBF documentation version 2") 36 | @test strip(read("example4.cbf", String)) == strip(read("example_out.cbf", String)) 37 | rm("example_out.cbf") 38 | 39 | end 40 | 41 | # test transformation utilities 42 | 43 | @testset "dualize" begin 44 | # max y + z 45 | # st x <= 1 46 | # (x,y,z) in SOC 47 | # x in {0,1} 48 | c = [0.0, -1.0, -1.0] 49 | A = [1.0 0.0 0.0; 50 | -1.0 0.0 0.0; 51 | 0.0 -1.0 0.0; 52 | 0.0 0.0 -1.0] 53 | b = [1.0, 0.0, 0.0, 0.0] 54 | con_cones = [(:NonNeg,1:1), (:SOC,2:4)] 55 | var_cones = [(:Free,1:3)] 56 | 57 | (c, A, b, con_cones, var_cones) = dualize(c, A, b, con_cones, var_cones) 58 | 59 | @test c == [1.0, 0.0, 0.0, 0.0] 60 | @test A == [-1.0 1.0 0.0 0.0; 61 | 0.0 0.0 1.0 0.0; 62 | 0.0 0.0 0.0 1.0] 63 | @test b == [0.0, -1.0, -1.0] 64 | @test con_cones == [(:Zero,1:3)] 65 | @test var_cones == [(:NonNeg,1:1), (:SOC,2:4)] 66 | 67 | end 68 | 69 | @testset "socrotated_to_soc" begin 70 | # SOCRotated1 from MathProgBase conic tests 71 | c = [ 0.0, 0.0, -1.0, -1.0] 72 | A = [ 1.0 0.0 0.0 0.0 73 | 0.0 1.0 0.0 0.0] 74 | b = [ 0.5, 1.0] 75 | con_cones = [(:Zero,1:2)] 76 | var_cones = [(:SOCRotated,1:4)] 77 | vartypes = fill(:Cont,4) 78 | 79 | (c, A, b, con_cones, var_cones, vartypes) = socrotated_to_soc(c, A, b, con_cones, var_cones, vartypes) 80 | 81 | @test c == [0.0,0.0,-1.0,-1.0] 82 | @test b == [0.5,1.0,0.0,0.0,0.0,0.0] 83 | @test A ≈ [1.0 0.0 0.0 0.0 84 | 0.0 1.0 0.0 0.0 85 | -1.0 -1.0 0.0 0.0 86 | -1.0 1.0 0.0 0.0 87 | 0.0 0.0 -1.4142135623730951 0.0 88 | 0.0 0.0 0.0 -1.4142135623730951] 89 | @test var_cones == [(:Free,1:4)] 90 | @test con_cones == [(:Zero,1:2),(:SOC,3:6)] 91 | 92 | c = [-1.0,-1.0] 93 | A = [0.0 0.0; 0.0 0.0; -1.0 0.0; 0.0 -1.0] 94 | b = [0.5, 1.0, 0.0, 0.0] 95 | con_cones = [(:SOCRotated,1:4)] 96 | var_cones = [(:Free,1:2)] 97 | vartypes = fill(:Cont,2) 98 | 99 | (c, A, b, con_cones, var_cones, vartypes) = socrotated_to_soc(c, A, b, con_cones, var_cones, vartypes) 100 | 101 | @test c == [-1.0,-1.0,0.0,0.0,0.0,0.0] 102 | @test b == [0.5,1.0,0.0,0.0,0.0,0.0,0.0,0.0] 103 | @test A == [0.0 0.0 1.0 0.0 0.0 0.0 104 | 0.0 0.0 0.0 1.0 0.0 0.0 105 | -1.0 0.0 0.0 0.0 1.0 0.0 106 | 0.0 -1.0 0.0 0.0 0.0 1.0 107 | 0.0 0.0 -1.0 -1.0 0.0 0.0 108 | 0.0 0.0 -1.0 1.0 0.0 0.0 109 | 0.0 0.0 0.0 0.0 -1.4142135623730951 0.0 110 | 0.0 0.0 0.0 0.0 0.0 -1.4142135623730951] 111 | @test var_cones == [(:Free,1:2),(:Free,3:6)] 112 | @test con_cones == [(:Zero,1:4),(:SOC,5:8)] 113 | end 114 | 115 | @testset "remove_ints_in_nonlinear_cones" begin 116 | # SOCINT1 117 | c = [ 0.0, -2.0, -1.0] 118 | A = sparse([ 1.0 0.0 0.0]) 119 | b = [ 1.0] 120 | con_cones = [(:Zero,1)] 121 | var_cones = [(:SOC,1:3)] 122 | vartypes = [:Cont,:Bin,:Bin] 123 | 124 | (c, A, b, con_cones, var_cones, vartypes) = remove_ints_in_nonlinear_cones(c, A, b, con_cones, var_cones, vartypes) 125 | 126 | @test c == [0.0,-2.0,-1.0,0.0,0.0] 127 | @test b == [1.0,0.0,0.0] 128 | @test A == [1.0 0.0 0.0 0.0 0.0 129 | 0.0 1.0 0.0 -1.0 0.0 130 | 0.0 0.0 1.0 0.0 -1.0] 131 | @test var_cones == [(:SOC,[1,4,5]),(:Free,[2,3])] 132 | @test con_cones == [(:Zero,[1]),(:Zero,[2,3])] 133 | end 134 | 135 | 136 | # SDP tests 137 | 138 | @testset "roundtrip read/write" begin 139 | dat = readcbfdata("example1.cbf") 140 | @test dat.sense == :Min 141 | @test dat.objoffset == 0.0 142 | @test isempty(dat.intlist) 143 | writecbfdata("example_out.cbf",dat,"# Example C.1 from the CBF documentation version 2") 144 | @test strip(read("example1.cbf", String)) == strip(read("example_out.cbf", String)) 145 | rm("example_out.cbf") 146 | end 147 | 148 | @testset "roundtrip through MPB format" begin 149 | dat = readcbfdata("example1.cbf") 150 | (c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset) = cbftompb(dat) 151 | newdat = mpbtocbf("example", c, A, b, con_cones, var_cones, vartypes, dat.sense) 152 | writecbfdata("example_out.cbf",newdat,"# Example C.1 from the CBF documentation version 2") 153 | output = """ 154 | # Example C.1 from the CBF documentation version 2 155 | VER 156 | 2 157 | 158 | OBJSENSE 159 | MIN 160 | 161 | PSDVAR 162 | 1 163 | 3 164 | 165 | VAR 166 | 3 1 167 | F 3 168 | 169 | CON 170 | 5 2 171 | L= 2 172 | Q 3 173 | 174 | OBJFCOORD 175 | 5 176 | 0 0 0 2.0 177 | 0 0 1 1.0 178 | 0 1 1 2.0 179 | 0 1 2 1.0 180 | 0 2 2 2.0 181 | 182 | OBJACOORD 183 | 1 184 | 1 1.0 185 | 186 | FCOORD 187 | 9 188 | 0 0 0 0 1.0 189 | 1 0 0 0 1.0 190 | 1 0 0 1 1.0 191 | 1 0 0 2 1.0 192 | 0 0 1 1 1.0 193 | 1 0 1 1 1.0 194 | 1 0 1 2 1.0 195 | 0 0 2 2 1.0 196 | 1 0 2 2 1.0 197 | 198 | ACOORD 199 | 6 200 | 1 0 1.0 201 | 3 0 1.0 202 | 0 1 1.0 203 | 2 1 1.0 204 | 1 2 1.0 205 | 4 2 1.0 206 | 207 | BCOORD 208 | 2 209 | 0 -1.0 210 | 1 -0.5 211 | 212 | """ 213 | @test read("example_out.cbf", String) == output 214 | rm("example_out.cbf") 215 | end 216 | 217 | @testset "Instance with only PSD variables" begin 218 | dat = readcbfdata("psd_var_only.cbf") 219 | (c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset) = cbftompb(dat) 220 | @test var_cones == [(:SDP, [1, 2, 3])] 221 | end 222 | 223 | SCSSOLVER = SCSSolver(eps=1e-6, verbose=0) 224 | 225 | @testset "roundtrip through MPB solver" begin 226 | dat = readcbfdata("example1.cbf") 227 | (c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset) = cbftompb(dat) 228 | m = MathProgBase.ConicModel(SCSSOLVER) 229 | MathProgBase.loadproblem!(m, c, A, b, con_cones, var_cones) 230 | MathProgBase.optimize!(m) 231 | @test MathProgBase.status(m) == :Optimal 232 | 233 | (scalar_solution, psdvar_solution) = ConicBenchmarkUtilities.mpb_sol_to_cbf(dat,MathProgBase.getsolution(m)) 234 | 235 | jm = JuMP.Model(solver=SCSSOLVER) 236 | @JuMP.variable(jm, x[1:3]) 237 | @JuMP.variable(jm, X[1:3,1:3], SDP) 238 | 239 | @JuMP.objective(jm, Min, dot([2 1 0; 1 2 1; 0 1 2],X) + x[2]) 240 | @JuMP.constraint(jm, X[1,1]+X[2,2]+X[3,3]+x[2] == 1.0) 241 | @JuMP.constraint(jm, dot(ones(3,3),X) + x[1] + x[3] == 0.5) 242 | @JuMP.constraint(jm, norm([x[1],x[3]]) <= x[2]) 243 | @test JuMP.solve(jm) == :Optimal 244 | @test JuMP.getobjectivevalue(jm) ≈ MathProgBase.getobjval(m) atol=1e-4 245 | for i in 1:3 246 | @test JuMP.getvalue(x[i]) ≈ scalar_solution[i] atol=1e-4 247 | end 248 | for i in 1:3, j in 1:3 249 | @test JuMP.getvalue(X[i,j]) ≈ psdvar_solution[1][i,j] atol=1e-4 250 | end 251 | end 252 | 253 | @testset "example3.cbf" begin 254 | dat = readcbfdata("example3.cbf") 255 | (c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset) = cbftompb(dat) 256 | 257 | @test dat.sense == :Min 258 | @test dat.objoffset == 1.0 259 | @test all(vartypes .== :Cont) 260 | 261 | m = MathProgBase.ConicModel(SCSSOLVER) 262 | MathProgBase.loadproblem!(m, c, A, b, con_cones, var_cones) 263 | MathProgBase.optimize!(m) 264 | @test MathProgBase.status(m) == :Optimal 265 | 266 | scalar_solution, psdvar_solution = ConicBenchmarkUtilities.mpb_sol_to_cbf(dat,MathProgBase.getsolution(m)) 267 | 268 | jm = JuMP.Model(solver=SCSSOLVER) 269 | @JuMP.variable(jm, x[1:2]) 270 | @JuMP.variable(jm, X[1:2,1:2], SDP) 271 | 272 | @JuMP.objective(jm, Min, X[1,1] + X[2,2] + x[1] + x[2] + 1) 273 | @JuMP.constraint(jm, X[1,2] + X[2,1] - x[1] - x[2] ≥ 0.0) 274 | @JuMP.SDconstraint(jm, [0 1; 1 3]*x[1] + [3 1; 1 0]*x[2] - [1 0; 0 1] >= 0) 275 | 276 | @test JuMP.solve(jm) == :Optimal 277 | @test JuMP.getobjectivevalue(jm) ≈ MathProgBase.getobjval(m)+dat.objoffset atol=1e-4 278 | for i in 1:2 279 | @test JuMP.getvalue(x[i]) ≈ scalar_solution[i] atol=1e-4 280 | end 281 | for i in 1:2, j in 1:2 282 | @test JuMP.getvalue(X[i,j]) ≈ psdvar_solution[1][i,j] atol=1e-4 283 | end 284 | 285 | # should match example3 modulo irrelevant changes 286 | ConicBenchmarkUtilities.jump_to_cbf(jm, "example3", "sdptest.cbf") 287 | 288 | output = """ 289 | # Generated by ConicBenchmarkUtilities.jl 290 | VER 291 | 2 292 | 293 | OBJSENSE 294 | MIN 295 | 296 | PSDVAR 297 | 1 298 | 2 299 | 300 | VAR 301 | 2 1 302 | F 2 303 | 304 | PSDCON 305 | 1 306 | 2 307 | 308 | CON 309 | 1 1 310 | L- 1 311 | 312 | OBJFCOORD 313 | 2 314 | 0 0 0 1.0 315 | 0 1 1 1.0 316 | 317 | OBJACOORD 318 | 2 319 | 0 1.0 320 | 1 1.0 321 | 322 | FCOORD 323 | 1 324 | 0 0 0 1 -0.9999999999999999 325 | 326 | ACOORD 327 | 2 328 | 0 0 1.0 329 | 0 1 1.0 330 | 331 | HCOORD 332 | 4 333 | 0 0 0 1 0.9999999999999999 334 | 0 0 1 1 3.0 335 | 0 1 0 0 3.0 336 | 0 1 0 1 0.9999999999999999 337 | 338 | DCOORD 339 | 2 340 | 0 0 0 -1.0 341 | 0 1 1 -1.0 342 | 343 | """ 344 | 345 | @test read("sdptest.cbf", String) == output 346 | rm("sdptest.cbf") 347 | end 348 | 349 | end -------------------------------------------------------------------------------- /src/mpb.jl: -------------------------------------------------------------------------------- 1 | 2 | const conemap = Dict("L=" => :Zero, "F" => :Free, 3 | "L-" => :NonPos, "L+" => :NonNeg, 4 | "Q" => :SOC, "QR" => :SOCRotated, 5 | "EXP" => :ExpPrimal, "EXP*" => :ExpDual) 6 | const conemap_rev = Dict(:Zero => "L=", :Free => "F", 7 | :NonPos => "L-", :NonNeg => "L+", 8 | :SOC => "Q", :SOCRotated => "QR", 9 | :ExpPrimal => "EXP", :ExpDual => "EXP*") 10 | 11 | function cbfcones_to_mpbcones(c::Vector{Tuple{String,Int}},total) 12 | i = 1 13 | mpb_cones = Vector{Tuple{Symbol,Vector{Int}}}() 14 | 15 | for (cname,count) in c 16 | conesymbol = conemap[cname] 17 | if conesymbol == :ExpPrimal 18 | @assert count == 3 19 | indices = i+2:-1:i 20 | else 21 | indices = i:(i+count-1) 22 | end 23 | push!(mpb_cones, (conesymbol, collect(indices))) 24 | i += count 25 | end 26 | @assert i == total + 1 27 | return mpb_cones 28 | end 29 | 30 | # https://github.com/JuliaLang/julia/issues/13942#issuecomment-217324812 31 | function unzip(A::Array{T}) where T <: Tuple 32 | res = map(x -> x[], T.parameters) 33 | res_len = length(res) 34 | for t in A 35 | for i in 1:res_len 36 | push!(res[i], t[i]) 37 | end 38 | end 39 | return res 40 | end 41 | 42 | psd_len(n) = div(n*(n+1),2) 43 | # returns offset from starting index for (i,j) term in n x n matrix 44 | function idx_to_offset(n,i,j) 45 | @assert 1 <= i <= n 46 | @assert 1 <= j <= n 47 | # upper triangle 48 | if i > j 49 | i,j = j,i 50 | end 51 | # think row major 52 | return psd_len(n) - psd_len(n-i+1) + (j-i) 53 | end 54 | 55 | function cbftompb(dat::CBFData) 56 | @assert dat.nvar == (isempty(dat.var) ? 0 : sum(c->c[2],dat.var)) 57 | @assert dat.nconstr == (isempty(dat.con) ? 0 : sum(c->c[2],dat.con)) 58 | 59 | c = zeros(dat.nvar) 60 | for (i,v) in dat.objacoord 61 | c[i] = v 62 | end 63 | 64 | var_cones = cbfcones_to_mpbcones(dat.var, dat.nvar) 65 | con_cones = cbfcones_to_mpbcones(dat.con, dat.nconstr) 66 | 67 | I_A, J_A, V_A = unzip(dat.acoord) 68 | b = zeros(dat.nconstr) 69 | for (i,v) in dat.bcoord 70 | b[i] = v 71 | end 72 | 73 | psdvarstartidx = Int[] 74 | for i in 1:length(dat.psdvar) 75 | if i == 1 76 | push!(psdvarstartidx,dat.nvar+1) 77 | else 78 | push!(psdvarstartidx,psdvarstartidx[i-1] + psd_len(dat.psdvar[i-1])) 79 | end 80 | push!(var_cones,(:SDP,psdvarstartidx[i]:psdvarstartidx[i]+psd_len(dat.psdvar[i])-1)) 81 | end 82 | nvar = (length(dat.psdvar) > 0) ? psdvarstartidx[end] + psd_len(dat.psdvar[end]) - 1 : dat.nvar 83 | 84 | psdconstartidx = Int[] 85 | for i in 1:length(dat.psdcon) 86 | if i == 1 87 | push!(psdconstartidx,dat.nconstr+1) 88 | else 89 | push!(psdconstartidx,psdconstartidx[i-1] + psd_len(dat.psdcon[i-1])) 90 | end 91 | push!(con_cones,(:SDP,psdconstartidx[i]:psdconstartidx[i]+psd_len(dat.psdcon[i])-1)) 92 | end 93 | nconstr = (length(dat.psdcon) > 0) ? psdconstartidx[end] + psd_len(dat.psdcon[end]) - 1 : dat.nconstr 94 | 95 | c = [c;zeros(nvar-dat.nvar)] 96 | for (matidx,i,j,v) in dat.objfcoord 97 | ix = psdvarstartidx[matidx] + idx_to_offset(dat.psdvar[matidx],i,j) 98 | @assert c[ix] == 0.0 99 | scale = (i == j) ? 1.0 : sqrt(2) 100 | c[ix] = scale*v 101 | end 102 | 103 | for (conidx,matidx,i,j,v) in dat.fcoord 104 | ix = psdvarstartidx[matidx] + idx_to_offset(dat.psdvar[matidx],i,j) 105 | push!(I_A,conidx) 106 | push!(J_A,ix) 107 | scale = (i == j) ? 1.0 : sqrt(2) 108 | push!(V_A,scale*v) 109 | end 110 | 111 | for (conidx,varidx,i,j,v) in dat.hcoord 112 | ix = psdconstartidx[conidx] + idx_to_offset(dat.psdcon[conidx],i,j) 113 | push!(I_A,ix) 114 | push!(J_A,varidx) 115 | scale = (i == j) ? 1.0 : sqrt(2) 116 | push!(V_A,scale*v) 117 | end 118 | 119 | b = [b;zeros(nconstr-dat.nconstr)] 120 | for (conidx,i,j,v) in dat.dcoord 121 | ix = psdconstartidx[conidx] + idx_to_offset(dat.psdcon[conidx],i,j) 122 | @assert b[ix] == 0.0 123 | scale = (i == j) ? 1.0 : sqrt(2) 124 | b[ix] = scale*v 125 | end 126 | 127 | A = sparse(I_A,J_A,-V_A,nconstr,nvar) 128 | 129 | vartypes = fill(:Cont, nvar) 130 | vartypes[dat.intlist] .= :Int 131 | 132 | return c, A, b, con_cones, var_cones, vartypes, dat.sense, dat.objoffset 133 | end 134 | 135 | function mpbtocbf(name, c, A, b, con_cones, var_cones, vartypes, sense=:Min) 136 | num_scalar_var = 0 137 | for (cone,idx) in var_cones 138 | if cone != :SDP 139 | num_scalar_var += length(idx) 140 | end 141 | end 142 | num_scalar_con = 0 143 | for (cone,idx) in con_cones 144 | if cone != :SDP 145 | num_scalar_con += length(idx) 146 | end 147 | end 148 | 149 | # need to shuffle rows and columns to put them in order 150 | var_idx_old_to_new = zeros(Int,length(c)) 151 | con_idx_old_to_new = zeros(Int,length(b)) 152 | var_idx_new_to_old = zeros(Int,num_scalar_var) 153 | con_idx_new_to_old = zeros(Int,num_scalar_con) 154 | 155 | # CBF fields 156 | var = Vector{Tuple{String,Int}}() 157 | con = Vector{Tuple{String,Int}}() 158 | 159 | i = 1 160 | for (cone,idx) in var_cones 161 | if cone == :ExpPrimal 162 | @assert all(var_idx_old_to_new[idx] .== 0) 163 | @assert length(idx) == 3 164 | # MPB: (x,y,z) : y*exp(x/y) <= z 165 | # CBF: (z,y,x) : y*exp(x/y) <= z 166 | var_idx_old_to_new[idx] = i+2:-1:i 167 | var_idx_new_to_old[i+2:-1:i] = idx 168 | i += 3 169 | push!(var, (conemap_rev[cone],length(idx))) 170 | elseif cone != :SDP 171 | for k in idx 172 | var_idx_old_to_new[k] = i 173 | var_idx_new_to_old[i] = k 174 | i += 1 175 | end 176 | push!(var, (conemap_rev[cone],length(idx))) 177 | end 178 | end 179 | @assert i - 1 == num_scalar_var 180 | 181 | i = 1 182 | for (cone,idx) in con_cones 183 | if cone == :ExpPrimal 184 | @assert all(con_idx_old_to_new[idx] .== 0) 185 | @assert length(idx) == 3 186 | con_idx_old_to_new[idx] = i+2:-1:i 187 | con_idx_new_to_old[i+2:-1:i] = idx 188 | i += 3 189 | push!(con, (conemap_rev[cone],length(idx))) 190 | elseif cone != :SDP 191 | for k in idx 192 | @assert con_idx_old_to_new[k] == 0 193 | con_idx_old_to_new[k] = i 194 | con_idx_new_to_old[i] = k 195 | i += 1 196 | end 197 | push!(con, (conemap_rev[cone],length(idx))) 198 | end 199 | end 200 | @assert i - 1 == num_scalar_con 201 | 202 | objacoord = collect(zip(findnz(sparse(c[var_idx_new_to_old]))...)) 203 | bcoord = collect(zip(findnz(sparse(b[con_idx_new_to_old]))...)) 204 | # MPB is b - Ax ∈ K, CBF is b + Ax ∈ K 205 | Acbf = -A[con_idx_new_to_old,var_idx_new_to_old] 206 | 207 | acoord = collect(zip(findnz(Acbf)...))::Vector{Tuple{Int,Int,Float64}} 208 | 209 | intlist = Int[] 210 | for i in 1:length(vartypes) 211 | if var_idx_old_to_new[i] == 0 && vartypes[i] != :Cont 212 | error("CBF format does not support integer restrictions on PSD variables") 213 | end 214 | if vartypes[i] == :Cont 215 | elseif vartypes[i] == :Int 216 | push!(intlist,var_idx_old_to_new[i]) 217 | elseif vartypes[i] == :Bin 218 | # TODO: Check if we need to add variable bounds also 219 | push!(intlist,var_idx_old_to_new[i]) 220 | else 221 | error("Unrecognized variable category $(vartypes[i])") 222 | end 223 | end 224 | 225 | psdvar = Int[] 226 | psdcon = Int[] 227 | 228 | # Map from MPB linear variable index to (psdvar,i,j) 229 | psdvar_idx_old_to_new = fill((0,0,0), length(c)) 230 | # Map from MPB linear constraint index to (psdcon,i,j) 231 | psdcon_idx_old_to_new = fill((0,0,0),length(b)) 232 | 233 | for (cone,idx) in var_cones 234 | if cone == :SDP 235 | y = length(idx) 236 | conedim = round(Int, sqrt(0.25 + 2y) - 0.5) 237 | push!(psdvar, conedim) 238 | k = 1 239 | for i in 1:conedim, j in i:conedim 240 | psdvar_idx_old_to_new[idx[k]] = (length(psdvar), i, j) 241 | k += 1 242 | end 243 | @assert length(idx) == k - 1 244 | end 245 | end 246 | 247 | for (cone,idx) in con_cones 248 | if cone == :SDP 249 | y = length(idx) 250 | conedim = round(Int, sqrt(0.25 + 2y) - 0.5) 251 | push!(psdcon, conedim) 252 | k = 1 253 | for i in 1:conedim, j in i:conedim 254 | psdcon_idx_old_to_new[idx[k]] = (length(psdcon), i, j) 255 | k += 1 256 | end 257 | @assert length(idx) == k - 1 258 | end 259 | end 260 | 261 | objfcoord = Vector{Tuple{Int,Int,Int,Float64}}() 262 | for i in 1:length(c) 263 | if c[i] != 0.0 && psdvar_idx_old_to_new[i] != (0,0,0) 264 | varidx, vari, varj = psdvar_idx_old_to_new[i] 265 | scale = (vari == varj) ? 1.0 : sqrt(2) 266 | push!(objfcoord, (varidx, vari, varj, c[i]/scale)) 267 | end 268 | end 269 | dcoord = Vector{Tuple{Int,Int,Int,Float64}}() 270 | for i in 1:length(b) 271 | if b[i] != 0.0 && psdcon_idx_old_to_new[i] != (0,0,0) 272 | conidx, coni, conj = psdcon_idx_old_to_new[i] 273 | scale = (coni == conj) ? 1.0 : sqrt(2) 274 | push!(dcoord, (conidx, coni, conj, b[i]/scale)) 275 | end 276 | end 277 | 278 | A_I, A_J, A_V = findnz(A) 279 | fcoord = Vector{Tuple{Int,Int,Int,Int,Float64}}() 280 | hcoord = Vector{Tuple{Int,Int,Int,Int,Float64}}() 281 | 282 | for (i,j,v) in zip(A_I,A_J,A_V) 283 | if psdvar_idx_old_to_new[j] != (0,0,0) 284 | if psdcon_idx_old_to_new[i] != (0,0,0) 285 | error("CBF format does not allow PSD variables to appear in affine expressions defining PSD constraints") 286 | end 287 | newrow = con_idx_old_to_new[i] 288 | @assert newrow != 0 289 | varidx, vari, varj = psdvar_idx_old_to_new[j] 290 | scale = (vari == varj) ? 1.0 : sqrt(2) 291 | push!(fcoord, (newrow, varidx, vari, varj, -v/scale)) 292 | elseif psdcon_idx_old_to_new[i] != (0,0,0) 293 | newcol = var_idx_old_to_new[j] 294 | conidx, coni, conj = psdcon_idx_old_to_new[i] 295 | scale = (coni == conj) ? 1.0 : sqrt(2) 296 | push!(hcoord, (conidx, newcol, coni, conj, -v/scale)) 297 | end 298 | end 299 | 300 | return CBFData(name,sense,var,psdvar,con,psdcon,objacoord,objfcoord,0.0,fcoord,acoord,bcoord,hcoord,dcoord,intlist,num_scalar_var,num_scalar_con) 301 | end 302 | 303 | # converts an MPB solution to CBF solution 304 | # no transformation needed unless PSD vars present 305 | function mpb_sol_to_cbf(dat::CBFData,x::Vector) 306 | scalar_solution = x[1:dat.nvar] 307 | 308 | psdvar_solutions = Vector{Matrix{Float64}}() 309 | startidx = dat.nvar+1 310 | for i in 1:length(dat.psdvar) 311 | endidx = startidx + psd_len(dat.psdvar[i]) - 1 312 | svec_solution = x[startidx:endidx] 313 | push!(psdvar_solutions, make_smat!(Matrix{Float64}(undef,dat.psdvar[i],dat.psdvar[i]), svec_solution)) 314 | startidx = endidx + 1 315 | end 316 | 317 | return scalar_solution, psdvar_solutions 318 | end 319 | 320 | # Copied from Pajarito.jl 321 | function make_smat!(smat::Matrix{Float64}, svec::Vector{Float64}) 322 | dim = size(smat, 1) 323 | kSD = 1 324 | for jSD in 1:dim, iSD in jSD:dim 325 | if jSD == iSD 326 | smat[iSD, jSD] = svec[kSD] 327 | else 328 | smat[iSD, jSD] = smat[jSD, iSD] = (1/sqrt(2)) * svec[kSD] 329 | end 330 | kSD += 1 331 | end 332 | return smat 333 | end 334 | --------------------------------------------------------------------------------