diff --git a/ext/MathOptInterfaceLDLFactorizationsExt.jl b/ext/MathOptInterfaceLDLFactorizationsExt.jl index 9211069776..c3b5c7a31c 100644 --- a/ext/MathOptInterfaceLDLFactorizationsExt.jl +++ b/ext/MathOptInterfaceLDLFactorizationsExt.jl @@ -21,7 +21,11 @@ function MOI.Bridges.Constraint.compute_sparse_sqrt_fallback( ) where {F<:MOI.ScalarQuadraticFunction,S<:MOI.AbstractSet} n = LinearAlgebra.checksquare(Q) factor = LDLFactorizations.ldl(Q) - if minimum(factor.D) < 0 + # Ideally we should use `LDLFactorizations.factorized(factor)` here, but it + # has some false negatives. Instead we check that the factorization appeared + # to work. This is a heuristic. There might be other cases where check is + # insufficient. + if minimum(factor.D) < 0 || any(issubnormal, factor.D) msg = """ Unable to transform a quadratic constraint into a SecondOrderCone constraint because the quadratic constraint is not convex. diff --git a/test/Bridges/Constraint/test_QuadtoSOCBridge.jl b/test/Bridges/Constraint/test_QuadtoSOCBridge.jl index 00b64a3ff1..d34cfe5093 100644 --- a/test/Bridges/Constraint/test_QuadtoSOCBridge.jl +++ b/test/Bridges/Constraint/test_QuadtoSOCBridge.jl @@ -363,11 +363,11 @@ function test_compute_sparse_sqrt_edge_cases() [1.0 0.0; 0.0 2.0], # Cholesky works, with pivoting [1.0 0.0 1.0; 0.0 1.0 1.0; 1.0 1.0 3.0], - # Cholesky fails due to 0 eigen value + # Cholesky fails due to 0 eigen value. LDL works [1.0 1.0; 1.0 1.0], # Cholesky succeeds, even though 0 eigen value [2.0 2.0; 2.0 2.0], - # Cholesky fails because of 0 column/row + # Cholesky fails because of 0 column/row. LDL works [2.0 0.0; 0.0 0.0], ] B = SparseArrays.sparse(A) @@ -378,12 +378,18 @@ function test_compute_sparse_sqrt_edge_cases() end @test isapprox(A, U' * U; atol = 1e-10) end - A = [-1.0 0.0; 0.0 1.0] - B = SparseArrays.sparse(A) - @test_throws( - MOI.UnsupportedConstraint{typeof(f),typeof(s)}, - MOI.Bridges.Constraint.compute_sparse_sqrt(B, f, s), - ) + # Test failures + for A in [ + [-1.0 0.0; 0.0 1.0], + # Found from test_quadratic_nonconvex_constraint_basic + [0.0 -1.0; -1.0 0.0], + ] + B = SparseArrays.sparse(A) + @test_throws( + MOI.UnsupportedConstraint{typeof(f),typeof(s)}, + MOI.Bridges.Constraint.compute_sparse_sqrt(B, f, s), + ) + end return end