From 520b0f597ffe2ecff1ca8de45a29d011f79be9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 6 May 2026 21:56:25 +0200 Subject: [PATCH 1/8] Implement bridge_cost --- src/attributes.jl | 103 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/attributes.jl b/src/attributes.jl index 3bb712f..eae161a 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -515,3 +515,106 @@ end function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractModelAttribute) return MOI.get(optimizer.dual_problem.dual_model, dual_attribute(attr)) end + +# Generic fallback for `VariableBridgingCost`/`ConstraintBridgingCost` on the +# `DualOptimizer`. The generic `MOI.get(::DualOptimizer, ::AbstractModelAttribute)` +# above goes through `dual_attribute` which has no method for these attributes; +# we override it here so that the standard `(supports ? 0 : Inf)` fallback is +# used when no more specific method below applies. +function MOI.get( + optimizer::DualOptimizer, + attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost}, +) + return MOI.get_fallback(optimizer, attr) +end + +# A `VariableBridgingCost{S}` query on the `DualOptimizer` corresponds to the +# cost of adding the dual constraint that is created for the variables in `S`, +# mirroring the structure of `MOI.supports_add_constrained_variables`. +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.VariableBridgingCost{MOI.Reals}, +) where {T} + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintBridgingCost{ + MOI.ScalarAffineFunction{T}, + MOI.EqualTo{T}, + }(), + ) +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.VariableBridgingCost{S}, +) where {T,S<:MOI.AbstractVectorSet} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintBridgingCost{MOI.VectorAffineFunction{T},D}(), + ) +end + +# Adding a scalar constrained variable is equivalent (per the MOI fallback for +# `supports_add_constrained_variable`) to adding a free variable and then a +# `VariableIndex`-in-`S` constraint, so the bridging cost is the sum. +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.VariableBridgingCost{S}, +) where {T,S<:MOI.AbstractScalarSet} + free_cost = + MOI.get(optimizer, MOI.VariableBridgingCost{MOI.Reals}())::Float64 + if !isfinite(free_cost) + return free_cost + end + constraint_cost = MOI.get( + optimizer, + MOI.ConstraintBridgingCost{MOI.VariableIndex,S}(), + )::Float64 + if !isfinite(constraint_cost) + return constraint_cost + end + return free_cost + constraint_cost +end + +# A `ConstraintBridgingCost{F,S}` query on the `DualOptimizer` corresponds to +# the cost of adding the dual constrained variable in `_dual_set_type(S)`, +# mirroring the structure of `MOI.supports_constraint`. +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.ConstraintBridgingCost{F,S}, +) where { + T, + F<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}, + S<:MOI.AbstractScalarSet, +} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.VariableBridgingCost{D}(), + ) +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.ConstraintBridgingCost{F,S}, +) where { + T, + F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}, + S<:MOI.AbstractVectorSet, +} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.VariableBridgingCost{D}(), + ) +end From 68e3b7d19929e32b0e3aa4a843ccf091a7539b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 17:29:08 +0200 Subject: [PATCH 2/8] Fix format --- src/attributes.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/attributes.jl b/src/attributes.jl index eae161a..dccd732 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -537,10 +537,7 @@ function MOI.get( ) where {T} return MOI.get( optimizer.dual_problem.dual_model, - MOI.ConstraintBridgingCost{ - MOI.ScalarAffineFunction{T}, - MOI.EqualTo{T}, - }(), + MOI.ConstraintBridgingCost{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(), ) end From 8fe49ccc827ca74f3839287ec4b784227cbbe4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 17:54:12 +0200 Subject: [PATCH 3/8] Add supports file --- src/Dualization.jl | 1 + src/MOI_wrapper.jl | 78 -------------------------- src/supports.jl | 135 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 78 deletions(-) create mode 100644 src/supports.jl diff --git a/src/Dualization.jl b/src/Dualization.jl index 5f3e0e0..bca8b5f 100644 --- a/src/Dualization.jl +++ b/src/Dualization.jl @@ -22,6 +22,7 @@ include("dualize.jl") include("MOI_wrapper.jl") include("vectorize_emulator.jl") include("attributes.jl") +include("supports.jl") export dualize export dual_optimizer diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index b1e0bdb..977e53b 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -101,56 +101,6 @@ end DualOptimizer() = error("DualOptimizer must have a solver attached") -MOI.supports(::DualOptimizer, ::MOI.ObjectiveSense) = true - -function MOI.supports( - optimizer::DualOptimizer{T}, - ::MOI.ObjectiveFunction{F}, -) where {T,F} - # If the objective function is `MOI.VariableIndex` or - # `MOI.ScalarAffineFunction`, then a `MOI.ScalarAffineFunction` is set as - # the objective function for the dual problem. - # If it is `MOI.ScalarQuadraticFunction` , a `MOI.ScalarQuadraticFunction` - # is set as objective function for the dual problem. - attr = if F <: MOI.ScalarQuadraticFunction - MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}() - else - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}() - end - return supported_objective(F) && - MOI.supports(optimizer.dual_problem.dual_model, attr) -end - -function MOI.supports_constraint( - optimizer::DualOptimizer{T}, - ::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}}, - S::Type{<:MOI.AbstractScalarSet}, -) where {T} - D = _dual_set_type(S) - if D === nothing - return false - end - model = optimizer.dual_problem.dual_model - if D <: MOI.AbstractVectorSet # The dual of `EqualTo` is `Reals` - return MOI.supports_add_constrained_variables(model, D) - else - return MOI.supports_add_constrained_variable(model, D) - end -end - -function MOI.supports_constraint( - optimizer::DualOptimizer{T}, - ::Type{<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}}, - S::Type{<:MOI.AbstractVectorSet}, -) where {T} - D = _dual_set_type(S) - if D === nothing - return false - end - model = optimizer.dual_problem.dual_model - return MOI.supports_add_constrained_variables(model, D) -end - function _change_constant( model, ci::MOI.ConstraintIndex{<:MOI.ScalarAffineFunction,S}, @@ -205,34 +155,6 @@ function MOI.modify( return end -function MOI.supports_add_constrained_variables( - optimizer::DualOptimizer{T}, - ::Type{MOI.Reals}, -) where {T} - return MOI.supports_constraint( - optimizer.dual_problem.dual_model, - MOI.ScalarAffineFunction{T}, - MOI.EqualTo{T}, - ) - # If `_dual_set_type(MOI.Reals)` was `MOI.Zeros`, - # we would not need this method as special case of the one below -end - -function MOI.supports_add_constrained_variables( - optimizer::DualOptimizer{T}, - S::Type{<:MOI.AbstractVectorSet}, -) where {T} - D = _dual_set_type(S) - if D === nothing - return false - end - return MOI.supports_constraint( - optimizer.dual_problem.dual_model, - MOI.VectorAffineFunction{T}, - D, - ) -end - function MOI.copy_to(dest::DualOptimizer, src::MOI.ModelLike) MOI.empty!(dest) dualize( diff --git a/src/supports.jl b/src/supports.jl new file mode 100644 index 0000000..84b6384 --- /dev/null +++ b/src/supports.jl @@ -0,0 +1,135 @@ +MOI.supports(::DualOptimizer, ::MOI.ObjectiveSense) = true + +function MOI.supports( + optimizer::DualOptimizer{T}, + ::MOI.ObjectiveFunction{F}, +) where {T,F} + # If the objective function is `MOI.VariableIndex` or + # `MOI.ScalarAffineFunction`, then a `MOI.ScalarAffineFunction` is set as + # the objective function for the dual problem. + # If it is `MOI.ScalarQuadraticFunction` , a `MOI.ScalarQuadraticFunction` + # is set as objective function for the dual problem. + attr = if F <: MOI.ScalarQuadraticFunction + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{T}}() + else + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}() + end + return supported_objective(F) && + MOI.supports(optimizer.dual_problem.dual_model, attr) +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.ConstraintBridgingCost{ + <:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}, + S<:MOI.AbstractScalarSet, + }, +) where {T} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.VariableBridgingCost{D}(), + ) +end + +function MOI.supports_constraint( + optimizer::DualOptimizer{T}, + ::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}}, + S::Type{<:MOI.AbstractScalarSet}, +) where {T} + D = _dual_set_type(S) + if D === nothing + return false + end + model = optimizer.dual_problem.dual_model + if D <: MOI.AbstractVectorSet # The dual of `EqualTo` is `Reals` + return MOI.supports_add_constrained_variables(model, D) + else + return MOI.supports_add_constrained_variable(model, D) + end +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.ConstraintBridgingCost{ + <:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}, + S<:MOI.AbstractVectorSet, + }, +) where {T} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.VariableBridgingCost{D}(), + ) +end + +function MOI.supports_constraint( + optimizer::DualOptimizer{T}, + ::Type{<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}}, + S::Type{<:MOI.AbstractVectorSet}, +) where {T} + D = _dual_set_type(S) + if D === nothing + return false + end + model = optimizer.dual_problem.dual_model + return MOI.supports_add_constrained_variables(model, D) +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.VariableBridgingCost{MOI.Reals}, +) where {T} + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintBridgingCost{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(), + ) +end + +function MOI.supports_add_constrained_variables( + optimizer::DualOptimizer{T}, + ::Type{MOI.Reals}, +) where {T} + return MOI.supports_constraint( + optimizer.dual_problem.dual_model, + MOI.ScalarAffineFunction{T}, + MOI.EqualTo{T}, + ) + # If `_dual_set_type(MOI.Reals)` was `MOI.Zeros`, + # we would not need this method as special case of the one below +end + +function MOI.get( + optimizer::DualOptimizer{T}, + ::MOI.VariableBridgingCost{MOI.AbstractVectorSet}, +) where {T} + D = _dual_set_type(S) + if D === nothing + return Inf + end + return MOI.get( + optimizer.dual_problem.dual_model, + MOI.ConstraintBridgingCost{MOI.VectorAffineFunction{T},D}(), + ) +end + +function MOI.supports_add_constrained_variables( + optimizer::DualOptimizer{T}, + S::Type{<:MOI.AbstractVectorSet}, +) where {T} + D = _dual_set_type(S) + if D === nothing + return false + end + return MOI.supports_constraint( + optimizer.dual_problem.dual_model, + MOI.VectorAffineFunction{T}, + D, + ) +end From 32b8573347a0d8f1d652faced9bd9701412c467d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 17:57:25 +0200 Subject: [PATCH 4/8] fixes --- src/attributes.jl | 100 ---------------------------------------------- src/supports.jl | 11 +++++ 2 files changed, 11 insertions(+), 100 deletions(-) diff --git a/src/attributes.jl b/src/attributes.jl index dccd732..3bb712f 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -515,103 +515,3 @@ end function MOI.get(optimizer::DualOptimizer, attr::MOI.AbstractModelAttribute) return MOI.get(optimizer.dual_problem.dual_model, dual_attribute(attr)) end - -# Generic fallback for `VariableBridgingCost`/`ConstraintBridgingCost` on the -# `DualOptimizer`. The generic `MOI.get(::DualOptimizer, ::AbstractModelAttribute)` -# above goes through `dual_attribute` which has no method for these attributes; -# we override it here so that the standard `(supports ? 0 : Inf)` fallback is -# used when no more specific method below applies. -function MOI.get( - optimizer::DualOptimizer, - attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost}, -) - return MOI.get_fallback(optimizer, attr) -end - -# A `VariableBridgingCost{S}` query on the `DualOptimizer` corresponds to the -# cost of adding the dual constraint that is created for the variables in `S`, -# mirroring the structure of `MOI.supports_add_constrained_variables`. -function MOI.get( - optimizer::DualOptimizer{T}, - ::MOI.VariableBridgingCost{MOI.Reals}, -) where {T} - return MOI.get( - optimizer.dual_problem.dual_model, - MOI.ConstraintBridgingCost{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}(), - ) -end - -function MOI.get( - optimizer::DualOptimizer{T}, - ::MOI.VariableBridgingCost{S}, -) where {T,S<:MOI.AbstractVectorSet} - D = _dual_set_type(S) - if D === nothing - return Inf - end - return MOI.get( - optimizer.dual_problem.dual_model, - MOI.ConstraintBridgingCost{MOI.VectorAffineFunction{T},D}(), - ) -end - -# Adding a scalar constrained variable is equivalent (per the MOI fallback for -# `supports_add_constrained_variable`) to adding a free variable and then a -# `VariableIndex`-in-`S` constraint, so the bridging cost is the sum. -function MOI.get( - optimizer::DualOptimizer{T}, - ::MOI.VariableBridgingCost{S}, -) where {T,S<:MOI.AbstractScalarSet} - free_cost = - MOI.get(optimizer, MOI.VariableBridgingCost{MOI.Reals}())::Float64 - if !isfinite(free_cost) - return free_cost - end - constraint_cost = MOI.get( - optimizer, - MOI.ConstraintBridgingCost{MOI.VariableIndex,S}(), - )::Float64 - if !isfinite(constraint_cost) - return constraint_cost - end - return free_cost + constraint_cost -end - -# A `ConstraintBridgingCost{F,S}` query on the `DualOptimizer` corresponds to -# the cost of adding the dual constrained variable in `_dual_set_type(S)`, -# mirroring the structure of `MOI.supports_constraint`. -function MOI.get( - optimizer::DualOptimizer{T}, - ::MOI.ConstraintBridgingCost{F,S}, -) where { - T, - F<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}, - S<:MOI.AbstractScalarSet, -} - D = _dual_set_type(S) - if D === nothing - return Inf - end - return MOI.get( - optimizer.dual_problem.dual_model, - MOI.VariableBridgingCost{D}(), - ) -end - -function MOI.get( - optimizer::DualOptimizer{T}, - ::MOI.ConstraintBridgingCost{F,S}, -) where { - T, - F<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}, - S<:MOI.AbstractVectorSet, -} - D = _dual_set_type(S) - if D === nothing - return Inf - end - return MOI.get( - optimizer.dual_problem.dual_model, - MOI.VariableBridgingCost{D}(), - ) -end diff --git a/src/supports.jl b/src/supports.jl index 84b6384..b6df80d 100644 --- a/src/supports.jl +++ b/src/supports.jl @@ -18,6 +18,17 @@ function MOI.supports( MOI.supports(optimizer.dual_problem.dual_model, attr) end +# Generic fallback for `VariableBridgingCost`/`ConstraintBridgingCost` +# calls `dual_attribute` which won't work for sets that don't implement `dual_set_type` +# so we define custom `get` methods instead. + +function MOI.get( + optimizer::DualOptimizer, + attr::Union{MOI.VariableBridgingCost,MOI.ConstraintBridgingCost}, +) + return MOI.get_fallback(optimizer, attr) +end + function MOI.get( optimizer::DualOptimizer{T}, ::MOI.ConstraintBridgingCost{ From 441a58b0af7f877ea212be896887d4eadcdcde88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 18:19:31 +0200 Subject: [PATCH 5/8] Add tests --- src/supports.jl | 12 ++++++------ test/Tests/test_MOI_wrapper.jl | 11 +++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/supports.jl b/src/supports.jl index b6df80d..58196ae 100644 --- a/src/supports.jl +++ b/src/supports.jl @@ -33,9 +33,9 @@ function MOI.get( optimizer::DualOptimizer{T}, ::MOI.ConstraintBridgingCost{ <:Union{MOI.VariableIndex,MOI.ScalarAffineFunction{T}}, - S<:MOI.AbstractScalarSet, + S, }, -) where {T} +) where {T,S<:MOI.AbstractScalarSet} D = _dual_set_type(S) if D === nothing return Inf @@ -67,9 +67,9 @@ function MOI.get( optimizer::DualOptimizer{T}, ::MOI.ConstraintBridgingCost{ <:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction{T}}, - S<:MOI.AbstractVectorSet, + S, }, -) where {T} +) where {T,S<:MOI.AbstractVectorSet} D = _dual_set_type(S) if D === nothing return Inf @@ -118,8 +118,8 @@ end function MOI.get( optimizer::DualOptimizer{T}, - ::MOI.VariableBridgingCost{MOI.AbstractVectorSet}, -) where {T} + ::MOI.VariableBridgingCost{S}, +) where {T,S<:MOI.AbstractVectorSet} D = _dual_set_type(S) if D === nothing return Inf diff --git a/test/Tests/test_MOI_wrapper.jl b/test/Tests/test_MOI_wrapper.jl index cdcad6d..6c973ba 100644 --- a/test/Tests/test_MOI_wrapper.jl +++ b/test/Tests/test_MOI_wrapper.jl @@ -96,6 +96,10 @@ @testset "support" begin for opt in dual_linear_optimizer @test !MOI.supports_constraint(opt, MOI.VariableIndex, MOI.Integer) + @test MOI.get( + opt, + MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.Integer}(), + ) == Inf @test MOI.supports(opt, MOI.ObjectiveSense()) end for opt in dual_conic_optimizer @@ -104,6 +108,13 @@ MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, ) + @test MOI.get( + opt, + MOI.ConstraintBridgingCost{ + MOI.VectorOfVariables, + MOI.PositiveSemidefiniteConeTriangle, + }(), + ) < Inf end end From 54cbb32c5d1985948ae50f5a1f5604cf1cba9337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 18:38:34 +0200 Subject: [PATCH 6/8] Add tests --- test/Solvers/hypatia_test.jl | 13 +++++++++++++ test/Tests/test_MOI_wrapper.jl | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/test/Solvers/hypatia_test.jl b/test/Solvers/hypatia_test.jl index 77e343c..6d75ec2 100644 --- a/test/Solvers/hypatia_test.jl +++ b/test/Solvers/hypatia_test.jl @@ -5,6 +5,19 @@ import Hypatia +@testset "VariableBridgingCost" begin + opt = MOI.instantiate( + dual_optimizer(Hypatia.Optimizer); + with_bridge_type = Float64, + ) + @test MOI.supports_add_constrained_variables(opt, MOI.Reals) + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Reals}()) == 0 + @test MOI.supports_add_constrained_variables(opt, MOI.Nonnegatives) + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Nonnegatives}()) == 0 + @test MOI.supports_add_constrained_variables(opt, MOI.GeometricMeanCone) + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.GeometricMeanCone}()) == 2 +end + @testset "Solve problems with different coefficient_type" begin function mineig(::Type{T}) where {T} model = GenericModel{T}( diff --git a/test/Tests/test_MOI_wrapper.jl b/test/Tests/test_MOI_wrapper.jl index 6c973ba..4e8b41d 100644 --- a/test/Tests/test_MOI_wrapper.jl +++ b/test/Tests/test_MOI_wrapper.jl @@ -114,7 +114,7 @@ MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, }(), - ) < Inf + ) == 0 end end From 7c6c0a8249d158dc3c5a7bdb9e04018288c6787d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 19:57:53 +0200 Subject: [PATCH 7/8] Add tests --- test/Solvers/hypatia_test.jl | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/test/Solvers/hypatia_test.jl b/test/Solvers/hypatia_test.jl index 6d75ec2..1595607 100644 --- a/test/Solvers/hypatia_test.jl +++ b/test/Solvers/hypatia_test.jl @@ -5,17 +5,35 @@ import Hypatia -@testset "VariableBridgingCost" begin - opt = MOI.instantiate( - dual_optimizer(Hypatia.Optimizer); - with_bridge_type = Float64, - ) +@testset "VariableBridgingCost / ConstraintBridgingCost" begin + opt = MOI.instantiate(dual_optimizer(Hypatia.Optimizer)) + # Specialized `VariableBridgingCost{MOI.Reals}` @test MOI.supports_add_constrained_variables(opt, MOI.Reals) - @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Reals}()) == 0 + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Reals}()) == 1 + # Specialized `VariableBridgingCost{S<:AbstractVectorSet}`, supported branch @test MOI.supports_add_constrained_variables(opt, MOI.Nonnegatives) @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Nonnegatives}()) == 0 - @test MOI.supports_add_constrained_variables(opt, MOI.GeometricMeanCone) - @test MOI.get(opt, MOI.VariableBridgingCost{MOI.GeometricMeanCone}()) == 2 + # Same dispatch, supported with nonzero cost + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.Zeros}()) == 1 + # Same dispatch, `_dual_set_type` returns `nothing` + @test !MOI.supports_add_constrained_variables(opt, MOI.SOS1{Float64}) + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.SOS1{Float64}}()) == Inf + # Specialized scalar `ConstraintBridgingCost`, supported branch + @test MOI.get( + opt, + MOI.ConstraintBridgingCost{ + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + }(), + ) == 1 + # Specialized vector `ConstraintBridgingCost`, `_dual_set_type` returns `nothing` + @test !MOI.supports_constraint(opt, MOI.VectorOfVariables, MOI.SOS1{Float64}) + @test MOI.get( + opt, + MOI.ConstraintBridgingCost{MOI.VectorOfVariables,MOI.SOS1{Float64}}(), + ) == Inf + # Generic fallback (no specialized method matches) + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}()) == 0 end @testset "Solve problems with different coefficient_type" begin From 431610a1aea1f5457630b4937ae510b33220a4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 16 May 2026 20:12:52 +0200 Subject: [PATCH 8/8] Fix format --- test/Solvers/hypatia_test.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/Solvers/hypatia_test.jl b/test/Solvers/hypatia_test.jl index 1595607..2a3e9bb 100644 --- a/test/Solvers/hypatia_test.jl +++ b/test/Solvers/hypatia_test.jl @@ -27,13 +27,18 @@ import Hypatia }(), ) == 1 # Specialized vector `ConstraintBridgingCost`, `_dual_set_type` returns `nothing` - @test !MOI.supports_constraint(opt, MOI.VectorOfVariables, MOI.SOS1{Float64}) + @test !MOI.supports_constraint( + opt, + MOI.VectorOfVariables, + MOI.SOS1{Float64}, + ) @test MOI.get( opt, MOI.ConstraintBridgingCost{MOI.VectorOfVariables,MOI.SOS1{Float64}}(), ) == Inf # Generic fallback (no specialized method matches) - @test MOI.get(opt, MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}()) == 0 + @test MOI.get(opt, MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}()) == + 0 end @testset "Solve problems with different coefficient_type" begin