diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 5dcee28f15..4fefef90cc 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -14,6 +14,7 @@ const MOIU = MOI.Utilities # used in macro const SVF = MOI.SingleVariable const VVF = MOI.VectorOfVariables const SAF{T} = MOI.ScalarAffineFunction{T} +const GAF{T} = MOI.GenericScalarAffineFunction{T} const VAF{T} = MOI.VectorAffineFunction{T} const SQF{T} = MOI.ScalarQuadraticFunction{T} const VQF{T} = MOI.VectorQuadraticFunction{T} diff --git a/src/Utilities/constraints.jl b/src/Utilities/constraints.jl index f20936c31b..19012e533c 100644 --- a/src/Utilities/constraints.jl +++ b/src/Utilities/constraints.jl @@ -15,7 +15,7 @@ function normalize_and_add_constraint( func::MOI.AbstractScalarFunction, set::MOI.AbstractScalarSet; allow_modify_function::Bool = false, -) where {T} +) return MOI.add_constraint( model, normalize_constant( @@ -45,11 +45,11 @@ function normalize_constant( return func, set end function normalize_constant( - func::Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}, + func::Union{MOI.GenericScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}}, set::MOI.AbstractScalarSet; allow_modify_function::Bool = false, ) where {T} - set = shift_constant(set, -func.constant) + set = shift_constant(set, -MOI.constant(func)) if !allow_modify_function func = copy(func) end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 2a1810bd07..4b03589355 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -164,6 +164,13 @@ end function map_indices(index_map::F, f::MOI.VectorOfVariables) where {F<:Function} return MOI.VectorOfVariables(index_map.(f.variables)) end +function map_indices(index_map::Function, f::F) where {F <: VAF} + return F(map_indices.(index_map, f.terms), MOI.constant(f)) +end + +function map_indices(index_map::FI, f::F) where {FI <: Function, F <: MOI.GenericScalarAffineFunction} + return F(map_indices.(index_map, MOI.scalar_terms(f)), MOI.constant(f)) +end function map_indices(index_map::F, f::Union{SAF,VAF}) where {F<:Function} return typeof(f)(map_indices.(index_map, f.terms), MOI.constant(f)) @@ -286,11 +293,11 @@ end function substitute_variables( variable_map::F, - func::MOI.ScalarAffineFunction{T}, -) where {T,F<:Function} - g = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{T}[], MOI.constant(func)) + func::FT, +) where {T, F <: Function, FT <: MOI.GenericScalarAffineFunction{T}} + g = FT(MOI.ScalarAffineTerm{T}[], MOI.constant(func)) for term in func.terms - operate!( + operate!( +, T, g, @@ -714,7 +721,7 @@ canonicalize!(f::Union{MOI.VectorOfVariables,MOI.SingleVariable}) = f Convert a function to canonical form in-place, without allocating a copy to hold the result. See [`canonical`](@ref). """ -function canonicalize!(f::Union{SAF,VAF}) +function canonicalize!(f::Union{MOI.ScalarAffineFunction,VAF}) sort_and_compress!( f.terms, MOI.term_indices, @@ -724,6 +731,29 @@ function canonicalize!(f::Union{SAF,VAF}) return f end +function canonicalize!(f::MOI.ZippedAffineFunction) + delete_indices = BitSet() + for i in 1:length(f.terms.variable_indices)-1 + if iszero(f.terms.coefficients[i]) + push!(delete_indices, i) + continue + end + for j in i+1:length(f.terms.variable_indices) + if j in delete_indices + continue + end + if f.terms.variable_indices[i] == f.terms.variable_indices[j] + f.terms.coefficients[i] += f.terms.coefficients[j] + f.terms.coefficients[j] = 0 + push!(delete_indices, j) + end + end + end + deleteat!(f.terms.coefficients, delete_indices) + deleteat!(f.terms.variable_indices, delete_indices) + return f +end + """ canonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction}) @@ -873,21 +903,22 @@ function isapprox_zero(f::MOI.AbstractFunction, tol) return all_coefficients(α -> isapprox_zero(α, tol), f) end -_is_constant(f::MOI.ScalarAffineFunction) = isempty(f.terms) +_is_constant(f::MOI.GenericScalarAffineFunction) = isempty(f.terms) + function _is_constant(f::MOI.ScalarQuadraticFunction) return isempty(f.affine_terms) && isempty(f.quadratic_terms) end Base.iszero(::MOI.SingleVariable) = false function Base.iszero( - f::Union{MOI.ScalarAffineFunction,MOI.ScalarQuadraticFunction}, + f::Union{MOI.GenericScalarAffineFunction,MOI.ScalarQuadraticFunction}, ) return iszero(MOI.constant(f)) && _is_constant(canonical(f)) end Base.isone(::MOI.SingleVariable) = false function Base.isone( - f::Union{MOI.ScalarAffineFunction,MOI.ScalarQuadraticFunction}, + f::Union{MOI.GenericScalarAffineFunction,MOI.ScalarQuadraticFunction}, ) return isone(MOI.constant(f)) && _is_constant(canonical(f)) end @@ -1029,8 +1060,8 @@ end Return a new function `f` modified according to `change`. """ -function modify_function(f::SAF, change::MOI.ScalarConstantChange) - return SAF(f.terms, change.new_constant) +function modify_function(f::F, change::MOI.ScalarConstantChange) where {F <: MOI.GenericScalarAffineFunction} + return F(MOI.scalar_terms(f), change.new_constant) end function modify_function(f::VAF, change::MOI.VectorConstantChange) return VAF(f.terms, change.new_constant) @@ -1071,11 +1102,11 @@ function _modifycoefficient( end function modify_function( - f::MOI.ScalarAffineFunction{T}, + f::F, change::MOI.ScalarCoefficientChange{T}, -) where {T} - terms = _modifycoefficient(f.terms, change.variable, change.new_coefficient) - return MOI.ScalarAffineFunction(terms, f.constant) +) where {T, F <: MOI.GenericScalarAffineFunction{T}} + terms = _modifycoefficient(MOI.scalar_terms(f), change.variable, change.new_coefficient) + return F(terms, MOI.constant(f)) end function modify_function( @@ -1456,7 +1487,7 @@ end # Functions convertible to a ScalarAffineFunction const ScalarAffineLike{T} = - Union{T,MOI.SingleVariable,MOI.ScalarAffineFunction{T}} + Union{T,MOI.SingleVariable,MOI.GenericScalarAffineFunction{T}} # Functions convertible to a ScalarQuadraticFunction const ScalarQuadraticLike{T} = Union{ScalarAffineLike{T},MOI.ScalarQuadraticFunction{T}} @@ -1465,7 +1496,7 @@ const ScalarQuadraticLike{T} = # `+(::SingleVariable, ::Any)` which should rather be # `+(::SingleVariable, ::Number)`. const TypedScalarLike{T} = - Union{MOI.ScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}} + Union{MOI.GenericScalarAffineFunction{T},MOI.ScalarQuadraticFunction{T}} # Used for overloading Base operator functions so `T` is not in the union to # avoid overloading e.g. `+(::Float64, ::Float64)` const ScalarLike{T} = Union{MOI.SingleVariable,TypedScalarLike{T}} @@ -2334,7 +2365,7 @@ function Base.:*(f::TypedLike, g::Bool) end Base.:*(f::Bool, g::TypedLike) = g * f -function Base.:^(func::MOI.ScalarAffineFunction{T}, p::Integer) where {T} +function Base.:^(func::MOI.GenericScalarAffineFunction{T}, p::Integer) where {T} if iszero(p) return one(MOI.ScalarQuadraticFunction{T}) elseif isone(p) @@ -2374,7 +2405,7 @@ LinearAlgebra.symmetric(f::ScalarLike, ::Symbol) = f function promote_operation( ::typeof(/), ::Type{T}, - ::Type{<:Union{MOI.SingleVariable,MOI.ScalarAffineFunction{T}}}, + ::Type{<:Union{MOI.SingleVariable,MOI.GenericScalarAffineFunction{T}}}, ::Type{T}, ) where {T} return MOI.ScalarAffineFunction{T} @@ -2567,7 +2598,7 @@ end number_of_affine_terms(::Type{T}, ::T) where {T} = 0 number_of_affine_terms(::Type, ::SVF) = 1 number_of_affine_terms(::Type, f::VVF) = length(f.variables) -function number_of_affine_terms(::Type{T}, f::Union{SAF{T},VAF{T}}) where {T} +function number_of_affine_terms(::Type{T}, f::Union{GAF{T},VAF{T}}) where {T} return length(f.terms) end function number_of_affine_terms(::Type{T}, f::Union{SQF{T},VQF{T}}) where {T} @@ -2576,7 +2607,7 @@ end function number_of_quadratic_terms( ::Type{T}, - ::Union{T,SVF,VVF,SAF{T},VAF{T}}, + ::Union{T,SVF,VVF,GAF{T},VAF{T}}, ) where {T} return 0 end @@ -2629,11 +2660,12 @@ function fill_terms( terms::Vector{MOI.VectorAffineTerm{T}}, offset::Int, output_offset::Int, - func::Union{SAF{T},VAF{T}}, + func::Union{GAF{T},VAF{T}}, ) where {T} n = number_of_affine_terms(T, func) return terms[offset.+(1:n)] .= offset_term.(func.terms, output_offset) end + function fill_terms( terms::Vector{MOI.VectorAffineTerm{T}}, offset::Int, @@ -2682,9 +2714,9 @@ function fill_constant( constant::Vector{T}, offset::Int, output_offset::Int, - func::Union{SAF{T},SQF{T}}, + func::Union{GAF{T},SQF{T}}, ) where {T} - return constant[offset+1] = func.constant + return constant[offset+1] = MOI.constant(func) end function fill_constant( constant::Vector{T}, @@ -2708,12 +2740,12 @@ function vectorize(funcs::AbstractVector{MOI.SingleVariable}) end """ - vectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where T + vectorize(funcs::AbstractVector{<:MOI.GenericScalarAffineFunction{T}}) where {T} Returns the vector of scalar affine functions in the form of a `MOI.VectorAffineFunction{T}`. """ -function vectorize(funcs::AbstractVector{MOI.ScalarAffineFunction{T}}) where {T} +function vectorize(funcs::AbstractVector{<:MOI.GenericScalarAffineFunction{T}}) where {T} nterms = mapreduce(func -> number_of_affine_terms(T, func), +, funcs, init = 0) out_dim = mapreduce(func -> output_dim(T, func), +, funcs, init = 0) diff --git a/src/Utilities/mutable_arithmetics.jl b/src/Utilities/mutable_arithmetics.jl index f35deecbe6..0d87269b8a 100644 --- a/src/Utilities/mutable_arithmetics.jl +++ b/src/Utilities/mutable_arithmetics.jl @@ -4,15 +4,16 @@ MA.mutability(::Type{<:TypedLike}) = MA.IsMutable() -function MA.mutable_copy(func::MOI.ScalarAffineFunction) +function MA.mutable_copy(func::F) where {F <: MOI.GenericScalarAffineFunction} terms = [ MOI.ScalarAffineTerm( MA.copy_if_mutable(t.coefficient), t.variable_index, ) for t in func.terms ] - return MOI.ScalarAffineFunction(terms, MA.copy_if_mutable(func.constant)) + return F(terms, MA.copy_if_mutable(func.constant)) end + function MA.mutable_copy(func::MOI.ScalarQuadraticFunction) affine_terms = [ MOI.ScalarAffineTerm( @@ -75,14 +76,14 @@ function MA.promote_operation( op::PROMOTE_IMPLEMENTED_OP, F::Type{<:ScalarLike{T}}, G::Type{<:ScalarLike{T}}, -) where {T,N} +) where {T} return promote_operation(op, T, F, G) end function MA.promote_operation( op::PROMOTE_IMPLEMENTED_OP, F::Type{T}, G::Type{<:TypedLike{T}}, -) where {T,N} +) where {T} return promote_operation(op, T, F, G) end function MA.promote_operation( diff --git a/src/functions.jl b/src/functions.jl index 1f0b9aebb6..78b06fbca3 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -71,10 +71,20 @@ struct ScalarAffineTerm{T} variable_index::VariableIndex end -# Note: ScalarAffineFunction is mutable because its `constant` field is likely of an immutable +# Note: GenericScalarAffineFunction is mutable because its `constant` field is likely of an immutable # type, while its `terms` field is of a mutable type, meaning that creating a `ScalarAffineFunction` # allocates, and it is desirable to provide a zero-allocation option for working with # ScalarAffineFunctions. See https://github.com/jump-dev/MathOptInterface.jl/pull/343. + +mutable struct GenericScalarAffineFunction{T, VT<:AbstractVector{ScalarAffineTerm{T}}} <: AbstractScalarFunction + terms::VT + constant::T +end + +function GenericScalarAffineFunction{T}(terms::VT, constant::T) where {T, VT <: AbstractVector{ScalarAffineTerm{T}}} + return GenericScalarAffineFunction{T,VT}(terms::VT, constant::T) +end + """ ScalarAffineFunction{T}(terms, constant) @@ -86,11 +96,88 @@ The scalar-valued affine function ``a^T x + b``, where: Duplicate variable indices in `terms` are accepted, and the corresponding coefficients are summed together. """ -mutable struct ScalarAffineFunction{T} <: AbstractScalarFunction - terms::Vector{ScalarAffineTerm{T}} - constant::T +const ScalarAffineFunction{T} = GenericScalarAffineFunction{T, Vector{ScalarAffineTerm{T}}} +function ScalarAffineFunction(terms::Vector{ScalarAffineTerm{T}}, constant::T) where {T} + ScalarAffineFunction{T}(terms, constant) +end + +struct ZipTermVector{T} <: AbstractVector{ScalarAffineTerm{T}} + coefficients::Vector{T} + variable_indices::Vector{VariableIndex} +end +const ZippedAffineFunction{T} = GenericScalarAffineFunction{T, ZipTermVector{T}} + +# Use `Base.Integer` here to disambiguate from the MOI set of the same name. +Base.getindex(zv::ZipTermVector, i::Base.Integer) = ScalarAffineTerm(zv.coefficients[i], zv.variable_indices[i]) +Base.size(zv::ZipTermVector) = size(zv.coefficients) + +function ZipTermVector(terms::AbstractVector{ScalarAffineTerm{T}}) where {T} + coefficients = Vector{T}() + variable_indices = Vector{VariableIndex}() + Base.sizehint!(coefficients, length(terms)) + Base.sizehint!(variable_indices, length(terms)) + for term in terms + push!(coefficients, term.coefficient) + push!(variable_indices, term.variable_index) + end + return ZipTermVector{T}( + coefficients, + variable_indices, + ) +end + +function Base.push!(zv::ZipTermVector, term::ScalarAffineTerm) + push!(zv.coefficients, term.coefficient) + push!(zv.variable_indices, term.variable_index) + return zv +end + +function Base.empty!(zv::ZipTermVector) + empty!(zv.coefficients) + empty!(zv.variable_indices) + return zv +end + +# Use `Base.Integer` here to disambiguate from the MOI set of the same name. +function Base.setindex!(zv::ZipTermVector, term::ScalarAffineTerm, idx::Base.Integer) + zv.coefficients[idx] = term.coefficient + zv.variable_indices[idx] = term.variable_index + return term +end + +scalar_terms(func::ScalarAffineFunction) = func.terms + +function scalar_terms(func::ZippedAffineFunction) + return [ + ScalarAffineTerm(func.terms.coefficients[idx], func.terms.variable_indices[idx]) + for idx in eachindex(func.terms) + ] +end + +coefficients(func::ScalarAffineFunction) = [term.coefficient for term in func.terms] +variable_indices(func::ScalarAffineFunction) = [term.variable_index for term in func.terms] + +coefficients(func::ZippedAffineFunction) = func.terms.coefficients +variable_indices(func::ZippedAffineFunction) = func.terms.variable_indices + +function ZippedAffineFunction(terms::AbstractVector{MathOptInterface.ScalarAffineTerm{T}}, c::T) where {T} + return ZippedAffineFunction{T}( + ZipTermVector(terms), + c, + ) end +function ZippedAffineFunction(coefficients::Vector{T}, variable_indices::Vector{MathOptInterface.VariableIndex}, c::T = zero(T)) where {T} + return ZippedAffineFunction( + ZipTermVector( + coefficients, + variable_indices, + ), + c, + ) +end + + """ struct VectorAffineTerm{T} output_index::Int64 @@ -307,11 +394,10 @@ function dict_compare(d1::Dict, d2::Dict{<:Any,T}, compare::Function) where {T} end # Build a dictionary where the duplicate keys are summed -function sum_dict(kvs::Vector{Pair{K,V}}) where {K,V} +function sum_dict(kvs::Union{Vector{Pair{K,V}}, Iterators.Zip{Tuple{Vector{K},Vector{V}}}}) where {K,V} d = Dict{K,V}() - for kv in kvs - key = kv.first - d[key] = kv.second + Base.get(d, key, zero(V)) + for (key, v) in kvs + d[key] = v + Base.get(d, key, zero(V)) end return d end @@ -374,6 +460,10 @@ function _dicts(f::Union{ScalarAffineFunction,VectorAffineFunction}) return (sum_dict(term_pair.(f.terms)),) end +function _dicts(f::ZippedAffineFunction) + return (sum_dict(zip(f.terms.variable_indices, f.terms.coefficients)),) +end + function _dicts(f::Union{ScalarQuadraticFunction,VectorQuadraticFunction}) return ( sum_dict(term_pair.(f.affine_terms)), @@ -386,7 +476,7 @@ end Returns the constant term of the scalar function """ -constant(f::Union{ScalarAffineFunction,ScalarQuadraticFunction}) = f.constant +constant(f::Union{GenericScalarAffineFunction,ScalarQuadraticFunction}) = f.constant """ constant(f::Union{VectorAffineFunction, VectorQuadraticFunction}) @@ -401,13 +491,13 @@ function Base.isapprox( kwargs..., ) where { F<:Union{ - ScalarAffineFunction, + GenericScalarAffineFunction, ScalarQuadraticFunction, VectorAffineFunction, VectorQuadraticFunction, }, G<:Union{ - ScalarAffineFunction, + GenericScalarAffineFunction, ScalarQuadraticFunction, VectorAffineFunction, VectorQuadraticFunction, @@ -423,7 +513,7 @@ function Base.isapprox( end function constant( - f::Union{ScalarAffineFunction,ScalarQuadraticFunction}, + f::Union{GenericScalarAffineFunction,ScalarQuadraticFunction}, T::DataType, ) return constant(f) @@ -468,6 +558,13 @@ function Base.copy( return F(copy(func.terms), copy(constant(func))) end +function Base.copy(func::ZippedAffineFunction) + return ZippedAffineFunction( + copy(func.terms), + copy(func.constant), + ) +end + """ copy(func::Union{ScalarQuadraticFunction, VectorQuadraticFunction}) @@ -487,8 +584,21 @@ end # Define shortcuts for # SingleVariable -> ScalarAffineFunction function ScalarAffineFunction{T}(f::SingleVariable) where {T} - return ScalarAffineFunction([ScalarAffineTerm(one(T), f.variable)], zero(T)) + return ScalarAffineFunction( + [ScalarAffineTerm(one(T), f.variable)], zero(T), + ) +end + +function ZippedAffineFunction{T}(f::SingleVariable) where {T} + return ZippedAffineFunction{T}( + ZipTermVector( + [one(T)], + [f.variable], + ), + zero(T), + ) end + # VectorOfVariables -> VectorAffineFunction function VectorAffineFunction{T}(f::VectorOfVariables) where {T} n = length(f.variables) @@ -503,7 +613,7 @@ end # Conversion between scalar functions # Conversion to SingleVariable -function Base.convert(::Type{SingleVariable}, f::ScalarAffineFunction) +function Base.convert(::Type{SingleVariable}, f::Union{ScalarAffineFunction, ZippedAffineFunction}) if ( !iszero(f.constant) || !isone(length(f.terms)) || @@ -522,15 +632,15 @@ function Base.convert( end # Conversion to ScalarAffineFunction -function Base.convert(::Type{ScalarAffineFunction{T}}, α::T) where {T} - return ScalarAffineFunction{T}(ScalarAffineTerm{T}[], α) +function Base.convert(::Type{SF}, α::T) where {T, SF <: GenericScalarAffineFunction{T}} + return SF(ScalarAffineTerm{T}[], α) end function Base.convert( - ::Type{ScalarAffineFunction{T}}, + ::Type{SF}, f::SingleVariable, -) where {T} - return ScalarAffineFunction{T}(f) +) where {T, SF <: GenericScalarAffineFunction{T}} + return SF(f) end function Base.convert( @@ -548,27 +658,27 @@ function Base.convert( end function Base.convert( - ::Type{ScalarAffineFunction{T}}, - f::ScalarAffineFunction{T}, -) where {T} + ::Type{SF}, + f::SF, +) where {T, SF <: GenericScalarAffineFunction{T}} return f end function Base.convert( - ::Type{ScalarAffineFunction{T}}, - f::ScalarAffineFunction, -) where {T} - return ScalarAffineFunction{T}(f.terms, f.constant) + ::Type{SF}, + f::GenericScalarAffineFunction, +) where {SF <: GenericScalarAffineFunction} + return SF(f.terms, f.constant) end function Base.convert( - ::Type{ScalarAffineFunction{T}}, + ::Type{SF}, f::ScalarQuadraticFunction{T}, -) where {T} +) where {T, SF <: GenericScalarAffineFunction{T}} if !Base.isempty(f.quadratic_terms) throw(InexactError(:convert, ScalarAffineFunction{T}, f)) end - return ScalarAffineFunction{T}(f.affine_terms, f.constant) + return SF(f.affine_terms, f.constant) end # Conversion to ScalarQuadraticFunction diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index 4c355e8547..4d63aca0ec 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -888,13 +888,13 @@ Bridge graph with 2 variable nodes, 0 constraint nodes and 0 objective nodes. Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be bridged into supported constrained variables and constraints. See details below: [1] constrained variables in `MOI.LessThan{$T}` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because: - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it. + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it. """ @test sprint(MOIB.print_graph, bridged) == """ Bridge graph with 1 variable nodes, 1 constraint nodes and 0 objective nodes. [1] constrained variables in `MOI.LessThan{$T}` are not supported - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported """ MOIB.add_bridge(bridged, MOIB.Variable.VectorizeBridge{T}) @test debug_string(MOIB.debug_supports_add_constrained_variable, S) == @@ -905,7 +905,7 @@ Constrained variables in `MOI.LessThan{$T}` are not supported and cannot be brid Cannot use `$(MOIB.Variable.VectorizeBridge{T,MOI.Nonpositives})` because: [2] constrained variables in `MOI.Nonpositives` are not supported Cannot add free variables and then constrain them because: - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported [2] constrained variables in `MOI.Nonpositives` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because no added bridge supports bridging it. @@ -929,8 +929,8 @@ Bridge graph with 2 variable nodes, 1 constraint nodes and 0 objective nodes. F = MOI.ScalarAffineFunction{T} S = MOI.Interval{T} @test debug_string(MOIB.debug_supports_constraint, F, S) == """ -`MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below: - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because no added bridge supports bridging it. +`MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.Interval{$T}` constraints are not supported and cannot be bridged into supported constrained variables and constraints. See details below: + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.Interval{$T}` constraints are not supported because no added bridge supports bridging it. """ MOIB.add_bridge(bridged, MOIB.Constraint.SplitIntervalBridge{T}) @test debug_string(MOIB.debug_supports_constraint, F, S) == @@ -956,16 +956,16 @@ Bridge graph with 2 variable nodes, 1 constraint nodes and 0 objective nodes. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. [3] constrained variables in `MOI.Interval{$T}` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because: + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.Interval{$T}` constraints are not supported because: Cannot use `$(MOIB.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}})` because: - (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported - (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported + (2) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.GreaterThan{$T}` constraints are not supported + (3) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported Cannot use `$(MOIB.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T}})` because: [3] constrained variables in `MOI.Interval{$T}` are not supported - (2) `MOI.ScalarAffineFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are not supported because: + (2) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.GreaterThan{$T}` constraints are not supported because: Cannot use `$(MOIB.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.GreaterThan{T}})` because: [1] constrained variables in `MOI.GreaterThan{$T}` are not supported - (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because: + (3) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported because: Cannot use `$(MOIB.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.LessThan{T}})` because: [2] constrained variables in `MOI.LessThan{$T}` are not supported """, @@ -983,12 +983,12 @@ Bridge graph with 2 variable nodes, 1 constraint nodes and 0 objective nodes. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. [4] constrained variables in `MOI.Interval{$T}` are not supported because no added bridge supports bridging it. Cannot add free variables and then constrain them because free variables are bridged but no functionize bridge was added. - (1) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are not supported because: + (1) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.Interval{$T}` constraints are not supported because: Cannot use `$(MOIB.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}})` because: - (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported + (3) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported Cannot use `$(MOIB.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T}})` because: [4] constrained variables in `MOI.Interval{$T}` are not supported - (3) `MOI.ScalarAffineFunction{$T}`-in-`MOI.LessThan{$T}` constraints are not supported because: + (3) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.LessThan{$T}` constraints are not supported because: Cannot use `$(MOIB.Constraint.ScalarSlackBridge{T,MOI.ScalarAffineFunction{T},MOI.LessThan{T}})` because: [2] constrained variables in `MOI.LessThan{$T}` are not supported """, @@ -1293,7 +1293,7 @@ Bridge graph with 5 variable nodes, 11 constraint nodes and 0 objective nodes. (6) `MOI.VectorOfVariables`-in-`MOI.Nonnegatives` constraints are bridged (distance 1) by $(MOIB.Constraint.NonnegToNonposBridge{T,MOI.VectorAffineFunction{T},MOI.VectorOfVariables}). (7) `MOI.SingleVariable`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.GreaterToLessBridge{T,MOI.ScalarAffineFunction{T},MOI.SingleVariable}). (8) `MOI.SingleVariable`-in-`MOI.Interval{$T}` constraints are bridged (distance 2) by $(MOIB.Constraint.ScalarFunctionizeBridge{T,MOI.Interval{T}}). - (9) `MOI.ScalarAffineFunction{$T}`-in-`MOI.Interval{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}}). + (9) `MOI.GenericScalarAffineFunction{$T, Vector{MOI.ScalarAffineTerm{$T}}}`-in-`MOI.Interval{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.SplitIntervalBridge{T,MOI.ScalarAffineFunction{T},MOI.Interval{T},MOI.GreaterThan{T},MOI.LessThan{T}}). (10) `MOI.SingleVariable`-in-`MOI.LessThan{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.LessToGreaterBridge{T,MOI.ScalarAffineFunction{T},MOI.SingleVariable}). (11) `MOI.SingleVariable`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOIB.Constraint.VectorizeBridge{T,MOI.VectorAffineFunction{T},MOI.Zeros,MOI.SingleVariable}). """, diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 181d1dedf1..7f3991f2f2 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -54,6 +54,36 @@ end @test MOI.output_dimension(quad) == 0 @test quad isa MOI.VectorQuadraticFunction{Int} end + @testset "vectorize pure column affine" begin + g1 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(2, x)], 3) + g2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Int}[], 1) + g3 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(5, y)], 4) + @test g ≈ MOIU.vectorize([g1, g2, g3]) + vov = MOIU.vectorize(MOI.SingleVariable[]) + @test MOI.output_dimension(vov) == 0 + @test vov isa MOI.VectorOfVariables + aff = MOIU.vectorize(MOI.ZippedAffineFunction{Int}[]) + @test MOI.output_dimension(aff) == 0 + @test aff isa MOI.VectorAffineFunction{Int} + quad = MOIU.vectorize(MOI.ScalarQuadraticFunction{Int}[]) + @test MOI.output_dimension(quad) == 0 + @test quad isa MOI.VectorQuadraticFunction{Int} + end + @testset "vectorize mixed affine functions" begin + g1 = MOI.ZippedAffineFunction([MOI.ScalarAffineTerm(2, x)], 3) + g2 = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Int}[], 1) + g3 = MOI.ZippedAffineFunction([5], [y], 4) + @test g ≈ MOIU.vectorize([g1, g2, g3]) + vov = MOIU.vectorize(MOI.SingleVariable[]) + @test MOI.output_dimension(vov) == 0 + @test vov isa MOI.VectorOfVariables + aff = MOIU.vectorize(MOI.ZippedAffineFunction{Int}[]) + @test MOI.output_dimension(aff) == 0 + @test aff isa MOI.VectorAffineFunction{Int} + quad = MOIU.vectorize(MOI.ScalarQuadraticFunction{Int}[]) + @test MOI.output_dimension(quad) == 0 + @test quad isa MOI.VectorQuadraticFunction{Int} + end @testset "operate vcat" begin v = MOI.VectorOfVariables([y, w]) wf = MOI.SingleVariable(w)