From e87aba8017daa98c6bdf981f24a19e7ef63bb444 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 14:27:55 -0700 Subject: [PATCH 01/10] Use FiniteDifferences.to_vec to test jacobian --- src/testers.jl | 53 ++++++++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/testers.jl b/src/testers.jl index 20c8caf3..8e3553b3 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -115,58 +115,47 @@ All keyword arguments except for `fdm` and `fkwargs` are passed to `isapprox`. """ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), kwargs...) _ensure_not_running_on_functor(f, "test_scalar") - # z = x + im * y - # Ω = u(x, y) + im * v(x, y) Ω = f(z; fkwargs...) + vz, z_from_vec = to_vec(z) + # orthonormal tangent vectors + Δzs = z_from_vec.(eachcol(one(first(vz)) * I(length(vz)))) + # test jacobian using forward mode - Δx = one(z) - @testset "$f at $z, with tangent $Δx" begin - # check ∂u_∂x and (if Ω is complex) ∂v_∂x via forward mode - frule_test(f, (z, Δx); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) - if z isa Complex - # check that same tangent is produced for tangent 1.0 and 1.0 + 0.0im + @testset "$f at $z, with tangent $Δz" for (i, Δz) in enumerate(Δzs) + frule_test(f, (z, Δz); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) + if !isa(Δz, Real) && i == 1 + # check that same tangent is produced for tangent real(one(z)) and one(z) @test isapprox( - frule((Zero(), real(Δx)), f, z; fkwargs...)[2], - frule((Zero(), Δx), f, z; fkwargs...)[2], + frule((Zero(), real(Δz)), f, z; fkwargs...)[2], + frule((Zero(), Δz), f, z; fkwargs...)[2], rtol=rtol, atol=atol, kwargs..., ) end end - if z isa Complex - Δy = one(z) * im - @testset "$f at $z, with tangent $Δy" begin - # check ∂u_∂y and (if Ω is complex) ∂v_∂y via forward mode - frule_test(f, (z, Δy); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) - end - end + vΩ, Ω_from_vec = to_vec(Ω) + # orthonormal cotangent vectors + ΔΩs = Ω_from_vec.(eachcol(one(first(vΩ)) * I(length(vΩ)))) + + Δx = Δzs[1] # test jacobian transpose using reverse mode - Δu = one(Ω) - @testset "$f at $z, with cotangent $Δu" begin - # check ∂u_∂x and (if z is complex) ∂u_∂y via reverse mode - rrule_test(f, Δu, (z, Δx); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) - if Ω isa Complex - # check that same cotangent is produced for cotangent 1.0 and 1.0 + 0.0im + @testset "$f at $z, with cotangent $ΔΩ" for (i, ΔΩ) in enumerate(ΔΩs) + rrule_test(f, ΔΩ, (z, Δx); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) + if !isa(ΔΩ, Real) && i == 1 + # check that same cotangent is produced for cotangent real(one(Ω)) and one(Ω) back = rrule(f, z)[2] @test isapprox( - extern(back(real(Δu))[2]), - extern(back(Δu)[2]), + extern(back(real(ΔΩ))[2]), + extern(back(ΔΩ)[2]), rtol=rtol, atol=atol, kwargs..., ) end end - if Ω isa Complex - Δv = one(Ω) * im - @testset "$f at $z, with cotangent $Δv" begin - # check ∂v_∂x and (if z is complex) ∂v_∂y via reverse mode - rrule_test(f, Δv, (z, Δx); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) - end - end end """ From 17c6bd19475b314980bd371cc57cc3ed51580bf7 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 14:28:10 -0700 Subject: [PATCH 02/10] Explain requirements in docstring --- src/testers.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/testers.jl b/src/testers.jl index 8e3553b3..f460dd26 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -112,6 +112,9 @@ at input point `z` to confirm that there are correct `frule` and `rrule`s provid `fkwargs` are passed to `f` as keyword arguments. All keyword arguments except for `fdm` and `fkwargs` are passed to `isapprox`. + +To use this tester for a scalar type `MyNumber <: AbstractNumber`, +`FiniteDifferences.to_vec(::MyNumber)` must be implemented. """ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), kwargs...) _ensure_not_running_on_functor(f, "test_scalar") From 2f4ebbf9144b9a80f7145da0983ced87f82d495e Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 14:29:00 -0700 Subject: [PATCH 03/10] Add quaternion test --- Project.toml | 7 +++++++ test/runtests.jl | 1 + test/testers.jl | 27 +++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/Project.toml b/Project.toml index 423720e8..bb2ae835 100644 --- a/Project.toml +++ b/Project.toml @@ -14,4 +14,11 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" ChainRulesCore = "0.9.1" Compat = "3" FiniteDifferences = "0.10" +Quaternions = "0.4" julia = "1" + +[extras] +Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" + +[targets] +test = ["Quaternions"] diff --git a/test/runtests.jl b/test/runtests.jl index 93a2cf76..649e11ea 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,7 @@ using ChainRulesCore using ChainRulesTestUtils using LinearAlgebra +using Quaternions using Random using Test diff --git a/test/testers.jl b/test/testers.jl index 33e9a7c2..8c143ff9 100644 --- a/test/testers.jl +++ b/test/testers.jl @@ -40,6 +40,8 @@ function ChainRulesCore.frule((_, Δiter), ::typeof(iterfun), iter) return s, ∂s end +quatfun(q::Quaternion) = Quaternion(q.v3, 2 * q.v1, 3 * q.s, 4 * q.v2) + @testset "testers.jl" begin @testset "test_scalar" begin double(x) = 2x @@ -263,4 +265,29 @@ end frule_test(iterfun, (x, ẋ)) rrule_test(iterfun, randn(), (x, x̄)) end + + @testset "test quaternion non-standard scalar" begin + function FiniteDifferences.to_vec(q::Quaternion) + function Quaternion_from_vec(q_vec) + return Quaternion(q_vec[1], q_vec[2], q_vec[3], q_vec[4]) + end + return [q.s, q.v1, q.v2, q.v3], Quaternion_from_vec + end + + function ChainRulesCore.frule((_, Δq), ::typeof(quatfun), q) + ∂q = Quaternion(Δq) + return quatfun(q), Quaternion(∂q.v3, 2 * ∂q.v1, 3 * ∂q.s, 4 * ∂q.v2) + end + + function ChainRulesCore.rrule(::typeof(quatfun), q) + function quatfun_pullback(ΔΩ) + ∂Ω = Quaternion(ΔΩ) + return (NO_FIELDS, Quaternion(3 * ∂Ω.v2, 2 * ∂Ω.v1, 4 * ∂Ω.v3, ∂Ω.s)) + end + return quatfun(q), quatfun_pullback + end + + q = quatrand() + test_scalar(quatfun, q) + end end From 0b84838b9c2025aa183662cb9d6f9ccf3224328b Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 14:29:10 -0700 Subject: [PATCH 04/10] Use FiniteDifferences in test --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 649e11ea..8125266c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ using ChainRulesCore using ChainRulesTestUtils +using FiniteDifferences using LinearAlgebra using Quaternions using Random From c6e67da86e232a50f4cfeb02672ee23b76d1ff63 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 14:30:34 -0700 Subject: [PATCH 05/10] Increment version number --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index bb2ae835..383ceb9e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ChainRulesTestUtils" uuid = "cdddcdb0-9152-4a09-a978-84456f9df70a" -version = "0.5.2" +version = "0.5.3" [deps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" From 62c4d1a046d0d5f2c24310ed1c0faf1009aa3a3f Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 15:26:39 -0700 Subject: [PATCH 06/10] Don't call LinearAlgebra.I Not supported in Julia 1.0 --- src/testers.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/testers.jl b/src/testers.jl index f460dd26..a28a8696 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -121,8 +121,9 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), Ω = f(z; fkwargs...) vz, z_from_vec = to_vec(z) + ident_mat = Diagonal(ones(eltype(vz), length(vz))) # orthonormal tangent vectors - Δzs = z_from_vec.(eachcol(one(first(vz)) * I(length(vz)))) + Δzs = z_from_vec.(eachcol(ident_mat)) # test jacobian using forward mode @testset "$f at $z, with tangent $Δz" for (i, Δz) in enumerate(Δzs) @@ -141,7 +142,7 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), vΩ, Ω_from_vec = to_vec(Ω) # orthonormal cotangent vectors - ΔΩs = Ω_from_vec.(eachcol(one(first(vΩ)) * I(length(vΩ)))) + ΔΩs = Ω_from_vec.(eachcol(ident_mat)) Δx = Δzs[1] # test jacobian transpose using reverse mode From 21ed347bdbb791edc5127a0fdec3bdaf0228a185 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 6 Oct 2020 21:17:34 -0700 Subject: [PATCH 07/10] Don't use eachcol Not supported by Julia v1 --- src/testers.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/testers.jl b/src/testers.jl index a28a8696..a994fccc 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -121,9 +121,9 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), Ω = f(z; fkwargs...) vz, z_from_vec = to_vec(z) - ident_mat = Diagonal(ones(eltype(vz), length(vz))) # orthonormal tangent vectors - Δzs = z_from_vec.(eachcol(ident_mat)) + vz_basis = Diagonal(ones(eltype(vz), length(vz))) + Δzs = [z_from_vec(vz_basis[:, i]) for i in axes(vz_basis, 2)] # test jacobian using forward mode @testset "$f at $z, with tangent $Δz" for (i, Δz) in enumerate(Δzs) @@ -142,7 +142,8 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), vΩ, Ω_from_vec = to_vec(Ω) # orthonormal cotangent vectors - ΔΩs = Ω_from_vec.(eachcol(ident_mat)) + vΩ_basis = Diagonal(ones(eltype(vΩ), length(vΩ))) + ΔΩs = [Ω_from_vec(vΩ_basis[:, i]) for i in axes(vΩ_basis, 2)] Δx = Δzs[1] # test jacobian transpose using reverse mode From 1d275ac48b552ceb52ecf37a5163b9a31564e199 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 13 Oct 2020 11:44:01 -0700 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Lyndon White --- src/testers.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/testers.jl b/src/testers.jl index a994fccc..3e6bdbc6 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -113,7 +113,7 @@ at input point `z` to confirm that there are correct `frule` and `rrule`s provid `fkwargs` are passed to `f` as keyword arguments. All keyword arguments except for `fdm` and `fkwargs` are passed to `isapprox`. -To use this tester for a scalar type `MyNumber <: AbstractNumber`, +To use this tester for a scalar type `MyNumber <: Number`, `FiniteDifferences.to_vec(::MyNumber)` must be implemented. """ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), kwargs...) @@ -123,7 +123,7 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), vz, z_from_vec = to_vec(z) # orthonormal tangent vectors vz_basis = Diagonal(ones(eltype(vz), length(vz))) - Δzs = [z_from_vec(vz_basis[:, i]) for i in axes(vz_basis, 2)] + Δzs = [z_from_vec(@view vz_basis[:, i]) for i in axes(vz_basis, 2)] # test jacobian using forward mode @testset "$f at $z, with tangent $Δz" for (i, Δz) in enumerate(Δzs) From 828278c07df3a4ace6b79eb33ced20dba6b29d3e Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 13 Oct 2020 11:46:17 -0700 Subject: [PATCH 09/10] Add basis vectors helper function --- src/testers.jl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/testers.jl b/src/testers.jl index 3e6bdbc6..22a33453 100644 --- a/src/testers.jl +++ b/src/testers.jl @@ -100,6 +100,21 @@ function _make_jvp_call(fdm, f, xs, ẋs, ignores) return jvp(fdm, f2, sigargs...) end +""" + _basis_vectors(x::T) -> Vector{T} + +Get a set of basis (co)tangent vectors for `x`. + +This function assumes that the (co)tangent vectors are of the same type as `x` and requires +that `FiniteDifferences.to_vec` be implemented for inputs of the same type as `x`. +""" +function _basis_vectors(x) + v, from_vec = FiniteDifferences.to_vec(x) + basis_coords = Diagonal(ones(eltype(v), length(v))) + basis_vecs = [from_vec(@view basis_coords[:, i]) for i in axes(basis_coords, 2)] + return basis_vecs +end + """ test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=central_fdm(5, 1), fkwargs=NamedTuple(), kwargs...) @@ -120,10 +135,9 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), _ensure_not_running_on_functor(f, "test_scalar") Ω = f(z; fkwargs...) - vz, z_from_vec = to_vec(z) - # orthonormal tangent vectors - vz_basis = Diagonal(ones(eltype(vz), length(vz))) - Δzs = [z_from_vec(@view vz_basis[:, i]) for i in axes(vz_basis, 2)] + Δzs = _basis_vectors(z) + Δx = first(Δzs) + ΔΩs = _basis_vectors(Ω) # test jacobian using forward mode @testset "$f at $z, with tangent $Δz" for (i, Δz) in enumerate(Δzs) @@ -140,12 +154,6 @@ function test_scalar(f, z; rtol=1e-9, atol=1e-9, fdm=_fdm, fkwargs=NamedTuple(), end end - vΩ, Ω_from_vec = to_vec(Ω) - # orthonormal cotangent vectors - vΩ_basis = Diagonal(ones(eltype(vΩ), length(vΩ))) - ΔΩs = [Ω_from_vec(vΩ_basis[:, i]) for i in axes(vΩ_basis, 2)] - - Δx = Δzs[1] # test jacobian transpose using reverse mode @testset "$f at $z, with cotangent $ΔΩ" for (i, ΔΩ) in enumerate(ΔΩs) rrule_test(f, ΔΩ, (z, Δx); rtol=rtol, atol=atol, fdm=fdm, fkwargs=fkwargs, kwargs...) From 630feac9363dcebb494994f2dd51c2877b594100 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Tue, 13 Oct 2020 11:48:17 -0700 Subject: [PATCH 10/10] Increment version number --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 383ceb9e..b6741225 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ChainRulesTestUtils" uuid = "cdddcdb0-9152-4a09-a978-84456f9df70a" -version = "0.5.3" +version = "0.5.4" [deps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"