Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ForwardDiff"
uuid = "f6369f11-7733-5829-9624-2563aa707210"
version = "1.0.1"
version = "1.0.2"

[deps]
CommonSubexpressions = "bbf7d656-a473-5ed7-a52c-81e309532950"
Expand Down
56 changes: 48 additions & 8 deletions src/apiutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,36 @@ end

function seed!(duals::AbstractArray{Dual{T,V,N}}, x,
seed::Partials{N,V} = zero(Partials{N,V})) where {T,V,N}
for idx in structural_eachindex(duals, x)
duals[idx] = Dual{T,V,N}(x[idx], seed)
if isbitstype(V)
for idx in structural_eachindex(duals, x)
duals[idx] = Dual{T,V,N}(x[idx], seed)
end
else
for idx in structural_eachindex(duals, x)
if isassigned(x, idx)
duals[idx] = Dual{T,V,N}(x[idx], seed)
else
Base._unsetindex!(duals, idx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

:/, this is quite unfortunate, is this really the only way to fix this?

Copy link
Member

Choose a reason for hiding this comment

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

I think the idea is that if x[idx] is #undef, we want duals[idx] to be the same

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I wasn't 100% percent happy but it was the best I could think of and what apparently is used in base. IMO we really want to ensure here that we get the same behaviour as copyto! on the primal part, to avoid that yduals contains any surprising values, possibly from previous chunks or differentations.

Copy link
Member Author

Choose a reason for hiding this comment

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

julia> x = BigFloat[1, 2];

julia> y = similar(x); y[2] = 1;

julia> x
2-element Vector{BigFloat}:
 1.0
 2.0

julia> y
2-element Vector{BigFloat}:
 #undef
   1.0

julia> copyto!(x, y);

julia> x
2-element Vector{BigFloat}:
 #undef
   1.0

end
end
end
return duals
end

function seed!(duals::AbstractArray{Dual{T,V,N}}, x,
seeds::NTuple{N,Partials{N,V}}) where {T,V,N}
for (i, idx) in zip(1:N, structural_eachindex(duals, x))
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
if isbitstype(V)
for (i, idx) in zip(1:N, structural_eachindex(duals, x))
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
end
else
for (i, idx) in zip(1:N, structural_eachindex(duals, x))
if isassigned(x, idx)
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
else
Base._unsetindex!(duals, idx)
end
end
end
return duals
end
Expand All @@ -90,8 +110,18 @@ function seed!(duals::AbstractArray{Dual{T,V,N}}, x, index,
seed::Partials{N,V} = zero(Partials{N,V})) where {T,V,N}
offset = index - 1
idxs = Iterators.drop(structural_eachindex(duals, x), offset)
for idx in idxs
duals[idx] = Dual{T,V,N}(x[idx], seed)
if isbitstype(V)
for idx in idxs
duals[idx] = Dual{T,V,N}(x[idx], seed)
end
else
for idx in idxs
if isassigned(x, idx)
duals[idx] = Dual{T,V,N}(x[idx], seed)
else
Base._unsetindex!(duals, idx)
end
end
end
return duals
end
Expand All @@ -100,8 +130,18 @@ function seed!(duals::AbstractArray{Dual{T,V,N}}, x, index,
seeds::NTuple{N,Partials{N,V}}, chunksize = N) where {T,V,N}
offset = index - 1
idxs = Iterators.drop(structural_eachindex(duals, x), offset)
for (i, idx) in zip(1:chunksize, idxs)
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
if isbitstype(V)
for (i, idx) in zip(1:chunksize, idxs)
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
end
else
for (i, idx) in zip(1:chunksize, idxs)
if isassigned(x, idx)
duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
else
Base._unsetindex!(duals, idx)
end
end
end
return duals
end
29 changes: 29 additions & 0 deletions test/JacobianTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,33 @@ end
end
end

# issues #436, #740
@testset "BigFloat" begin
Copy link
Member

Choose a reason for hiding this comment

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

Does this test cover all the new branches? It seems CodeCov is sleeping at the wheel.
I think we may also want to add cases where only some of the values are uninitialized?

Copy link
Member Author

@devmotion devmotion Aug 25, 2025

Choose a reason for hiding this comment

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

Codecov results are available on the codecov webpage: https://app.codecov.io/github/JuliaDiff/ForwardDiff.jl/commit/6f8f6a977d4d69fda7c492f6e0a47cb8a6853045 (I assume the codecov app is not installed in the repo and hence there's no nice summary in the PR - AFAICT I don't have sufficient permissions to install it)

Indeed, you were right, the tests did not cover all new branches. I extended the tests, and locally it seems that they cover all branches as commenting out any of the branches leads to test failures.

Copy link
Member Author

Choose a reason for hiding this comment

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

# Unassigned entries in the output
x = BigFloat.(1:9)
for chunksize in (1, 2, 9)
y = similar(x)
@test all(i -> !isassigned(y, i), eachindex(y))
cfg = ForwardDiff.JacobianConfig(copyto!, y, x, ForwardDiff.Chunk{chunksize}())
res = ForwardDiff.jacobian(copyto!, y, x, cfg)
@test y == x
@test res isa Matrix{BigFloat}
@test res == I
end

# Unassigned (but unused) entry in the input and unassigned entries in the output
resize!(x, 10)
f = (y, x) -> copyto!(y, 1, x, 1, 9)
for chunksize in (1, 2, 10)
y = similar(x, 9)
@test all(i -> !isassigned(y, i), eachindex(y))
cfg = ForwardDiff.JacobianConfig(f, y, x, ForwardDiff.Chunk{chunksize}())
res = ForwardDiff.jacobian(f, y, x, cfg)
@test y == x[1:(end-1)]
@test res isa Matrix{BigFloat}
@test res[:, 1:(end-1)] == I
@test all(iszero, res[:, end])
end
end

end # module
Loading