Description
Let us say that we have a sparse matrix where the entries are Dual numbers.
using ForwardDiff, SparseArrays
d = ForwardDiff.Dual(1.0, 2.0)
a = sparse([0 d])
Let us define two zero values with different partials. One with zero partials (true zero) and one with non-zero partials (false zero):
# 0, with ∂ = 1
d_false_zero = ForwardDiff.Dual(0.0, 1.0)
# 0, with ∂ = 0
d_true_zero = ForwardDiff.Dual(0.0, 0.0)
If we replace the existing entry at [2]
, everything is well:
## Insertion into existing element keeps partials
a[2] = d_false_zero
println("Existing element:")
println(a[2].partials == d_false_zero.partials)
This gives the expected output:
Existing element:
true
Now let us try to insert our false zero into the first position, which we know isn't yet stored:
## Insertion into unstored element drops partials
a[1] = d_false_zero
println("Zero element:")
println(a[1].partials == d_false_zero.partials)
Zero element:
false
The partials were lost, due to the iszero
check here in SparseArrays: https://github.com/JuliaLang/julia/blob/cb30aa7a089f4d5f1c9f834d96c3788f2e93ebcc/stdlib/SparseArrays/src/sparsematrix.jl#L2652
Some quick and ugly type piracy can fix this, but may have other unintended consequences:
import Base.iszero
Base.iszero(d::ForwardDiff.Dual) = iszero(d.value) && iszero(d.partials)
I'm not sure what the intended behavior should be, since the entry is technically zero, but not a neutral element for addition of Duals.