Skip to content

Commit 057e1f0

Browse files
initial repo
1 parent fc6a193 commit 057e1f0

File tree

11 files changed

+356
-1
lines changed

11 files changed

+356
-1
lines changed

.codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
comment: false

.github/workflows/CI.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
on:
3+
pull_request:
4+
branches:
5+
- master
6+
push:
7+
branches:
8+
- master
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
group:
15+
- Core
16+
steps:
17+
- uses: actions/checkout@v2
18+
- uses: julia-actions/setup-julia@v1
19+
with:
20+
version: 1
21+
- uses: actions/cache@v1
22+
env:
23+
cache-name: cache-artifacts
24+
with:
25+
path: ~/.julia/artifacts
26+
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
27+
restore-keys: |
28+
${{ runner.os }}-test-${{ env.cache-name }}-
29+
${{ runner.os }}-test-
30+
${{ runner.os }}-
31+
- uses: julia-actions/julia-buildpkg@v1
32+
- uses: julia-actions/julia-runtest@v1
33+
env:
34+
GROUP: ${{ matrix.group }}
35+
- uses: julia-actions/julia-processcoverage@v1
36+
- uses: codecov/codecov-action@v1
37+
with:
38+
file: lcov.info

.github/workflows/CompatHelper.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CompatHelper
2+
on:
3+
schedule:
4+
- cron: '00 00 * * *'
5+
workflow_dispatch:
6+
jobs:
7+
CompatHelper:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Pkg.add("CompatHelper")
11+
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
12+
- name: CompatHelper.main()
13+
env:
14+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15+
COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}
16+
run: julia -e 'using CompatHelper; CompatHelper.main()'

.github/workflows/TagBot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: TagBot
2+
on:
3+
issue_comment:
4+
types:
5+
- created
6+
workflow_dispatch:
7+
jobs:
8+
TagBot:
9+
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: JuliaRegistries/TagBot@v1
13+
with:
14+
token: ${{ secrets.GITHUB_TOKEN }}
15+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.jl.cov
2+
*.jl.*.cov
3+
*.jl.mem
4+
*.jl.*.mem
5+
Manifest.toml
6+
.DS_Store

CITATION.bib

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@article{DifferentialEquations.jl-2017,
2+
author = {Rackauckas, Christopher and Nie, Qing},
3+
doi = {10.5334/jors.151},
4+
journal = {The Journal of Open Research Software},
5+
keywords = {Applied Mathematics},
6+
note = {Exported from https://app.dimensions.ai on 2019/05/05},
7+
number = {1},
8+
pages = {},
9+
title = {DifferentialEquations.jl – A Performant and Feature-Rich Ecosystem for Solving Differential Equations in Julia},
10+
url = {https://app.dimensions.ai/details/publication/pub.1085583166 and http://openresearchsoftware.metajnl.com/articles/10.5334/jors.151/galley/245/download/},
11+
volume = {5},
12+
year = {2017}
13+
}

LICENSE.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The PreallocationTools.jl package is licensed under the MIT "Expat" License:
2+
3+
> Copyright (c) 2016-2020: ChrisRackauckas, Julia Computing.
4+
>
5+
> Permission is hereby granted, free of charge, to any person obtaining a copy
6+
> of this software and associated documentation files (the "Software"), to deal
7+
> in the Software without restriction, including without limitation the rights
8+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
> copies of the Software, and to permit persons to whom the Software is
10+
> furnished to do so, subject to the following conditions:
11+
>
12+
> The above copyright notice and this permission notice shall be included in all
13+
> copies or substantial portions of the Software.
14+
>
15+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
> SOFTWARE.
22+
>

Project.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name = "PreallocationTools"
2+
uuid = "d236fae5-4411-538c-8e31-a6e3d9e00b46"
3+
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>"]
4+
version = "0.1.0"
5+
6+
[deps]
7+
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
8+
LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800"
9+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
10+
11+
[compat]
12+
ArrayInterface = "2.6, 3.0"
13+
ForwardDiff = "0.10.3"
14+
julia = "1.6"
15+
16+
[extras]
17+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
18+
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
19+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
20+
21+
[targets]
22+
test = ["LinearAlgebra", "OrdinaryDiffEq", "Test"]

README.md

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,126 @@
1-
PreallocationTools.jl
1+
# PreallocationTools.jl
2+
3+
[![Join the chat at https://gitter.im/JuliaDiffEq/Lobby](https://badges.gitter.im/JuliaDiffEq/Lobby.svg)](https://gitter.im/JuliaDiffEq/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4+
[![Build Status](https://github.com/SciML/PreallocationTools.jl/workflows/CI/badge.svg)](https://github.com/SciML/PreallocationTools.jl/actions?query=workflow%3ACI)
5+
6+
PreallocationTools.jl is a set of tools for helping build non-allocating
7+
pre-cached functions for high-performance computing in Julia. Its tools handle
8+
edge cases of automatic differentiation to make it easier for users to get
9+
high performance even in the cases where code generation may change the
10+
function that is being called.
11+
12+
## dualcache
13+
14+
`dualcache` is a method for generating doubly-preallocated vectors which are
15+
compatible with non-allocating forward-mode automatic differentiation by
16+
ForwardDiff.jl. Since ForwardDiff uses chunked duals in its forward pass, two
17+
vector sizes are required in order for the arrays to be properly defined.
18+
`dualcache` creates a dispatching type to solve this, so that by passing a
19+
qualifier it can automatically switch between the required cache. This method
20+
is fully type-stable and non-dynamic, made for when the highest performance is
21+
needed.
22+
23+
### Using dualcache
24+
25+
```julia
26+
dualcache(u::AbstractArray, N = Val{default_cache_size(length(u))})
27+
```
28+
29+
The `dualcache` function builds a `DualCache` object that stores both a version
30+
of the cache for `u` and for the `Dual` version of `u`, allowing use of
31+
pre-cached vectors with forward-mode automatic differentiation. To access the
32+
caches, one uses:
33+
34+
```julia
35+
get_tmp(tmp::DualCache, u)
36+
```
37+
38+
When `u` has an element subtype of `Dual` numbers, then it returns the `Dual`
39+
version of the cache. Otherwise it returns the standard cache (for use in the
40+
calls without automatic differentiation).
41+
42+
In order to preallocate to the right size, the `dualcache` needs to be specified
43+
to have the corrent `N` matching the chunk size of the dual numbers or larger.
44+
In a differential equation, optimization, etc., the default chunk size is computed
45+
from the state vector `u`, and thus if one creates the `dualcache` via
46+
`dualcache(u)` it will match the default chunking of the solver libraries.
47+
48+
### Example
49+
50+
```julia
51+
using LinearAlgebra, OrdinaryDiffEq
52+
function foo(du, u, (A, tmp), t)
53+
mul!(tmp, A, u)
54+
@. du = u + tmp
55+
nothing
56+
end
57+
prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), zeros(5,5)))
58+
solve(prob, Rosenbrock23())
59+
```
60+
61+
fails because `tmp` is only real numbers, but during automatic differentiation
62+
we need `tmp` to be a cache of dual numbers. Since `u` is the value that will
63+
have the dual numbers, we dispatch based on that:
64+
65+
```julia
66+
using LinearAlgebra, OrdinaryDiffEq, PreallocationTools
67+
function foo(du, u, (A, tmp), t)
68+
tmp = get_tmp(tmp, u)
69+
mul!(tmp, A, u)
70+
@. du = u + tmp
71+
nothing
72+
end
73+
chunk_size = 5
74+
prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), dualcache(zeros(5,5), Val{chunk_size})))
75+
solve(prob, TRBDF2(chunk_size=chunk_size))
76+
```
77+
78+
or just using the default chunking:
79+
80+
```julia
81+
using LinearAlgebra, OrdinaryDiffEq, PreallocationTools
82+
function foo(du, u, (A, tmp), t)
83+
tmp = get_tmp(tmp, u)
84+
mul!(tmp, A, u)
85+
@. du = u + tmp
86+
nothing
87+
end
88+
chunk_size = 5
89+
prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), dualcache(zeros(5,5))))
90+
solve(prob, TRBDF2())
91+
```
92+
93+
## LazyBufferCache
94+
95+
```julia
96+
LazyBufferCache(f::F=identity)
97+
```
98+
99+
A `LazyBufferCache` is a `Dict`-like type for the caches which automatically defines
100+
new cache vectors on demand when they are required. The function `f` is a length
101+
map which maps `length_of_cache = f(length(u))`, which by default creates cache
102+
vectors of the same length.
103+
104+
Note that `LazyBufferCache` does cause a dynamic dispatch, though it is type-stable.
105+
This gives it a ~100ns overhead, and thus on very small problems it can reduce
106+
performance, but for any sufficiently sized calculation (e.g. >20 ODEs) this
107+
may not be even measurable.
108+
109+
### Example
110+
111+
```julia
112+
using LinearAlgebra, OrdinaryDiffEq, PreallocationTools
113+
function foo(du, u, (A, lbc), t)
114+
tmp = lbc[u]
115+
mul!(tmp, A, u)
116+
@. du = u + tmp
117+
nothing
118+
end
119+
prob = ODEProblem(foo, ones(5, 5), (0., 1.0), (ones(5,5), LazyBufferCache()))
120+
solve(prob, TRBDF2())
121+
```
122+
123+
## Similar Projects
124+
125+
[AutoPreallocation.jl](https://github.com/oxinabox/AutoPreallocation.jl) tries
126+
to do this automatically at the compiler level.

src/PreallocationTools.jl

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
module PreallocationTools
2+
3+
using ForwardDiff, ArrayInterface, LabelledArrays
4+
5+
struct DiffCache{T<:AbstractArray, S<:AbstractArray}
6+
du::T
7+
dual_du::S
8+
end
9+
10+
function DiffCache(u::AbstractArray{T}, siz, ::Type{Val{chunk_size}}) where {T, chunk_size}
11+
x = ArrayInterface.restructure(u,zeros(ForwardDiff.Dual{nothing,T,chunk_size}, siz...))
12+
DiffCache(u, x)
13+
end
14+
15+
"""
16+
17+
`dualcache(u::AbstractArray, N = Val{default_cache_size(length(u))})`
18+
19+
Builds a `DualCache` object that stores both a version of the cache for `u`
20+
and for the `Dual` version of `u`, allowing use of pre-cached vectors with
21+
forward-mode automatic differentiation.
22+
23+
"""
24+
dualcache(u::AbstractArray, N=Val{ForwardDiff.pickchunksize(length(u))}) = DiffCache(u, size(u), N)
25+
26+
function get_tmp(dc::DiffCache, u::T) where T<:ForwardDiff.Dual
27+
x = reinterpret(T, dc.dual_du)
28+
end
29+
30+
function get_tmp(dc::DiffCache, u::AbstractArray{T}) where T<:ForwardDiff.Dual
31+
x = reinterpret(T, dc.dual_du)
32+
end
33+
34+
function get_tmp(dc::DiffCache, u::LabelledArrays.LArray{T,N,D,Syms}) where {T,N,D,Syms}
35+
x = reinterpret(T, dc.dual_du.__x)
36+
LabelledArrays.LArray{T,N,D,Syms}(x)
37+
end
38+
39+
get_tmp(dc::DiffCache, u::Number) = dc.du
40+
get_tmp(dc::DiffCache, u::AbstractArray) = dc.du
41+
42+
"""
43+
b = LazyBufferCache(f=identity)
44+
45+
A lazily allocated buffer object. Given a vector `u`, `b[u]` returns a `Vector` of the
46+
same element type and length `f(length(u))` (defaulting to the same length), which is
47+
allocated as needed and then cached within `b` for subsequent usage.
48+
"""
49+
struct LazyBufferCache{F<:Function}
50+
bufs::Dict # a dictionary mapping types to buffers
51+
lengthmap::F
52+
LazyBufferCache(f::F=identity) where {F<:Function} = new{F}(Dict()) # start with empty dict
53+
end
54+
55+
# override the [] method
56+
function Base.getindex(b::LazyBufferCache, u::AbstractArray{T}) where {T}
57+
n = b.lengthmap(size(u)) # required buffer length
58+
buf = get!(b.bufs, T) do
59+
similar(u, T, n) # buffer to allocate if it was not found in b.bufs
60+
end::typeof(u) # declare type since b.bufs dictionary is untyped
61+
# Doesn't work well with matrices, needs more thought!
62+
#return resize!(buf, n) # resize the buffer if needed, e.g. if problem was resized
63+
end
64+
65+
export dualcache, get_tmp, LazyBufferCache
66+
67+
end

0 commit comments

Comments
 (0)