From fc22593e9a9aa2d021c783c8ebb3e78056042cba Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Fri, 20 Feb 2026 13:05:05 -0500 Subject: [PATCH 01/11] initial --- lib/MadNLPCliqueTrees/Project.toml | 23 +++ .../src/MadNLPCliqueTrees.jl | 149 ++++++++++++++++++ lib/MadNLPCliqueTrees/test/runtests.jl | 31 ++++ 3 files changed, 203 insertions(+) create mode 100644 lib/MadNLPCliqueTrees/Project.toml create mode 100644 lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl create mode 100644 lib/MadNLPCliqueTrees/test/runtests.jl diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml new file mode 100644 index 000000000..3d4ce77ca --- /dev/null +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -0,0 +1,23 @@ +name = "MadNLPCliqueTrees" +uuid = "b01c5a2c-fd8a-4b14-b48d-9b33b3498a9c" +version = "0.1.0" + +[deps] +CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" +MadNLPTests = "b52a2a03-04ab-4a5f-9698-6a2deff93217" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[compat] +CliqueTrees = "1.14" +MadNLP = "0.8" +MadNLPTests = "0.5" +julia = "1.10" + +[extras] +MadNLPTests = "b52a2a03-04ab-4a5f-9698-6a2deff93217" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test", "MadNLPTests"] diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl new file mode 100644 index 000000000..1e37933e4 --- /dev/null +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -0,0 +1,149 @@ +module MadNLPCliqueTrees + +using CliqueTrees +using CliqueTrees.Multifrontal +using CliqueTrees.Multifrontal: FVector, flatindices, setflatindex! +using LinearAlgebra +using SparseArrays + +import MadNLP: + MadNLP, + @kwdef, + MadNLPLogger, + AbstractOptions, + AbstractLinearSolver, + LinearFactorization, + LDL, + CHOLESKY, + set_options!, + introduce, + factorize!, + solve_linear_system!, + improve!, + is_inertia, + inertia, + input_type, + is_supported, + default_options + +@kwdef mutable struct CliqueTreesOptions <: AbstractOptions + cliquetrees_algorithm::LinearFactorization = LDL +end + +const FChordalLDLt{T} = ChordalLDLt{:L, T, Int, FVector{T}, FVector{Int}} +const FChordalCholesky{T} = ChordalCholesky{:L, T, Int, FVector{T}, FVector{Int}} + +mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolver{T} + tril::SparseMatrixCSC{T, Int32} + F::F + P::Vector{Int} + opt::CliqueTreesOptions + logger::MadNLPLogger +end + +function _build_factorization(tril::SparseMatrixCSC{T, Int32}, ::Val{LDL}) where T + csc = SparseMatrixCSC{T, Int}(tril) + sym = Symmetric(csc, :L) + F = ChordalLDLt{:L}(sym)::FChordalLDLt{T} + P = flatindices(F, sym) + return F, P +end + +function _build_factorization(tril::SparseMatrixCSC{T, Int32}, ::Val{CHOLESKY}) where T + csc = SparseMatrixCSC{T, Int}(tril) + sym = Symmetric(csc, :L) + F = ChordalCholesky{:L}(sym)::FChordalCholesky{T} + P = flatindices(F, sym) + return F, P +end + +function CliqueTreesSolver( + tril::SparseMatrixCSC{T, Int32}; + opt = CliqueTreesOptions(), + logger = MadNLPLogger(), +) where T + F, P = _build_factorization(tril, Val(opt.cliquetrees_algorithm)) + return CliqueTreesSolver{T, typeof(F)}(tril, F, P, opt, logger) +end + +function factorize!(M::CliqueTreesSolver{T, <:ChordalLDLt}) where T + fill!(M.F, zero(T)) + fill!(M.F.d, zero(T)) + nzval = M.tril.nzval + @inbounds for i in eachindex(M.P) + setflatindex!(M.F, nzval[i], M.P[i]) + end + ldlt!(M.F; check=false) + return M +end + +function solve_linear_system!(M::CliqueTreesSolver{T, <:ChordalLDLt}, rhs::Vector{T}) where T + if issuccess(M.F) + ldiv!(M.F, rhs) + end + return rhs +end + +function inertia(M::CliqueTreesSolver{T, <:ChordalLDLt}) where T + d = M.F.d + pos = 0; zer = 0; neg = 0 + @inbounds for di in d + if di > 0 + pos += 1 + elseif di == 0 + zer += 1 + else + neg += 1 + end + end + return pos, zer, neg +end + +introduce(::CliqueTreesSolver{T, <:ChordalLDLt}) where T = + "CliqueTrees/LDLᵀ v$(pkgversion(CliqueTrees))" + +function factorize!(M::CliqueTreesSolver{T, <:ChordalCholesky}) where T + fill!(M.F, zero(T)) + nzval = M.tril.nzval + @inbounds for i in eachindex(M.P) + setflatindex!(M.F, nzval[i], M.P[i]) + end + cholesky!(M.F; check=false) + return M +end + +function solve_linear_system!(M::CliqueTreesSolver{T, <:ChordalCholesky}, rhs::Vector{T}) where T + if issuccess(M.F) + ldiv!(M.F, rhs) + end + return rhs +end + +function inertia(M::CliqueTreesSolver{T, <:ChordalCholesky}) where T + n = size(M.tril, 1) + if issuccess(M.F) + return (n, 0, 0) + else + return (0, n, 0) + end +end + +introduce(::CliqueTreesSolver{T, <:ChordalCholesky}) where T = + "CliqueTrees/Cholesky v$(pkgversion(CliqueTrees))" + +is_inertia(::CliqueTreesSolver) = true +improve!(::CliqueTreesSolver) = false +input_type(::Type{<:CliqueTreesSolver}) = :csc +default_options(::Type{<:CliqueTreesSolver}) = CliqueTreesOptions() +is_supported(::Type{<:CliqueTreesSolver}, ::Type{T}) where T <: AbstractFloat = true + +export CliqueTreesSolver, CliqueTreesOptions + +for name in names(MadNLP, all = true) + if Base.isexported(MadNLP, name) + @eval using MadNLP: $(name) + @eval export $(name) + end +end + +end # module MadNLPCliqueTrees diff --git a/lib/MadNLPCliqueTrees/test/runtests.jl b/lib/MadNLPCliqueTrees/test/runtests.jl new file mode 100644 index 000000000..ce5cd84b2 --- /dev/null +++ b/lib/MadNLPCliqueTrees/test/runtests.jl @@ -0,0 +1,31 @@ +using Test, MadNLP, MadNLPCliqueTrees, MadNLPTests +using SparseArrays + +@testset "MadNLPCliqueTrees test" begin + # Default LDL algorithm + MadNLPTests.test_linear_solver(CliqueTreesSolver, Float32) + MadNLPTests.test_linear_solver(CliqueTreesSolver, Float64) + + # Cholesky algorithm on positive definite matrix + @testset "Cholesky algorithm" begin + for T in (Float32, Float64) + row = Int32[1,2,2]; col = Int32[1,1,2]; val = T[1., .1, 2.] + b = T[1.0, 3.0] + sol = T[0.8542713567839195, 1.4572864321608041] + csc = sparse(row, col, val, 2, 2) + opt = CliqueTreesOptions(cliquetrees_algorithm=MadNLP.CHOLESKY) + M = CliqueTreesSolver(csc; opt=opt) + MadNLP.factorize!(M) + @test MadNLP.is_inertia(M) + @test MadNLP.inertia(M) == (2, 0, 0) + x = MadNLP.solve_linear_system!(M, copy(b)) + @test MadNLPTests.solcmp(x, sol) + end + end + + test_madnlp( + "CliqueTrees", + () -> MadNLP.Optimizer(linear_solver = CliqueTreesSolver, print_level = MadNLP.ERROR), + [], + ) +end From a7aea3cb61c6ccb757a9f394665754ec1f730b6b Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Fri, 20 Feb 2026 13:07:56 -0500 Subject: [PATCH 02/11] add options --- lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index 1e37933e4..013c34b36 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -1,6 +1,7 @@ module MadNLPCliqueTrees using CliqueTrees +using CliqueTrees: EliminationAlgorithm, SupernodeType using CliqueTrees.Multifrontal using CliqueTrees.Multifrontal: FVector, flatindices, setflatindex! using LinearAlgebra @@ -28,6 +29,8 @@ import MadNLP: @kwdef mutable struct CliqueTreesOptions <: AbstractOptions cliquetrees_algorithm::LinearFactorization = LDL + cliquetrees_ordering::EliminationAlgorithm = AMF() + cliquetrees_supernode::SupernodeType = Maximal() end const FChordalLDLt{T} = ChordalLDLt{:L, T, Int, FVector{T}, FVector{Int}} @@ -41,18 +44,18 @@ mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolv logger::MadNLPLogger end -function _build_factorization(tril::SparseMatrixCSC{T, Int32}, ::Val{LDL}) where T +function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{LDL}) where T csc = SparseMatrixCSC{T, Int}(tril) sym = Symmetric(csc, :L) - F = ChordalLDLt{:L}(sym)::FChordalLDLt{T} + F = ChordalLDLt{:L}(sym; alg=opt.cliquetrees_ordering, snd=opt.cliquetrees_supernode)::FChordalLDLt{T} P = flatindices(F, sym) return F, P end -function _build_factorization(tril::SparseMatrixCSC{T, Int32}, ::Val{CHOLESKY}) where T +function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{CHOLESKY}) where T csc = SparseMatrixCSC{T, Int}(tril) sym = Symmetric(csc, :L) - F = ChordalCholesky{:L}(sym)::FChordalCholesky{T} + F = ChordalCholesky{:L}(sym; alg=opt.cliquetrees_ordering, snd=opt.cliquetrees_supernode)::FChordalCholesky{T} P = flatindices(F, sym) return F, P end @@ -62,7 +65,7 @@ function CliqueTreesSolver( opt = CliqueTreesOptions(), logger = MadNLPLogger(), ) where T - F, P = _build_factorization(tril, Val(opt.cliquetrees_algorithm)) + F, P = _build_factorization(tril, opt, Val(opt.cliquetrees_algorithm)) return CliqueTreesSolver{T, typeof(F)}(tril, F, P, opt, logger) end From 8274511bf65a6faa3212dd50f6e4e318ebf454e4 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Fri, 20 Feb 2026 13:08:54 -0500 Subject: [PATCH 03/11] rm MadNLPTests dep --- lib/MadNLPCliqueTrees/Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index 3d4ce77ca..74b1716a8 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -6,13 +6,11 @@ version = "0.1.0" CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" -MadNLPTests = "b52a2a03-04ab-4a5f-9698-6a2deff93217" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] CliqueTrees = "1.14" MadNLP = "0.8" -MadNLPTests = "0.5" julia = "1.10" [extras] From 929e406d05958a701c848aa1f23e0150378d5bd9 Mon Sep 17 00:00:00 2001 From: "Klamkin, Michael" Date: Fri, 20 Feb 2026 13:36:01 -0500 Subject: [PATCH 04/11] test options --- lib/MadNLPCliqueTrees/test/runtests.jl | 30 +++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/MadNLPCliqueTrees/test/runtests.jl b/lib/MadNLPCliqueTrees/test/runtests.jl index ce5cd84b2..117344b4d 100644 --- a/lib/MadNLPCliqueTrees/test/runtests.jl +++ b/lib/MadNLPCliqueTrees/test/runtests.jl @@ -1,4 +1,4 @@ -using Test, MadNLP, MadNLPCliqueTrees, MadNLPTests +using Test, MadNLP, MadNLPCliqueTrees, MadNLPTests, CliqueTrees using SparseArrays @testset "MadNLPCliqueTrees test" begin @@ -28,4 +28,32 @@ using SparseArrays () -> MadNLP.Optimizer(linear_solver = CliqueTreesSolver, print_level = MadNLP.ERROR), [], ) + + @testset "Elimination orderings" begin + for alg in [MMD(), MCS(), LexBFS(), MCSM(), LexM(), MF(), RCMMD(), RCMGL()] + test_madnlp( + "CliqueTrees/$(nameof(typeof(alg)))", + () -> MadNLP.Optimizer( + linear_solver = CliqueTreesSolver, + print_level = MadNLP.ERROR, + cliquetrees_ordering = alg, + ), + [], + ) + end + end + + @testset "Supernode types" begin + for snd in [Fundamental(), Nodal()] + test_madnlp( + "CliqueTrees/$(nameof(typeof(snd)))", + () -> MadNLP.Optimizer( + linear_solver = CliqueTreesSolver, + print_level = MadNLP.ERROR, + cliquetrees_supernode = snd, + ), + [], + ) + end + end end From 4eaa28b7da3f8a42227f9b8d11146f924c5635e5 Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Fri, 20 Feb 2026 17:20:57 -0500 Subject: [PATCH 05/11] Integrate CliqueTrees.jl. --- lib/MadNLPCliqueTrees/Project.toml | 2 +- .../src/MadNLPCliqueTrees.jl | 27 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index 74b1716a8..190f49329 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -9,7 +9,7 @@ MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -CliqueTrees = "1.14" +CliqueTrees = "1.14.1" MadNLP = "0.8" julia = "1.10" diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index 013c34b36..7c7dd758d 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -3,7 +3,8 @@ module MadNLPCliqueTrees using CliqueTrees using CliqueTrees: EliminationAlgorithm, SupernodeType using CliqueTrees.Multifrontal -using CliqueTrees.Multifrontal: FVector, flatindices, setflatindex! +using CliqueTrees.Multifrontal: FChordalLDLt, FChordalCholesky, flatindices, setflatindex! +using CliqueTrees: DEFAULT_ELIMINATION_ALGORITHM, DEFAULT_SUPERNODE_TYPE using LinearAlgebra using SparseArrays @@ -29,34 +30,29 @@ import MadNLP: @kwdef mutable struct CliqueTreesOptions <: AbstractOptions cliquetrees_algorithm::LinearFactorization = LDL - cliquetrees_ordering::EliminationAlgorithm = AMF() - cliquetrees_supernode::SupernodeType = Maximal() + cliquetrees_ordering::EliminationAlgorithm = DEFAULT_ELIMINATION_ALGORITHM # AMF + cliquetrees_supernode::SupernodeType = DEFAULT_SUPERNODE_TYPE # Maximal end -const FChordalLDLt{T} = ChordalLDLt{:L, T, Int, FVector{T}, FVector{Int}} -const FChordalCholesky{T} = ChordalCholesky{:L, T, Int, FVector{T}, FVector{Int}} - mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolver{T} tril::SparseMatrixCSC{T, Int32} F::F - P::Vector{Int} + P::Vector{Int32} opt::CliqueTreesOptions logger::MadNLPLogger end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{LDL}) where T - csc = SparseMatrixCSC{T, Int}(tril) - sym = Symmetric(csc, :L) - F = ChordalLDLt{:L}(sym; alg=opt.cliquetrees_ordering, snd=opt.cliquetrees_supernode)::FChordalLDLt{T} - P = flatindices(F, sym) + S = Symmetric(tril, :L) + F = ChordalLDLt{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalLDLt{:L, T, Int32} + P = flatindices(F, S) return F, P end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{CHOLESKY}) where T - csc = SparseMatrixCSC{T, Int}(tril) - sym = Symmetric(csc, :L) - F = ChordalCholesky{:L}(sym; alg=opt.cliquetrees_ordering, snd=opt.cliquetrees_supernode)::FChordalCholesky{T} - P = flatindices(F, sym) + S = Symmetric(tril, :L) + F = ChordalCholesky{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalCholesky{:L, T, Int32} + P = flatindices(F, S) return F, P end @@ -71,7 +67,6 @@ end function factorize!(M::CliqueTreesSolver{T, <:ChordalLDLt}) where T fill!(M.F, zero(T)) - fill!(M.F.d, zero(T)) nzval = M.tril.nzval @inbounds for i in eachindex(M.P) setflatindex!(M.F, nzval[i], M.P[i]) From 087800d171fb6112c85cebe0acd5dcb69d1608e5 Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Mon, 23 Feb 2026 11:04:47 -0500 Subject: [PATCH 06/11] Add option to use pivoting. --- lib/MadNLPCliqueTrees/Project.toml | 2 +- .../src/MadNLPCliqueTrees.jl | 32 +++++++------------ 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index 190f49329..d2dbea31d 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -9,7 +9,7 @@ MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -CliqueTrees = "1.14.1" +CliqueTrees = "1.15" MadNLP = "0.8" julia = "1.10" diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index 7c7dd758d..2f7b03fdf 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -3,9 +3,10 @@ module MadNLPCliqueTrees using CliqueTrees using CliqueTrees: EliminationAlgorithm, SupernodeType using CliqueTrees.Multifrontal -using CliqueTrees.Multifrontal: FChordalLDLt, FChordalCholesky, flatindices, setflatindex! +using CliqueTrees.Multifrontal: FChordalLDLt, FChordalCholesky using CliqueTrees: DEFAULT_ELIMINATION_ALGORITHM, DEFAULT_SUPERNODE_TYPE using LinearAlgebra +using LinearAlgebra: PivotingStrategy using SparseArrays import MadNLP: @@ -32,12 +33,12 @@ import MadNLP: cliquetrees_algorithm::LinearFactorization = LDL cliquetrees_ordering::EliminationAlgorithm = DEFAULT_ELIMINATION_ALGORITHM # AMF cliquetrees_supernode::SupernodeType = DEFAULT_SUPERNODE_TYPE # Maximal + cliquetrees_strategy::PivotingStrategy = NoPivot() end mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolver{T} tril::SparseMatrixCSC{T, Int32} F::F - P::Vector{Int32} opt::CliqueTreesOptions logger::MadNLPLogger end @@ -45,15 +46,13 @@ end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{LDL}) where T S = Symmetric(tril, :L) F = ChordalLDLt{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalLDLt{:L, T, Int32} - P = flatindices(F, S) - return F, P + return F end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{CHOLESKY}) where T S = Symmetric(tril, :L) F = ChordalCholesky{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalCholesky{:L, T, Int32} - P = flatindices(F, S) - return F, P + return F end function CliqueTreesSolver( @@ -61,17 +60,12 @@ function CliqueTreesSolver( opt = CliqueTreesOptions(), logger = MadNLPLogger(), ) where T - F, P = _build_factorization(tril, opt, Val(opt.cliquetrees_algorithm)) - return CliqueTreesSolver{T, typeof(F)}(tril, F, P, opt, logger) + F = _build_factorization(tril, opt, Val(opt.cliquetrees_algorithm)) + return CliqueTreesSolver{T, typeof(F)}(tril, F, opt, logger) end function factorize!(M::CliqueTreesSolver{T, <:ChordalLDLt}) where T - fill!(M.F, zero(T)) - nzval = M.tril.nzval - @inbounds for i in eachindex(M.P) - setflatindex!(M.F, nzval[i], M.P[i]) - end - ldlt!(M.F; check=false) + ldlt!(copy!(M.F, M.tril), M.opt.cliquetrees_strategy; check=false) return M end @@ -79,6 +73,7 @@ function solve_linear_system!(M::CliqueTreesSolver{T, <:ChordalLDLt}, rhs::Vecto if issuccess(M.F) ldiv!(M.F, rhs) end + return rhs end @@ -101,12 +96,7 @@ introduce(::CliqueTreesSolver{T, <:ChordalLDLt}) where T = "CliqueTrees/LDLᵀ v$(pkgversion(CliqueTrees))" function factorize!(M::CliqueTreesSolver{T, <:ChordalCholesky}) where T - fill!(M.F, zero(T)) - nzval = M.tril.nzval - @inbounds for i in eachindex(M.P) - setflatindex!(M.F, nzval[i], M.P[i]) - end - cholesky!(M.F; check=false) + cholesky!(copy!(M.F, M.tril), M,opt.cliquetrees_strategy; check=false) return M end @@ -114,11 +104,13 @@ function solve_linear_system!(M::CliqueTreesSolver{T, <:ChordalCholesky}, rhs::V if issuccess(M.F) ldiv!(M.F, rhs) end + return rhs end function inertia(M::CliqueTreesSolver{T, <:ChordalCholesky}) where T n = size(M.tril, 1) + if issuccess(M.F) return (n, 0, 0) else From 10a185fa22af5385e8a657496d8fa966d43c16b4 Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Mon, 23 Feb 2026 11:05:59 -0500 Subject: [PATCH 07/11] Bump `CliqueTrees` compat. --- lib/MadNLPCliqueTrees/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index d2dbea31d..e0360e745 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -9,7 +9,7 @@ MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -CliqueTrees = "1.15" +CliqueTrees = "1.15.2" MadNLP = "0.8" julia = "1.10" From d3b30cdb2c2a5d7d775bda76f746a23ffef6b0ca Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Mon, 23 Feb 2026 15:27:54 -0500 Subject: [PATCH 08/11] Automatic strategy selection. --- lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index 2f7b03fdf..be8cab12e 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -33,7 +33,19 @@ import MadNLP: cliquetrees_algorithm::LinearFactorization = LDL cliquetrees_ordering::EliminationAlgorithm = DEFAULT_ELIMINATION_ALGORITHM # AMF cliquetrees_supernode::SupernodeType = DEFAULT_SUPERNODE_TYPE # Maximal - cliquetrees_strategy::PivotingStrategy = NoPivot() + cliquetrees_strategy::PivotingStrategy = choose_strategy(cliquetrees_algorithm) +end + +function choose_strategy(alg::LinearFactorization) + if alg == CHOLESKY + strategy = NoPivot() + elseif alg == LDL + strategy = RowMaximum() + else + error() + end + + return strategy end mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolver{T} From 82756b60c51a7664c97f9ec266ce55cad43ffa9c Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Sun, 1 Mar 2026 10:32:33 -0500 Subject: [PATCH 09/11] Fix bug. --- lib/MadNLPCliqueTrees/Project.toml | 3 +++ lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index e0360e745..f71c55c82 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -8,6 +8,9 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +[sources] +MadNLP = {path = "../.."} + [compat] CliqueTrees = "1.15.2" MadNLP = "0.8" diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index be8cab12e..0f5017ee2 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -108,7 +108,7 @@ introduce(::CliqueTreesSolver{T, <:ChordalLDLt}) where T = "CliqueTrees/LDLᵀ v$(pkgversion(CliqueTrees))" function factorize!(M::CliqueTreesSolver{T, <:ChordalCholesky}) where T - cholesky!(copy!(M.F, M.tril), M,opt.cliquetrees_strategy; check=false) + cholesky!(copy!(M.F, M.tril), M.opt.cliquetrees_strategy; check=false) return M end From 72da7ebc40fab68a178e5211ef3b4aee3fa4a203 Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Sun, 1 Mar 2026 21:12:48 -0500 Subject: [PATCH 10/11] Add regularization. --- .github/workflows/test.yml | 31 ++++++++++ lib/MadNLPCliqueTrees/Project.toml | 7 +-- .../src/MadNLPCliqueTrees.jl | 18 +++--- lib/MadNLPCliqueTrees/test/runtests.jl | 59 ++++++++++++++----- src/KKT/Dense/augmented.jl | 2 +- src/KKT/Dense/condensed.jl | 2 +- src/KKT/Sparse/augmented.jl | 2 +- src/KKT/Sparse/condensed.jl | 2 +- src/KKT/Sparse/scaled_augmented.jl | 2 +- src/KKT/Sparse/unreduced.jl | 2 +- src/LinearSolvers/cholmod.jl | 2 +- src/LinearSolvers/lapack.jl | 1 + src/LinearSolvers/ldl.jl | 2 +- src/LinearSolvers/mumps.jl | 2 +- src/LinearSolvers/umfpack.jl | 2 +- 15 files changed, 98 insertions(+), 38 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49931137d..e4017c760 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,6 +103,37 @@ jobs: # - uses: codecov/codecov-action@v5 # with: # file: lcov.info + test-cliquetrees: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: ['1'] + julia-arch: [x64] + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/install-juliaup@v2 + with: + channel: ${{ matrix.julia-version }} + - uses: julia-actions/cache@v1 + - name: test MadNLPCliqueTrees + shell: julia --project=./lib/MadNLPCliqueTrees --color=yes {0} + run: | + using Pkg + Pkg.Registry.update() + Pkg.develop(path=".") + Pkg.instantiate() + using MadNLP + PATH_MADNLP = dirname(pathof(MadNLP)) + Pkg.develop(path=joinpath(PATH_MADNLP, "..", "lib", "MadNLPTests")) + Pkg.test("MadNLPCliqueTrees", coverage=true) + - uses: julia-actions/julia-processcoverage@v1 + with: + directories: lib/MadNLPCliqueTrees/src + - uses: codecov/codecov-action@v5 + with: + file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} # need to run one on cuda and one on amdgpu test-gpu: strategy: diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index f71c55c82..9ee4e7916 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -8,12 +8,9 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -[sources] -MadNLP = {path = "../.."} - [compat] -CliqueTrees = "1.15.2" -MadNLP = "0.8" +CliqueTrees = "1.16" +MadNLP = "0.9" julia = "1.10" [extras] diff --git a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl index 0f5017ee2..045d4e9a5 100644 --- a/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl +++ b/lib/MadNLPCliqueTrees/src/MadNLPCliqueTrees.jl @@ -1,10 +1,9 @@ module MadNLPCliqueTrees using CliqueTrees -using CliqueTrees: EliminationAlgorithm, SupernodeType +using CliqueTrees: EliminationAlgorithm, DEFAULT_ELIMINATION_ALGORITHM using CliqueTrees.Multifrontal -using CliqueTrees.Multifrontal: FChordalLDLt, FChordalCholesky -using CliqueTrees: DEFAULT_ELIMINATION_ALGORITHM, DEFAULT_SUPERNODE_TYPE +using CliqueTrees.Multifrontal: FChordalLDLt, FChordalCholesky, AbstractRegularization, NoRegularization using LinearAlgebra using LinearAlgebra: PivotingStrategy using SparseArrays @@ -32,8 +31,8 @@ import MadNLP: @kwdef mutable struct CliqueTreesOptions <: AbstractOptions cliquetrees_algorithm::LinearFactorization = LDL cliquetrees_ordering::EliminationAlgorithm = DEFAULT_ELIMINATION_ALGORITHM # AMF - cliquetrees_supernode::SupernodeType = DEFAULT_SUPERNODE_TYPE # Maximal cliquetrees_strategy::PivotingStrategy = choose_strategy(cliquetrees_algorithm) + cliquetrees_regularization::AbstractRegularization = NoRegularization() end function choose_strategy(alg::LinearFactorization) @@ -51,19 +50,20 @@ end mutable struct CliqueTreesSolver{T, F <: Factorization{T}} <: AbstractLinearSolver{T} tril::SparseMatrixCSC{T, Int32} F::F + signs::Vector{Int} opt::CliqueTreesOptions logger::MadNLPLogger end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{LDL}) where T S = Symmetric(tril, :L) - F = ChordalLDLt{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalLDLt{:L, T, Int32} + F = ChordalLDLt{:L}(S; alg = opt.cliquetrees_ordering)::FChordalLDLt{:L, T, Int32} return F end function _build_factorization(tril::SparseMatrixCSC{T, Int32}, opt::CliqueTreesOptions, ::Val{CHOLESKY}) where T S = Symmetric(tril, :L) - F = ChordalCholesky{:L}(S; alg = opt.cliquetrees_ordering, snd = opt.cliquetrees_supernode)::FChordalCholesky{:L, T, Int32} + F = ChordalCholesky{:L}(S; alg = opt.cliquetrees_ordering)::FChordalCholesky{:L, T, Int32} return F end @@ -71,13 +71,15 @@ function CliqueTreesSolver( tril::SparseMatrixCSC{T, Int32}; opt = CliqueTreesOptions(), logger = MadNLPLogger(), + pos = size(tril, 1), ) where T + signs = fill(-1, size(tril, 1)); signs[1:pos] .= 1 F = _build_factorization(tril, opt, Val(opt.cliquetrees_algorithm)) - return CliqueTreesSolver{T, typeof(F)}(tril, F, opt, logger) + return CliqueTreesSolver{T, typeof(F)}(tril, F, signs, opt, logger) end function factorize!(M::CliqueTreesSolver{T, <:ChordalLDLt}) where T - ldlt!(copy!(M.F, M.tril), M.opt.cliquetrees_strategy; check=false) + ldlt!(copy!(M.F, M.tril), M.opt.cliquetrees_strategy; signs=M.signs, reg=M.opt.cliquetrees_regularization, check=false) return M end diff --git a/lib/MadNLPCliqueTrees/test/runtests.jl b/lib/MadNLPCliqueTrees/test/runtests.jl index 117344b4d..a8ae9fd40 100644 --- a/lib/MadNLPCliqueTrees/test/runtests.jl +++ b/lib/MadNLPCliqueTrees/test/runtests.jl @@ -1,4 +1,5 @@ using Test, MadNLP, MadNLPCliqueTrees, MadNLPTests, CliqueTrees +using CliqueTrees.Multifrontal: DynamicRegularization, GMW81, SE99 using SparseArrays @testset "MadNLPCliqueTrees test" begin @@ -26,11 +27,11 @@ using SparseArrays test_madnlp( "CliqueTrees", () -> MadNLP.Optimizer(linear_solver = CliqueTreesSolver, print_level = MadNLP.ERROR), - [], + ["eigmina"], ) @testset "Elimination orderings" begin - for alg in [MMD(), MCS(), LexBFS(), MCSM(), LexM(), MF(), RCMMD(), RCMGL()] + for alg in (AMF(), MMD(), MF()) test_madnlp( "CliqueTrees/$(nameof(typeof(alg)))", () -> MadNLP.Optimizer( @@ -38,22 +39,50 @@ using SparseArrays print_level = MadNLP.ERROR, cliquetrees_ordering = alg, ), - [], + ["eigmina"], ) end end - @testset "Supernode types" begin - for snd in [Fundamental(), Nodal()] - test_madnlp( - "CliqueTrees/$(nameof(typeof(snd)))", - () -> MadNLP.Optimizer( - linear_solver = CliqueTreesSolver, - print_level = MadNLP.ERROR, - cliquetrees_supernode = snd, - ), - [], - ) - end + @testset "Dynamic Regularization" begin + reg = DynamicRegularization() + + test_madnlp( + "CliqueTrees/$(nameof(typeof(reg)))", + () -> MadNLP.Optimizer( + linear_solver = CliqueTreesSolver, + print_level = MadNLP.ERROR, + cliquetrees_regularization = reg, + ), + [], + ) + end + + @testset "GMW81" begin + reg = GMW81() + + test_madnlp( + "CliqueTrees/$(nameof(typeof(reg)))", + () -> MadNLP.Optimizer( + linear_solver = CliqueTreesSolver, + print_level = MadNLP.ERROR, + cliquetrees_regularization = reg, + ), + ["unbounded"], + ) + end + + @testset "SE99" begin + reg = SE99() + + test_madnlp( + "CliqueTrees/$(nameof(typeof(reg)))", + () -> MadNLP.Optimizer( + linear_solver = CliqueTreesSolver, + print_level = MadNLP.ERROR, + cliquetrees_regularization = reg, + ), + ["eigmina"], + ) end end diff --git a/src/KKT/Dense/augmented.jl b/src/KKT/Dense/augmented.jl index a1c1a9f97..3f8c3b3d8 100644 --- a/src/KKT/Dense/augmented.jl +++ b/src/KKT/Dense/augmented.jl @@ -81,7 +81,7 @@ function create_kkt_system( fill!(diag_hess, zero(T)) quasi_newton = create_quasi_newton(hessian_approximation, cb, n; options=qn_options) - _linear_solver = linear_solver(aug_com; opt = opt_linear_solver) + _linear_solver = linear_solver(aug_com; opt = opt_linear_solver, pos = n + ns) return DenseKKTSystem( hess, jac, quasi_newton, diff --git a/src/KKT/Dense/condensed.jl b/src/KKT/Dense/condensed.jl index d1fb46cca..386bd3beb 100644 --- a/src/KKT/Dense/condensed.jl +++ b/src/KKT/Dense/condensed.jl @@ -94,7 +94,7 @@ function create_kkt_system( ind_ineq_shifted = cb.ind_ineq .+ n .+ ns quasi_newton = create_quasi_newton(hessian_approximation, cb, n; options=qn_options) - _linear_solver = linear_solver(aug_com; opt = opt_linear_solver) + _linear_solver = linear_solver(aug_com; opt = opt_linear_solver, pos = n + m - ns) return DenseCondensedKKTSystem( hess, jac, quasi_newton, jac_ineq, diff --git a/src/KKT/Sparse/augmented.jl b/src/KKT/Sparse/augmented.jl index 245f3be82..f2ea84fcb 100644 --- a/src/KKT/Sparse/augmented.jl +++ b/src/KKT/Sparse/augmented.jl @@ -126,7 +126,7 @@ function create_kkt_system( hess_com, hess_csc_map = coo_to_csc(hess_raw) _linear_solver = linear_solver( - aug_com; opt = opt_linear_solver + aug_com; opt = opt_linear_solver, pos = n_tot ) return SparseKKTSystem( diff --git a/src/KKT/Sparse/condensed.jl b/src/KKT/Sparse/condensed.jl index 1174daece..bc520a7d3 100644 --- a/src/KKT/Sparse/condensed.jl +++ b/src/KKT/Sparse/condensed.jl @@ -116,7 +116,7 @@ function create_kkt_system( jt_csc ) - _linear_solver = linear_solver(aug_com; opt = opt_linear_solver) + _linear_solver = linear_solver(aug_com; opt = opt_linear_solver, pos = n_tot) ext = get_sparse_condensed_ext(VT, hess_com, jptr, jt_csc_map, hess_csc_map) return SparseCondensedKKTSystem( hess, hess_raw, hess_com, hess_csc_map, diff --git a/src/KKT/Sparse/scaled_augmented.jl b/src/KKT/Sparse/scaled_augmented.jl index 86bf05124..000a2f3fa 100644 --- a/src/KKT/Sparse/scaled_augmented.jl +++ b/src/KKT/Sparse/scaled_augmented.jl @@ -160,7 +160,7 @@ function create_kkt_system( hess_com, hess_csc_map = coo_to_csc(hess_raw) _linear_solver = linear_solver( - aug_com; opt = opt_linear_solver + aug_com; opt = opt_linear_solver, pos = n_tot ) return ScaledSparseKKTSystem( diff --git a/src/KKT/Sparse/unreduced.jl b/src/KKT/Sparse/unreduced.jl index 9b305085e..2444f6558 100644 --- a/src/KKT/Sparse/unreduced.jl +++ b/src/KKT/Sparse/unreduced.jl @@ -145,7 +145,7 @@ function create_kkt_system( jac_com, jac_csc_map = coo_to_csc(jac_raw) hess_com, hess_csc_map = coo_to_csc(hess_raw) - _linear_solver = linear_solver(aug_com; opt = opt_linear_solver) + _linear_solver = linear_solver(aug_com; opt = opt_linear_solver, pos = n_tot) return SparseUnreducedKKTSystem( hess, jac_callback, jac, quasi_newton, reg, pr_diag, du_diag, l_diag, u_diag, l_lower, u_lower, l_lower_aug, u_lower_aug, diff --git a/src/LinearSolvers/cholmod.jl b/src/LinearSolvers/cholmod.jl index 0ef949549..868bf7625 100644 --- a/src/LinearSolvers/cholmod.jl +++ b/src/LinearSolvers/cholmod.jl @@ -17,7 +17,7 @@ end function CHOLMODSolver( csc::SparseMatrixCSC{T}; - opt=CHOLMODOptions(), logger=MadNLPLogger(), + opt=CHOLMODOptions(), logger=MadNLPLogger(), pos=nothing, ) where T p = Vector{Float64}(undef,csc.n) d = Vector{Float64}(undef,csc.n) diff --git a/src/LinearSolvers/lapack.jl b/src/LinearSolvers/lapack.jl index 2dd2d6e5c..88a2e0b19 100644 --- a/src/LinearSolvers/lapack.jl +++ b/src/LinearSolvers/lapack.jl @@ -22,6 +22,7 @@ mutable struct LapackCPUSolver{T, MT} <: AbstractLinearSolver{T} A::MT; opt=LapackOptions(), logger=MadNLPLogger(), + pos=nothing, ) where {MT <: AbstractMatrix} T = eltype(A) m,n = size(A) diff --git a/src/LinearSolvers/ldl.jl b/src/LinearSolvers/ldl.jl index 699345cf3..bf287b8bc 100644 --- a/src/LinearSolvers/ldl.jl +++ b/src/LinearSolvers/ldl.jl @@ -13,7 +13,7 @@ end function LDLSolver( tril::SparseMatrixCSC{T}; - opt=LDLFactorizationsOptions(), logger=MadNLPLogger(), + opt=LDLFactorizationsOptions(), logger=MadNLPLogger(), pos=nothing, ) where T # TODO: convert tril to triu, not full full, tril_to_full_view = get_tril_to_full(T,tril) diff --git a/src/LinearSolvers/mumps.jl b/src/LinearSolvers/mumps.jl index 2184137ea..8d78a931d 100644 --- a/src/LinearSolvers/mumps.jl +++ b/src/LinearSolvers/mumps.jl @@ -164,7 +164,7 @@ end # --------------------------------------------------------------------------------------- function MumpsSolver(csc::SparseMatrixCSC{T,Int32}; - opt=MumpsOptions(), logger=MadNLPLogger(), + opt=MumpsOptions(), logger=MadNLPLogger(), pos=nothing, ) where T I,J = findIJ(csc) diff --git a/src/LinearSolvers/umfpack.jl b/src/LinearSolvers/umfpack.jl index bc3ef87f7..244eb24b7 100644 --- a/src/LinearSolvers/umfpack.jl +++ b/src/LinearSolvers/umfpack.jl @@ -21,7 +21,7 @@ end function UmfpackSolver( csc::SparseMatrixCSC{T}; - opt=UmfpackOptions(), logger=MadNLPLogger(), + opt=UmfpackOptions(), logger=MadNLPLogger(), pos=nothing, ) where T p = Vector{Float64}(undef,csc.n) d = Vector{Float64}(undef,csc.n) From 752a5a0e9787be306f3f6da3039825246d9b93c7 Mon Sep 17 00:00:00 2001 From: Richard Samuelson Date: Sun, 1 Mar 2026 22:28:42 -0500 Subject: [PATCH 11/11] Bump CliqueTrees version: `1.16.2`. --- lib/MadNLPCliqueTrees/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/MadNLPCliqueTrees/Project.toml b/lib/MadNLPCliqueTrees/Project.toml index 9ee4e7916..8f97a4454 100644 --- a/lib/MadNLPCliqueTrees/Project.toml +++ b/lib/MadNLPCliqueTrees/Project.toml @@ -9,7 +9,7 @@ MadNLP = "2621e9c9-9eb4-46b1-8089-e8c72242dfb6" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] -CliqueTrees = "1.16" +CliqueTrees = "1.16.2" MadNLP = "0.9" julia = "1.10"