diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 459f2fcb..0aa550a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,24 +20,15 @@ jobs: os: windows-latest arch: x64 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v6 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v5 with: file: lcov.info diff --git a/Project.toml b/Project.toml index 2f513fae..a8b34a03 100644 --- a/Project.toml +++ b/Project.toml @@ -25,10 +25,10 @@ UnsafeArrays = "c4a57d5a-5b31-53a6-b365-19f8c011fbd6" [compat] AMD = "0.4, 0.5" COSMOAccelerators = "^0.1.0" -DataStructures = "^0.17.0, ^0.18.0" +DataStructures = "0.17, 18.0, 0.19" IterTools = "^1" IterativeSolvers = "^0.9" -MathOptInterface = "1.20" +MathOptInterface = "1.25" QDLDL = "0.4.1" Reexport = "0.2, ^1" Requires = "^1" diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a4ba8462..84dd67e9 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -7,25 +7,126 @@ const MOIU = MOI.Utilities const CI = MOI.ConstraintIndex const VI = MOI.VariableIndex -const SparseTriplets{Tv} = Tuple{Vector{<:Integer}, Vector{<:Integer}, Vector{Tv}} - const Affine = MOI.ScalarAffineFunction{<: AbstractFloat} const Quadratic = MOI.ScalarQuadraticFunction{<: AbstractFloat} const VectorAffine = MOI.VectorAffineFunction{<: AbstractFloat} -const Interval = MOI.Interval{<: AbstractFloat} -const GreaterThan = MOI.GreaterThan{<: AbstractFloat} -const IntervalConvertible = Union{GreaterThan, Interval} - -const Zeros = MOI.Zeros -const SOC = MOI.SecondOrderCone -const PSDT = MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle} -const ComplexPSDT = MOI.Scaled{MOI.HermitianPositiveSemidefiniteConeTriangle} -const SupportedVectorSets = Union{Zeros, MOI.Nonnegatives, SOC, PSDT, ComplexPSDT, MOI.ExponentialCone, MOI.DualExponentialCone, MOI.PowerCone, MOI.DualPowerCone} -const AggregatedSets = Union{Zeros, MOI.Nonnegatives, MOI.GreaterThan} -aggregate_equivalent(::Type{<: MOI.Zeros}) = COSMO.ZeroSet -aggregate_equivalent(::Type{<: Union{MOI.GreaterThan, MOI.Nonnegatives}}) = COSMO.Nonnegatives - +const SUPPORTED_CONES{T} = Union{ + MOI.Zeros, + MOI.Nonnegatives, + MOI.HyperRectangle{T}, + MOI.SecondOrderCone, + MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle}, + MOI.Scaled{MOI.HermitianPositiveSemidefiniteConeTriangle}, + MOI.ExponentialCone, + MOI.DualExponentialCone, + MOI.PowerCone{T}, + MOI.DualPowerCone{T}, +} + +# Mimicking SCS._SetConstants +struct _SetConstants{T} + b::Vector{T} + box_lower_or_power::Vector{T} + box_upper::Vector{T} + _SetConstants{T}() where {T} = new{T}(T[], T[], T[]) +end + +function Base.empty!(x::_SetConstants) + empty!(x.b) + empty!(x.box_lower_or_power) + empty!(x.box_upper) + return x +end + +function Base.resize!(x::_SetConstants, n) + resize!(x.b, n) + resize!(x.box_lower_or_power, n) + resize!(x.box_upper, n) +end + +function MOIU.load_constants(x::_SetConstants, offset, f) + MOIU.load_constants(x.b, offset, f) + return +end + +function MOIU.load_constants( + x::_SetConstants{T}, + offset, + set::MOI.HyperRectangle{T}, +) where {T} + I = offset .+ (eachindex(set.lower)) + x.box_lower_or_power[I] = set.lower + x.box_upper[I] = set.upper + return +end + +function MOIU.load_constants( + x::_SetConstants{T}, + offset, + set::Union{MOI.PowerCone{T},MOI.DualPowerCone{T}}, +) where {T} + x.box_lower_or_power[offset+1] = set.exponent + return +end + +function MOIU.function_constants(x::_SetConstants, rows) + return MOIU.function_constants(x.b, rows) +end + +function MOIU.set_from_constants(x::_SetConstants, S, rows) + return MOIU.set_from_constants(x.b, S, rows) +end + +function MOIU.set_from_constants( + x::_SetConstants{T}, + ::Type{S}, + rows, +) where {T,S<:Union{MOI.PowerCone{T},MOI.DualPowerCone{T}}} + @assert length(rows) == 3 + return S(x.box_lower_or_power[first(rows)]) +end + +function MOIU.set_from_constants( + x::_SetConstants{T}, + ::Type{MOI.HyperRectangle{T}}, + rows, +) where {T} + return MOI.HyperRectangle( + x.box_lower_or_power[rows], + x.box_upper[rows], + ) +end + +MOIU.@product_of_sets( + _Cones, + MOI.Zeros, + MOI.Nonnegatives, + MOI.HyperRectangle{T}, + MOI.SecondOrderCone, + MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle}, + MOI.Scaled{MOI.HermitianPositiveSemidefiniteConeTriangle}, + MOI.ExponentialCone, + MOI.DualExponentialCone, + MOI.PowerCone{T}, + MOI.DualPowerCone{T}, +) + +const OptimizerCache{T} = MOIU.GenericModel{ + T, + MOIU.ObjectiveContainer{T}, + MOIU.VariablesContainer{T}, + MOIU.MatrixOfConstraints{ + T, + MOIU.MutableSparseMatrixCSC{ + T, + Int, + MOIU.OneBasedIndexing, + }, + _SetConstants{T}, + _Cones{T}, + }, +} ############################## # MAIN INTERFACE OBJECTS AND FUNCTIONS @@ -38,10 +139,7 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer is_empty::Bool sense::MOI.OptimizationSense objconstant::T - set_constant::Vector{T} - constr_constant::Vector{T} - rowranges::Dict{Int, UnitRange{Int}} - idxmap::MOIU.IndexMap + cones::Union{Nothing,_Cones{T}} function Optimizer{T}(; user_settings...) where {T <: AbstractFloat} inner = COSMO.Model{T}() @@ -52,31 +150,11 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer is_empty = true sense = MOI.MIN_SENSE objconstant = zero(T) - set_constant = T[] - constr_constant = T[] - rowranges = Dict{Int, UnitRange{Int}}() - idxmap = MOIU.IndexMap() - new(inner, hasresults, results, is_empty, sense, objconstant, set_constant, constr_constant, rowranges, idxmap) + new(inner, hasresults, results, is_empty, sense, objconstant, nothing) end end Optimizer(args...; kwargs...) = Optimizer{DefaultFloat}(args...; kwargs...) -function printIdxmap(idxmap::MOIU.IndexMap) - println(">>Variable Map with $(length(idxmap.var_map)) entries:") - dkeys = collect(keys(idxmap.var_map)) - dvalues = collect(values(idxmap.var_map)) - for i=1:length(dkeys) - println("i=$(i): $(dkeys[i].value) => $(dvalues[i].value)") - end - - println(">>Constraint Map with $(length(idxmap.con_map)) entries:") - dkeys = collect(keys(idxmap.con_map)) - dvalues = collect(values(idxmap.con_map)) - for i=1:length(dkeys) - println("i=$(i): $(dkeys[i].value) => $(dvalues[i].value)") - end -end - function Base.show(io::IO, obj::Optimizer) if obj.is_empty print(io,"Empty COSMO - Optimizer") @@ -109,10 +187,7 @@ function MOI.empty!(optimizer::Optimizer{T}) where {T <: AbstractFloat} optimizer.is_empty = true optimizer.sense = MOI.MIN_SENSE # model parameter, so needs to be reset optimizer.objconstant = zero(T) - optimizer.set_constant = T[] - optimizer.constr_constant = T[] # the 5 in (3 * x1 + 2 * x2 + 5 ≤ 10 ) - optimizer.idxmap = MOIU.IndexMap() - optimizer.rowranges = Dict{Int, UnitRange{Int}}() + optimizer.cones = nothing optimizer end @@ -124,88 +199,44 @@ MOI.is_empty(optimizer::Optimizer) = optimizer.is_empty # MODEL --> SOLVER FORMAT FUNCTIONS ############################## -function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike; copy_names = false) - copy_names && error("Copying names is not supported.") +function MOI.default_cache(::Optimizer{T}, ::Type{T}) where {T} + return MOIU.UniversalFallback(OptimizerCache{T}()) +end + +function MOI.copy_to( + dest::Optimizer{T}, + src::MOIU.UniversalFallback{OptimizerCache{T}}, +) where {T} MOI.empty!(dest) - idxmap = MOIU.IndexMap(dest, src) - LOCs = assign_constraint_row_ranges!(dest.rowranges, idxmap, src) - processconstraints!(dest, src, idxmap, LOCs, dest.rowranges) + + dest.cones = src.model.constraints.sets + processconstraints!(dest, src.model) pre_allocate_variables!(dest.inner) dest.is_empty = false dest.inner.states.IS_ASSEMBLED = true - dest.idxmap = idxmap # pass attributes, e.g. objective, warm strarting vars, etc. dest.sense = MOI.get(src, MOI.ObjectiveSense()) - COSMO.pass_attributes!(dest, src, idxmap) + COSMO.pass_attributes!(dest, src) # if no cost function is provided, allocate P, q with correct sizes dest.sense == MOI.FEASIBILITY_SENSE && COSMO.allocate_cost_variables!(dest) - return idxmap + return MOIU.identity_index_map(src) end + +function MOI.copy_to(dest::Optimizer{T}, src::MOI.ModelLike) where {T} + cache = MOIU.UniversalFallback(OptimizerCache{T}()) + index_map = MOI.copy_to(cache, src) + MOI.copy_to(dest, cache) + return index_map +end + function MOI.optimize!(optimizer::Optimizer) optimizer.results = COSMO.optimize!(optimizer.inner) optimizer.hasresults = true nothing end - -function MOIU.IndexMap(dest::Optimizer, src::MOI.ModelLike) - idxmap = MOIU.IndexMap() - # map model variable indices to solver variable indices - vis_src = MOI.get(src, MOI.ListOfVariableIndices()) - for i in eachindex(vis_src) - idxmap[vis_src[i]] = MOI.VariableIndex(i) - end - # map model constraint indices to solver constraint indices. For now this is important since solver expects following - # order: {0}-variables, R+-variables, SOCP cones, psd cones - # LOCs = MOI.get(src, MOI.ListOfConstraintTypesPresent()) - # sort!(LOCs, by=x-> sort_sets(x[2])) - i = 0 - for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) - MOI.supports_constraint(dest, F, S) || throw(MOI.UnsupportedConstraint{F, S}) - cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}()) - for ci in cis_src - i += 1 - idxmap[ci] = CI{F, S}(i) - end - end - idxmap -end - -# This is important for set merging -sort_sets(s::Type{<: MOI.Zeros}) = 1 -sort_sets(s::Union{Type{ <: MOI.GreaterThan}, Type{ <: MOI.Nonnegatives}}) = 2 -sort_sets(s::Type{MOI.Interval}) = 3 -sort_sets(s::Type{<: MOI.AbstractSet}) = 4 - -# this has to be in the right order -# returns a Dictionary that maps a constraint index to a range -function assign_constraint_row_ranges!(rowranges::Dict{Int, UnitRange{Int}}, idxmap::MOIU.IndexMap, src::MOI.ModelLike) - startrow = 1 - LOCs = MOI.get(src, MOI.ListOfConstraintTypesPresent()) - sort!(LOCs, by = x -> sort_sets(x[2])) - for (F, S) in LOCs - # Returns Array of constraint indices that match F,S, each constraint index is just a type-safe wrapper for Int - cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}()) - for ci_src in cis_src - set = MOI.get(src, MOI.ConstraintSet(), ci_src) - ci_dest = idxmap[ci_src] - endrow = startrow + MOI.dimension(set) - 1 - rowranges[ci_dest.value] = startrow : endrow - startrow = endrow + 1 - end - end - return LOCs -end - -function constraint_rows(rowranges::Dict{Int, UnitRange{Int}}, ci::CI{<:Any, <:MOI.AbstractScalarSet}) - rowrange = rowranges[ci.value] - length(rowrange) == 1 || error() - first(rowrange) -end -constraint_rows(rowranges::Dict{Int, UnitRange{Int}}, ci::CI{<:Any, <:MOI.AbstractVectorSet}) = rowranges[ci.value] - # this is handled explicitly in `copy_to` before the objective function is processed MOI.supports(::Optimizer, ::MOI.ObjectiveSense) = true # MOIU.load(optimizer::Optimizer, ::MOI.ObjectiveSense, sense) = nothing @@ -219,10 +250,9 @@ end MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{<:Union{Affine, Quadratic}}) = true # function MOIU.load(dest::Optimizer{T}, ::MOI.ObjectiveFunction, f::MOI.VariableIndex) where {T <: AbstractFloat} -# idxmap = dest.idxmap # n = MOI.get(dest, MOI.NumberOfVariables()) # dest.inner.p.q = zeros(T, n) -# dest.inner.p.q[idxmap[f.variable].value] = one(T) +# dest.inner.p.q[f.variable.value] = one(T) # dest.inner.p.P = spzeros(T, n, n) # dest.objconstant = zero(T) # dest.objconstant = apply_sense!(dest.sense, dest.inner.p.P, dest.inner.p.q, dest.objconstant) @@ -230,12 +260,11 @@ MOI.supports(::Optimizer, ::MOI.ObjectiveFunction{<:Union{Affine, Quadratic}}) = # end function process_objective!(dest::Optimizer{T}, f::MOI.ScalarAffineFunction{T}) where {T <: AbstractFloat} - idxmap = dest.idxmap n = MOI.get(dest, MOI.NumberOfVariables()) dest.inner.p.P = spzeros(T, n, n) dest.inner.p.q = zeros(T, n) - processlinearterms!(dest.inner.p.q, f.terms, idxmap) + processlinearterms!(dest.inner.p.q, f.terms) dest.objconstant = f.constant dest.objconstant = apply_sense!(dest.sense, dest.inner.p.P, dest.inner.p.q, dest.objconstant) @@ -243,17 +272,16 @@ function process_objective!(dest::Optimizer{T}, f::MOI.ScalarAffineFunction{T}) end function process_objective!(dest::Optimizer{T}, f::MOI.ScalarQuadraticFunction{T}) where {T <: AbstractFloat} - idxmap = dest.idxmap n = MOI.get(dest, MOI.NumberOfVariables()) - I = [Int(idxmap[term.variable_1].value) for term in f.quadratic_terms] - J = [Int(idxmap[term.variable_2].value) for term in f.quadratic_terms] + I = [Int(term.variable_1.value) for term in f.quadratic_terms] + J = [Int(term.variable_2.value) for term in f.quadratic_terms] V = [term.coefficient for term in f.quadratic_terms] symmetrize!(I, J, V) dest.inner.p.P = sparse(I, J, V, n, n) dest.inner.p.q = zeros(T, n) - processlinearterms!(dest.inner.p.q, f.affine_terms, idxmap) + processlinearterms!(dest.inner.p.q, f.affine_terms) dest.objconstant = f.constant dest.objconstant = apply_sense!(dest.sense, dest.inner.p.P, dest.inner.p.q, dest.objconstant) @@ -273,11 +301,11 @@ function apply_sense!(sense::MOI.OptimizationSense, P::AbstractMatrix{T}, q::Abs return c end -function processlinearterms!(q, terms::Vector{<:MOI.ScalarAffineTerm}, idxmap::MOIU.IndexMap) +function processlinearterms!(q, terms::Vector{<:MOI.ScalarAffineTerm}) for term in terms var = term.variable coeff = term.coefficient - q[idxmap[var].value] += coeff + q[var.value] += coeff end end @@ -305,184 +333,90 @@ get_var_index(t::MOI.VectorAffineTerm) = get_var_index(t.scalar_term) coefficient(t::MOI.ScalarAffineTerm) = t.coefficient coefficient(t::MOI.VectorAffineTerm) = coefficient(t.scalar_term) -function processconstraints!(optimizer::Optimizer{T}, src::MOI.ModelLike, idxmap, LOCs, rowranges::Dict{Int, UnitRange{Int}}) where {T <: AbstractFloat} - - if length(LOCs) > 0 - m = mapreduce(length, +, values(rowranges), init = 0) - else - # handle case of a problem without constraints - m = 0 +function processconstraints!(optimizer::Optimizer{T}, src::MOI.ModelLike) where {T <: AbstractFloat} + println(src) + convex_sets = COSMO.AbstractConvexSet{T}[] + + for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) + processSets!(convex_sets, src.constraints, F, S) end - b = zeros(T, m) - constant = zeros(T, m) - I = Int[] - J = Int[] - V = T[] - convex_sets = Array{COSMO.AbstractConvexSet{T}}(undef, 0) - set_constant = zeros(T, m) - if m > 0 - # loop over constraints and modify A, l, u and constants - for (F, S) in LOCs - processconstraints!((I, J, V), b, convex_sets, constant, set_constant, src, idxmap, rowranges, F, S) - end - else - convex_sets = [COSMO.ZeroSet{T}(0)] #fall back if no constraints present + if isempty(convex_sets) + push!(convex_sets, COSMO.ZeroSet{T}(0)) #fall back if no constraints present end - optimizer.set_constant = set_constant - # subtract constant from right hand side - n = MOI.get(src, MOI.NumberOfVariables()) - A = sparse(I, J, V, m, n) # store constraint matrices in inner model model = optimizer.inner - model.p.A = A - model.p.b = b + model.p.A = -Base.convert(SparseMatrixCSC{T,Int}, src.constraints.coefficients) + model.p.b = src.constraints.constants.b model.p.C = CompositeConvexSet{T}(deepcopy(convex_sets)) - model.p.model_size = [size(A, 1); size(A,2 )] - optimizer.constr_constant = constant + model.p.model_size = [size(model.p.A, 1), size(model.p.A, 2)] return nothing end -function processconstraints!(triplets::SparseTriplets{T}, b::Vector{T}, COSMOconvexSets::Array{COSMO.AbstractConvexSet{T}}, constant::Vector{T}, set_constant::Vector{T}, - src::MOI.ModelLike, idxmap, rowranges::Dict{Int, UnitRange{Int}}, - F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) where {T <: AbstractFloat} - cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}()) - aggregate_dim = 0 - - # loop over all constraints of same (F, S) - for ci in cis_src - s = MOI.get(src, MOI.ConstraintSet(), ci) - f = MOI.get(src, MOI.ConstraintFunction(), ci) - rows = constraint_rows(rowranges, idxmap[ci]) - aggregate_dim += length(rows) - b[rows] = MOI.constant(f, T) - constant[rows] = b[rows] - if typeof(s) <: MOI.AbstractScalarSet && !(typeof(s) <: MOI.Interval) - set_constant[rows] = MOI.constant(s) - end - processConstraint!(triplets, f, rows, idxmap, s) - processSet!(b, rows, COSMOconvexSets, s) - end - - process_aggregate_set!(b, COSMOconvexSets, aggregate_dim, cis_src, src, S) - - nothing -end - -############################## -# PROCESS FUNCTIONS -############################## - -# process function like f(x) = coeff*x_1 -function processConstraint!(triplets::SparseTriplets{T}, f::MOI.ScalarAffineFunction, row::Int, idxmap::MOIU.IndexMap, s::MOI.AbstractSet) where {T <: AbstractFloat} - (I, J, V) = triplets - for term in f.terms - push!(I, row) - push!(J, idxmap[term.variable].value) - push!(V, -term.coefficient) - end +function processSets!(convex_sets, src, ::Type{MOI.VectorAffineFunction{T}}, ::Type{MOI.Zeros}) where {T} + push!(convex_sets, COSMO.ZeroSet{T}(MOIU.num_rows(src.sets, MOI.Zeros))) + return nothing end -function processConstraint!(triplets::SparseTriplets{T}, f::MOI.VectorAffineFunction, rows::UnitRange{Int}, idxmap::MOIU.IndexMap, s::MOI.AbstractSet) where {T <: AbstractFloat} - (I, J, V) = triplets - - vis_src = get_var_index.(f.terms) - vis_dest = map(vi -> idxmap[vi], vis_src) - - A = sparse(output_index.(f.terms), map(x-> x.value, vis_dest), coefficient.(f.terms)) - # sparse combines duplicates with + but does not remove zeros created so we call dropzeros! - dropzeros!(A) - A_I, A_J, A_V = findnz(A) - - offset = first(rows) - 1 - - append!(J, A_J) - append!(V, -A_V) - append!(I, A_I .+ offset) +function processSets!(convex_sets, src, ::Type{MOI.VectorAffineFunction{T}}, ::Type{MOI.Nonnegatives}) where {T} + push!(convex_sets, COSMO.Nonnegatives{T}(MOIU.num_rows(src.sets, MOI.Nonnegatives))) + return nothing end - -############################## -# PROCESS SETS -############################## - - -process_aggregate_set!(b::Vector{T}, cs, dim::Int, cis_src, src, S::Type{<: MOI.AbstractSet}) where {T <: AbstractFloat} = false - -function process_aggregate_set!(b::Vector{T}, cs, dim::Int, cis_src, src, S::Type{<: AggregatedSets}) where {T <: AbstractFloat} - if length(cs) > 0 && cs[end] isa aggregate_equivalent(S){T} - prev_dim = cs[end].dim - cs[end] = aggregate_equivalent(S){T}(dim + prev_dim) - else - push!(cs, aggregate_equivalent(S){T}(dim)) - end +function processSets!(convex_sets, src, ::Type{MOI.VectorAffineFunction{T}}, ::Type{MOI.HyperRectangle{T}}) where {T} + offset = MOIU.num_rows(src.sets, MOI.Zeros) + MOIU.num_rows(src.sets, MOI.Nonnegatives) + rows = offset .+ (1:MOIU.num_rows(src.sets, MOI.HyperRectangle{T})) + push!(convex_sets, COSMO.Box{T}(src.constants.box_lower_or_power[rows], src.constants.box_upper[rows])) + return nothing end -function process_aggregate_set!(b::Vector{T}, cs, dim::Int, cis_src, src, s::Type{MOI.Interval{T}}) where {T <: AbstractFloat} - l = zeros(T, length(cis_src)) - u = zeros(T, length(cis_src)) - for (k, ci) in enumerate(cis_src) - s = MOI.get(src, MOI.ConstraintSet(), ci) - l[k] = s.lower - u[k] = s.upper +function processSets!(convex_sets, src, ::Type{MOI.VectorAffineFunction{T}}, ::Type{S}) where {T, S} + for ci in MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, S}()) + set = MOI.get(src, MOI.ConstraintSet(), ci)::S + processSet!(convex_sets, set, T) end - push!(cs, COSMO.Box{T}(l, u)) - nothing -end - - -function processSet!(b::Vector{T}, row::Int, cs, s::GreaterThan) where {T <: AbstractFloat} - b[row] -= s.lower - # push!(cs, COSMO.Nonnegatives{T}(1)) - nothing -end - -# do not process the AggregatedSets and Intervall constraints individually -function processSet!(b::Vector{T}, rows::Union{Int, UnitRange{Int}}, cs, s::Union{AggregatedSets, MOI.Interval{T}}) where {T <: AbstractFloat} - nothing + return nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::SOC) where {T <: AbstractFloat} - push!(cs, COSMO.SecondOrderCone{T}(length(rows))) +function processSet!(cs, s::MOI.SecondOrderCone, ::Type{T}) where {T <: AbstractFloat} + push!(cs, COSMO.SecondOrderCone{T}(MOI.dimension(s))) nothing end - -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::PSDT) where {T <: AbstractFloat} - push!(cs, COSMO.PsdConeTriangle{T, T}(length(rows))) +function processSet!(cs, s::MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle}, ::Type{T}) where {T <: AbstractFloat} + push!(cs, COSMO.PsdConeTriangle{T, T}(MOI.dimension(s))) nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::ComplexPSDT) where {T <: AbstractFloat} - push!(cs, COSMO.PsdConeTriangle{T, Complex{T}}(length(rows))) +function processSet!(cs, s::MOI.Scaled{MOI.HermitianPositiveSemidefiniteConeTriangle}, ::Type{T}) where {T <: AbstractFloat} + push!(cs, COSMO.PsdConeTriangle{T, Complex{T}}(MOI.dimension(s))) nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::MOI.ExponentialCone) where {T <: AbstractFloat} +function processSet!(cs, ::MOI.ExponentialCone, ::Type{T}) where {T <: AbstractFloat} push!(cs, COSMO.ExponentialCone{T}()) nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::MOI.DualExponentialCone) where {T <: AbstractFloat} +function processSet!(cs, ::MOI.DualExponentialCone, ::Type{T}) where {T <: AbstractFloat} push!(cs, COSMO.DualExponentialCone{T}()) nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::MOI.PowerCone) where {T <: AbstractFloat} +function processSet!(cs, s::MOI.PowerCone, ::Type{T}) where {T <: AbstractFloat} push!(cs, COSMO.PowerCone{T}(T(s.exponent))) nothing end -function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::MOI.DualPowerCone) where {T <: AbstractFloat} +function processSet!(cs, s::MOI.DualPowerCone, ::Type{T}) where {T <: AbstractFloat} push!(cs, COSMO.DualPowerCone{T}(T(s.exponent))) nothing end - -function pass_attributes!(dest::Optimizer{T}, src::MOI.ModelLike, idxmap::MOIU.IndexMap) where {T <: AbstractFloat} +function pass_attributes!(dest::Optimizer{T}, src::MOI.ModelLike) where {T <: AbstractFloat} model_attributes = MOI.get(src, MOI.ListOfModelAttributesSet()) @@ -510,7 +444,7 @@ function pass_attributes!(dest::Optimizer{T}, src::MOI.ModelLike, idxmap::MOIU.I vis_src = MOI.get(src, MOI.ListOfVariableIndices()) for vi in vis_src value = MOI.get(src, MOI.VariablePrimalStart(), vi) - process_warm_start!(dest, MOI.VariablePrimalStart(), idxmap[vi], value) + process_warm_start!(dest, MOI.VariablePrimalStart(), vi, value) end end @@ -524,7 +458,7 @@ function pass_attributes!(dest::Optimizer{T}, src::MOI.ModelLike, idxmap::MOIU.I elseif attr == MOI.ConstraintPrimalStart() || attr == MOI.ConstraintDualStart() for ci in cis_src value = MOI.get(src, attr, ci) - process_warm_start!(dest, attr, idxmap[ci], value) + process_warm_start!(dest, attr, ci, value) end else throw(MOI.UnsupportedAttribute(attr)) @@ -667,25 +601,14 @@ MOI.get(optimizer::Optimizer, ::RawResult) = optimizer.results MOIU.map_indices(::Function, r::COSMO.Result) = r -_unshift(optimizer::Optimizer, offset, value, s) = value -_unshift(optimizer::Optimizer, offset, value, s::Type{<:MOI.AbstractScalarSet}) = value + optimizer.set_constant[offset] -_shift(optimizer::Optimizer, offset, value, s) = value -_shift(optimizer::Optimizer, offset, value, s::Type{<:MOI.AbstractScalarSet}) = value - optimizer.set_constant[offset] - -function MOI.get(optimizer::Optimizer, a::MOI.ConstraintPrimal, ci_src::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet +function MOI.get(optimizer::Optimizer, a::MOI.ConstraintPrimal, ci::CI{<:MOI.VectorAffineFunction, S}) where S <: SUPPORTED_CONES MOI.check_result_index_bounds(optimizer, a) - # offset = constroffset(optimizer, ci) - # rows = constrrows(optimizer, ci) - rows = COSMO.constraint_rows(optimizer.rowranges, ci_src) - c_primal = optimizer.results.s[rows] - # (typeof(c_primal) <: AbstractArray && length(c_primal) == 1) && (c_primal = first(c_primal)) - return _unshift(optimizer, rows, c_primal, S) + return optimizer.results.s[MOIU.rows(optimizer.cones, ci)] end -function MOI.get(optimizer::Optimizer, a::MOI.ConstraintDual, ci_src::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet +function MOI.get(optimizer::Optimizer, a::MOI.ConstraintDual, ci::CI{<:MOI.VectorAffineFunction, S}) where S <: SUPPORTED_CONES MOI.check_result_index_bounds(optimizer, a) - rows = constraint_rows(optimizer.rowranges, ci_src) - return optimizer.results.y[rows] + return optimizer.results.y[MOIU.rows(optimizer.cones, ci)] end ## Variable attributes: @@ -704,20 +627,17 @@ process_warm_start!(optimizer::Optimizer, a::MOI.VariablePrimalStart, vi::VI, va MOI.supports(::Optimizer, a::MOI.ConstraintPrimalStart, ::Type{<:MOI.ConstraintIndex}) = true function process_warm_start!(optimizer::Optimizer, a::MOI.ConstraintPrimalStart, ci::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet - (value == nothing || isa(value, Array{Nothing, 1})) && return nothing + (value === nothing || isa(value, Array{Nothing, 1})) && return nothing MOI.is_empty(optimizer) && throw(MOI.CannotSetAttribute(a)) - rows = constraint_rows(optimizer.rowranges, ci) # this undoes the shifting that was used in get(MOI.ConstraintPrimal) - value = _shift(optimizer, rows, value, S) - COSMO.warm_start_slack!(optimizer.inner, value, rows) + COSMO.warm_start_slack!(optimizer.inner, value, MOIU.rows(optimizer.cones, ci)) end MOI.supports(::Optimizer, a::MOI.ConstraintDualStart, ::Type{<:MOI.ConstraintIndex}) = true function process_warm_start!(optimizer::Optimizer, a::MOI.ConstraintDualStart, ci::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet - (value == nothing || isa(value, Array{Nothing, 1})) && return nothing + (value === nothing || isa(value, Array{Nothing, 1})) && return nothing MOI.is_empty(optimizer) && throw(MOI.CannotSetAttribute(a)) - rows = constraint_rows(optimizer.rowranges, ci) - COSMO.warm_start_dual!(optimizer.inner, value, rows) + COSMO.warm_start_dual!(optimizer.inner, value, MOIU.rows(optimizer.cones, ci)) nothing end @@ -773,5 +693,4 @@ end ## Supported Constraints -MOI.supports_constraint(optimizer::Optimizer, ::Type{<:Affine}, ::Type{<: IntervalConvertible}) = true -MOI.supports_constraint(optimizer::Optimizer, ::Type{<:VectorAffine}, ::Type{<:SupportedVectorSets}) = true +MOI.supports_constraint(optimizer::Optimizer{T}, ::Type{MOI.VectorAffineFunction{T}}, ::Type{<:SUPPORTED_CONES{T}}) where {T} = true diff --git a/test/UnitTests/moi_wrapper.jl b/test/UnitTests/moi_wrapper.jl index 6da532ee..0b9eb256 100644 --- a/test/UnitTests/moi_wrapper.jl +++ b/test/UnitTests/moi_wrapper.jl @@ -179,8 +179,9 @@ function test_warm_starting() y_c1 = MOI.get(bridged, MOI.ConstraintDual(), con1); y_c2 = MOI.get(bridged, MOI.ConstraintDual(), con2); optimizer = get_inner_optimizer(bridged) - y_c1_rows = optimizer.rowranges[1]; - y_c2_rows = optimizer.rowranges[2]; + inner_c1, inner_c2 = MOI.get(optimizer.cones, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64},MOI.Nonnegatives}()) + y_c1_rows = MOI.Utilities.rows(optimizer.cones, inner_c1); + y_c2_rows = MOI.Utilities.rows(optimizer.cones, inner_c2); # provide warm start values to the model MOI.set.(bridged, MOI.VariablePrimalStart(), x, x_sol)