From cba9df44ccdcd15967a86374b14370bb4716eec9 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Thu, 21 Oct 2021 21:36:26 +0200 Subject: [PATCH 01/12] Add rules for LogExpFunctions --- .gitignore | 1 + Project.toml | 2 ++ src/DiffRules.jl | 2 ++ src/rules.jl | 27 +++++++++++++++++++++++++ test/runtests.jl | 52 +++++++++++++++++++++++++++++------------------- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 8c960ec..97e6a6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.jl.cov *.jl.*.cov *.jl.mem +/Manifest.toml diff --git a/Project.toml b/Project.toml index 818be1c..5751f17 100644 --- a/Project.toml +++ b/Project.toml @@ -3,11 +3,13 @@ uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" version = "1.3.1" [deps] +LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" [compat] +LogExpFunctions = "0.3" NaNMath = "0.3" SpecialFunctions = "0.8, 0.9, 0.10, 1.0" julia = "1" diff --git a/src/DiffRules.jl b/src/DiffRules.jl index b70cae5..e51f4d9 100644 --- a/src/DiffRules.jl +++ b/src/DiffRules.jl @@ -2,6 +2,8 @@ __precompile__() module DiffRules +import LogExpFunctions + include("api.jl") include("rules.jl") diff --git a/src/rules.jl b/src/rules.jl index 6ace211..88cb5af 100644 --- a/src/rules.jl +++ b/src/rules.jl @@ -232,3 +232,30 @@ end :(ifelse(($y > $x) | (signbit($y) < signbit($x)), ifelse(isnan($y), zero($y), one($y)), ifelse(isnan($x), one($y), zero($y)))) @define_diffrule NaNMath.min(x, y) = :(ifelse(($y < $x) | (signbit($y) > signbit($x)), ifelse(isnan($y), one($x), zero($x)), ifelse(isnan($x), zero($x), one($x)))), :(ifelse(($y < $x) | (signbit($y) > signbit($x)), ifelse(isnan($y), zero($y), one($y)), ifelse(isnan($x), one($x), zero($x)))) + +################### +# LogExpFunctions # +################### + +# unary +@define_diffrule LogExpFunctions.xlogx(x) = :(1 + log($x)) +@define_diffrule LogExpFunctions.logistic(x) = :(z = LogExpFunctions.logistic($x); z * (1 - z)) +@define_diffrule LogExpFunctions.logit(x) = :(inv($x * (1 - $x))) +@define_diffrule LogExpFunctions.log1psq(x) = :(2 * $x / (1 + $x^2)) +@define_diffrule LogExpFunctions.log1pexp(x) = :(LogExpFunctions.logistic($x)) +@define_diffrule LogExpFunctions.log1mexp(x) = :(-exp($x - LogExpFunctions.log1mexp($x))) +@define_diffrule LogExpFunctions.log2mexp(x) = :(-exp($x - LogExpFunctions.log2mexp($x))) +@define_diffrule LogExpFunctions.logexpm1(x) = :(exp($x - LogExpFunctions.logexpm1($x))) + +# binary +@define_diffrule LogExpFunctions.xlogy(x, y) = :(log($y)), :($x / $y) +@define_diffrule LogExpFunctions.logaddexp(x, y) = + :(exp($x - LogExpFunctions.logaddexp($x, $y))), :(exp($y - LogExpFunctions.logaddexp($x, $y))) +@define_diffrule LogExpFunctions.logsubexp(x, y) = + :(z = LogExpFunctions.logsubexp($x, $y); $x > $y ? exp($x - z) : -exp($x - z)), + :(z = LogExpFunctions.logsubexp($x, $y); $x > $y ? -exp($y - z) : exp($y - z)) + +# only defined in LogExpFunctions >= 0.3.2 +if isdefined(LogExpFunctions, :xlog1py) + @define_diffrule LogExpFunctions.xlog1py(x, y) = :(log1p($y)), :($x / (1 + $y)) +end diff --git a/test/runtests.jl b/test/runtests.jl index e3da95f..c60f984 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,7 +6,7 @@ else import Random Random.seed!(1) end -import SpecialFunctions, NaNMath +import SpecialFunctions, NaNMath, LogExpFunctions using DiffRules @@ -23,27 +23,37 @@ for (M, f, arity) in DiffRules.diffrules() if arity == 1 @test DiffRules.hasdiffrule(M, f, 1) deriv = DiffRules.diffrule(M, f, :goo) - modifier = in(f, (:asec, :acsc, :asecd, :acscd, :acosh, :acoth)) ? 1 : 0 + modifier = if f in (:asec, :acsc, :asecd, :acscd, :acosh, :acoth) + 1 + elseif f === :log1mexp + -1 + else + 0 + end @eval begin - goo = rand() + $modifier - @test isapprox($deriv, finitediff($M.$f, goo), rtol=0.05) - # test for 2pi functions - if "mod2pi" == string($M.$f) - goo = 4pi + $modifier - @test NaN === $deriv + let + goo = rand() + $modifier + @test isapprox($deriv, finitediff($M.$f, goo), rtol=0.05) + # test for 2pi functions + if "mod2pi" == string($M.$f) + goo = 4pi + $modifier + @test NaN === $deriv + end end end elseif arity == 2 @test DiffRules.hasdiffrule(M, f, 2) derivs = DiffRules.diffrule(M, f, :foo, :bar) @eval begin - foo, bar = rand(1:10), rand() - dx, dy = $(derivs[1]), $(derivs[2]) - if !(isnan(dx)) - @test isapprox(dx, finitediff(z -> $M.$f(z, bar), float(foo)), rtol=0.05) - end - if !(isnan(dy)) - @test isapprox(dy, finitediff(z -> $M.$f(foo, z), bar), rtol=0.05) + let + foo, bar = rand(1:10), rand() + dx, dy = $(derivs[1]), $(derivs[2]) + if !(isnan(dx)) + @test isapprox(dx, finitediff(z -> $M.$f(z, bar), float(foo)), rtol=0.05) + end + if !(isnan(dy)) + @test isapprox(dy, finitediff(z -> $M.$f(foo, z), bar), rtol=0.05) + end end end elseif arity == 3 @@ -72,11 +82,13 @@ derivs = DiffRules.diffrule(:Base, :rem2pi, :x, :y) for xtype in [:Float64, :BigFloat, :Int64] for mode in [:RoundUp, :RoundDown, :RoundToZero, :RoundNearest] @eval begin - x = $xtype(rand(1 : 10)) - y = $mode - dx, dy = $(derivs[1]), $(derivs[2]) - @test isapprox(dx, finitediff(z -> rem2pi(z, y), float(x)), rtol=0.05) - @test isnan(dy) + let + x = $xtype(rand(1 : 10)) + y = $mode + dx, dy = $(derivs[1]), $(derivs[2]) + @test isapprox(dx, finitediff(z -> rem2pi(z, y), float(x)), rtol=0.05) + @test isnan(dy) + end end end end From 1514bf49108e318f53c9c04672143b4165a4aaa5 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:09:53 +0200 Subject: [PATCH 02/12] Add keyword argument to `diffrules` --- src/api.jl | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/api.jl b/src/api.jl index 53500a0..642d05c 100644 --- a/src/api.jl +++ b/src/api.jl @@ -94,21 +94,31 @@ Examples: hasdiffrule(M::Union{Expr,Symbol}, f::Symbol, arity::Int) = haskey(DEFINED_DIFFRULES, (M, f, arity)) """ - diffrules() + diffrules(; modules=(:Base, :SpecialFunctions, :NaNMath)) -Return a list of keys that can be used to access all defined differentiation rules. +Return a list of keys that can be used to access all defined differentiation rules for +functions in the `modules`. Each key is of the form `(M::Symbol, f::Symbol, arity::Int)`. -Here, `arity` refers to the number of arguments accepted by `f`. +Here, `arity` refers to the number of arguments accepted by `f` and `M` is one of the +`modules`. -Examples: +# Examples - julia> first(DiffRules.diffrules()) - (:Base, :asind, 1) +```jldoctest +julia> first(DiffRules.diffrules()) +(:Base, :log2, 1) +julia> first(DiffRules.diffrules(; modules=(:SpecialFunctions,))) +(:SpecialFunctions, :erfi, 1) +``` """ -diffrules() = keys(DEFINED_DIFFRULES) +function diffrules(; modules=(:Base, :SpecialFunctions, :NaNMath)) + return Iterators.filter(keys(DEFINED_DIFFRULES)) do (M, _, _) + return M in modules + end +end # For v0.6 and v0.7 compatibility, need to support having the diff rule function enter as a # `Expr(:quote...)` and a `QuoteNode`. When v0.6 support is dropped, the function will From 06f4025bbbafc8f81aa51623c5ac908f92d2b22d Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:10:04 +0200 Subject: [PATCH 03/12] Add tests --- Project.toml | 3 ++- test/runtests.jl | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/Project.toml b/Project.toml index 5751f17..b8cb8db 100644 --- a/Project.toml +++ b/Project.toml @@ -15,8 +15,9 @@ SpecialFunctions = "0.8, 0.9, 0.10, 1.0" julia = "1" [extras] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "Random"] +test = ["Documenter", "Test", "Random"] diff --git a/test/runtests.jl b/test/runtests.jl index c60f984..9944c3c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,24 +1,23 @@ -if VERSION < v"0.7-" - using Base.Test - srand(1) -else - using Test - import Random - Random.seed!(1) -end -import SpecialFunctions, NaNMath, LogExpFunctions using DiffRules +using Test +import Documenter +import SpecialFunctions, NaNMath, LogExpFunctions +import Random +Random.seed!(1) function finitediff(f, x) ϵ = cbrt(eps(typeof(x))) * max(one(typeof(x)), abs(x)) return (f(x + ϵ) - f(x - ϵ)) / (ϵ + ϵ) end +@testset "DiffRules" begin +@testset "check rules" begin non_numeric_arg_functions = [(:Base, :rem2pi, 2), (:Base, :ifelse, 3)] -for (M, f, arity) in DiffRules.diffrules() +modules = (:Base, :SpecialFunctions, :NaNMath, :LogExpFunctions) +for (M, f, arity) in DiffRules.diffrules(; modules=modules) (M, f, arity) ∈ non_numeric_arg_functions && continue if arity == 1 @test DiffRules.hasdiffrule(M, f, 1) @@ -92,6 +91,26 @@ for xtype in [:Float64, :BigFloat, :Int64] end end end +end + + @testset "diffrules" begin + modules = Set(first(x) for x in DiffRules.diffrules()) + @test modules == Set((:Base, :SpecialFunctions, :NaNMath)) + + modules = Set(first(x) for x in DiffRules.diffrules(; modules=(:Base, :LogExpFunctions))) + @test modules == Set((:Base, :LogExpFunctions)) + end + + @testset "doctests" begin + Documenter.DocMeta.setdocmeta!( + DiffRules, + :DocTestSetup, + :(using DiffRules, SpecialFunctions); + recursive=true, + ) + Documenter.doctest(DiffRules) + end +end # Test ifelse separately as first argument is boolean #= From 34c47b1d737f417465babee21804157f82a42e13 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:15:31 +0200 Subject: [PATCH 04/12] Add more doctests --- src/api.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/api.jl b/src/api.jl index 642d05c..8dc6fb8 100644 --- a/src/api.jl +++ b/src/api.jl @@ -16,12 +16,13 @@ interpolated wherever they are used on the RHS. Note that differentiation rules are purely symbolic, so no type annotations should be used. -Examples: +# Examples +```julia @define_diffrule Base.cos(x) = :(-sin(\$x)) @define_diffrule Base.:/(x, y) = :(inv(\$y)), :(-\$x / (\$y^2)) @define_diffrule Base.polygamma(m, x) = :NaN, :(polygamma(\$m + 1, \$x)) - +``` """ macro define_diffrule(def) @assert isa(def, Expr) && def.head == :(=) "Diff rule expression does not have a left and right side" @@ -50,8 +51,9 @@ interpolated into the returned expression. In the `n`-ary case, an `n`-tuple of expressions will be returned where the `i`th expression is the derivative of `f` w.r.t the `i`th argument. -Examples: +# Examples +```jldoctest julia> DiffRules.diffrule(:Base, :sin, 1) :(cos(1)) @@ -60,9 +62,7 @@ Examples: julia> DiffRules.diffrule(:Base, :sin, :(x * y^2)) :(cos(x * y ^ 2)) - - julia> DiffRules.diffrule(:Base, :^, :(x + 2), :c) - (:(c * (x + 2) ^ (c - 1)), :((x + 2) ^ c * log(x + 2))) +``` """ diffrule(M::Union{Expr,Symbol}, f::Symbol, args...) = DEFINED_DIFFRULES[M,f,length(args)](args...) @@ -74,8 +74,9 @@ otherwise. Here, `arity` refers to the number of arguments accepted by `f`. -Examples: +# Examples +```jldoctest julia> DiffRules.hasdiffrule(:Base, :sin, 1) true @@ -90,6 +91,7 @@ Examples: julia> DiffRules.hasdiffrule(:Base, :-, 3) false +``` """ hasdiffrule(M::Union{Expr,Symbol}, f::Symbol, arity::Int) = haskey(DEFINED_DIFFRULES, (M, f, arity)) From d34683afbaeae39205369db4063126b9c72eb3b5 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:17:26 +0200 Subject: [PATCH 05/12] Remove whitespace --- src/api.jl | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/api.jl b/src/api.jl index 8dc6fb8..0fe01a5 100644 --- a/src/api.jl +++ b/src/api.jl @@ -19,9 +19,9 @@ Note that differentiation rules are purely symbolic, so no type annotations shou # Examples ```julia - @define_diffrule Base.cos(x) = :(-sin(\$x)) - @define_diffrule Base.:/(x, y) = :(inv(\$y)), :(-\$x / (\$y^2)) - @define_diffrule Base.polygamma(m, x) = :NaN, :(polygamma(\$m + 1, \$x)) +@define_diffrule Base.cos(x) = :(-sin(\$x)) +@define_diffrule Base.:/(x, y) = :(inv(\$y)), :(-\$x / (\$y^2)) +@define_diffrule Base.polygamma(m, x) = :NaN, :(polygamma(\$m + 1, \$x)) ``` """ macro define_diffrule(def) @@ -54,14 +54,14 @@ is the derivative of `f` w.r.t the `i`th argument. # Examples ```jldoctest - julia> DiffRules.diffrule(:Base, :sin, 1) - :(cos(1)) +julia> DiffRules.diffrule(:Base, :sin, 1) +:(cos(1)) - julia> DiffRules.diffrule(:Base, :sin, :x) - :(cos(x)) +julia> DiffRules.diffrule(:Base, :sin, :x) +:(cos(x)) - julia> DiffRules.diffrule(:Base, :sin, :(x * y^2)) - :(cos(x * y ^ 2)) +julia> DiffRules.diffrule(:Base, :sin, :(x * y^2)) +:(cos(x * y ^ 2)) ``` """ diffrule(M::Union{Expr,Symbol}, f::Symbol, args...) = DEFINED_DIFFRULES[M,f,length(args)](args...) @@ -77,20 +77,20 @@ Here, `arity` refers to the number of arguments accepted by `f`. # Examples ```jldoctest - julia> DiffRules.hasdiffrule(:Base, :sin, 1) - true +julia> DiffRules.hasdiffrule(:Base, :sin, 1) +true - julia> DiffRules.hasdiffrule(:Base, :sin, 2) - false +julia> DiffRules.hasdiffrule(:Base, :sin, 2) +false - julia> DiffRules.hasdiffrule(:Base, :-, 1) - true +julia> DiffRules.hasdiffrule(:Base, :-, 1) +true - julia> DiffRules.hasdiffrule(:Base, :-, 2) - true +julia> DiffRules.hasdiffrule(:Base, :-, 2) +true - julia> DiffRules.hasdiffrule(:Base, :-, 3) - false +julia> DiffRules.hasdiffrule(:Base, :-, 3) +false ``` """ hasdiffrule(M::Union{Expr,Symbol}, f::Symbol, arity::Int) = haskey(DEFINED_DIFFRULES, (M, f, arity)) From b4438fffb4a94a1a29b66c025424bce721217707 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:28:41 +0200 Subject: [PATCH 06/12] More stable tests --- src/api.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/api.jl b/src/api.jl index 0fe01a5..f1f5c72 100644 --- a/src/api.jl +++ b/src/api.jl @@ -109,11 +109,18 @@ Here, `arity` refers to the number of arguments accepted by `f` and `M` is one o # Examples ```jldoctest -julia> first(DiffRules.diffrules()) -(:Base, :log2, 1) +julia> modules = Set(M for (M, f, arity) in DiffRules.diffrules()); -julia> first(DiffRules.diffrules(; modules=(:SpecialFunctions,))) -(:SpecialFunctions, :erfi, 1) +julia> modules == Set((:Base, :SpecialFunctions, :NaNMath)) +true + +julia> modules = Set(M for (M, f, arity) in DiffRules.diffrules(; modules=(:Base,))); + +julia> modules == Set((:Base,)) +true + +julia> isempty(DiffRules.diffrules(; modules=(:StatsFuns,))) +true ``` """ function diffrules(; modules=(:Base, :SpecialFunctions, :NaNMath)) From 95f1a6153f03ee79d3f296120dfb25bb6eca9c34 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 16:57:57 +0200 Subject: [PATCH 07/12] Fix `log2mexp` tests --- test/runtests.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 9944c3c..78010af 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,11 +23,13 @@ for (M, f, arity) in DiffRules.diffrules(; modules=modules) @test DiffRules.hasdiffrule(M, f, 1) deriv = DiffRules.diffrule(M, f, :goo) modifier = if f in (:asec, :acsc, :asecd, :acscd, :acosh, :acoth) - 1 + 1.0 elseif f === :log1mexp - -1 + -1.0 + elseif f === :log2mexp + -0.5 else - 0 + 0.0 end @eval begin let From be9d649f66a6e030066479611b5e39d392aaff29 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 18:30:04 +0200 Subject: [PATCH 08/12] Update CI and cancel builds for old commits in PRs --- .github/workflows/CompatHelper.yml | 27 +++++++++++++++++++++------ .github/workflows/TagBot.yml | 8 ++++++-- .github/workflows/ci.yml | 8 ++++++++ .github/workflows/docs.yml | 10 +++++++++- .github/workflows/downstream.yml | 7 +++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index bcdb51a..39e1d95 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -1,16 +1,31 @@ name: CompatHelper on: schedule: - - cron: '00 00 * * *' + - cron: 0 0 * * * workflow_dispatch: jobs: CompatHelper: runs-on: ubuntu-latest steps: - - name: Pkg.add("CompatHelper") - run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - - name: CompatHelper.main() + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} # optional - run: julia -e 'using CompatHelper; CompatHelper.main()' + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d77d3a0..f49313b 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,11 +1,15 @@ name: TagBot on: - schedule: - - cron: 0 * * * * + issue_comment: + types: + - created + workflow_dispatch: jobs: TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e211b2..509d556 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,5 @@ name: CI + on: pull_request: branches: @@ -7,6 +8,13 @@ on: branches: - master tags: '*' + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2763219..a42659c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,10 +1,18 @@ name: Documentation + on: push: branches: - master tags: '*' pull_request: + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + jobs: build: runs-on: ubuntu-latest @@ -12,7 +20,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1.5' + version: '1' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 7af4f84..b4e2f48 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -1,10 +1,17 @@ name: IntegrationTest + on: push: branches: [master] tags: [v*] pull_request: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + jobs: test: name: ${{ matrix.package.repo }}/${{ matrix.package.group }} From ebc9987e774da2a3a4ce61597bd3156fbaab214d Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 18:30:48 +0200 Subject: [PATCH 09/12] Update src/api.jl Co-authored-by: Michael Abbott <32575566+mcabbott@users.noreply.github.com> --- src/api.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api.jl b/src/api.jl index f1f5c72..c8ea96d 100644 --- a/src/api.jl +++ b/src/api.jl @@ -106,6 +106,8 @@ Each key is of the form `(M::Symbol, f::Symbol, arity::Int)`. Here, `arity` refers to the number of arguments accepted by `f` and `M` is one of the `modules`. +The default `modules` does *not* include all rules defined by this package, but rather, exactly those packages for which `v1.0` provided rules. This is done in order not to break downstream packages, man or which assumed this list would never change. To include all rules, specify `modules = :all`. + # Examples ```jldoctest From da0a416156c47b87bd0e92da53f909020ac8ad52 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 21:24:44 +0200 Subject: [PATCH 10/12] Change to `filter_modules` and update docs --- Project.toml | 3 +- docs/make.jl | 10 ++++++- src/api.jl | 77 ++++++++++++++++++++++++++++++++++++++---------- test/runtests.jl | 23 +++++---------- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/Project.toml b/Project.toml index b8cb8db..5751f17 100644 --- a/Project.toml +++ b/Project.toml @@ -15,9 +15,8 @@ SpecialFunctions = "0.8, 0.9, 0.10, 1.0" julia = "1" [extras] -Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Documenter", "Test", "Random"] +test = ["Test", "Random"] diff --git a/docs/make.jl b/docs/make.jl index a2aee17..f8488dc 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,12 +1,20 @@ using Documenter, DiffRules +DocMeta.setdocmeta!( + DiffRules, + :DocTestSetup, + :(using DiffRules); + recursive=true, +) + makedocs(modules=[DiffRules], - doctest = false, sitename = "DiffRules", pages = ["Documentation" => "index.md"], format = Documenter.HTML( prettyurls = get(ENV, "CI", nothing) == "true" ), + strict=true, + checkdocs=:exports, ) deploydocs(repo = "github.com/JuliaDiff/DiffRules.jl") diff --git a/src/api.jl b/src/api.jl index c8ea96d..4c31044 100644 --- a/src/api.jl +++ b/src/api.jl @@ -95,39 +95,86 @@ false """ hasdiffrule(M::Union{Expr,Symbol}, f::Symbol, arity::Int) = haskey(DEFINED_DIFFRULES, (M, f, arity)) +# show a deprecation warning if `filter_modules` in `diffrules()` is specified implicitly +# we use a custom singleton to figure out if the keyword argument was set explicitly +struct DefaultFilterModules end + +function deprecated_modules(modules) + return if modules isa DefaultFilterModules + Base.depwarn( + "the implicit keyword argument " * + "`filter_modules=(:Base, :SpecialFunctions, :NaNMath)` in `diffrules()` is " * + "deprecated and will be changed to `filter_modules=nothing` in an upcoming " * + "breaking release of DiffRules (i.e., `diffrules()` will return all rules " * + "defined in DiffRules)", + :diffrules, + ) + (:Base, :SpecialFunctions, :NaNMath) + else + modules + end +end + """ - diffrules(; modules=(:Base, :SpecialFunctions, :NaNMath)) + diffrules(; filter_modules=(:Base, :SpecialFunctions, :NaNMath)) Return a list of keys that can be used to access all defined differentiation rules for -functions in the `modules`. +modules in `filter_modules`. Each key is of the form `(M::Symbol, f::Symbol, arity::Int)`. - Here, `arity` refers to the number of arguments accepted by `f` and `M` is one of the -`modules`. +modules in `filter_modules`. -The default `modules` does *not* include all rules defined by this package, but rather, exactly those packages for which `v1.0` provided rules. This is done in order not to break downstream packages, man or which assumed this list would never change. To include all rules, specify `modules = :all`. +To include all rules, specify `filter_modules = nothing`. + +!!! note + Calling `diffrules()` with the implicit default keyword argument `filter_modules` + does *not* return all rules defined by this package but rather only rules for the + packages for which DiffRules 1.0 provided rules. This is done in order to not to + break downstream packages that assumed this list would never change. + It is planned to change `diffrules()` to return all rules, i.e., to use the + default keyword argument `filter_modules=nothing`, in an upcoming breaking release + of DiffRules. # Examples ```jldoctest -julia> modules = Set(M for (M, f, arity) in DiffRules.diffrules()); - -julia> modules == Set((:Base, :SpecialFunctions, :NaNMath)) -true +julia> first(DiffRules.diffrules()) +(:Base, :log2, 1) +``` -julia> modules = Set(M for (M, f, arity) in DiffRules.diffrules(; modules=(:Base,))); +If you call `diffrules()`, only rules for Base, SpecialFunctions, and +NaNMath are returned but no rules for LogExpFunctions: +```jldoctest +julia> any(M in :LogExpFunctions for (M, _, _) in DiffRules.diffrules()) +false +``` -julia> modules == Set((:Base,)) +If you set `filter_modules=nothing`, all rules defined in DiffRules are +returned and in particular also rules for LogExpFunctions: +```jldoctest +julia> any( + M in :LogExpFunctions + for (M, _, _) in DiffRules.diffrules(; filter_modules=nothing) + ) true +``` -julia> isempty(DiffRules.diffrules(; modules=(:StatsFuns,))) +If you set `filter_modules=(:Base,)` only rules for functions in Base are +returned: +```jldoctest +julia> all(M === :Base for (M, _, _) in DiffRules.diffrules(; filter_modules=(:Base,))) true ``` """ -function diffrules(; modules=(:Base, :SpecialFunctions, :NaNMath)) - return Iterators.filter(keys(DEFINED_DIFFRULES)) do (M, _, _) - return M in modules +function diffrules(; filter_modules=DefaultFilterModules()) + modules = deprecated_modules(filter_modules) + return if modules === nothing + keys(DEFINED_DIFFRULES) + else + Iterators.filter(keys(DEFINED_DIFFRULES)) do (M, _, _) + return M in modules + end end end diff --git a/test/runtests.jl b/test/runtests.jl index 78010af..533cf93 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,6 @@ using DiffRules using Test -import Documenter import SpecialFunctions, NaNMath, LogExpFunctions import Random Random.seed!(1) @@ -16,8 +15,7 @@ end non_numeric_arg_functions = [(:Base, :rem2pi, 2), (:Base, :ifelse, 3)] -modules = (:Base, :SpecialFunctions, :NaNMath, :LogExpFunctions) -for (M, f, arity) in DiffRules.diffrules(; modules=modules) +for (M, f, arity) in DiffRules.diffrules(; filter_modules=nothing) (M, f, arity) ∈ non_numeric_arg_functions && continue if arity == 1 @test DiffRules.hasdiffrule(M, f, 1) @@ -96,21 +94,14 @@ end end @testset "diffrules" begin - modules = Set(first(x) for x in DiffRules.diffrules()) - @test modules == Set((:Base, :SpecialFunctions, :NaNMath)) + rules = @test_deprecated(DiffRules.diffrules()) + @test Set(M for (M, _, _) in rules) == Set((:Base, :SpecialFunctions, :NaNMath)) - modules = Set(first(x) for x in DiffRules.diffrules(; modules=(:Base, :LogExpFunctions))) - @test modules == Set((:Base, :LogExpFunctions)) - end + rules = DiffRules.diffrules(; filter_modules=nothing) + @test Set(M for (M, _, _) in rules) == Set((:Base, :SpecialFunctions, :NaNMath, :LogExpFunctions)) - @testset "doctests" begin - Documenter.DocMeta.setdocmeta!( - DiffRules, - :DocTestSetup, - :(using DiffRules, SpecialFunctions); - recursive=true, - ) - Documenter.doctest(DiffRules) + rules = DiffRules.diffrules(; filter_modules=(:Base, :LogExpFunctions)) + @test Set(M for (M, _, _) in rules) == Set((:Base, :LogExpFunctions)) end end From e1c8cfd7cb6ab8a76ce48d13cbe19b7255aa62fb Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 21:24:57 +0200 Subject: [PATCH 11/12] Add preview of docs --- .github/workflows/DocsPreviewCleanup.yml | 26 ++++++++++++++++++++++++ docs/make.jl | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/DocsPreviewCleanup.yml diff --git a/.github/workflows/DocsPreviewCleanup.yml b/.github/workflows/DocsPreviewCleanup.yml new file mode 100644 index 0000000..6420be9 --- /dev/null +++ b/.github/workflows/DocsPreviewCleanup.yml @@ -0,0 +1,26 @@ +name: Docs Preview Cleanup + +on: + pull_request: + types: [closed] + +jobs: + docs-preview-cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v2 + with: + ref: gh-pages + - name: Delete preview and history + push changes + run: | + if [ -d "previews/PR$PRNUM" ]; then + git config user.name "Documenter.jl" + git config user.email "documenter@juliadocs.github.io" + git rm -rf "previews/PR$PRNUM" + git commit -m "delete preview" + git branch gh-pages-new $(echo "delete history" | git commit-tree HEAD^{tree}) + git push --force origin gh-pages-new:gh-pages + fi + env: + PRNUM: ${{ github.event.number }} diff --git a/docs/make.jl b/docs/make.jl index f8488dc..ccdf86b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -17,4 +17,4 @@ makedocs(modules=[DiffRules], checkdocs=:exports, ) -deploydocs(repo = "github.com/JuliaDiff/DiffRules.jl") +deploydocs(; repo="github.com/JuliaDiff/DiffRules.jl", push_preview=true) From 1449b08225dcd0dd6412c23548f40a8756d61a8d Mon Sep 17 00:00:00 2001 From: David Widmann Date: Sat, 23 Oct 2021 21:27:33 +0200 Subject: [PATCH 12/12] Fix doctests --- src/api.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api.jl b/src/api.jl index 4c31044..59236c4 100644 --- a/src/api.jl +++ b/src/api.jl @@ -146,7 +146,7 @@ julia> first(DiffRules.diffrules()) If you call `diffrules()`, only rules for Base, SpecialFunctions, and NaNMath are returned but no rules for LogExpFunctions: ```jldoctest -julia> any(M in :LogExpFunctions for (M, _, _) in DiffRules.diffrules()) +julia> any(M === :LogExpFunctions for (M, _, _) in DiffRules.diffrules()) false ``` @@ -154,7 +154,7 @@ If you set `filter_modules=nothing`, all rules defined in DiffRules are returned and in particular also rules for LogExpFunctions: ```jldoctest julia> any( - M in :LogExpFunctions + M === :LogExpFunctions for (M, _, _) in DiffRules.diffrules(; filter_modules=nothing) ) true