Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Format with Runic
da0580609466b256075c58cb1257e94769a46419
31 changes: 31 additions & 0 deletions .github/workflows/Format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Format

on:
pull_request:
branches:
- master
push:
branches:
- master

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:
runic:
name: Runic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: julia-actions/setup-julia@v3
with:
version: '1'
- uses: julia-actions/cache@v3
- uses: fredrikekre/runic-action@v1
with:
version: '1'
extensions: 'jl,md'
docstrings: 'true'
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliadiff.org/ReverseDiff.jl/dev)
[![Build status](https://github.com/JuliaDiff/ReverseDiff.jl/workflows/CI/badge.svg)](https://github.com/JuliaDiff/ReverseDiff.jl/actions)
[![codecov.io](https://codecov.io/github/JuliaDiff/ReverseDiff.jl/coverage.svg?branch=master)](https://codecov.io/github/JuliaDiff/ReverseDiff.jl?branch=master)
[![code style: runic](https://img.shields.io/badge/code_style-%E1%9A%B1%E1%9A%A2%E1%9A%BE%E1%9B%81%E1%9A%B2-black)](https://github.com/fredrikekre/Runic.jl)

ReverseDiff is a fast and compile-able tape-based **reverse mode automatic differentiation (AD)** that
implements methods to take **gradients**, **Jacobians**, **Hessians**, and
Expand All @@ -21,7 +22,7 @@ differentiation methods (such as [finite differencing](https://en.wikipedia.org/

To install ReverseDiff, simply use Julia's package manager:

```julia
```julia-repl
julia> Pkg.add("ReverseDiff")
```

Expand Down
21 changes: 12 additions & 9 deletions benchmark/benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@ function grad_benchmark_driver!(out, f, x)
else
println("skipping compiled GradientTape benchmark because the tape is too long ($(length(tp.tape)) elements)")
end
return nothing
end

####################################################################

rosenbrock(x) = sum(map(ReverseDiff.@forward((i, j) -> (1 - j)^2 + 100*(i - j^2)^2), x[2:end], x[1:end-1]))
rosenbrock(x) = sum(map(ReverseDiff.@forward((i, j) -> (1 - j)^2 + 100 * (i - j^2)^2), x[2:end], x[1:(end - 1)]))

# function rosenbrock(x)
# i = x[2:end]
Expand All @@ -82,16 +83,18 @@ grad_benchmark_driver!(out, rosenbrock, x)
####################################################################

function ackley(x::AbstractVector)
a, b, c = 20.0, -0.2, 2.0*π
a, b, c = 20.0, -0.2, 2.0 * π
len_recip = inv(length(x))
sum_sqrs = zero(eltype(x))
sum_cos = sum_sqrs
for i in x
sum_cos += cos(c*i)
sum_cos += cos(c * i)
sum_sqrs += i^2
end
return (-a * exp(b * sqrt(len_recip*sum_sqrs)) -
exp(len_recip*sum_cos) + a + e)
return (
-a * exp(b * sqrt(len_recip * sum_sqrs)) -
exp(len_recip * sum_cos) + a + e
)
end

x = rand(100000)
Expand All @@ -103,8 +106,8 @@ grad_benchmark_driver!(out, ackley, x)
function generate_matrix_test(n)
return x -> begin
@assert length(x) == 2n^2 + n
a = reshape(x[1:n^2], n, n)
b = reshape(x[n^2 + 1:2n^2], n, n)
a = reshape(x[1:(n^2)], n, n)
b = reshape(x[(n^2 + 1):2n^2], n, n)
return trace(log.((a * b) + a - b))
end
end
Expand All @@ -119,15 +122,15 @@ grad_benchmark_driver!(out, matrix_test, x)

relu(x) = log.(1.0 .+ exp.(x))

ReverseDiff.@forward sigmoid(n) = 1. / (1. + exp(-n))
ReverseDiff.@forward sigmoid(n) = 1.0 / (1.0 + exp(-n))

function neural_net(w1, w2, w3, x1)
x2 = relu(w1 * x1)
x3 = relu(w2 * x2)
return sigmoid(dot(w3, x3))
end

xs = (randn(10,10), randn(10,10), randn(10), rand(10))
xs = (randn(10, 10), randn(10, 10), randn(10), rand(10))
outs = map(similar, xs)
grad_benchmark_driver!(outs, neural_net, xs)

Expand Down
14 changes: 7 additions & 7 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
using Documenter, ReverseDiff

makedocs(;
modules=[ReverseDiff],
sitename="ReverseDiff.jl",
format=Documenter.HTML(;
prettyurls=get(ENV, "CI", nothing) == "true",
modules = [ReverseDiff],
sitename = "ReverseDiff.jl",
format = Documenter.HTML(;
prettyurls = get(ENV, "CI", nothing) == "true",
),
pages=[
pages = [
"Home" => "index.md",
"Limitation of ReverseDiff" => "limits.md",
"API" => "api.md",
],
checkdocs=:exports,
checkdocs = :exports,
)

deploydocs(;
repo="github.com/JuliaDiff/ReverseDiff.jl.git", push_preview=true,
repo = "github.com/JuliaDiff/ReverseDiff.jl.git", push_preview = true,
)
4 changes: 2 additions & 2 deletions ext/StatisticsExt.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module StatisticsExt

using ReverseDiff: ReverseDiff, SpecialInstruction, TrackedArray, deriv, increment_deriv!,
istracked, record!, tape, track, unseed!, value, value!
istracked, record!, tape, track, unseed!, value, value!
using Statistics: Statistics, mean

function Statistics.mean(x::TrackedArray{V,D}) where {V,D}
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)
Expand Down
4 changes: 2 additions & 2 deletions src/ReverseDiff.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ using ChainRulesCore
const ARRAY_TYPES = (:AbstractArray, :AbstractVector, :AbstractMatrix, :Array, :Vector, :Matrix)
const REAL_TYPES = (:Bool, :Integer, :AbstractIrrational, :Rational, :BigFloat, :BigInt, :AbstractFloat, :Real, :Dual)

const SKIPPED_UNARY_SCALAR_FUNCS = Symbol[:isinf, :isnan, :isfinite, :iseven, :isodd, :isreal, :isinteger, :exponent]
const SKIPPED_UNARY_SCALAR_FUNCS = Symbol[:isinf, :isnan, :isfinite, :iseven, :isodd, :isreal, :isinteger, :exponent]
const SKIPPED_BINARY_SCALAR_FUNCS = Symbol[:isequal, :isless, :<, :>, :(==), :(!=), :(<=), :(>=)]

# Some functions with derivatives in DiffRules are not supported
# For instance, ReverseDiff does not support functions with complex results and derivatives
const SKIPPED_DIFFRULES = Tuple{Symbol,Symbol}[
const SKIPPED_DIFFRULES = Tuple{Symbol, Symbol}[
(:SpecialFunctions, :hankelh1),
(:SpecialFunctions, :hankelh1x),
(:SpecialFunctions, :hankelh2),
Expand Down
22 changes: 11 additions & 11 deletions src/api/Config.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,28 @@ GradientConfig(input::Tuple, tp::InstructionTape = InstructionTape()) = Gradient
Like `GradientConfig(input, tp)`, except the provided type `D` is assumed to be the element
type of the target function's output.
"""
function GradientConfig(input::Tuple, ::Type{D}, tp::InstructionTape = InstructionTape()) where D
function GradientConfig(input::Tuple, ::Type{D}, tp::InstructionTape = InstructionTape()) where {D}
return _GradientConfig(map(x -> track(similar(x), D, tp), input), tp)
end

function GradientConfig(input::AbstractArray, ::Type{D}, tp::InstructionTape = InstructionTape()) where D
function GradientConfig(input::AbstractArray, ::Type{D}, tp::InstructionTape = InstructionTape()) where {D}
return _GradientConfig(track(similar(input), D, tp), tp)
end

##################
# JacobianConfig #
##################

struct JacobianConfig{I,O} <: AbstractConfig
struct JacobianConfig{I, O} <: AbstractConfig
input::I
output::O
tape::InstructionTape
# disable default outer constructor
JacobianConfig{I,O}(input, output, tape) where {I,O} = new{I,O}(input, output, tape)
JacobianConfig{I, O}(input, output, tape) where {I, O} = new{I, O}(input, output, tape)
end

# "private" convienence constructor
_JacobianConfig(input::I, output::O, tape::InstructionTape) where {I,O} = JacobianConfig{I,O}(input, output, tape)
_JacobianConfig(input::I, output::O, tape::InstructionTape) where {I, O} = JacobianConfig{I, O}(input, output, tape)

"""
ReverseDiff.JacobianConfig(input, tp::InstructionTape = InstructionTape())
Expand Down Expand Up @@ -99,16 +99,16 @@ stored or modified in any way.

See `ReverseDiff.jacobian` for a description of acceptable types for `input`.
"""
function JacobianConfig(output::AbstractArray{D}, input::Tuple, tp::InstructionTape = InstructionTape()) where D
function JacobianConfig(output::AbstractArray{D}, input::Tuple, tp::InstructionTape = InstructionTape()) where {D}
cfg_input = map(x -> track(similar(x), D, tp), input)
cfg_output = track!(similar(output, TrackedReal{D,D,Nothing}), output, tp)
cfg_output = track!(similar(output, TrackedReal{D, D, Nothing}), output, tp)
return _JacobianConfig(cfg_input, cfg_output, tp)
end

# we dispatch on V<:Real here because InstructionTape is actually also an AbstractArray
function JacobianConfig(output::AbstractArray{D}, input::AbstractArray{V}, tp::InstructionTape = InstructionTape()) where {D,V<:Real}
function JacobianConfig(output::AbstractArray{D}, input::AbstractArray{V}, tp::InstructionTape = InstructionTape()) where {D, V <: Real}
cfg_input = track(similar(input), D, tp)
cfg_output = track!(similar(output, TrackedReal{D,D,Nothing}), output, tp)
cfg_output = track!(similar(output, TrackedReal{D, D, Nothing}), output, tp)
return _JacobianConfig(cfg_input, cfg_output, tp)
end

Expand All @@ -123,7 +123,7 @@ JacobianConfig(result::DiffResult, input, tp::InstructionTape) = JacobianConfig(
# HessianConfig #
#################

struct HessianConfig{G<:GradientConfig,J<:JacobianConfig} <: AbstractConfig
struct HessianConfig{G <: GradientConfig, J <: JacobianConfig} <: AbstractConfig
gradient_config::G
jacobian_config::J
end
Expand All @@ -149,7 +149,7 @@ end
Like `HessianConfig(input, tp)`, except the provided type `D` is assumed to be the element
type of the target function's output.
"""
function HessianConfig(input::AbstractArray, ::Type{D}, gtp::InstructionTape = InstructionTape(), jtp::InstructionTape = InstructionTape()) where D
function HessianConfig(input::AbstractArray, ::Type{D}, gtp::InstructionTape = InstructionTape(), jtp::InstructionTape = InstructionTape()) where {D}
jcfg = JacobianConfig(input, D, jtp)
gcfg = GradientConfig(jcfg.input, gtp)
return HessianConfig(gcfg, jcfg)
Expand Down
8 changes: 4 additions & 4 deletions src/api/gradients.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ end
###########################

"""
ReverseDiff.gradient!(tape::Union{GradientTape,CompiledGradient}, input)
ReverseDiff.gradient!(tape::Union{GradientTape, CompiledGradient}, input)

If `input` is an `AbstractArray`, assume `tape` represents a function of the form
`f(::AbstractArray)::Real` and return `∇f(input)`.
Expand All @@ -59,14 +59,14 @@ If `input` is a tuple of `AbstractArray`s, assume `tape` represents a function o
`f(::AbstractArray...)::Real` and return a `Tuple` where the `i`th element is the gradient
of `f` w.r.t. `input[i].`
"""
function gradient!(tape::Union{GradientTape,CompiledGradient}, input)
function gradient!(tape::Union{GradientTape, CompiledGradient}, input)
result = construct_result(input_hook(tape))
result = gradient!(result, tape, input)
return result
end

"""
ReverseDiff.gradient!(result, tape::Union{GradientTape,CompiledGradient}, input)
ReverseDiff.gradient!(result, tape::Union{GradientTape, CompiledGradient}, input)

This method is exactly like `ReverseDiff.gradient!(tape, input)`, except it stores the
resulting gradient(s) in `result` rather than allocating new memory.
Expand All @@ -77,7 +77,7 @@ case the primal value `f(input)` (or `f(input...)`, if `isa(input, Tuple)`) will
in it as well. An immutable `DiffResult` cannot be updated in place and is replaced, so use
the returned value: `result = ReverseDiff.gradient!(result, tape, input)`.
"""
function gradient!(result, tape::Union{GradientTape,CompiledGradient}, input)
function gradient!(result, tape::Union{GradientTape, CompiledGradient}, input)
seeded_forward_pass!(tape, input)
result = seeded_reverse_pass!(result, tape)
return result
Expand Down
26 changes: 15 additions & 11 deletions src/api/hessians.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,21 @@ function hessian!(result, f, input::AbstractArray, cfg::HessianConfig = HessianC
return result
end

function hessian!(result::DiffResult, f, input::AbstractArray,
cfg::HessianConfig = HessianConfig(result, input))
function hessian!(
result::DiffResult, f, input::AbstractArray,
cfg::HessianConfig = HessianConfig(result, input)
)
∇f! = (y, x) -> begin
gradient_result = DiffResult(zero(eltype(y)), y)
gradient_result = gradient!(gradient_result, f, x, cfg.gradient_config)
result = DiffResults.value!(result, value(DiffResults.value(gradient_result)))
return y
end
jacobian!(DiffResults.hessian(result), ∇f!,
DiffResults.gradient(result), input,
cfg.jacobian_config)
jacobian!(
DiffResults.hessian(result), ∇f!,
DiffResults.gradient(result), input,
cfg.jacobian_config
)
return result
end

Expand All @@ -63,21 +67,21 @@ end
##########################

"""
ReverseDiff.hessian!(tape::Union{HessianTape,CompiledHessian}, input)
ReverseDiff.hessian!(tape::Union{HessianTape, CompiledHessian}, input)

Assuming `tape` represents a function of the form `f(::AbstractArray{<:Real})::Real`,
return the Hessian `H(f)(input)`.
"""
function hessian!(tape::Union{HessianTape,CompiledHessian}, input::AbstractArray)
function hessian!(tape::Union{HessianTape, CompiledHessian}, input::AbstractArray)
result = construct_result(output_hook(tape), input_hook(tape))
result = hessian!(result, tape, input)
return result
end

"""
ReverseDiff.hessian!(result::AbstractArray, tape::Union{HessianTape,CompiledHessian}, input)
ReverseDiff.hessian!(result::AbstractArray, tape::Union{HessianTape, CompiledHessian}, input)

ReverseDiff.hessian!(result::DiffResult, tape::Union{HessianTape,CompiledHessian}, input)
ReverseDiff.hessian!(result::DiffResult, tape::Union{HessianTape, CompiledHessian}, input)

This method is exactly like `ReverseDiff.hessian!(tape, input)`, except it stores the
resulting Hessian in `result` rather than allocating new memory.
Expand All @@ -87,13 +91,13 @@ If `result` is a `DiffResults.DiffResult`, the primal value `f(input)` and the g
`DiffResult` cannot be updated in place and is replaced, so use the returned value:
`result = ReverseDiff.hessian!(result, tape, input)`.
"""
function hessian!(result::AbstractArray, tape::Union{HessianTape,CompiledHessian}, input::AbstractArray)
function hessian!(result::AbstractArray, tape::Union{HessianTape, CompiledHessian}, input::AbstractArray)
seeded_forward_pass!(tape, input)
result = seeded_reverse_pass!(result, tape)
return result
end

function hessian!(result::DiffResult, tape::Union{HessianTape,CompiledHessian}, input::AbstractArray)
function hessian!(result::DiffResult, tape::Union{HessianTape, CompiledHessian}, input::AbstractArray)
seeded_forward_pass!(tape, input)
inner = DiffResult(DiffResults.gradient(result), DiffResults.hessian(result))
inner = seeded_reverse_pass!(inner, tape)
Expand Down
8 changes: 4 additions & 4 deletions src/api/jacobians.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ end
###########################

"""
ReverseDiff.jacobian!(tape::Union{JacobianTape,CompiledJacobian}, input)
ReverseDiff.jacobian!(tape::Union{JacobianTape, CompiledJacobian}, input)

If `input` is an `AbstractArray`, assume `tape` represents a function of the form
`f(::AbstractArray{<:Real})::AbstractArray{<:Real}` or `f!(::AbstractArray{<:Real},
Expand All @@ -102,14 +102,14 @@ execute `tape` with new `input` values. There is no way to re-run `tape`'s tape
`output` values; since `f!` can mutate `output`, there exists no stable "hook" for loading
new `output` values into the tape.
"""
function jacobian!(tape::Union{JacobianTape,CompiledJacobian}, input)
function jacobian!(tape::Union{JacobianTape, CompiledJacobian}, input)
result = construct_result(output_hook(tape), input_hook(tape))
result = jacobian!(result, tape, input)
return result
end

"""
ReverseDiff.jacobian!(result, tape::Union{JacobianTape,CompiledJacobian}, input)
ReverseDiff.jacobian!(result, tape::Union{JacobianTape, CompiledJacobian}, input)

This method is exactly like `ReverseDiff.jacobian!(tape, input)`, except it stores the
resulting Jacobian(s) in `result` rather than allocating new memory.
Expand All @@ -120,7 +120,7 @@ case the primal value of the target function will be stored in it as well. An im
`DiffResult` cannot be updated in place and is replaced, so use the returned value:
`result = ReverseDiff.jacobian!(result, tape, input)`.
"""
function jacobian!(result, tape::Union{JacobianTape,CompiledJacobian}, input)
function jacobian!(result, tape::Union{JacobianTape, CompiledJacobian}, input)
seeded_forward_pass!(tape, input)
result = seeded_reverse_pass!(result, tape)
return result
Expand Down
Loading
Loading