diff --git a/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl b/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl index 7b31e03d9f..e3e5351881 100644 --- a/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/IndicatorToMILPBridge.jl @@ -126,9 +126,6 @@ function MOI.delete( model::MOI.ModelLike, bridge::IndicatorToMILPBridge{T}, ) where {T} - if bridge.slack === nothing - return # final_touch not called, so we can safely skip - end MOI.delete.(model, bridge.slack_bounds) MOI.delete(model, bridge.constraint) MOI.delete(model, bridge.slack::MOI.VariableIndex) diff --git a/src/Bridges/Constraint/bridges/RSOCtoSOCBridge.jl b/src/Bridges/Constraint/bridges/RSOCtoSOCBridge.jl index c25f678f82..718675d02f 100644 --- a/src/Bridges/Constraint/bridges/RSOCtoSOCBridge.jl +++ b/src/Bridges/Constraint/bridges/RSOCtoSOCBridge.jl @@ -59,6 +59,8 @@ end function MOI.Bridges.map_function(::Type{<:RSOCtoSOCBridge{T}}, func) where {T} scalars = MOI.Utilities.eachscalar(func) + # We cannot construct MOI.RotatedSecondOrderCone(1) + @assert length(scalars) >= 2 t, u, x = scalars[1], scalars[2], scalars[3:end] ts = MOI.Utilities.operate!(/, T, t, sqrt(T(2))) us = MOI.Utilities.operate!(/, T, u, sqrt(T(2))) diff --git a/src/Bridges/Constraint/bridges/SOCtoPSDBridge.jl b/src/Bridges/Constraint/bridges/SOCtoPSDBridge.jl index 32133a17ff..0d483a9797 100644 --- a/src/Bridges/Constraint/bridges/SOCtoPSDBridge.jl +++ b/src/Bridges/Constraint/bridges/SOCtoPSDBridge.jl @@ -256,12 +256,9 @@ end function MOI.Bridges.map_function(::Type{<:RSOCtoPSDBridge{T}}, func) where {T} scalars = MOI.Utilities.eachscalar(func) - if length(scalars) < 2 - error( - "Unable to bridge RotatedSecondOrderCone to PSD because the ", - "dimension is too small: got $(length(scalars)), expected >= 2.", - ) - elseif length(scalars) == 2 + # We cannot construct MOI.RotatedSecondOrderCone(1) + @assert length(scalars) >= 2 + if length(scalars) == 2 return func end # Input is (t, u, x), and we need [t x'; x 2uI] diff --git a/src/Bridges/Constraint/bridges/SOCtoRSOCBridge.jl b/src/Bridges/Constraint/bridges/SOCtoRSOCBridge.jl index af71aa6d88..bc03635030 100644 --- a/src/Bridges/Constraint/bridges/SOCtoRSOCBridge.jl +++ b/src/Bridges/Constraint/bridges/SOCtoRSOCBridge.jl @@ -50,13 +50,6 @@ function MOI.Bridges.map_set( ::Type{<:SOCtoRSOCBridge}, set::MOI.SecondOrderCone, ) - if MOI.dimension(set) == 1 - error( - "Unable to reformulate a `SecondOrderCone` into a " * - "`RotatedSecondOrderCone` because the dimension of `1` is too " * - "small", - ) - end return MOI.RotatedSecondOrderCone(MOI.dimension(set)) end @@ -69,6 +62,14 @@ end function MOI.Bridges.map_function(::Type{<:SOCtoRSOCBridge{T}}, func) where {T} scalars = MOI.Utilities.eachscalar(func) + if length(scalars) < 2 + err = DimensionMismatch( + "Unable to reformulate a `SecondOrderCone` into a " * + "`RotatedSecondOrderCone` because the output dimension is too " * + "small", + ) + throw(err) + end t, u, x = scalars[1], scalars[2], scalars[3:end] ts = MOI.Utilities.operate!(/, T, t, sqrt(T(2))) us = MOI.Utilities.operate!(/, T, u, sqrt(T(2))) diff --git a/src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl b/src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl index a704e86755..fb941a73fb 100644 --- a/src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/SOS1ToMILPBridge.jl @@ -116,9 +116,6 @@ function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::SOS1ToMILPBridge) end function MOI.delete(model::MOI.ModelLike, bridge::SOS1ToMILPBridge) - if isempty(bridge.variables) - return - end MOI.delete(model, bridge.equal_to) for ci in bridge.less_than MOI.delete(model, ci) diff --git a/src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl b/src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl index 2e25b961fa..e7931d7012 100644 --- a/src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl +++ b/src/Bridges/Constraint/bridges/SOS2ToMILPBridge.jl @@ -116,9 +116,6 @@ function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::SOS2ToMILPBridge) end function MOI.delete(model::MOI.ModelLike, bridge::SOS2ToMILPBridge) - if isempty(bridge.variables) - return - end MOI.delete(model, bridge.equal_to) for ci in bridge.less_than MOI.delete(model, ci) diff --git a/src/Bridges/Constraint/bridges/ScalarSlackBridge.jl b/src/Bridges/Constraint/bridges/ScalarSlackBridge.jl index 65ce399a7c..fd7d46ae17 100644 --- a/src/Bridges/Constraint/bridges/ScalarSlackBridge.jl +++ b/src/Bridges/Constraint/bridges/ScalarSlackBridge.jl @@ -25,13 +25,6 @@ function MOI.get( return 1 end -function MOI.get( - ::_AbstractSlackBridge{T,VF,ZS,F,S}, - ::MOI.NumberOfConstraints{VF,S}, -)::Int64 where {T,VF,ZS,F,S} - return 1 -end - function MOI.get( bridge::_AbstractSlackBridge{T,VF,ZS,F}, ::MOI.ListOfConstraintIndices{F,ZS}, @@ -39,6 +32,13 @@ function MOI.get( return [bridge.equality] end +function MOI.get( + ::_AbstractSlackBridge{T,VF,ZS,F,S}, + ::MOI.NumberOfConstraints{VF,S}, +)::Int64 where {T,VF,ZS,F,S} + return 1 +end + function MOI.get( bridge::_AbstractSlackBridge{T,VF,ZS,F,S}, ::MOI.ListOfConstraintIndices{VF,S}, @@ -55,20 +55,20 @@ function MOI.get( # that the original set was the same as the slacked set. return error( "Internal error: this method should never be called because it " * - "represents and invalid state. Please open an issue to report.", + "represents an invalid state. Please open an issue to report.", ) end function MOI.get( bridge::_AbstractSlackBridge{T,VF,S,F,S}, - ::MOI.ListOfConstraintIndices{F,S}, + ::MOI.ListOfConstraintIndices{VF,S}, ) where {T,VF,S,F} # This method is needed to resolve a possible ambiguity reported by # Test.detect_ambiguities. It can't happen in practice because it would mean # that the original set was the same as the slacked set. return error( "Internal error: this method should never be called because it " * - "represents and invalid state. Please open an issue to report.", + "represents an invalid state. Please open an issue to report.", ) end diff --git a/src/Bridges/Objective/bridge.jl b/src/Bridges/Objective/bridge.jl index d2b03ddfc4..2bd5fa7042 100644 --- a/src/Bridges/Objective/bridge.jl +++ b/src/Bridges/Objective/bridge.jl @@ -95,12 +95,13 @@ end function MOI.set( ::MOI.ModelLike, - ::MOI.ObjectiveSense, + attr::MOI.ObjectiveSense, bridge::AbstractBridge, ::MOI.OptimizationSense, ) return throw( - ArgumentError( + MOI.SetAttributeNotAllowed( + attr, "Objective bridge of type `$(typeof(bridge))` does not support " * "modifying the objective sense. As a workaround, set the sense " * "to `MOI.FEASIBILITY_SENSE` to clear the objective function and " * @@ -111,11 +112,12 @@ end function MOI.get( ::MOI.ModelLike, - ::MOI.ObjectiveFunction, + attr::MOI.ObjectiveFunction, bridge::AbstractBridge, ) return throw( - ArgumentError( + MOI.GetAttributeNotAllowed( + attr, "ObjectiveFunction bridge of type `$(typeof(bridge))` does not" * " support getting the objective function.", ), diff --git a/src/Bridges/bridge_optimizer.jl b/src/Bridges/bridge_optimizer.jl index 0a17df458e..f8c335c246 100644 --- a/src/Bridges/bridge_optimizer.jl +++ b/src/Bridges/bridge_optimizer.jl @@ -624,6 +624,7 @@ function MOI.delete(b::AbstractBridgeOptimizer, vis::Vector{MOI.VariableIndex}) else MOI.delete(b.model, vis) end + return end function MOI.delete(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex) diff --git a/test/Bridges/Constraint/RSOCBridge.jl b/test/Bridges/Constraint/RSOCBridge.jl index db52767cc0..8bc4bc15c5 100644 --- a/test/Bridges/Constraint/RSOCBridge.jl +++ b/test/Bridges/Constraint/RSOCBridge.jl @@ -179,6 +179,26 @@ function test_runtests() return end +function test_dimension_mismatch_SOCR() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SOCR{Float64}(inner) + @test_throws( + DimensionMismatch, + MOI.add_constrained_variables(model, MOI.SecondOrderCone(1)), + ) + return +end + +function test_dimension_mismatch_RSOC() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.RSOC{Float64}(inner) + @test_throws( + DimensionMismatch, + MOI.add_constrained_variables(model, MOI.RotatedSecondOrderCone(1)), + ) + return +end + end # module TestConstraintRSOC.runtests() diff --git a/test/Bridges/Constraint/SOCtoPSDBridge.jl b/test/Bridges/Constraint/SOCtoPSDBridge.jl index 3f91df871b..ae9e1b02fe 100644 --- a/test/Bridges/Constraint/SOCtoPSDBridge.jl +++ b/test/Bridges/Constraint/SOCtoPSDBridge.jl @@ -231,6 +231,16 @@ function test_bridging_cost_RSOCtoPSD() return end +function test_dimension_mismatch_RSOC_to_PSD() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.RSOCtoPSD{Float64}(inner) + @test_throws( + DimensionMismatch, + MOI.add_constrained_variables(model, MOI.RotatedSecondOrderCone(1)), + ) + return +end + end # module TestConstraintSOCtoPSD.runtests() diff --git a/test/Bridges/Constraint/ScalarSlackBridge.jl b/test/Bridges/Constraint/ScalarSlackBridge.jl index 830c0a4a0a..eb5a932fb1 100644 --- a/test/Bridges/Constraint/ScalarSlackBridge.jl +++ b/test/Bridges/Constraint/ScalarSlackBridge.jl @@ -411,6 +411,47 @@ function test_runtests() return end +function test_basis_status() + inner = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + model = MOI.Bridges.Constraint.ScalarSlack{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, 1.0 * x, MOI.GreaterThan(1.0)) + y = MOI.get(inner, MOI.ListOfVariableIndices()) + MOI.set.(inner, MOI.VariableBasisStatus(), y, MOI.BASIC) + @test MOI.get(model, MOI.ConstraintBasisStatus(), c) == MOI.BASIC + d = MOI.add_constraint(model, 1.0 * x, MOI.Interval(1.0, 2.0)) + z = last(MOI.get(inner, MOI.ListOfVariableIndices())) + MOI.set(inner, MOI.VariableBasisStatus(), z, MOI.SUPER_BASIC) + @test MOI.get(model, MOI.ConstraintBasisStatus(), d) == MOI.SUPER_BASIC + return +end + +function test_internal_error() + F, S = MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64} + BT = MOI.Bridges.Constraint.ScalarSlackBridge{Float64,F,S} + model = MOI.Utilities.Model{Float64}() + x = MOI.add_variable(model) + set = MOI.EqualTo(1.0) + bridge = MOI.Bridges.Constraint.bridge_constraint(BT, model, 1.0 * x, set) + @test_throws( + ErrorException( + "Internal error: this method should never be called because it " * + "represents an invalid state. Please open an issue to report.", + ), + MOI.get(bridge, MOI.NumberOfConstraints{MOI.VariableIndex,S}()), + ) + @test_throws( + ErrorException( + "Internal error: this method should never be called because it " * + "represents an invalid state. Please open an issue to report.", + ), + MOI.get(bridge, MOI.ListOfConstraintIndices{MOI.VariableIndex,S}()), + ) + return +end + end # module TestConstraintSlack.runtests() diff --git a/test/Bridges/Constraint/SplitIntervalBridge.jl b/test/Bridges/Constraint/SplitIntervalBridge.jl index 79009370e5..21395cad26 100644 --- a/test/Bridges/Constraint/SplitIntervalBridge.jl +++ b/test/Bridges/Constraint/SplitIntervalBridge.jl @@ -499,6 +499,33 @@ function test_runtests_vector() return end +function test_get_function() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SplitInterval{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, 1.0 * x, MOI.Interval(-Inf, Inf)) + @test MOI.get(model, MOI.ConstraintFunction(), c) ≈ 1.0 * x + return +end + +function test_modify_set() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Constraint.SplitInterval{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, 1.0 * x, MOI.Interval(-Inf, Inf)) + for set in [ + MOI.Interval(-Inf, Inf), + MOI.Interval(1.0, 2.0), + MOI.Interval(2.0, 3.0), + MOI.Interval(-Inf, Inf), + ] + MOI.set(model, MOI.ConstraintSet(), c, set) + @test MOI.get(model, MOI.ConstraintSet(), c) == set + @test MOI.get(model, MOI.ConstraintFunction(), c) ≈ 1.0 * x + end + return +end + end # module TestConstraintSplitInterval.runtests() diff --git a/test/Bridges/Constraint/VectorizeBridge.jl b/test/Bridges/Constraint/VectorizeBridge.jl index fefbe0fc4a..e17e9c4e42 100644 --- a/test/Bridges/Constraint/VectorizeBridge.jl +++ b/test/Bridges/Constraint/VectorizeBridge.jl @@ -285,6 +285,22 @@ function test_VectorNonlinearFunction() return end +function test_constraint_primal_ray() + inner = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + model = MOI.Bridges.Constraint.Vectorize{Float64}(inner) + x = MOI.add_variable(model) + c = MOI.add_constraint(model, 1.0 * x, MOI.EqualTo(3.0)) + MOI.set(inner, MOI.PrimalStatus(), MOI.INFEASIBILITY_CERTIFICATE) + y = only(MOI.get(inner, MOI.ListOfVariableIndices())) + MOI.set(inner, MOI.VariablePrimal(), y, 1.23) + @test MOI.get(model, MOI.ConstraintPrimal(), c) == 1.23 + MOI.set(inner, MOI.PrimalStatus(), MOI.FEASIBLE_POINT) + @test MOI.get(model, MOI.ConstraintPrimal(), c) == 1.23 + return +end + end # module TestConstraintVectorize.runtests() diff --git a/test/Bridges/Objective/SlackBridge.jl b/test/Bridges/Objective/SlackBridge.jl index 68fb70e2e7..9757aae392 100644 --- a/test/Bridges/Objective/SlackBridge.jl +++ b/test/Bridges/Objective/SlackBridge.jl @@ -46,7 +46,7 @@ function test_SlackBridge_ObjectiveSense_modify() MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) @test_throws( - ArgumentError, + MOI.SetAttributeNotAllowed, MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE), ) MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) @@ -328,7 +328,8 @@ function test_original() ("y", MOI.GreaterThan{Float64}(2.0)), ], ) - err = ArgumentError( + err = MOI.SetAttributeNotAllowed( + MOI.ObjectiveSense(), "Objective bridge of type `$(MOI.Bridges.Objective.SlackBridge{Float64,MOI.ScalarQuadraticFunction{Float64},MOI.ScalarQuadraticFunction{Float64}})`" * " does not support modifying the objective sense. As a workaround, set" * " the sense to `MOI.FEASIBILITY_SENSE` to clear the objective function" * diff --git a/test/Bridges/Variable/HermitianToSymmetricPSDBridge.jl b/test/Bridges/Variable/HermitianToSymmetricPSDBridge.jl index 151dca914e..d018e74589 100644 --- a/test/Bridges/Variable/HermitianToSymmetricPSDBridge.jl +++ b/test/Bridges/Variable/HermitianToSymmetricPSDBridge.jl @@ -113,6 +113,28 @@ function test_runtests() return end +function test_delete() + inner = MOI.Utilities.Model{Float64}() + model = MOI.Bridges.Variable.HermitianToSymmetricPSD{Float64}(inner) + set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) + x, _ = MOI.add_constrained_variables(model, set) + MOI.delete(model, x) + @test MOI.is_empty(inner) + return +end + +function test_set_variable_primal_start() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Variable.HermitianToSymmetricPSD{Float64}(inner) + set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) + x, _ = MOI.add_constrained_variables(model, set) + MOI.set(model, MOI.VariablePrimalStart(), x[1], 1.0) + @test MOI.get(model, MOI.VariablePrimalStart(), x[1]) == 1.0 + MOI.set(model, MOI.VariablePrimalStart(), x[1], nothing) + @test MOI.get(model, MOI.VariablePrimalStart(), x[1]) === nothing + return +end + end # module TestVariableHermitianToSymmetricPSD.runtests() diff --git a/test/Bridges/Variable/NonposToNonnegBridge.jl b/test/Bridges/Variable/NonposToNonnegBridge.jl index 93381d1b25..6d7f00a213 100644 --- a/test/Bridges/Variable/NonposToNonnegBridge.jl +++ b/test/Bridges/Variable/NonposToNonnegBridge.jl @@ -199,6 +199,14 @@ function test_runtests() return end +function test_adjoint_map_function() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Variable.NonposToNonneg{Float64}(inner) + x, _ = MOI.add_constrained_variables(model, MOI.Nonpositives(1)) + @test MOI.Bridges.adjoint_map_function(model.map[only(x)], 1.23) == -1.23 + return +end + end # module TestVariableFlipSign.runtests() diff --git a/test/Bridges/Variable/RSOCtoSOCBridge.jl b/test/Bridges/Variable/RSOCtoSOCBridge.jl index 0134868226..695e9c28a4 100644 --- a/test/Bridges/Variable/RSOCtoSOCBridge.jl +++ b/test/Bridges/Variable/RSOCtoSOCBridge.jl @@ -144,6 +144,15 @@ function test_runtests() return end +function test_adjoint_map_function() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Variable.RSOCtoSOC{Float64}(inner) + x, _ = MOI.add_constrained_variables(model, MOI.RotatedSecondOrderCone(3)) + @test MOI.Bridges.adjoint_map_function(model.map[first(x)], [2, 0.5, 1]) ≈ + [2.5 / sqrt(2), +1.5 / sqrt(2), 1.0] + return +end + end # module TestVariableRSOCtoSOC.runtests() diff --git a/test/Bridges/Variable/SOCtoRSOCBridge.jl b/test/Bridges/Variable/SOCtoRSOCBridge.jl index cd65caf75d..59388e51aa 100644 --- a/test/Bridges/Variable/SOCtoRSOCBridge.jl +++ b/test/Bridges/Variable/SOCtoRSOCBridge.jl @@ -151,6 +151,15 @@ function test_runtests() return end +function test_adjoint_map_function() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Variable.SOCtoRSOC{Float64}(inner) + x, _ = MOI.add_constrained_variables(model, MOI.SecondOrderCone(3)) + @test MOI.Bridges.adjoint_map_function(model.map[first(x)], [2, 0.5, 1]) ≈ + [2.5 / sqrt(2), +1.5 / sqrt(2), 1.0] + return +end + end # module TestVariableSOCtoRSOC.runtests() diff --git a/test/Bridges/Variable/VectorizeBridge.jl b/test/Bridges/Variable/VectorizeBridge.jl index 4148623501..0f732cbc8e 100644 --- a/test/Bridges/Variable/VectorizeBridge.jl +++ b/test/Bridges/Variable/VectorizeBridge.jl @@ -287,6 +287,30 @@ function test_runtests() return end +function test_list_of_constraint_indices() + inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + model = MOI.Bridges.Variable.Vectorize{Float64}(inner) + x, _ = MOI.add_constrained_variable(model, MOI.EqualTo(1.0)) + attr = MOI.ListOfConstraintIndices{MOI.VectorOfVariables,MOI.Zeros}() + @test isempty(MOI.get(model, attr)) + return +end + +function test_variable_primal_ray() + inner = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + model = MOI.Bridges.Variable.Vectorize{Float64}(inner) + x, _ = MOI.add_constrained_variable(model, MOI.EqualTo(1.0)) + MOI.set(inner, MOI.PrimalStatus(), MOI.INFEASIBILITY_CERTIFICATE) + y = only(MOI.get(inner, MOI.ListOfVariableIndices())) + MOI.set(inner, MOI.VariablePrimal(), y, 1.23) + @test MOI.get(model, MOI.VariablePrimal(), x) == 1.23 + MOI.set(inner, MOI.PrimalStatus(), MOI.FEASIBLE_POINT) + @test MOI.get(model, MOI.VariablePrimal(), x) == 2.23 + return +end + end # module TestVariableVectorize.runtests() diff --git a/test/Bridges/Variable/bridge.jl b/test/Bridges/Variable/bridge.jl index 86961fdc3e..85389328f6 100644 --- a/test/Bridges/Variable/bridge.jl +++ b/test/Bridges/Variable/bridge.jl @@ -49,6 +49,15 @@ function test_AbstractBridge() return end +function test_bridging_cost() + model = MOI.Bridges.Variable.SingleBridgeOptimizer{DummyVariableBridge}( + MOI.Utilities.Model{Float64}(), + ) + @test MOI.Bridges.bridging_cost(model) == 1.0 + @test MOI.Bridges.bridging_cost(model, MOI.ZeroOne) == 1.0 + return +end + end # module TestVariableBridge.runtests() diff --git a/test/Bridges/bridge_optimizer.jl b/test/Bridges/bridge_optimizer.jl index 879a54a4c9..faf7af6b9c 100644 --- a/test/Bridges/bridge_optimizer.jl +++ b/test/Bridges/bridge_optimizer.jl @@ -1286,6 +1286,134 @@ function test_issue_2452_integer() return end +struct EmptyBridgeOptimizer <: MOI.Bridges.AbstractBridgeOptimizer end + +function test_supports_bridging_constrained_variable_fallback() + @test !MOI.Bridges.supports_bridging_constrained_variable( + EmptyBridgeOptimizer(), + MOI.ZeroOne, + ) + return +end + +function test_supports_bridging_constraint_fallback() + @test !MOI.Bridges.supports_bridging_constraint( + EmptyBridgeOptimizer(), + MOI.ScalarAffineFunction{Float64}, + MOI.EqualTo{Float64}, + ) + return +end + +function test_delete_variable_from_bridged_objective() + model = MOI.Utilities.Model{Float64}() + b = MOI.Bridges.Objective.VectorFunctionize{Float64}(model) + x = MOI.add_variables(b, 2) + y = MOI.add_variable(b) + f = MOI.VectorOfVariables(x) + MOI.set(b, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(b, MOI.ObjectiveFunction{typeof(f)}(), f) + G = MOI.VectorAffineFunction{Float64} + g = MOI.get(model, MOI.ObjectiveFunction{G}()) + MOI.delete(b, y) + @test g ≈ MOI.get(model, MOI.ObjectiveFunction{G}()) + MOI.delete(b, x[1]) + @test ≈( + MOI.Utilities.vectorize([MOI.Utilities.scalarize(g)[2]]), + MOI.get(model, MOI.ObjectiveFunction{G}()), + ) + MOI.delete(b, x[2]) + @test ≈( + MOI.Utilities.vectorize([zero(MOI.ScalarAffineFunction{Float64})]), + MOI.get(model, MOI.ObjectiveFunction{G}()), + ) + return +end + +function test_get_ObjectiveFunctionValue() + model = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + b = MOI.Bridges.full_bridge_optimizer(model, Float64) + x = MOI.add_variable(b) + MOI.set(b, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(b, MOI.ObjectiveFunction{MOI.VariableIndex}(), x) + y = only(MOI.get(model, MOI.ListOfVariableIndices())) + MOI.set(model, MOI.VariablePrimal(1), y, 1.23) + @test MOI.get(b, MOI.Bridges.ObjectiveFunctionValue{typeof(x)}(1)) == 1.23 + return +end + +function test_bridged_function() + model = MOI.Utilities.Model{Float64}() + b = MOI.Bridges.Variable.Free{Float64}(model) + x = MOI.add_variable(b) + y, _ = MOI.add_constrained_variable(b, MOI.GreaterThan(1.0)) + @test_throws( + ErrorException("Using bridged variable in `VariableIndex` function."), + MOI.Bridges.bridged_function(b, x), + ) + @test MOI.Bridges.bridged_function(b, y) == y + return +end + +function test_bridged_constraint_function_no_variable_bridges() + model = MOI.Utilities.Model{Float64}() + bridged = MOI.Bridges.full_bridge_optimizer(model, Float64) + x = MOI.add_variable(bridged) + set = MOI.ZeroOne() + @test MOI.Bridges.bridged_constraint_function(bridged, x, set) == (x, set) + return +end + +function test_bridged_constraint_function_with_variable_bridges() + model = MOI.Utilities.Model{Float64}() + b = MOI.Bridges.Variable.Free{Float64}(model) + x = MOI.add_variable(b) + set = MOI.ZeroOne() + f, set = MOI.Bridges.bridged_constraint_function(b, 1.0 * x, set) + @test set == MOI.ZeroOne() + y = MOI.get(model, MOI.ListOfVariableIndices()) + @test f ≈ 1.0 * y[1] - 1.0 * y[2] + return +end + +struct EmptyObjectiveBridge <: MOI.Bridges.Objective.AbstractBridge end + +function test_fallback_bridge_objective() + model = MOI.Utilities.Model{Float64}() + x = MOI.add_variable(model) + @test_throws( + MOI.UnsupportedAttribute(MOI.ObjectiveFunction{MOI.VariableIndex}()), + MOI.Bridges.Objective.bridge_objective(EmptyObjectiveBridge, model, x), + ) + return +end + +function test_fallback_set_objective_sense() + model = MOI.Utilities.Model{Float64}() + bridge = EmptyObjectiveBridge() + @test_throws( + MOI.SetAttributeNotAllowed{MOI.ObjectiveSense}, + MOI.set(model, MOI.ObjectiveSense(), bridge, MOI.MIN_SENSE), + ) + return +end + +function test_fallback_get_objective_function() + model = MOI.Utilities.Model{Float64}() + attr = MOI.ObjectiveFunction{MOI.VariableIndex}() + @test_throws( + MOI.GetAttributeNotAllowed{typeof(attr)}, + MOI.get(model, attr, EmptyObjectiveBridge()), + ) + return +end + +function test_fallback_delete_objective_bridge() + model = MOI.Utilities.Model{Float64}() + @test_throws ArgumentError MOI.delete(model, EmptyObjectiveBridge()) + return +end + end # module TestBridgeOptimizer.runtests() diff --git a/test/Bridges/debug.jl b/test/Bridges/debug.jl index 386116be21..700c828ca2 100644 --- a/test/Bridges/debug.jl +++ b/test/Bridges/debug.jl @@ -314,6 +314,23 @@ function test_print_active_bridges_variable_bridged_with_constraint() return end +function test_print_graph_stdout() + model = MOI.Utilities.Model{Float64}() + bridged = MOI.Bridges.full_bridge_optimizer(model, Float64) + dir = mktempdir() + filename = joinpath(dir, "tmp.out") + open(filename, "w") do io + redirect_stdout(io) do + MOI.Bridges.print_graph(bridged) + return + end + return + end + @test read(filename, String) == + "Bridge graph with 0 variable nodes, 0 constraint nodes and 0 objective nodes.\n" + return +end + end TestBridgesDebug.runtests()