From e192c11483031c204fa379ca2fda0aecec320f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 9 Dec 2025 12:28:44 +0100 Subject: [PATCH 1/4] Allow creating model from parts --- src/Utilities/matrix_of_constraints.jl | 15 +++++++++++++-- src/Utilities/model.jl | 14 +++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Utilities/matrix_of_constraints.jl b/src/Utilities/matrix_of_constraints.jl index 921dd820ac..62aed54192 100644 --- a/src/Utilities/matrix_of_constraints.jl +++ b/src/Utilities/matrix_of_constraints.jl @@ -92,13 +92,24 @@ mutable struct MatrixOfConstraints{T,AT,BT,ST} <: MOI.ModelLike caches::Vector{Any} are_indices_mapped::Vector{BitSet} final_touch::Bool - function MatrixOfConstraints{T,AT,BT,ST}() where {T,AT,BT,ST} - model = new{T,AT,BT,ST}(AT(), BT(), ST(), Any[], BitSet[], false) + function MatrixOfConstraints{T}(coefficients, constants, sets) where {T} + model = new{T,typeof(coefficients),typeof(constants),typeof(sets)}( + coefficients, + constants, + sets, + Any[], + BitSet[], + false, + ) MOI.empty!(model) return model end end +function MatrixOfConstraints{T,AT,BT,ST}() where {T,AT,BT,ST} + return MatrixOfConstraints{T}(AT(), BT(), ST()) +end + ### ### Interface for the .coefficients field ### diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 184488c3b8..06ef912a3d 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -32,12 +32,12 @@ mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T} # A useful dictionary for extensions to store things. These are # _not_ copied between models. ext::Dict{Symbol,Any} - function GenericModel{T,O,V,C}() where {T,O,V,C} - return new{T,O,V,C}( + function GenericModel{T}(objective, variables, constraints) where {T} + return new{T,typeof(objective),typeof(variables),typeof(constraints)}( "", - O(), - V(), - C(), + objective, + variables, + constraints, Dict{MOI.VariableIndex,String}(), nothing, Dict{MOI.ConstraintIndex,String}(), @@ -47,6 +47,10 @@ mutable struct GenericModel{T,O,V,C} <: AbstractModelLike{T} end end +function GenericModel{T,O,V,C}() where {T,O,V,C} + return GenericModel{T}(O(), V(), C()) +end + abstract type AbstractOptimizer{T} <: MOI.AbstractOptimizer end """ From 8c2c27539e55beaff04f2f836481d6fd7ca7a478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 19 Dec 2025 22:31:12 +0100 Subject: [PATCH 2/4] Relax empty --- src/Utilities/matrix_of_constraints.jl | 14 +++++++++----- src/Utilities/sparse_matrix.jl | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Utilities/matrix_of_constraints.jl b/src/Utilities/matrix_of_constraints.jl index 62aed54192..c5fbc5e779 100644 --- a/src/Utilities/matrix_of_constraints.jl +++ b/src/Utilities/matrix_of_constraints.jl @@ -101,7 +101,7 @@ mutable struct MatrixOfConstraints{T,AT,BT,ST} <: MOI.ModelLike BitSet[], false, ) - MOI.empty!(model) + _reset_caches!(model) return model end end @@ -303,13 +303,17 @@ function rows end MOI.is_empty(v::MatrixOfConstraints) = MOI.is_empty(v.sets) -function MOI.empty!(v::MatrixOfConstraints{T}) where {T} - MOI.empty!(v.coefficients) - empty!(v.constants) - MOI.empty!(v.sets) +function _reset_caches!(v::MatrixOfConstraints{T}) where {T} v.caches = [Tuple{_affine_function_type(T, S),S}[] for S in set_types(v.sets)] v.are_indices_mapped = [BitSet() for _ in eachindex(v.caches)] +end + +function MOI.empty!(v::MatrixOfConstraints) + MOI.empty!(v.coefficients) + empty!(v.constants) + MOI.empty!(v.sets) + _reset_caches!(v) v.final_touch = false return end diff --git a/src/Utilities/sparse_matrix.jl b/src/Utilities/sparse_matrix.jl index 98b4b4195c..9353818fe4 100644 --- a/src/Utilities/sparse_matrix.jl +++ b/src/Utilities/sparse_matrix.jl @@ -184,10 +184,10 @@ function Base.convert( end function _first_in_column( - A::MutableSparseMatrixCSC{Tv,Ti}, + A::MutableSparseMatrixCSC, row::Integer, col::Integer, -) where {Tv,Ti} +) range = SparseArrays.nzrange(A, col) row = _shift(row, OneBasedIndexing(), A.indexing) idx = searchsortedfirst(view(A.rowval, range), row) From d8f2ab1abfb5b2c97b95fdf252625065988b211d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 20 Dec 2025 09:13:49 +0100 Subject: [PATCH 3/4] Add tests --- src/Utilities/sparse_matrix.jl | 20 +++++-- test/Utilities/matrix_of_constraints.jl | 76 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/Utilities/sparse_matrix.jl b/src/Utilities/sparse_matrix.jl index 9353818fe4..5d555e007b 100644 --- a/src/Utilities/sparse_matrix.jl +++ b/src/Utilities/sparse_matrix.jl @@ -183,19 +183,27 @@ function Base.convert( ) end +_indexing(A::MutableSparseMatrixCSC) = A.indexing +_indexing(::SparseArrays.SparseMatrixCSC) = OneBasedIndexing() + +const _SparseMatrixCSC{Tv,Ti} = Union{ + MutableSparseMatrixCSC{Tv,Ti}, + SparseArrays.SparseMatrixCSC{Tv,Ti} +} + function _first_in_column( - A::MutableSparseMatrixCSC, + A::_SparseMatrixCSC, row::Integer, col::Integer, ) range = SparseArrays.nzrange(A, col) - row = _shift(row, OneBasedIndexing(), A.indexing) + row = _shift(row, OneBasedIndexing(), _indexing(A)) idx = searchsortedfirst(view(A.rowval, range), row) return get(range, idx, last(range) + 1) end function extract_function( - A::MutableSparseMatrixCSC{T}, + A::_SparseMatrixCSC{T}, row::Integer, constant::T, ) where {T} @@ -205,7 +213,7 @@ function extract_function( if idx > last(SparseArrays.nzrange(A, col)) continue end - r = _shift(A.rowval[idx], A.indexing, OneBasedIndexing()) + r = _shift(A.rowval[idx], _indexing(A), OneBasedIndexing()) if r == row push!( func.terms, @@ -217,7 +225,7 @@ function extract_function( end function extract_function( - A::MutableSparseMatrixCSC{T}, + A::_SparseMatrixCSC{T}, rows::UnitRange, constants::Vector{T}, ) where {T} @@ -231,7 +239,7 @@ function extract_function( if idx[col] > last(SparseArrays.nzrange(A, col)) continue end - row = _shift(A.rowval[idx[col]], A.indexing, OneBasedIndexing()) + row = _shift(A.rowval[idx[col]], _indexing(A), OneBasedIndexing()) if row != rows[output_index] continue end diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 96069f4c63..065801a07a 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -714,6 +714,82 @@ function test_unsupported_constraint() return end +MOI.Utilities.@product_of_sets( + _EqualTos, + MOI.EqualTo{T}, +) + +function _equality_constraints(A::AbstractMatrix{T}, b::AbstractVector{T}) where {T} + sets = _EqualTos{T}() + for _ in eachindex(b) + MOI.Utilities.add_set(sets, MOI.Utilities.set_index(sets, MOI.EqualTo{T})) + end + MOI.Utilities.final_touch(sets) + constants = MOI.Utilities.Hyperrectangle(b, b) + model = MOI.Utilities.MatrixOfConstraints{T}(A, constants, sets) + model.final_touch = true + return model +end + + +# Inspired from MatrixOfConstraints +function test_lp_standard_form() + s = """ + variables: x1, x2 + cx1: x1 >= 0.0 + cx2: x2 >= 0.0 + c1: 1x1 == 5.0 + c2: 3x1 + 4x2 == 6.0 + minobjective: 7x1 + 8x2 + """ + expected = MOI.Utilities.Model{Float64}() + MOI.Utilities.loadfromstring!(expected, s) + + var_names = ["x1", "x2"] + con_names = ["c1", "c2"] + + A = SparseArrays.sparse([ + 1.0 0.0 + 3.0 4.0 + ]) + b = [5.0, 6.0] + form = MOI.Utilities.GenericModel{Float16}( + expected.objective, + expected.variables, + _equality_constraints(A, b), + ) + + model = MOI.Utilities.Model{Float64}() + MOI.copy_to( + MOI.Bridges.Constraint.Scalarize{Float64}(model), + form, + ) + MOI.set( + model, + MOI.VariableName(), + MOI.VariableIndex.(eachindex(var_names)), + var_names, + ) + MOI.set( + model, + MOI.ConstraintName(), + MOI.ConstraintIndex{ + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + }.(eachindex(con_names)), + con_names, + ) + + MOI.Test.util_test_models_equal( + model, + expected, + var_names, + con_names, + ) + + return +end + end TestMatrixOfConstraints.runtests() From 10dbeb4dcd9787d707e8c79e9d4b39ea2d3a9c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 20 Dec 2025 09:13:58 +0100 Subject: [PATCH 4/4] Fix format --- src/Utilities/matrix_of_constraints.jl | 2 +- src/Utilities/sparse_matrix.jl | 12 +++------- test/Utilities/matrix_of_constraints.jl | 32 +++++++++++-------------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/Utilities/matrix_of_constraints.jl b/src/Utilities/matrix_of_constraints.jl index c5fbc5e779..797aa8ef55 100644 --- a/src/Utilities/matrix_of_constraints.jl +++ b/src/Utilities/matrix_of_constraints.jl @@ -306,7 +306,7 @@ MOI.is_empty(v::MatrixOfConstraints) = MOI.is_empty(v.sets) function _reset_caches!(v::MatrixOfConstraints{T}) where {T} v.caches = [Tuple{_affine_function_type(T, S),S}[] for S in set_types(v.sets)] - v.are_indices_mapped = [BitSet() for _ in eachindex(v.caches)] + return v.are_indices_mapped = [BitSet() for _ in eachindex(v.caches)] end function MOI.empty!(v::MatrixOfConstraints) diff --git a/src/Utilities/sparse_matrix.jl b/src/Utilities/sparse_matrix.jl index 5d555e007b..fd5b37f41a 100644 --- a/src/Utilities/sparse_matrix.jl +++ b/src/Utilities/sparse_matrix.jl @@ -186,16 +186,10 @@ end _indexing(A::MutableSparseMatrixCSC) = A.indexing _indexing(::SparseArrays.SparseMatrixCSC) = OneBasedIndexing() -const _SparseMatrixCSC{Tv,Ti} = Union{ - MutableSparseMatrixCSC{Tv,Ti}, - SparseArrays.SparseMatrixCSC{Tv,Ti} -} +const _SparseMatrixCSC{Tv,Ti} = + Union{MutableSparseMatrixCSC{Tv,Ti},SparseArrays.SparseMatrixCSC{Tv,Ti}} -function _first_in_column( - A::_SparseMatrixCSC, - row::Integer, - col::Integer, -) +function _first_in_column(A::_SparseMatrixCSC, row::Integer, col::Integer) range = SparseArrays.nzrange(A, col) row = _shift(row, OneBasedIndexing(), _indexing(A)) idx = searchsortedfirst(view(A.rowval, range), row) diff --git a/test/Utilities/matrix_of_constraints.jl b/test/Utilities/matrix_of_constraints.jl index 065801a07a..fa2f405d17 100644 --- a/test/Utilities/matrix_of_constraints.jl +++ b/test/Utilities/matrix_of_constraints.jl @@ -714,15 +714,18 @@ function test_unsupported_constraint() return end -MOI.Utilities.@product_of_sets( - _EqualTos, - MOI.EqualTo{T}, -) +MOI.Utilities.@product_of_sets(_EqualTos, MOI.EqualTo{T},) -function _equality_constraints(A::AbstractMatrix{T}, b::AbstractVector{T}) where {T} +function _equality_constraints( + A::AbstractMatrix{T}, + b::AbstractVector{T}, +) where {T} sets = _EqualTos{T}() for _ in eachindex(b) - MOI.Utilities.add_set(sets, MOI.Utilities.set_index(sets, MOI.EqualTo{T})) + MOI.Utilities.add_set( + sets, + MOI.Utilities.set_index(sets, MOI.EqualTo{T}), + ) end MOI.Utilities.final_touch(sets) constants = MOI.Utilities.Hyperrectangle(b, b) @@ -731,7 +734,6 @@ function _equality_constraints(A::AbstractMatrix{T}, b::AbstractVector{T}) where return model end - # Inspired from MatrixOfConstraints function test_lp_standard_form() s = """ @@ -760,10 +762,7 @@ function test_lp_standard_form() ) model = MOI.Utilities.Model{Float64}() - MOI.copy_to( - MOI.Bridges.Constraint.Scalarize{Float64}(model), - form, - ) + MOI.copy_to(MOI.Bridges.Constraint.Scalarize{Float64}(model), form) MOI.set( model, MOI.VariableName(), @@ -776,16 +775,13 @@ function test_lp_standard_form() MOI.ConstraintIndex{ MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, - }.(eachindex(con_names)), + }.( + eachindex(con_names), + ), con_names, ) - MOI.Test.util_test_models_equal( - model, - expected, - var_names, - con_names, - ) + MOI.Test.util_test_models_equal(model, expected, var_names, con_names) return end