diff --git a/docs/src/manual/models.md b/docs/src/manual/models.md index adc6112c4c1..b242b7cb5ed 100644 --- a/docs/src/manual/models.md +++ b/docs/src/manual/models.md @@ -447,9 +447,9 @@ matrix form of a linear program. ```jldoctest julia> begin model = Model() - @variable(model, x >= 1) + @variable(model, x >= 1, Bin) @variable(model, 2 <= y) - @variable(model, 3 <= z <= 4) + @variable(model, 3 <= z <= 4, Int) @constraint(model, x == 5) @constraint(model, 2x + 3y <= 6) @constraint(model, -4y >= 5z + 7) @@ -461,10 +461,10 @@ julia> data = lp_matrix_data(model); julia> data.A 4×3 SparseArrays.SparseMatrixCSC{Float64, Int64} with 7 stored entries: - 1.0 ⋅ ⋅ + 1.0 ⋅ ⋅ ⋅ -4.0 -5.0 - 2.0 3.0 ⋅ - 1.0 1.0 ⋅ + 2.0 3.0 ⋅ + 1.0 1.0 ⋅ julia> data.b_lower 4-element Vector{Float64}: @@ -503,8 +503,22 @@ julia> data.c_offset julia> data.sense MAX_SENSE::OptimizationSense = 1 + +julia> data.integers +1-element Vector{Int64}: + 3 + +julia> data.binaries +1-element Vector{Int64}: + 1 ``` +!!! warning + [`lp_matrix_data`](@ref) is intentionally limited in the types of problems + that it supports and the structure of the matrices it outputs. It is mainly + intended as a pedagogical and debugging tool. It should not be used to + interface solvers, see [Implementing a solver interface](@ref) instead. + ## Backends !!! info diff --git a/src/lp_matrix_data.jl b/src/lp_matrix_data.jl index 51815622553..22f22a55bba 100644 --- a/src/lp_matrix_data.jl +++ b/src/lp_matrix_data.jl @@ -18,6 +18,8 @@ struct LPMatrixData{T} c::Vector{T} c_offset::T sense::MOI.OptimizationSense + integers::Vector{Int} + binaries::Vector{Int} variables::Vector{GenericVariableRef{T}} affine_constraints::Vector{ConstraintRef} variable_constraints::Vector{ConstraintRef} @@ -35,6 +37,7 @@ struct storing data for an equivalent linear program in the form: & x_l \\le x \\le x_u \\end{aligned} ``` +where elements in `x` may be continuous, integer, or binary variables. ## Fields @@ -50,9 +53,13 @@ The struct returned by [`lp_matrix_data`](@ref) has the fields: the value of `typemin(T)` is used. * `x_upper::Vector{T}`: the dense vector of variable upper bounds. If missing, the value of `typemax(T)` is used. - * `c::Vector{T}`: the dense vector of linear objective coefficiennts + * `c::Vector{T}`: the dense vector of linear objective coefficients * `c_offset::T`: the constant term in the objective function. * `sense::MOI.OptimizationSense`: the objective sense of the model. + * `integers::Vector{Int}`: the sorted list of column indices that are integer + variables. + * `binaries::Vector{Int}`: the sorted list of column indices that are binary + variables. * `variables::Vector{GenericVariableRef{T}}`: a vector of [`GenericVariableRef`](@ref), corresponding to order of the columns in the matrix form. * `affine_constraints::Vector{ConstraintRef}`: a vector of [`ConstraintRef`](@ref), @@ -62,9 +69,6 @@ The struct returned by [`lp_matrix_data`](@ref) has the fields: The models supported by [`lp_matrix_data`](@ref) are intentionally limited to linear programs. - -If your model has integrality, use [`relax_integrality`](@ref) to remove integer -restrictions before calling [`lp_matrix_data`](@ref). """ function lp_matrix_data(model::GenericModel{T}) where {T} variables = all_variables(model) @@ -80,6 +84,8 @@ function lp_matrix_data(model::GenericModel{T}) where {T} I = Int[], J = Int[], V = T[], + integers = Int[], + binaries = Int[], variable_to_column = columns, bound_constraints = ConstraintRef[], affine_constraints = ConstraintRef[], @@ -97,6 +103,8 @@ function lp_matrix_data(model::GenericModel{T}) where {T} cache.c, cache.c_offset[], MOI.get(model, MOI.ObjectiveSense()), + sort!(cache.integers), + sort!(cache.binaries), variables, cache.affine_constraints, cache.bound_constraints, @@ -128,6 +136,32 @@ function _fill_standard_form( return end +function _fill_standard_form( + model::GenericModel{T}, + ::Type{GenericVariableRef{T}}, + ::Type{MOI.Integer}, + cache::Any, +) where {T} + for c in all_constraints(model, GenericVariableRef{T}, MOI.Integer) + c_obj = constraint_object(c) + push!(cache.integers, cache.variable_to_column[c_obj.func]) + end + return +end + +function _fill_standard_form( + model::GenericModel{T}, + ::Type{GenericVariableRef{T}}, + ::Type{MOI.ZeroOne}, + cache::Any, +) where {T} + for c in all_constraints(model, GenericVariableRef{T}, MOI.ZeroOne) + c_obj = constraint_object(c) + push!(cache.binaries, cache.variable_to_column[c_obj.func]) + end + return +end + function _fill_standard_form( model::GenericModel{T}, ::Type{F}, diff --git a/test/test_lp_matrix_data.jl b/test/test_lp_matrix_data.jl index c5c906bd9ce..c368251a739 100644 --- a/test/test_lp_matrix_data.jl +++ b/test/test_lp_matrix_data.jl @@ -10,9 +10,9 @@ using Test function test_standard_matrix_form() model = Model() - @variable(model, x >= 1) + @variable(model, x >= 1, Bin) @variable(model, 2 <= y) - @variable(model, 3 <= z <= 4) + @variable(model, 3 <= z <= 4, Int) @constraint(model, x == 5) @constraint(model, 2x + 3y <= 6) @constraint(model, -4y >= 5z + 7) @@ -27,11 +27,17 @@ function test_standard_matrix_form() @test a.c == [2, 0, 0] @test a.c_offset == 1 @test a.sense == MOI.MAX_SENSE + @test a.integers == [3] + @test a.binaries == [1] @objective(model, Min, y) + unset_binary(x) + set_integer(x) b = lp_matrix_data(model) b.sense == MOI.MIN_SENSE b.c == [0, 1, 0] b.c_offset == 0 + @test b.integers == [1, 3] + @test b.binaries == Int[] return end @@ -73,10 +79,11 @@ end function test_standard_matrix_form_bad_constraint() model = Model() - @variable(model, x, Bin) + @variable(model, x[1:3]) + @constraint(model, x in SecondOrderCone()) @test_throws( ErrorException( - "Unsupported constraint type in `lp_matrix_data`: $(VariableRef) -in- $(MOI.ZeroOne)", + "Unsupported constraint type in `lp_matrix_data`: $(Vector{VariableRef}) -in- $(MOI.SecondOrderCone)", ), lp_matrix_data(model), )