JuliaDiff / JuliaDiff/ForwardDiff.jl
jacobian(f, x) silently returns zeros if f is complex-valued
Nobody has claimed this yet.
- Dominant language
- Julia
- Stars
- 1k
- Forks
- 160
- PR merge metrics
- No merged PRs in 30d
Description
jacobian returns zeros, with Duals leaked into the eltype, instead of the Jacobian or an error:
julia> g(x) = [cis(x[1])*x[2], x[1] + 2im*x[2]];
julia> J = ForwardDiff.jacobian(g, [0.5, 1.0]);
julia> eltype(J)
Complex{ForwardDiff.Dual{ForwardDiff.Tag{typeof(g), Float64}, Float64, 2}}
julia> all(iszero, J)
true
extract_jacobian! extracts with partials(T, ydual, i), which for a Complex{Dual} hits the partials(x, i...) = zero(x) fallback. derivative(f, x) is fine since it goes through extract_derivative, which handles Complex{<:Dual}.
extract_derivative cannot be reused directly because it hardcodes the partial index, while the Jacobian needs index i. But generalising it to an index is enough, and keeps the knowledge of how a complex number carries derivative information in one place:
@inline extract_partial(::Type{T}, y::Real, i) where {T} = zero(y)
@inline extract_partial(::Type{T}, y::Complex, i) where {T} = zero(y)
@inline extract_partial(::Type{T}, y::Dual, i) where {T} = partials(T, y, i)
@inline extract_partial(::Type{T}, y::Complex{<:Dual}, i) where {T} =
complex(partials(T, real(y), i), partials(T, imag(y), i))
extract_jacobian!/extract_jacobian_chunk! then use extract_partial(T, ·, i) instead of partials(T, ·, i), and the scalar extract_derivative methods become the i = 1 case.
One more piece is needed for the result eltype, since jacobian allocates similar(ydual, valtype(T, eltype(ydual)), ...):
@inline valtype(::Type{T}, ::Type{Complex{D}}) where {T,D} = Complex{valtype(T, D)}
With those, vector mode, chunk mode and jacobian! all give the correct ComplexF64 Jacobian and the real paths are unchanged.
If we would rather not support complex-valued f here, it should at least throw rather than return zeros.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading extract_jacobian!, extract_jacobian_chunk!, extract_derivative, and valtype, focusing on how Complex{Dual} values are handled. Exercise vector mode, chunk mode, and jacobian! with the reproducer from the issue. Done means complex-valued functions produce a correct ComplexF64 Jacobian, while real-valued paths remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100