From 947382ae728d59fb7504d7d03c66627453603151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=5BA=1B=5BA=1B=5BA=1B=5BB=1B=5BB=1B=5BBwilltebbutt?= Date: Wed, 17 Jan 2018 22:27:51 +0000 Subject: [PATCH 01/22] initial empty commit From 93b60582f85c137705629c4e23e1e79a9702bcf9 Mon Sep 17 00:00:00 2001 From: willtebbutt Date: Sat, 11 Jul 2020 17:01:36 +0100 Subject: [PATCH 02/22] Implement rand_tangent and difference (#91) * Implement rand_tangent and differnece * Bump docs build version * Extra tests * Test on latest version * Remove old Manifest * Add tests / remove untested feature * Moves Foo to runtests * Modify testsets * Regenerate Manifest * Fix typo * Add TODO Co-authored-by: Nick Robinson * Fix docs hopefully * Don't export difference or rand_tangent * Bump patch * Fix tests Co-authored-by: Nick Robinson --- src/rand_tangent.jl | 40 ++++++++++++++++++++++ test/rand_tangent.jl | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/rand_tangent.jl create mode 100644 test/rand_tangent.jl diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl new file mode 100644 index 0000000..2fee1af --- /dev/null +++ b/src/rand_tangent.jl @@ -0,0 +1,40 @@ +""" + rand_tangent([rng::AbstractRNG,] x) + +Returns a randomly generated tangent vector appropriate for the primal value `x`. +""" +rand_tangent(x) = rand_tangent(Random.GLOBAL_RNG, x) + +rand_tangent(rng::AbstractRNG, x::Symbol) = DoesNotExist() +rand_tangent(rng::AbstractRNG, x::AbstractChar) = DoesNotExist() +rand_tangent(rng::AbstractRNG, x::AbstractString) = DoesNotExist() + +rand_tangent(rng::AbstractRNG, x::Integer) = DoesNotExist() + +rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} = randn(rng, T) + +rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) + +function rand_tangent(rng::AbstractRNG, x::T) where {T<:Tuple} + return Composite{T}(rand_tangent.(Ref(rng), x)...) +end + +function rand_tangent(rng::AbstractRNG, xs::T) where {T<:NamedTuple} + return Composite{T}(; map(x -> rand_tangent(rng, x), xs)...) +end + +function rand_tangent(rng::AbstractRNG, x::T) where {T} + if !isstructtype(T) + throw(ArgumentError("Non-struct types are not supported by this fallback.")) + end + + field_names = fieldnames(T) + if length(field_names) > 0 + tangents = map(field_names) do field_name + rand_tangent(rng, getfield(x, field_name)) + end + return Composite{T}(; NamedTuple{field_names}(tangents)...) + else + return NO_FIELDS + end +end diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl new file mode 100644 index 0000000..54de519 --- /dev/null +++ b/test/rand_tangent.jl @@ -0,0 +1,80 @@ +using FiniteDifferences: rand_tangent + +@testset "generate_tangent" begin + rng = MersenneTwister(123456) + + @testset "Primal: $(typeof(x)), Tangent: $T_tangent" for (x, T_tangent) in [ + + # Things without sensible tangents. + ("hi", DoesNotExist), + ('a', DoesNotExist), + (:a, DoesNotExist), + (true, DoesNotExist), + (4, DoesNotExist), + + # Numbers. + (5.0, Float64), + (5.0 + 0.4im, Complex{Float64}), + + # StridedArrays. + (randn(Float32, 3), Vector{Float32}), + (randn(Complex{Float64}, 2), Vector{Complex{Float64}}), + (randn(5, 4), Matrix{Float64}), + (randn(Complex{Float32}, 5, 4), Matrix{Complex{Float32}}), + ([randn(5, 4), 4.0], Vector{Any}), + + # Tuples. + ((4.0, ), Composite{Tuple{Float64}}), + ((5.0, randn(3)), Composite{Tuple{Float64, Vector{Float64}}}), + + # NamedTuples. + ((a=4.0, ), Composite{NamedTuple{(:a,), Tuple{Float64}}}), + ((a=5.0, b=1), Composite{NamedTuple{(:a, :b), Tuple{Float64, Int}}}), + + # structs. + (sin, typeof(NO_FIELDS)), + (Foo(5.0, 4, rand(rng, 3)), Composite{Foo}), + (Foo(4.0, 3, Foo(5.0, 2, 4)), Composite{Foo}), + + # LinearAlgebra types (also just structs). + ( + UpperTriangular(randn(3, 3)), + Composite{UpperTriangular{Float64, Matrix{Float64}}}, + ), + ( + Diagonal(randn(2)), + Composite{Diagonal{Float64, Vector{Float64}}}, + ), + ( + SVector{2, Float64}(1.0, 2.0), + Composite{typeof(SVector{2, Float64}(1.0, 2.0))}, + ), + ( + SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0), + Composite{typeof(SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0))}, + ), + ( + Symmetric(randn(2, 2)), + Composite{Symmetric{Float64, Matrix{Float64}}}, + ), + ( + Hermitian(randn(ComplexF64, 1, 1)), + Composite{Hermitian{ComplexF64, Matrix{ComplexF64}}}, + ), + ( + Adjoint(randn(ComplexF64, 3, 3)), + Composite{Adjoint{ComplexF64, Matrix{ComplexF64}}}, + ), + ( + Transpose(randn(3)), + Composite{Transpose{Float64, Vector{Float64}}}, + ), + ] + @test rand_tangent(rng, x) isa T_tangent + @test rand_tangent(x) isa T_tangent + @test x + rand_tangent(rng, x) isa typeof(x) + end + + # Ensure struct fallback errors for non-struct types. + @test_throws ArgumentError invoke(rand_tangent, Tuple{AbstractRNG, Any}, rng, 5.0) +end From 786983c4a826d2e91ace46a517e4e2bbf534fefa Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 29 Apr 2021 23:57:40 +0100 Subject: [PATCH 03/22] make rand_tangent of struct with no pertable fields return DoesNotExist --- src/rand_tangent.jl | 7 ++++++- test/rand_tangent.jl | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 2fee1af..b5a5476 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -33,7 +33,12 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} tangents = map(field_names) do field_name rand_tangent(rng, getfield(x, field_name)) end - return Composite{T}(; NamedTuple{field_names}(tangents)...) + if all(tangent isa DoesNotExist for tangent in tangents) + # if none of my fields can be perturbed then I can't be perturbed + return DoesNotExist() + else + Composite{T}(; NamedTuple{field_names}(tangents)...) + end else return NO_FIELDS end diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 54de519..2aa83f8 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -32,9 +32,13 @@ using FiniteDifferences: rand_tangent ((a=5.0, b=1), Composite{NamedTuple{(:a, :b), Tuple{Float64, Int}}}), # structs. - (sin, typeof(NO_FIELDS)), (Foo(5.0, 4, rand(rng, 3)), Composite{Foo}), (Foo(4.0, 3, Foo(5.0, 2, 4)), Composite{Foo}), + (sin, typeof(NO_FIELDS)), + # all fields DoesNotExist implies DoesNotExist + (Pair(:a, "b"), DoesNotExist), + (1:10, DoesNotExist), + (1:2:10, DoesNotExist), # LinearAlgebra types (also just structs). ( From 22e1fbd573bb33f656615a12ef5ed421606b58c7 Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Tue, 4 May 2021 11:36:19 +0100 Subject: [PATCH 04/22] copy `rand_tangent(::BigFloat)` from `ChainRulesTestUtils` (#155) --- src/rand_tangent.jl | 4 ++++ test/rand_tangent.jl | 1 + 2 files changed, 5 insertions(+) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 2fee1af..63e5a25 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -13,6 +13,10 @@ rand_tangent(rng::AbstractRNG, x::Integer) = DoesNotExist() rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} = randn(rng, T) +# TODO: right now Julia don't allow `randn(rng, BigFloat)` +# see: https://github.com/JuliaLang/julia/issues/17629 +rand_tangent(rng::AbstractRNG, ::BigFloat) = big(randn(rng)) + rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) function rand_tangent(rng::AbstractRNG, x::T) where {T<:Tuple} diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 54de519..3234322 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -15,6 +15,7 @@ using FiniteDifferences: rand_tangent # Numbers. (5.0, Float64), (5.0 + 0.4im, Complex{Float64}), + (big(5.0), BigFloat), # StridedArrays. (randn(Float32, 3), Vector{Float32}), From ebff52d9ee2e2a6f05c9e63bde2288fc444441fd Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Wed, 26 May 2021 15:04:22 +0100 Subject: [PATCH 05/22] rename differentials (#162) * ignore dev * bump version, compat * rename DoesNotExist * rename Composite to Tangent * rename Zero to ZeroTangent * update docs * docs manifest * Update docs/Project.toml Co-authored-by: Lyndon White Co-authored-by: Lyndon White --- src/rand_tangent.jl | 18 ++++++++--------- test/rand_tangent.jl | 46 ++++++++++++++++++++++---------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index c6967a9..fef39ef 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -5,11 +5,11 @@ Returns a randomly generated tangent vector appropriate for the primal value `x` """ rand_tangent(x) = rand_tangent(Random.GLOBAL_RNG, x) -rand_tangent(rng::AbstractRNG, x::Symbol) = DoesNotExist() -rand_tangent(rng::AbstractRNG, x::AbstractChar) = DoesNotExist() -rand_tangent(rng::AbstractRNG, x::AbstractString) = DoesNotExist() +rand_tangent(rng::AbstractRNG, x::Symbol) = NoTangent() +rand_tangent(rng::AbstractRNG, x::AbstractChar) = NoTangent() +rand_tangent(rng::AbstractRNG, x::AbstractString) = NoTangent() -rand_tangent(rng::AbstractRNG, x::Integer) = DoesNotExist() +rand_tangent(rng::AbstractRNG, x::Integer) = NoTangent() rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} = randn(rng, T) @@ -20,11 +20,11 @@ rand_tangent(rng::AbstractRNG, ::BigFloat) = big(randn(rng)) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) function rand_tangent(rng::AbstractRNG, x::T) where {T<:Tuple} - return Composite{T}(rand_tangent.(Ref(rng), x)...) + return Tangent{T}(rand_tangent.(Ref(rng), x)...) end function rand_tangent(rng::AbstractRNG, xs::T) where {T<:NamedTuple} - return Composite{T}(; map(x -> rand_tangent(rng, x), xs)...) + return Tangent{T}(; map(x -> rand_tangent(rng, x), xs)...) end function rand_tangent(rng::AbstractRNG, x::T) where {T} @@ -37,11 +37,11 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} tangents = map(field_names) do field_name rand_tangent(rng, getfield(x, field_name)) end - if all(tangent isa DoesNotExist for tangent in tangents) + if all(tangent isa NoTangent for tangent in tangents) # if none of my fields can be perturbed then I can't be perturbed - return DoesNotExist() + return NoTangent() else - Composite{T}(; NamedTuple{field_names}(tangents)...) + Tangent{T}(; NamedTuple{field_names}(tangents)...) end else return NO_FIELDS diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 3104b67..6f2b2f9 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -6,11 +6,11 @@ using FiniteDifferences: rand_tangent @testset "Primal: $(typeof(x)), Tangent: $T_tangent" for (x, T_tangent) in [ # Things without sensible tangents. - ("hi", DoesNotExist), - ('a', DoesNotExist), - (:a, DoesNotExist), - (true, DoesNotExist), - (4, DoesNotExist), + ("hi", NoTangent), + ('a', NoTangent), + (:a, NoTangent), + (true, NoTangent), + (4, NoTangent), # Numbers. (5.0, Float64), @@ -25,54 +25,54 @@ using FiniteDifferences: rand_tangent ([randn(5, 4), 4.0], Vector{Any}), # Tuples. - ((4.0, ), Composite{Tuple{Float64}}), - ((5.0, randn(3)), Composite{Tuple{Float64, Vector{Float64}}}), + ((4.0, ), Tangent{Tuple{Float64}}), + ((5.0, randn(3)), Tangent{Tuple{Float64, Vector{Float64}}}), # NamedTuples. - ((a=4.0, ), Composite{NamedTuple{(:a,), Tuple{Float64}}}), - ((a=5.0, b=1), Composite{NamedTuple{(:a, :b), Tuple{Float64, Int}}}), + ((a=4.0, ), Tangent{NamedTuple{(:a,), Tuple{Float64}}}), + ((a=5.0, b=1), Tangent{NamedTuple{(:a, :b), Tuple{Float64, Int}}}), # structs. - (Foo(5.0, 4, rand(rng, 3)), Composite{Foo}), - (Foo(4.0, 3, Foo(5.0, 2, 4)), Composite{Foo}), + (Foo(5.0, 4, rand(rng, 3)), Tangent{Foo}), + (Foo(4.0, 3, Foo(5.0, 2, 4)), Tangent{Foo}), (sin, typeof(NO_FIELDS)), - # all fields DoesNotExist implies DoesNotExist - (Pair(:a, "b"), DoesNotExist), - (1:10, DoesNotExist), - (1:2:10, DoesNotExist), + # all fields NoTangent implies NoTangent + (Pair(:a, "b"), NoTangent), + (1:10, NoTangent), + (1:2:10, NoTangent), # LinearAlgebra types (also just structs). ( UpperTriangular(randn(3, 3)), - Composite{UpperTriangular{Float64, Matrix{Float64}}}, + Tangent{UpperTriangular{Float64, Matrix{Float64}}}, ), ( Diagonal(randn(2)), - Composite{Diagonal{Float64, Vector{Float64}}}, + Tangent{Diagonal{Float64, Vector{Float64}}}, ), ( SVector{2, Float64}(1.0, 2.0), - Composite{typeof(SVector{2, Float64}(1.0, 2.0))}, + Tangent{typeof(SVector{2, Float64}(1.0, 2.0))}, ), ( SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0), - Composite{typeof(SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0))}, + Tangent{typeof(SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0))}, ), ( Symmetric(randn(2, 2)), - Composite{Symmetric{Float64, Matrix{Float64}}}, + Tangent{Symmetric{Float64, Matrix{Float64}}}, ), ( Hermitian(randn(ComplexF64, 1, 1)), - Composite{Hermitian{ComplexF64, Matrix{ComplexF64}}}, + Tangent{Hermitian{ComplexF64, Matrix{ComplexF64}}}, ), ( Adjoint(randn(ComplexF64, 3, 3)), - Composite{Adjoint{ComplexF64, Matrix{ComplexF64}}}, + Tangent{Adjoint{ComplexF64, Matrix{ComplexF64}}}, ), ( Transpose(randn(3)), - Composite{Transpose{Float64, Vector{Float64}}}, + Tangent{Transpose{Float64, Vector{Float64}}}, ), ] @test rand_tangent(rng, x) isa T_tangent From 89133ec7dae24eeecc6d098fbc468ea666e8e37d Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 27 May 2021 23:15:07 +0100 Subject: [PATCH 06/22] =Make rand_tangent on adjoint an transpose return natural --- src/rand_tangent.jl | 4 +++- test/rand_tangent.jl | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index fef39ef..46f7703 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -13,11 +13,13 @@ rand_tangent(rng::AbstractRNG, x::Integer) = NoTangent() rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} = randn(rng, T) -# TODO: right now Julia don't allow `randn(rng, BigFloat)` +# TODO: right now Julia don't allow `randn(rng, BigFloat)` # see: https://github.com/JuliaLang/julia/issues/17629 rand_tangent(rng::AbstractRNG, ::BigFloat) = big(randn(rng)) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) +rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) +rand_tangent(rng::AbstractRNG, x::Tangent) = tangent(rand_tangent(rng, parent(x))) function rand_tangent(rng::AbstractRNG, x::T) where {T<:Tuple} return Tangent{T}(rand_tangent.(Ref(rng), x)...) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 6f2b2f9..3f91221 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -24,6 +24,11 @@ using FiniteDifferences: rand_tangent (randn(Complex{Float32}, 5, 4), Matrix{Complex{Float32}}), ([randn(5, 4), 4.0], Vector{Any}), + # Wrapper Arrays + (randn(5, 4)', Adjoint{Float64, Matrix{Float64}}, + (transpose(randn(5, 4)), Transpose{Float64, Matrix{Float64}}, + + # Tuples. ((4.0, ), Tangent{Tuple{Float64}}), ((5.0, randn(3)), Tangent{Tuple{Float64, Vector{Float64}}}), From f4a97a713e38d633390fe2d8b5d8f39287bbc9fe Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 27 May 2021 23:24:15 +0100 Subject: [PATCH 07/22] typo --- src/rand_tangent.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 46f7703..cdca3aa 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -19,7 +19,7 @@ rand_tangent(rng::AbstractRNG, ::BigFloat) = big(randn(rng)) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) -rand_tangent(rng::AbstractRNG, x::Tangent) = tangent(rand_tangent(rng, parent(x))) +rand_tangent(rng::AbstractRNG, x::Transpose) = transpose(rand_tangent(rng, parent(x))) function rand_tangent(rng::AbstractRNG, x::T) where {T<:Tuple} return Tangent{T}(rand_tangent.(Ref(rng), x)...) From 6a09c8a60e8a77d7e9ed35c062a92e92806ec3cf Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Thu, 27 May 2021 23:30:47 +0100 Subject: [PATCH 08/22] braces --- test/rand_tangent.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 3f91221..9895d00 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -25,8 +25,8 @@ using FiniteDifferences: rand_tangent ([randn(5, 4), 4.0], Vector{Any}), # Wrapper Arrays - (randn(5, 4)', Adjoint{Float64, Matrix{Float64}}, - (transpose(randn(5, 4)), Transpose{Float64, Matrix{Float64}}, + (randn(5, 4)', Adjoint{Float64, Matrix{Float64}}), + (transpose(randn(5, 4)), Transpose{Float64, Matrix{Float64}}), # Tuples. From f71f77bd0d863320e103e2f22f47333c97296f02 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Fri, 28 May 2021 13:24:03 +0100 Subject: [PATCH 09/22] stop testing + behavour that is not defines in this package --- test/rand_tangent.jl | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 9895d00..876f91c 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -71,20 +71,19 @@ using FiniteDifferences: rand_tangent Hermitian(randn(ComplexF64, 1, 1)), Tangent{Hermitian{ComplexF64, Matrix{ComplexF64}}}, ), - ( - Adjoint(randn(ComplexF64, 3, 3)), - Tangent{Adjoint{ComplexF64, Matrix{ComplexF64}}}, - ), - ( - Transpose(randn(3)), - Tangent{Transpose{Float64, Vector{Float64}}}, - ), ] @test rand_tangent(rng, x) isa T_tangent @test rand_tangent(x) isa T_tangent - @test x + rand_tangent(rng, x) isa typeof(x) end - # Ensure struct fallback errors for non-struct types. - @test_throws ArgumentError invoke(rand_tangent, Tuple{AbstractRNG, Any}, rng, 5.0) + @testset "erroring cases" begin + # Ensure struct fallback errors for non-struct types. + @test_throws ArgumentError invoke(rand_tangent, Tuple{AbstractRNG, Any}, rng, 5.0) + end + + @testset "compsition of addition" begin + x = Foo(1.5, 2, Foo(1.1, 3, [1.7, 1.4, 0.9])) + @test x + rand_tangent(x) isa typeof(x) + @test x + (rand_tangent(x) + rand_tangent(x)) isa typeof(x) + end end From e7767990662cb21931fa7fa56e7f33a41fb8d7ed Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Tue, 1 Jun 2021 12:32:53 +0100 Subject: [PATCH 10/22] Replace `NO_FIELDS` by `NoTangent()` (#163) --- src/rand_tangent.jl | 18 +++++++----------- test/rand_tangent.jl | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index cdca3aa..4ca2548 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -35,17 +35,13 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} end field_names = fieldnames(T) - if length(field_names) > 0 - tangents = map(field_names) do field_name - rand_tangent(rng, getfield(x, field_name)) - end - if all(tangent isa NoTangent for tangent in tangents) - # if none of my fields can be perturbed then I can't be perturbed - return NoTangent() - else - Tangent{T}(; NamedTuple{field_names}(tangents)...) - end + tangents = map(field_names) do field_name + rand_tangent(rng, getfield(x, field_name)) + end + if all(tangent isa NoTangent for tangent in tangents) + # if none of my fields can be perturbed then I can't be perturbed + return NoTangent() else - return NO_FIELDS + Tangent{T}(; NamedTuple{field_names}(tangents)...) end end diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 876f91c..92a2d50 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -40,7 +40,7 @@ using FiniteDifferences: rand_tangent # structs. (Foo(5.0, 4, rand(rng, 3)), Tangent{Foo}), (Foo(4.0, 3, Foo(5.0, 2, 4)), Tangent{Foo}), - (sin, typeof(NO_FIELDS)), + (sin, NoTangent), # all fields NoTangent implies NoTangent (Pair(:a, "b"), NoTangent), (1:10, NoTangent), From 460fc3d23028efe3e1253eef865a4396fe30dc39 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Fri, 28 May 2021 19:50:27 +0100 Subject: [PATCH 11/22] make rand_tangent give only nice numbers --- src/rand_tangent.jl | 17 ++++++++++++++--- test/rand_tangent.jl | 8 ++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 4ca2548..8450f50 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -1,7 +1,9 @@ """ rand_tangent([rng::AbstractRNG,] x) -Returns a randomly generated tangent vector appropriate for the primal value `x`. +Returns a arbitary tangent vector _appropriate_ for the primal value `x`. +Note that despite the name, no promises on the statistical randomness are made. +Rather it is an arbitary value, that is generated using the `rng`. """ rand_tangent(x) = rand_tangent(Random.GLOBAL_RNG, x) @@ -11,11 +13,20 @@ rand_tangent(rng::AbstractRNG, x::AbstractString) = NoTangent() rand_tangent(rng::AbstractRNG, x::Integer) = NoTangent() -rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} = randn(rng, T) +# Try and make nice numbers with short decimal representations for good error messages +# while also not biasing the sample space too much +function rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} + return round(8randn(rng, T), sigdigits=6, base=2) +end +rand_tangent(rng::AbstractRNG, x::Float64) = rand(rng, -9:0.01:9) +function rand_tangent(rng::AbstractRNG, x::ComplexF64) + return ComplexF64(rand(rng, -9:0.1:9), rand(rng, -9:0.1:9)) +end + # TODO: right now Julia don't allow `randn(rng, BigFloat)` # see: https://github.com/JuliaLang/julia/issues/17629 -rand_tangent(rng::AbstractRNG, ::BigFloat) = big(randn(rng)) +rand_tangent(rng::AbstractRNG, ::BigFloat) = big(rand_tangent(rng, Float64)) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 92a2d50..0331f24 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -86,4 +86,12 @@ using FiniteDifferences: rand_tangent @test x + rand_tangent(x) isa typeof(x) @test x + (rand_tangent(x) + rand_tangent(x)) isa typeof(x) end + + @testset "niceness of printing" begin + for i in 1:50 + @test length(string(rand_tangent(1.0))) <= 6 + @test length(string(rand_tangent(1.0 + 1.0im))) <= 12 + @test length(string(rand_tangent(1f0))) <= 12 + end + end end From a7a416c8df2f01c40dd47ec5b0535454c096f5f0 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Fri, 28 May 2021 20:29:25 +0100 Subject: [PATCH 12/22] fix rand_tangent(::BigFloat) --- src/rand_tangent.jl | 8 +++----- test/rand_tangent.jl | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 8450f50..e7a0760 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -16,17 +16,15 @@ rand_tangent(rng::AbstractRNG, x::Integer) = NoTangent() # Try and make nice numbers with short decimal representations for good error messages # while also not biasing the sample space too much function rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} - return round(8randn(rng, T), sigdigits=6, base=2) + return round(8randn(rng, T), sigdigits=5, base=2) end rand_tangent(rng::AbstractRNG, x::Float64) = rand(rng, -9:0.01:9) function rand_tangent(rng::AbstractRNG, x::ComplexF64) return ComplexF64(rand(rng, -9:0.1:9), rand(rng, -9:0.1:9)) end - -# TODO: right now Julia don't allow `randn(rng, BigFloat)` -# see: https://github.com/JuliaLang/julia/issues/17629 -rand_tangent(rng::AbstractRNG, ::BigFloat) = big(rand_tangent(rng, Float64)) +#BigFloat/MPFR is finicky about short numbers, this doesn't always work as well as it should +rand_tangent(rng::AbstractRNG, ::BigFloat) = round(big(8randn(rng)), sigdigits=5, base=2) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 0331f24..6beb1c4 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -92,6 +92,7 @@ using FiniteDifferences: rand_tangent @test length(string(rand_tangent(1.0))) <= 6 @test length(string(rand_tangent(1.0 + 1.0im))) <= 12 @test length(string(rand_tangent(1f0))) <= 12 + @test length(string(rand_tangent(big"1.0"))) <= 8 end end end From 00596e3e221f6caa4b0087c8a6aa01ad2a0b2f0b Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Tue, 1 Jun 2021 12:40:30 +0100 Subject: [PATCH 13/22] Explain why scaling randn --- src/rand_tangent.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index e7a0760..930a3c7 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -16,7 +16,8 @@ rand_tangent(rng::AbstractRNG, x::Integer) = NoTangent() # Try and make nice numbers with short decimal representations for good error messages # while also not biasing the sample space too much function rand_tangent(rng::AbstractRNG, x::T) where {T<:Number} - return round(8randn(rng, T), sigdigits=5, base=2) + # multiply by 9 to give a bigger range of values tested: no so tightly clustered around 0. + return round(9 * randn(rng, T), sigdigits=5, base=2) end rand_tangent(rng::AbstractRNG, x::Float64) = rand(rng, -9:0.01:9) function rand_tangent(rng::AbstractRNG, x::ComplexF64) @@ -24,7 +25,9 @@ function rand_tangent(rng::AbstractRNG, x::ComplexF64) end #BigFloat/MPFR is finicky about short numbers, this doesn't always work as well as it should -rand_tangent(rng::AbstractRNG, ::BigFloat) = round(big(8randn(rng)), sigdigits=5, base=2) + +# multiply by 9 to give a bigger range of values tested: no so tightly clustered around 0. +rand_tangent(rng::AbstractRNG, ::BigFloat) = round(big(9 * randn(rng)), sigdigits=5, base=2) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) From 12033c677d910d09bee9df48797fd6edd060a4cd Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Tue, 1 Jun 2021 13:01:10 +0100 Subject: [PATCH 14/22] only test printing length on 1.6 (also relax length for bigfloats) --- test/rand_tangent.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 6beb1c4..8863279 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -87,12 +87,13 @@ using FiniteDifferences: rand_tangent @test x + (rand_tangent(x) + rand_tangent(x)) isa typeof(x) end - @testset "niceness of printing" begin + # Julia 1.6 changed to using Ryu printing algorithm and seems better at printing short + VERSION > v"1.6" && @testset "niceness of printing" begin for i in 1:50 @test length(string(rand_tangent(1.0))) <= 6 @test length(string(rand_tangent(1.0 + 1.0im))) <= 12 @test length(string(rand_tangent(1f0))) <= 12 - @test length(string(rand_tangent(big"1.0"))) <= 8 + @test length(string(rand_tangent(big"1.0"))) <= 9 end end end From 231e6851510390be2cbffe234d7211904ee6c38e Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Tue, 1 Jun 2021 14:39:57 +0100 Subject: [PATCH 15/22] Relax big further --- test/rand_tangent.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 8863279..f5815e8 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -93,7 +93,7 @@ using FiniteDifferences: rand_tangent @test length(string(rand_tangent(1.0))) <= 6 @test length(string(rand_tangent(1.0 + 1.0im))) <= 12 @test length(string(rand_tangent(1f0))) <= 12 - @test length(string(rand_tangent(big"1.0"))) <= 9 + @test length(string(rand_tangent(big"1.0"))) <= 12 end end end From bc71ac112f8b58a10df0d98a55bf4bed9c846579 Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Tue, 8 Jun 2021 13:36:45 +0100 Subject: [PATCH 16/22] Add `rand_tangent` for types (#172) --- src/rand_tangent.jl | 3 +++ test/rand_tangent.jl | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 930a3c7..96d4b6a 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -57,3 +57,6 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} Tangent{T}(; NamedTuple{field_names}(tangents)...) end end + +rand_tangent(rng::AbstractRNG, ::Type) = NoTangent() +rand_tangent(rng::AbstractRNG, ::Module) = NoTangent() diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index f5815e8..a7f861c 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -11,6 +11,15 @@ using FiniteDifferences: rand_tangent (:a, NoTangent), (true, NoTangent), (4, NoTangent), + (FiniteDifferences, NoTangent), # Module object + # Types (not instances of type) + (Foo, NoTangent), + (Union{Int, Foo}, NoTangent), + (Union{Int, Foo}, NoTangent), + (Vector, NoTangent), + (Vector{Float64}, NoTangent), + (Integer, NoTangent), + (Type{<:Real}, NoTangent), # Numbers. (5.0, Float64), From 61a83edf67d289ce6688de7db980bf7e3d021203 Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Wed, 9 Jun 2021 16:31:35 +0100 Subject: [PATCH 17/22] Revert "Add `rand_tangent` for types (#172)" This reverts commit bc71ac112f8b58a10df0d98a55bf4bed9c846579. --- src/rand_tangent.jl | 3 --- test/rand_tangent.jl | 9 --------- 2 files changed, 12 deletions(-) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 96d4b6a..930a3c7 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -57,6 +57,3 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} Tangent{T}(; NamedTuple{field_names}(tangents)...) end end - -rand_tangent(rng::AbstractRNG, ::Type) = NoTangent() -rand_tangent(rng::AbstractRNG, ::Module) = NoTangent() diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index a7f861c..f5815e8 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -11,15 +11,6 @@ using FiniteDifferences: rand_tangent (:a, NoTangent), (true, NoTangent), (4, NoTangent), - (FiniteDifferences, NoTangent), # Module object - # Types (not instances of type) - (Foo, NoTangent), - (Union{Int, Foo}, NoTangent), - (Union{Int, Foo}, NoTangent), - (Vector, NoTangent), - (Vector{Float64}, NoTangent), - (Integer, NoTangent), - (Type{<:Real}, NoTangent), # Numbers. (5.0, Float64), From 96b1abd05679272adf1217c77ce28e7b0a4a7944 Mon Sep 17 00:00:00 2001 From: Miha Zgubic Date: Wed, 9 Jun 2021 16:33:53 +0100 Subject: [PATCH 18/22] Revert "Revert "Add `rand_tangent` for types (#172)"" This reverts commit 61a83edf67d289ce6688de7db980bf7e3d021203. --- src/rand_tangent.jl | 3 +++ test/rand_tangent.jl | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 930a3c7..96d4b6a 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -57,3 +57,6 @@ function rand_tangent(rng::AbstractRNG, x::T) where {T} Tangent{T}(; NamedTuple{field_names}(tangents)...) end end + +rand_tangent(rng::AbstractRNG, ::Type) = NoTangent() +rand_tangent(rng::AbstractRNG, ::Module) = NoTangent() diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index f5815e8..a7f861c 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -11,6 +11,15 @@ using FiniteDifferences: rand_tangent (:a, NoTangent), (true, NoTangent), (4, NoTangent), + (FiniteDifferences, NoTangent), # Module object + # Types (not instances of type) + (Foo, NoTangent), + (Union{Int, Foo}, NoTangent), + (Union{Int, Foo}, NoTangent), + (Vector, NoTangent), + (Vector{Float64}, NoTangent), + (Integer, NoTangent), + (Type{<:Real}, NoTangent), # Numbers. (5.0, Float64), From b8d55734e041a23f63be089262453a922f001c47 Mon Sep 17 00:00:00 2001 From: Alex Robson Date: Thu, 1 Jul 2021 11:24:00 +0100 Subject: [PATCH 19/22] Add rand_tangent(rng::AbstractRNG, x::StridedArray{T, 0}) where {T} --- src/rand_tangent.jl | 1 + test/rand_tangent.jl | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/rand_tangent.jl b/src/rand_tangent.jl index 96d4b6a..adb0ac8 100644 --- a/src/rand_tangent.jl +++ b/src/rand_tangent.jl @@ -29,6 +29,7 @@ end # multiply by 9 to give a bigger range of values tested: no so tightly clustered around 0. rand_tangent(rng::AbstractRNG, ::BigFloat) = round(big(9 * randn(rng)), sigdigits=5, base=2) +rand_tangent(rng::AbstractRNG, x::StridedArray{T, 0}) where {T} = fill(rand_tangent(x[1])) rand_tangent(rng::AbstractRNG, x::StridedArray) = rand_tangent.(Ref(rng), x) rand_tangent(rng::AbstractRNG, x::Adjoint) = adjoint(rand_tangent(rng, parent(x))) rand_tangent(rng::AbstractRNG, x::Transpose) = transpose(rand_tangent(rng, parent(x))) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index a7f861c..6020286 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -27,6 +27,8 @@ using FiniteDifferences: rand_tangent (big(5.0), BigFloat), # StridedArrays. + (fill(randn(Float32)), Array{Float32, 0}), + (fill(randn(Float64)), Array{Float64, 0}), (randn(Float32, 3), Vector{Float32}), (randn(Complex{Float64}, 2), Vector{Complex{Float64}}), (randn(5, 4), Matrix{Float64}), From b4bedebd3b3d76361a98128642795723e063c748 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Wed, 21 Jul 2021 20:25:58 +0100 Subject: [PATCH 20/22] include rand_tangent --- src/ChainRulesTestUtils.jl | 1 + test/runtests.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ChainRulesTestUtils.jl b/src/ChainRulesTestUtils.jl index 1dd306c..eba4e0a 100644 --- a/src/ChainRulesTestUtils.jl +++ b/src/ChainRulesTestUtils.jl @@ -20,6 +20,7 @@ __init__() = init_test_inferred_setting!() include("global_config.jl") +include("rand_tangent.jl") include("generate_tangent.jl") include("data_generation.jl") include("iterator.jl") diff --git a/test/runtests.jl b/test/runtests.jl index 845d696..10f73c6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,6 +14,7 @@ ChainRulesTestUtils.TEST_INFERRED[] = true include("check_result.jl") include("testers.jl") include("data_generation.jl") + include("rand_tangent.jl") include("deprecated.jl") end From 30f4c037beb2781e7f36c3fc5ebd059de3fefbd9 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Wed, 21 Jul 2021 20:29:22 +0100 Subject: [PATCH 21/22] Don't import rand_tangent from FiniteDifferences --- src/ChainRulesTestUtils.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ChainRulesTestUtils.jl b/src/ChainRulesTestUtils.jl index eba4e0a..fad458a 100644 --- a/src/ChainRulesTestUtils.jl +++ b/src/ChainRulesTestUtils.jl @@ -9,8 +9,6 @@ using LinearAlgebra using Random using Test -import FiniteDifferences: rand_tangent - export TestIterator export test_approx, test_scalar, test_frule, test_rrule, generate_well_conditioned_matrix export ⊢ From fc3fb1eeea52bf1d32720208cfe437a261d029b4 Mon Sep 17 00:00:00 2001 From: Lyndon White Date: Wed, 21 Jul 2021 21:29:05 +0100 Subject: [PATCH 22/22] Fix up tests that don't work because of move --- src/ChainRulesTestUtils.jl | 2 +- test/iterator.jl | 2 +- test/rand_tangent.jl | 30 +++++++++++++----------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/ChainRulesTestUtils.jl b/src/ChainRulesTestUtils.jl index fad458a..03b794e 100644 --- a/src/ChainRulesTestUtils.jl +++ b/src/ChainRulesTestUtils.jl @@ -11,7 +11,7 @@ using Test export TestIterator export test_approx, test_scalar, test_frule, test_rrule, generate_well_conditioned_matrix -export ⊢ +export ⊢, rand_tangent export @maybe_inferred __init__() = init_test_inferred_setting!() diff --git a/test/iterator.jl b/test/iterator.jl index bbd9f19..df50a9b 100644 --- a/test/iterator.jl +++ b/test/iterator.jl @@ -88,7 +88,7 @@ @testset "rand_tangent" begin data = randn(2, 3, 4) iter = TestIterator(data, Base.SizeUnknown(), Base.EltypeUnknown()) - ∂iter = FiniteDifferences.rand_tangent(iter) + ∂iter = rand_tangent(iter) @test ∂iter isa typeof(iter) @test size(∂iter.data) == size(iter.data) @test eltype(∂iter.data) === eltype(iter.data) diff --git a/test/rand_tangent.jl b/test/rand_tangent.jl index 6020286..ed479a5 100644 --- a/test/rand_tangent.jl +++ b/test/rand_tangent.jl @@ -1,6 +1,10 @@ -using FiniteDifferences: rand_tangent - -@testset "generate_tangent" begin +# Test struct for `rand_tangent` and `difference`. +struct Bar + a::Float64 + b::Int + c::Any + end +@testset "rand_tangent" begin rng = MersenneTwister(123456) @testset "Primal: $(typeof(x)), Tangent: $T_tangent" for (x, T_tangent) in [ @@ -13,9 +17,9 @@ using FiniteDifferences: rand_tangent (4, NoTangent), (FiniteDifferences, NoTangent), # Module object # Types (not instances of type) - (Foo, NoTangent), - (Union{Int, Foo}, NoTangent), - (Union{Int, Foo}, NoTangent), + (Bar, NoTangent), + (Union{Int, Bar}, NoTangent), + (Union{Int, Bar}, NoTangent), (Vector, NoTangent), (Vector{Float64}, NoTangent), (Integer, NoTangent), @@ -49,8 +53,8 @@ using FiniteDifferences: rand_tangent ((a=5.0, b=1), Tangent{NamedTuple{(:a, :b), Tuple{Float64, Int}}}), # structs. - (Foo(5.0, 4, rand(rng, 3)), Tangent{Foo}), - (Foo(4.0, 3, Foo(5.0, 2, 4)), Tangent{Foo}), + (Bar(5.0, 4, rand(rng, 3)), Tangent{Bar}), + (Bar(4.0, 3, Bar(5.0, 2, 4)), Tangent{Bar}), (sin, NoTangent), # all fields NoTangent implies NoTangent (Pair(:a, "b"), NoTangent), @@ -66,14 +70,6 @@ using FiniteDifferences: rand_tangent Diagonal(randn(2)), Tangent{Diagonal{Float64, Vector{Float64}}}, ), - ( - SVector{2, Float64}(1.0, 2.0), - Tangent{typeof(SVector{2, Float64}(1.0, 2.0))}, - ), - ( - SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0), - Tangent{typeof(SMatrix{2, 2, ComplexF64}(1.0, 2.0, 3.0, 4.0))}, - ), ( Symmetric(randn(2, 2)), Tangent{Symmetric{Float64, Matrix{Float64}}}, @@ -93,7 +89,7 @@ using FiniteDifferences: rand_tangent end @testset "compsition of addition" begin - x = Foo(1.5, 2, Foo(1.1, 3, [1.7, 1.4, 0.9])) + x = Bar(1.5, 2, Bar(1.1, 3, [1.7, 1.4, 0.9])) @test x + rand_tangent(x) isa typeof(x) @test x + (rand_tangent(x) + rand_tangent(x)) isa typeof(x) end