diff --git a/src/MatrixOptInterface.jl b/src/MatrixOptInterface.jl index 0fbc923..4ec268b 100644 --- a/src/MatrixOptInterface.jl +++ b/src/MatrixOptInterface.jl @@ -68,5 +68,6 @@ include("sparse_matrix.jl") include("conic_form.jl") include("matrix_input.jl") include("change_form.jl") +include("moi_to_lp.jl") end diff --git a/src/matrix_input.jl b/src/matrix_input.jl index f0d5812..ce1c9a0 100644 --- a/src/matrix_input.jl +++ b/src/matrix_input.jl @@ -285,7 +285,7 @@ s.t. c_lb <= Ax <= c_ub v_lb <= x <= v_ub ``` """ -struct LPForm{T,AT<:AbstractMatrix{T},VT<:AbstractVector{T}} <: LPMixedForm{T} #, V<:AbstractVector{T} #, M<:AbstractMatrix{T}} +mutable struct LPForm{T,AT<:AbstractMatrix{T},VT<:AbstractVector{T}} <: LPMixedForm{T} #, V<:AbstractVector{T} #, M<:AbstractMatrix{T}} sense::MOI.OptimizationSense c::VT A::AT @@ -293,6 +293,30 @@ struct LPForm{T,AT<:AbstractMatrix{T},VT<:AbstractVector{T}} <: LPMixedForm{T} # c_ub::VT v_lb::VT v_ub::VT + function LPForm{T,AT,VT}( + sense::MOI.OptimizationSense, + c, + A, + c_lb, + c_ub, + v_lb, + v_ub, + ) where {T,AT,VT} + model = new{T,AT,VT}( + sense, + c, + A, + c_lb, + c_ub, + v_lb, + v_ub, + ) + return model + end + function LPForm{T,AT,VT}() where {T,AT,VT} + model = new{T,AT,VT}() + return model + end end function _constraint_bound_sense(model::LPForm, i) diff --git a/src/moi_to_lp.jl b/src/moi_to_lp.jl new file mode 100644 index 0000000..2b9346f --- /dev/null +++ b/src/moi_to_lp.jl @@ -0,0 +1,117 @@ +import MathOptInterface + +const MOI = MathOptInterface + +MOI.Utilities.@product_of_sets( + _LPProductOfSets, + MOI.EqualTo{T}, + MOI.LessThan{T}, + MOI.GreaterThan{T}, + MOI.Interval{T}, +) + +const LinearOptimizerCache = MOI.Utilities.GenericModel{ + Float64, + MOI.Utilities.ObjectiveContainer{Float64}, + MOI.Utilities.VariablesContainer{Float64}, + MOI.Utilities.MatrixOfConstraints{ + Float64, + MOI.Utilities.MutableSparseMatrixCSC{ + Float64, + Int, + MOI.Utilities.OneBasedIndexing, + }, + MOI.Utilities.Hyperrectangle{Float64}, + _LPProductOfSets{Float64}, + }, +} + +const SCALAR_SETS = Union{ + MOI.GreaterThan{Float64}, + MOI.LessThan{Float64}, + MOI.EqualTo{Float64}, + MOI.Interval{Float64}, +} + +# ======================= +# `copy_to` function +# ======================= + +function _index_map( + src::LinearOptimizerCache, + index_map, + ::Type{F}, + ::Type{S}, +) where {F,S} + inner = index_map.con_map[F, S] + for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) + row = MOI.Utilities.rows(src.constraints, ci) + inner[ci] = MOI.ConstraintIndex{F,S}(row) + end + return +end + +function _index_map( + src::LinearOptimizerCache, + index_map, + F::Type{MOI.VariableIndex}, + ::Type{S}, +) where {S} + inner = index_map.con_map[F, S] + for ci in MOI.get(src, MOI.ListOfConstraintIndices{F,S}()) + col = index_map[MOI.VariableIndex(ci.value)].value + inner[ci] = MOI.ConstraintIndex{F,S}(col) + end + return +end + +""" + _index_map(src::LinearOptimizerCache) +Create an `IndexMap` mapping the variables and constraints in `LinearOptimizerCache` +to their corresponding 1-based columns and rows. +""" +function _index_map(src::LinearOptimizerCache) + index_map = MOI.IndexMap() + for (i, x) in enumerate(MOI.get(src, MOI.ListOfVariableIndices())) + index_map[x] = MOI.VariableIndex(i) + end + for (F, S) in MOI.get(src, MOI.ListOfConstraintTypesPresent()) + _index_map(src, index_map, F, S) + end + return index_map +end + +function MOI.copy_to(dest::LPForm{T,AT,VT}, src::LinearOptimizerCache) where {T,AT,VT} + obj = + MOI.get(src, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()) + c = zeros(length(src.variables.lower)) + for term in obj.terms + c[term.variable.value] += term.coefficient + end + # handle constant obj? + # obj.constant + dest.sense = MOI.get(src, MOI.ObjectiveSense()) + dest.c = convert(VT, c) + dest.A = convert(AT, src.constraints.coefficients) + dest.c_lb = convert(VT, src.constraints.constants.lower) + dest.c_ub = convert(VT, src.constraints.constants.upper) + dest.v_lb = convert(VT, src.variables.lower) + dest.v_ub = convert(VT, src.variables.upper) + map = _index_map(src) + return map +end + +function MOI.copy_to(dest::LPForm{T,AT,VT}, src::MOI.ModelLike) where {T,AT,VT} + # check supported constraints + cache = LinearOptimizerCache() + src_to_cache = MOI.copy_to(cache, src) + cache_to_dest = MOI.copy_to(dest, cache) + index_map = MOI.IndexMap() + for (src_x, cache_x) in src_to_cache.var_map + index_map[src_x] = cache_to_dest[cache_x] + end + for (src_ci, cache_ci) in src_to_cache.con_map + index_map[src_ci] = cache_to_dest[cache_ci] + end + return index_map +end diff --git a/test/moi_to_lp.jl b/test/moi_to_lp.jl new file mode 100644 index 0000000..e20d39b --- /dev/null +++ b/test/moi_to_lp.jl @@ -0,0 +1,30 @@ +s = """ +variables: x1, x2 +cx1: x1 >= 0.0 +cx2: x2 >= 0.0 +c1: x1 + 2x2 == 5.0 +c2: 3x1 + 4x2 == 6.0 +minobjective: 7x1 + 8x2 +""" +moi = MOIU.Model{Float64}() +MOIU.loadfromstring!(moi, s) + +var_names = ["x1", "x2"] +con_names = ["c1", "c2"] +vcon_names = ["cx1", "cx2"] + +sense = MOI.MIN_SENSE +v_lb = [0.0, 0.0] +v_ub = [Inf, Inf] +const dense_A = [ + 1.0 2.0 + 3.0 4.0 +] +dense_b = [5.0, 6.0] +dense_c = [7.0, 8.0] + +using SparseArrays +@show lp = MatrixOptInterface.LPForm{Float64, SparseArrays.SparseMatrixCSC{Float64,Int64}, Vector{Float64}}() +@show index_map = MOI.copy_to(lp, moi) + + diff --git a/test/runtests.jl b/test/runtests.jl index 308ff0a..517dd3d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,7 @@ const ATOL = 1e-4 const RTOL = 1e-4 include("conic_form.jl") +include("moi_to_lp.jl") const dense_A = [ 1.0 2.0