From 5f39551848862ea7f372c37a9a4997a1ad9f388f Mon Sep 17 00:00:00 2001 From: Gabriel Gerlero Date: Mon, 18 Dec 2023 12:51:46 -0300 Subject: [PATCH] Initial release --- .github/workflows/CI.yml | 4 +++- .github/workflows/Format.yml | 8 +++++++ Project.toml | 11 +++++++++- README.md | 31 ++++++++++++++++++++++++++- docs/make.jl | 34 +++++++++++++++--------------- src/FastForwardDiff.jl | 41 +++++++++++++++++++++++++++++++++++- test/runtests.jl | 36 +++++++++++++++++++++++++++---- 7 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/Format.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bb5dde0..d54acca 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -20,7 +20,9 @@ jobs: matrix: version: - '1.6' - - '1.9' + - '1.7' + - '1.8' + - '1' - 'nightly' os: - ubuntu-latest diff --git a/.github/workflows/Format.yml b/.github/workflows/Format.yml new file mode 100644 index 0000000..dae3974 --- /dev/null +++ b/.github/workflows/Format.yml @@ -0,0 +1,8 @@ +name: Format suggestions +on: + pull_request: +jobs: + code-style: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/julia-format@v2 diff --git a/Project.toml b/Project.toml index 22f337d..4cb3386 100644 --- a/Project.toml +++ b/Project.toml @@ -1,9 +1,18 @@ name = "FastForwardDiff" uuid = "dda8b3af-db81-48ce-b77a-9f93a3049212" authors = ["Gabriel Gerlero"] -version = "1.0.0-DEV" +version = "1.0.0" + +[deps] +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] +Aqua = "0.8" +ForwardDiff = "0.10" +JET = "0.4, 0.5, 0.6, 0.7, 0.8" +Test = "1" +Unitful = "1" julia = "1.6" [extras] diff --git a/README.md b/README.md index 51e8ac8..660b9fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# FastForwardDiff +# FastForwardDiff.jl [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://gerlero.github.io/FastForwardDiff.jl/stable/) [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://gerlero.github.io/FastForwardDiff.jl/dev/) @@ -6,3 +6,32 @@ [![Coverage](https://codecov.io/gh/gerlero/FastForwardDiff.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/gerlero/FastForwardDiff.jl) [![PkgEval](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/F/FastForwardDiff.svg)](https://JuliaCI.github.io/NanosoldierReports/pkgeval_badges/F/FastForwardDiff.html) [![Aqua](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) +[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle) + +**Fast and easy derivatives of scalar functions** with forward-mode automatic differentiation. + +Compared to the scalar differentiation offered by the very popular [ForwardDiff.jl](https://github.com/JuliaDiff/ForwardDiff.jl) package, **FastForwardDiff** adds: + +- **Efficient single-pass value and derivatives** with the `value_and_derivative` and `value_and_derivatives` functions. Faster and easier than ForwardDiff's equivalent (i.e., the [DiffResults API](https://github.com/JuliaDiff/DiffResults.jl)) ✅ +- **[Unitful.jl](https://github.com/PainterQubits/Unitful.jl) support.** Correctly handles units in the inputs and outputs of functions ✅ + +Internally, it relies on the proven dual-number implementation from ForwardDiff. + +## Example + +```julia +julia> using FastForwardDiff + +julia> f(x) = x^2 + 2x + 1 + +julia> derivative(f, 3) +8 + +julia> value_and_derivative(f, 3) +(16, 8) +``` + +## Documentation + +- [**STABLE**](https://gerlero.github.io/FastForwardDiff.jl/stable/) — documentation of the most recent release +- [**DEV**](https://gerlero.github.io/FastForwardDiff.jl/dev/) — documentation of the in-development version diff --git a/docs/make.jl b/docs/make.jl index aa6f663..3cff7cd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,25 +1,25 @@ using FastForwardDiff using Documenter -DocMeta.setdocmeta!(FastForwardDiff, :DocTestSetup, :(using FastForwardDiff); recursive=true) +DocMeta.setdocmeta!(FastForwardDiff, + :DocTestSetup, + :(using FastForwardDiff); + recursive = true) makedocs(; - modules=[FastForwardDiff], - authors="Gabriel Gerlero", - repo="https://github.com/gerlero/FastForwardDiff.jl/blob/{commit}{path}#{line}", - sitename="FastForwardDiff.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://gerlero.github.io/FastForwardDiff.jl", - edit_link="main", - assets=String[], - ), - pages=[ + modules = [FastForwardDiff], + authors = "Gabriel Gerlero", + repo = "https://github.com/gerlero/FastForwardDiff.jl/blob/{commit}{path}#{line}", + sitename = "FastForwardDiff.jl", + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://gerlero.github.io/FastForwardDiff.jl", + edit_link = "main", + assets = String[],), + pages = [ "Home" => "index.md", - ], -) + ],) deploydocs(; - repo="github.com/gerlero/FastForwardDiff.jl", - devbranch="main", -) + repo = "github.com/gerlero/FastForwardDiff.jl", + devbranch = "main",) diff --git a/src/FastForwardDiff.jl b/src/FastForwardDiff.jl index a531f68..2a9e617 100644 --- a/src/FastForwardDiff.jl +++ b/src/FastForwardDiff.jl @@ -1,5 +1,44 @@ module FastForwardDiff -# Write your package code here. +using ForwardDiff: Dual, Tag, value, extract_derivative +using Unitful: unit, ustrip + +""" + derivative(f, x) -> f'(x) + +Compute the derivative of `f` at `x` using forward-mode automatic differentiation. +""" +@inline function derivative(f, x) + T = typeof(Tag(f, typeof(one(x)))) + ydual = f(Dual{T}(ustrip(x), one(x)) * unit(x)) + return extract_derivative(T, ustrip(ydual)) * unit(ydual) / unit(x) +end + +""" + value_and_derivative(f, x) -> f(x), f'(x) + +Compute the value and derivative of `f` at `x` in a single pass using forward-mode automatic differentiation. +""" +@inline function value_and_derivative(f, x) + T = typeof(Tag(f, typeof(one(x)))) + ydual = f(Dual{T}(ustrip(x), one(x)) * unit(x)) + return value(T, ustrip(ydual)) * unit(ydual), + extract_derivative(T, ustrip(ydual)) * unit(ydual) / unit(x) +end + +""" + value_and_derivatives(f, x) -> f(x), f'(x), f''(x) + +Compute the value and first and second derivatives of `f` at `x` in a single pass using forward-mode automatic differentiation. +""" +@inline function value_and_derivatives(f, x) + T = typeof(Tag(f, typeof(one(x)))) + ydual, ddual = value_and_derivative(f, Dual{T}(ustrip(x), one(x)) * unit(x)) + return value(T, ustrip(ydual)) * unit(ydual), + value(T, ustrip(ddual)) * unit(ddual), + extract_derivative(T, ustrip(ddual)) * unit(ddual) / unit(x) +end + +export derivative, value_and_derivative, value_and_derivatives end diff --git a/test/runtests.jl b/test/runtests.jl index 3f5bbba..d980b51 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,12 +3,40 @@ using Test using Aqua using JET +using Unitful + @testset "FastForwardDiff.jl" begin @testset "Code quality (Aqua.jl)" begin - Aqua.test_all(FastForwardDiff) + Aqua.test_all(FastForwardDiff, ambiguities = false) + end + + if VERSION >= v"1.8" + @testset "Code linting (JET.jl)" begin + JET.test_package(FastForwardDiff; target_defined_modules = true) + end + end + + @testset "derivatives" begin + f(x) = 3x^3 + x^2 - 4x - 2 + df(x) = 9x^2 + 2x - 4 + ddf(x) = 18x + 2 + + for x in [1, 2, 3, 4, 5] + @test (@inferred derivative(f, x)) == df(x) + @test (@inferred value_and_derivative(f, x)) == (f(x), df(x)) + @test (@inferred value_and_derivatives(f, x)) == (f(x), df(x), ddf(x)) + end end - @testset "Code linting (JET.jl)" begin - JET.test_package(FastForwardDiff; target_defined_modules = true) + + @testset "Unitful" begin + f(x) = 3x^3 * u"m/s^3" + x^2 * u"m/s^2" - 4x * u"m/s" - 2u"m" + df(x) = 9x^2 * u"m/s^3" + 2x * u"m/s^2" - 4u"m/s" + ddf(x) = 18x * u"m/s^3" + 2u"m/s^2" + + for x in [1u"s", 2u"s", 3u"s", 4u"s", 5u"s"] + @test (@inferred derivative(f, x)) == df(x) + @test (@inferred value_and_derivative(f, x)) == (f(x), df(x)) + @test (@inferred value_and_derivatives(f, x)) == (f(x), df(x), ddf(x)) + end end - # Write your tests here. end