From ae341997c8292cf80ac529d83b4c959b8d17b416 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Wed, 29 Jan 2025 11:36:29 -0600 Subject: [PATCH 1/8] Enzyme support --- .buildkite/pipeline.yml | 24 +++---- src/ADNLPModels.jl | 2 + src/enzyme.jl | 138 +++++++++++++++++++++----------------- test/Project.toml | 2 + test/enzyme.jl | 72 ++++++++++---------- test/nlp/nlpmodelstest.jl | 2 +- test/nls/nlpmodelstest.jl | 2 +- test/sparse_hessian.jl | 25 +++++-- 8 files changed, 152 insertions(+), 115 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 219a812f..33d28bb9 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -11,18 +11,18 @@ steps: julia --color=yes --project=test -e 'include("test/gpu.jl")' timeout_in_minutes: 30 - # - label: "CPUs -- Enzyme.jl" - # plugins: - # - JuliaCI/julia#v1: - # version: "1.10" - # agents: - # queue: "juliaecosystem" - # os: "linux" - # arch: "x86_64" - # command: | - # julia --color=yes --project=test -e 'using Pkg; Pkg.add("Enzyme"); Pkg.develop(path="."); Pkg.instantiate()' - # julia --color=yes --project=test -e 'include("test/enzyme.jl")' - # timeout_in_minutes: 30 + - label: "CPUs -- Enzyme.jl" + plugins: + - JuliaCI/julia#v1: + version: "1.10" + agents: + queue: "juliaecosystem" + os: "linux" + arch: "x86_64" + command: | + julia --color=yes --project=test -e 'using Pkg; Pkg.add("Enzyme"); Pkg.develop(path="."); Pkg.instantiate()' + julia --color=yes --project=test -e 'include("test/enzyme.jl")' + timeout_in_minutes: 30 - label: "CPUs -- Zygote.jl" plugins: diff --git a/src/ADNLPModels.jl b/src/ADNLPModels.jl index a50d1005..58ce105a 100644 --- a/src/ADNLPModels.jl +++ b/src/ADNLPModels.jl @@ -205,6 +205,8 @@ get_F(::AbstractNLPModel, ::AbstractNLPModel) = () -> () Return the lagrangian function `ℓ(x) = obj_weight * f(x) + c(x)ᵀy`. """ function get_lag(nlp::AbstractADNLPModel, b::ADBackend, obj_weight::Real) + # println("Check") + # return x -> obj_weight * nlp.f(x) return ℓ(x; obj_weight = obj_weight) = obj_weight * nlp.f(x) end diff --git a/src/enzyme.jl b/src/enzyme.jl index 2469fb1a..7876d40e 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -23,9 +23,10 @@ function EnzymeReverseADJacobian( return EnzymeReverseADJacobian() end -struct EnzymeReverseADHessian{T} <: ADBackend +struct EnzymeReverseADHessian{T,F} <: ADBackend seed::Vector{T} Hv::Vector{T} + f::F end function EnzymeReverseADHessian( @@ -41,11 +42,12 @@ function EnzymeReverseADHessian( seed = zeros(T, nvar) Hv = zeros(T, nvar) - return EnzymeReverseADHessian(seed, Hv) + return EnzymeReverseADHessian(seed, Hv, f) end -struct EnzymeReverseADHvprod{T} <: InPlaceADbackend +struct EnzymeReverseADHvprod{T,F} <: InPlaceADbackend grad::Vector{T} + f::F end function EnzymeReverseADHvprod( @@ -57,7 +59,7 @@ function EnzymeReverseADHvprod( kwargs..., ) where {T} grad = zeros(T, nvar) - return EnzymeReverseADHvprod(grad) + return EnzymeReverseADHvprod(grad,f) end struct EnzymeReverseADJprod{T} <: InPlaceADbackend @@ -72,7 +74,7 @@ function EnzymeReverseADJprod( x0::AbstractVector{T} = rand(nvar), kwargs..., ) where {T} - cx = zeros(T, nvar) + cx = zeros(T, ncon) return EnzymeReverseADJprod(cx) end @@ -88,7 +90,7 @@ function EnzymeReverseADJtprod( x0::AbstractVector{T} = rand(nvar), kwargs..., ) where {T} - cx = zeros(T, nvar) + cx = zeros(T, ncon) return EnzymeReverseADJtprod(cx) end @@ -169,7 +171,7 @@ function SparseEnzymeADJacobian( ) end -struct SparseEnzymeADHessian{R, C, S, L} <: ADBackend +struct SparseEnzymeADHessian{R, C, S, L, F} <: ADBackend nvar::Int rowval::Vector{Int} colptr::Vector{Int} @@ -182,6 +184,7 @@ struct SparseEnzymeADHessian{R, C, S, L} <: ADBackend y::Vector{R} grad::Vector{R} cx::Vector{R} + f::F ℓ::L end @@ -247,10 +250,14 @@ function SparseEnzymeADHessian( grad = similar(x0) function ℓ(x, y, obj_weight, cx) - res = obj_weight * f(x) if ncon != 0 c!(cx, x) - res += sum(cx[i] * y[i] for i = 1:ncon) + end + res = obj_weight * f(x) + if ncon != 0 + for i = 1:ncon + res += cx[i] * y[i] + end end return res end @@ -270,19 +277,73 @@ function SparseEnzymeADHessian( y, grad, cx, + f, ℓ, ) end @init begin @require Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" begin + function _gradient!(dx, f, x) + Enzyme.make_zero!(dx) + Enzyme.autodiff( + Enzyme.set_runtime_activity(Enzyme.Reverse), + f, + Enzyme.Active, + Enzyme.Duplicated(x, dx), + ) + return nothing + end + + function _hvp!(res, f, x, v) + Enzyme.autodiff( + Enzyme.set_runtime_activity(Enzyme.Forward), + _gradient!, + res, + Enzyme.Const(f), + Enzyme.Duplicated(x, v), + ) + return nothing + end + + function _gradient!(dx, ℓ, x, y, obj_weight, cx) + Enzyme.make_zero!(dx) + dcx = Enzyme.make_zero(cx) + Enzyme.autodiff( + Enzyme.set_runtime_activity(Enzyme.Reverse), + ℓ, + Enzyme.Active, + Enzyme.Duplicated(x, dx), + Enzyme.Const(y), + Enzyme.Const(obj_weight), + Enzyme.Duplicated(cx, dcx), + ) + return nothing + end + + function _hvp!(res, ℓ, x, v, y, obj_weight, cx) + dcx = Enzyme.make_zero(cx) + Enzyme.autodiff( + Enzyme.set_runtime_activity(Enzyme.Forward), + _gradient!, + res, + Enzyme.Const(ℓ), + Enzyme.Duplicated(x, v), + Enzyme.Const(y), + Enzyme.Const(obj_weight), + Enzyme.Duplicated(cx, dcx), + ) + return nothing + end + function ADNLPModels.gradient(::EnzymeReverseADGradient, f, x) g = similar(x) - Enzyme.gradient!(Enzyme.Reverse, g, Enzyme.Const(f), x) + Enzyme.autodiff(Enzyme.set_runtime_activity(Enzyme.Reverse), Enzyme.Const(f), Enzyme.Active, Enzyme.Duplicated(x, g)) return g end function ADNLPModels.gradient!(::EnzymeReverseADGradient, g, f, x) + Enzyme.make_zero!(g) Enzyme.autodiff(Enzyme.Reverse, Enzyme.Const(f), Enzyme.Active, Enzyme.Duplicated(x, g)) return g end @@ -296,7 +357,8 @@ end fill!(b.seed, zero(T)) for i = 1:n b.seed[i] = one(T) - Enzyme.hvp!(b.Hv, Enzyme.Const(f), x, b.seed) + grad = Enzyme.make_zero(x) + _hvp!(Enzyme.DuplicatedNoNeed(grad, b.Hv), f, x, b.seed) view(hess, :, i) .= b.Hv b.seed[i] = zero(T) end @@ -314,11 +376,12 @@ end end function Jtprod!(b::EnzymeReverseADJtprod, Jtv, c!, x, v, ::Val) + Enzyme.make_zero!(Jtv) Enzyme.autodiff( Enzyme.Reverse, Enzyme.Const(c!), - Enzyme.Duplicated(b.cx, Jtv), - Enzyme.Duplicated(x, v), + Enzyme.Duplicated(b.cx, v), + Enzyme.Duplicated(x, Jtv), ) return Jtv end @@ -333,15 +396,7 @@ end y, obj_weight::Real = one(eltype(x)), ) - Enzyme.autodiff( - Enzyme.Forward, - Enzyme.Const(Enzyme.gradient!), - Enzyme.Const(Enzyme.Reverse), - Enzyme.DuplicatedNoNeed(b.grad, Hv), - Enzyme.Const(ℓ), - Enzyme.Duplicated(x, v), - Enzyme.Const(y), - ) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), ℓ, x, v) return Hv end @@ -354,14 +409,7 @@ end ::Val{:obj}, obj_weight::Real = one(eltype(x)), ) - Enzyme.autodiff( - Enzyme.Forward, - Enzyme.Const(Enzyme.gradient!), - Enzyme.Const(Enzyme.Reverse), - Enzyme.DuplicatedNoNeed(b.grad, Hv), - Enzyme.Const(f), - Enzyme.Duplicated(x, v), - ) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), f, x, v) return Hv end @@ -491,36 +539,6 @@ end b.v[col] = 1 end - function _gradient!(dx, ℓ, x, y, obj_weight, cx) - Enzyme.make_zero!(dx) - dcx = Enzyme.make_zero(cx) - res = Enzyme.autodiff( - Enzyme.Reverse, - ℓ, - Enzyme.Active, - Enzyme.Duplicated(x, dx), - Enzyme.Const(y), - Enzyme.Const(obj_weight), - Enzyme.Duplicated(cx, dcx), - ) - return nothing - end - - function _hvp!(res, ℓ, x, v, y, obj_weight, cx) - dcx = Enzyme.make_zero(cx) - Enzyme.autodiff( - Enzyme.Forward, - _gradient!, - res, - Enzyme.Const(ℓ), - Enzyme.Duplicated(x, v), - Enzyme.Const(y), - Enzyme.Const(obj_weight), - Enzyme.Duplicated(cx, dcx), - ) - return nothing - end - _hvp!( Enzyme.DuplicatedNoNeed(b.grad, b.compressed_hessian_icol), b.ℓ, diff --git a/test/Project.toml b/test/Project.toml index e6ae782e..502b8f56 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,6 @@ [deps] +ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" +Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" ManualNLPModels = "30dfa513-9b2f-4fb3-9796-781eabac1617" diff --git a/test/enzyme.jl b/test/enzyme.jl index a844166e..9909c9af 100644 --- a/test/enzyme.jl +++ b/test/enzyme.jl @@ -9,12 +9,12 @@ import Enzyme EnzymeReverseAD() = ADNLPModels.ADModelBackend( ADNLPModels.EnzymeReverseADGradient(), - ADNLPModels.EnzymeReverseADHvprod(zeros(1)), + ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), ADNLPModels.EnzymeReverseADJprod(zeros(1)), ADNLPModels.EnzymeReverseADJtprod(zeros(1)), ADNLPModels.EnzymeReverseADJacobian(), - ADNLPModels.EnzymeReverseADHessian(zeros(1), zeros(1)), - ADNLPModels.EnzymeReverseADHvprod(zeros(1)), + ADNLPModels.EnzymeReverseADHessian(zeros(1), zeros(1), identity), + ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), @@ -28,28 +28,8 @@ function mysum!(y, x) end function test_autodiff_backend_error() - @testset "Error without loading package - $backend" for backend in [:EnzymeReverseAD] + @testset "Enzyme basic operations - $backend" for backend in [:EnzymeReverseAD] adbackend = eval(backend)() - # @test_throws ArgumentError gradient(adbackend.gradient_backend, sum, [1.0]) - # @test_throws ArgumentError gradient!(adbackend.gradient_backend, [1.0], sum, [1.0]) - # @test_throws ArgumentError jacobian(adbackend.jacobian_backend, identity, [1.0]) - # @test_throws ArgumentError hessian(adbackend.hessian_backend, sum, [1.0]) - # @test_throws ArgumentError Jprod!( - # adbackend.jprod_backend, - # [1.0], - # [1.0], - # identity, - # [1.0], - # Val(:c), - # ) - # @test_throws ArgumentError Jtprod!( - # adbackend.jtprod_backend, - # [1.0], - # [1.0], - # identity, - # [1.0], - # Val(:c), - # ) gradient(adbackend.gradient_backend, sum, [1.0]) gradient!(adbackend.gradient_backend, [1.0], sum, [1.0]) jacobian(adbackend.jacobian_backend, sum, [1.0]) @@ -61,6 +41,26 @@ end test_autodiff_backend_error() +push!( + ADNLPModels.predefined_backend, + :enzyme_backend => Dict( + :gradient_backend => ADNLPModels.EnzymeReverseADGradient, + :jprod_backend => ADNLPModels.EnzymeReverseADJprod, + :jtprod_backend => ADNLPModels.EnzymeReverseADJtprod, + :hprod_backend => ADNLPModels.EnzymeReverseADHvprod, + :jacobian_backend => ADNLPModels.EnzymeReverseADJacobian, + :hessian_backend => ADNLPModels.EnzymeReverseADHessian, + :ghjvprod_backend => ADNLPModels.ForwardDiffADGHjvprod, + :jprod_residual_backend => ADNLPModels.EnzymeReverseADJprod, + :jtprod_residual_backend => ADNLPModels.EnzymeReverseADJtprod, + :hprod_residual_backend => ADNLPModels.EnzymeReverseADHvprod, + :jacobian_residual_backend => ADNLPModels.EnzymeReverseADJacobian, + :hessian_residual_backend => ADNLPModels.EnzymeReverseADHessian, + ), +) + +const test_enzyme = true + include("sparse_jacobian.jl") include("sparse_jacobian_nls.jl") include("sparse_hessian.jl") @@ -78,18 +78,20 @@ end list_sparse_hess_backend = ( ( ADNLPModels.SparseEnzymeADHessian, + "star coloring", Dict(:coloring_algorithm => GreedyColoringAlgorithm{:direct}()), ), ( ADNLPModels.SparseEnzymeADHessian, + "acyclic coloring", Dict(:coloring_algorithm => GreedyColoringAlgorithm{:substitution}()), ), ) @testset "Sparse Hessian" begin - for (backend, kw) in list_sparse_hess_backend - sparse_hessian(backend, kw) - sparse_hessian_nls(backend, kw) + for (backend, info, kw) in list_sparse_hess_backend + sparse_hessian(backend, info, kw) + sparse_hessian_nls(backend, info, kw) end end @@ -106,18 +108,18 @@ include("nls/basic.jl") include("nlp/nlpmodelstest.jl") include("nls/nlpmodelstest.jl") -@testset "Basic NLP tests using $backend " for backend in (:enzyme,) - test_autodiff_model("$backend", backend = backend) +@testset "Basic NLP tests using enzyme_backend" begin + test_autodiff_model("enzyme_backend", backend = :enzyme_backend) end -@testset "Checking NLPModelsTest (NLP) tests with $backend" for backend in (:enzyme,) - nlp_nlpmodelstest(backend) +@testset "Basic NLS tests using enzyme_backend" begin + autodiff_nls_test("enzyme_backend", backend = :enzyme_backend) end -@testset "Basic NLS tests using $backend " for backend in (:enzyme,) - autodiff_nls_test("$backend", backend = backend) +@testset "Checking NLPModelsTest (NLP) tests with enzyme_backend" begin + nlp_nlpmodelstest(:enzyme_backend) end -@testset "Checking NLPModelsTest (NLS) tests with $backend" for backend in (:enzyme,) - nls_nlpmodelstest(backend) +@testset "Checking NLPModelsTest (NLS) tests with enzyme_backend" begin + nls_nlpmodelstest(:enzyme_backend) end diff --git a/test/nlp/nlpmodelstest.jl b/test/nlp/nlpmodelstest.jl index 6be6611a..9f260bc1 100644 --- a/test/nlp/nlpmodelstest.jl +++ b/test/nlp/nlpmodelstest.jl @@ -18,7 +18,7 @@ function nlp_nlpmodelstest(backend) @testset "Check multiple precision" begin multiple_precision_nlp(nlp_from_T, exclude = [], linear_api = true) end - if backend != :enzyme + if backend != :enzyme && backend != :enzyme_backend @testset "Check view subarray" begin view_subarray_nlp(nlp_ad, exclude = []) end diff --git a/test/nls/nlpmodelstest.jl b/test/nls/nlpmodelstest.jl index f6b29882..07217013 100644 --- a/test/nls/nlpmodelstest.jl +++ b/test/nls/nlpmodelstest.jl @@ -42,7 +42,7 @@ function nls_nlpmodelstest(backend) @testset "Check multiple precision" begin multiple_precision_nls(nls_from_T, exclude = exclude, linear_api = true) end - if backend != :enzyme + if backend != :enzyme && backend != :enzyme_backend @testset "Check view subarray" begin view_subarray_nls.(nlss, exclude = exclude) end diff --git a/test/sparse_hessian.jl b/test/sparse_hessian.jl index 98c0cf72..4ab16d82 100644 --- a/test/sparse_hessian.jl +++ b/test/sparse_hessian.jl @@ -1,6 +1,5 @@ function sparse_hessian(backend, info, kw) @testset "Basic Hessian derivative with backend=$(backend) -- $info -- T=$(T)" for T in ( - Float32, Float64, ) c!(cx, x) = begin @@ -34,6 +33,7 @@ function sparse_hessian(backend, info, kw) # Test also the implementation of the backends b = nlp.adbackend.hessian_backend + @show b obj_weight = 0.5 @test nlp.meta.nnzh == ADNLPModels.get_nln_nnzh(b, nvar) ADNLPModels.hess_structure!(b, nlp, rows, cols) @@ -65,16 +65,29 @@ function sparse_hessian(backend, info, kw) ) @test nlp.adbackend.hessian_backend isa ADNLPModels.EmptyADbackend - n = 4 - x = ones(T, 4) + # n = 4 + x0 = ones(T, 4) + function f(x) + n = length(x) + sum(100 * (x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 for i = 1:(n - 1)) + # res = 0 + # n = length(x) + # for i in 1:(n-1) + # res += 100 * (x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 + # end + # res + end nlp = ADNLPModel( - x -> sum(100 * (x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 for i = 1:(n - 1)), - x, + # x -> sum(100 * (x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 for i = 1:(n - 1)), + # x -> sum(100 * (x[i + 1] - x[i]^2)^2 + (x[i] - 1)^2 for i = 1:3), + # x -> 100 * (x[2] - x[1]^2)^2 + (x[1] - 1)^2, + f, + x0, hessian_backend = backend, name = "Extended Rosenbrock"; kw..., ) - @test hess(nlp, x) == T[802 -400 0 0; -400 1002 -400 0; 0 -400 1002 -400; 0 0 -400 200] + @test hess(nlp, x0) == T[802 -400 0 0; -400 1002 -400 0; 0 -400 1002 -400; 0 0 -400 200] x = ones(T, 2) nlp = ADNLPModel(x -> x[1]^2 + x[1] * x[2], x, hessian_backend = backend; kw...) From 3e3acc0a6278212103415b0b78de91feca2db7b2 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 11:28:21 -0600 Subject: [PATCH 2/8] Resolve SubArray issue with buffer --- src/enzyme.jl | 82 +++++++++++++++++++++++++++------------ src/predefined_backend.jl | 4 +- test/enzyme.jl | 16 ++++---- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/src/enzyme.jl b/src/enzyme.jl index 7876d40e..faa2ff76 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -62,8 +62,11 @@ function EnzymeReverseADHvprod( return EnzymeReverseADHvprod(grad,f) end -struct EnzymeReverseADJprod{T} <: InPlaceADbackend - cx::Vector{T} +struct EnzymeReverseADJprod{V} <: InPlaceADbackend + cx::V # length ncon, primal output buffer + xbuf::V # length nvar, input x buffer + vbuf::V # length nvar, input v buffer (tangent direction) + jvbuf::V # length ncon, output Jv buffer end function EnzymeReverseADJprod( @@ -74,12 +77,18 @@ function EnzymeReverseADJprod( x0::AbstractVector{T} = rand(nvar), kwargs..., ) where {T} - cx = zeros(T, ncon) - return EnzymeReverseADJprod(cx) + cx = fill!(similar(x0, ncon), zero(T)) + xbuf = similar(x0) + vbuf = similar(x0) + jvbuf = fill!(similar(x0, ncon), zero(T)) + return EnzymeReverseADJprod(cx, xbuf, vbuf, jvbuf) end -struct EnzymeReverseADJtprod{T} <: InPlaceADbackend - cx::Vector{T} +struct EnzymeReverseADJtprod{V} <: InPlaceADbackend + cx::V # length ncon, primal output buffer + xbuf::V # length nvar, input x buffer + vbuf::V # length ncon, cotangent seed buffer + jtvbuf::V # length nvar, output Jtv buffer end function EnzymeReverseADJtprod( @@ -90,11 +99,14 @@ function EnzymeReverseADJtprod( x0::AbstractVector{T} = rand(nvar), kwargs..., ) where {T} - cx = zeros(T, ncon) - return EnzymeReverseADJtprod(cx) + cx = fill!(similar(x0, ncon), zero(T)) + xbuf = similar(x0) + vbuf = fill!(similar(x0, ncon), zero(T)) + jtvbuf = similar(x0) + return EnzymeReverseADJtprod(cx, xbuf, vbuf, jtvbuf) end -struct SparseEnzymeADJacobian{R, C, S} <: ADBackend +struct SparseEnzymeADJacobian{R, C, S, V} <: ADBackend nvar::Int ncon::Int rowval::Vector{Int} @@ -102,8 +114,9 @@ struct SparseEnzymeADJacobian{R, C, S} <: ADBackend nzval::Vector{R} result_coloring::C compressed_jacobian::S - v::Vector{R} - cx::Vector{R} + v::V + cx::V + xbuf::V end function SparseEnzymeADJacobian( @@ -154,7 +167,8 @@ function SparseEnzymeADJacobian( timer = @elapsed begin v = similar(x0) - cx = zeros(T, ncon) + cx = fill!(similar(x0, ncon), zero(T)) + xbuf = similar(x0) end show_time && println(" • Allocation of the AD buffers for the sparse Jacobian: $timer seconds.") @@ -168,24 +182,26 @@ function SparseEnzymeADJacobian( compressed_jacobian, v, cx, + xbuf, ) end -struct SparseEnzymeADHessian{R, C, S, L, F} <: ADBackend +struct SparseEnzymeADHessian{R, C, S, L, F, V} <: ADBackend nvar::Int rowval::Vector{Int} colptr::Vector{Int} nzval::Vector{R} result_coloring::C coloring_mode::Symbol - compressed_hessian_icol::Vector{R} + compressed_hessian_icol::V compressed_hessian::S - v::Vector{R} - y::Vector{R} - grad::Vector{R} - cx::Vector{R} + v::V + y::V + grad::V + cx::V f::F ℓ::L + xbuf::V end function SparseEnzymeADHessian( @@ -248,6 +264,7 @@ function SparseEnzymeADHessian( y = similar(x0, ncon) cx = similar(x0, ncon) grad = similar(x0) + xbuf = similar(x0) function ℓ(x, y, obj_weight, cx) if ncon != 0 @@ -279,6 +296,7 @@ function SparseEnzymeADHessian( cx, f, ℓ, + xbuf, ) end @@ -366,23 +384,29 @@ end end function Jprod!(b::EnzymeReverseADJprod, Jv, c!, x, v, ::Val) + copyto!(b.xbuf, x) + copyto!(b.vbuf, v) Enzyme.autodiff( Enzyme.Forward, Enzyme.Const(c!), - Enzyme.Duplicated(b.cx, Jv), - Enzyme.Duplicated(x, v), + Enzyme.Duplicated(b.cx, b.jvbuf), + Enzyme.Duplicated(b.xbuf, b.vbuf), ) + copyto!(Jv, b.jvbuf) return Jv end function Jtprod!(b::EnzymeReverseADJtprod, Jtv, c!, x, v, ::Val) - Enzyme.make_zero!(Jtv) + copyto!(b.xbuf, x) + copyto!(b.vbuf, v) + Enzyme.make_zero!(b.jtvbuf) Enzyme.autodiff( Enzyme.Reverse, Enzyme.Const(c!), - Enzyme.Duplicated(b.cx, v), - Enzyme.Duplicated(x, Jtv), + Enzyme.Duplicated(b.cx, b.vbuf), + Enzyme.Duplicated(b.xbuf, b.jtvbuf), ) + copyto!(Jtv, b.jtvbuf) return Jtv end @@ -442,6 +466,10 @@ end # SparseMatrixColorings.jl requires a SparseMatrixCSC for the decompression A = SparseMatrixCSC(b.ncon, b.nvar, b.colptr, b.rowval, b.nzval) + # Enzyme.Duplicated requires primal and shadow to have the same type. + # Copy x into a pre-allocated buffer to ensure type match with b.v. + copyto!(b.xbuf, x) + groups = column_groups(b.result_coloring) for (icol, cols) in enumerate(groups) # Update the seed @@ -456,7 +484,7 @@ end Enzyme.Forward, Enzyme.Const(c!), Enzyme.Duplicated(b.cx, b.compressed_jacobian), - Enzyme.Duplicated(x, b.v), + Enzyme.Duplicated(b.xbuf, b.v), ) # Update the columns of the Jacobian that have the color `icol` @@ -531,6 +559,10 @@ end # SparseMatrixColorings.jl requires a SparseMatrixCSC for the decompression A = SparseMatrixCSC(b.nvar, b.nvar, b.colptr, b.rowval, b.nzval) + # Enzyme.Duplicated requires primal and shadow to have the same type. + # Copy x into a pre-allocated buffer to ensure type match with b.v. + copyto!(b.xbuf, x) + groups = column_groups(b.result_coloring) for (icol, cols) in enumerate(groups) # Update the seed @@ -542,7 +574,7 @@ end _hvp!( Enzyme.DuplicatedNoNeed(b.grad, b.compressed_hessian_icol), b.ℓ, - x, + b.xbuf, b.v, y, obj_weight, diff --git a/src/predefined_backend.jl b/src/predefined_backend.jl index 463e8c59..993e3d3a 100644 --- a/src/predefined_backend.jl +++ b/src/predefined_backend.jl @@ -47,13 +47,13 @@ enzyme_backend = Dict( :gradient_backend => EnzymeReverseADGradient, :jprod_backend => EnzymeReverseADJprod, :jtprod_backend => EnzymeReverseADJtprod, - :hprod_backend => EnzymeReverseADHvprod, + :hprod_backend => ForwardDiffADHvprod, :jacobian_backend => SparseEnzymeADJacobian, :hessian_backend => SparseEnzymeADHessian, :ghjvprod_backend => ForwardDiffADGHjvprod, :jprod_residual_backend => EnzymeReverseADJprod, :jtprod_residual_backend => EnzymeReverseADJtprod, - :hprod_residual_backend => EnzymeReverseADHvprod, + :hprod_residual_backend => ForwardDiffADHvprod, :jacobian_residual_backend => SparseEnzymeADJacobian, :hessian_residual_backend => SparseEnzymeADHessian, ) diff --git a/test/enzyme.jl b/test/enzyme.jl index 9909c9af..2b3117fe 100644 --- a/test/enzyme.jl +++ b/test/enzyme.jl @@ -10,8 +10,8 @@ import Enzyme EnzymeReverseAD() = ADNLPModels.ADModelBackend( ADNLPModels.EnzymeReverseADGradient(), ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), - ADNLPModels.EnzymeReverseADJprod(zeros(1)), - ADNLPModels.EnzymeReverseADJtprod(zeros(1)), + ADNLPModels.EnzymeReverseADJprod(zeros(1), zeros(1), zeros(1), zeros(1)), + ADNLPModels.EnzymeReverseADJtprod(zeros(1), zeros(1), zeros(1), zeros(1)), ADNLPModels.EnzymeReverseADJacobian(), ADNLPModels.EnzymeReverseADHessian(zeros(1), zeros(1), identity), ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), @@ -47,15 +47,15 @@ push!( :gradient_backend => ADNLPModels.EnzymeReverseADGradient, :jprod_backend => ADNLPModels.EnzymeReverseADJprod, :jtprod_backend => ADNLPModels.EnzymeReverseADJtprod, - :hprod_backend => ADNLPModels.EnzymeReverseADHvprod, - :jacobian_backend => ADNLPModels.EnzymeReverseADJacobian, - :hessian_backend => ADNLPModels.EnzymeReverseADHessian, + :hprod_backend => ADNLPModels.ForwardDiffADHvprod, + :jacobian_backend => ADNLPModels.SparseEnzymeADJacobian, + :hessian_backend => ADNLPModels.SparseEnzymeADHessian, :ghjvprod_backend => ADNLPModels.ForwardDiffADGHjvprod, :jprod_residual_backend => ADNLPModels.EnzymeReverseADJprod, :jtprod_residual_backend => ADNLPModels.EnzymeReverseADJtprod, - :hprod_residual_backend => ADNLPModels.EnzymeReverseADHvprod, - :jacobian_residual_backend => ADNLPModels.EnzymeReverseADJacobian, - :hessian_residual_backend => ADNLPModels.EnzymeReverseADHessian, + :hprod_residual_backend => ADNLPModels.ForwardDiffADHvprod, + :jacobian_residual_backend => ADNLPModels.SparseEnzymeADJacobian, + :hessian_residual_backend => ADNLPModels.SparseEnzymeADHessian, ), ) From 8a8d0cf8c14244d37099b8a448e8545a99c723f9 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 11:51:03 -0600 Subject: [PATCH 3/8] jtprod fix --- src/enzyme.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/enzyme.jl b/src/enzyme.jl index faa2ff76..3831568d 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -396,12 +396,21 @@ end return Jv end + # Wrapper that calls c!(y, x) but returns nothing. + # Enzyme reverse mode requires functions to return nothing (not their output array), + # otherwise it errors with "Duplicated Returns not yet handled". + function _void_c!(c!, y, x) + c!(y, x) + return nothing + end + function Jtprod!(b::EnzymeReverseADJtprod, Jtv, c!, x, v, ::Val) copyto!(b.xbuf, x) copyto!(b.vbuf, v) Enzyme.make_zero!(b.jtvbuf) Enzyme.autodiff( Enzyme.Reverse, + Enzyme.Const(_void_c!), Enzyme.Const(c!), Enzyme.Duplicated(b.cx, b.vbuf), Enzyme.Duplicated(b.xbuf, b.jtvbuf), From f049d4e6ce3002c08e307e0eff18f3fbe0f6aea4 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 11:56:46 -0600 Subject: [PATCH 4/8] Fix Hvprod --- src/enzyme.jl | 52 +++++++++++++++++++++++++++++++++------ src/predefined_backend.jl | 4 +-- test/enzyme.jl | 11 ++++++--- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/enzyme.jl b/src/enzyme.jl index 3831568d..d1a472c4 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -45,9 +45,14 @@ function EnzymeReverseADHessian( return EnzymeReverseADHessian(seed, Hv, f) end -struct EnzymeReverseADHvprod{T,F} <: InPlaceADbackend - grad::Vector{T} +struct EnzymeReverseADHvprod{V, F, C, L} <: InPlaceADbackend + grad::V + xbuf::V + cx::V f::F + c!::C + ℓ::L + ncon::Int end function EnzymeReverseADHvprod( @@ -58,8 +63,24 @@ function EnzymeReverseADHvprod( x0::AbstractVector{T} = rand(nvar), kwargs..., ) where {T} - grad = zeros(T, nvar) - return EnzymeReverseADHvprod(grad,f) + grad = fill!(similar(x0), zero(T)) + xbuf = similar(x0) + cx = fill!(similar(x0, ncon), zero(T)) + + function ℓ(x, y, obj_weight, cx) + if ncon != 0 + c!(cx, x) + end + res = obj_weight * f(x) + if ncon != 0 + for i = 1:ncon + res += cx[i] * y[i] + end + end + return res + end + + return EnzymeReverseADHvprod(grad, xbuf, cx, f, c!, ℓ, ncon) end struct EnzymeReverseADJprod{V} <: InPlaceADbackend @@ -424,12 +445,13 @@ end Hv, x, v, - ℓ, + ℓ_unused, ::Val{:lag}, y, obj_weight::Real = one(eltype(x)), ) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), ℓ, x, v) + copyto!(b.xbuf, x) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.ℓ, b.xbuf, v, y, obj_weight, b.cx) return Hv end @@ -438,11 +460,25 @@ end Hv, x, v, - f, + f_unused, ::Val{:obj}, obj_weight::Real = one(eltype(x)), ) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), f, x, v) + copyto!(b.xbuf, x) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.ℓ, b.xbuf, v, Float64[], obj_weight, b.cx) + return Hv + end + + function Hvprod!( + b::EnzymeReverseADHvprod, + Hv, + x, + v, + ci, + ::Val{:ci}, + ) + copyto!(b.xbuf, x) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), ci, b.xbuf, v) return Hv end diff --git a/src/predefined_backend.jl b/src/predefined_backend.jl index 993e3d3a..463e8c59 100644 --- a/src/predefined_backend.jl +++ b/src/predefined_backend.jl @@ -47,13 +47,13 @@ enzyme_backend = Dict( :gradient_backend => EnzymeReverseADGradient, :jprod_backend => EnzymeReverseADJprod, :jtprod_backend => EnzymeReverseADJtprod, - :hprod_backend => ForwardDiffADHvprod, + :hprod_backend => EnzymeReverseADHvprod, :jacobian_backend => SparseEnzymeADJacobian, :hessian_backend => SparseEnzymeADHessian, :ghjvprod_backend => ForwardDiffADGHjvprod, :jprod_residual_backend => EnzymeReverseADJprod, :jtprod_residual_backend => EnzymeReverseADJtprod, - :hprod_residual_backend => ForwardDiffADHvprod, + :hprod_residual_backend => EnzymeReverseADHvprod, :jacobian_residual_backend => SparseEnzymeADJacobian, :hessian_residual_backend => SparseEnzymeADHessian, ) diff --git a/test/enzyme.jl b/test/enzyme.jl index 2b3117fe..f6215b56 100644 --- a/test/enzyme.jl +++ b/test/enzyme.jl @@ -7,14 +7,17 @@ using ADNLPModels: # Automatically loads the code for Enzyme with Requires import Enzyme +_noop_c!(y, x) = nothing +_noop_ℓ(x, y, obj_weight, cx) = zero(eltype(x)) + EnzymeReverseAD() = ADNLPModels.ADModelBackend( ADNLPModels.EnzymeReverseADGradient(), - ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), + ADNLPModels.EnzymeReverseADHvprod(zeros(1), zeros(1), zeros(0), identity, _noop_c!, _noop_ℓ, 0), ADNLPModels.EnzymeReverseADJprod(zeros(1), zeros(1), zeros(1), zeros(1)), ADNLPModels.EnzymeReverseADJtprod(zeros(1), zeros(1), zeros(1), zeros(1)), ADNLPModels.EnzymeReverseADJacobian(), ADNLPModels.EnzymeReverseADHessian(zeros(1), zeros(1), identity), - ADNLPModels.EnzymeReverseADHvprod(zeros(1), identity), + ADNLPModels.EnzymeReverseADHvprod(zeros(1), zeros(1), zeros(0), identity, _noop_c!, _noop_ℓ, 0), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), @@ -47,13 +50,13 @@ push!( :gradient_backend => ADNLPModels.EnzymeReverseADGradient, :jprod_backend => ADNLPModels.EnzymeReverseADJprod, :jtprod_backend => ADNLPModels.EnzymeReverseADJtprod, - :hprod_backend => ADNLPModels.ForwardDiffADHvprod, + :hprod_backend => ADNLPModels.EnzymeReverseADHvprod, :jacobian_backend => ADNLPModels.SparseEnzymeADJacobian, :hessian_backend => ADNLPModels.SparseEnzymeADHessian, :ghjvprod_backend => ADNLPModels.ForwardDiffADGHjvprod, :jprod_residual_backend => ADNLPModels.EnzymeReverseADJprod, :jtprod_residual_backend => ADNLPModels.EnzymeReverseADJtprod, - :hprod_residual_backend => ADNLPModels.ForwardDiffADHvprod, + :hprod_residual_backend => ADNLPModels.EnzymeReverseADHvprod, :jacobian_residual_backend => ADNLPModels.SparseEnzymeADJacobian, :hessian_residual_backend => ADNLPModels.SparseEnzymeADHessian, ), From 2b9afb806a3511816c865db7aad53fbb6ba9cf49 Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 12:02:08 -0600 Subject: [PATCH 5/8] Fix --- src/enzyme.jl | 51 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/enzyme.jl b/src/enzyme.jl index d1a472c4..2510118f 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -465,20 +465,55 @@ end obj_weight::Real = one(eltype(x)), ) copyto!(b.xbuf, x) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.ℓ, b.xbuf, v, Float64[], obj_weight, b.cx) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.f, b.xbuf, v) + Hv .*= obj_weight return Hv end - function Hvprod!( + # jth_hprod: Hessian-vector product for the j-th constraint. + # Uses the Lagrangian with y = e_j (unit vector) and obj_weight = 0, + # avoiding the closure x -> c(x)[j] that Enzyme can't handle. + function NLPModels.hprod!( b::EnzymeReverseADHvprod, - Hv, - x, - v, - ci, - ::Val{:ci}, + nlp::ADModel, + x::AbstractVector, + v::AbstractVector, + j::Integer, + Hv::AbstractVector, + ) + copyto!(b.xbuf, x) + b.cx .= 0 + # Build y = e_{j-nlin} (unit vector for nonlinear constraint index) + y_ej = Enzyme.make_zero(b.cx) + k = 0 + for i in nlp.meta.nln + k += 1 + if i == j + y_ej[k] = one(eltype(x)) + break + end + end + _hvp!( + Enzyme.DuplicatedNoNeed(b.grad, Hv), + b.ℓ, b.xbuf, v, y_ej, zero(eltype(x)), b.cx, + ) + return Hv + end + + # hprod_residual: Hessian-vector product for the i-th residual. + # Uses forward-over-reverse on F_i(x) = F(x)[i]. + function NLPModels.hprod_residual!( + b::EnzymeReverseADHvprod, + nls::AbstractADNLSModel, + x::AbstractVector, + v::AbstractVector, + i::Integer, + Hv::AbstractVector, ) + F = get_F(nls) # out-of-place version + Fi(x) = F(x)[i] copyto!(b.xbuf, x) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), ci, b.xbuf, v) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), Fi, b.xbuf, v) return Hv end From caac03f8abc27f95b1e95f7ba2e082d4aa17296a Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 12:02:38 -0600 Subject: [PATCH 6/8] Cleanup --- test/Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Project.toml b/test/Project.toml index 502b8f56..da81f119 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,4 @@ [deps] -ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a" Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" From 7f46cdbd4c672d4c6cdfb91203ebdbe51e217b3b Mon Sep 17 00:00:00 2001 From: Michel Schanen Date: Fri, 13 Feb 2026 12:22:30 -0600 Subject: [PATCH 7/8] More fixes --- src/enzyme.jl | 37 +++++++++++++++++++++++++------------ test/enzyme.jl | 17 +++++++++++++++-- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/enzyme.jl b/src/enzyme.jl index 2510118f..fa6d9ab9 100644 --- a/src/enzyme.jl +++ b/src/enzyme.jl @@ -46,9 +46,12 @@ function EnzymeReverseADHessian( end struct EnzymeReverseADHvprod{V, F, C, L} <: InPlaceADbackend - grad::V - xbuf::V - cx::V + grad::V # length nvar, gradient buffer (primal in DuplicatedNoNeed) + hvbuf::V # length nvar, Hv output buffer (shadow in DuplicatedNoNeed) + xbuf::V # length nvar, input x buffer + vbuf::V # length nvar, input v buffer (tangent direction) + cx::V # length ncon, constraint output buffer + ybuf::V # length ncon, multiplier buffer for jth_hprod f::F c!::C ℓ::L @@ -64,8 +67,11 @@ function EnzymeReverseADHvprod( kwargs..., ) where {T} grad = fill!(similar(x0), zero(T)) + hvbuf = similar(x0) xbuf = similar(x0) + vbuf = similar(x0) cx = fill!(similar(x0, ncon), zero(T)) + ybuf = fill!(similar(x0, ncon), zero(T)) function ℓ(x, y, obj_weight, cx) if ncon != 0 @@ -80,7 +86,7 @@ function EnzymeReverseADHvprod( return res end - return EnzymeReverseADHvprod(grad, xbuf, cx, f, c!, ℓ, ncon) + return EnzymeReverseADHvprod(grad, hvbuf, xbuf, vbuf, cx, ybuf, f, c!, ℓ, ncon) end struct EnzymeReverseADJprod{V} <: InPlaceADbackend @@ -451,7 +457,9 @@ end obj_weight::Real = one(eltype(x)), ) copyto!(b.xbuf, x) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.ℓ, b.xbuf, v, y, obj_weight, b.cx) + copyto!(b.vbuf, v) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, b.hvbuf), b.ℓ, b.xbuf, b.vbuf, y, obj_weight, b.cx) + copyto!(Hv, b.hvbuf) return Hv end @@ -465,8 +473,9 @@ end obj_weight::Real = one(eltype(x)), ) copyto!(b.xbuf, x) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), b.f, b.xbuf, v) - Hv .*= obj_weight + copyto!(b.vbuf, v) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, b.hvbuf), b.f, b.xbuf, b.vbuf) + @. Hv = obj_weight * b.hvbuf return Hv end @@ -482,21 +491,23 @@ end Hv::AbstractVector, ) copyto!(b.xbuf, x) + copyto!(b.vbuf, v) b.cx .= 0 # Build y = e_{j-nlin} (unit vector for nonlinear constraint index) - y_ej = Enzyme.make_zero(b.cx) + b.ybuf .= 0 k = 0 for i in nlp.meta.nln k += 1 if i == j - y_ej[k] = one(eltype(x)) + b.ybuf[k] = one(eltype(x)) break end end _hvp!( - Enzyme.DuplicatedNoNeed(b.grad, Hv), - b.ℓ, b.xbuf, v, y_ej, zero(eltype(x)), b.cx, + Enzyme.DuplicatedNoNeed(b.grad, b.hvbuf), + b.ℓ, b.xbuf, b.vbuf, b.ybuf, zero(eltype(x)), b.cx, ) + copyto!(Hv, b.hvbuf) return Hv end @@ -513,7 +524,9 @@ end F = get_F(nls) # out-of-place version Fi(x) = F(x)[i] copyto!(b.xbuf, x) - _hvp!(Enzyme.DuplicatedNoNeed(b.grad, Hv), Fi, b.xbuf, v) + copyto!(b.vbuf, v) + _hvp!(Enzyme.DuplicatedNoNeed(b.grad, b.hvbuf), Fi, b.xbuf, b.vbuf) + copyto!(Hv, b.hvbuf) return Hv end diff --git a/test/enzyme.jl b/test/enzyme.jl index f6215b56..0d1a9df6 100644 --- a/test/enzyme.jl +++ b/test/enzyme.jl @@ -7,17 +7,30 @@ using ADNLPModels: # Automatically loads the code for Enzyme with Requires import Enzyme +# Dummy constraint and Lagrangian functions for constructing an EnzymeReverseADHvprod +# with no constraints. These are only used by EnzymeReverseAD() below, which builds +# a minimal ADModelBackend for the low-level smoke tests (gradient, jacobian, …). +# Real models get their own ℓ constructed from f and c! at model-creation time. _noop_c!(y, x) = nothing _noop_ℓ(x, y, obj_weight, cx) = zero(eltype(x)) +# Construct an EnzymeReverseADHvprod with pre-allocated buffers of size `n` and +# no constraints (ncon = 0). Fields: grad, hvbuf, xbuf, vbuf, cx, ybuf, f, c!, ℓ, ncon. +function _make_enzyme_hvprod(n) + ADNLPModels.EnzymeReverseADHvprod( + zeros(n), zeros(n), zeros(n), zeros(n), zeros(0), zeros(0), + identity, _noop_c!, _noop_ℓ, 0, + ) +end + EnzymeReverseAD() = ADNLPModels.ADModelBackend( ADNLPModels.EnzymeReverseADGradient(), - ADNLPModels.EnzymeReverseADHvprod(zeros(1), zeros(1), zeros(0), identity, _noop_c!, _noop_ℓ, 0), + _make_enzyme_hvprod(1), ADNLPModels.EnzymeReverseADJprod(zeros(1), zeros(1), zeros(1), zeros(1)), ADNLPModels.EnzymeReverseADJtprod(zeros(1), zeros(1), zeros(1), zeros(1)), ADNLPModels.EnzymeReverseADJacobian(), ADNLPModels.EnzymeReverseADHessian(zeros(1), zeros(1), identity), - ADNLPModels.EnzymeReverseADHvprod(zeros(1), zeros(1), zeros(0), identity, _noop_c!, _noop_ℓ, 0), + _make_enzyme_hvprod(1), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), ADNLPModels.EmptyADbackend(), From cf5f00982920c590419dfa4138a4993cc96fd68e Mon Sep 17 00:00:00 2001 From: Alexis Montoison <35051714+amontoison@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:37:28 -0700 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/ADNLPModels.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/ADNLPModels.jl b/src/ADNLPModels.jl index 58ce105a..9874adf8 100644 --- a/src/ADNLPModels.jl +++ b/src/ADNLPModels.jl @@ -205,11 +205,8 @@ get_F(::AbstractNLPModel, ::AbstractNLPModel) = () -> () Return the lagrangian function `ℓ(x) = obj_weight * f(x) + c(x)ᵀy`. """ function get_lag(nlp::AbstractADNLPModel, b::ADBackend, obj_weight::Real) - # println("Check") - # return x -> obj_weight * nlp.f(x) return ℓ(x; obj_weight = obj_weight) = obj_weight * nlp.f(x) end - function get_lag(nlp::AbstractADNLPModel, b::ADBackend, obj_weight::Real, y::AbstractVector) if nlp.meta.nnln == 0 return get_lag(nlp, b, obj_weight)