Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Merge pull request #101 from invenia/aa/1.0
Browse files Browse the repository at this point in the history
Updates for Julia 0.7/1.0
  • Loading branch information
rofinn committed Oct 23, 2018
2 parents 1219789 + 7badedc commit 0a5195a
Show file tree
Hide file tree
Showing 43 changed files with 655 additions and 546 deletions.
17 changes: 12 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ os:
- linux
- osx
julia:
- 0.6
- 0.7
- 1.0
- nightly
notifications:
email: false
Expand All @@ -18,7 +19,13 @@ matrix:
# - julia -e 'Pkg.clone(pwd()); Pkg.build("Nabla"); Pkg.test("Nabla"; coverage=true)'
after_success:
# push coverage results to Codecov
- julia -e 'cd(Pkg.dir("Nabla")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'
# build documentation
- julia -e 'Pkg.add("Documenter")'
- julia -e 'cd(Pkg.dir("Nabla")); include(joinpath("docs", "make.jl"))'
- julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'
jobs:
include:
- stage: "Documentation"
julia: 1.0
os: linux
script:
- julia --project=docs/ -e 'using Pkg; Pkg.instantiate(); Pkg.develop(PackageSpec(path=pwd()))'
- julia --project=docs/ docs/make.jl
after_success: skip
26 changes: 26 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name = "Nabla"
uuid = "49c96f43-aa6d-5a04-a506-44c7070ebe78"
version = "0.2.0"

[deps]
DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
DualNumbers = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"
FDM = "e25cca7e-83ef-51fa-be6c-dfe2a3123128"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"

[compat]
julia = "0.7, 1.0"
DiffRules = "0.0"
DualNumbers = "0.6.0"
FDM = "0.1.0, 0.2.0"
SpecialFunctions = ">=0.5.0"

[extras]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["BenchmarkTools", "Distributions", "Random", "Test"]
6 changes: 3 additions & 3 deletions REQUIRE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
julia 0.6
DualNumbers 0.3.0
julia 0.7
DualNumbers 0.6.0
DiffRules 0.0.1
FDM 0.1.0
SpecialFunctions 0.3.0
SpecialFunctions 0.5.0
42 changes: 19 additions & 23 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
environment:
matrix:
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
- julia_version: 0.7
- julia_version: 1
- julia_version: nightly

platform:
- x86 # 32-bit
- x64 # 64-bit

matrix:
allow_failures:
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
- julia_version: nightly

branches:
only:
Expand All @@ -22,24 +24,18 @@ notifications:
on_build_status_changed: false

install:
- ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12"
# If there's a newer build queued for the same PR, cancel this one
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." }
# Download most recent Julia Windows binary
- ps: (new-object net.webclient).DownloadFile(
$env:JULIA_URL,
"C:\projects\julia-binary.exe")
# Run installer silently, output to C:\projects\julia
- C:\projects\julia-binary.exe /S /D=C:\projects\julia
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))

build_script:
# Need to convert from shallow to complete for Pkg.clone to work
- IF EXIST .git\shallow (git fetch --unshallow)
- C:\projects\julia\bin\julia -e "versioninfo();
Pkg.clone(pwd(), \"Nabla\"); Pkg.build(\"Nabla\")"
- echo "%JL_BUILD_SCRIPT%"
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"

test_script:
- C:\projects\julia\bin\julia -e "Pkg.test(\"Nabla\")"
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"

# # Uncomment to support code coverage upload. Should only be enabled for packages
# # which would have coverage gaps without running on Windows
# on_success:
# - echo "%JL_CODECOV_SCRIPT%"
# - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"
5 changes: 5 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

[compat]
Documenter = "~0.19"
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ makedocs(

deploydocs(
repo = "github.com/invenia/Nabla.jl.git",
julia = "0.6",
julia = "1.0",
target = "build",
deps = nothing,
make = nothing,
Expand Down
4 changes: 2 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ x, y = randn.(rng, [N, N])
A = randn(rng, N, N)
# Construct a vector-quadratic function in `x` and `y`.
f(x, y) = y.' * (A * x)
f(x, y) = y' * (A * x)
f(x, y)
```

Only a small amount of [matrix calculus](https://en.wikipedia.org/wiki/Matrix_calculus) is required to the find the gradient of `f(x, y)` w.r.t. `x` and `y`, which we denote by `∇x` and `∇y` respectively, to be

```@example toy
(∇x, ∇y) = (A.'y, A * x)
(∇x, ∇y) = (A'y, A * x)
```

## High-Level Interface
Expand Down
12 changes: 12 additions & 0 deletions src/Nabla.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ __precompile__()

module Nabla

using SpecialFunctions
using LinearAlgebra

# Some aliases used repeatedly throughout the package.
export ∇Scalar, ∇Array, SymOrExpr, ∇ArrayOrScalar
const ∇Scalar = Number
Expand All @@ -11,6 +14,15 @@ module Nabla
const ∇ArrayOrScalar = Union{AbstractArray{<:∇Scalar}, ∇Scalar}
const SymOrExpr = Union{Symbol, Expr}

# ones/zeros(::AbstractArray) is deprecated in 0.7 and removed in 1.0, but it's a
# pretty useful method, so we'll define our own for internal use
for f in (:ones, :zeros)
like = Symbol(f, "like")
@eval begin
$(like)(a::AbstractArray) = $(f)(eltype(a), size(a))
$(like)(n::Integer) = $(f)(n)
end
end

# Meta-programming utilities specific to Nabla.
include("code_transformation/util.jl")
Expand Down
20 changes: 10 additions & 10 deletions src/code_transformation/differentiable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ unionise_arg(arg::Expr) =
Expr(Symbol("::"), arg.args[1:end-1]..., unionise_type(arg.args[end])) :
arg.head == Symbol("...") ?
Expr(Symbol("..."), unionise_arg(arg.args[1])) :
throw(error("Unrecognised argument in Symbol ($arg)."))
throw(ArgumentError("Unrecognised argument in Symbol ($arg)."))

"""
unionise_subtype(arg::Union{Symbol, Expr})
Expand All @@ -23,7 +23,7 @@ unionise_subtype(arg::Symbol) = arg
unionise_subtype(arg::Expr) =
arg.head == Symbol("<:") ?
Expr(Symbol("<:"), arg.args[1:end-1]..., unionise_type(arg.args[end])) :
throw(error("Unrecognised argument in arg ($arg)."))
throw(ArgumentError("Unrecognised argument in arg ($arg)."))

"""
get_quote_body(code)
Expand All @@ -42,7 +42,7 @@ Unionise the code inside a call to `eval`, such that when the `eval` call actual
the code inside will be unionised.
"""
function unionise_eval(code::Expr)
body = Expr(:macrocall, Symbol("@unionise"), deepcopy(get_quote_body(code.args[end])))
body = Expr(:macrocall, Symbol("@unionise"), nothing, deepcopy(get_quote_body(code.args[end])))
return length(code.args) == 3 ?
Expr(:call, :eval, deepcopy(code.args[2]), quot(body)) :
Expr(:call, :eval, quot(body))
Expand All @@ -55,10 +55,10 @@ Unionise the code in a call to @eval, such that when the `eval` call actually oc
code inside will be unionised.
"""
function unionise_macro_eval(code::Expr)
body = Expr(:macrocall, Symbol("@unionise"), deepcopy(code.args[end]))
return length(code.args) == 3 ?
Expr(:macrocall, Symbol("@eval"), deepcopy(code.args[2]), body) :
Expr(:macrocall, Symbol("@eval"), body)
body = Expr(:macrocall, Symbol("@unionise"), nothing, deepcopy(code.args[end]))
return length(code.args) == 4 ?
Expr(:macrocall, Symbol("@eval"), nothing, deepcopy(code.args[3]), body) :
Expr(:macrocall, Symbol("@eval"), nothing, body)
end

"""
Expand Down Expand Up @@ -95,13 +95,13 @@ function unionise_struct(code::Expr)
curly = Expr(:curly, name.args[1], unionise_subtype.(name.args[2:end])...)
if is_subtype_expr
return Expr(
:type,
:struct,
code.args[1],
Expr(Symbol("<:"), curly, tmp.args[2]),
code.args[3],
)
else
return Expr(:type, code.args[1], curly, code.args[3])
return Expr(:struct, code.args[1], curly, code.args[3])
end
else
return code
Expand Down Expand Up @@ -131,7 +131,7 @@ function unionise(code::Expr)
return unionise_eval(code)
elseif code.head == :macrocall && code.args[1] == Symbol("@eval")
return unionise_macro_eval(code)
elseif code.head == :type
elseif code.head == :struct
return unionise_struct(code)
else
return Expr(code.head, [unionise(arg) for arg in code.args]...)
Expand Down
25 changes: 22 additions & 3 deletions src/code_transformation/util.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
function get_union_call(foo::Symbol, type_tuple::Expr)
function add_kwargs!(ex::Expr; kwargs...)
ex.head === :call || throw(ArgumentError("expression is not a function call"))
isempty(ex.args) && throw(ArgumentError("expression body is empty"))
if !isempty(kwargs)
params = Expr(:parameters)
for (name, value) in kwargs
push!(params.args, Expr(:kw, name, value))
end
# Parameters need to come after the function name and before positional arguments
if length(ex.args) == 1
push!(ex.args, params)
else
insert!(ex.args, 2, params)
end
end
ex
end

function get_union_call(foo::Symbol, type_tuple::Expr; kwargs...)
# Get types from tuple and create a collection of symbols for use in the call.
types = get_types(get_body(type_tuple))
arg_names = [Symbol("x$j") for j in 1:length(types)]

# Generate the call.
typed_args = [:($name::$typ) for (name, typ) in zip(arg_names, unionise_type.(types))]
return replace_body(type_tuple, Expr(:call, foo, typed_args...)), arg_names
typed_args = map((name, typ) -> :($name::$(unionise_type(typ))), arg_names, types)
call = add_kwargs!(Expr(:call, foo, typed_args...); kwargs...)

return replace_body(type_tuple, call), arg_names
end

"""
Expand Down
43 changes: 21 additions & 22 deletions src/core.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using DualNumbers

import Base: push!, length, show, getindex, setindex!, endof, eachindex, isassigned,
isapprox, zero, one
import Base: push!, length, show, getindex, setindex!, eachindex, isassigned,
isapprox, zero, one, lastindex

export Leaf, Tape, Node, Branch, ∇

""" Basic unit on the computational graph."""
abstract type Node{T} end

""" A topologically ordered collection of Nodes. """
immutable Tape
struct Tape
tape::Vector{Any}
Tape() = new(Vector{Any}())
Tape(N::Int) = new(Vector{Any}(N))
Tape(N::Int) = new(Vector{Any}(undef, N))
end
function show(io::IO, tape::Tape)
if length(tape) == 0
Expand All @@ -24,14 +25,17 @@ function show(io::IO, tape::Tape)
end
@inline getindex(tape::Tape, n::Int) = Base.getindex(tape.tape, n)
@inline getindex(tape::Tape, node::Node) = Base.getindex(tape, node.pos)
@inline endof(tape::Tape) = length(tape)
@inline lastindex(tape::Tape) = length(tape)
@inline setindex!(tape::Tape, x, n::Int) = (tape.tape[n] = x; tape)
@inline eachindex(tape::Tape) = eachindex(tape.tape)
@inline length(tape::Tape) = length(tape.tape)
@inline push!(tape::Tape, node::Node) = (push!(tape.tape, node); tape)
@inline isassigned(tape::Tape, n::Int) = isassigned(tape.tape, n)
@inline isassigned(tape::Tape, node::Node) = isassigned(tape, node.pos)

# Make `Tape`s broadcast as scalars without a warning on 0.7
Base.Broadcast.broadcastable(tape::Tape) = Ref(tape)

"""
An element at the 'bottom' of the computational graph.
Expand Down Expand Up @@ -63,16 +67,16 @@ args - Values indicating which elements in the tape will require updating by thi
tape - The Tape to which this Branch is assigned.
pos - the location of this Branch in the tape to which it is assigned.
"""
immutable Branch{T} <: Node{T}
struct Branch{T} <: Node{T}
val::T
f
args::Tuple
tape::Tape
pos::Int
end
function Branch(f, args::Tuple, tape::Tape)
function Branch(f, args::Tuple, tape::Tape; kwargs...)
unboxed = unbox.(args)
branch = Branch(f(unboxed...), f, args, tape, length(tape) + 1)
branch = Branch(f(unboxed...; kwargs...), f, args, tape, length(tape) + 1)
push!(tape, branch)
return branch
end
Expand All @@ -99,7 +103,7 @@ Get `.val` if `x` is a Node, otherwise is equivalent to `identity`.
unbox(x::Node) = x.val
unbox(x) = x

isapprox(n::Node, f) = Nabla.unbox(n) f
isapprox(n::Node, f) = unbox(n) f
isapprox(f, n::Node) = n f

zero(n::Node) = zero(unbox(n))
Expand Down Expand Up @@ -177,8 +181,8 @@ function ∇(f, get_output::Bool=false)
y isa Node || return zero.(args)
∇f = (y)
∇args = ([isassigned(∇f, arg_) ? ∇f[arg_] : zero(arg)
for (arg_, arg) in zip(args_, args)]...)
return get_output ? (y, ∇args) : ∇args
for (arg_, arg) in zip(args_, args)]...,)
return get_output ? (y, ∇args) : ∇args
end
end

Expand All @@ -205,16 +209,16 @@ end
# A collection of methods for initialising nested indexable containers to zero.
for (f_name, scalar_init, array_init) in
zip((:zerod_container, :oned_container, :randned_container),
(:zero, :one, Nullable()),
(:zeros, :ones, Nullable()))
if !isnull(scalar_init)
(:zero, :one, nothing),
(:zeros, :ones, nothing))
if scalar_init !== nothing
@eval @inline $f_name(x::Number) = $scalar_init(x)
end
if !isnull(array_init)
@eval @inline $f_name(x::AbstractArray{<:Real}) = $array_init(x)
if array_init !== nothing
@eval @inline $f_name(x::AbstractArray{<:Real}) = $array_init(eltype(x), size(x))
end
eval(quote
@inline $f_name(x::Tuple) = ([$f_name(n) for n in x]...)
@inline $f_name(x::Tuple) = map($f_name, x)
@inline function $f_name(x)
y = Base.copy(x)
for n in eachindex(y)
Expand Down Expand Up @@ -250,8 +254,3 @@ function fmad_expr(f, x::Type{<:Tuple})
return body
end
@generated fmad(f, x) = fmad_expr(f, x)

function Base.exp10(x::Dual)
y = exp10(DualNumbers.value(x))
return Dual(y, y * log(10) * DualNumbers.epsilon(x))
end

0 comments on commit 0a5195a

Please sign in to comment.