Skip to content

Commit

Permalink
New Constructor for adding fields. Closes #23.
Browse files Browse the repository at this point in the history
  • Loading branch information
jonniedie committed Jun 24, 2020
1 parent eb97a86 commit 5918c59
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ research/
/docs/site/
/docs/make_local.jl
/examples/wip
/examples/.ipynb_checkpoints
/examples/.ipynb_checkpoints
.vscode/settings.json
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ComponentArrays"
uuid = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
authors = ["Jonnie Diegelman <[email protected]>"]
version = "0.4.5"
version = "0.5.0"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
45 changes: 16 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,47 +28,34 @@ in [DifferentialEquations.jl](https://github.com/SciML/DifferentialEquations.jl)
flat vectors is fair game.

## New Features!
### v0.4.0
- Zygote rules for DiffEqFlux support! Still experimental though!
### v0.5.0
- Constructor for making new `ComponentVector`s with additional fields! Watch out, it's slow!
```julia
using ComponentArrays, UnPack
using Flux: glorot_uniform

dense_layer(in, out) = ComponentArray(W=glorot_uniform(out, in), b=zeros(out))
julia> x = ComponentArray(a=5, b=[1, 2])
ComponentVector{Int64}(a = 5, b = [1, 2])

layers = (L1=dense_layer(2, 50), L2=dense_layer(50, 2))
θ = ComponentArray(u=u0, p=layers)

function dudt(u, p, t)
@unpack L1, L2 = p
return L2.W * tanh.(L1.W * u .+ L1.b) .+ L2.b
end
julia> moar_x = ComponentArray(x; c=zeros(2,2), d=(a=2, b=10))
ComponentVector{Int64}(a = 5, b = [1, 2], c = [0 0; 0 0], d = (a = 2, b = 10))
```
Check out [the docs](https://jonniedie.github.io/ComponentArrays.jl/dev/examples/DiffEqFlux/) for a complete example.

### v0.4.0
- Zygote rules for DiffEqFlux support! Check out [the docs](https://jonniedie.github.io/ComponentArrays.jl/dev/examples/DiffEqFlux/) for an example!

### v0.3.0
- Matrix and higher-dimensional array components!
```julia
julia> x = ComponentArray(a=rand(), b=rand(3), c=rand(3,3));

julia> x.c
3×3 reshape(view(::Array{Float64,1}, 5:13), 3, 3) with eltype Float64:
0.508171 0.740476 0.730907
0.112437 0.0329141 0.943972
0.661702 0.760624 0.777929
```
...and plenty more!

## General use
The easiest way to construct 1-dimensional ```ComponentArray```s is as if they were ```NamedTuple```s. In fact, a good way to think about them is as arbitrarily nested, mutable ```NamedTuple```s that can be passed through a solver.
The easiest way to construct 1-dimensional ```ComponentArray```s (aliased as `ComponentVector`) is as if they were ```NamedTuple```s. In fact, a good way to think about them is as arbitrarily nested, mutable ```NamedTuple```s that can be passed through a solver.
```julia
julia> c = (a=2, b=[1, 2]);

julia> x = ComponentArray(a=5, b=[(a=20., b=0), (a=33., b=0), (a=44., b=3)], c=c)
ComponentArray{Float64}(a = 5.0, b = [(a = 20.0, b = 0.0), (a = 33.0, b = 0.0), (a = 44.0, b = 3.0)], c = (a = 2.0, b = [1.0, 2.0]))
ComponentVector{Float64}(a = 5.0, b = [(a = 20.0, b = 0.0), (a = 33.0, b = 0.0), (a = 44.0, b = 3.0)], c = (a = 2.0, b = [1.0, 2.0]))

julia> x.c.a = 400; x
ComponentArray{Float64}(a = 5.0, b = [(a = 20.0, b = 0.0), (a = 33.0, b = 0.0), (a = 44.0, b = 3.0)], c = (a = 400.0, b = [1.0, 2.0]))
ComponentVector{Float64}(a = 5.0, b = [(a = 20.0, b = 0.0), (a = 33.0, b = 0.0), (a = 44.0, b = 3.0)], c = (a = 400.0, b = [1.0, 2.0]))

julia> x[8]
400.0
Expand All @@ -86,14 +73,14 @@ julia> collect(x)
1.0
2.0

julia> typeof(similar(x, Int32)) === typeof(ComponentArray{Int32}(a=5, b=[(a=20., b=0), (a=33., b=0), (a=44., b=3)], c=c))
julia> typeof(similar(x, Int32)) === typeof(ComponentVector{Int32}(a=5, b=[(a=20., b=0), (a=33., b=0), (a=44., b=3)], c=c))
true
```

Higher dimensional ```ComponentArray```s can be created too, but it's a little messy at the moment. The nice thing for modeling is that dimension expansion through broadcasted operations can create higher-dimensional ```ComponentArray```s automatically, so Jacobian cache arrays that are created internally with ```false .* x .* x'``` will be ```ComponentArray```s with proper axes. Check out the [ODE with Jacobian](https://github.com/jonniedie/ComponentArrays.jl/blob/master/examples/ODE_jac_example.jl) example in the examples folder to see how this looks in practice.
Higher dimensional ```ComponentArray```s can be created too, but it's a little messy at the moment. The nice thing for modeling is that dimension expansion through broadcasted operations can create higher-dimensional ```ComponentArray```s automatically, so Jacobian cache arrays that are created internally with ```false .* x .* x'``` will be two-dimensional ```ComponentArray```s (aliased as `ComponentMatrix`) with proper axes. Check out the [ODE with Jacobian](https://github.com/jonniedie/ComponentArrays.jl/blob/master/examples/ODE_jac_example.jl) example in the examples folder to see how this looks in practice.
```julia
julia> x = ComponentArray(a=1, b=[2, 1, 4], c=c)
ComponentArray{Float64}(a = 1.0, b = [2.0, 1.0, 4.0], c = (a = 2.0, b = [1.0, 2.0]))
ComponentVector{Float64}(a = 1.0, b = [2.0, 1.0, 4.0], c = (a = 2.0, b = [1.0, 2.0]))

julia> x2 = x .* x'
7×7 ComponentArray{Tuple{Axis{(a = 1, b = 2:4, c = (5:7, (a = 1, b = 2:3)))},Axis{(a = 1, b = 2:4, c = (5:7, (a = 1, b = 2:3)))}},Float64,2,Array{Float64,2}}:
Expand All @@ -115,7 +102,7 @@ julia> x2[:a,:a]
1.0

julia> x2[:a,:c]
ComponentArray{Float64}(a = 2.0, b = [1.0, 2.0])
ComponentVector{Float64}(a = 2.0, b = [1.0, 2.0])

julia> x2[:b,:c]
3×3 ComponentArray{Tuple{Axis{NamedTuple()},Axis{(a = 1, b = 2:3)}},Float64,2,SubArray{Float64,2,Array{Float64,2},Tuple{UnitRange{Int64},UnitRange{Int64}},false}}:
Expand Down
9 changes: 4 additions & 5 deletions src/axis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ abstract type AbstractAxis{IdxMap} end
@inline indexmap(::AbstractAxis{IdxMap}) where IdxMap = IdxMap
@inline indexmap(::Type{<:AbstractAxis{IdxMap}}) where IdxMap = IdxMap

Base.propertynames(::AbstractAxis{IdxMap}) where IdxMap = propertynames(IdxMap)


struct FlatAxis <: AbstractAxis{NamedTuple()} end

Expand Down Expand Up @@ -129,8 +127,9 @@ Axis(::Number) = NullAxis()
Axis(::NamedTuple{()}) = FlatAxis()
Axis(x) = FlatAxis()



const NotShapedAxis = Union{Axis{IdxMap}, FlatAxis, NullAxis} where {IdxMap}
const NotPartitionedAxis = Union{Axis{IdxMap}, FlatAxis, NullAxis, ShapedAxis{Shape, IdxMap}} where {Shape, IdxMap}
const NotShapedOrPartitionedAxis = Union{Axis{IdxMap}, FlatAxis, NullAxis} where {IdxMap}
const NotShapedOrPartitionedAxis = Union{Axis{IdxMap}, FlatAxis, NullAxis} where {IdxMap}


Base.merge(axs::Axis...) = Axis(merge(indexmap.(axs)...))
31 changes: 30 additions & 1 deletion src/componentarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ ComponentVector{T}(::UndefInitializer, ax) where {T} = ComponentArray{T}(undef,
ComponentVector(data::AbstractVector, ax) = ComponentArray(data, ax)
ComponentVector(data::AbstractArray, ax) = throw(DimensionMismatch("A `ComponentVector` must be initialized with a 1-dimensional array. This array is $(ndims(data))-dimensional."))

# Add new fields to component Vector
function ComponentArray(x::ComponentVector; kwargs...)
return foldl((x1, kwarg) -> _maybe_add_field(x1, kwarg), (kwargs...,); init=x)
end
ComponentVector(x::ComponentVector; kwargs...) = ComponentArray(x; kwargs...)


"""
x = ComponentMatrix(data::AbstractMatrix, ax...)
x = ComponentMatrix{T}(data::AbstractMatrix, ax...) where T
Expand Down Expand Up @@ -123,6 +130,10 @@ function make_idx(data, nt::NamedTuple, last_val)
end
return (data, ViewAxis(last_index(last_val) .+ (1:len), (;kvs...)))
end
function make_idx(data, pair::Pair, last_val)
data, ax = make_idx(data, pair.second, last_val)
return (data, ViewAxis(last_val:(last_val+len-1), Axis(pair.second)))
end
make_idx(data, x, last_val) = (
push!(data, x),
ViewAxis(last_index(last_val) + 1)
Expand Down Expand Up @@ -161,6 +172,22 @@ function make_idx(data, x::A, last_val) where A<:AbstractArray
end
end


#TODO: Make all internal function names start with underscores
_maybe_add_field(x, pair) = haskey(x, pair.first) ? _update_field(x, pair) : _add_field(x, pair)
function _add_field(x, pair)
data = copy(getdata(x))
new_data, new_ax = make_idx(data, pair.second, length(data))
new_ax = Axis(NamedTuple{tuple(pair.first)}(tuple(new_ax)))
new_ax = merge(getaxes(x)[1], new_ax)
return ComponentArray(new_data, new_ax)
end
function _update_field(x, pair)
x_copy = copy(x)
x_copy[pair.first] = pair.second
return x_copy
end

pushcat!(a, b) = reduce((x1,x2) -> push!(x1,x2), b; init=a)

# Reshape ComponentArrays with ShapedAxis axes
Expand Down Expand Up @@ -234,8 +261,10 @@ Base.size(x::ComponentArray) = size(getdata(x))

Base.reinterpret(::Type{T}, x::ComponentArray, args...) where T = ComponentArray(reinterpret(T, getdata(x), args...), getaxes(x))

Base.propertynames(x::ComponentVector) = propertynames(getaxes(x)[1])
Base.propertynames(x::ComponentVector) = propertynames(indexmap(getaxes(x)[1]))

Base.keys(x::ComponentVector) = keys(indexmap(getaxes(x)[1]))

Base.haskey(x::ComponentVector, s::Symbol) = haskey(indexmap(getaxes(x)[1]), s)

Base.IndexStyle(::Type{<:ComponentArray{T,N,<:A,<:Axes}}) where {T,N,A,Axes} = IndexStyle(A)
11 changes: 11 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ end
# Issue #24
@test ComponentVector(a=1, b=2f0) == ComponentVector{Float32}(a = 1.0, b = 2.0)
@test ComponentVector(a=1, b=2+im) == ComponentVector{Complex{Int64}}(a = 1 + 0im, b = 2 + 1im)

# Issue #23
sz = size(ca)
temp = ComponentArray(ca; d=100)
temp2 = ComponentVector(temp; d=4)
temp3 = ComponentArray(temp2; e=(a=20, b=[2 4; 1 4]))
@test sz == size(ca)
@test temp.d == 100
@test temp2.d == 4
@test !haskey(ca, :d)
@test all(temp3.e.b .== [2 4; 1 4])
end

@testset "Attributes" begin
Expand Down

2 comments on commit 5918c59

@jonniedie
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/16850

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.0 -m "<description of version>" 5918c59a7118ad9ccb4c8228450595c50a7b6aa6
git push origin v0.5.0

Please sign in to comment.