From b5b61f964ac30ecaa788900e19db0467f9a819aa Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:40:28 -0400 Subject: [PATCH] Use SparseConnectivityTracer.jl --- Project.toml | 4 + README.md | 2 - docs/Project.toml | 4 - docs/src/backend.md | 20 ++--- docs/src/performance.md | 2 +- docs/src/predefined.md | 26 ++---- docs/src/reference.md | 12 +-- src/ADNLPModels.jl | 47 ++-------- src/predefined_backend.jl | 12 +-- src/sparse_diff_tools.jl | 145 ------------------------------ src/sparse_hessian.jl | 8 +- src/sparse_jacobian.jl | 3 +- src/sparse_sym.jl | 174 ------------------------------------ src/sparsity_pattern.jl | 38 ++++++++ test/Project.toml | 6 -- test/runtests.jl | 26 +++--- test/script_OP.jl | 2 - test/sparse_hessian.jl | 3 - test/sparse_jacobian.jl | 4 - test/sparse_jacobian_nls.jl | 4 - 20 files changed, 94 insertions(+), 448 deletions(-) delete mode 100644 src/sparse_diff_tools.jl delete mode 100644 src/sparse_sym.jl create mode 100644 src/sparsity_pattern.jl diff --git a/Project.toml b/Project.toml index 4364b41c..dec03ff0 100644 --- a/Project.toml +++ b/Project.toml @@ -3,6 +3,7 @@ uuid = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" version = "0.7.2" [deps] +ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" ColPack = "ffa27691-3a59-46ab-a8d4-551f45b8d401" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -10,9 +11,12 @@ NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6" Requires = "ae029012-a4dd-5104-9daa-d747884805df" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5" [compat] +ADTypes = "1.2.1" ColPack = "0.4" +SparseConnectivityTracer = "0.5" ForwardDiff = "0.9.0, 0.10.0" NLPModels = "0.18, 0.19, 0.20, 0.21" Requires = "1" diff --git a/README.md b/README.md index 40459ac6..67b58f2d 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,6 @@ The following AD packages are supported: and as optional dependencies (you must load the package before): - `Enzyme.jl`; -- `SparseDiffTools.jl`; -- `Symbolics.jl`; - `Zygote.jl`. ## Bug reports and discussions diff --git a/docs/Project.toml b/docs/Project.toml index c8657e93..0d1e6c95 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -10,8 +10,6 @@ OptimizationProblems = "5049e819-d29b-5fba-b941-0eee7e64c1c6" Percival = "01435c0c-c90d-11e9-3788-63660f8fbccc" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" SolverBenchmark = "581a75fa-a23a-52d0-a590-d6201de2218a" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] @@ -24,6 +22,4 @@ OptimizationProblems = "0.7" Percival = "0.7" Plots = "1" SolverBenchmark = "0.5" -SymbolicUtils = "=1.5.1" -Symbolics = "5.3" Zygote = "0.6.62" diff --git a/docs/src/backend.md b/docs/src/backend.md index 22b0d6a8..30e0ebc6 100644 --- a/docs/src/backend.md +++ b/docs/src/backend.md @@ -7,14 +7,14 @@ The backend information is in a structure [`ADNLPModels.ADModelBackend`](@ref) i The functions used internally to define the NLPModel API and the possible backends are defined in the following table: -| Functions | FowardDiff backends | ReverseDiff backends | Zygote backends | Enzyme backend | SparseDiffTools backend | Symbolics backend | +| Functions | FowardDiff backends | ReverseDiff backends | Zygote backends | Enzyme backend | Sparse backend | | ----------- | ----------- | ----------- | ----------- | ----------- | ----------- | ----------- | -| `gradient` and `gradient!` | `ForwardDiffADGradient`/`GenericForwardDiffADGradient` | `ReverseDiffADGradient`/`GenericReverseDiffADGradient` | `ZygoteADGradient` | `EnzymeADGradient` | -- | -- | -| `jacobian` | `ForwardDiffADJacobian` | `ReverseDiffADJacobian` | `ZygoteADJacobian` | -- | `SDTSparseADJacobian` | `SparseADJacobian`/`SparseSymbolicsADJacobian` | -| `hessian` | `ForwardDiffADHessian` | `ReverseDiffADHessian` | `ZygoteADHessian` | -- | -- | `SparseADHessian`/`SparseSymbolicsADHessian` | -| `Jprod` | `ForwardDiffADJprod`/`GenericForwardDiffADJprod` | `ReverseDiffADJprod`/`GenericReverseDiffADJprod` | `ZygoteADJprod` | -- | `SDTForwardDiffADJprod` | -- | -| `Jtprod` | `ForwardDiffADJtprod`/`GenericForwardDiffADJtprod` | `ReverseDiffADJtprod`/`GenericReverseDiffADJtprod` | `ZygoteADJtprod` | -- | -- | -- | -| `Hvprod` | `ForwardDiffADHvprod`/`GenericForwardDiffADHvprod` | `ReverseDiffADHvprod`/`GenericReverseDiffADHvprod` | -- | -- | `SDTForwardDiffADHvprod` | -- | +| `gradient` and `gradient!` | `ForwardDiffADGradient`/`GenericForwardDiffADGradient` | `ReverseDiffADGradient`/`GenericReverseDiffADGradient` | `ZygoteADGradient` | `EnzymeADGradient` | -- | +| `jacobian` | `ForwardDiffADJacobian` | `ReverseDiffADJacobian` | `ZygoteADJacobian` | -- | `SparseADJacobian` | +| `hessian` | `ForwardDiffADHessian` | `ReverseDiffADHessian` | `ZygoteADHessian` | -- | `SparseADHessian` | +| `Jprod` | `ForwardDiffADJprod`/`GenericForwardDiffADJprod` | `ReverseDiffADJprod`/`GenericReverseDiffADJprod` | `ZygoteADJprod` | -- | +| `Jtprod` | `ForwardDiffADJtprod`/`GenericForwardDiffADJtprod` | `ReverseDiffADJtprod`/`GenericReverseDiffADJtprod` | `ZygoteADJtprod` | -- | +| `Hvprod` | `ForwardDiffADHvprod`/`GenericForwardDiffADHvprod` | `ReverseDiffADHvprod`/`GenericReverseDiffADHvprod` | -- | | `directional_second_derivative` | `ForwardDiffADGHjvprod` | -- | -- | -- | -- | The functions `hess_structure!`, `hess_coord!`, `jac_structure!` and `jac_coord!` defined in `ad.jl` are generic to all the backends for now. @@ -49,7 +49,7 @@ Thanks to the backends inside `ADNLPModels.jl`, it is easy to change the backend ```@example adnlp nlp = ADNLPModel(f, x0, gradient_backend = ADNLPModels.ReverseDiffADGradient) -grad(nlp, nlp.meta.x0) # returns the gradient at x0 using `ReverseDiff` +grad(nlp, nlp.meta.x0) # returns the gradient at x0 using `ReverseDiff` ``` It is also possible to try some new implementation for each function. First, we define a new `ADBackend` structure. @@ -81,7 +81,7 @@ Finally, we use the homemade backend to compute the gradient. ```@example adnlp nlp = ADNLPModel(sum, ones(3), gradient_backend = NewADGradient) -grad(nlp, nlp.meta.x0) # returns the gradient at x0 using `NewADGradient` +grad(nlp, nlp.meta.x0) # returns the gradient at x0 using `NewADGradient` ``` ### Change backend @@ -104,7 +104,7 @@ set_adbackend!(nlp, adback) get_adbackend(nlp) ``` -The alternative is to use ``set_adbackend!` and pass the new backends via `kwargs`. In the second approach, it is possible to pass either the type of the desired backend or an instance as shown below. +The alternative is to use `set_adbackend!` and pass the new backends via `kwargs`. In the second approach, it is possible to pass either the type of the desired backend or an instance as shown below. ```@example adnlp2 set_adbackend!( diff --git a/docs/src/performance.md b/docs/src/performance.md index f2625538..0110a03d 100644 --- a/docs/src/performance.md +++ b/docs/src/performance.md @@ -84,7 +84,7 @@ v = ones(2) It is tempting to define the most generic and efficient `ADNLPModel` from the start. ```@example ex2 -using ADNLPModels, NLPModels, Symbolics +using ADNLPModels, NLPModels f(x) = (x[1] - x[2])^2 x0 = ones(2) lcon = ucon = ones(1) diff --git a/docs/src/predefined.md b/docs/src/predefined.md index 5dce2e6b..8a64c2a7 100644 --- a/docs/src/predefined.md +++ b/docs/src/predefined.md @@ -49,30 +49,12 @@ get_adbackend(nlp) ## Hessian and Jacobian computations -It is to be noted that by default the Jacobian and Hessian matrices are dense. +It is to be noted that by default the Jacobian and Hessian matrices are sparse. ```@example ex1 -(get_nnzj(nlp), get_nnzh(nlp)) # number of nonzeros elements in the Jacobian and Hessian +(get_nnzj(nlp), get_nnzh(nlp)) # number of nonzeros elements in the Jacobian and Hessian ``` -To enable sparse computations of these entries, one needs to first load the package [`Symbolics.jl`](https://github.com/JuliaSymbolics/Symbolics.jl) - -```@example ex1 -using Symbolics -``` - -and now - -```@example ex1 -ADNLPModels.predefined_backend[:optimized][:jacobian_backend] -``` - -```@example ex1 -ADNLPModels.predefined_backend[:optimized][:hessian_backend] -``` - -Choosing another optimization problem with the optimized backend will compute sparse Jacobian and Hessian matrices. - ```@example ex1 f(x) = (x[1] - 1)^2 T = Float64 @@ -92,4 +74,6 @@ x = rand(T, 2) jac(nlp, x) ``` -The package [`Symbolics.jl`](https://github.com/JuliaSymbolics/Symbolics.jl) is used to compute the sparsity pattern of the sparse matrix. The evaluation of the number of directional derivatives needed to evaluate the matrix is done by [`ColPack.jl`](https://github.com/michel2323/ColPack.jl). +The package [`SparseConnectivityTracer.jl`](https://github.com/adrhill/SparseConnectivityTracer.jl) is used to compute the sparsity pattern of Jacobians and Hessians. +The evaluation of the number of directional derivatives and the seeds needed to evaluate the compressed Jacobians and Hessians is done by [`ColPack.jl`](https://github.com/exanauts/ColPack.jl). +We acknowledge Guillaume Dalle (@gdalle), Adrian Hill (@adrhill), and Michel Schanen (@michel2323) for the development of these packages. diff --git a/docs/src/reference.md b/docs/src/reference.md index bfe346a7..d0ac148a 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -1,17 +1,17 @@ # Reference -​ + ## Contents -​ + ```@contents Pages = ["reference.md"] ``` -​ + ## Index -​ + ```@index Pages = ["reference.md"] ``` -​ + ```@autodocs Modules = [ADNLPModels] -``` \ No newline at end of file +``` diff --git a/src/ADNLPModels.jl b/src/ADNLPModels.jl index 481724bb..18650372 100644 --- a/src/ADNLPModels.jl +++ b/src/ADNLPModels.jl @@ -2,8 +2,11 @@ module ADNLPModels # stdlib using LinearAlgebra, SparseArrays + # external -using ColPack, ForwardDiff, ReverseDiff +using ADTypes: ADTypes, AbstractSparsityDetector +using SparseConnectivityTracer, ColPack, ForwardDiff, ReverseDiff + # JSO using NLPModels using Requires @@ -16,39 +19,13 @@ const ADModel{T, S} = Union{AbstractADNLPModel{T, S}, AbstractADNLSModel{T, S}} include("ad.jl") include("ad_api.jl") -""" - compute_jacobian_sparsity(c!, cx, x0) - -Return a sparse matrix. -""" -function compute_jacobian_sparsity(args...) - throw( - ArgumentError( - "Please load Symbolics.jl to enable sparse Jacobian or implement `compute_jacobian_sparsity`.", - ), - ) -end - -""" - compute_hessian_sparsity(f, nvar, c!, ncon) - -Return a sparse matrix. -""" -function compute_hessian_sparsity(args...) - throw( - ArgumentError( - "Please load Symbolics.jl to enable sparse Hessian or implement `compute_hessian_sparsity`.", - ), - ) -end - +include("sparsity_pattern.jl") include("sparse_jacobian.jl") include("sparse_hessian.jl") include("forward.jl") include("reverse.jl") include("enzyme.jl") -include("sparse_diff_tools.jl") include("zygote.jl") include("predefined_backend.jl") include("nlp.jl") @@ -181,20 +158,6 @@ function ADNLSModel!(model::AbstractNLSModel; kwargs...) end end -@init begin - @require Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" begin - include("sparse_sym.jl") - - predefined_backend[:default][:jacobian_backend] = SparseADJacobian - predefined_backend[:default][:jacobian_residual_backend] = SparseADJacobian - predefined_backend[:optimized][:jacobian_backend] = SparseADJacobian - predefined_backend[:optimized][:jacobian_residual_backend] = SparseADJacobian - - predefined_backend[:default][:hessian_backend] = SparseADHessian - predefined_backend[:optimized][:hessian_backend] = SparseReverseADHessian - end -end - export get_adbackend, set_adbackend! """ diff --git a/src/predefined_backend.jl b/src/predefined_backend.jl index 2b207ccf..6e2d0ffd 100644 --- a/src/predefined_backend.jl +++ b/src/predefined_backend.jl @@ -3,13 +3,13 @@ default_backend = Dict( :hprod_backend => ForwardDiffADHvprod, :jprod_backend => ForwardDiffADJprod, :jtprod_backend => ForwardDiffADJtprod, - :jacobian_backend => ForwardDiffADJacobian, - :hessian_backend => ForwardDiffADHessian, + :jacobian_backend => SparseADJacobian, # ForwardDiffADJacobian + :hessian_backend => SparseADHessian, # ForwardDiffADHessian :ghjvprod_backend => ForwardDiffADGHjvprod, :hprod_residual_backend => ForwardDiffADHvprod, :jprod_residual_backend => ForwardDiffADJprod, :jtprod_residual_backend => ForwardDiffADJtprod, - :jacobian_residual_backend => ForwardDiffADJacobian, + :jacobian_residual_backend => SparseADJacobian, # ForwardDiffADJacobian, :hessian_residual_backend => ForwardDiffADHessian, ) @@ -18,13 +18,13 @@ optimized = Dict( :hprod_backend => ReverseDiffADHvprod, :jprod_backend => ForwardDiffADJprod, :jtprod_backend => ReverseDiffADJtprod, - :jacobian_backend => ForwardDiffADJacobian, - :hessian_backend => ForwardDiffADHessian, + :jacobian_backend => SparseADJacobian, # ForwardDiffADJacobian + :hessian_backend => SparseReverseADHessian, # ForwardDiffADHessian, :ghjvprod_backend => ForwardDiffADGHjvprod, :hprod_residual_backend => ReverseDiffADHvprod, :jprod_residual_backend => ForwardDiffADJprod, :jtprod_residual_backend => ReverseDiffADJtprod, - :jacobian_residual_backend => ForwardDiffADJacobian, + :jacobian_residual_backend => SparseADJacobian, # ForwardDiffADJacobian :hessian_residual_backend => ForwardDiffADHessian, ) diff --git a/src/sparse_diff_tools.jl b/src/sparse_diff_tools.jl deleted file mode 100644 index 8ec2917d..00000000 --- a/src/sparse_diff_tools.jl +++ /dev/null @@ -1,145 +0,0 @@ -@init begin - @require SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804" begin - function sparse_matrix_colors(A, alg::SparseDiffTools.SparseDiffToolsColoringAlgorithm) - return SparseDiffTools.matrix_colors(A, alg) - end - - struct SDTSparseADJacobian{Tv, Ti, T, T2, T3, T4, T5} <: ADNLPModels.ADBackend - cfJ::SparseDiffTools.ForwardColorJacCache{T, T2, T3, T4, T5, SparseMatrixCSC{Tv, Ti}} - end - - function SDTSparseADJacobian( - nvar, - f, - ncon, - c!; - x0::S = rand(nvar), - alg::SparseDiffTools.SparseDiffToolsColoringAlgorithm = SparseDiffTools.GreedyD1Color(), - kwargs..., - ) where {S} - T = eltype(S) - output = similar(x0, ncon) - J = compute_jacobian_sparsity(c!, output, x0) - colors = sparse_matrix_colors(J, alg) - jac = SparseMatrixCSC{T, Int}(J.m, J.n, J.colptr, J.rowval, T.(J.nzval)) - - dx = fill!(S(undef, ncon), 0) - cfJ = SparseDiffTools.ForwardColorJacCache(c!, x0, colorvec = colors, dx = dx, sparsity = jac) - SDTSparseADJacobian(cfJ) - end - - function get_nln_nnzj(b::SDTSparseADJacobian, nvar, ncon) - nnz(b.cfJ.sparsity) - end - - function NLPModels.jac_structure!( - b::SDTSparseADJacobian, - nlp::ADModel, - rows::AbstractVector{<:Integer}, - cols::AbstractVector{<:Integer}, - ) - rows .= rowvals(b.cfJ.sparsity) - for i = 1:(nlp.meta.nvar) - for j = b.cfJ.sparsity.colptr[i]:(b.cfJ.sparsity.colptr[i + 1] - 1) - cols[j] = i - end - end - return rows, cols - end - - function NLPModels.jac_coord!( - b::SDTSparseADJacobian, - nlp::ADModel, - x::AbstractVector, - vals::AbstractVector, - ) - SparseDiffTools.forwarddiff_color_jacobian!(b.cfJ.sparsity, nlp.c!, x, b.cfJ) - vals .= nonzeros(b.cfJ.sparsity) - return vals - end - - function NLPModels.jac_structure_residual!( - b::SDTSparseADJacobian, - nls::AbstractADNLSModel, - rows::AbstractVector{<:Integer}, - cols::AbstractVector{<:Integer}, - ) - rows .= rowvals(b.cfJ.sparsity) - for i = 1:(nls.meta.nvar) - for j = b.cfJ.sparsity.colptr[i]:(b.cfJ.sparsity.colptr[i + 1] - 1) - cols[j] = i - end - end - return rows, cols - end - - function NLPModels.jac_coord_residual!( - b::SDTSparseADJacobian, - nls::AbstractADNLSModel, - x::AbstractVector, - vals::AbstractVector, - ) - SparseDiffTools.forwarddiff_color_jacobian!(b.cfJ.sparsity, nls.F!, x, b.cfJ) - vals .= nonzeros(b.cfJ.sparsity) - return vals - end - - struct SDTForwardDiffADJprod{T} <: InPlaceADbackend - tmp_in::Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}} - tmp_out::Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}} - end - - function SDTForwardDiffADJprod( - nvar::Integer, - f, - ncon::Integer = 0, - c!::Function = (args...) -> []; - x0::AbstractVector{T} = rand(nvar), - kwargs..., - ) where {T} - tmp_in = Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}}( - undef, - nvar, - ) - tmp_out = Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}}( - undef, - ncon, - ) - return SDTForwardDiffADJprod(tmp_in, tmp_out) - end - - function Jprod!(b::SDTForwardDiffADJprod, Jv, c!, x, v, ::Val) - SparseDiffTools.auto_jacvec!(Jv, c!, x, v, b.tmp_in, b.tmp_out) - return Jv - end - - struct SDTForwardDiffADHvprod{T} <: ADBackend - tmp_in::Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}} - tmp_out::Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}} - end - function SDTForwardDiffADHvprod( - nvar::Integer, - f, - ncon::Integer = 0, - c::Function = (args...) -> []; - x0::AbstractVector{T} = rand(nvar), - kwargs..., - ) where {T} - tmp_in = Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}}( - undef, - nvar, - ) - tmp_out = Vector{SparseDiffTools.Dual{ForwardDiff.Tag{SparseDiffTools.DeivVecTag, T}, T, 1}}( - undef, - nvar, - ) - return SDTForwardDiffADHvprod(tmp_in, tmp_out) - end - - function Hvprod!(b::SDTForwardDiffADHvprod, Hv, x, v, f, args...) - ϕ!(dy, x; f = f) = ForwardDiff.gradient!(dy, f, x) - SparseDiffTools.auto_hesvecgrad!(Hv, (dy, x) -> ϕ!(dy, x), x, v, b.tmp_in, b.tmp_out) - return Hv - end - end -end diff --git a/src/sparse_hessian.jl b/src/sparse_hessian.jl index 383042fe..d72d5dcd 100644 --- a/src/sparse_hessian.jl +++ b/src/sparse_hessian.jl @@ -21,11 +21,11 @@ function SparseADHessian( c!; x0::S = rand(nvar), alg = ColPackColoration(), + detector::AbstractSparsityDetector = TracerSparsityDetector(), kwargs..., ) where {S} T = eltype(S) - Hs = compute_hessian_sparsity(f, nvar, c!, ncon) - H = ncon == 0 ? Hs : Hs[1:nvar, 1:nvar] + H = compute_hessian_sparsity(f, nvar, c!, ncon, detector=detector) colors = sparse_matrix_colors(H, alg) ncolors = maximum(colors) @@ -93,10 +93,10 @@ function SparseReverseADHessian( c!; x0::AbstractVector{T} = rand(nvar), alg = ColPackColoration(), + detector::AbstractSparsityDetector = TracerSparsityDetector(), kwargs..., ) where {T} - Hs = compute_hessian_sparsity(f, nvar, c!, ncon) - H = ncon == 0 ? Hs : Hs[1:nvar, 1:nvar] + H = compute_hessian_sparsity(f, nvar, c!, ncon, detector=detector) colors = sparse_matrix_colors(H, alg) ncolors = maximum(colors) diff --git a/src/sparse_jacobian.jl b/src/sparse_jacobian.jl index 1b12fd2c..fccfad2d 100644 --- a/src/sparse_jacobian.jl +++ b/src/sparse_jacobian.jl @@ -16,10 +16,11 @@ function SparseADJacobian( c!; x0::AbstractVector{T} = rand(nvar), alg = ColPackColoration(), + detector::AbstractSparsityDetector = TracerSparsityDetector(), kwargs..., ) where {T} output = similar(x0, ncon) - J = compute_jacobian_sparsity(c!, output, x0) + J = compute_jacobian_sparsity(c!, output, x0, detector=detector) colors = sparse_matrix_colors(J, alg) ncolors = maximum(colors) diff --git a/src/sparse_sym.jl b/src/sparse_sym.jl deleted file mode 100644 index e90c5514..00000000 --- a/src/sparse_sym.jl +++ /dev/null @@ -1,174 +0,0 @@ -function compute_hessian_sparsity(f, nvar, c!, ncon) - Symbolics.@variables xs[1:nvar] - xsi = Symbolics.scalarize(xs) - fun = f(xsi) - if ncon > 0 - Symbolics.@variables ys[1:ncon] - ysi = Symbolics.scalarize(ys) - cx = similar(ysi) - fun = fun + dot(c!(cx, xsi), ysi) - end - S = Symbolics.hessian_sparsity(fun, ncon == 0 ? xsi : [xsi; ysi]) # , full = false - return S -end - -function compute_jacobian_sparsity(c!, cx, x0) - S = Symbolics.jacobian_sparsity(c!, cx, x0) - return S -end - -## ----- Symbolics Jacobian ----- - -struct SparseSymbolicsADJacobian{T} <: ADBackend - nnzj::Int - rows::Vector{Int} - cols::Vector{Int} - cfJ::T -end - -function SparseSymbolicsADJacobian(nvar, f, ncon, c!; kwargs...) - Symbolics.@variables xs[1:nvar] out[1:ncon] - wi = Symbolics.scalarize(xs) - wo = Symbolics.scalarize(out) - fun = c!(wo, wi) - J = Symbolics.jacobian_sparsity(c!, wo, wi) - rows, cols, _ = findnz(J) - vals = Symbolics.sparsejacobian_vals(fun, wi, rows, cols) - nnzj = length(vals) - # cfJ is a Tuple{Expr, Expr}, cfJ[2] is the in-place function - # that we need to update a vector `vals` with the nonzeros of Jc(x). - cfJ = Symbolics.build_function(vals, wi, expression = Val{false}) - SparseSymbolicsADJacobian(nnzj, rows, cols, cfJ[2]) -end - -function get_nln_nnzj(b::SparseSymbolicsADJacobian, nvar, ncon) - b.nnzj -end - -function NLPModels.jac_structure!( - b::SparseSymbolicsADJacobian, - nlp::ADModel, - rows::AbstractVector{<:Integer}, - cols::AbstractVector{<:Integer}, -) - rows .= b.rows - cols .= b.cols - return rows, cols -end - -function NLPModels.jac_coord!( - b::SparseSymbolicsADJacobian, - nlp::ADModel, - x::AbstractVector, - vals::AbstractVector, -) - @eval $(b.cfJ)($vals, $x) - return vals -end - -function NLPModels.jac_structure_residual!( - b::SparseSymbolicsADJacobian, - nls::AbstractADNLSModel, - rows::AbstractVector{<:Integer}, - cols::AbstractVector{<:Integer}, -) - rows .= b.rows - cols .= b.cols - return rows, cols -end - -function NLPModels.jac_coord_residual!( - b::SparseSymbolicsADJacobian, - nls::AbstractADNLSModel, - x::AbstractVector, - vals::AbstractVector, -) - @eval $(b.cfJ)($vals, $x) - return vals -end - -## ----- Symbolics Hessian ----- - -struct SparseSymbolicsADHessian{T, H} <: ADBackend - nnzh::Int - rows::Vector{Int} - cols::Vector{Int} - y::AbstractVector{T} - cfH::H -end - -function SparseSymbolicsADHessian(nvar, f, ncon, c!; x0::S = rand(nvar), kwargs...) where {S} - Symbolics.@variables xs[1:nvar], μs - xsi = Symbolics.scalarize(xs) - fun = μs * f(xsi) - Symbolics.@variables ys[1:ncon] - ysi = Symbolics.scalarize(ys) - if ncon > 0 - cx = similar(ysi) - fun = fun + dot(c!(cx, xsi), ysi) - end - H = Symbolics.hessian_sparsity(fun, ncon == 0 ? xsi : [xsi; ysi], full = false) - H = ncon == 0 ? H : H[1:nvar, 1:nvar] - rows, cols, _ = findnz(H) - vals = Symbolics.sparsehessian_vals(fun, xsi, rows, cols) - nnzh = length(vals) - # cfH is a Tuple{Expr, Expr}, cfH[2] is the in-place function - # that we need to update a vector `vals` with the nonzeros of ∇²ℓ(x, y, μ). - cfH = Symbolics.build_function(vals, xsi, ysi, μs, expression = Val{false}) - y = fill!(S(undef, ncon), 0) - return SparseSymbolicsADHessian(nnzh, rows, cols, y, cfH[2]) -end - -function get_nln_nnzh(b::SparseSymbolicsADHessian, nvar) - b.nnzh -end - -function NLPModels.hess_structure!( - b::SparseSymbolicsADHessian, - nlp::ADModel, - rows::AbstractVector{<:Integer}, - cols::AbstractVector{<:Integer}, -) - rows .= b.rows - cols .= b.cols - return rows, cols -end - -function NLPModels.hess_coord!( - b::SparseSymbolicsADHessian, - nlp::ADModel, - x::AbstractVector, - y::AbstractVector, - obj_weight::Real, - vals::AbstractVector, -) - @eval $(b.cfH)($vals, $x, $y, $obj_weight) - return vals -end - -function NLPModels.hess_coord!( - b::SparseSymbolicsADHessian, - nlp::ADModel, - x::AbstractVector, - obj_weight::Real, - vals::AbstractVector, -) - b.y .= 0 - @eval $(b.cfH)($vals, $x, $(b.y), $obj_weight) - return vals -end - -function NLPModels.hess_coord!( - b::SparseSymbolicsADHessian, - nlp::ADModel, - x::AbstractVector, - j::Integer, - vals::AbstractVector{T}, -) where {T} - for (w, k) in enumerate(nlp.meta.nln) - b.y[w] = k == j ? 1 : 0 - end - obj_weight = zero(T) - @eval $(b.cfH)($vals, $x, $(b.y), $obj_weight) - return vals -end diff --git a/src/sparsity_pattern.jl b/src/sparsity_pattern.jl new file mode 100644 index 00000000..74051fe0 --- /dev/null +++ b/src/sparsity_pattern.jl @@ -0,0 +1,38 @@ +""" + compute_jacobian_sparsity(c, x0; detector) + compute_jacobian_sparsity(c!, cx, x0; detector) + +Return a sparse boolean matrix that represents the adjacency matrix of the Jacobian of c(x). +""" +function compute_jacobian_sparsity end + +function compute_jacobian_sparsity(c, x0; detector::AbstractSparsityDetector=TracerSparsityDetector()) + S = ADTypes.jacobian_sparsity(c, x0, detector) + return S +end + +function compute_jacobian_sparsity(c!, cx, x0; detector::AbstractSparsityDetector=TracerSparsityDetector()) + S = ADTypes.jacobian_sparsity(c!, cx, x0, detector) + return S +end + +""" + compute_hessian_sparsity(f, nvar, c!, ncon; detector) + +Return a sparse boolean matrix that represents the adjacency matrix of the Hessian of f(x) + λᵀc(x). +""" +function compute_hessian_sparsity(f, nvar, c!, ncon; detector::AbstractSparsityDetector=TracerSparsityDetector()) + function lagrangian(x) + if ncon == 0 + return f(x) + else + cx = zeros(eltype(x), ncon) + y0 = rand(ncon) + return f(x) + dot(c!(cx, x), y0) + end + end + + x0 = rand(nvar) + S = ADTypes.hessian_sparsity(lagrangian, x0, detector) + return S +end diff --git a/test/Project.toml b/test/Project.toml index 0f94a872..2843f676 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,9 +9,6 @@ NLPModelsModifiers = "e01155f1-5c6f-4375-a9d8-616dd036575f" NLPModelsTest = "7998695d-6960-4d3a-85c4-e1bceb8cd856" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" @@ -24,7 +21,4 @@ NLPModels = "0.21" NLPModelsModifiers = "0.7" NLPModelsTest = "0.10" ReverseDiff = "1" -SparseDiffTools = "2.3" -Symbolics = "5.3" -SymbolicUtils = "=1.5.1" Zygote = "0.6" diff --git a/test/runtests.jl b/test/runtests.jl index 3eea51f6..565d85cb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,21 +3,21 @@ using ADNLPModels, ManualNLPModels, NLPModels, NLPModelsModifiers, NLPModelsTest using ADNLPModels: gradient, gradient!, jacobian, hessian, Jprod!, Jtprod!, directional_second_derivative, Hvprod! -@testset "Error without loading package for sparsity pattern" begin - f(x) = sum(x) - c!(cx, x) = begin - cx .= 1 - return x - end - nvar, ncon = 2, 1 +@testset "Test sparsity pattern of Jacobian and Hessian" begin + f(x) = sum(x.^2) + c(x) = x + c!(cx, x) = copyto!(cx, x) + nvar, ncon = 2, 2 x0 = ones(nvar) cx = rand(ncon) - @test_throws ArgumentError ADNLPModels.compute_jacobian_sparsity(c!, cx, x0) - @test_throws ArgumentError ADNLPModels.compute_hessian_sparsity(f, nvar, c!, ncon) + S = ADNLPModels.compute_jacobian_sparsity(c, x0) + @test S == I + S = ADNLPModels.compute_jacobian_sparsity(c!, cx, x0) + @test S == I + S = ADNLPModels.compute_hessian_sparsity(f, nvar, c!, ncon) + @test S == I end -using SparseDiffTools, Symbolics - @testset "Test using a NLPModel instead of AD-backend" begin include("manual.jl") end @@ -43,15 +43,15 @@ push!( ADNLPModels.predefined_backend, :zygote_backend => Dict( :gradient_backend => ADNLPModels.ZygoteADGradient, - :hprod_backend => ADNLPModels.SDTForwardDiffADHvprod, :jprod_backend => ADNLPModels.ZygoteADJprod, :jtprod_backend => ADNLPModels.ZygoteADJtprod, + :hprod_backend => ADNLPModels.ForwardDiffADHvprod, :jacobian_backend => ADNLPModels.ZygoteADJacobian, :hessian_backend => ADNLPModels.ZygoteADHessian, :ghjvprod_backend => ADNLPModels.ForwardDiffADGHjvprod, - :hprod_residual_backend => ADNLPModels.SDTForwardDiffADHvprod, :jprod_residual_backend => ADNLPModels.ZygoteADJprod, :jtprod_residual_backend => ADNLPModels.ZygoteADJtprod, + :hprod_residual_backend => ADNLPModels.ForwardDiffADHvprod, :jacobian_residual_backend => ADNLPModels.ZygoteADJacobian, :hessian_residual_backend => ADNLPModels.ZygoteADHessian, ), diff --git a/test/script_OP.jl b/test/script_OP.jl index 2ecad87c..3b8cd908 100644 --- a/test/script_OP.jl +++ b/test/script_OP.jl @@ -2,8 +2,6 @@ # optional deps # using Enzyme -# using SparseDiffTools -using Symbolics # AD deps using ForwardDiff, ReverseDiff diff --git a/test/sparse_hessian.jl b/test/sparse_hessian.jl index ce35c886..a7f277aa 100644 --- a/test/sparse_hessian.jl +++ b/test/sparse_hessian.jl @@ -1,9 +1,6 @@ list_sparse_hess_backend = ( (ADNLPModels.SparseADHessian, Dict()), - (ADNLPModels.SparseADHessian, Dict(:alg => SparseDiffTools.GreedyD1Color())), - (ADNLPModels.SparseADHessian, Dict(:alg => SparseDiffTools.AcyclicColoring())), (ADNLPModels.ForwardDiffADHessian, Dict()), - (ADNLPModels.SparseSymbolicsADHessian, Dict()), ) dt = (Float32, Float64) diff --git a/test/sparse_jacobian.jl b/test/sparse_jacobian.jl index 1d3da99d..20b4d05e 100644 --- a/test/sparse_jacobian.jl +++ b/test/sparse_jacobian.jl @@ -1,10 +1,6 @@ list_sparse_jac_backend = ( (ADNLPModels.SparseADJacobian, Dict()), # default - (ADNLPModels.SparseADJacobian, Dict(:alg => SparseDiffTools.GreedyD1Color())), - (ADNLPModels.SparseADJacobian, Dict(:alg => SparseDiffTools.AcyclicColoring())), (ADNLPModels.ForwardDiffADJacobian, Dict()), - (ADNLPModels.SparseSymbolicsADJacobian, Dict()), - (ADNLPModels.SDTSparseADJacobian, Dict()), ) dt = (Float32, Float64) @testset "Basic Jacobian derivative with backend=$(backend) and T=$(T)" for T in dt, diff --git a/test/sparse_jacobian_nls.jl b/test/sparse_jacobian_nls.jl index f0b94607..23df0dc3 100644 --- a/test/sparse_jacobian_nls.jl +++ b/test/sparse_jacobian_nls.jl @@ -1,10 +1,6 @@ list_sparse_jac_backend = ( (ADNLPModels.SparseADJacobian, Dict()), # default - (ADNLPModels.SparseADJacobian, Dict(:alg => SparseDiffTools.GreedyD1Color())), - (ADNLPModels.SparseADJacobian, Dict(:alg => SparseDiffTools.AcyclicColoring())), (ADNLPModels.ForwardDiffADJacobian, Dict()), - (ADNLPModels.SparseSymbolicsADJacobian, Dict()), - (ADNLPModels.SDTSparseADJacobian, Dict()), ) dt = (Float32, Float64) @testset "Basic Jacobian of residual derivative with backend=$(backend) and T=$(T)" for T in dt,