Repository: JuliaDiff/FiniteDifferences.jl
Pull request #79 (2020-04-24 23:10:00)

 remerge #76 with bumped minor version 
Will probably have to rebase once #78 is merged.


Patch:
diff --git a/Project.toml b/Project.toml
index 1e986a8..eeb5ea8 100644
--- a/Project.toml
+++ b/Project.toml
@@ -1,6 +1,6 @@
 name = "FiniteDifferences"
 uuid = "26cc04aa-876d-5657-8c51-4c34ba976000"
-version = "0.9.8"
+version = "0.10.0"
 
 [deps]
 LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
diff --git a/src/grad.jl b/src/grad.jl
index 2635c3f..461f0c1 100644
--- a/src/grad.jl
+++ b/src/grad.jl
@@ -2,11 +2,11 @@
     jacobian(fdm, f, x...)
 
 Approximate the Jacobian of `f` at `x` using `fdm`. Results will be returned as a
-`Matrix{<:Number}` of `size(length(y_vec), length(x_vec))` where `x_vec` is the flattened
+`Matrix{<:Real}` of `size(length(y_vec), length(x_vec))` where `x_vec` is the flattened
 version of `x`, and `y_vec` the flattened version of `f(x...)`. Flattening performed by
 [`to_vec`](@ref).
 """
-function jacobian(fdm, f, x::Vector{<:Number}; len=nothing)
+function jacobian(fdm, f, x::Vector{<:Real}; len=nothing)
     len !== nothing && Base.depwarn(
         "`len` keyword argument to `jacobian` is no longer required " *
         "and will not be permitted in the future.",
@@ -38,11 +38,11 @@ end
 replace_arg(x, xs::Tuple, k::Int) = ntuple(p -> p == k ? x : xs[p], length(xs))
 
 """
-    _jvp(fdm, f, x::Vector{<:Number}, ẋ::AbstractVector{<:Number})
+    _jvp(fdm, f, x::Vector{<:Real}, ẋ::AbstractVector{<:Real})
 
 Convenience function to compute `jacobian(f, x) * ẋ`.
 """
-function _jvp(fdm, f, x::Vector{<:Number}, ẋ::Vector{<:Number})
+function _jvp(fdm, f, x::Vector{<:Real}, ẋ::Vector{<:Real})
     return fdm(ε -> f(x .+ ε .* ẋ), zero(eltype(x)))
 end
 
@@ -75,7 +75,7 @@ end
 
 j′vp(fdm, f, ȳ, xs...) = j′vp(fdm, xs->f(xs...), ȳ, xs)[1]
 
-function _j′vp(fdm, f, ȳ::Vector{<:Number}, x::Vector{<:Number})
+function _j′vp(fdm, f, ȳ::Vector{<:Real}, x::Vector{<:Real})
     return transpose(first(jacobian(fdm, f, x))) * ȳ
 end
 
diff --git a/src/to_vec.jl b/src/to_vec.jl
index 002512d..b42feb3 100644
--- a/src/to_vec.jl
+++ b/src/to_vec.jl
@@ -4,15 +4,22 @@
 Transform `x` into a `Vector`, and return the vector, and a closure which inverts the
 transformation.
 """
-function to_vec(x::Number)
-    function Number_from_vec(x_vec)
+function to_vec(x::Real)
+    function Real_from_vec(x_vec)
         return first(x_vec)
     end
-    return [x], Number_from_vec
+    return [x], Real_from_vec
 end
 
-# Base case -- if x is already a Vector{<:Number} there's no conversion necessary.
-to_vec(x::Vector{<:Number}) = (x, identity)
+function to_vec(z::Complex)
+    function Complex_from_vec(z_vec)
+        return Complex(z_vec[1], z_vec[2])
+    end
+    return [real(z), imag(z)], Complex_from_vec
+end
+
+# Base case -- if x is already a Vector{<:Real} there's no conversion necessary.
+to_vec(x::Vector{<:Real}) = (x, identity)
 
 function to_vec(x::AbstractVector)
     x_vecs_and_backs = map(to_vec, x)


Test patch:
diff --git a/test/grad.jl b/test/grad.jl
index e47667a..44097ce 100644
--- a/test/grad.jl
+++ b/test/grad.jl
@@ -2,7 +2,7 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
 
 @testset "grad" begin
 
-    @testset "jvp(::$T)" for T in (Float64, ComplexF64)
+    @testset "jvp(::$T)" for T in (Float64,)
         rng, N, M, fdm = MersenneTwister(123456), 2, 3, central_fdm(5, 1)
         x, y = randn(rng, T, N), randn(rng, T, M)
         ẋ, ẏ = randn(rng, T, N), randn(rng, T, M)
@@ -14,7 +14,7 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
         @test ż_manual ≈ ż_multi
     end
 
-    @testset "grad(::$T)" for T in (Float64, ComplexF64)
+    @testset "grad(::$T)" for T in (Float64,)
         rng, fdm = MersenneTwister(123456), central_fdm(5, 1)
         x = randn(rng, T, 2)
         xc = copy(x)
@@ -43,9 +43,12 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
         @test xc == x
     end
 
-    @testset "jacobian / _jvp / _j′vp (::$T)" for T in (Float64, ComplexF64)
+    @testset "jacobian / _jvp / _j′vp (::$T)" for T in (Float64,)
         rng, P, Q, fdm = MersenneTwister(123456), 3, 2, central_fdm(5, 1)
-        ȳ, A, x, ẋ = randn(rng, T, P), randn(rng, T, P, Q), randn(rng, T, Q), randn(rng, T, Q)
+        ȳ = randn(rng, T, P)
+        A = randn(rng, T, P, Q)
+        x = randn(rng, T, Q)
+        ẋ = randn(rng, T, Q)
         Ac = copy(A)
 
         check_jac_and_jvp_and_j′vp(fdm, x->A * x, ȳ, x, ẋ, A)
@@ -54,7 +57,8 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
         @test Ac == A
 
         # Prevent regression against https://github.com/JuliaDiff/FiniteDifferences.jl/issues/67
-        @test first(jacobian(fdm, identity, x)) ≈ one(Matrix{T}(undef, length(x), length(x)))
+        J = first(jacobian(fdm, identity, x))
+        @test J ≈ one(Matrix{T}(undef, size(J)))
     end
 
     @testset "multi vars jacobian/grad" begin
@@ -113,7 +117,7 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
         end
     end
 
-    @testset "j′vp(::$T)" for T in (Float64, ComplexF64)
+    @testset "j′vp(::$T)" for T in (Float64,)
         rng, N, M, fdm = MersenneTwister(123456), 2, 3, central_fdm(5, 1)
         x, y = randn(rng, T, N), randn(rng, T, M)
         z̄ = randn(rng, T, N + M)
@@ -124,4 +128,53 @@ using FiniteDifferences: grad, jacobian, _jvp, jvp, j′vp, _j′vp, to_vec
         @test x̄ȳ_manual ≈ vcat(x̄ȳ_auto...)
         @test x̄ȳ_manual ≈ vcat(x̄ȳ_multi...)
     end
+
+    # Tests for complex numbers, to prevent regressions against
+    # https://github.com/JuliaDiff/FiniteDifferences.jl/pull/76 and
+    # https://github.com/JuliaDiff/FiniteDifferences.jl/issues/75
+    @testset "Complex correctness - $T" for T in (ComplexF64,)
+        rng = MersenneTwister(123456)
+        x = randn(rng, T)
+        y = randn(rng, T)
+        fdm = FiniteDifferences.Central(5, 1)
+
+        # Addition.
+        dx, dy = FiniteDifferences.jacobian(fdm, +, x, y)
+        @test dx ≈ [1 0; 0 1]
+        @test dy ≈ [1 0; 0 1]
+
+        # Negation.
+        dx, = FiniteDifferences.jacobian(fdm, -, x)
+        @test dx ≈ [-1 0; 0 -1]
+
+        # Multiplication.
+        dx, dy = FiniteDifferences.jacobian(fdm, *, x, y)
+        @test dx ≈ [real(y) -imag(y); imag(y) real(y)]
+        @test dy ≈ [real(x) -imag(x); imag(x) real(x)]
+
+        # Magnitude
+        dx, = FiniteDifferences.grad(fdm, abs2, x)
+        @test real(dx) ≈ 2 * real(x)
+        @test imag(dx) ≈ 2 * imag(x)
+
+        # Concatenate tangents / cotangents.
+        ẋ = randn(rng, T)
+        ẏ = randn(rng, T)
+        ẋẏ = vcat(first(to_vec(ẋ)), first(to_vec(ẏ)))
+        z̄ = randn(rng, T)
+        z̄_vec = first(to_vec(z̄))
+
+        # Check jvp against jacobian result.
+        f(x, y) = 5 * sin(x) * cos(y)
+        Jx, Jy = FiniteDifferences.jacobian(fdm, f, x, y)
+        ż = jvp(fdm, f, (x, ẋ), (y, ẏ))
+        ż_vec = [real(ż), imag(ż)]
+        ż_manual = hcat(Jx, Jy) * ẋẏ
+        @test ż_vec ≈ ż_manual
+
+        # Check j′vp against jacobian result.
+        x̄, ȳ = j′vp(fdm, f, z̄, x, y)
+        @test [real(x̄), imag(x̄)] ≈ Jx'z̄_vec
+        @test [real(ȳ), imag(ȳ)] ≈ Jy'z̄_vec
+    end
 end