From faf49aaca17186e31a757df07cb7780a23be8fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 14:33:27 +0200 Subject: [PATCH 01/11] Use dependabot and drop support for Julia < 1.10 --- .github/dependabot.yml | 12 +++++++ .github/workflows/CompatHelper.yml | 43 -------------------------- .github/workflows/ci.yml | 2 +- Project.toml | 14 +++------ docs/Project.toml | 1 - src/tracked.jl | 6 +--- test/Project.toml | 36 +++++++++++++++++++++ test/TrackedTests.jl | 10 +++--- test/derivatives/ArrayFunctionTests.jl | 5 ++- 9 files changed, 60 insertions(+), 69 deletions(-) delete mode 100644 .github/workflows/CompatHelper.yml create mode 100644 test/Project.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d60f0707..a1b6231d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,15 @@ updates: directory: "/" # Location of package manifests schedule: interval: "monthly" + - package-ecosystem: "julia" + directories: # Location of Julia projects + - "/" + schedule: + interval: "daily" + groups: + # Group all Julia package updates into a single PR: + all-julia-packages: + patterns: + - "*" + ignore: + - dependency-name: "ReverseDiff" diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml deleted file mode 100644 index 535861c7..00000000 --- a/.github/workflows/CompatHelper.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CompatHelper -on: - schedule: - - cron: 0 0 * * * - workflow_dispatch: -permissions: - contents: write - pull-requests: write -jobs: - CompatHelper: - runs-on: ubuntu-latest - steps: - - name: Check if Julia is already available in the PATH - id: julia_in_path - run: which julia - continue-on-error: true - - name: Install Julia, but only if it is not already available in the PATH - uses: julia-actions/setup-julia@v3 - with: - version: '1' - arch: ${{ runner.arch }} - if: steps.julia_in_path.outcome != 'success' - - 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(; subdirs = ["", "docs"]) - shell: julia --color=yes {0} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49cf4ec9..cfd89532 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: version: - - 'min' + - 'min-patch' - 'lts' - '1' os: diff --git a/Project.toml b/Project.toml index 3609fcd7..1d8e4743 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "ReverseDiff" uuid = "37e2e3b7-166d-5795-8a7a-e32c996b4267" -version = "1.17.1" +version = "1.18.0-dev" [deps] ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" @@ -33,13 +33,7 @@ Random = "<0.0.1, 1" SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" Statistics = "<0.0.1, 1" -julia = "1" +julia = "1.10" -[extras] -DiffTests = "de460e47-3fe3-5279-bb4a-814414816d5d" -FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" -IrrationalConstants = "92d709cd-6900-40b7-9082-c6be49f344b6" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[targets] -test = ["DiffTests", "FillArrays", "IrrationalConstants", "Test"] +[workspace] +projects = ["test", "docs"] diff --git a/docs/Project.toml b/docs/Project.toml index 18704e95..4b8c876b 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,7 +4,6 @@ ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" [compat] Documenter = "1" -ReverseDiff = "1" [sources] ReverseDiff = { path = ".." } \ No newline at end of file diff --git a/src/tracked.jl b/src/tracked.jl index 1198dea5..7d642bc2 100644 --- a/src/tracked.jl +++ b/src/tracked.jl @@ -446,11 +446,7 @@ Base.fld(a::TrackedReal, b::TrackedReal) = fld(value(a), value(b)) Base.cld(a::TrackedReal, b::TrackedReal) = cld(value(a), value(b)) -if VERSION ≥ v"1.4" - Base.div(x::TrackedReal, y::TrackedReal, r::RoundingMode) = div(value(x), value(y), r) -else - Base.div(x::TrackedReal, y::TrackedReal) = div(value(x), value(y)) -end +Base.div(x::TrackedReal, y::TrackedReal, r::RoundingMode) = div(value(x), value(y), r) Base.trunc(t::TrackedReal) = trunc(value(t)) Base.trunc(::Type{R}, t::TrackedReal) where {R<:Real} = trunc(R, value(t)) diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 00000000..c8c65ab6 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,36 @@ +[deps] +ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +DiffResults = "163ba53b-c6d8-5494-b064-1a9d43ac40c5" +DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b" +DiffTests = "de460e47-3fe3-5279-bb4a-814414816d5d" +FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +IrrationalConstants = "92d709cd-6900-40b7-9082-c6be49f344b6" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +LogExpFunctions = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[compat] +ChainRulesCore = "1" +DiffResults = "1" +DiffRules = "1.4" +DiffTests = "< 0.1.2" +ForwardDiff = "0.10, 1" +IrrationalConstants = "0.2" +LinearAlgebra = "<0.0.1, 1" +LogExpFunctions = "0.3, 1" +NaNMath = "0.3, 1" +Random = "<0.0.1, 1" +SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" +StaticArrays = "0.10, 0.11, 0.12, 1.0" +Statistics = "<0.0.1, 1" +Test = "<0.0.1, 1" + +[sources] +ReverseDiff = { path = ".." } \ No newline at end of file diff --git a/test/TrackedTests.jl b/test/TrackedTests.jl index 006490ad..6a61ab13 100644 --- a/test/TrackedTests.jl +++ b/test/TrackedTests.jl @@ -780,12 +780,10 @@ tr_rand = rand(MersenneTwister(1), TrackedReal{Int,Float64,Nothing}) @test div(v_float, tr_float2) === div(v_float, v_float2) @test div(tr_float, v_float2) === div(v_float, v_float2) -if VERSION ≥ v"1.4" - for r in (RoundUp, RoundDown) - @test div(tr_float, tr_float2, r) === div(v_float, v_float2, r) - @test div(v_float, tr_float2, r) === div(v_float, v_float2, r) - @test div(tr_float, v_float2, r) === div(v_float, v_float2, r) - end +for r in (RoundUp, RoundDown) + @test div(tr_float, tr_float2, r) === div(v_float, v_float2, r) + @test div(v_float, tr_float2, r) === div(v_float, v_float2, r) + @test div(tr_float, v_float2, r) === div(v_float, v_float2, r) end @test trunc(tr_float) === trunc(v_float) diff --git a/test/derivatives/ArrayFunctionTests.jl b/test/derivatives/ArrayFunctionTests.jl index ad86baaf..3bae169b 100644 --- a/test/derivatives/ArrayFunctionTests.jl +++ b/test/derivatives/ArrayFunctionTests.jl @@ -16,10 +16,9 @@ end @test any(iszero, track([ones(2); 0.0])) end -# SparseArrays >= 1.10 pirates `hcat`/`vcat` for `Union{AbstractVecOrMat{<:Number},Number}`, +# SparseArrays pirates `hcat`/`vcat` for `Union{AbstractVecOrMat{<:Number},Number}`, # which is ambiguous with the methods defined for `TrackedArray`. -const SPARSE_CAT_AMBIGUITY = - VERSION >= v"1.10" && any(k -> k.name == "SparseArrays", keys(Base.loaded_modules)) +const SPARSE_CAT_AMBIGUITY = any(k -> k.name == "SparseArrays", keys(Base.loaded_modules)) function testcat(f, args::Tuple, type, kwargs=NamedTuple()) x = f(track.(args)...; kwargs...) From 0b2233f722f8a631272545d6c08de19da25b9a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 14:38:53 +0200 Subject: [PATCH 02/11] Fix compat entries --- Project.toml | 2 -- test/Project.toml | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 1d8e4743..f34984a1 100644 --- a/Project.toml +++ b/Project.toml @@ -21,10 +21,8 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" ChainRulesCore = "1" DiffResults = "1" DiffRules = "1.4" -DiffTests = "< 0.1.2" ForwardDiff = "0.10, 1" FunctionWrappers = "1" -IrrationalConstants = "0.2" LinearAlgebra = "<0.0.1, 1" LogExpFunctions = "0.3, 1" MacroTools = "0.5" diff --git a/test/Project.toml b/test/Project.toml index c8c65ab6..69dfe514 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -21,6 +21,7 @@ ChainRulesCore = "1" DiffResults = "1" DiffRules = "1.4" DiffTests = "< 0.1.2" +FillArrays = "1" ForwardDiff = "0.10, 1" IrrationalConstants = "0.2" LinearAlgebra = "<0.0.1, 1" From 9caa296515beb12be2d1e4e56291893cb4e47474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 20:56:34 +0200 Subject: [PATCH 03/11] Clean compat entries --- Project.toml | 6 +++--- test/Project.toml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index f34984a1..337b2e57 100644 --- a/Project.toml +++ b/Project.toml @@ -23,14 +23,14 @@ DiffResults = "1" DiffRules = "1.4" ForwardDiff = "0.10, 1" FunctionWrappers = "1" -LinearAlgebra = "<0.0.1, 1" +LinearAlgebra = "1" LogExpFunctions = "0.3, 1" MacroTools = "0.5" NaNMath = "0.3, 1" -Random = "<0.0.1, 1" +Random = "1" SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" -Statistics = "<0.0.1, 1" +Statistics = "1" julia = "1.10" [workspace] diff --git a/test/Project.toml b/test/Project.toml index 69dfe514..c8a7e0c5 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,14 +24,14 @@ DiffTests = "< 0.1.2" FillArrays = "1" ForwardDiff = "0.10, 1" IrrationalConstants = "0.2" -LinearAlgebra = "<0.0.1, 1" +LinearAlgebra = "1" LogExpFunctions = "0.3, 1" NaNMath = "0.3, 1" -Random = "<0.0.1, 1" +Random = "1" SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" -Statistics = "<0.0.1, 1" -Test = "<0.0.1, 1" +Statistics = "1" +Test = "1" [sources] ReverseDiff = { path = ".." } \ No newline at end of file From 1bbb6ffabdc08864fde28035a3f9625a08bf050e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 20:58:54 +0200 Subject: [PATCH 04/11] Add downgrade action --- .github/workflows/Downgrade.yml | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/Downgrade.yml diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml new file mode 100644 index 00000000..f3cf9f39 --- /dev/null +++ b/.github/workflows/Downgrade.yml @@ -0,0 +1,53 @@ +name: Downgrade + +on: + pull_request: + branches: + - master + push: + branches: + - master + workflow_dispatch: + +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: 'Downgrade / Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }}' + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1' + os: + - ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: julia-actions/setup-julia@v3 + with: + version: ${{ matrix.version }} + - uses: julia-actions/cache@v3 + - uses: julia-actions/julia-downgrade-compat@v2 + with: + projects: ".,test" + skip: LinearAlgebra,Random,Statistics,Test + name: Downgrade dependencies to oldest supported versions + - uses: julia-actions/julia-buildpkg@v1 + with: + project: "test" + - uses: julia-actions/julia-runtest@v1 + with: + coverage: true + allow_reresolve: false + force_latest_compatible_version: false + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v7 + with: + files: lcov.info + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} From ff42e238df4950b728a3c41dcad65e1986846ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 20:59:35 +0200 Subject: [PATCH 05/11] Simplify name --- .github/workflows/Downgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index f3cf9f39..4ae78874 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -17,7 +17,7 @@ concurrency: jobs: test: - name: 'Downgrade / Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }}' + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} strategy: fail-fast: false From 40f243800a22aaed676bd446c8a64c1d18e50dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 21:11:49 +0200 Subject: [PATCH 06/11] Clean up compat entries --- Project.toml | 10 +++++----- test/Project.toml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index 337b2e57..00bfb8e8 100644 --- a/Project.toml +++ b/Project.toml @@ -21,15 +21,15 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" ChainRulesCore = "1" DiffResults = "1" DiffRules = "1.4" -ForwardDiff = "0.10, 1" +ForwardDiff = "1" FunctionWrappers = "1" LinearAlgebra = "1" -LogExpFunctions = "0.3, 1" +LogExpFunctions = "1" MacroTools = "0.5" -NaNMath = "0.3, 1" +NaNMath = "1" Random = "1" -SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" -StaticArrays = "0.10, 0.11, 0.12, 1.0" +SpecialFunctions = "2" +StaticArrays = "1" Statistics = "1" julia = "1.10" diff --git a/test/Project.toml b/test/Project.toml index c8a7e0c5..507026d3 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -22,14 +22,14 @@ DiffResults = "1" DiffRules = "1.4" DiffTests = "< 0.1.2" FillArrays = "1" -ForwardDiff = "0.10, 1" +ForwardDiff = "1" IrrationalConstants = "0.2" LinearAlgebra = "1" -LogExpFunctions = "0.3, 1" -NaNMath = "0.3, 1" +LogExpFunctions = "1" +NaNMath = "1" Random = "1" -SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" -StaticArrays = "0.10, 0.11, 0.12, 1.0" +SpecialFunctions = "2" +StaticArrays = "1" Statistics = "1" Test = "1" From f2297c9f1f6dc72811d3230f53daf0e7bc8b2e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Tue, 15 Sep 2026 21:16:31 +0200 Subject: [PATCH 07/11] Use latest MacroTools version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 00bfb8e8..ed1d262f 100644 --- a/Project.toml +++ b/Project.toml @@ -25,7 +25,7 @@ ForwardDiff = "1" FunctionWrappers = "1" LinearAlgebra = "1" LogExpFunctions = "1" -MacroTools = "0.5" +MacroTools = "0.5.16" NaNMath = "1" Random = "1" SpecialFunctions = "2" From 3b56dd152f1c175e70b68c4286b3bbf2edd22c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Wed, 16 Sep 2026 12:57:16 +0200 Subject: [PATCH 08/11] Require FunctionWrappers 1.1.2 or newer The downgrade test resolved FunctionWrappers 1.0.0 and errored in every testset that exercises a `CompiledTape` (ChainRulesTests, GradientTests, JacobianTests, HessianTests): LoadError: Module IR does not contain specified entry function Stacktrace: [1] assume @ FunctionWrappers/src/FunctionWrappers.jl:11 [inlined] ... [5] forward_pass!(compiled_tape::ReverseDiff.CompiledTape{...}) @ ReverseDiff src/api/tape.jl:124 FunctionWrappers <= 1.1.1 defines `assume` with the pre-Julia-1.6 form of `Base.llvmcall` in which the second element of the tuple is the function body. Since v"1.6.0-DEV.663" Julia interprets that element as the name of the entry function in the supplied module IR, so the call fails at run time. FunctionWrappers 1.1.2 is the first release that branches on `VERSION` and emits a named `@fw_assume` definition. ReverseDiff therefore never worked with FunctionWrappers < 1.1.2 on any Julia version we still support, so the lower bound was simply wrong. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ed1d262f..495641e8 100644 --- a/Project.toml +++ b/Project.toml @@ -22,7 +22,7 @@ ChainRulesCore = "1" DiffResults = "1" DiffRules = "1.4" ForwardDiff = "1" -FunctionWrappers = "1" +FunctionWrappers = "1.1.2" LinearAlgebra = "1" LogExpFunctions = "1" MacroTools = "0.5.16" From 6e3fe71002c7f6ecd704c69842e0c5a66cd907b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 17 Sep 2026 11:04:35 +0200 Subject: [PATCH 09/11] Require StaticArrays 1.6.4 `JacobianConfig` builds its tracked output buffer with `similar(output, TrackedReal{D,D,Nothing})` and then fills it element-wise in `track!`. `TrackedReal` is never `isbitstype`: it is a `mutable struct`, and it holds the `InstructionTape`. `MArray` only supports `setindex!` for isbits eltypes, since it fakes element assignment with `unsafe_store!` into its single `NTuple` field. StaticArrays only returns a `SizedArray` from `similar` for non-isbits eltypes from 1.6.4 on; 1.6.3 and earlier return an `MArray` whose `setindex!` errors with setindex!() with non-isbitstype eltype is not supported by StaticArrays. Consider using SizedArray. So `hessian!(::DiffResult, f, ::MVector)` and the corresponding `jacobian!` path need StaticArrays >= 1.6.4. Without the bound the Downgrade job would resolve a version that cannot run them. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 2 +- test/Project.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 495641e8..a420febf 100644 --- a/Project.toml +++ b/Project.toml @@ -29,7 +29,7 @@ MacroTools = "0.5.16" NaNMath = "1" Random = "1" SpecialFunctions = "2" -StaticArrays = "1" +StaticArrays = "1.6.4" Statistics = "1" julia = "1.10" diff --git a/test/Project.toml b/test/Project.toml index 507026d3..33298a9e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -29,7 +29,7 @@ LogExpFunctions = "1" NaNMath = "1" Random = "1" SpecialFunctions = "2" -StaticArrays = "1" +StaticArrays = "1.6.4" Statistics = "1" Test = "1" From 9a22e42d32fab42bdd2a15532cbd64c661ae08fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 17 Sep 2026 12:50:54 +0200 Subject: [PATCH 10/11] Move `Statistics.mean` into a package extension `Statistics` was a hard dependency for exactly one method, `mean(::TrackedArray)`, and its two instruction hooks. Nothing is lost by making it weak. `ReverseDiff` does not export or re-export `mean`, so reaching the method at all requires the name, and every route to it already loads `Statistics`: `using Statistics`, a qualified `Statistics.mean`, or a package such as StatsBase that re-exports it and depends on it. The extension therefore loads exactly when the method can be called. In return `ReverseDiff` stops loading `Statistics` for everyone, which also stops it triggering `StaticArraysStatisticsExt` downstream, since `StaticArrays` is a hard dependency and takes `Statistics` as a weakdep. The tests assert the extension is absent before `using Statistics` and present after. Without them a broken extension would go unnoticed: `TrackedArray <: AbstractArray`, so the generic `mean` falls through to the tracked `sum`, records a different instruction and still returns the right value and gradient. Co-Authored-By: Claude Opus 5 (1M context) --- Project.toml | 5 +++++ ext/StatisticsExt.jl | 28 ++++++++++++++++++++++++++++ src/ReverseDiff.jl | 1 - src/derivatives/linalg/reductions.jl | 25 ------------------------- test/derivatives/LinAlgTests.jl | 10 ++++++++-- 5 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 ext/StatisticsExt.jl diff --git a/Project.toml b/Project.toml index a420febf..596d5dd1 100644 --- a/Project.toml +++ b/Project.toml @@ -15,8 +15,13 @@ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[weakdeps] Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +[extensions] +StatisticsExt = "Statistics" + [compat] ChainRulesCore = "1" DiffResults = "1" diff --git a/ext/StatisticsExt.jl b/ext/StatisticsExt.jl new file mode 100644 index 00000000..727b9a6c --- /dev/null +++ b/ext/StatisticsExt.jl @@ -0,0 +1,28 @@ +module StatisticsExt + +using ReverseDiff: ReverseDiff, SpecialInstruction, TrackedArray, deriv, increment_deriv!, + istracked, record!, tape, track, unseed!, value, value! +using Statistics: Statistics, mean + +function Statistics.mean(x::TrackedArray{V,D}) where {V,D} + tp = tape(x) + out = track(mean(value(x)), D, tp) + record!(tp, SpecialInstruction, mean, x, out) + return out +end + +@noinline function ReverseDiff.special_reverse_exec!(instruction::SpecialInstruction{typeof(mean)}) + input = instruction.input + output = instruction.output + istracked(input) && increment_deriv!(input, inv(length(input)) * deriv(output)) + unseed!(output) + return nothing +end + +@noinline function ReverseDiff.special_forward_exec!(instruction::SpecialInstruction{typeof(mean)}) + input = instruction.input + value!(instruction.output, mean(value(input))) + return nothing +end + +end # module diff --git a/src/ReverseDiff.jl b/src/ReverseDiff.jl index 0c99f069..011a026f 100644 --- a/src/ReverseDiff.jl +++ b/src/ReverseDiff.jl @@ -3,7 +3,6 @@ module ReverseDiff using Base: RefValue using Random using LinearAlgebra -using Statistics using FunctionWrappers: FunctionWrapper diff --git a/src/derivatives/linalg/reductions.jl b/src/derivatives/linalg/reductions.jl index 0887a045..dbdb5ca3 100644 --- a/src/derivatives/linalg/reductions.jl +++ b/src/derivatives/linalg/reductions.jl @@ -59,31 +59,6 @@ end return nothing end -######## -# mean # -######## - -function Statistics.mean(x::TrackedArray{V,D}) where {V,D} - tp = tape(x) - out = track(mean(value(x)), D, tp) - record!(tp, SpecialInstruction, mean, x, out) - return out -end - -@noinline function special_reverse_exec!(instruction::SpecialInstruction{typeof(mean)}) - input = instruction.input - output = instruction.output - istracked(input) && increment_deriv!(input, inv(length(input)) * deriv(output)) - unseed!(output) - return nothing -end - -@noinline function special_forward_exec!(instruction::SpecialInstruction{typeof(mean)}) - input = instruction.input - value!(instruction.output, mean(value(input))) - return nothing -end - ####### # dot # ####### diff --git a/test/derivatives/LinAlgTests.jl b/test/derivatives/LinAlgTests.jl index 8c4c9fdb..15da808f 100644 --- a/test/derivatives/LinAlgTests.jl +++ b/test/derivatives/LinAlgTests.jl @@ -1,8 +1,14 @@ module LinAlgTests -using ReverseDiff, ForwardDiff, Test, LinearAlgebra, Statistics +using ReverseDiff, ForwardDiff, Test, LinearAlgebra -include(joinpath(dirname(@__FILE__), "../utils.jl")) +@test Base.get_extension(ReverseDiff, :StatisticsExt) === nothing + +using Statistics + +@test Base.get_extension(ReverseDiff, :StatisticsExt) !== nothing + +include("../utils.jl") x, a, b = rand(3, 3), rand(3, 3), rand(3, 3) tp = InstructionTape() From 5fb518cb3eaaa7ee7abea6a8b68e9f0add66ad41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller-Widmann?= Date: Thu, 17 Sep 2026 14:14:42 +0200 Subject: [PATCH 11/11] Skip the extension-absence test in the Downgrade job `julia-downgrade-compat` merges the main and test projects and promotes weakdeps to dependencies: Info: Promoting Statistics from weakdep to dependency in merged project so `Statistics` loads with `ReverseDiff` there and `StatisticsExt` is always present. The job sets `DOWNGRADE_TEST` for the test step and the assertion is skipped when it is set. The check that the extension does load after `using Statistics` still runs everywhere. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/Downgrade.yml | 3 +++ test/derivatives/LinAlgTests.jl | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 4ae78874..9ccbd196 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -45,6 +45,9 @@ 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/test/derivatives/LinAlgTests.jl b/test/derivatives/LinAlgTests.jl index 15da808f..dd53bdfa 100644 --- a/test/derivatives/LinAlgTests.jl +++ b/test/derivatives/LinAlgTests.jl @@ -2,7 +2,9 @@ module LinAlgTests using ReverseDiff, ForwardDiff, Test, LinearAlgebra -@test Base.get_extension(ReverseDiff, :StatisticsExt) === nothing +if get(ENV, "DOWNGRADE_TEST", "false")::String != "true" + @test Base.get_extension(ReverseDiff, :StatisticsExt) === nothing +end using Statistics