diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 114f690988..a627f4cc0d 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -1127,8 +1127,8 @@ function MOI.set( ci::MOI.ConstraintIndex{F}, func, ) where {F} - if typeof(func) != F - throw(ArgumentError("Invalid type when setting ConstraintFunction.")) + if !(func isa F) + throw(MOI.FunctionTypeMismatch{F,typeof(func)}()) end if Variable.has_bridges(Variable.bridges(b)) set = MOI.get(b, MOI.ConstraintSet(), ci) @@ -1165,8 +1165,8 @@ function MOI.set( ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, value, ) where {S<:MOI.AbstractScalarSet} - if typeof(value) != S - throw(ArgumentError("Invalid type when setting ConstraintSet.")) + if !(value isa S) + throw(MOI.SetTypeMismatch{S,typeof(value)}()) end _set_substituted(b, attr, ci, value) return @@ -1191,8 +1191,8 @@ function MOI.set( ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,S}, set, ) where {S<:MOI.AbstractScalarSet} - if typeof(set) != S - throw(ArgumentError("Invalid type when setting ConstraintSet.")) + if !(set isa S) + throw(MOI.SetTypeMismatch{S,typeof(set)}()) end if Variable.has_bridges(Variable.bridges(b)) func = MOI.get(b, MOI.ConstraintFunction(), ci) diff --git a/src/Test/test_modification.jl b/src/Test/test_modification.jl index 21bb1150c2..56bfe919f7 100644 --- a/src/Test/test_modification.jl +++ b/src/Test/test_modification.jl @@ -951,10 +951,16 @@ function test_modification_incorrect(model::MOI.ModelLike, ::Config) MOI.EqualTo(1.0), ) @test_throws( - ArgumentError, + MOI.SetTypeMismatch{MOI.EqualTo{Float64},MOI.LessThan{Float64}}, MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(1.0)), ) - @test_throws(ArgumentError, MOI.set(model, MOI.ConstraintFunction(), c, x)) + @test_throws( + MOI.FunctionTypeMismatch{ + MOI.ScalarAffineFunction{Float64}, + MOI.VariableIndex, + }, + MOI.set(model, MOI.ConstraintFunction(), c, x), + ) return end @@ -978,11 +984,11 @@ function test_modification_incorrect_VariableIndex( x = MOI.add_variable(model) c = MOI.add_constraint(model, x, MOI.GreaterThan(zero(T))) @test_throws( - ArgumentError, + MOI.SetTypeMismatch{MOI.GreaterThan{T},MOI.LessThan{T}}, MOI.set(model, MOI.ConstraintSet(), c, MOI.LessThan(one(T))), ) @test_throws( - ArgumentError, + MOI.FunctionTypeMismatch{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}, MOI.set(model, MOI.ConstraintFunction(), c, one(T) * x), ) y = MOI.add_variable(model) diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index eaef32c1b3..afe1479c13 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -107,15 +107,21 @@ function MOI.delete(model::AbstractModel, vis::Vector{MOI.VariableIndex}) return end -function MOI.is_valid( +# `AbstractModel` fields `.variables` and `.constraints` act like a +# `StructOfConstraints` where `.variables` contains the `VariableIndex`-in-`S` +# constraints and `.constraints` contains the other constraints. +function constraints( model::AbstractModel, - ci::CI{MOI.VariableIndex,S}, -) where {S} - return MOI.is_valid(model.variables, ci) + ci::MOI.ConstraintIndex{MOI.VariableIndex}, +) + return model.variables +end +function constraints(model::AbstractModel, ci::MOI.ConstraintIndex) + return model.constraints end function MOI.is_valid(model::AbstractModel, ci::MOI.ConstraintIndex) - return MOI.is_valid(model.constraints, ci) + return MOI.is_valid(constraints(model, ci), ci) end function MOI.is_valid(model::AbstractModel, x::MOI.VariableIndex) @@ -375,21 +381,8 @@ function MOI.get( return MOI.get(model.constraints, attr, ci) end -function _delete_constraint( - model::AbstractModel, - ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, -) where {S} - MOI.throw_if_not_valid(model, ci) - MOI.delete(model.variables, ci) - return -end - -function _delete_constraint(model::AbstractModel, ci::MOI.ConstraintIndex) - return MOI.delete(model.constraints, ci) -end - function MOI.delete(model::AbstractModel, ci::MOI.ConstraintIndex) - _delete_constraint(model, ci) + MOI.delete(constraints(model, ci), ci) model.name_to_con = nothing delete!(model.con_to_name, ci) return @@ -404,43 +397,13 @@ function MOI.modify( return end -function MOI.set( - ::AbstractModel, - ::MOI.ConstraintFunction, - ::MOI.ConstraintIndex{MOI.VariableIndex,<:MOI.AbstractScalarSet}, - ::MOI.VariableIndex, -) - return throw(MOI.SettingVariableIndexNotAllowed()) -end - -function MOI.set( - model::AbstractModel{T}, - attr::MOI.ConstraintSet, - ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, - set::S, -) where {T,S<:SUPPORTED_VARIABLE_SCALAR_SETS{T}} - MOI.throw_if_not_valid(model, ci) - MOI.set(model.variables, attr, ci, set) - return -end - -function MOI.set( - model::AbstractModel, - attr::MOI.ConstraintSet, - ci::MOI.ConstraintIndex{<:MOI.AbstractFunction,S}, - set::S, -) where {S<:MOI.AbstractSet} - MOI.set(model.constraints, attr, ci, set) - return -end - function MOI.set( model::AbstractModel, - attr::MOI.ConstraintFunction, - ci::MOI.ConstraintIndex{F,<:MOI.AbstractSet}, - func::F, -) where {F<:MOI.AbstractFunction} - MOI.set(model.constraints, attr, ci, func) + attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, + ci::MOI.ConstraintIndex, + value, +) + MOI.set(constraints(model, ci), attr, ci, value) return end @@ -482,29 +445,12 @@ function MOI.get( return MOI.get(model.constraints, loc) end -function MOI.get( - model::AbstractModel, - ::MOI.ConstraintFunction, - ci::CI{MOI.VariableIndex}, -) - MOI.throw_if_not_valid(model, ci) - return MOI.VariableIndex(ci.value) -end function MOI.get( model::AbstractModel, attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, ci::MOI.ConstraintIndex, ) - return MOI.get(model.constraints, attr, ci) -end - -function MOI.get( - model::AbstractModel, - ::MOI.ConstraintSet, - ci::CI{MOI.VariableIndex,S}, -) where {S} - MOI.throw_if_not_valid(model, ci) - return set_from_constants(model.variables, S, ci.value) + return MOI.get(constraints(model, ci), attr, ci) end function MOI.is_empty(model::AbstractModel) diff --git a/src/Utilities/variables_container.jl b/src/Utilities/variables_container.jl index 07dca0f147..c84ba053c7 100644 --- a/src/Utilities/variables_container.jl +++ b/src/Utilities/variables_container.jl @@ -288,22 +288,68 @@ function MOI.is_valid( return !iszero(b.set_mask[ci.value] & _single_variable_flag(S)) end +function MOI.get( + model::VariablesContainer, + ::MOI.ConstraintFunction, + ci::CI{MOI.VariableIndex}, +) + MOI.throw_if_not_valid(model, ci) + return MOI.VariableIndex(ci.value) +end + function MOI.set( - b::VariablesContainer, + ::VariablesContainer, + ::MOI.ConstraintFunction, + ::MOI.ConstraintIndex{MOI.VariableIndex}, + ::MOI.VariableIndex, +) + return throw(MOI.SettingVariableIndexNotAllowed()) +end + +function MOI.set( + ::VariablesContainer, + ::MOI.ConstraintFunction, + ci::MOI.ConstraintIndex, + func, +) + return throw(MOI.FunctionTypeMismatch{MOI.func_type(ci),typeof(func)}()) +end + +function MOI.get( + model::VariablesContainer, ::MOI.ConstraintSet, ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, - set::S, ) where {S} + MOI.throw_if_not_valid(model, ci) + return set_from_constants(model, S, ci.value) +end + +function MOI.set( + model::VariablesContainer{T}, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.VariableIndex,S}, + set::S, +) where {T,S<:SUPPORTED_VARIABLE_SCALAR_SETS{T}} + MOI.throw_if_not_valid(model, ci) flag = _single_variable_flag(S) if !iszero(flag & _LOWER_BOUND_MASK) - b.lower[ci.value] = _lower_bound(set) + model.lower[ci.value] = _lower_bound(set) end if !iszero(flag & _UPPER_BOUND_MASK) - b.upper[ci.value] = _upper_bound(set) + model.upper[ci.value] = _upper_bound(set) end return end +function MOI.set( + ::VariablesContainer, + ::MOI.ConstraintSet, + ci::MOI.ConstraintIndex{MOI.VariableIndex}, + set, +) + return throw(MOI.SetTypeMismatch{MOI.set_type(ci),typeof(set)}()) +end + function MOI.get( b::VariablesContainer, ::MOI.NumberOfConstraints{MOI.VariableIndex,S}, diff --git a/src/attributes.jl b/src/attributes.jl index 9ddfdf470d..f9e6dba43f 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -145,9 +145,9 @@ message(err::SubmitNotAllowed) = err.message end An error indicating that the requested attribute `attr` could not be retrieved, -because the solver returned too few results compared to what was requested. -For instance, the user tries to retrieve `VariablePrimal(2)` when only one -solution is available, or when the model is infeasible and has no solution. +because the solver returned too few results compared to what was requested. +For instance, the user tries to retrieve `VariablePrimal(2)` when only one +solution is available, or when the model is infeasible and has no solution. See also: [`check_result_index_bounds`](@ref). """ @@ -159,9 +159,9 @@ end """ check_result_index_bounds(model::ModelLike, attr) -This function checks whether enough results are available in the `model` for -the requested `attr`, using its `result_index` field. If the model -does not have sufficient results to answer the query, it throws a +This function checks whether enough results are available in the `model` for +the requested `attr`, using its `result_index` field. If the model +does not have sufficient results to answer the query, it throws a [`ResultIndexBoundsError`](@ref). """ function check_result_index_bounds(model::ModelLike, attr) @@ -1025,10 +1025,10 @@ attribute_value_type(::ObjectiveFunctionType) = Type{<:AbstractFunction} A model attribute for the objective value of the primal solution `result_index`. -If the solver does not have a primal value for the objective because the +If the solver does not have a primal value for the objective because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check [`PrimalStatus`](@ref) before accessing the `ObjectiveValue` attribute. @@ -1046,10 +1046,10 @@ end A model attribute for the value of the objective function of the dual problem for the `result_index`th dual result. -If the solver does not have a dual value for the objective because the +If the solver does not have a dual value for the objective because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a primal solution is available), the result is undefined. Users should first check [`DualStatus`](@ref) before accessing the `DualObjectiveValue` attribute. @@ -1228,10 +1228,10 @@ struct VariablePrimalStart <: AbstractVariableAttribute end A variable attribute for the assignment to some primal variable's value in result `result_index`. If `result_index` is omitted, it is 1 by default. -If the solver does not have a primal value for the variable because the +If the solver does not have a primal value for the variable because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check [`PrimalStatus`](@ref) before accessing the `VariablePrimal` attribute. @@ -1298,10 +1298,10 @@ Possible values are: A variable attribute for the `BasisStatusCode` of a variable in result `result_index`, with respect to an available optimal solution basis. -If the solver does not have a basis statue for the variable because the +If the solver does not have a basis statue for the variable because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check [`PrimalStatus`](@ref) before accessing the `VariableBasisStatus` attribute. @@ -1408,10 +1408,10 @@ These solvers may return the value of `s` for `ConstraintPrimal`, rather than `b - Ax`. (Although these are constrained by an equality constraint, due to numerical tolerances they may not be identical.) -If the solver does not have a primal value for the constraint because the +If the solver does not have a primal value for the constraint because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check [`PrimalStatus`](@ref) before accessing the `ConstraintPrimal` attribute. @@ -1430,10 +1430,10 @@ end A constraint attribute for the assignment to some constraint's dual value(s) in result `result_index`. If `result_index` is omitted, it is 1 by default. -If the solver does not have a dual value for the variable because the +If the solver does not have a dual value for the variable because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a primal solution is available), the result is undefined. Users should first check [`DualStatus`](@ref) before accessing the `ConstraintDual` attribute. @@ -1452,10 +1452,10 @@ A constraint attribute for the `BasisStatusCode` of some constraint in result `result_index`, with respect to an available optimal solution basis. If `result_index` is omitted, it is 1 by default. -If the solver does not have a basis statue for the constraint because the +If the solver does not have a basis statue for the constraint because the `result_index` is beyond the available solutions (whose number is indicated by -the [`ResultCount`](@ref) attribute), getting this attribute must throw a -[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for +the [`ResultCount`](@ref) attribute), getting this attribute must throw a +[`ResultIndexBoundsError`](@ref). Otherwise, if the result is unavailable for another reason (for instance, only a dual solution is available), the result is undefined. Users should first check [`PrimalStatus`](@ref) before accessing the `ConstraintBasisStatus` attribute. @@ -1540,6 +1540,15 @@ struct ConstraintFunction <: AbstractConstraintAttribute end attribute_value_type(::ConstraintFunction) = AbstractFunction +struct FunctionTypeMismatch{F1,F2} <: Exception end +function Base.showerror(io::IO, err::FunctionTypeMismatch{F1,F2}) where {F1,F2} + return print( + io, + """$(typeof(err)): Cannot modify functions of different types. + Constraint type is $F1 while the replacement function is of type $F2.""", + ) +end + function throw_set_error_fallback( ::ModelLike, attr::ConstraintFunction, @@ -1553,15 +1562,11 @@ func_type(c::ConstraintIndex{F,S}) where {F,S} = F function throw_set_error_fallback( ::ModelLike, ::ConstraintFunction, - constraint_index::ConstraintIndex, + ci::ConstraintIndex, func::AbstractFunction; kwargs..., ) - return throw( - ArgumentError("""Cannot modify functions of different types. - Constraint type is $(func_type(constraint_index)) while the replacement - function is of type $(typeof(func))."""), - ) + return throw(FunctionTypeMismatch{func_type(ci),typeof(func)}()) end """ @@ -1573,6 +1578,16 @@ struct ConstraintSet <: AbstractConstraintAttribute end attribute_value_type(::ConstraintSet) = AbstractSet +struct SetTypeMismatch{S1,S2} <: Exception end +function Base.showerror(io::IO, err::SetTypeMismatch{S1,S2}) where {S1,S2} + return print( + io, + """$(typeof(err)): Cannot modify sets of different types. Constraint + type is $S1 while the replacement set is of type $S2. Use `transform` + instead.""", + ) +end + function throw_set_error_fallback( ::ModelLike, attr::ConstraintSet, @@ -1590,11 +1605,7 @@ function throw_set_error_fallback( set::AbstractSet; kwargs..., ) - return throw( - ArgumentError("""Cannot modify sets of different types. Constraint - type is $(set_type(constraint_index)) while the replacement set is of - type $(typeof(set)). Use `transform` instead."""), - ) + return throw(SetTypeMismatch{set_type(constraint_index),typeof(set)}()) end """ diff --git a/test/errors.jl b/test/errors.jl index 5fa7c704e3..36f60ae3cb 100644 --- a/test/errors.jl +++ b/test/errors.jl @@ -188,7 +188,10 @@ function test_errors_ConstraintFunction_NotAllowed() MOI.set(model, MOI.ConstraintFunction(), ci, vi) ) @test_throws( - ArgumentError, + MOI.FunctionTypeMismatch{ + MOI.VariableIndex, + MOI.ScalarAffineFunction{Float64}, + }, MOI.set( model, MOI.ConstraintFunction(), @@ -208,11 +211,11 @@ function test_errors_ConstraintSet_NotAllowed() MOI.set(model, MOI.ConstraintSet(), ci, MOI.EqualTo(1.0)) ) @test_throws( - ArgumentError, + MOI.SetTypeMismatch{MOI.EqualTo{Float64},MOI.EqualTo{Int}}, MOI.set(model, MOI.ConstraintSet(), ci, MOI.EqualTo(1)) ) @test_throws( - ArgumentError, + MOI.SetTypeMismatch{MOI.EqualTo{Float64},MOI.GreaterThan{Float64}}, MOI.set(model, MOI.ConstraintSet(), ci, MOI.GreaterThan(1.0)) ) end