diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 9ccbd196..4ae78874 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -45,9 +45,6 @@ jobs: coverage: true allow_reresolve: false force_latest_compatible_version: false - env: - # `julia-downgrade-compat` promotes weakdeps to deps, so extensions always load - DOWNGRADE_TEST: "true" - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v7 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 90022731..1ad1ff41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,6 @@ jobs: - uses: julia-actions/cache@v3 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - env: - JULIA_NUM_THREADS: 4 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v7 with: diff --git a/test/ChainRulesTests.jl b/test/ChainRulesTests.jl index 7433d78b..1bfc7b3a 100644 --- a/test/ChainRulesTests.jl +++ b/test/ChainRulesTests.jl @@ -1,5 +1,3 @@ -module ChainRulesTest - using LinearAlgebra using ChainRulesCore using DiffResults @@ -283,5 +281,3 @@ module IsolatedModuleForTestingScoping end # end of SubModule end # end of IsolatedModuleForTestingScoping - -end diff --git a/test/MacrosTests.jl b/test/MacrosTests.jl index 6e4ad358..082f3946 100644 --- a/test/MacrosTests.jl +++ b/test/MacrosTests.jl @@ -1,5 +1,3 @@ -module MacrosTests - using ReverseDiff, ForwardDiff, Test, StaticArrays using ForwardDiff: Dual, Partials, partials @@ -344,5 +342,3 @@ using ReverseDiff: @grad, TrackedReal, TrackedVector, TrackedMatrix, TrackedArra @test g1 == g2 @test custom_grad_called end - -end # module diff --git a/test/PrecisionTests.jl b/test/PrecisionTests.jl index 6ba039a3..9bcec747 100644 --- a/test/PrecisionTests.jl +++ b/test/PrecisionTests.jl @@ -1,5 +1,3 @@ -module PrecisionTests - using ReverseDiff, Test, LinearAlgebra, Statistics include("utils.jl") @@ -53,5 +51,3 @@ for (n, d) in ((1.0e-200, 1.0e-200), (1.0e200, 1.0e200)) @test ReverseDiff.gradient(f, [d]) ≈ [∂] rtol = 8 * eps(Float64) end end - -end # module diff --git a/test/Project.toml b/test/Project.toml index 80006f85..23b91319 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,6 +9,7 @@ IrrationalConstants = "92d709cd-6900-40b7-9082-c6be49f344b6" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" @@ -27,6 +28,7 @@ IrrationalConstants = "0.2" LinearAlgebra = "1" LogExpFunctions = "1" NaNMath = "1" +ParallelTestRunner = "2.0.1" Random = "1" SpecialFunctions = "2" StaticArrays = "1.6.4" diff --git a/test/TapeTests.jl b/test/TapeTests.jl index d22d1fa5..00b7e1cf 100644 --- a/test/TapeTests.jl +++ b/test/TapeTests.jl @@ -1,5 +1,3 @@ -module TapeTests - using ReverseDiff, Test using ReverseDiff: SpecialInstruction, ScalarInstruction, NULL_TAPE @@ -62,5 +60,3 @@ for Instr in (SpecialInstruction, ScalarInstruction) @test length(NULL_TAPE) == 0 @test repr(MIME"text/plain"(), NULL_TAPE) == "0-element InstructionTape" end - -end # module diff --git a/test/ThreadsTests.jl b/test/ThreadsTests.jl new file mode 100644 index 00000000..7a77358c --- /dev/null +++ b/test/ThreadsTests.jl @@ -0,0 +1,32 @@ +using ReverseDiff, Test + +# Run on a multithreaded worker, see `test_worker` in runtests.jl +@test Threads.nthreads() > 1 + +@testset "recording from multiple threads (#218)" begin + function g(x, i) + acc = zero(eltype(x)) + for _ in 1:100 + acc += (i * x[1] - x[2])^2 + end + return acc + sum(abs2, i .* x) + end + function f(x) + s = Vector{Any}(undef, 64) + @sync for i in eachindex(s) + Threads.@spawn s[i] = g(x, i) + end + return sum(s) + end + f_serial(x) = sum(i -> g(x, i), 1:64) + + x = [1.0, 2.0] + ∇f = ReverseDiff.gradient(f_serial, x) + for _ in 1:20 + @test ReverseDiff.gradient(f, x) ≈ ∇f + end + + # tape replay + tape = ReverseDiff.compile(ReverseDiff.GradientTape(f, x)) + @test ReverseDiff.gradient!(similar(x), tape, x) ≈ ∇f +end diff --git a/test/TrackedTests.jl b/test/TrackedTests.jl index 863df5fe..97be9f9f 100644 --- a/test/TrackedTests.jl +++ b/test/TrackedTests.jl @@ -1,5 +1,3 @@ -module TrackedTests - using ReverseDiff, Test using ReverseDiff: TrackedReal, TrackedArray @@ -652,6 +650,7 @@ instr = first(recorded) @test instr.cache === nothing for T in (UInt, Int) + local ta_sub, idx, recorded, instr ta_sub = ta[:, T(1):T(2)] idx = ReverseDiff.index_iterable(axes(ta), (:, T(1):T(2))) @test collect(idx) == [(i, j) for i in 1:3, j in 1:2] @@ -897,5 +896,3 @@ end @test occursin(r"^TrackedReal<\w{3}>\(1\.0, 0\.0, ---, ---\)$", repr(TrackedReal(1.0, 0.0))) ta = TrackedArray([1.0, 2.0], [0.0, 3.0], InstructionTape()) @test occursin(r"^TrackedReal<\w{3}>\(2\.0, 3\.0, \w{3}, 2, \w{3}\)$", repr(ta[2])) - -end diff --git a/test/api/ConfigTests.jl b/test/api/ConfigTests.jl index d5e4e507..5c6971b2 100644 --- a/test/api/ConfigTests.jl +++ b/test/api/ConfigTests.jl @@ -1,5 +1,3 @@ -module ConfigTests - using ReverseDiff, Test include(joinpath(dirname(@__FILE__), "../utils.jl")) @@ -75,5 +73,3 @@ cfg = HessianConfig(DiffResults.HessianResult(y), x, gtp, jtp) # These two act weird: work locally but fail in test mode #@test issimilar(cfg.gradient_config, GradientConfig(track(x, Int), gtp)) #@test issimilar(cfg.jacobian_config, JacobianConfig(y, x, jtp)) - -end # module diff --git a/test/api/GradientTests.jl b/test/api/GradientTests.jl index e101ab7f..8aa67c70 100644 --- a/test/api/GradientTests.jl +++ b/test/api/GradientTests.jl @@ -1,5 +1,3 @@ -module GradientTests - using DiffTests, ForwardDiff, ReverseDiff, StaticArrays, Test, LinearAlgebra include(joinpath(dirname(@__FILE__), "../utils.jl")) @@ -489,33 +487,3 @@ end # a `Float64` buffer throws instead of zeroing the gradient @test_throws ArgumentError ReverseDiff.gradient(W -> loss(zeros(3, 5), W, H), W) end - -@testset "recording from multiple threads (#218)" begin - function g(x, i) - acc = zero(eltype(x)) - for _ in 1:100 - acc += (i * x[1] - x[2])^2 - end - return acc + sum(abs2, i .* x) - end - function f(x) - s = Vector{Any}(undef, 64) - @sync for i in eachindex(s) - Threads.@spawn s[i] = g(x, i) - end - return sum(s) - end - f_serial(x) = sum(i -> g(x, i), 1:64) - - x = [1.0, 2.0] - ∇f = ReverseDiff.gradient(f_serial, x) - for _ in 1:20 - @test ReverseDiff.gradient(f, x) ≈ ∇f - end - - # tape replay - tape = ReverseDiff.compile(ReverseDiff.GradientTape(f, x)) - @test ReverseDiff.gradient!(similar(x), tape, x) ≈ ∇f -end - -end # module diff --git a/test/api/HessianTests.jl b/test/api/HessianTests.jl index 73d5ffdb..de4d8708 100644 --- a/test/api/HessianTests.jl +++ b/test/api/HessianTests.jl @@ -1,5 +1,3 @@ -module HessianTests - using DiffTests, ForwardDiff, ReverseDiff, StaticArrays, Test include(joinpath(dirname(@__FILE__), "../utils.jl")) @@ -130,5 +128,3 @@ f251(x) = sum(abs2, x) @test DiffResults.hessian(result) == hess end end - -end # module diff --git a/test/api/JacobianTests.jl b/test/api/JacobianTests.jl index 8c09f88f..2d4927e7 100644 --- a/test/api/JacobianTests.jl +++ b/test/api/JacobianTests.jl @@ -1,5 +1,3 @@ -module JacobianTests - using DiffTests, ForwardDiff, ReverseDiff, StaticArrays, Test include(joinpath(dirname(@__FILE__), "../utils.jl")) @@ -269,57 +267,6 @@ for f in DiffTests.BINARY_MATRIX_TO_MATRIX_FUNCS test_binary_jacobian(f, rand(5, 5), rand(5, 5)) end -for f in (DiffTests.ARRAY_TO_ARRAY_FUNCS..., DiffTests.MATRIX_TO_MATRIX_FUNCS...) - test_println("ARRAY_TO_ARRAY_FUNCS + MATRIX_TO_MATRIX_FUNCS", f) - - x = rand(5, 5) - test = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(f, y), x) - - # without JacobianTape - - J = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(f, y), x) - test_approx(J, test) - - # with JacobianTape - - tp = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(f, y), rand(eltype(x), size(x))) - J = ReverseDiff.jacobian!(tp, x) - test_approx(J, test) -end - -for f in DiffTests.BINARY_MATRIX_TO_MATRIX_FUNCS - test_println("BINARY_MATRIX_TO_MATRIX_FUNCS", f) - - a, b = rand(5, 5), rand(5, 5) - - test_val = f(a, b) - test_a = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(x -> f(x, b), y), a) - test_b = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(x -> f(a, x), y), b) - - # without JacobianTape - - Ja = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(x -> f(x, b), y), a) - Jb = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(x -> f(a, x), y), b) - test_approx(Ja, test_a) - test_approx(Jb, test_b) - - # with JacobianTape - - ra = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(x -> f(x, b), y), rand(eltype(a), size(a))) - rb = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(x -> f(a, x), y), rand(eltype(b), size(b))) - Ja = ReverseDiff.jacobian!(ra, a) - Jb = ReverseDiff.jacobian!(rb, b) - test_approx(Ja, test_a) - test_approx(Jb, test_b) - - # The below will fail until support for the Jacobian of - # functions with multiple output arrays is implemented - - # Ja, Jb = ReverseDiff.jacobian((x, y) -> ReverseDiff.jacobian(f, (x, y)), (a, b)) - # test_approx(Ja test_a) - # test_approx(Jb test_b) -end - ############################################################################################ f251(x) = x .^ 2 @@ -348,5 +295,3 @@ end # an element that is not a number has no single derivative to seed @test_throws ArgumentError ReverseDiff.jacobian(x -> [x, x], [1.0, 2.0]) end - -end # module diff --git a/test/api/NestedJacobianTests.jl b/test/api/NestedJacobianTests.jl new file mode 100644 index 00000000..3153187c --- /dev/null +++ b/test/api/NestedJacobianTests.jl @@ -0,0 +1,54 @@ +using DiffTests, ForwardDiff, ReverseDiff, Test + +include("../utils.jl") + +for f in (DiffTests.ARRAY_TO_ARRAY_FUNCS..., DiffTests.MATRIX_TO_MATRIX_FUNCS...) + test_println("ARRAY_TO_ARRAY_FUNCS + MATRIX_TO_MATRIX_FUNCS", f) + + x = rand(5, 5) + test = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(f, y), x) + + # without JacobianTape + + J = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(f, y), x) + test_approx(J, test) + + # with JacobianTape + + tp = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(f, y), rand(eltype(x), size(x))) + J = ReverseDiff.jacobian!(tp, x) + test_approx(J, test) +end + +for f in DiffTests.BINARY_MATRIX_TO_MATRIX_FUNCS + test_println("BINARY_MATRIX_TO_MATRIX_FUNCS", f) + + a, b = rand(5, 5), rand(5, 5) + + test_val = f(a, b) + test_a = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(x -> f(x, b), y), a) + test_b = ForwardDiff.jacobian(y -> ForwardDiff.jacobian(x -> f(a, x), y), b) + + # without JacobianTape + + Ja = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(x -> f(x, b), y), a) + Jb = ReverseDiff.jacobian(y -> ReverseDiff.jacobian(x -> f(a, x), y), b) + test_approx(Ja, test_a) + test_approx(Jb, test_b) + + # with JacobianTape + + ra = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(x -> f(x, b), y), rand(eltype(a), size(a))) + rb = ReverseDiff.JacobianTape(y -> ReverseDiff.jacobian(x -> f(a, x), y), rand(eltype(b), size(b))) + Ja = ReverseDiff.jacobian!(ra, a) + Jb = ReverseDiff.jacobian!(rb, b) + test_approx(Ja, test_a) + test_approx(Jb, test_b) + + # The below will fail until support for the Jacobian of + # functions with multiple output arrays is implemented + + # Ja, Jb = ReverseDiff.jacobian((x, y) -> ReverseDiff.jacobian(f, (x, y)), (a, b)) + # test_approx(Ja test_a) + # test_approx(Jb test_b) +end diff --git a/test/compat/CompatTests.jl b/test/compat/CompatTests.jl index 3b84eb47..1b0e1021 100644 --- a/test/compat/CompatTests.jl +++ b/test/compat/CompatTests.jl @@ -1,5 +1,3 @@ -module CompatTests - using FillArrays, ReverseDiff, Test @test ReverseDiff.gradient(fill(2.0, 3)) do x @@ -9,5 +7,3 @@ end == fill(4.0, 3) @test ReverseDiff.gradient(fill(2.0, 3)) do x sum(abs2.(x .- (1:3))) end == [2, 0, -2] - -end diff --git a/test/derivatives/BroadcastTests.jl b/test/derivatives/BroadcastTests.jl index 3f3e1e13..705aec7c 100644 --- a/test/derivatives/BroadcastTests.jl +++ b/test/derivatives/BroadcastTests.jl @@ -1,5 +1,3 @@ -module BroadcastTests - using ReverseDiff using ForwardDiff @@ -686,5 +684,3 @@ end @test ReverseDiff.gradient!(tape3, x) == 2 .* x @test ReverseDiff.gradient!(ReverseDiff.compile(tape3), x) == 2 .* x end - -end # module diff --git a/test/derivatives/ElementwiseBinaryTests1.jl b/test/derivatives/ElementwiseBinaryTests1.jl new file mode 100644 index 00000000..013eda58 --- /dev/null +++ b/test/derivatives/ElementwiseBinaryTests1.jl @@ -0,0 +1,5 @@ +include("elementwise_utils.jl") + +for (M, fsym) in binary_diffrules()[1:2:end] + test_binary(M, fsym) +end diff --git a/test/derivatives/ElementwiseBinaryTests2.jl b/test/derivatives/ElementwiseBinaryTests2.jl new file mode 100644 index 00000000..9d485e89 --- /dev/null +++ b/test/derivatives/ElementwiseBinaryTests2.jl @@ -0,0 +1,5 @@ +include("elementwise_utils.jl") + +for (M, fsym) in binary_diffrules()[2:2:end] + test_binary(M, fsym) +end diff --git a/test/derivatives/ElementwiseTests.jl b/test/derivatives/ElementwiseTests.jl index 54486273..97f9f4c9 100644 --- a/test/derivatives/ElementwiseTests.jl +++ b/test/derivatives/ElementwiseTests.jl @@ -1,425 +1,4 @@ -module ElementwiseTests - -using ReverseDiff - -using DiffRules -using DiffTests -using ForwardDiff -using LogExpFunctions -using NaNMath -using SpecialFunctions - -using Test - -include(joinpath(dirname(@__FILE__), "../utils.jl")) - -x, y = rand(3, 3), rand(3, 3) -a, b = rand(3), rand(3) -n = rand() -tp = InstructionTape() -offset = 0.00001 - -function test_elementwise(f, fopt, x, tp) - xt = track(copy(x), tp) - - ######################################## - - y = map(f, x) - - # record - yt = map(fopt, xt) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(y, (length(x), length(x))) - ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) - test_approx(out, ForwardDiff.jacobian(z -> map(f, z), x); nans = true) - - # forward - x2 = x .- offset - ReverseDiff.value!(xt, x2) - ReverseDiff.forward_pass!(tp) - @test yt == map(f, x2) - ReverseDiff.value!(xt, x) - - empty!(tp) - - ######################################## - - y = broadcast(f, x) - - # record - yt = broadcast(fopt, xt) - @test yt == y - # a constant function has no derivative and is left off the tape - tracked = f !== DiffTests.num2num_4 - @test (yt isa ReverseDiff.TrackedArray) == tracked - ReverseDiff.finish!(tp) - @test length(tp) == (tracked ? 1 : 0) - - # reverse - out = similar(y, (length(x), length(x))) - ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) - test_approx(out, ForwardDiff.jacobian(z -> broadcast(f, z), x); nans = true) - - # forward, which cannot reach an untracked result (#313) - if tracked - x2 = x .- offset - ReverseDiff.value!(xt, x2) - ReverseDiff.forward_pass!(tp) - @test yt == broadcast(f, x2) - ReverseDiff.value!(xt, x) - end - - empty!(tp) - return nothing -end - -function test_map(f, fopt, a, b, tp) - at, bt = track(copy(a), tp), track(copy(b), tp) - c = map(f, a, b) - - ######################################## - - # record - ct = map(fopt, at, b) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(c, (length(c), length(a))) - ReverseDiff.seeded_reverse_pass!(out, ct, at, tp) - test_approx(out, ForwardDiff.jacobian(x -> map(f, x, b), a); nans = true) - - # forward - a2 = a .- offset - ReverseDiff.value!(at, a2) - ReverseDiff.forward_pass!(tp) - @test ct == map(f, a2, b) - ReverseDiff.value!(at, a) - - empty!(tp) - - ######################################## - - # record - ct = map(fopt, a, bt) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(c, (length(c), length(b))) - ReverseDiff.seeded_reverse_pass!(out, ct, bt, tp) - test_approx(out, ForwardDiff.jacobian(x -> map(f, a, x), b); nans = true) - - # forward - b2 = b .- offset - ReverseDiff.value!(bt, b2) - ReverseDiff.forward_pass!(tp) - @test ct == map(f, a, b2) - ReverseDiff.value!(bt, b) - - empty!(tp) - - ######################################## - - # record - ct = map(fopt, at, bt) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out_a = similar(c, (length(c), length(a))) - out_b = similar(c, (length(c), length(b))) - ReverseDiff.seeded_reverse_pass!(out_a, ct, at, tp) - ReverseDiff.seeded_reverse_pass!(out_b, ct, bt, tp) - jac = let a = a, b = b, f = f - ForwardDiff.jacobian(vcat(vec(a), vec(b))) do x - map(f, reshape(x[1:length(a)], size(a)), reshape(x[(length(a) + 1):end], size(b))) - end - end - test_approx(out_a, jac[:, 1:length(a)]; nans = true) - test_approx(out_b, jac[:, (length(a) + 1):end]; nans = true) - # forward - a2, b2 = a .- offset, b .- offset - ReverseDiff.value!(at, a2) - ReverseDiff.value!(bt, b2) - ReverseDiff.forward_pass!(tp) - @test ct == map(f, a2, b2) - ReverseDiff.value!(at, a) - ReverseDiff.value!(bt, b) - - empty!(tp) - return nothing -end - -function test_broadcast(f, fopt, a::AbstractArray, b::AbstractArray, tp, builtin::Bool = false) - at, bt = track(copy(a), tp), track(copy(b), tp) - - if builtin - g = fopt - else - g = (x, y) -> broadcast(fopt, x, y) - end - - c = g(a, b) - - ######################################## - - # record - ct = g(at, b) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(c, (length(c), length(a))) - ReverseDiff.seeded_reverse_pass!(out, ct, at, tp) - test_approx(out, ForwardDiff.jacobian(x -> g(x, b), a); nans = true) - - # forward - a2 = a .- offset - ReverseDiff.value!(at, a2) - ReverseDiff.forward_pass!(tp) - @test ct == g(a2, b) - ReverseDiff.value!(at, a) - - empty!(tp) - - ######################################## - - # record - ct = g(a, bt) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(c, (length(c), length(b))) - ReverseDiff.seeded_reverse_pass!(out, ct, bt, tp) - test_approx(out, ForwardDiff.jacobian(x -> g(a, x), b); nans = true) - - # forward - b2 = b .- offset - ReverseDiff.value!(bt, b2) - ReverseDiff.forward_pass!(tp) - @test ct == g(a, b2) - ReverseDiff.value!(bt, b) - - empty!(tp) - - ######################################## - - # record - ct = g(at, bt) - @test ct == c - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out_a = similar(c, (length(c), length(a))) - out_b = similar(c, (length(c), length(b))) - ReverseDiff.seeded_reverse_pass!(out_a, ct, at, tp) - ReverseDiff.seeded_reverse_pass!(out_b, ct, bt, tp) - jac = let a = a, b = b, g = g - ForwardDiff.jacobian(vcat(vec(a), vec(b))) do x - g(reshape(x[1:length(a)], size(a)), reshape(x[(length(a) + 1):end], size(b))) - end - end - test_approx(out_a, jac[:, 1:length(a)]; nans = true) - test_approx(out_b, jac[:, (length(a) + 1):end]; nans = true) - - # forward - a2, b2 = a .- offset, b .- offset - ReverseDiff.value!(at, a2) - ReverseDiff.value!(bt, b2) - ReverseDiff.forward_pass!(tp) - @test ct == g(a2, b2) - ReverseDiff.value!(at, a) - ReverseDiff.value!(bt, b) - - empty!(tp) - return nothing -end - -function test_broadcast(f, fopt, n::Number, x::AbstractArray, tp, builtin::Bool = false) - nt, xt = track(copy(n), tp), track(copy(x), tp) - - if builtin - g = fopt - else - g = (x, y) -> broadcast(fopt, x, y) - end - - y = g(n, x) - - ######################################## - - # record - yt = g(nt, x) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(y) - ReverseDiff.seeded_reverse_pass!(out, yt, nt, tp) - test_approx(out, ForwardDiff.derivative(z -> g(z, x), n); nans = true) - - # forward - n2 = n + offset - ReverseDiff.value!(nt, n2) - ReverseDiff.forward_pass!(tp) - @test yt == g(n2, x) - ReverseDiff.value!(nt, n) - - empty!(tp) - - ######################################## - - # record - yt = g(n, xt) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(y, (length(y), length(x))) - ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) - test_approx(out, ForwardDiff.jacobian(z -> g(n, z), x); nans = true) - - # forward - x2 = x .- offset - ReverseDiff.value!(xt, x2) - ReverseDiff.forward_pass!(tp) - @test yt == g(n, x2) - ReverseDiff.value!(xt, x) - - empty!(tp) - - ######################################## - - # record - yt = g(nt, xt) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out_n = similar(y) - out_x = similar(y, (length(y), length(x))) - ReverseDiff.seeded_reverse_pass!(out_n, yt, nt, tp) - ReverseDiff.seeded_reverse_pass!(out_x, yt, xt, tp) - jac = let x = x, g = g - ForwardDiff.jacobian(z -> g(z[1], reshape(z[2:end], size(x))), vcat(n, vec(x))) - end - test_approx(out_n, reshape(jac[:, 1], size(y)); nans = true) - test_approx(out_x, jac[:, 2:end]; nans = true) - - # forward - n2, x2 = n + offset, x .- offset - ReverseDiff.value!(nt, n2) - ReverseDiff.value!(xt, x2) - ReverseDiff.forward_pass!(tp) - @test yt == g(n2, x2) - ReverseDiff.value!(nt, n) - ReverseDiff.value!(xt, x) - - empty!(tp) - return nothing -end - -function test_broadcast(f, fopt, x::AbstractArray, n::Number, tp, builtin::Bool = false) - xt, nt = track(copy(x), tp), track(copy(n), tp) - - if builtin - g = fopt - else - g = (x, y) -> broadcast(fopt, x, y) - end - - y = g(x, n) - - ######################################## - - # record - yt = g(x, nt) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(y) - ReverseDiff.seeded_reverse_pass!(out, yt, nt, tp) - test_approx(out, ForwardDiff.derivative(z -> g(x, z), n); nans = true) - - # forward - n2 = n + offset - ReverseDiff.value!(nt, n2) - ReverseDiff.forward_pass!(tp) - @test yt == g(x, n2) - ReverseDiff.value!(nt, n) - - empty!(tp) - - ######################################## - - # record - yt = g(xt, n) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out = similar(y, (length(y), length(x))) - ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) - test_approx(out, ForwardDiff.jacobian(z -> g(z, n), x); nans = true) - - # forward - x2 = x .- offset - ReverseDiff.value!(xt, x2) - ReverseDiff.forward_pass!(tp) - @test yt == g(x2, n) - ReverseDiff.value!(xt, x) - - empty!(tp) - - ######################################## - - # record - yt = g(xt, nt) - @test yt == y - ReverseDiff.finish!(tp) - @test length(tp) == 1 - - # reverse - out_n = similar(y) - out_x = similar(y, (length(y), length(x))) - ReverseDiff.seeded_reverse_pass!(out_n, yt, nt, tp) - ReverseDiff.seeded_reverse_pass!(out_x, yt, xt, tp) - jac = let x = x, g = g - ForwardDiff.jacobian(z -> g(reshape(z[1:(end - 1)], size(x)), z[end]), vcat(vec(x), n)) - end - test_approx(out_x, jac[:, 1:(end - 1)]; nans = true) - test_approx(out_n, reshape(jac[:, end], size(y)); nans = true) - - # forward - x2, n2 = x .- offset, n + offset - ReverseDiff.value!(xt, x2) - ReverseDiff.value!(nt, n2) - ReverseDiff.forward_pass!(tp) - @test yt == g(x2, n2) - ReverseDiff.value!(xt, x) - ReverseDiff.value!(nt, n) - - empty!(tp) - return nothing -end +include("elementwise_utils.jl") for f in DiffTests.NUMBER_TO_NUMBER_FUNCS test_println("DiffTests.NUMBER_TO_NUMBER_FUNCS", f) @@ -438,21 +17,8 @@ for (M, fsym, arity) in DiffRules.diffrules(; filter_modules = nothing) test_println("forward-mode unary scalar functions", f) test_elementwise(f, f, modify_input(fsym, x), tp) test_elementwise(f, f, modify_input(fsym, a), tp) - elseif arity == 2 - in(fsym, SKIPPED_BINARY_SCALAR_TESTS) && continue - f = eval(:($M.$fsym)) - test_println("forward-mode binary scalar functions", f) - test_map(f, f, x, y, tp) - test_map(f, f, a, b, tp) - test_broadcast(f, f, x, y, tp) - test_broadcast(f, f, a, b, tp) - test_broadcast(f, f, x, a, tp) - test_broadcast(f, f, a, x, tp) - test_broadcast(f, f, n, x, tp) - test_broadcast(f, f, x, n, tp) - test_broadcast(f, f, n, a, tp) - test_broadcast(f, f, a, n, tp) end + # binary rules are tested in ElementwiseBinaryTests1.jl and ElementwiseBinaryTests2.jl end for f in DiffTests.BINARY_BROADCAST_OPS @@ -548,5 +114,3 @@ end end @test isempty(take_recorded!(tp)) end - -end # module diff --git a/test/derivatives/LinAlgTests.jl b/test/derivatives/LinAlgTests.jl index 6a627263..656817e3 100644 --- a/test/derivatives/LinAlgTests.jl +++ b/test/derivatives/LinAlgTests.jl @@ -1,11 +1,4 @@ -module LinAlgTests - using ReverseDiff, ForwardDiff, Test, LinearAlgebra, StaticArrays - -if get(ENV, "DOWNGRADE_TEST", "false")::String != "true" - @test Base.get_extension(ReverseDiff, :StatisticsExt) === nothing -end - using Statistics @test Base.get_extension(ReverseDiff, :StatisticsExt) !== nothing @@ -416,5 +409,3 @@ end test_approx(ReverseDiff.jacobian!(tp, x), J) end end - -end # module diff --git a/test/derivatives/ScalarTests.jl b/test/derivatives/ScalarTests.jl index f1e35aa2..ce639cab 100644 --- a/test/derivatives/ScalarTests.jl +++ b/test/derivatives/ScalarTests.jl @@ -1,5 +1,3 @@ -module ScalarTests - using ReverseDiff using DiffRules @@ -179,5 +177,3 @@ for f in ReverseDiff.SKIPPED_BINARY_SCALAR_FUNCS test_println("SKIPPED_BINARY_SCALAR_FUNCS", f) test_skip(eval(f), a, b, tp) end - -end # module diff --git a/test/derivatives/elementwise_utils.jl b/test/derivatives/elementwise_utils.jl new file mode 100644 index 00000000..44159a9c --- /dev/null +++ b/test/derivatives/elementwise_utils.jl @@ -0,0 +1,448 @@ +using ReverseDiff + +using DiffRules +using DiffTests +using ForwardDiff +using LogExpFunctions +using NaNMath +using SpecialFunctions + +using Test + +include("../utils.jl") + +x, y = rand(3, 3), rand(3, 3) +a, b = rand(3), rand(3) +n = rand() +tp = InstructionTape() +offset = 0.00001 + +function test_elementwise(f, fopt, x, tp) + xt = track(copy(x), tp) + + ######################################## + + y = map(f, x) + + # record + yt = map(fopt, xt) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(y, (length(x), length(x))) + ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) + test_approx(out, ForwardDiff.jacobian(z -> map(f, z), x); nans = true) + + # forward + x2 = x .- offset + ReverseDiff.value!(xt, x2) + ReverseDiff.forward_pass!(tp) + @test yt == map(f, x2) + ReverseDiff.value!(xt, x) + + empty!(tp) + + ######################################## + + y = broadcast(f, x) + + # record + yt = broadcast(fopt, xt) + @test yt == y + # a constant function has no derivative and is left off the tape + tracked = f !== DiffTests.num2num_4 + @test (yt isa ReverseDiff.TrackedArray) == tracked + ReverseDiff.finish!(tp) + @test length(tp) == (tracked ? 1 : 0) + + # reverse + out = similar(y, (length(x), length(x))) + ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) + test_approx(out, ForwardDiff.jacobian(z -> broadcast(f, z), x); nans = true) + + # forward, which cannot reach an untracked result (#313) + if tracked + x2 = x .- offset + ReverseDiff.value!(xt, x2) + ReverseDiff.forward_pass!(tp) + @test yt == broadcast(f, x2) + ReverseDiff.value!(xt, x) + end + + empty!(tp) + return nothing +end + +function test_map(f, fopt, a, b, tp) + at, bt = track(copy(a), tp), track(copy(b), tp) + c = map(f, a, b) + + ######################################## + + # record + ct = map(fopt, at, b) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(c, (length(c), length(a))) + ReverseDiff.seeded_reverse_pass!(out, ct, at, tp) + test_approx(out, ForwardDiff.jacobian(x -> map(f, x, b), a); nans = true) + + # forward + a2 = a .- offset + ReverseDiff.value!(at, a2) + ReverseDiff.forward_pass!(tp) + @test ct == map(f, a2, b) + ReverseDiff.value!(at, a) + + empty!(tp) + + ######################################## + + # record + ct = map(fopt, a, bt) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(c, (length(c), length(b))) + ReverseDiff.seeded_reverse_pass!(out, ct, bt, tp) + test_approx(out, ForwardDiff.jacobian(x -> map(f, a, x), b); nans = true) + + # forward + b2 = b .- offset + ReverseDiff.value!(bt, b2) + ReverseDiff.forward_pass!(tp) + @test ct == map(f, a, b2) + ReverseDiff.value!(bt, b) + + empty!(tp) + + ######################################## + + # record + ct = map(fopt, at, bt) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out_a = similar(c, (length(c), length(a))) + out_b = similar(c, (length(c), length(b))) + ReverseDiff.seeded_reverse_pass!(out_a, ct, at, tp) + ReverseDiff.seeded_reverse_pass!(out_b, ct, bt, tp) + jac = let a = a, b = b, f = f + ForwardDiff.jacobian(vcat(vec(a), vec(b))) do x + map(f, reshape(x[1:length(a)], size(a)), reshape(x[(length(a) + 1):end], size(b))) + end + end + test_approx(out_a, jac[:, 1:length(a)]; nans = true) + test_approx(out_b, jac[:, (length(a) + 1):end]; nans = true) + # forward + a2, b2 = a .- offset, b .- offset + ReverseDiff.value!(at, a2) + ReverseDiff.value!(bt, b2) + ReverseDiff.forward_pass!(tp) + @test ct == map(f, a2, b2) + ReverseDiff.value!(at, a) + ReverseDiff.value!(bt, b) + + empty!(tp) + return nothing +end + +function test_broadcast(f, fopt, a::AbstractArray, b::AbstractArray, tp, builtin::Bool = false) + at, bt = track(copy(a), tp), track(copy(b), tp) + + if builtin + g = fopt + else + g = (x, y) -> broadcast(fopt, x, y) + end + + c = g(a, b) + + ######################################## + + # record + ct = g(at, b) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(c, (length(c), length(a))) + ReverseDiff.seeded_reverse_pass!(out, ct, at, tp) + test_approx(out, ForwardDiff.jacobian(x -> g(x, b), a); nans = true) + + # forward + a2 = a .- offset + ReverseDiff.value!(at, a2) + ReverseDiff.forward_pass!(tp) + @test ct == g(a2, b) + ReverseDiff.value!(at, a) + + empty!(tp) + + ######################################## + + # record + ct = g(a, bt) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(c, (length(c), length(b))) + ReverseDiff.seeded_reverse_pass!(out, ct, bt, tp) + test_approx(out, ForwardDiff.jacobian(x -> g(a, x), b); nans = true) + + # forward + b2 = b .- offset + ReverseDiff.value!(bt, b2) + ReverseDiff.forward_pass!(tp) + @test ct == g(a, b2) + ReverseDiff.value!(bt, b) + + empty!(tp) + + ######################################## + + # record + ct = g(at, bt) + @test ct == c + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out_a = similar(c, (length(c), length(a))) + out_b = similar(c, (length(c), length(b))) + ReverseDiff.seeded_reverse_pass!(out_a, ct, at, tp) + ReverseDiff.seeded_reverse_pass!(out_b, ct, bt, tp) + jac = let a = a, b = b, g = g + ForwardDiff.jacobian(vcat(vec(a), vec(b))) do x + g(reshape(x[1:length(a)], size(a)), reshape(x[(length(a) + 1):end], size(b))) + end + end + test_approx(out_a, jac[:, 1:length(a)]; nans = true) + test_approx(out_b, jac[:, (length(a) + 1):end]; nans = true) + + # forward + a2, b2 = a .- offset, b .- offset + ReverseDiff.value!(at, a2) + ReverseDiff.value!(bt, b2) + ReverseDiff.forward_pass!(tp) + @test ct == g(a2, b2) + ReverseDiff.value!(at, a) + ReverseDiff.value!(bt, b) + + empty!(tp) + return nothing +end + +function test_broadcast(f, fopt, n::Number, x::AbstractArray, tp, builtin::Bool = false) + nt, xt = track(copy(n), tp), track(copy(x), tp) + + if builtin + g = fopt + else + g = (x, y) -> broadcast(fopt, x, y) + end + + y = g(n, x) + + ######################################## + + # record + yt = g(nt, x) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(y) + ReverseDiff.seeded_reverse_pass!(out, yt, nt, tp) + test_approx(out, ForwardDiff.derivative(z -> g(z, x), n); nans = true) + + # forward + n2 = n + offset + ReverseDiff.value!(nt, n2) + ReverseDiff.forward_pass!(tp) + @test yt == g(n2, x) + ReverseDiff.value!(nt, n) + + empty!(tp) + + ######################################## + + # record + yt = g(n, xt) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(y, (length(y), length(x))) + ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) + test_approx(out, ForwardDiff.jacobian(z -> g(n, z), x); nans = true) + + # forward + x2 = x .- offset + ReverseDiff.value!(xt, x2) + ReverseDiff.forward_pass!(tp) + @test yt == g(n, x2) + ReverseDiff.value!(xt, x) + + empty!(tp) + + ######################################## + + # record + yt = g(nt, xt) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out_n = similar(y) + out_x = similar(y, (length(y), length(x))) + ReverseDiff.seeded_reverse_pass!(out_n, yt, nt, tp) + ReverseDiff.seeded_reverse_pass!(out_x, yt, xt, tp) + jac = let x = x, g = g + ForwardDiff.jacobian(z -> g(z[1], reshape(z[2:end], size(x))), vcat(n, vec(x))) + end + test_approx(out_n, reshape(jac[:, 1], size(y)); nans = true) + test_approx(out_x, jac[:, 2:end]; nans = true) + + # forward + n2, x2 = n + offset, x .- offset + ReverseDiff.value!(nt, n2) + ReverseDiff.value!(xt, x2) + ReverseDiff.forward_pass!(tp) + @test yt == g(n2, x2) + ReverseDiff.value!(nt, n) + ReverseDiff.value!(xt, x) + + empty!(tp) + return nothing +end + +function test_broadcast(f, fopt, x::AbstractArray, n::Number, tp, builtin::Bool = false) + xt, nt = track(copy(x), tp), track(copy(n), tp) + + if builtin + g = fopt + else + g = (x, y) -> broadcast(fopt, x, y) + end + + y = g(x, n) + + ######################################## + + # record + yt = g(x, nt) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(y) + ReverseDiff.seeded_reverse_pass!(out, yt, nt, tp) + test_approx(out, ForwardDiff.derivative(z -> g(x, z), n); nans = true) + + # forward + n2 = n + offset + ReverseDiff.value!(nt, n2) + ReverseDiff.forward_pass!(tp) + @test yt == g(x, n2) + ReverseDiff.value!(nt, n) + + empty!(tp) + + ######################################## + + # record + yt = g(xt, n) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out = similar(y, (length(y), length(x))) + ReverseDiff.seeded_reverse_pass!(out, yt, xt, tp) + test_approx(out, ForwardDiff.jacobian(z -> g(z, n), x); nans = true) + + # forward + x2 = x .- offset + ReverseDiff.value!(xt, x2) + ReverseDiff.forward_pass!(tp) + @test yt == g(x2, n) + ReverseDiff.value!(xt, x) + + empty!(tp) + + ######################################## + + # record + yt = g(xt, nt) + @test yt == y + ReverseDiff.finish!(tp) + @test length(tp) == 1 + + # reverse + out_n = similar(y) + out_x = similar(y, (length(y), length(x))) + ReverseDiff.seeded_reverse_pass!(out_n, yt, nt, tp) + ReverseDiff.seeded_reverse_pass!(out_x, yt, xt, tp) + jac = let x = x, g = g + ForwardDiff.jacobian(z -> g(reshape(z[1:(end - 1)], size(x)), z[end]), vcat(vec(x), n)) + end + test_approx(out_x, jac[:, 1:(end - 1)]; nans = true) + test_approx(out_n, reshape(jac[:, end], size(y)); nans = true) + + # forward + x2, n2 = x .- offset, n + offset + ReverseDiff.value!(xt, x2) + ReverseDiff.value!(nt, n2) + ReverseDiff.forward_pass!(tp) + @test yt == g(x2, n2) + ReverseDiff.value!(xt, x) + ReverseDiff.value!(nt, n) + + empty!(tp) + return nothing +end + +# Tested binary rules, sorted so that ElementwiseBinaryTests1.jl and +# ElementwiseBinaryTests2.jl can each take every other one +function binary_diffrules() + rules = Tuple{Symbol, Symbol}[] + for (M, fsym, arity) in DiffRules.diffrules(; filter_modules = nothing) + if arity == 2 && !((M, fsym) in ReverseDiff.SKIPPED_DIFFRULES) && !(fsym in SKIPPED_BINARY_SCALAR_TESTS) + push!(rules, (M, fsym)) + end + end + return sort!(rules) +end + +function test_binary(M, fsym) + f = eval(:($M.$fsym)) + test_println("forward-mode binary scalar functions", f) + test_map(f, f, x, y, tp) + test_map(f, f, a, b, tp) + test_broadcast(f, f, x, y, tp) + test_broadcast(f, f, a, b, tp) + test_broadcast(f, f, x, a, tp) + test_broadcast(f, f, a, x, tp) + test_broadcast(f, f, n, x, tp) + test_broadcast(f, f, x, n, tp) + test_broadcast(f, f, n, a, tp) + test_broadcast(f, f, a, n, tp) + return nothing +end diff --git a/test/runtests.jl b/test/runtests.jl index abc1e2ca..f4b636e1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,96 +1,20 @@ -using Test - -test_println(kind, f, pad = " ") = println(pad, "testing $(kind): `$(f)`...") - -@testset "ReverseDiff" begin - - @testset "TapeTests" begin - println("running TapeTests...") - t = @elapsed include("TapeTests.jl") - println("done (took $t seconds).") - end - - @testset "TrackedTests" begin - println("running TrackedTests...") - t = @elapsed include("TrackedTests.jl") - println("done (took $t seconds).") - end - - @testset "MacrosTests" begin - println("running MacrosTests...") - t = @elapsed include("MacrosTests.jl") - println("done (took $t seconds).") - end - - @testset "ChainRulesTests" begin - println("running ChainRulesTests...") - t = @elapsed include("ChainRulesTests.jl") - println("done (took $t seconds).") - end - - @testset "ScalarTests" begin - println("running ScalarTests...") - t = @elapsed include("derivatives/ScalarTests.jl") - println("done (took $t seconds).") - end - - @testset "LinAlgTests" begin - println("running LinAlgTests...") - t = @elapsed include("derivatives/LinAlgTests.jl") - println("done (took $t seconds).") - end - - @testset "PrecisionTests" begin - println("running PrecisionTests...") - t = @elapsed include("PrecisionTests.jl") - println("done (took $t seconds).") - end - - @testset "ElementWiseTests" begin - println("running ElementwiseTests...") - t = @elapsed include("derivatives/ElementwiseTests.jl") - println("done (took $t seconds).") - end - - @testset "BroadcastTests" begin - println("running BroadcastTests...") - t = @elapsed include("derivatives/BroadcastTests.jl") - println("done (took $t seconds).") - end - - @testset "ArrayFunctionTests" begin - println("running ArrayFunctionTests...") - t = @elapsed include("derivatives/ArrayFunctionTests.jl") - println("done (took $t seconds).") - end - - @testset "GradientTests" begin - println("running GradientTests...") - t = @elapsed include("api/GradientTests.jl") - println("done (took $t seconds).") - end - - @testset "JacobianTests" begin - println("running JacobianTests...") - t = @elapsed include("api/JacobianTests.jl") - println("done (took $t seconds).") - end - - @testset "HessianTests" begin - println("running HessianTests...") - t = @elapsed include("api/HessianTests.jl") - println("done (took $t seconds).") - end - - @testset "ConfigTests" begin - println("running ConfigTests...") - t = @elapsed include("api/ConfigTests.jl") - println("done (took $t seconds).") - end - - @testset "CompatTests" begin - println("running CompatTests...") - t = @elapsed include("compat/CompatTests.jl") - println("done (took $t seconds).") +using ReverseDiff +using ParallelTestRunner + +# Every file under `test/` except this one and the shared helpers is a test, named by its path, +# e.g. "api/GradientTests". Select tests via `Pkg.test("ReverseDiff"; test_args=["api/GradientTests"])`, +# exclude them with a leading `!`, and list all with `test_args=["--list"]`. +const testsuite = find_tests(@__DIR__) +delete!(testsuite, "utils") +delete!(testsuite, "derivatives/elementwise_utils") + +# Workers are single-threaded by default +function test_worker(name) + if name == "ThreadsTests" + return addworker(; exeflags = ["--threads=4"]) + else + return nothing end end + +runtests(ReverseDiff, ARGS; testsuite, test_worker)