Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Functionality for FieldVectors (from StaticArrays) #307

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/apiutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ end
###################################
# vector mode function evaluation #
###################################

@generated function dualize(::Type{T}, x::SArray{S,V,D,N}) where {T,S,V,D,N}
@generated function dualize(::Type{T}, x::Union{FieldVector, SArray}) where {T}
N = length(x)
V = eltype(x)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type constraint on x allows this (and the other similar changes in this PR) to technically work, since we can probably assume that no upstream code will re-overload length or eltype for Union{FieldVector, SArray}.

However, it's a bit sketchy...it makes it easier for bugs to accidentally be introduced in the future via seemingly innocent changes (e.g. loosening the type constraint on x).

Would it be hard to just pull these out of the type parameter directly instead?

Copy link
Author

@kylejbrown17 kylejbrown17 Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like calling length and eltype makes this more straightforward to extend to additional types. @zsunberg and I decided to retrieve N and V via length and eltype because it was already quite complicated to try to extract them directly from the type parameters.

I've tried a few different variations on this:

@generated function dualize(::Type{T}, x::Union{FieldVector{N,V}, SArray{S,V,D,N}}) where {T,S,V,D,N}

Everything I've tried fails when called on an instance of FieldVector, although most of the approaches seem to work on SVector. I'm not 100% sure what you mean by "loosen the type constraint", but it seems like that would cause an even bigger headache if we want to extract the parameters this way.

If we tried to extract type parameters directly within the function body, we'd need a separate case for each type (because V and N are in different places for each type):

N,V = supertype(typeof(x)).parameters              # <: FieldVector
S,V,D,N = typeof(x).parameters             # SArray

Either of the above ways requires more customization than simply calling length and eltype. I think this way is more stable. It should work for vector types for which length and eltype are defined.

Another option is to have two different function definitions in every case where we need V and N.

Copy link
Member

@jrevels jrevels Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this way is more stable. It should work for vector types for which length and eltype are defined.

I think you might misunderstand my concern, which is my bad - I didn't explain the issue directly enough. If this were normal code, then of course I'd be fine with length and eltype getting used here.

However, this code is being called in an @generated thunk, and in general, calling overloadable methods inside of @generated function thunks is something that should be done with care, because downstream code can cause the generator to be "invalidated" in ways the compiler can't easily predict. Such code can cause world-age errors or silently incorrect method lookups, and are very hard for end-users to fix/debug/understand because they can depend on precompilation settings, code load order, etc. Thus, we want to be very careful about potentially introducing these kinds of bugs.

In this case, since FieldVector is an abstract type, so I could subtype it and overload length and eltype on that subtype. For the sake of example, let's call this subtype MyFieldVectorSubtype. If a user loaded ForwardDiff, called dualize on some other <:FieldVector, then loaded the code that defines FieldVectorSubtype, then called dualize on an instance of MyFieldVectorSubtype, the compiler could possibly throw a world-age error (most likely), silently call the wrong methods, run into some other compilation error, etc.

While this may seem like a niche class of bugs, they can crop up in practice quite regularly; for one ForwardDiff-related instance of such a bug, see JuliaArrays/StaticArrays.jl#316.

By the way, I really appreciate this PR, and am excited for this feature!

EDIT: With the above in mind, it might be sufficient to just define some internal helper functions, and leave a note in the code, e.g.

# these should not be overloaded for new types as doing so could 
# invalidate several of ForwardDiff's internal generated functions
_length(::Type{<:FieldVector{N}}) where {N} = N
_length(::Type{<:SArray{<:Any,<:Any,<:Any,N}}) where {N} = N
_eltype(::Type{<:FieldVector{<:Any,V}}) where {V} = V
_eltype(::Type{<:SArray{<:Any,V}}) where {V} = V

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, yeah, we didn't understand the specific concerns with @generated

What about

@generated function dualize(::Type{T}, x::Union{FieldVector{N,V}, SArray{<:Any,V,<:Any,N}}) where {T,V,N}

?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we didn't know about the <:Any syntax in the type parameters)

Copy link

@zsunberg zsunberg Mar 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that doesn't work, I think two dualize functions would be a little simpler than _length and _eltype

EDIT: never mind - I didn't see that this is scattered throughout the other code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrevels, thanks for clarifying! This makes more sense. We'll incorporate the changes you suggested and get back to you.

dx = Expr(:tuple, [:(Dual{T}(x[$i], chunk, Val{$i}())) for i in 1:N]...)
return quote
chunk = Chunk{N}()
chunk = Chunk{$N}()
$(Expr(:meta, :inline))
return SArray{S}($(dx))
# return SArray{S}($(dx))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should delete this commented out line.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry about that. Not sure why I left that sitting in there.

similar_type($x,Dual{T,$V,$N})($(dx))
end
end

@inline static_dual_eval(::Type{T}, f, x::SArray) where {T} = f(dualize(T, x))
@inline static_dual_eval(::Type{T}, f, x::Union{FieldVector, SArray}) where {T} = f(dualize(T, x))

function vector_mode_dual_eval(f, x, cfg::Union{JacobianConfig,GradientConfig})
xdual = cfg.duals
Expand Down
23 changes: 12 additions & 11 deletions src/gradient.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,23 @@ function gradient!(result::Union{AbstractArray,DiffResult}, f, x::AbstractArray,
return result
end

@inline gradient(f, x::SArray) = vector_mode_gradient(f, x)
@inline gradient(f, x::SArray, cfg::GradientConfig) = gradient(f, x)
@inline gradient(f, x::Union{FieldVector, SArray}) = vector_mode_gradient(f, x)
@inline gradient(f, x::Union{FieldVector, SArray}, cfg::GradientConfig) = gradient(f, x)

@inline gradient!(result::Union{AbstractArray,DiffResult}, f, x::SArray) = vector_mode_gradient!(result, f, x)
@inline gradient!(result::Union{AbstractArray,DiffResult}, f, x::SArray, cfg::GradientConfig) = gradient!(result, f, x)
@inline gradient!(result::Union{AbstractArray,DiffResult}, f, x::Union{FieldVector, SArray}) = vector_mode_gradient!(result, f, x)
@inline gradient!(result::Union{AbstractArray,DiffResult}, f, x::Union{FieldVector, SArray}, cfg::GradientConfig) = gradient!(result, f, x)

#####################
# result extraction #
#####################

@generated function extract_gradient(::Type{T}, y::Real, ::SArray{S,X,D,N}) where {T,S,X,D,N}
@generated function extract_gradient(::Type{T}, y::Real, x::Union{FieldVector, SArray}) where {T}
N = length(x)
V = eltype(x)
result = Expr(:tuple, [:(partials(T, y, $i)) for i in 1:N]...)
return quote
$(Expr(:meta, :inline))
return SArray{S}($result)
return similar_type($x,$V)($result)
end
end

Expand Down Expand Up @@ -102,16 +104,15 @@ function vector_mode_gradient!(result, f, x, cfg::GradientConfig{T}) where {T}
return result
end

@inline function vector_mode_gradient(f::F, x::SArray{S,V}) where {F,S,V}
T = typeof(Tag(f,V))
@inline function vector_mode_gradient(f::F, x::Union{FieldVector, SArray}) where {F}
T = typeof(Tag(f,eltype(x)))
return extract_gradient(T, static_dual_eval(T, f, x), x)
end

@inline function vector_mode_gradient!(result, f::F, x::SArray{S,V}) where {F,S,V}
T = typeof(Tag(f,V))
@inline function vector_mode_gradient!(result, f::F, x::Union{FieldVector, SArray}) where {F}
T = typeof(Tag(f,eltype(x)))
return extract_gradient!(T, result, static_dual_eval(T, f, x))
end

##############
# chunk mode #
##############
Expand Down
13 changes: 7 additions & 6 deletions src/hessian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,18 @@ function hessian!(result::DiffResult, f, x::AbstractArray, cfg::HessianConfig{T}
return result
end

hessian(f, x::SArray) = jacobian(y -> gradient(f, y), x)
hessian(f, x::Union{FieldVector, SArray}) = jacobian(y -> gradient(f, y), x)

hessian(f, x::SArray, cfg::HessianConfig) = hessian(f, x)
hessian(f, x::Union{FieldVector, SArray}, cfg::HessianConfig) = hessian(f, x)

hessian!(result::AbstractArray, f, x::SArray) = jacobian!(result, y -> gradient(f, y), x)
hessian!(result::AbstractArray, f, x::Union{FieldVector, SArray}) = jacobian!(result, y -> gradient(f, y), x)

hessian!(result::MutableDiffResult, f, x::SArray) = hessian!(result, f, x, HessianConfig(f, result, x))
hessian!(result::MutableDiffResult, f, x::Union{FieldVector, SArray}) = hessian!(result, f, x, HessianConfig(f, result, x))

hessian!(result::ImmutableDiffResult, f, x::SArray, cfg::HessianConfig) = hessian!(result, f, x)
hessian!(result::ImmutableDiffResult, f, x::Union{FieldVector, SArray}, cfg::HessianConfig) = hessian!(result, f, x)

function hessian!(result::ImmutableDiffResult, f::F, x::SArray{S,V}) where {F,S,V}
function hessian!(result::ImmutableDiffResult, f::F, x::Union{FieldVector, SArray}) where {F}
V = eltype(x)
T = typeof(Tag(f,V))
d1 = dualize(T, x)
d2 = dualize(T, d1)
Expand Down
31 changes: 19 additions & 12 deletions src/jacobian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ stored in `y`.
Set `check` to `Val{false}()` to disable tag checking. This can lead to perturbation confusion, so should be used with care.
"""
function jacobian(f!, y::AbstractArray, x::AbstractArray, cfg::JacobianConfig{T} = JacobianConfig(f!, y, x), ::Val{CHK}=Val{true}()) where {T, CHK}
CHK && checktag(T, f!, x)
CHK && checktag(T, f!, x)
if chunksize(cfg) == length(x)
return vector_mode_jacobian(f!, y, x, cfg)
else
Expand Down Expand Up @@ -78,26 +78,29 @@ function jacobian!(result::Union{AbstractArray,DiffResult}, f!, y::AbstractArray
return result
end

@inline jacobian(f, x::SArray) = vector_mode_jacobian(f, x)
@inline jacobian(f, x::SArray, cfg::JacobianConfig) = jacobian(f, x)
@inline jacobian(f, x::Union{FieldVector, SArray}) = vector_mode_jacobian(f, x)
@inline jacobian(f, x::Union{FieldVector, SArray}, cfg::JacobianConfig) = jacobian(f, x)

@inline jacobian!(result::Union{AbstractArray,DiffResult}, f, x::SArray) = vector_mode_jacobian!(result, f, x)
@inline jacobian!(result::Union{AbstractArray,DiffResult}, f, x::SArray, cfg::JacobianConfig) = jacobian!(result, f, x)
@inline jacobian!(result::Union{AbstractArray,DiffResult}, f, x::Union{FieldVector, SArray}) = vector_mode_jacobian!(result, f, x)
@inline jacobian!(result::Union{AbstractArray,DiffResult}, f, x::Union{FieldVector, SArray}, cfg::JacobianConfig) = jacobian!(result, f, x)

#####################
# result extraction #
#####################

@generated function extract_jacobian(::Type{T}, ydual::SArray{SY,VY,DY,M},
x::SArray{SX,VX,DX,N}) where {T,SY,VY,DY,M,SX,VX,DX,N}
@generated function extract_jacobian(::Type{T}, ydual::Union{FieldVector, SArray},
x::Union{FieldVector, SArray}) where {T}
M = length(ydual)
N = length(x)
result = Expr(:tuple, [:(partials(T, ydual[$i], $j)) for i in 1:M, j in 1:N]...)
return quote
$(Expr(:meta, :inline))
return SArray{Tuple{M,N}}($result)
return SArray{Tuple{$M,$N}}($result)
end
end

function extract_jacobian(::Type{T}, ydual::AbstractArray, x::SArray{S,V,D,N}) where {T,S,V,D,N}
function extract_jacobian(::Type{T}, ydual::AbstractArray, x::Union{FieldVector, SArray}) where {T}
N = length(x)
result = similar(ydual, valtype(eltype(ydual)), length(ydual), N)
return extract_jacobian!(T, result, ydual, N)
end
Expand Down Expand Up @@ -165,20 +168,24 @@ function vector_mode_jacobian!(result, f!::F, y, x, cfg::JacobianConfig{T,V,N})
return result
end

@inline function vector_mode_jacobian(f::F, x::SArray{S,V}) where {F,S,V}
@inline function vector_mode_jacobian(f::F, x::Union{FieldVector, SArray}) where {F}
V = eltype(x)
T = typeof(Tag(f,V))
return extract_jacobian(T, static_dual_eval(T, f, x), x)
end

@inline function vector_mode_jacobian!(result, f::F, x::SArray{S,V,D,N}) where {F,S,V,D,N}
@inline function vector_mode_jacobian!(result, f::F, x::Union{FieldVector, SArray}) where {F}
N = length(x)
V = eltype(x)
T = typeof(Tag(f,V))
ydual = static_dual_eval(T, f, x)
result = extract_jacobian!(T, result, ydual, N)
result = extract_value!(T, result, ydual)
return result
end

@inline function vector_mode_jacobian!(result::ImmutableDiffResult, f::F, x::SArray{S,V,D,N}) where {F,S,V,D,N}
@inline function vector_mode_jacobian!(result::ImmutableDiffResult, f::F, x::Union{FieldVector, SArray}) where {F}
V = eltype(x)
T = typeof(Tag(f,V))
ydual = static_dual_eval(T, f, x)
result = DiffResults.jacobian!(result, extract_jacobian(T, ydual, x))
Expand Down
60 changes: 60 additions & 0 deletions test/GradientTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,65 @@ sresult3 = ForwardDiff.gradient!(sresult3, prod, sx, scfg)
@test DiffResults.gradient(sresult2) == DiffResults.gradient(result)
@test DiffResults.gradient(sresult3) == DiffResults.gradient(result)

println(" ...testing specialized FieldVector codepaths")

struct Point3D{R<:Real} <: FieldVector{3,R}
x::R
y::R
z::R
end
StaticArrays.similar_type(p::Type{P}, ::Type{R}, size::Size{(3,)}) where {P<:Point3D, R<:Real} = Point3D{R}

x = rand(3, 1)
fx = Point3D(x)

cfg = ForwardDiff.GradientConfig(nothing, x)
fcfg = ForwardDiff.GradientConfig(nothing, fx)

actual = ForwardDiff.gradient(prod, x)
@test ForwardDiff.gradient(prod, fx) == actual[:]
@test ForwardDiff.gradient(prod, fx, cfg) == actual[:]
@test ForwardDiff.gradient(prod, fx, fcfg) == actual[:]

out = similar(x)
ForwardDiff.gradient!(out, prod, fx)
@test out == actual

out = similar(x)
ForwardDiff.gradient!(out, prod, fx, cfg)
@test out == actual

out = similar(x)
ForwardDiff.gradient!(out, prod, fx, fcfg)
@test out == actual

result = DiffResults.GradientResult(x)
result = ForwardDiff.gradient!(result, prod, x)

result1 = DiffResults.GradientResult(x)
result2 = DiffResults.GradientResult(x)
result3 = DiffResults.GradientResult(x)
result1 = ForwardDiff.gradient!(result1, prod, fx)
result2 = ForwardDiff.gradient!(result2, prod, fx, cfg)
result3 = ForwardDiff.gradient!(result3, prod, fx, fcfg)
@test DiffResults.value(result1) == DiffResults.value(result)
@test DiffResults.value(result2) == DiffResults.value(result)
@test DiffResults.value(result3) == DiffResults.value(result)
@test DiffResults.gradient(result1) == DiffResults.gradient(result)
@test DiffResults.gradient(result2) == DiffResults.gradient(result)
@test DiffResults.gradient(result3) == DiffResults.gradient(result)

fresult1 = DiffResults.GradientResult(fx)
fresult2 = DiffResults.GradientResult(fx)
fresult3 = DiffResults.GradientResult(fx)
fresult1 = ForwardDiff.gradient!(fresult1, prod, fx)
fresult2 = ForwardDiff.gradient!(fresult2, prod, fx, cfg)
fresult3 = ForwardDiff.gradient!(fresult3, prod, fx, fcfg)
@test DiffResults.value(fresult1) == DiffResults.value(result)
@test DiffResults.value(fresult2) == DiffResults.value(result)
@test DiffResults.value(fresult3) == DiffResults.value(result)
@test DiffResults.gradient(fresult1) == DiffResults.gradient(result)[:]
@test DiffResults.gradient(fresult2) == DiffResults.gradient(result)[:]
@test DiffResults.gradient(fresult3) == DiffResults.gradient(result)[:]

end # module
68 changes: 68 additions & 0 deletions test/HessianTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,72 @@ sresult3 = ForwardDiff.hessian!(sresult3, prod, sx, ForwardDiff.HessianConfig(pr
@test DiffResults.hessian(sresult2) == DiffResults.hessian(result)
@test DiffResults.hessian(sresult3) == DiffResults.hessian(result)


println(" ...testing specialized FieldVector codepaths")

struct Point3D{R<:Real} <: FieldVector{3,R}
x::R
y::R
z::R
end
StaticArrays.similar_type(p::Type{P}, ::Type{R}, size::Size{(3,)}) where {P<:Point3D, R<:Real} = Point3D{R}

x = rand(3, 1)
fx = Point3D(x)

cfg = ForwardDiff.HessianConfig(nothing, x)
fcfg = ForwardDiff.HessianConfig(nothing, fx)

actual = ForwardDiff.hessian(prod, x)
@test ForwardDiff.hessian(prod, fx) == actual
@test ForwardDiff.hessian(prod, fx, cfg) == actual
@test ForwardDiff.hessian(prod, fx, fcfg) == actual

out = similar(x, length(x), length(x))
ForwardDiff.hessian!(out, prod, fx)
@test out == actual

out = similar(x, length(x), length(x))
ForwardDiff.hessian!(out, prod, fx, cfg)
@test out == actual

out = similar(x, length(x), length(x))
ForwardDiff.hessian!(out, prod, fx, fcfg)
@test out == actual

result = DiffResults.HessianResult(x)
result = ForwardDiff.hessian!(result, prod, x)

result1 = DiffResults.HessianResult(x)
result2 = DiffResults.HessianResult(x)
result3 = DiffResults.HessianResult(x)
result1 = ForwardDiff.hessian!(result1, prod, fx)
result2 = ForwardDiff.hessian!(result2, prod, fx, ForwardDiff.HessianConfig(prod, result2, x, ForwardDiff.Chunk(x), nothing))
result3 = ForwardDiff.hessian!(result3, prod, fx, ForwardDiff.HessianConfig(prod, result3, x, ForwardDiff.Chunk(x), nothing))
@test DiffResults.value(result1) == DiffResults.value(result)
@test DiffResults.value(result2) == DiffResults.value(result)
@test DiffResults.value(result3) == DiffResults.value(result)
@test DiffResults.gradient(result1) == DiffResults.gradient(result)
@test DiffResults.gradient(result2) == DiffResults.gradient(result)
@test DiffResults.gradient(result3) == DiffResults.gradient(result)
@test DiffResults.hessian(result1) == DiffResults.hessian(result)
@test DiffResults.hessian(result2) == DiffResults.hessian(result)
@test DiffResults.hessian(result3) == DiffResults.hessian(result)

fresult1 = DiffResults.HessianResult(fx)
fresult2 = DiffResults.HessianResult(fx)
fresult3 = DiffResults.HessianResult(fx)
fresult1 = ForwardDiff.hessian!(fresult1, prod, fx)
fresult2 = ForwardDiff.hessian!(fresult2, prod, fx, ForwardDiff.HessianConfig(prod, fresult2, x, ForwardDiff.Chunk(x), nothing))
fresult3 = ForwardDiff.hessian!(fresult3, prod, fx, ForwardDiff.HessianConfig(prod, fresult3, x, ForwardDiff.Chunk(x), nothing))
@test DiffResults.value(fresult1) == DiffResults.value(result)
@test DiffResults.value(fresult2) == DiffResults.value(result)
@test DiffResults.value(fresult3) == DiffResults.value(result)
@test DiffResults.gradient(fresult1) == DiffResults.gradient(result)[:]
@test DiffResults.gradient(fresult2) == DiffResults.gradient(result)[:]
@test DiffResults.gradient(fresult3) == DiffResults.gradient(result)[:]
@test DiffResults.hessian(fresult1) == DiffResults.hessian(result)
@test DiffResults.hessian(fresult2) == DiffResults.hessian(result)
@test DiffResults.hessian(fresult3) == DiffResults.hessian(result)

end # module
62 changes: 62 additions & 0 deletions test/JacobianTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,66 @@ sresult3 = ForwardDiff.jacobian!(sresult3, diff, sx, scfg)
@test DiffResults.jacobian(sresult2) == DiffResults.jacobian(result)
@test DiffResults.jacobian(sresult3) == DiffResults.jacobian(result)


println(" ...testing specialized FieldVector codepaths")

struct Point3D{R<:Real} <: FieldVector{3,R}
x::R
y::R
z::R
end
StaticArrays.similar_type(p::Type{P}, ::Type{R}, size::Size{(3,)}) where {P<:Point3D, R<:Real} = Point3D{R}

x = rand(3, 1)
fx = Point3D(x)

cfg = ForwardDiff.JacobianConfig(nothing, x)
fcfg = ForwardDiff.JacobianConfig(nothing, fx)

actual = ForwardDiff.jacobian(diff, x)
@test ForwardDiff.jacobian(diff, fx) == actual
@test ForwardDiff.jacobian(diff, fx, cfg) == actual
@test ForwardDiff.jacobian(diff, fx, fcfg) == actual

out = similar(x, 2, 3)
ForwardDiff.jacobian!(out, diff, fx)
@test out == actual

out = similar(x, 2, 3)
ForwardDiff.jacobian!(out, diff, fx, cfg)
@test out == actual

out = similar(x, 2, 3)
ForwardDiff.jacobian!(out, diff, fx, fcfg)
@test out == actual

result = DiffResults.JacobianResult(similar(x, 2), x)
result = ForwardDiff.jacobian!(result, diff, x)

result1 = DiffResults.JacobianResult(similar(fx, 2), fx)
result2 = DiffResults.JacobianResult(similar(fx, 2), fx)
result3 = DiffResults.JacobianResult(similar(fx, 2), fx)
result1 = ForwardDiff.jacobian!(result1, diff, fx)
result2 = ForwardDiff.jacobian!(result2, diff, fx, cfg)
result3 = ForwardDiff.jacobian!(result3, diff, fx, fcfg)
@test DiffResults.value(result1) == DiffResults.value(result)
@test DiffResults.value(result2) == DiffResults.value(result)
@test DiffResults.value(result3) == DiffResults.value(result)
@test DiffResults.jacobian(result1) == DiffResults.jacobian(result)
@test DiffResults.jacobian(result2) == DiffResults.jacobian(result)
@test DiffResults.jacobian(result3) == DiffResults.jacobian(result)

sy = @SVector fill(zero(eltype(fx)), 2)
fresult1 = DiffResults.JacobianResult(sy, fx)
fresult2 = DiffResults.JacobianResult(sy, fx)
fresult3 = DiffResults.JacobianResult(sy, fx)
fresult1 = ForwardDiff.jacobian!(fresult1, diff, fx)
fresult2 = ForwardDiff.jacobian!(fresult2, diff, fx, cfg)
fresult3 = ForwardDiff.jacobian!(fresult3, diff, fx, fcfg)
@test DiffResults.value(fresult1) == DiffResults.value(result)
@test DiffResults.value(fresult2) == DiffResults.value(result)
@test DiffResults.value(fresult3) == DiffResults.value(result)
@test DiffResults.jacobian(fresult1) == DiffResults.jacobian(result)
@test DiffResults.jacobian(fresult2) == DiffResults.jacobian(result)
@test DiffResults.jacobian(fresult3) == DiffResults.jacobian(result)
end # module
Loading