diff --git a/Project.toml b/Project.toml index 3693b96bc5..6cf49918e7 100644 --- a/Project.toml +++ b/Project.toml @@ -18,12 +18,6 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -[weakdeps] -LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" - -[extensions] -MathOptInterfaceLDLFactorizationsExt = "LDLFactorizations" - [compat] BenchmarkTools = "1" CodecBzip2 = "0.6, 0.7, 0.8" @@ -31,7 +25,6 @@ CodecZlib = "0.6, 0.7" ForwardDiff = "1" JSON = "0.21, 1" JSONSchema = "1" -LDLFactorizations = "0.10" LinearAlgebra = "1" MutableArithmetics = "1" NaNMath = "0.3, 1" @@ -45,9 +38,8 @@ Test = "1" julia = "1.10" [extras] -LDLFactorizations = "40e66cde-538c-5869-a4ad-c39174c6795b" JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" [targets] -test = ["LDLFactorizations", "JSONSchema", "ParallelTestRunner"] +test = ["JSONSchema", "ParallelTestRunner"] diff --git a/ext/MathOptInterfaceLDLFactorizationsExt.jl b/ext/MathOptInterfaceLDLFactorizationsExt.jl deleted file mode 100644 index c3b5c7a31c..0000000000 --- a/ext/MathOptInterfaceLDLFactorizationsExt.jl +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2017: Miles Lubin and contributors -# Copyright (c) 2017: Google Inc. -# -# Use of this source code is governed by an MIT-style license that can be found -# in the LICENSE.md file or at https://opensource.org/licenses/MIT. - -module MathOptInterfaceLDLFactorizationsExt - -import LDLFactorizations -import LinearAlgebra -import MathOptInterface as MOI -import SparseArrays - -# The type signature of this function is not important, so long as it is more -# specific than the (untyped) generic fallback with the error pointing to -# LDLFactorizations.jl -function MOI.Bridges.Constraint.compute_sparse_sqrt_fallback( - Q::AbstractMatrix, - ::F, - ::S, -) where {F<:MOI.ScalarQuadraticFunction,S<:MOI.AbstractSet} - n = LinearAlgebra.checksquare(Q) - factor = LDLFactorizations.ldl(Q) - # 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. - """ - throw(MOI.UnsupportedConstraint{F,S}(msg)) - end - # We have Q = P' * L * D * L' * P. We want to find Q = U' * U, so - # U = sqrt(D) * L' * P. First, compute L'. Note I and J are reversed: - J, I, V = SparseArrays.findnz(factor.L) - # Except L doesn't include the identity along the diagonal. Add it back. - append!(J, 1:n) - append!(I, 1:n) - append!(V, ones(n)) - # Now scale by sqrt(D) - for (k, i) in enumerate(I) - V[k] *= sqrt(factor.D[i, i]) - end - # Finally, permute the columns of L'. The rows stay in the same order. - return I, factor.P[J], V -end - -end # module diff --git a/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl b/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl index 437547c006..a4f4f0b6d6 100644 --- a/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl +++ b/src/Bridges/Constraint/bridges/QuadtoSOCBridge.jl @@ -60,25 +60,6 @@ end const QuadtoSOC{T,OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T},OT} -function compute_sparse_sqrt_fallback(Q, ::F, ::S) where {F,S} - msg = """ - Unable to transform a quadratic constraint into a SecondOrderCone - constraint because the quadratic constraint is not strongly convex and - our Cholesky decomposition failed. - - If the constraint is convex but not strongly convex, you can work-around - this issue by manually installing and loading `LDLFactorizations.jl`: - ```julia - import Pkg; Pkg.add("LDLFactorizations") - using LDLFactorizations - ``` - - LDLFactorizations.jl is not included by default because it is licensed - under the LGPL. - """ - return throw(MOI.AddConstraintNotAllowed{F,S}(msg)) -end - function compute_sparse_sqrt(Q, func, set) # There's a big try-catch here because Cholesky can fail even if # `check = false`. As one example, it currently (v1.12) fails with @@ -88,10 +69,7 @@ function compute_sparse_sqrt(Q, func, set) # The try-catch isn't a performance concern because the alternative is not # being able to reformulate the problem. try - factor = LinearAlgebra.cholesky(Q; check = false) - if !LinearAlgebra.issuccess(factor) - return compute_sparse_sqrt_fallback(Q, func, set) - end + factor = LinearAlgebra.cholesky(Q) L, p = SparseArrays.sparse(factor.L), factor.p # We have Q = P' * L * L' * P. We want to find Q = U' * U, so U = L' * P # First, compute L'. Note I and J are reversed @@ -99,11 +77,12 @@ function compute_sparse_sqrt(Q, func, set) # Then, we want to permute the columns of L'. The rows stay in the same # order. return I, p[J], V - catch err - if err isa MOI.AddConstraintNotAllowed - rethrow(err) - end - msg = "There was an error computing a matrix square root" + catch + msg = """ + Unable to transform a quadratic constraint into a SecondOrderCone + constraint because the quadratic constraint is not strongly convex and + our Cholesky decomposition failed. + """ throw(MOI.UnsupportedConstraint{typeof(func),typeof(set)}(msg)) end end diff --git a/test/Bridges/Constraint/test_QuadtoSOCBridge.jl b/test/Bridges/Constraint/test_QuadtoSOCBridge.jl index 9ca703a8e4..a32cde5480 100644 --- a/test/Bridges/Constraint/test_QuadtoSOCBridge.jl +++ b/test/Bridges/Constraint/test_QuadtoSOCBridge.jl @@ -10,7 +10,6 @@ import LinearAlgebra import SparseArrays using Test -import LDLFactorizations import MathOptInterface as MOI function runtests() @@ -345,13 +344,16 @@ function test_semidefinite_cholesky_fail() model = MOI.Bridges.Constraint.QuadtoSOC{Float64}(inner) x = MOI.add_variables(model, 2) f = 0.5 * x[1] * x[1] + 1.0 * x[1] * x[2] + 0.5 * x[2] * x[2] - c = MOI.add_constraint(model, f, MOI.LessThan(1.0)) - F, S = MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone - ci = only(MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())) - g = MOI.get(inner, MOI.ConstraintFunction(), ci) - y = MOI.get(inner, MOI.ListOfVariableIndices()) - sum_y = 1.0 * y[1] + 1.0 * y[2] - @test isapprox(g, MOI.Utilities.vectorize([1.0, 1.0, sum_y, 0.0])) + @test_throws( + MOI.UnsupportedConstraint, + MOI.add_constraint(model, f, MOI.LessThan(1.0)), + ) + # F, S = MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone + # ci = only(MOI.get(inner, MOI.ListOfConstraintIndices{F,S}())) + # g = MOI.get(inner, MOI.ConstraintFunction(), ci) + # y = MOI.get(inner, MOI.ListOfVariableIndices()) + # sum_y = 1.0 * y[1] + 1.0 * y[2] + # @test isapprox(g, MOI.Utilities.vectorize([1.0, 1.0, sum_y, 0.0])) return end @@ -361,12 +363,8 @@ 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. 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. LDL works - [2.0 0.0; 0.0 0.0], ] B = SparseArrays.sparse(A) f = zero(MOI.ScalarQuadraticFunction{eltype(A)}) @@ -381,6 +379,8 @@ function test_compute_sparse_sqrt_edge_cases() # Test failures for A in Any[ [-1.0 0.0; 0.0 1.0], + [1.0 1.0; 1.0 1.0], + [2.0 0.0; 0.0 0.0], # Found from test_quadratic_nonconvex_constraint_basic [0.0 -1.0; -1.0 0.0], # Different element type. We could potentially make this work in future, @@ -400,20 +400,6 @@ function test_compute_sparse_sqrt_edge_cases() return end -function test_compute_sparse_sqrt_fallback() - # Test the default fallback that is hit when LDLFactorizations isn't loaded. - # We could put the test somewhere else so it runs before this file is - # loaded, but that's pretty flakey for a long-term solution. Instead, we're - # going to abuse the lack of a strong type signature to hit it: - f = zero(MOI.ScalarAffineFunction{Float64}) - A = SparseArrays.sparse([-1.0 0.0; 0.0 1.0]) - @test_throws( - MOI.AddConstraintNotAllowed{typeof(f),MOI.GreaterThan{Float64}}, - MOI.Bridges.Constraint.compute_sparse_sqrt(A, f, MOI.GreaterThan(0.0)), - ) - return -end - end # module TestConstraintQuadToSOC.runtests()