From b0c410f7e946a2bdef29b6c1ec012d302afe78aa Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 8 Oct 2025 14:20:34 +1300 Subject: [PATCH 1/3] [Bridges] throw MOI error subtypes instead of Base.error() --- ...plexNormInfinityToSecondOrderConeBridge.jl | 6 ++-- .../bridges/IndicatorToMILPBridge.jl | 9 +++-- .../bridges/IntegerToZeroOneBridge.jl | 5 +-- .../Constraint/bridges/SplitIntervalBridge.jl | 11 +++--- src/Bridges/Objective/bridges/SlackBridge.jl | 17 +++++----- .../Objective/bridges/VectorSlackBridge.jl | 9 +++-- src/Bridges/bridge_optimizer.jl | 34 ++++++++----------- ...plexNormInfinityToSecondOrderConeBridge.jl | 2 +- .../Constraint/IndicatorToMILPBridge.jl | 9 ++--- .../Constraint/IntegerToZeroOneBridge.jl | 5 +-- .../Bridges/Constraint/SplitIntervalBridge.jl | 5 +-- test/Bridges/Objective/SlackBridge.jl | 10 +++--- test/Bridges/Objective/VectorSlackBridge.jl | 12 ++++--- test/Bridges/Variable/VectorizeBridge.jl | 2 +- 14 files changed, 60 insertions(+), 76 deletions(-) diff --git a/src/Bridges/Constraint/bridges/ComplexNormInfinityToSecondOrderConeBridge.jl b/src/Bridges/Constraint/bridges/ComplexNormInfinityToSecondOrderConeBridge.jl index 8a989883a2..86ec17f1ea 100644 --- a/src/Bridges/Constraint/bridges/ComplexNormInfinityToSecondOrderConeBridge.jl +++ b/src/Bridges/Constraint/bridges/ComplexNormInfinityToSecondOrderConeBridge.jl @@ -42,10 +42,8 @@ function bridge_constraint( ) where {T} fi_s = MOI.Utilities.scalarize(f) if !iszero(imag(fi_s[1])) - error( - "The epigraph variable `t` in `[t; x] in NormInfinityCone()` " * - "must be real. It is: $(fi_s[1])", - ) + msg = "The epigraph variable `t` in `[t; x] in NormInfinityCone()` must be real. It is: $(fi_s[1])" + throw(MOI.AddConstraintNotAllowed{typeof(f),typeof(s)}(msg)) end t = real(fi_s[1]) cis = MOI.ConstraintIndex{MOI.VectorAffineFunction{T},MOI.SecondOrderCone}[] diff --git a/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl b/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl index 8056af283b..18ce4616a2 100644 --- a/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl @@ -201,9 +201,9 @@ function _is_binary(model::MOI.ModelLike, x::MOI.VariableIndex) end function MOI.Bridges.final_touch( - bridge::IndicatorToMILPBridge{T,F}, + bridge::IndicatorToMILPBridge{T,F,A,S}, model::MOI.ModelLike, -) where {T,F} +) where {T,F,A,S} bounds = Dict{MOI.VariableIndex,NTuple{2,T}}() scalars = collect(MOI.Utilities.eachscalar(bridge.f)) fi = scalars[2] @@ -211,9 +211,8 @@ function MOI.Bridges.final_touch( if ret === nothing throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, fi)) elseif !_is_binary(model, scalars[1]) - error( - "Unable to reformulate indicator constraint to a MILP. The indicator variable must be binary.", - ) + msg = "Unable to reformulate indicator constraint to a MILP. The indicator variable must be binary." + throw(MOI.AddConstraintNotAllowed{F,MOI.Indicator{A,S}}(msg)) end if bridge.slack === nothing # This is the first time calling final_touch diff --git a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl index 4f31530e13..136d64905e 100644 --- a/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl +++ b/src/Bridges/Constraint/bridges/IntegerToZeroOneBridge.jl @@ -156,10 +156,7 @@ function MOI.Bridges.final_touch( if ret === bridge.last_bounds return nothing # final_touch already called elseif ret[1] == typemin(T) || ret[2] == typemax(T) - error( - "Unable to use IntegerToZeroOneBridge because the variable " * - "$(bridge.x) has a non-finite domain", - ) + throw(MOI.Bridges.BridgeRequiresFiniteDomainError(bridge, bridge.x)) end f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(T(1), bridge.x)], T(0)) lb, ub = ceil(Int, ret[1]), floor(Int, ret[2]) diff --git a/src/Bridges/Constraint/bridges/SplitIntervalBridge.jl b/src/Bridges/Constraint/bridges/SplitIntervalBridge.jl index 0c9e501671..ab720e9f46 100644 --- a/src/Bridges/Constraint/bridges/SplitIntervalBridge.jl +++ b/src/Bridges/Constraint/bridges/SplitIntervalBridge.jl @@ -230,9 +230,8 @@ function MOI.get( if bridge.upper !== nothing return MOI.get(model, attr, bridge.upper) end - return error( - "Cannot get `$attr` for a constraint in the interval `[-Inf, Inf]`.", - ) + msg = "Cannot get `$attr` for a constraint in the interval `[-Inf, Inf]`." + return throw(MOI.GetAttributeNotAllowed(attr, msg)) end function MOI.set( @@ -333,10 +332,8 @@ function MOI.get( # The only case where the interval `[-∞, ∞]` is allowed is for # `VariableIndex` constraints but `ConstraintBasisStatus` is not # defined for `VariableIndex` constraints. - error( - "Cannot get `$attr` for a constraint in the interval " * - "`[-Inf, Inf]`.", - ) + msg = "Cannot get `$attr` for a constraint in the interval `[-Inf, Inf]`." + throw(MOI.GetAttributeNotAllowed(attr, msg)) end return upper_stat end diff --git a/src/Bridges/Objective/bridges/SlackBridge.jl b/src/Bridges/Objective/bridges/SlackBridge.jl index 9413f65c02..60b963697d 100644 --- a/src/Bridges/Objective/bridges/SlackBridge.jl +++ b/src/Bridges/Objective/bridges/SlackBridge.jl @@ -58,15 +58,16 @@ function bridge_objective( ) where {T,F,G<:MOI.AbstractScalarFunction} slack = MOI.add_variable(model) f = MOI.Utilities.operate(-, T, func, slack) - if MOI.get(model, MOI.ObjectiveSense()) == MOI.MIN_SENSE - set = MOI.LessThan(zero(T)) - elseif MOI.get(model, MOI.ObjectiveSense()) == MOI.MAX_SENSE - set = MOI.GreaterThan(zero(T)) + sense = MOI.get(model, MOI.ObjectiveSense()) + if sense == MOI.FEASIBILITY_SENSE + msg = "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when using `MOI.Bridges.Objective.SlackBridge`." + throw(MOI.SetAttributeNotAllowed(MOI.ObjectiveFunction{G}(), msg)) + end + set = if sense == MOI.MIN_SENSE + MOI.LessThan(zero(T)) else - error( - "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when", - " using `MOI.Bridges.Objective.SlackBridge`.", - ) + @assert sense == MOI.MAX_SENSE + MOI.GreaterThan(zero(T)) end constraint = MOI.Utilities.normalize_and_add_constraint(model, f, set) MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), slack) diff --git a/src/Bridges/Objective/bridges/VectorSlackBridge.jl b/src/Bridges/Objective/bridges/VectorSlackBridge.jl index 9a8d979e39..5c3227cef8 100644 --- a/src/Bridges/Objective/bridges/VectorSlackBridge.jl +++ b/src/Bridges/Objective/bridges/VectorSlackBridge.jl @@ -69,15 +69,14 @@ function bridge_objective( ) sense = MOI.get(model, MOI.ObjectiveSense()) if sense == MOI.FEASIBILITY_SENSE - error( - "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when", - " using `MOI.Bridges.Objective.VectorSlackBridge`.", - ) + msg = "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when using `MOI.Bridges.Objective.VectorSlackBridge`." + throw(MOI.SetAttributeNotAllowed(MOI.ObjectiveFunction{G}(), msg)) end slacks = MOI.VectorOfVariables(variables[slacked_objectives]) f = if sense == MOI.MIN_SENSE MOI.Utilities.operate(-, T, slacks, funcs[slacked_objectives]) - elseif sense == MOI.MAX_SENSE + else + @assert sense == MOI.MAX_SENSE MOI.Utilities.operate(-, T, funcs[slacked_objectives], slacks) end set = MOI.Nonnegatives(length(slacked_objectives)) diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 76fbae02d1..a45fbdc185 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -2032,36 +2032,32 @@ _check_double_single_variable(::AbstractBridgeOptimizer, ::Any, ::Any) = nothing function MOI.add_constraint( b::AbstractBridgeOptimizer, - f::MOI.AbstractFunction, - s::MOI.AbstractSet, -) + f::F, + s::S, +) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} if Variable.has_bridges(Variable.bridges(b)) - if f isa MOI.VariableIndex + if F <: MOI.VariableIndex if is_bridged(b, f) - if MOI.is_valid( - b, - MOI.ConstraintIndex{MOI.VariableIndex,typeof(s)}(f.value), - ) + ci = MOI.ConstraintIndex{MOI.VariableIndex,S}(f.value) + if MOI.is_valid(b, ci) # The other constraint could have been through a variable bridge. - error( - "Cannot add two `VariableIndex`-in-`$(typeof(s))`", - " on the same variable $(f).", - ) + msg = "Cannot add two `VariableIndex`-in-`$S` on the same variable $f." + throw(MOI.AddConstraintNotAllowed{F,S}(msg)) end BridgeType = Constraint.concrete_bridge_type( constraint_scalar_functionize_bridge(b), - typeof(f), - typeof(s), + F, + S, ) MOI.add_constraint(Variable.bridges(b), f, s) return add_bridged_constraint(b, BridgeType, f, s) end - elseif f isa MOI.VectorOfVariables + elseif F <: MOI.VectorOfVariables if any(vi -> is_bridged(b, vi), f.variables) BridgeType = Constraint.concrete_bridge_type( constraint_vector_functionize_bridge(b), - typeof(f), - typeof(s), + F, + S, ) return add_bridged_constraint(b, BridgeType, f, s) end @@ -2069,12 +2065,12 @@ function MOI.add_constraint( f, s = bridged_constraint_function(b, f, s) end end - if is_bridged(b, typeof(f), typeof(s)) + if is_bridged(b, F, S) _check_double_single_variable(b, f, s) # We compute `BridgeType` first as `concrete_bridge_type` calls # `bridge_type` which might throw an `UnsupportedConstraint` error in # which case, we do not want any modification to have been done - BridgeType = Constraint.concrete_bridge_type(b, typeof(f), typeof(s)) + BridgeType = Constraint.concrete_bridge_type(b, F, S) # `add_constraint` might throw an `UnsupportedConstraint` but no # modification has been done in the previous line return add_bridged_constraint(b, BridgeType, f, s) diff --git a/test/Bridges/Constraint/ComplexNormInfinityToSecondOrderConeBridge.jl b/test/Bridges/Constraint/ComplexNormInfinityToSecondOrderConeBridge.jl index 4c4c48dbaa..dd7dd92bdf 100644 --- a/test/Bridges/Constraint/ComplexNormInfinityToSecondOrderConeBridge.jl +++ b/test/Bridges/Constraint/ComplexNormInfinityToSecondOrderConeBridge.jl @@ -58,7 +58,7 @@ function test_imag_t() f_x = (1.0 + 2.0im) * x[2] + (3.0 + 4.0im) f = MOI.Utilities.operate(vcat, Complex{Float64}, f_t, f_x) @test_throws( - ErrorException( + MOI.AddConstraintNotAllowed{typeof(f),MOI.NormInfinityCone}( "The epigraph variable `t` in `[t; x] in NormInfinityCone()` " * "must be real. It is: $f_t", ), diff --git a/test/Bridges/Constraint/IndicatorToMILPBridge.jl b/test/Bridges/Constraint/IndicatorToMILPBridge.jl index f5f4acc56b..2f4a34668f 100644 --- a/test/Bridges/Constraint/IndicatorToMILPBridge.jl +++ b/test/Bridges/Constraint/IndicatorToMILPBridge.jl @@ -306,13 +306,10 @@ function test_runtests_error_not_binary() model = MOI.Bridges.Constraint.IndicatorToMILP{Int}(inner) x = MOI.add_variables(model, 2) MOI.add_constraint(model, x[2], MOI.Interval(0, 4)) - c = MOI.add_constraint( - model, - MOI.VectorOfVariables(x), - MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)), - ) + set = MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(2)) + c = MOI.add_constraint(model, MOI.VectorOfVariables(x), set) @test_throws( - ErrorException( + MOI.AddConstraintNotAllowed{MOI.VectorOfVariables,typeof(set)}( "Unable to reformulate indicator constraint to a MILP. The indicator variable must be binary.", ), MOI.Bridges.final_touch(model), diff --git a/test/Bridges/Constraint/IntegerToZeroOneBridge.jl b/test/Bridges/Constraint/IntegerToZeroOneBridge.jl index cc2f4bfa7a..936cec85ae 100644 --- a/test/Bridges/Constraint/IntegerToZeroOneBridge.jl +++ b/test/Bridges/Constraint/IntegerToZeroOneBridge.jl @@ -78,10 +78,7 @@ function test_finite_domain_error() model = MOI.Bridges.Constraint.IntegerToZeroOne{Int}(inner) x, _ = MOI.add_constrained_variable(model, MOI.Integer()) @test_throws( - ErrorException( - "Unable to use IntegerToZeroOneBridge because the variable " * - "$(x) has a non-finite domain", - ), + MOI.Bridges.BridgeRequiresFiniteDomainError, MOI.Bridges.final_touch(model), ) return diff --git a/test/Bridges/Constraint/SplitIntervalBridge.jl b/test/Bridges/Constraint/SplitIntervalBridge.jl index 21395cad26..488a8a490a 100644 --- a/test/Bridges/Constraint/SplitIntervalBridge.jl +++ b/test/Bridges/Constraint/SplitIntervalBridge.jl @@ -320,10 +320,7 @@ function _test_interval( MOI.ConstraintPrimalStart(), MOI.ConstraintBasisStatus(), ] - err = ErrorException( - "Cannot get `$attr` for a constraint " * - "in the interval `[-Inf, Inf]`.", - ) + err = MOI.GetAttributeNotAllowed{typeof(attr)} @test_throws err MOI.get(bridged_mock, attr, ci) end @test zero(T) == MOI.get(bridged_mock, MOI.ConstraintDualStart(), ci) diff --git a/test/Bridges/Objective/SlackBridge.jl b/test/Bridges/Objective/SlackBridge.jl index 9757aae392..2295c5b036 100644 --- a/test/Bridges/Objective/SlackBridge.jl +++ b/test/Bridges/Objective/SlackBridge.jl @@ -28,12 +28,14 @@ function test_SlackBridge_ObjectiveSense_error() model = MOI.Bridges.Objective.Slack{Float64}(inner) x = MOI.add_variable(model) f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.1, x)], 1.2) + attr = MOI.ObjectiveFunction{typeof(f)}() @test_throws( - ErrorException( - "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when" * - " using `MOI.Bridges.Objective.SlackBridge`.", + MOI.SetAttributeNotAllowed( + attr, + "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when " * + "using `MOI.Bridges.Objective.SlackBridge`.", ), - MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f), + MOI.set(model, attr, f), ) return end diff --git a/test/Bridges/Objective/VectorSlackBridge.jl b/test/Bridges/Objective/VectorSlackBridge.jl index 142007c4fa..c7342bc4e2 100644 --- a/test/Bridges/Objective/VectorSlackBridge.jl +++ b/test/Bridges/Objective/VectorSlackBridge.jl @@ -115,11 +115,15 @@ function test_objective_sense_before_function() model = MOI.Bridges.Objective.VectorSlack{Float64}(inner) x = MOI.add_variable(model) f = MOI.Utilities.operate(vcat, Float64, 1.0 * x, 1.0 * x) - err = ErrorException( - "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when using " * - "`MOI.Bridges.Objective.VectorSlackBridge`.", + attr = MOI.ObjectiveFunction{typeof(f)}() + @test_throws( + MOI.SetAttributeNotAllowed( + attr, + "Set `MOI.ObjectiveSense` before `MOI.ObjectiveFunction` when " * + "using `MOI.Bridges.Objective.VectorSlackBridge`.", + ), + MOI.set(model, attr, f), ) - @test_throws err MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) return end diff --git a/test/Bridges/Variable/VectorizeBridge.jl b/test/Bridges/Variable/VectorizeBridge.jl index 0f732cbc8e..e24570ddc1 100644 --- a/test/Bridges/Variable/VectorizeBridge.jl +++ b/test/Bridges/Variable/VectorizeBridge.jl @@ -128,7 +128,7 @@ function test_exp3_with_add_constrained_variable_y() @test MOI.get(bridged_mock, MOI.ConstraintDual(), ec) ≈ [-1.0, log(5) - 1, 1 / 5] - err = ErrorException( + err = MOI.AddConstraintNotAllowed{MOI.VariableIndex,MOI.LessThan{Float64}}( "Cannot add two `VariableIndex`-in-`MathOptInterface.LessThan{Float64}`" * " on the same variable MOI.VariableIndex(-1).", ) From 7de0693004bdb46b528c6b34887ff0d19833ff14 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 8 Oct 2025 15:11:53 +1300 Subject: [PATCH 2/3] Add another --- src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl | 12 ++++++------ test/Bridges/Constraint/QuadtoSOCBridge.jl | 5 ++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl b/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl index 98364a5dcb..1031949636 100644 --- a/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl +++ b/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl @@ -256,12 +256,12 @@ function _primal_start_or_error(model, attr, v) var_attr = MOI.VariablePrimalStart() value = MOI.get(model, MOI.VariablePrimalStart(), v) if isnothing(value) - error( - "In order to set the `$attr`, the", - "`MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the ", - "`$var_attr` but it is not set. Set the `$var_attr` first before ", - "setting the `$attr` in order to fix this.", - ) + msg = + "In order to set the `$attr`, the " * + "`MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the " * + "`$var_attr` but it is not set. Set the `$var_attr` first before " * + "setting the `$attr` in order to fix this." + throw(MOI.SetAttributeNotAllowed(attr, msg)) end return value end diff --git a/test/Bridges/Constraint/QuadtoSOCBridge.jl b/test/Bridges/Constraint/QuadtoSOCBridge.jl index 0c460cf9cf..6ea2b9cf1c 100644 --- a/test/Bridges/Constraint/QuadtoSOCBridge.jl +++ b/test/Bridges/Constraint/QuadtoSOCBridge.jl @@ -164,9 +164,8 @@ function test_qcp() S = MOI.GreaterThan{Float64} ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{F,S}())) for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] - err = ErrorException( - "In order to set the `$attr`, the`MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the `MathOptInterface.VariablePrimalStart()` but it is not set. Set the `MathOptInterface.VariablePrimalStart()` first before setting the `$attr` in order to fix this.", - ) + msg = "In order to set the `$attr`, the`MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the `MathOptInterface.VariablePrimalStart()` but it is not set. Set the `MathOptInterface.VariablePrimalStart()` first before setting the `$attr` in order to fix this." + err = MOI.SetAttributeNotAllowed(attr, msg) @test_throws err MOI.set(bridged_mock, attr, ci, 0.0) end return From de3b473c6f5c1aec24b9af9299fbf34d60f361df Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 9 Oct 2025 11:54:03 +1300 Subject: [PATCH 3/3] Update --- test/Bridges/Constraint/QuadtoSOCBridge.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Bridges/Constraint/QuadtoSOCBridge.jl b/test/Bridges/Constraint/QuadtoSOCBridge.jl index 6ea2b9cf1c..20db1a09df 100644 --- a/test/Bridges/Constraint/QuadtoSOCBridge.jl +++ b/test/Bridges/Constraint/QuadtoSOCBridge.jl @@ -164,7 +164,7 @@ function test_qcp() S = MOI.GreaterThan{Float64} ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{F,S}())) for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()] - msg = "In order to set the `$attr`, the`MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the `MathOptInterface.VariablePrimalStart()` but it is not set. Set the `MathOptInterface.VariablePrimalStart()` first before setting the `$attr` in order to fix this." + msg = "In order to set the `$attr`, the `MOI.Bridges.Constraint.QuadtoSOCBridge` needs to get the `MathOptInterface.VariablePrimalStart()` but it is not set. Set the `MathOptInterface.VariablePrimalStart()` first before setting the `$attr` in order to fix this." err = MOI.SetAttributeNotAllowed(attr, msg) @test_throws err MOI.set(bridged_mock, attr, ci, 0.0) end